diff --git a/debian/.gitignore b/debian/.gitignore index 7f43aa6ed98..24e62d94b96 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -12,6 +12,7 @@ /openvswitch-controller /openvswitch-datapath-source /openvswitch-dbg +/openvswitch-ipsec /openvswitch-pki /openvswitch-pki-server /openvswitch-switch diff --git a/debian/automake.mk b/debian/automake.mk index c768d56b56b..20432062a2c 100644 --- a/debian/automake.mk +++ b/debian/automake.mk @@ -24,6 +24,9 @@ EXTRA_DIST += \ debian/openvswitch-datapath-source.copyright \ debian/openvswitch-datapath-source.dirs \ debian/openvswitch-datapath-source.install \ + debian/openvswitch-ipsec.dirs \ + debian/openvswitch-ipsec.init \ + debian/openvswitch-ipsec.install \ debian/openvswitch-pki-server.apache2 \ debian/openvswitch-pki-server.dirs \ debian/openvswitch-pki-server.install \ @@ -39,6 +42,7 @@ EXTRA_DIST += \ debian/openvswitch-switch.postrm \ debian/openvswitch-switch.template \ debian/ovs-bugtool \ + debian/ovs-monitor-ipsec \ debian/python-openvswitch.dirs \ debian/python-openvswitch.install \ debian/rules \ diff --git a/debian/control b/debian/control index 53e5b98d667..622daeb3bf8 100644 --- a/debian/control +++ b/debian/control @@ -41,6 +41,19 @@ Description: Open vSwitch switch implementations . Open vSwitch is a full-featured software-based Ethernet switch. +Package: openvswitch-ipsec +Architecture: any +Depends: + ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, ipsec-tools, racoon, + openvswitch-common (= ${binary:Version}), + openvswitch-switch (= ${binary:Version}), + python-openvswitch (= ${binary:Version}) +Description: Open vSwitch GRE-over-IPsec support + The ovs-monitor-ipsec script provides support for encrypting GRE + tunnels with IPsec. + . + Open vSwitch is a full-featured software-based Ethernet switch. + Package: openvswitch-pki Architecture: all Depends: @@ -90,13 +103,14 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${binary:Version}), openvswitch-controller (= ${binary:Version}), + openvswitch-ipsec (= ${binary:Version}), openvswitch-switch (= ${binary:Version}) Description: Debug symbols for Open vSwitch packages This package contains the debug symbols for all the other openvswitch-* packages. Install it to debug one of them or to examine a core dump produced by one of them. -Package: python-openvswitch +Package: python-openvswitch Architecture: all Section: python Depends: ${python:Depends}, openvswitch-switch (= ${binary:Version}) diff --git a/debian/openvswitch-ipsec.dirs b/debian/openvswitch-ipsec.dirs new file mode 100644 index 00000000000..02130d0e9c2 --- /dev/null +++ b/debian/openvswitch-ipsec.dirs @@ -0,0 +1 @@ +usr/share/openvswitch/scripts diff --git a/debian/openvswitch-ipsec.init b/debian/openvswitch-ipsec.init new file mode 100755 index 00000000000..f3c9a13a089 --- /dev/null +++ b/debian/openvswitch-ipsec.init @@ -0,0 +1,184 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino +# +# This is free software; you may redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, +# or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License with +# the Debian operating system, in /usr/share/common-licenses/GPL; if +# not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# +### BEGIN INIT INFO +# Provides: openvswitch-ipsec +# Required-Start: $network $local_fs $remote_fs +# Required-Stop: $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Open vSwitch GRE-over-IPsec daemon +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +DAEMON=/usr/share/openvswitch/scripts/ovs-monitor-ipsec # Daemon's location +NAME=ovs-monitor-ipsec # Introduce the short server's name here +LOGDIR=/var/log/openvswitch # Log directory to use + +PIDFILE=/var/run/openvswitch/$NAME.pid + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +DODTIME=10 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +set -e + +running_pid() { +# Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" " "|cut -d " " -f 2` + # Is this the expected server + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() { +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +start_server() { + PYTHONPATH=/usr/share/openvswitch/python \ + /usr/share/openvswitch/scripts/ovs-monitor-ipsec \ + --pidfile-name=$PIDFILE --detach --monitor \ + unix:/var/run/openvswitch/db.sock + + return 0 +} + +stop_server() { + if [ -e $PIDFILE ]; then + kill `cat $PIDFILE` + fi + + return 0 +} + +force_stop() { +# Force the process to die killing it manually + [ ! -e "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + sleep "$DIETIME"s + if running ; then + kill -9 $pid + sleep "$DIETIME"s + if running ; then + echo "Cannot kill $NAME (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE +} + + +case "$1" in + start) + log_daemon_msg "Starting $NAME" + # Check if it's running first + if running ; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if start_server && running ; then + # It's ok, the server started and is running + log_end_msg 0 + else + # Either we could not start it or it is not running + # after we did + # NOTE: Some servers might die some time after they start, + # this code does not try to detect this and might give + # a false positive (use 'status' for that) + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping $NAME" + if running ; then + # Only stop the server if we see it running + stop_server + log_end_msg $? + else + # If it's not running don't do anything + log_progress_msg "apparently not running" + log_end_msg 0 + exit 0 + fi + ;; + force-stop) + # First try to stop gracefully the program + $0 stop + if running; then + # If it's still running try to kill it more forcefully + log_daemon_msg "Stopping (force) $NAME" + force_stop + log_end_msg $? + fi + ;; + restart|force-reload) + log_daemon_msg "Restarting $NAME" + stop_server + # Wait some sensible amount, some server need this + [ -n "$DIETIME" ] && sleep $DIETIME + start_server + running + log_end_msg $? + ;; + status) + log_daemon_msg "Checking status of $NAME" + if running ; then + log_progress_msg "running" + log_end_msg 0 + else + log_progress_msg "apparently not running" + log_end_msg 1 + exit 1 + fi + ;; + # Use this if the daemon cannot reload + reload) + log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" + log_warning_msg "cannot re-read the config file (use restart)." + ;; + *) + N=/etc/init.d/openvswitch-ipsec + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/openvswitch-ipsec.install b/debian/openvswitch-ipsec.install new file mode 100644 index 00000000000..72cacfa2530 --- /dev/null +++ b/debian/openvswitch-ipsec.install @@ -0,0 +1 @@ +debian/ovs-monitor-ipsec usr/share/openvswitch/scripts diff --git a/debian/ovs-monitor-ipsec b/debian/ovs-monitor-ipsec new file mode 100755 index 00000000000..1caece3a91f --- /dev/null +++ b/debian/ovs-monitor-ipsec @@ -0,0 +1,349 @@ +#!/usr/bin/python +# Copyright (c) 2009, 2010 Nicira Networks +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# A daemon to monitor attempts to create GRE-over-IPsec tunnels. +# Uses racoon and setkey to support the configuration. Assumes that +# OVS has complete control over IPsec configuration for the box. + +# xxx To-do: +# - Doesn't actually check that Interface is connected to bridge +# - Doesn't support cert authentication + + +import getopt +import logging, logging.handlers +import os +import stat +import subprocess +import sys + +from ovs.db import error +from ovs.db import types +import ovs.util +import ovs.daemon +import ovs.db.idl + + +# By default log messages as DAEMON into syslog +s_log = logging.getLogger("ovs-monitor-ipsec") +l_handler = logging.handlers.SysLogHandler( + "/dev/log", + facility=logging.handlers.SysLogHandler.LOG_DAEMON) +l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s') +l_handler.setFormatter(l_formatter) +s_log.addHandler(l_handler) + + +setkey = "/usr/sbin/setkey" + +# Class to configure the racoon daemon, which handles IKE negotiation +class Racoon: + # Default locations for files + conf_file = "/etc/racoon/racoon.conf" + cert_file = "/etc/racoon/certs" + psk_file = "/etc/racoon/psk.txt" + + # Default racoon configuration file we use for IKE + conf_template = """# Configuration file generated by Open vSwitch +# +# Do not modify by hand! + +path pre_shared_key "/etc/racoon/psk.txt"; +path certificate "/etc/racoon/certs"; + +remote anonymous { + exchange_mode main; + proposal { + encryption_algorithm aes; + hash_algorithm sha1; + authentication_method pre_shared_key; + dh_group 2; + } +} + +sainfo anonymous { + pfs_group 2; + lifetime time 1 hour; + encryption_algorithm aes; + authentication_algorithm hmac_sha1, hmac_md5; + compression_algorithm deflate; +} +""" + + def __init__(self): + self.psk_hosts = {} + self.cert_hosts = {} + + # Replace racoon's conf file with our template + f = open(Racoon.conf_file, "w") + f.write(Racoon.conf_template) + f.close() + + # Clear out any pre-shared keys + self.commit_psk() + + self.reload() + + def reload(self): + exitcode = subprocess.call(["/etc/init.d/racoon", "reload"]) + if exitcode != 0: + s_log.warning("couldn't reload racoon") + + def commit_psk(self): + f = open(Racoon.psk_file, 'w') + + # The file must only be accessible by root + os.chmod(Racoon.psk_file, stat.S_IRUSR | stat.S_IWUSR) + + f.write("# Generated by Open vSwitch...do not modify by hand!\n\n") + for host, psk in self.psk_hosts.iteritems(): + f.write("%s %s\n" % (host, psk)) + f.close() + + def add_psk(self, host, psk): + self.psk_hosts[host] = psk + self.commit_psk() + + def del_psk(self, host): + if host in self.psk_hosts: + del self.psk_hosts[host] + self.commit_psk() + + +# Class to configure IPsec on a system using racoon for IKE and setkey +# for maintaining the Security Association Database (SAD) and Security +# Policy Database (SPD). Only policies for GRE are supported. +class IPsec: + def __init__(self): + self.sad_flush() + self.spd_flush() + self.racoon = Racoon() + + def call_setkey(self, cmds): + try: + p = subprocess.Popen([setkey, "-c"], stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + except: + s_log.error("could not call setkey") + sys.exit(1) + + # xxx It is safer to pass the string into the communicate() + # xxx method, but it didn't work for slightly longer commands. + # xxx An alternative may need to be found. + p.stdin.write(cmds) + return p.communicate()[0] + + def get_spi(self, local_ip, remote_ip, proto="esp"): + # Run the setkey dump command to retrieve the SAD. Then, parse + # the output looking for SPI buried in the output. Note that + # multiple SAD entries can exist for the same "flow", since an + # older entry could be in a "dying" state. + spi_list = [] + host_line = "%s %s" % (local_ip, remote_ip) + results = self.call_setkey("dump ;").split("\n") + for i in range(len(results)): + if results[i].strip() == host_line: + # The SPI is in the line following the host pair + spi_line = results[i+1] + if (spi_line[1:4] == proto): + spi = spi_line.split()[2] + spi_list.append(spi.split('(')[1].rstrip(')')) + return spi_list + + def sad_flush(self): + self.call_setkey("flush;") + + def sad_del(self, local_ip, remote_ip): + # To delete all SAD entries, we should be able to use setkey's + # "deleteall" command. Unfortunately, it's fundamentally broken + # on Linux and not documented as such. + cmds = "" + + # Delete local_ip->remote_ip SAD entries + spi_list = self.get_spi(local_ip, remote_ip) + for spi in spi_list: + cmds += "delete %s %s esp %s;\n" % (local_ip, remote_ip, spi) + + # Delete remote_ip->local_ip SAD entries + spi_list = self.get_spi(remote_ip, local_ip) + for spi in spi_list: + cmds += "delete %s %s esp %s;\n" % (remote_ip, local_ip, spi) + + if cmds: + self.call_setkey(cmds) + + def spd_flush(self): + self.call_setkey("spdflush;") + + def spd_add(self, local_ip, remote_ip): + cmds = ("spdadd %s %s gre -P out ipsec esp/transport//default;" % + (local_ip, remote_ip)) + cmds += "\n" + cmds += ("spdadd %s %s gre -P in ipsec esp/transport//default;" % + (remote_ip, local_ip)) + self.call_setkey(cmds) + + def spd_del(self, local_ip, remote_ip): + cmds = "spddelete %s %s gre -P out;" % (local_ip, remote_ip) + cmds += "\n" + cmds += "spddelete %s %s gre -P in;" % (remote_ip, local_ip) + self.call_setkey(cmds) + + def ipsec_cert_del(self, local_ip, remote_ip): + # Need to support cert...right now only PSK supported + self.racoon.del_psk(remote_ip) + self.spd_del(local_ip, remote_ip) + self.sad_del(local_ip, remote_ip) + + def ipsec_cert_update(self, local_ip, remote_ip, cert): + # Need to support cert...right now only PSK supported + self.racoon.add_psk(remote_ip, "abc12345") + self.spd_add(local_ip, remote_ip) + + def ipsec_psk_del(self, local_ip, remote_ip): + self.racoon.del_psk(remote_ip) + self.spd_del(local_ip, remote_ip) + self.sad_del(local_ip, remote_ip) + + def ipsec_psk_update(self, local_ip, remote_ip, psk): + self.racoon.add_psk(remote_ip, psk) + self.spd_add(local_ip, remote_ip) + + +def keep_table_columns(schema, table_name, column_types): + table = schema.tables.get(table_name) + if not table: + raise error.Error("schema has no %s table" % table_name) + + new_columns = {} + for column_name, column_type in column_types.iteritems(): + column = table.columns.get(column_name) + if not column: + raise error.Error("%s table schema lacks %s column" + % (table_name, column_name)) + if column.type != column_type: + raise error.Error("%s column in %s table has type \"%s\", " + "expected type \"%s\"" + % (column_name, table_name, + column.type.toEnglish(), + column_type.toEnglish())) + new_columns[column_name] = column + table.columns = new_columns + return table + +def monitor_uuid_schema_cb(schema): + string_type = types.Type(types.BaseType(types.StringType)) + string_map_type = types.Type(types.BaseType(types.StringType), + types.BaseType(types.StringType), + 0, sys.maxint) + + new_tables = {} + new_tables["Interface"] = keep_table_columns( + schema, "Interface", {"name": string_type, + "type": string_type, + "options": string_map_type, + "other_config": string_map_type}) + schema.tables = new_tables + +def usage(): + print "usage: %s [OPTIONS] DATABASE" % sys.argv[0] + print "where DATABASE is a socket on which ovsdb-server is listening." + ovs.daemon.usage() + print "Other options:" + print " -h, --help display this help message" + sys.exit(0) + +def main(argv): + try: + options, args = getopt.gnu_getopt( + argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS) + except getopt.GetoptError, geo: + sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg)) + sys.exit(1) + + for key, value in options: + if key in ['-h', '--help']: + usage() + elif not ovs.daemon.parse_opt(key, value): + sys.stderr.write("%s: unhandled option %s\n" + % (ovs.util.PROGRAM_NAME, key)) + sys.exit(1) + + if len(args) != 1: + sys.stderr.write("%s: exactly one nonoption argument is required " + "(use --help for help)\n" % ovs.util.PROGRAM_NAME) + sys.exit(1) + + ovs.daemon.die_if_already_running() + + remote = args[0] + idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb) + + ovs.daemon.daemonize() + + ipsec = IPsec() + + interfaces = {} + while True: + if not idl.run(): + poller = ovs.poller.Poller() + idl.wait(poller) + poller.block() + continue + + new_interfaces = {} + for rec in idl.data["Interface"].itervalues(): + name = rec.name.as_scalar() + local_ip = rec.other_config.get("ipsec_local_ip") + if rec.type.as_scalar() == "gre" and local_ip: + new_interfaces[name] = { + "remote_ip": rec.options.get("remote_ip"), + "local_ip": local_ip, + "ipsec_cert": rec.other_config.get("ipsec_cert"), + "ipsec_psk": rec.other_config.get("ipsec_psk") } + + if interfaces != new_interfaces: + for name, vals in interfaces.items(): + if name not in new_interfaces.keys(): + ipsec.ipsec_cert_del(vals["local_ip"], vals["remote_ip"]) + for name, vals in new_interfaces.items(): + if vals == interfaces.get(name): + s_log.warning( + "configuration changed for %s, need to delete " + "interface first" % name) + continue + + if vals["ipsec_cert"]: + ipsec.ipsec_cert_update(vals["local_ip"], + vals["remote_ip"], vals["ipsec_cert"]) + elif vals["ipsec_psk"]: + ipsec.ipsec_psk_update(vals["local_ip"], + vals["remote_ip"], vals["ipsec_psk"]) + else: + s_log.warning( + "no ipsec_cert or ipsec_psk defined for %s" % name) + continue + + interfaces = new_interfaces + +if __name__ == '__main__': + try: + main(sys.argv) + except SystemExit: + # Let system.exit() calls complete normally + raise + except: + s_log.exception("traceback") diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema index a7d25703609..07dd79fbc3a 100644 --- a/vswitchd/vswitch.ovsschema +++ b/vswitchd/vswitch.ovsschema @@ -134,6 +134,8 @@ "ofport": { "type": {"key": "integer", "min": 0, "max": 1}, "ephemeral": true}, + "other_config": { + "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "statistics": { "type": {"key": "string", "value": "integer", "min": 0, "max": "unlimited"}, "ephemeral": true}, diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index 86fd3f9be81..5b5655ddd36 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -775,6 +775,27 @@ + + Key-value pairs for rarely used interface features. Currently, + the only keys are for configuring GRE-over-IPsec, which is only + available through the openvswitch-ipsec package for + Debian. The currently defined key-value pairs are: +
+
ipsec-local-ip
+
Required key for GRE-over-IPsec interfaces. Additionally, + the must be gre and the + ipsec-psk key must + be set. The in_key, out_key, and + key must not be + set.
+
ipsec-psk
+
Required key for GRE-over-IPsec interfaces. Specifies a + pre-shared key for authentication that must be identical on + both sides of the tunnel. Additionally, the + ipsec-local-ip key must also be set.
+
+
+

Key-value pairs that report interface statistics. The current