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";
70 encryption_algorithm aes;
72 authentication_method pre_shared_key;
80 encryption_algorithm aes;
81 authentication_algorithm hmac_sha1, hmac_md5;
82 compression_algorithm deflate;
90 # Replace racoon's conf file with our template
91 f = open(Racoon.conf_file, "w")
92 f.write(Racoon.conf_template)
95 # Clear out any pre-shared keys
101 exitcode = subprocess.call(["/etc/init.d/racoon", "reload"])
103 s_log.warning("couldn't reload racoon")
105 def commit_psk(self):
106 f = open(Racoon.psk_file, 'w')
108 # The file must only be accessible by root
109 os.chmod(Racoon.psk_file, stat.S_IRUSR | stat.S_IWUSR)
111 f.write("# Generated by Open vSwitch...do not modify by hand!\n\n")
112 for host, psk in self.psk_hosts.iteritems():
113 f.write("%s %s\n" % (host, psk))
116 def add_psk(self, host, psk):
117 self.psk_hosts[host] = psk
120 def del_psk(self, host):
121 if host in self.psk_hosts:
122 del self.psk_hosts[host]
126 # Class to configure IPsec on a system using racoon for IKE and setkey
127 # for maintaining the Security Association Database (SAD) and Security
128 # Policy Database (SPD). Only policies for GRE are supported.
133 self.racoon = Racoon()
135 def call_setkey(self, cmds):
137 p = subprocess.Popen([setkey, "-c"], stdin=subprocess.PIPE,
138 stdout=subprocess.PIPE)
140 s_log.error("could not call setkey")
143 # xxx It is safer to pass the string into the communicate()
144 # xxx method, but it didn't work for slightly longer commands.
145 # xxx An alternative may need to be found.
147 return p.communicate()[0]
149 def get_spi(self, local_ip, remote_ip, proto="esp"):
150 # Run the setkey dump command to retrieve the SAD. Then, parse
151 # the output looking for SPI buried in the output. Note that
152 # multiple SAD entries can exist for the same "flow", since an
153 # older entry could be in a "dying" state.
155 host_line = "%s %s" % (local_ip, remote_ip)
156 results = self.call_setkey("dump ;").split("\n")
157 for i in range(len(results)):
158 if results[i].strip() == host_line:
159 # The SPI is in the line following the host pair
160 spi_line = results[i+1]
161 if (spi_line[1:4] == proto):
162 spi = spi_line.split()[2]
163 spi_list.append(spi.split('(')[1].rstrip(')'))
167 self.call_setkey("flush;")
169 def sad_del(self, local_ip, remote_ip):
170 # To delete all SAD entries, we should be able to use setkey's
171 # "deleteall" command. Unfortunately, it's fundamentally broken
172 # on Linux and not documented as such.
175 # Delete local_ip->remote_ip SAD entries
176 spi_list = self.get_spi(local_ip, remote_ip)
178 cmds += "delete %s %s esp %s;\n" % (local_ip, remote_ip, spi)
180 # Delete remote_ip->local_ip SAD entries
181 spi_list = self.get_spi(remote_ip, local_ip)
183 cmds += "delete %s %s esp %s;\n" % (remote_ip, local_ip, spi)
186 self.call_setkey(cmds)
189 self.call_setkey("spdflush;")
191 def spd_add(self, local_ip, remote_ip):
192 cmds = ("spdadd %s %s gre -P out ipsec esp/transport//default;" %
193 (local_ip, remote_ip))
195 cmds += ("spdadd %s %s gre -P in ipsec esp/transport//default;" %
196 (remote_ip, local_ip))
197 self.call_setkey(cmds)
199 def spd_del(self, local_ip, remote_ip):
200 cmds = "spddelete %s %s gre -P out;" % (local_ip, remote_ip)
202 cmds += "spddelete %s %s gre -P in;" % (remote_ip, local_ip)
203 self.call_setkey(cmds)
205 def ipsec_cert_del(self, local_ip, remote_ip):
206 # Need to support cert...right now only PSK supported
207 self.racoon.del_psk(remote_ip)
208 self.spd_del(local_ip, remote_ip)
209 self.sad_del(local_ip, remote_ip)
211 def ipsec_cert_update(self, local_ip, remote_ip, cert):
212 # Need to support cert...right now only PSK supported
213 self.racoon.add_psk(remote_ip, "abc12345")
214 self.spd_add(local_ip, remote_ip)
216 def ipsec_psk_del(self, local_ip, remote_ip):
217 self.racoon.del_psk(remote_ip)
218 self.spd_del(local_ip, remote_ip)
219 self.sad_del(local_ip, remote_ip)
221 def ipsec_psk_update(self, local_ip, remote_ip, psk):
222 self.racoon.add_psk(remote_ip, psk)
223 self.spd_add(local_ip, remote_ip)
226 def keep_table_columns(schema, table_name, column_types):
227 table = schema.tables.get(table_name)
229 raise error.Error("schema has no %s table" % table_name)
232 for column_name, column_type in column_types.iteritems():
233 column = table.columns.get(column_name)
235 raise error.Error("%s table schema lacks %s column"
236 % (table_name, column_name))
237 if column.type != column_type:
238 raise error.Error("%s column in %s table has type \"%s\", "
239 "expected type \"%s\""
240 % (column_name, table_name,
241 column.type.toEnglish(),
242 column_type.toEnglish()))
243 new_columns[column_name] = column
244 table.columns = new_columns
247 def monitor_uuid_schema_cb(schema):
248 string_type = types.Type(types.BaseType(types.StringType))
249 string_map_type = types.Type(types.BaseType(types.StringType),
250 types.BaseType(types.StringType),
254 new_tables["Interface"] = keep_table_columns(
255 schema, "Interface", {"name": string_type,
257 "options": string_map_type,
258 "other_config": string_map_type})
259 schema.tables = new_tables
262 print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
263 print "where DATABASE is a socket on which ovsdb-server is listening."
265 print "Other options:"
266 print " -h, --help display this help message"
271 options, args = getopt.gnu_getopt(
272 argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
273 except getopt.GetoptError, geo:
274 sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
277 for key, value in options:
278 if key in ['-h', '--help']:
280 elif not ovs.daemon.parse_opt(key, value):
281 sys.stderr.write("%s: unhandled option %s\n"
282 % (ovs.util.PROGRAM_NAME, key))
286 sys.stderr.write("%s: exactly one nonoption argument is required "
287 "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
290 ovs.daemon.die_if_already_running()
293 idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
295 ovs.daemon.daemonize()
302 poller = ovs.poller.Poller()
308 for rec in idl.data["Interface"].itervalues():
309 name = rec.name.as_scalar()
310 local_ip = rec.other_config.get("ipsec_local_ip")
311 if rec.type.as_scalar() == "gre" and local_ip:
312 new_interfaces[name] = {
313 "remote_ip": rec.options.get("remote_ip"),
314 "local_ip": local_ip,
315 "ipsec_cert": rec.other_config.get("ipsec_cert"),
316 "ipsec_psk": rec.other_config.get("ipsec_psk") }
318 if interfaces != new_interfaces:
319 for name, vals in interfaces.items():
320 if name not in new_interfaces.keys():
321 ipsec.ipsec_cert_del(vals["local_ip"], vals["remote_ip"])
322 for name, vals in new_interfaces.items():
323 if vals == interfaces.get(name):
325 "configuration changed for %s, need to delete "
326 "interface first" % name)
329 if vals["ipsec_cert"]:
330 ipsec.ipsec_cert_update(vals["local_ip"],
331 vals["remote_ip"], vals["ipsec_cert"])
332 elif vals["ipsec_psk"]:
333 ipsec.ipsec_psk_update(vals["local_ip"],
334 vals["remote_ip"], vals["ipsec_psk"])
337 "no ipsec_cert or ipsec_psk defined for %s" % name)
340 interfaces = new_interfaces
342 if __name__ == '__main__':
346 # Let system.exit() calls complete normally
349 s_log.exception("traceback")
350 sys.exit(ovs.daemon.RESTART_EXIT_CODE)