--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino <jfs@debian.org>
+#
+# 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
--- /dev/null
+#!/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")