2 # Copyright (c) 2009, 2010 Nicira Networks
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 # A daemon to monitor attempts to create GRE-over-IPsec tunnels.
18 # Uses racoon and setkey to support the configuration. Assumes that
19 # OVS has complete control over IPsec configuration for the box.
22 # - Doesn't actually check that Interface is connected to bridge
23 # - Doesn't support cert authentication
27 import logging, logging.handlers
33 from ovs.db import error
34 from ovs.db import types
40 # By default log messages as DAEMON into syslog
41 s_log = logging.getLogger("ovs-monitor-ipsec")
42 l_handler = logging.handlers.SysLogHandler(
44 facility=logging.handlers.SysLogHandler.LOG_DAEMON)
45 l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
46 l_handler.setFormatter(l_formatter)
47 s_log.addHandler(l_handler)
50 setkey = "/usr/sbin/setkey"
52 # Class to configure the racoon daemon, which handles IKE negotiation
54 # Default locations for files
55 conf_file = "/etc/racoon/racoon.conf"
56 cert_file = "/etc/racoon/certs"
57 psk_file = "/etc/racoon/psk.txt"
59 # Default racoon configuration file we use for IKE
60 conf_template = """# Configuration file generated by Open vSwitch
62 # Do not modify by hand!
64 path pre_shared_key "/etc/racoon/psk.txt";
65 path certificate "/etc/racoon/certs";
71 encryption_algorithm aes;
73 authentication_method pre_shared_key;
81 encryption_algorithm aes;
82 authentication_algorithm hmac_sha1, hmac_md5;
83 compression_algorithm deflate;
91 # Replace racoon's conf file with our template
92 f = open(Racoon.conf_file, "w")
93 f.write(Racoon.conf_template)
96 # Clear out any pre-shared keys
102 exitcode = subprocess.call(["/etc/init.d/racoon", "reload"])
104 s_log.warning("couldn't reload racoon")
106 def commit_psk(self):
107 f = open(Racoon.psk_file, 'w')
109 # The file must only be accessible by root
110 os.chmod(Racoon.psk_file, stat.S_IRUSR | stat.S_IWUSR)
112 f.write("# Generated by Open vSwitch...do not modify by hand!\n\n")
113 for host, psk in self.psk_hosts.iteritems():
114 f.write("%s %s\n" % (host, psk))
117 def add_psk(self, host, psk):
118 self.psk_hosts[host] = psk
121 def del_psk(self, host):
122 if host in self.psk_hosts:
123 del self.psk_hosts[host]
127 # Class to configure IPsec on a system using racoon for IKE and setkey
128 # for maintaining the Security Association Database (SAD) and Security
129 # Policy Database (SPD). Only policies for GRE are supported.
134 self.racoon = Racoon()
136 def call_setkey(self, cmds):
138 p = subprocess.Popen([setkey, "-c"], stdin=subprocess.PIPE,
139 stdout=subprocess.PIPE)
141 s_log.error("could not call setkey")
144 # xxx It is safer to pass the string into the communicate()
145 # xxx method, but it didn't work for slightly longer commands.
146 # xxx An alternative may need to be found.
148 return p.communicate()[0]
150 def get_spi(self, local_ip, remote_ip, proto="esp"):
151 # Run the setkey dump command to retrieve the SAD. Then, parse
152 # the output looking for SPI buried in the output. Note that
153 # multiple SAD entries can exist for the same "flow", since an
154 # older entry could be in a "dying" state.
156 host_line = "%s %s" % (local_ip, remote_ip)
157 results = self.call_setkey("dump ;").split("\n")
158 for i in range(len(results)):
159 if results[i].strip() == host_line:
160 # The SPI is in the line following the host pair
161 spi_line = results[i+1]
162 if (spi_line[1:4] == proto):
163 spi = spi_line.split()[2]
164 spi_list.append(spi.split('(')[1].rstrip(')'))
168 self.call_setkey("flush;")
170 def sad_del(self, local_ip, remote_ip):
171 # To delete all SAD entries, we should be able to use setkey's
172 # "deleteall" command. Unfortunately, it's fundamentally broken
173 # on Linux and not documented as such.
176 # Delete local_ip->remote_ip SAD entries
177 spi_list = self.get_spi(local_ip, remote_ip)
179 cmds += "delete %s %s esp %s;\n" % (local_ip, remote_ip, spi)
181 # Delete remote_ip->local_ip SAD entries
182 spi_list = self.get_spi(remote_ip, local_ip)
184 cmds += "delete %s %s esp %s;\n" % (remote_ip, local_ip, spi)
187 self.call_setkey(cmds)
190 self.call_setkey("spdflush;")
192 def spd_add(self, local_ip, remote_ip):
193 cmds = ("spdadd %s %s gre -P out ipsec esp/transport//default;" %
194 (local_ip, remote_ip))
196 cmds += ("spdadd %s %s gre -P in ipsec esp/transport//default;" %
197 (remote_ip, local_ip))
198 self.call_setkey(cmds)
200 def spd_del(self, local_ip, remote_ip):
201 cmds = "spddelete %s %s gre -P out;" % (local_ip, remote_ip)
203 cmds += "spddelete %s %s gre -P in;" % (remote_ip, local_ip)
204 self.call_setkey(cmds)
206 def ipsec_cert_del(self, local_ip, remote_ip):
207 # Need to support cert...right now only PSK supported
208 self.racoon.del_psk(remote_ip)
209 self.spd_del(local_ip, remote_ip)
210 self.sad_del(local_ip, remote_ip)
212 def ipsec_cert_update(self, local_ip, remote_ip, cert):
213 # Need to support cert...right now only PSK supported
214 self.racoon.add_psk(remote_ip, "abc12345")
215 self.spd_add(local_ip, remote_ip)
217 def ipsec_psk_del(self, local_ip, remote_ip):
218 self.racoon.del_psk(remote_ip)
219 self.spd_del(local_ip, remote_ip)
220 self.sad_del(local_ip, remote_ip)
222 def ipsec_psk_update(self, local_ip, remote_ip, psk):
223 self.racoon.add_psk(remote_ip, psk)
224 self.spd_add(local_ip, remote_ip)
227 def keep_table_columns(schema, table_name, column_types):
228 table = schema.tables.get(table_name)
230 raise error.Error("schema has no %s table" % table_name)
233 for column_name, column_type in column_types.iteritems():
234 column = table.columns.get(column_name)
236 raise error.Error("%s table schema lacks %s column"
237 % (table_name, column_name))
238 if column.type != column_type:
239 raise error.Error("%s column in %s table has type \"%s\", "
240 "expected type \"%s\""
241 % (column_name, table_name,
242 column.type.toEnglish(),
243 column_type.toEnglish()))
244 new_columns[column_name] = column
245 table.columns = new_columns
248 def monitor_uuid_schema_cb(schema):
249 string_type = types.Type(types.BaseType(types.StringType))
250 string_map_type = types.Type(types.BaseType(types.StringType),
251 types.BaseType(types.StringType),
255 new_tables["Interface"] = keep_table_columns(
256 schema, "Interface", {"name": string_type,
258 "options": string_map_type,
259 "other_config": string_map_type})
260 schema.tables = new_tables
263 print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
264 print "where DATABASE is a socket on which ovsdb-server is listening."
266 print "Other options:"
267 print " -h, --help display this help message"
272 options, args = getopt.gnu_getopt(
273 argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
274 except getopt.GetoptError, geo:
275 sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
278 for key, value in options:
279 if key in ['-h', '--help']:
281 elif not ovs.daemon.parse_opt(key, value):
282 sys.stderr.write("%s: unhandled option %s\n"
283 % (ovs.util.PROGRAM_NAME, key))
287 sys.stderr.write("%s: exactly one nonoption argument is required "
288 "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
291 ovs.daemon.die_if_already_running()
294 idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
296 ovs.daemon.daemonize()
303 poller = ovs.poller.Poller()
309 for rec in idl.data["Interface"].itervalues():
310 name = rec.name.as_scalar()
311 ipsec_cert = rec.other_config.get("ipsec_cert")
312 ipsec_psk = rec.other_config.get("ipsec_psk")
313 is_ipsec = ipsec_cert or ipsec_psk
315 if rec.type.as_scalar() == "gre" and is_ipsec:
316 new_interfaces[name] = {
317 "remote_ip": rec.options.get("remote_ip"),
318 "local_ip": rec.options.get("local_ip", "0.0.0.0/0"),
319 "ipsec_cert": ipsec_cert,
320 "ipsec_psk": ipsec_psk }
322 if interfaces != new_interfaces:
323 for name, vals in interfaces.items():
324 if name not in new_interfaces.keys():
325 ipsec.ipsec_cert_del(vals["local_ip"], vals["remote_ip"])
326 for name, vals in new_interfaces.items():
327 if vals == interfaces.get(name):
329 "configuration changed for %s, need to delete "
330 "interface first" % name)
333 if vals["ipsec_cert"]:
334 ipsec.ipsec_cert_update(vals["local_ip"],
335 vals["remote_ip"], vals["ipsec_cert"])
336 elif vals["ipsec_psk"]:
337 ipsec.ipsec_psk_update(vals["local_ip"],
338 vals["remote_ip"], vals["ipsec_psk"])
341 "no ipsec_cert or ipsec_psk defined for %s" % name)
344 interfaces = new_interfaces
346 if __name__ == '__main__':
350 # Let system.exit() calls complete normally
353 s_log.exception("traceback")
354 sys.exit(ovs.daemon.RESTART_EXIT_CODE)