]> git.proxmox.com Git - mirror_ovs.git/commitdiff
ipsec: Use @PYTHON@ directly instead of "/usr/bin/env python"
authorTimothy Redaelli <tredaelli@redhat.com>
Sat, 10 Nov 2018 15:29:07 +0000 (16:29 +0100)
committerBen Pfaff <blp@ovn.org>
Sat, 10 Nov 2018 16:15:28 +0000 (08:15 -0800)
Using "/usr/bin/env" is against Fedora Packaging Guidelines [1].

Moreover, in this specific case, it also prevent "make rpm-fedora" to
successfully complete on "Fedora Rawhide" since "#!/usr/bin/env python"
must not be used anymore [2].

[1] https://fedoraproject.org/wiki/Packaging:Guidelines#Shebang_lines
[2] https://fedoraproject.org/wiki/Changes/Make_ambiguous_python_shebangs_error

CC: Qiuyu Xiao <qiuyu.xiao.qyx@gmail.com>
Fixes: 22c5eafb6efa ("ipsec: reintroduce IPsec support for tunneling")
Signed-off-by: Timothy Redaelli <tredaelli@redhat.com>
Signed-off-by: Ben Pfaff <blp@ovn.org>
configure.ac
ipsec/automake.mk
ipsec/ovs-monitor-ipsec [deleted file]
ipsec/ovs-monitor-ipsec.in [new file with mode: 0755]

index 2b3800310231cc60b744e09357df6485c360ca5a..bb726f89e25128e6573b618c04edcf7237484c4d 100644 (file)
@@ -208,6 +208,7 @@ AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp])
 AC_CONFIG_COMMANDS([utilities/bugtool/dummy], [:])
 AC_CONFIG_COMMANDS([ovn/dummy], [:])
 AC_CONFIG_COMMANDS([ovn/utilities/dummy], [:])
+AC_CONFIG_COMMANDS([ipsec/dummy], [:])
 
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
 
index 1e530cb42fe5702861f8e8abf016478ccbd525bc..66515e8f853b8306eb51e7427df7bd06405b1a7a 100644 (file)
@@ -5,6 +5,6 @@
 # notice and this notice are preserved.  This file is offered as-is,
 # without warranty of any kind.
 
-EXTRA_DIST += \
-        ipsec/ovs-monitor-ipsec
-FLAKE8_PYFILES += ipsec/ovs-monitor-ipsec
+bin_SCRIPTS += ipsec/ovs-monitor-ipsec
+EXTRA_DIST += ipsec/ovs-monitor-ipsec.in
+FLAKE8_PYFILES += ipsec/ovs-monitor-ipsec.in
diff --git a/ipsec/ovs-monitor-ipsec b/ipsec/ovs-monitor-ipsec
deleted file mode 100755 (executable)
index c93081d..0000000
+++ /dev/null
@@ -1,1235 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2017 Nicira, Inc.
-#
-# 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.
-
-import argparse
-import re
-import subprocess
-import sys
-import copy
-import os
-from string import Template
-
-import ovs.daemon
-import ovs.db.idl
-import ovs.dirs
-import ovs.unixctl
-import ovs.unixctl.server
-import ovs.util
-import ovs.vlog
-
-
-FILE_HEADER = "# Generated by ovs-monitor-ipsec...do not modify by hand!\n\n"
-transp_tmpl = {"gre": Template("""\
-conn $ifname-$version
-$auth_section
-    leftprotoport=gre
-    rightprotoport=gre
-
-"""), "gre64": Template("""\
-conn $ifname-$version
-$auth_section
-    leftprotoport=gre
-    rightprotoport=gre
-
-"""), "geneve": Template("""\
-conn $ifname-in-$version
-$auth_section
-    leftprotoport=udp/6081
-    rightprotoport=udp
-
-conn $ifname-out-$version
-$auth_section
-    leftprotoport=udp
-    rightprotoport=udp/6081
-
-"""), "stt": Template("""\
-conn $ifname-in-$version
-$auth_section
-    leftprotoport=tcp/7471
-    rightprotoport=tcp
-
-conn $ifname-out-$version
-$auth_section
-    leftprotoport=tcp
-    rightprotoport=tcp/7471
-
-"""), "vxlan": Template("""\
-conn $ifname-in-$version
-$auth_section
-    leftprotoport=udp/4789
-    rightprotoport=udp
-
-conn $ifname-out-$version
-$auth_section
-    leftprotoport=udp
-    rightprotoport=udp/4789
-
-""")}
-vlog = ovs.vlog.Vlog("ovs-monitor-ipsec")
-exiting = False
-monitor = None
-xfrm = None
-
-
-class XFRM(object):
-    """This class is a simple wrapper around ip-xfrm (8) command line
-    utility.  We are using this class only for informational purposes
-    so that ovs-monitor-ipsec could verify that IKE keying daemon has
-    installed IPsec policies and security associations into kernel as
-    expected."""
-
-    def __init__(self, ip_root_prefix):
-        self.IP = ip_root_prefix + "/sbin/ip"
-
-    def get_policies(self):
-        """This function returns IPsec policies (from kernel) in a dictionary
-        where <key> is destination IPv4 address and <value> is SELECTOR of
-        the IPsec policy."""
-        policies = {}
-        proc = subprocess.Popen([self.IP, 'xfrm', 'policy'],
-                                stdout=subprocess.PIPE)
-        while True:
-            line = proc.stdout.readline().strip()
-            if line == '':
-                break
-            a = line.split(" ")
-            if len(a) >= 4 and a[0] == "src" and a[2] == "dst":
-                dst = (a[3].split("/"))[0]
-                if dst not in policies:
-                    policies[dst] = []
-                policies[dst].append(line)
-                src = (a[3].split("/"))[0]
-                if src not in policies:
-                    policies[src] = []
-                policies[src].append(line)
-        return policies
-
-    def get_securities(self):
-        """This function returns IPsec security associations (from kernel)
-        in a dictionary where <key> is destination IPv4 address and <value>
-        is SELECTOR."""
-        securities = {}
-        proc = subprocess.Popen([self.IP, 'xfrm', 'state'],
-                                stdout=subprocess.PIPE)
-        while True:
-            line = proc.stdout.readline().strip()
-            if line == '':
-                break
-            a = line.split(" ")
-            if len(a) >= 4 and a[0] == "sel" \
-                    and a[1] == "src" and a[3] == "dst":
-                remote_ip = a[4].rstrip().split("/")[0]
-                local_ip = a[2].rstrip().split("/")[0]
-                if remote_ip not in securities:
-                    securities[remote_ip] = []
-                securities[remote_ip].append(line)
-                if local_ip not in securities:
-                    securities[local_ip] = []
-                securities[local_ip].append(line)
-        return securities
-
-
-class StrongSwanHelper(object):
-    """This class does StrongSwan specific configurations."""
-
-    STRONGSWAN_CONF = """%s
-charon.plugins.kernel-netlink.set_proto_port_transport_sa = yes
-charon.plugins.kernel-netlink.xfrm_ack_expires = 10
-charon.load_modular = yes
-charon.plugins.gcm.load = yes
-""" % (FILE_HEADER)
-
-    CONF_HEADER = """%s
-config setup
-    uniqueids=yes
-
-conn %%default
-    keyingtries=%%forever
-    type=transport
-    keyexchange=ikev2
-    auto=route
-    ike=aes256gcm16-sha256-modp2048
-    esp=aes256gcm16-modp2048
-
-""" % (FILE_HEADER)
-
-    CA_SECTION = """ca ca_auth
-    cacert=%s
-
-"""
-
-    SHUNT_POLICY = """conn prevent_unencrypted_gre
-    type=drop
-    leftprotoport=gre
-    mark={0}
-
-conn prevent_unencrypted_geneve
-    type=drop
-    leftprotoport=udp/6081
-    mark={0}
-
-conn prevent_unencrypted_stt
-    type=drop
-    leftprotoport=tcp/7471
-    mark={0}
-
-conn prevent_unencrypted_vxlan
-    type=drop
-    leftprotoport=udp/4789
-    mark={0}
-
-"""
-
-    auth_tmpl = {"psk": Template("""\
-    left=0.0.0.0
-    right=$remote_ip
-    authby=psk"""),
-                 "pki_remote": Template("""\
-    left=0.0.0.0
-    right=$remote_ip
-    leftid=$local_name
-    rightid=$remote_name
-    leftcert=$certificate
-    rightcert=$remote_cert"""),
-                 "pki_ca": Template("""\
-    left=0.0.0.0
-    right=$remote_ip
-    leftid=$local_name
-    rightid=$remote_name
-    leftcert=$certificate""")}
-
-    def __init__(self, root_prefix):
-        self.CHARON_CONF = root_prefix + "/etc/strongswan.d/ovs.conf"
-        self.IPSEC = root_prefix + "/usr/sbin/ipsec"
-        self.IPSEC_CONF = root_prefix + "/etc/ipsec.conf"
-        self.IPSEC_SECRETS = root_prefix + "/etc/ipsec.secrets"
-        self.conf_file = None
-        self.secrets_file = None
-
-    def restart_ike_daemon(self):
-        """This function restarts StrongSwan."""
-        f = open(self.CHARON_CONF, "w")
-        f.write(self.STRONGSWAN_CONF)
-        f.close()
-
-        f = open(self.IPSEC_CONF, "w")
-        f.write(self.CONF_HEADER)
-        f.close()
-
-        f = open(self.IPSEC_SECRETS, "w")
-        f.write(FILE_HEADER)
-        f.close()
-
-        vlog.info("Restarting StrongSwan")
-        subprocess.call([self.IPSEC, "restart"])
-
-    def get_active_conns(self):
-        """This function parses output from 'ipsec status' command.
-        It returns dictionary where <key> is interface name (as in OVSDB)
-        and <value> is another dictionary.  This another dictionary
-        uses strongSwan connection name as <key> and more detailed
-        sample line from the parsed outpus as <value>. """
-
-        conns = {}
-        proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE)
-
-        while True:
-            line = proc.stdout.readline().strip()
-            if line == '':
-                break
-            tunnel_name = line.split(":")
-            if len(tunnel_name) < 2:
-                continue
-            m = re.match(r"(.*)(-in-\d+|-out-\d+|-\d+).*", tunnel_name[0])
-            if not m:
-                continue
-            ifname = m.group(1)
-            if ifname not in conns:
-                conns[ifname] = {}
-            (conns[ifname])[tunnel_name[0]] = line
-
-        return conns
-
-    def config_init(self):
-        self.conf_file = open(self.IPSEC_CONF, "w")
-        self.secrets_file = open(self.IPSEC_SECRETS, "w")
-        self.conf_file.write(self.CONF_HEADER)
-        self.secrets_file.write(FILE_HEADER)
-
-    def config_global(self, monitor):
-        """Configure the global state of IPsec tunnels."""
-        needs_refresh = False
-
-        if monitor.conf_in_use != monitor.conf:
-            monitor.conf_in_use = copy.deepcopy(monitor.conf)
-            needs_refresh = True
-
-        # Configure the shunt policy
-        if monitor.conf_in_use["skb_mark"]:
-            skb_mark = monitor.conf_in_use["skb_mark"]
-            self.conf_file.write(self.SHUNT_POLICY.format(skb_mark))
-
-        # Configure the CA cert
-        if monitor.conf_in_use["pki"]["ca_cert"]:
-            cacert = monitor.conf_in_use["pki"]["ca_cert"]
-            self.conf_file.write(self.CA_SECTION % cacert)
-
-        return needs_refresh
-
-    def config_tunnel(self, tunnel):
-        if tunnel.conf["psk"]:
-            self.secrets_file.write('0.0.0.0 %s : PSK "%s"\n' %
-                            (tunnel.conf["remote_ip"], tunnel.conf["psk"]))
-            auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf)
-        else:
-            self.secrets_file.write("0.0.0.0 %s : RSA %s\n" %
-                                        (tunnel.conf["remote_ip"],
-                                        tunnel.conf["private_key"]))
-            if tunnel.conf["remote_cert"]:
-                tmpl = self.auth_tmpl["pki_remote"]
-                auth_section = tmpl.substitute(tunnel.conf)
-            else:
-                tmpl = self.auth_tmpl["pki_ca"]
-                auth_section = tmpl.substitute(tunnel.conf)
-
-        vals = tunnel.conf.copy()
-        vals["auth_section"] = auth_section
-        vals["version"] = tunnel.version
-        conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals)
-        self.conf_file.write(conf_text)
-
-    def config_fini(self):
-        self.secrets_file.close()
-        self.conf_file.close()
-        self.secrets_file = None
-        self.conf_file = None
-
-    def refresh(self, monitor):
-        """This functions refreshes strongSwan configuration.  Behind the
-        scenes this function calls:
-        1. once "ipsec update" command that tells strongSwan to load
-           all new tunnels from "ipsec.conf"; and
-        2. once "ipsec rereadsecrets" command that tells strongswan to load
-           secrets from "ipsec.conf" file
-        3. for every removed tunnel "ipsec stroke down-nb <tunnel>" command
-           that removes old tunnels.
-        Once strongSwan vici bindings will be distributed with major
-        Linux distributions this function could be simplified."""
-        vlog.info("Refreshing StrongSwan configuration")
-        subprocess.call([self.IPSEC, "update"])
-        subprocess.call([self.IPSEC, "rereadsecrets"])
-        # "ipsec update" command does not remove those tunnels that were
-        # updated or that disappeared from the ipsec.conf file.  So, we have
-        # to manually remove them by calling "ipsec stroke down-nb <tunnel>"
-        # command.  We use <version> number to tell apart tunnels that
-        # were just updated.
-        # "ipsec down-nb" command is designed to be non-blocking (opposed
-        # to "ipsec down" command).  This means that we should not be concerned
-        # about possibility of ovs-monitor-ipsec to block for each tunnel
-        # while strongSwan sends IKE messages over Internet.
-        conns_dict = self.get_active_conns()
-        for ifname, conns in conns_dict.iteritems():
-            tunnel = monitor.tunnels.get(ifname)
-            for conn in conns:
-                # IPsec "connection" names that we choose in strongswan
-                # must start with Interface name
-                if not conn.startswith(ifname):
-                    vlog.err("%s does not start with %s" % (conn, ifname))
-                    continue
-
-                # version number should be the first integer after
-                # interface name in IPsec "connection"
-                try:
-                    ver = int(re.findall(r'\d+', conn[len(ifname):])[0])
-                except IndexError:
-                    vlog.err("%s does not contain version number")
-                    continue
-                except ValueError:
-                    vlog.err("%s does not contain version number")
-                    continue
-
-                if not tunnel or tunnel.version != ver:
-                    vlog.info("%s is outdated %u" % (conn, ver))
-                    subprocess.call([self.IPSEC, "stroke", "down-nb", conn])
-
-
-class LibreSwanHelper(object):
-    """This class does LibreSwan specific configurations."""
-    CONF_HEADER = """%s
-config setup
-    uniqueids=yes
-
-conn %%default
-    keyingtries=%%forever
-    type=transport
-    auto=route
-    ike=aes_gcm256-sha2_256
-    esp=aes_gcm256
-    ikev2=insist
-
-""" % (FILE_HEADER)
-
-    SHUNT_POLICY = """conn prevent_unencrypted_gre
-    type=drop
-    left=%defaultroute
-    leftprotoport=gre
-    mark={0}
-
-conn prevent_unencrypted_geneve
-    type=drop
-    left=%defaultroute
-    leftprotoport=udp/6081
-    mark={0}
-
-conn prevent_unencrypted_stt
-    type=drop
-    left=%defaultroute
-    leftprotoport=tcp/7471
-    mark={0}
-
-conn prevent_unencrypted_vxlan
-    type=drop
-    left=%defaultroute
-    leftprotoport=udp/4789
-    mark={0}
-
-"""
-
-    auth_tmpl = {"psk": Template("""\
-    left=%defaultroute
-    right=$remote_ip
-    authby=secret"""),
-                 "pki_remote": Template("""\
-    left=%defaultroute
-    right=$remote_ip
-    leftid=@$local_name
-    rightid=@$remote_name
-    leftcert="$local_name"
-    rightcert="$remote_name"
-    leftrsasigkey=%cert"""),
-                 "pki_ca": Template("""\
-    left=%defaultroute
-    right=$remote_ip
-    leftid=@$local_name
-    rightid=@$remote_name
-    leftcert="ovs_certkey_$local_name"
-    leftrsasigkey=%cert
-    rightca=%same""")}
-
-    CERT_PREFIX = "ovs_cert_"
-    CERTKEY_PREFIX = "ovs_certkey_"
-
-    def __init__(self, libreswan_root_prefix):
-        self.IPSEC = libreswan_root_prefix + "/usr/sbin/ipsec"
-        self.IPSEC_CONF = libreswan_root_prefix + "/etc/ipsec.conf"
-        self.IPSEC_SECRETS = libreswan_root_prefix + "/etc/ipsec.secrets"
-        self.conf_file = None
-        self.secrets_file = None
-
-    def restart_ike_daemon(self):
-        """This function restarts LibreSwan."""
-        # Remove the stale information from the NSS database
-        self._nss_clear_database()
-
-        f = open(self.IPSEC_CONF, "w")
-        f.write(self.CONF_HEADER)
-        f.close()
-
-        f = open(self.IPSEC_SECRETS, "w")
-        f.write(FILE_HEADER)
-        f.close()
-
-        vlog.info("Restarting LibreSwan")
-        subprocess.call([self.IPSEC, "restart"])
-
-    def config_init(self):
-        self.conf_file = open(self.IPSEC_CONF, "w")
-        self.secrets_file = open(self.IPSEC_SECRETS, "w")
-        self.conf_file.write(self.CONF_HEADER)
-        self.secrets_file.write(FILE_HEADER)
-
-    def config_global(self, monitor):
-        """Configure the global state of IPsec tunnels."""
-        needs_refresh = False
-
-        if monitor.conf_in_use["pki"] != monitor.conf["pki"]:
-            # Clear old state
-            if monitor.conf_in_use["pki"]["certificate"]:
-                local_name = monitor.conf_in_use["pki"]["local_name"]
-                self._nss_delete_cert_and_key(self.CERTKEY_PREFIX + local_name)
-
-            if monitor.conf_in_use["pki"]["ca_cert"]:
-                self._nss_delete_cert(self.CERT_PREFIX + "cacert")
-
-            # Load new state
-            if monitor.conf["pki"]["certificate"]:
-                cert = monitor.conf["pki"]["certificate"]
-                key = monitor.conf["pki"]["private_key"]
-                name = monitor.conf["pki"]["local_name"]
-                name = self.CERTKEY_PREFIX + name
-                self._nss_import_cert_and_key(cert, key, name)
-
-            if monitor.conf["pki"]["ca_cert"]:
-                self._nss_import_cert(monitor.conf["pki"]["ca_cert"],
-                                      self.CERT_PREFIX + "cacert", 'CT,,')
-
-            monitor.conf_in_use["pki"] = copy.deepcopy(monitor.conf["pki"])
-            needs_refresh = True
-
-        # Configure the shunt policy
-        if monitor.conf["skb_mark"]:
-            skb_mark = monitor.conf["skb_mark"]
-            self.conf_file.write(self.SHUNT_POLICY.format(skb_mark))
-
-        # Will update conf_in_use later in the 'refresh' method
-        if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
-            needs_refresh = True
-
-        return needs_refresh
-
-    def config_tunnel(self, tunnel):
-        if tunnel.conf["psk"]:
-            self.secrets_file.write('%%any %s : PSK "%s"\n' %
-                            (tunnel.conf["remote_ip"], tunnel.conf["psk"]))
-            auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf)
-        elif tunnel.conf["remote_cert"]:
-            auth_section = self.auth_tmpl["pki_remote"].substitute(tunnel.conf)
-            self._nss_import_cert(tunnel.conf["remote_cert"],
-                                 self.CERT_PREFIX + tunnel.conf["remote_name"],
-                                 'P,P,P')
-        else:
-            auth_section = self.auth_tmpl["pki_ca"].substitute(tunnel.conf)
-
-        vals = tunnel.conf.copy()
-        vals["auth_section"] = auth_section
-        vals["version"] = tunnel.version
-        conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals)
-        self.conf_file.write(conf_text)
-
-    def config_fini(self):
-        self.secrets_file.close()
-        self.conf_file.close()
-        self.secrets_file = None
-        self.conf_file = None
-
-    def clear_tunnel_state(self, tunnel):
-        if tunnel.conf["remote_cert"]:
-            name = self.CERT_PREFIX + tunnel.conf["remote_name"]
-            self._nss_delete_cert(name)
-
-    def refresh(self, monitor):
-        vlog.info("Refreshing LibreSwan configuration")
-        subprocess.call([self.IPSEC, "auto", "--rereadsecrets"])
-        tunnels = set(monitor.tunnels.keys())
-
-        # Delete old connections
-        conns_dict = self.get_active_conns()
-        for ifname, conns in conns_dict.iteritems():
-            tunnel = monitor.tunnels.get(ifname)
-
-            for conn in conns:
-                # IPsec "connection" names must start with Interface name
-                if not conn.startswith(ifname):
-                    vlog.err("%s does not start with %s" % (conn, ifname))
-                    continue
-
-                # version number should be the first integer after
-                # interface name in IPsec "connection"
-                try:
-                    ver = int(re.findall(r'\d+', conn[len(ifname):])[0])
-                except ValueError:
-                    vlog.err("%s does not contain version number")
-                    continue
-                except IndexError:
-                    vlog.err("%s does not contain version number")
-                    continue
-
-                if not tunnel or tunnel.version != ver:
-                    vlog.info("%s is outdated %u" % (conn, ver))
-                    subprocess.call([self.IPSEC, "auto", "--delete", conn])
-                elif ifname in tunnels:
-                    tunnels.remove(ifname)
-
-        # Activate new connections
-        for name in tunnels:
-            ver = monitor.tunnels[name].version
-
-            if monitor.tunnels[name].conf["tunnel_type"] == "gre":
-                conn = "%s-%s" % (name, ver)
-                self._start_ipsec_connection(conn)
-            else:
-                conn_in = "%s-in-%s" % (name, ver)
-                conn_out = "%s-out-%s" % (name, ver)
-                self._start_ipsec_connection(conn_in)
-                self._start_ipsec_connection(conn_out)
-
-        # Update shunt policy if changed
-        if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
-            if monitor.conf["skb_mark"]:
-                subprocess.call([self.IPSEC, "auto", "--add",
-                            "--asynchronous", "prevent_unencrypted_gre"])
-                subprocess.call([self.IPSEC, "auto", "--add",
-                            "--asynchronous", "prevent_unencrypted_geneve"])
-                subprocess.call([self.IPSEC, "auto", "--add",
-                            "--asynchronous", "prevent_unencrypted_stt"])
-                subprocess.call([self.IPSEC, "auto", "--add",
-                            "--asynchronous", "prevent_unencrypted_vxlan"])
-            else:
-                subprocess.call([self.IPSEC, "auto", "--delete",
-                            "--asynchronous", "prevent_unencrypted_gre"])
-                subprocess.call([self.IPSEC, "auto", "--delete",
-                            "--asynchronous", "prevent_unencrypted_geneve"])
-                subprocess.call([self.IPSEC, "auto", "--delete",
-                            "--asynchronous", "prevent_unencrypted_stt"])
-                subprocess.call([self.IPSEC, "auto", "--delete",
-                            "--asynchronous", "prevent_unencrypted_vxlan"])
-            monitor.conf_in_use["skb_mark"] = monitor.conf["skb_mark"]
-
-    def get_active_conns(self):
-        """This function parses output from 'ipsec status' command.
-        It returns dictionary where <key> is interface name (as in OVSDB)
-        and <value> is another dictionary.  This another dictionary
-        uses LibreSwan connection name as <key> and more detailed
-        sample line from the parsed outpus as <value>. """
-
-        conns = {}
-        proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE)
-
-        while True:
-            line = proc.stdout.readline().strip()
-            if line == '':
-                break
-
-            m = re.search(r"#\d+: \"(.*)\".*", line)
-            if not m:
-                continue
-
-            conn = m.group(1)
-            m = re.match(r"(.*)(-in-\d+|-out-\d+|-\d+)", conn)
-            if not m:
-                continue
-
-            ifname = m.group(1)
-            if ifname not in conns:
-                conns[ifname] = {}
-            (conns[ifname])[conn] = line
-
-        return conns
-
-    def _start_ipsec_connection(self, conn):
-        # In a corner case, LibreSwan daemon restarts for some reason and
-        # the "ipsec auto --start" command is lost. Just retry to make sure
-        # the command is received by LibreSwan.
-        while True:
-            proc = subprocess.Popen([self.IPSEC, "auto", "--start",
-                                    "--asynchronous", conn],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            perr = str(proc.stderr.read())
-            pout = str(proc.stdout.read())
-            if not re.match(r".*Connection refused.*", perr) and \
-                    not re.match(r".*need --listen.*", pout):
-                break
-
-    def _nss_clear_database(self):
-        """Remove all OVS IPsec related state from the NSS database"""
-        try:
-            proc = subprocess.Popen(['certutil', '-L', '-d',
-                                    'sql:/etc/ipsec.d/'],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            lines = proc.stdout.readlines()
-
-            for line in lines:
-                s = line.strip().split()
-                if len(s) < 1:
-                    continue
-                name = s[0]
-                if name.startswith(self.CERT_PREFIX):
-                    self._nss_delete_cert(name)
-                elif name.startswith(self.CERTKEY_PREFIX):
-                    self._nss_delete_cert_and_key(name)
-
-        except Exception as e:
-            vlog.err("Failed to clear NSS database.\n" + str(e))
-
-    def _nss_import_cert(self, cert, name, cert_type):
-        """Cert_type is 'CT,,' for the CA certificate and 'P,P,P' for the
-        normal certificate."""
-        try:
-            proc = subprocess.Popen(['certutil', '-A', '-a', '-i', cert,
-                                    '-d', 'sql:/etc/ipsec.d/', '-n',
-                                    name, '-t', cert_type],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode:
-                raise Exception(proc.stderr.read())
-        except Exception as e:
-            vlog.err("Failed to import ceretificate into NSS.\n" + str(e))
-
-    def _nss_delete_cert(self, name):
-        try:
-            proc = subprocess.Popen(['certutil', '-D', '-d',
-                                    'sql:/etc/ipsec.d/', '-n', name],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode:
-                raise Exception(proc.stderr.read())
-        except Exception as e:
-            vlog.err("Failed to delete ceretificate from NSS.\n" + str(e))
-
-    def _nss_import_cert_and_key(self, cert, key, name):
-        try:
-            # Avoid deleting other files
-            path = os.path.abspath('/tmp/%s.p12' % name)
-            if not path.startswith('/tmp/'):
-                raise Exception("Illegal certificate name!")
-
-            # Create p12 file from pem files
-            proc = subprocess.Popen(['openssl', 'pkcs12', '-export',
-                                    '-in', cert, '-inkey', key, '-out',
-                                    path, '-name', name, '-passout', 'pass:'],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode:
-                raise Exception(proc.stderr.read())
-
-            # Load p12 file to the database
-            proc = subprocess.Popen(['pk12util', '-i', path, '-d',
-                                    'sql:/etc/ipsec.d/', '-W', ''],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode:
-                raise Exception(proc.stderr.read())
-
-        except Exception as e:
-            vlog.err("Import cert and key failed.\n" + str(e))
-        os.remove(path)
-
-    def _nss_delete_cert_and_key(self, name):
-        try:
-            # Delete certificate and private key
-            proc = subprocess.Popen(['certutil', '-F', '-d',
-                                    'sql:/etc/ipsec.d/', '-n', name],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode:
-                raise Exception(proc.stderr.read())
-
-        except Exception as e:
-            vlog.err("Delete cert and key failed.\n" + str(e))
-
-
-class IPsecTunnel(object):
-    """This is the base class for IPsec tunnel."""
-
-    unixctl_config_tmpl = Template("""\
-  Tunnel Type:    $tunnel_type
-  Remote IP:      $remote_ip
-  SKB mark:       $skb_mark
-  Local cert:     $certificate
-  Local name:     $local_name
-  Local key:      $private_key
-  Remote cert:    $remote_cert
-  Remote name:    $remote_name
-  CA cert:        $ca_cert
-  PSK:            $psk
-""")
-
-    unixctl_status_tmpl = Template("""\
-  Ofport:         $ofport
-  CFM state:      $cfm_state
-""")
-
-    def __init__(self, name, row):
-        self.name = name  # 'name' will not change because it is key in OVSDB
-        self.version = 0  # 'version' is increased on configuration changes
-        self.last_refreshed_version = -1
-        self.state = "INIT"
-        self.conf = {}
-        self.status = {}
-        self.update_conf(row)
-
-    def update_conf(self, row):
-        """This function updates IPsec tunnel configuration by using 'row'
-        from OVSDB interface table.  If configuration was actually changed
-        in OVSDB then this function returns True.  Otherwise, it returns
-        False."""
-        ret = False
-        options = row.options
-        remote_cert = options.get("remote_cert")
-        remote_name = options.get("remote_name")
-        if remote_cert:
-            remote_name = monitor._get_cn_from_cert(remote_cert)
-
-        new_conf = {
-            "ifname": self.name,
-            "tunnel_type": row.type,
-            "remote_ip": options.get("remote_ip"),
-            "skb_mark": monitor.conf["skb_mark"],
-            "certificate": monitor.conf["pki"]["certificate"],
-            "private_key": monitor.conf["pki"]["private_key"],
-            "ca_cert": monitor.conf["pki"]["ca_cert"],
-            "remote_cert": remote_cert,
-            "remote_name": remote_name,
-            "local_name": monitor.conf["pki"]["local_name"],
-            "psk": options.get("psk")}
-
-        if self.conf != new_conf:
-            # Configuration was updated in OVSDB.  Validate it and figure
-            # out what to do next with this IPsec tunnel.  Also, increment
-            # version number of this IPsec tunnel so that we could tell
-            # apart old and new tunnels in "ipsec status" output.
-            self.version += 1
-            ret = True
-            self.conf = new_conf
-
-            if self._is_valid_tunnel_conf():
-                self.state = "CONFIGURED"
-            else:
-                vlog.warn("%s contains invalid configuration%s" %
-                          (self.name, self.invalid_reason))
-                self.state = "INVALID"
-
-        new_status = {
-            "cfm_state": "Up" if row.cfm_fault == [False] else
-                         "Down" if row.cfm_fault == [True] else
-                         "Disabled",
-            "ofport": "Not assigned" if (row.ofport in [[], [-1]]) else
-                      row.ofport[0]}
-
-        if self.status != new_status:
-            # Tunnel has become unhealthy or ofport changed.  Simply log this.
-            vlog.dbg("%s changed status from %s to %s" %
-                     (self.name, str(self.status), str(new_status)))
-            self.status = new_status
-        return ret
-
-    def mark_for_removal(self):
-        """This function marks tunnel for removal."""
-        self.version += 1
-        self.state = "REMOVED"
-
-    def show(self, policies, securities, conns):
-        state = self.state
-        if self.state == "INVALID":
-            state += self.invalid_reason
-        header = "Interface name: %s v%u (%s)\n" % (self.name, self.version,
-                                                    state)
-        conf = self.unixctl_config_tmpl.substitute(self.conf)
-        status = self.unixctl_status_tmpl.substitute(self.status)
-        spds = "Kernel policies installed:\n"
-        remote_ip = self.conf["remote_ip"]
-        if remote_ip in policies:
-            for line in policies[remote_ip]:
-                spds += "  " + line + "\n"
-        sas = "Kernel security associations installed:\n"
-        if remote_ip in securities:
-            for line in securities[remote_ip]:
-                sas += "  " + line + "\n"
-        cons = "IPsec connections that are active:\n"
-        if self.name in conns:
-            for tname in conns[self.name]:
-                cons += "  " + conns[self.name][tname] + "\n"
-
-        return header + conf + status + spds + sas + cons + "\n"
-
-    def _is_valid_tunnel_conf(self):
-        """This function verifies if IPsec tunnel has valid configuration
-        set in 'conf'.  If it is valid, then it returns True.  Otherwise,
-        it returns False and sets the reason why configuration was considered
-        as invalid.
-
-        This function could be improved in future to also verify validness
-        of certificates themselves so that ovs-monitor-ipsec would not
-        pass malformed configuration to IKE daemon."""
-
-        self.invalid_reason = None
-
-        if not self.conf["remote_ip"]:
-            self.invalid_reason = ": 'remote_ip' is not set"
-            return False
-
-        if self.conf["psk"]:
-            if self.conf["certificate"] or self.conf["private_key"] \
-                    or self.conf["ca_cert"] or self.conf["remote_cert"] \
-                    or self.conf["remote_name"]:
-                self.invalid_reason = ": 'certificate', 'private_key', "\
-                                      "'ca_cert', 'remote_cert', and "\
-                                      "'remote_name' must be unset with PSK"
-                return False
-        # If configuring authentication with CA-signed certificate or
-        # self-signed certificate, the 'remote_name' should be specified at
-        # this point.  When using CA-signed certificate, the 'remote_name' is
-        # read from interface's options field. When using self-signed
-        # certificate, the 'remote_name' is extracted from the 'remote_cert'
-        # file.
-        elif self.conf["remote_name"]:
-            if not self.conf["certificate"]:
-                self.invalid_reason = ": must set 'certificate' as local"\
-                                      " certificate when using CA-signed"\
-                                      " certificate or self-signed"\
-                                      " certificate to authenticate peers"
-                return False
-            elif not self.conf["private_key"]:
-                self.invalid_reason = ": must set 'private_key' as local"\
-                                      " private key when using CA-signed"\
-                                      " certificate or self-signed"\
-                                      " certificate to authenticate peers"
-                return False
-            if not self.conf["remote_cert"] and not self.conf["ca_cert"]:
-                self.invalid_reason = ": must set 'remote_cert' when using"\
-                                      " self-signed certificate"\
-                                      " authentication or 'ca_cert' when"\
-                                      " using CA-signed certificate"\
-                                      " authentication"
-                return False
-        else:
-            self.invalid_reason = ": must choose a authentication method"
-            return False
-
-        return True
-
-
-class IPsecMonitor(object):
-    """This class monitors and configures IPsec tunnels"""
-
-    def __init__(self, root_prefix, ike_daemon):
-        self.IPSEC = root_prefix + "/usr/sbin/ipsec"
-        self.tunnels = {}
-
-        # Global configuration shared by all tunnels
-        self.conf = {
-            "pki": {
-                "private_key": None,
-                "certificate": None,
-                "ca_cert": None,
-                "local_name": None
-            },
-            "skb_mark": None
-        }
-        self.conf_in_use = copy.deepcopy(self.conf)
-
-        # Choose to either use StrongSwan or LibreSwan as IKE daemon
-        if ike_daemon == "strongswan":
-            self.ike_helper = StrongSwanHelper(root_prefix)
-        elif ike_daemon == "libreswan":
-            self.ike_helper = LibreSwanHelper(root_prefix)
-        else:
-            vlog.err("The IKE daemon should be strongswan or libreswan.")
-            sys.exit(1)
-
-        # Check whether ipsec command is available
-        if not os.path.isfile(self.IPSEC) or \
-                not os.access(self.IPSEC, os.X_OK):
-            vlog.err("IKE daemon is not installed in the system.")
-
-        self.ike_helper.restart_ike_daemon()
-
-    def is_tunneling_type_supported(self, tunnel_type):
-        """Returns True if we know how to configure IPsec for these
-        types of tunnels.  Otherwise, returns False."""
-        return tunnel_type in ["gre", "geneve", "vxlan", "stt"]
-
-    def is_ipsec_required(self, options_column):
-        """Return True if tunnel needs to be encrypted.  Otherwise,
-        returns False."""
-        return "psk" in options_column or \
-                "remote_name" in options_column or \
-                "remote_cert" in options_column
-
-    def add_tunnel(self, name, row):
-        """Adds a new tunnel that monitor will provision with 'name'."""
-        vlog.info("Tunnel %s appeared in OVSDB" % (name))
-        self.tunnels[name] = IPsecTunnel(name, row)
-
-    def update_tunnel(self, name, row):
-        """Updates configuration of already existing tunnel with 'name'."""
-        tunnel = self.tunnels[name]
-        if tunnel.update_conf(row):
-            vlog.info("Tunnel's '%s' configuration changed in OVSDB to %u" %
-                      (tunnel.name, tunnel.version))
-
-    def del_tunnel(self, name):
-        """Deletes tunnel by 'name'."""
-        vlog.info("Tunnel %s disappeared from OVSDB" % (name))
-        self.tunnels[name].mark_for_removal()
-
-    def update_conf(self, pki, skb_mark):
-        """Update the global configuration for IPsec tunnels"""
-        self.conf["pki"]["certificate"] = pki[0]
-        self.conf["pki"]["private_key"] = pki[1]
-        self.conf["pki"]["ca_cert"] = pki[2]
-        self.conf["pki"]["local_name"] = pki[3]
-
-        # Update skb_mark used in IPsec policies.
-        self.conf["skb_mark"] = skb_mark
-
-    def read_ovsdb_open_vswitch_table(self, data):
-        """This functions reads IPsec relevant configuration from Open_vSwitch
-        table."""
-        pki = [None, None, None, None]
-        skb_mark = None
-        is_valid = False
-
-        for row in data["Open_vSwitch"].rows.itervalues():
-            pki[0] = row.other_config.get("certificate")
-            pki[1] = row.other_config.get("private_key")
-            pki[2] = row.other_config.get("ca_cert")
-            skb_mark = row.other_config.get("ipsec_skb_mark")
-
-        # Test whether it's a valid configration
-        if pki[0] and pki[1]:
-            pki[3] = self._get_cn_from_cert(pki[0])
-            if pki[3]:
-                is_valid = True
-        elif not pki[0] and not pki[1] and not pki[2]:
-            is_valid = True
-
-        if not is_valid:
-            vlog.warn("The cert and key configuration is not valid. "
-                "The valid configuations are 1): certificate, private_key "
-                "and ca_cert are not set; or 2): certificate and "
-                "private_key are all set.")
-        else:
-            self.update_conf(pki, skb_mark)
-
-    def read_ovsdb_interface_table(self, data):
-        """This function reads the IPsec relevant configuration from Interface
-        table."""
-        ifaces = set()
-
-        for row in data["Interface"].rows.itervalues():
-            if not self.is_tunneling_type_supported(row.type):
-                continue
-            if not self.is_ipsec_required(row.options):
-                continue
-            if row.name in self.tunnels:
-                self.update_tunnel(row.name, row)
-            else:
-                self.add_tunnel(row.name, row)
-            ifaces.add(row.name)
-
-        # Mark for removal those tunnels that just disappeared from OVSDB
-        for tunnel in self.tunnels.keys():
-            if tunnel not in ifaces:
-                self.del_tunnel(tunnel)
-
-    def read_ovsdb(self, data):
-        """This function reads all configuration from OVSDB that
-        ovs-monitor-ipsec is interested in."""
-        self.read_ovsdb_open_vswitch_table(data)
-        self.read_ovsdb_interface_table(data)
-
-    def show(self, unix_conn, policies, securities):
-        """This function prints all tunnel state in 'unix_conn'.
-        It uses 'policies' and securities' received from Linux Kernel
-        to show if tunnels were actually configured by the IKE deamon."""
-        if not self.tunnels:
-            unix_conn.reply("No tunnels configured with IPsec")
-            return
-        s = ""
-        conns = self.ike_helper.get_active_conns()
-        for name, tunnel in self.tunnels.iteritems():
-            s += tunnel.show(policies, securities, conns)
-        unix_conn.reply(s)
-
-    def run(self):
-        """This function runs state machine that represents whole
-        IPsec configuration (i.e. merged together from individual
-        tunnel state machines).  It creates configuration files and
-        tells IKE daemon to update configuration."""
-        needs_refresh = False
-        removed_tunnels = []
-
-        self.ike_helper.config_init()
-
-        if self.ike_helper.config_global(self):
-            needs_refresh = True
-
-        for name, tunnel in self.tunnels.iteritems():
-            if tunnel.last_refreshed_version != tunnel.version:
-                tunnel.last_refreshed_version = tunnel.version
-                needs_refresh = True
-
-            if tunnel.state == "REMOVED" or tunnel.state == "INVALID":
-                removed_tunnels.append(name)
-            elif tunnel.state == "CONFIGURED":
-                self.ike_helper.config_tunnel(self.tunnels[name])
-
-        self.ike_helper.config_fini()
-
-        for name in removed_tunnels:
-            # LibreSwan needs to clear state from database
-            if hasattr(self.ike_helper, "clear_tunnel_state"):
-                self.ike_helper.clear_tunnel_state(self.tunnels[name])
-            del self.tunnels[name]
-
-        if needs_refresh:
-            self.ike_helper.refresh(self)
-
-    def _get_cn_from_cert(self, cert):
-        try:
-            proc = subprocess.Popen(['openssl', 'x509', '-noout', '-subject',
-                                    '-nameopt', 'RFC2253', '-in', cert],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode:
-                raise Exception(proc.stderr.read())
-            m = re.search(r"CN=(.+?),", proc.stdout.readline())
-            if not m:
-                raise Exception("No CN in the certificate subject.")
-        except Exception as e:
-            vlog.warn(str(e))
-            return None
-
-        return m.group(1)
-
-
-def unixctl_xfrm_policies(conn, unused_argv, unused_aux):
-    global xfrm
-    policies = xfrm.get_policies()
-    conn.reply(str(policies))
-
-
-def unixctl_xfrm_state(conn, unused_argv, unused_aux):
-    global xfrm
-    securities = xfrm.get_securities()
-    conn.reply(str(securities))
-
-
-def unixctl_ipsec_status(conn, unused_argv, unused_aux):
-    global monitor
-    conns = monitor.ike_helper.get_active_conns()
-    conn.reply(str(conns))
-
-
-def unixctl_show(conn, unused_argv, unused_aux):
-    global monitor
-    global xfrm
-    policies = xfrm.get_policies()
-    securities = xfrm.get_securities()
-    monitor.show(conn, policies, securities)
-
-
-def unixctl_refresh(conn, unused_argv, unused_aux):
-    global monitor
-    monitor.ike_helper.refresh(monitor)
-    conn.reply(None)
-
-
-def unixctl_exit(conn, unused_argv, unused_aux):
-    global monitor
-    global exiting
-    exiting = True
-
-    # Make sure persistent global states are cleared
-    monitor.update_conf([None, None, None, None], None)
-    # Make sure persistent tunnel states are cleared
-    for tunnel in monitor.tunnels.keys():
-        monitor.del_tunnel(tunnel)
-    monitor.run()
-
-    conn.reply(None)
-
-
-def main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument("database", metavar="DATABASE",
-                        help="A socket on which ovsdb-server is listening.")
-    parser.add_argument("--root-prefix", metavar="DIR",
-                        help="Use DIR as alternate root directory"
-                        " (for testing).")
-    parser.add_argument("--ike-daemon", metavar="IKE-DAEMON",
-                        help="The IKE daemon used for IPsec tunnels"
-                        " (either libreswan or strongswan).")
-
-    ovs.vlog.add_args(parser)
-    ovs.daemon.add_args(parser)
-    args = parser.parse_args()
-    ovs.vlog.handle_args(args)
-    ovs.daemon.handle_args(args)
-
-    global monitor
-    global xfrm
-
-    root_prefix = args.root_prefix if args.root_prefix else ""
-    xfrm = XFRM(root_prefix)
-    monitor = IPsecMonitor(root_prefix, args.ike_daemon)
-
-    remote = args.database
-    schema_helper = ovs.db.idl.SchemaHelper()
-    schema_helper.register_columns("Interface",
-                                   ["name", "type", "options", "cfm_fault",
-                                    "ofport"])
-    schema_helper.register_columns("Open_vSwitch", ["other_config"])
-    idl = ovs.db.idl.Idl(remote, schema_helper)
-
-    ovs.daemon.daemonize()
-
-    ovs.unixctl.command_register("xfrm/policies", "", 0, 0,
-                                 unixctl_xfrm_policies, None)
-    ovs.unixctl.command_register("xfrm/state", "", 0, 0,
-                                 unixctl_xfrm_state, None)
-    ovs.unixctl.command_register("ipsec/status", "", 0, 0,
-                                 unixctl_ipsec_status, None)
-    ovs.unixctl.command_register("tunnels/show", "", 0, 0,
-                                 unixctl_show, None)
-    ovs.unixctl.command_register("refresh", "", 0, 0, unixctl_refresh, None)
-    ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
-
-    error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None)
-    if error:
-        ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
-
-    # Sequence number when OVSDB was processed last time
-    seqno = idl.change_seqno
-
-    while True:
-        unixctl_server.run()
-        if exiting:
-            break
-
-        idl.run()
-        if seqno != idl.change_seqno:
-            monitor.read_ovsdb(idl.tables)
-            seqno = idl.change_seqno
-
-        monitor.run()
-
-        poller = ovs.poller.Poller()
-        unixctl_server.wait(poller)
-        idl.wait(poller)
-        poller.block()
-
-    unixctl_server.close()
-    idl.close()
-
-
-if __name__ == '__main__':
-    try:
-        main()
-    except SystemExit:
-        # Let system.exit() calls complete normally
-        raise
-    except:
-        vlog.exception("traceback")
-        sys.exit(ovs.daemon.RESTART_EXIT_CODE)
diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in
new file mode 100755 (executable)
index 0000000..4710be0
--- /dev/null
@@ -0,0 +1,1235 @@
+#! @PYTHON@
+# Copyright (c) 2017 Nicira, Inc.
+#
+# 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.
+
+import argparse
+import re
+import subprocess
+import sys
+import copy
+import os
+from string import Template
+
+import ovs.daemon
+import ovs.db.idl
+import ovs.dirs
+import ovs.unixctl
+import ovs.unixctl.server
+import ovs.util
+import ovs.vlog
+
+
+FILE_HEADER = "# Generated by ovs-monitor-ipsec...do not modify by hand!\n\n"
+transp_tmpl = {"gre": Template("""\
+conn $ifname-$version
+$auth_section
+    leftprotoport=gre
+    rightprotoport=gre
+
+"""), "gre64": Template("""\
+conn $ifname-$version
+$auth_section
+    leftprotoport=gre
+    rightprotoport=gre
+
+"""), "geneve": Template("""\
+conn $ifname-in-$version
+$auth_section
+    leftprotoport=udp/6081
+    rightprotoport=udp
+
+conn $ifname-out-$version
+$auth_section
+    leftprotoport=udp
+    rightprotoport=udp/6081
+
+"""), "stt": Template("""\
+conn $ifname-in-$version
+$auth_section
+    leftprotoport=tcp/7471
+    rightprotoport=tcp
+
+conn $ifname-out-$version
+$auth_section
+    leftprotoport=tcp
+    rightprotoport=tcp/7471
+
+"""), "vxlan": Template("""\
+conn $ifname-in-$version
+$auth_section
+    leftprotoport=udp/4789
+    rightprotoport=udp
+
+conn $ifname-out-$version
+$auth_section
+    leftprotoport=udp
+    rightprotoport=udp/4789
+
+""")}
+vlog = ovs.vlog.Vlog("ovs-monitor-ipsec")
+exiting = False
+monitor = None
+xfrm = None
+
+
+class XFRM(object):
+    """This class is a simple wrapper around ip-xfrm (8) command line
+    utility.  We are using this class only for informational purposes
+    so that ovs-monitor-ipsec could verify that IKE keying daemon has
+    installed IPsec policies and security associations into kernel as
+    expected."""
+
+    def __init__(self, ip_root_prefix):
+        self.IP = ip_root_prefix + "/sbin/ip"
+
+    def get_policies(self):
+        """This function returns IPsec policies (from kernel) in a dictionary
+        where <key> is destination IPv4 address and <value> is SELECTOR of
+        the IPsec policy."""
+        policies = {}
+        proc = subprocess.Popen([self.IP, 'xfrm', 'policy'],
+                                stdout=subprocess.PIPE)
+        while True:
+            line = proc.stdout.readline().strip()
+            if line == '':
+                break
+            a = line.split(" ")
+            if len(a) >= 4 and a[0] == "src" and a[2] == "dst":
+                dst = (a[3].split("/"))[0]
+                if dst not in policies:
+                    policies[dst] = []
+                policies[dst].append(line)
+                src = (a[3].split("/"))[0]
+                if src not in policies:
+                    policies[src] = []
+                policies[src].append(line)
+        return policies
+
+    def get_securities(self):
+        """This function returns IPsec security associations (from kernel)
+        in a dictionary where <key> is destination IPv4 address and <value>
+        is SELECTOR."""
+        securities = {}
+        proc = subprocess.Popen([self.IP, 'xfrm', 'state'],
+                                stdout=subprocess.PIPE)
+        while True:
+            line = proc.stdout.readline().strip()
+            if line == '':
+                break
+            a = line.split(" ")
+            if len(a) >= 4 and a[0] == "sel" \
+                    and a[1] == "src" and a[3] == "dst":
+                remote_ip = a[4].rstrip().split("/")[0]
+                local_ip = a[2].rstrip().split("/")[0]
+                if remote_ip not in securities:
+                    securities[remote_ip] = []
+                securities[remote_ip].append(line)
+                if local_ip not in securities:
+                    securities[local_ip] = []
+                securities[local_ip].append(line)
+        return securities
+
+
+class StrongSwanHelper(object):
+    """This class does StrongSwan specific configurations."""
+
+    STRONGSWAN_CONF = """%s
+charon.plugins.kernel-netlink.set_proto_port_transport_sa = yes
+charon.plugins.kernel-netlink.xfrm_ack_expires = 10
+charon.load_modular = yes
+charon.plugins.gcm.load = yes
+""" % (FILE_HEADER)
+
+    CONF_HEADER = """%s
+config setup
+    uniqueids=yes
+
+conn %%default
+    keyingtries=%%forever
+    type=transport
+    keyexchange=ikev2
+    auto=route
+    ike=aes256gcm16-sha256-modp2048
+    esp=aes256gcm16-modp2048
+
+""" % (FILE_HEADER)
+
+    CA_SECTION = """ca ca_auth
+    cacert=%s
+
+"""
+
+    SHUNT_POLICY = """conn prevent_unencrypted_gre
+    type=drop
+    leftprotoport=gre
+    mark={0}
+
+conn prevent_unencrypted_geneve
+    type=drop
+    leftprotoport=udp/6081
+    mark={0}
+
+conn prevent_unencrypted_stt
+    type=drop
+    leftprotoport=tcp/7471
+    mark={0}
+
+conn prevent_unencrypted_vxlan
+    type=drop
+    leftprotoport=udp/4789
+    mark={0}
+
+"""
+
+    auth_tmpl = {"psk": Template("""\
+    left=0.0.0.0
+    right=$remote_ip
+    authby=psk"""),
+                 "pki_remote": Template("""\
+    left=0.0.0.0
+    right=$remote_ip
+    leftid=$local_name
+    rightid=$remote_name
+    leftcert=$certificate
+    rightcert=$remote_cert"""),
+                 "pki_ca": Template("""\
+    left=0.0.0.0
+    right=$remote_ip
+    leftid=$local_name
+    rightid=$remote_name
+    leftcert=$certificate""")}
+
+    def __init__(self, root_prefix):
+        self.CHARON_CONF = root_prefix + "/etc/strongswan.d/ovs.conf"
+        self.IPSEC = root_prefix + "/usr/sbin/ipsec"
+        self.IPSEC_CONF = root_prefix + "/etc/ipsec.conf"
+        self.IPSEC_SECRETS = root_prefix + "/etc/ipsec.secrets"
+        self.conf_file = None
+        self.secrets_file = None
+
+    def restart_ike_daemon(self):
+        """This function restarts StrongSwan."""
+        f = open(self.CHARON_CONF, "w")
+        f.write(self.STRONGSWAN_CONF)
+        f.close()
+
+        f = open(self.IPSEC_CONF, "w")
+        f.write(self.CONF_HEADER)
+        f.close()
+
+        f = open(self.IPSEC_SECRETS, "w")
+        f.write(FILE_HEADER)
+        f.close()
+
+        vlog.info("Restarting StrongSwan")
+        subprocess.call([self.IPSEC, "restart"])
+
+    def get_active_conns(self):
+        """This function parses output from 'ipsec status' command.
+        It returns dictionary where <key> is interface name (as in OVSDB)
+        and <value> is another dictionary.  This another dictionary
+        uses strongSwan connection name as <key> and more detailed
+        sample line from the parsed outpus as <value>. """
+
+        conns = {}
+        proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE)
+
+        while True:
+            line = proc.stdout.readline().strip()
+            if line == '':
+                break
+            tunnel_name = line.split(":")
+            if len(tunnel_name) < 2:
+                continue
+            m = re.match(r"(.*)(-in-\d+|-out-\d+|-\d+).*", tunnel_name[0])
+            if not m:
+                continue
+            ifname = m.group(1)
+            if ifname not in conns:
+                conns[ifname] = {}
+            (conns[ifname])[tunnel_name[0]] = line
+
+        return conns
+
+    def config_init(self):
+        self.conf_file = open(self.IPSEC_CONF, "w")
+        self.secrets_file = open(self.IPSEC_SECRETS, "w")
+        self.conf_file.write(self.CONF_HEADER)
+        self.secrets_file.write(FILE_HEADER)
+
+    def config_global(self, monitor):
+        """Configure the global state of IPsec tunnels."""
+        needs_refresh = False
+
+        if monitor.conf_in_use != monitor.conf:
+            monitor.conf_in_use = copy.deepcopy(monitor.conf)
+            needs_refresh = True
+
+        # Configure the shunt policy
+        if monitor.conf_in_use["skb_mark"]:
+            skb_mark = monitor.conf_in_use["skb_mark"]
+            self.conf_file.write(self.SHUNT_POLICY.format(skb_mark))
+
+        # Configure the CA cert
+        if monitor.conf_in_use["pki"]["ca_cert"]:
+            cacert = monitor.conf_in_use["pki"]["ca_cert"]
+            self.conf_file.write(self.CA_SECTION % cacert)
+
+        return needs_refresh
+
+    def config_tunnel(self, tunnel):
+        if tunnel.conf["psk"]:
+            self.secrets_file.write('0.0.0.0 %s : PSK "%s"\n' %
+                            (tunnel.conf["remote_ip"], tunnel.conf["psk"]))
+            auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf)
+        else:
+            self.secrets_file.write("0.0.0.0 %s : RSA %s\n" %
+                                        (tunnel.conf["remote_ip"],
+                                        tunnel.conf["private_key"]))
+            if tunnel.conf["remote_cert"]:
+                tmpl = self.auth_tmpl["pki_remote"]
+                auth_section = tmpl.substitute(tunnel.conf)
+            else:
+                tmpl = self.auth_tmpl["pki_ca"]
+                auth_section = tmpl.substitute(tunnel.conf)
+
+        vals = tunnel.conf.copy()
+        vals["auth_section"] = auth_section
+        vals["version"] = tunnel.version
+        conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals)
+        self.conf_file.write(conf_text)
+
+    def config_fini(self):
+        self.secrets_file.close()
+        self.conf_file.close()
+        self.secrets_file = None
+        self.conf_file = None
+
+    def refresh(self, monitor):
+        """This functions refreshes strongSwan configuration.  Behind the
+        scenes this function calls:
+        1. once "ipsec update" command that tells strongSwan to load
+           all new tunnels from "ipsec.conf"; and
+        2. once "ipsec rereadsecrets" command that tells strongswan to load
+           secrets from "ipsec.conf" file
+        3. for every removed tunnel "ipsec stroke down-nb <tunnel>" command
+           that removes old tunnels.
+        Once strongSwan vici bindings will be distributed with major
+        Linux distributions this function could be simplified."""
+        vlog.info("Refreshing StrongSwan configuration")
+        subprocess.call([self.IPSEC, "update"])
+        subprocess.call([self.IPSEC, "rereadsecrets"])
+        # "ipsec update" command does not remove those tunnels that were
+        # updated or that disappeared from the ipsec.conf file.  So, we have
+        # to manually remove them by calling "ipsec stroke down-nb <tunnel>"
+        # command.  We use <version> number to tell apart tunnels that
+        # were just updated.
+        # "ipsec down-nb" command is designed to be non-blocking (opposed
+        # to "ipsec down" command).  This means that we should not be concerned
+        # about possibility of ovs-monitor-ipsec to block for each tunnel
+        # while strongSwan sends IKE messages over Internet.
+        conns_dict = self.get_active_conns()
+        for ifname, conns in conns_dict.iteritems():
+            tunnel = monitor.tunnels.get(ifname)
+            for conn in conns:
+                # IPsec "connection" names that we choose in strongswan
+                # must start with Interface name
+                if not conn.startswith(ifname):
+                    vlog.err("%s does not start with %s" % (conn, ifname))
+                    continue
+
+                # version number should be the first integer after
+                # interface name in IPsec "connection"
+                try:
+                    ver = int(re.findall(r'\d+', conn[len(ifname):])[0])
+                except IndexError:
+                    vlog.err("%s does not contain version number")
+                    continue
+                except ValueError:
+                    vlog.err("%s does not contain version number")
+                    continue
+
+                if not tunnel or tunnel.version != ver:
+                    vlog.info("%s is outdated %u" % (conn, ver))
+                    subprocess.call([self.IPSEC, "stroke", "down-nb", conn])
+
+
+class LibreSwanHelper(object):
+    """This class does LibreSwan specific configurations."""
+    CONF_HEADER = """%s
+config setup
+    uniqueids=yes
+
+conn %%default
+    keyingtries=%%forever
+    type=transport
+    auto=route
+    ike=aes_gcm256-sha2_256
+    esp=aes_gcm256
+    ikev2=insist
+
+""" % (FILE_HEADER)
+
+    SHUNT_POLICY = """conn prevent_unencrypted_gre
+    type=drop
+    left=%defaultroute
+    leftprotoport=gre
+    mark={0}
+
+conn prevent_unencrypted_geneve
+    type=drop
+    left=%defaultroute
+    leftprotoport=udp/6081
+    mark={0}
+
+conn prevent_unencrypted_stt
+    type=drop
+    left=%defaultroute
+    leftprotoport=tcp/7471
+    mark={0}
+
+conn prevent_unencrypted_vxlan
+    type=drop
+    left=%defaultroute
+    leftprotoport=udp/4789
+    mark={0}
+
+"""
+
+    auth_tmpl = {"psk": Template("""\
+    left=%defaultroute
+    right=$remote_ip
+    authby=secret"""),
+                 "pki_remote": Template("""\
+    left=%defaultroute
+    right=$remote_ip
+    leftid=@$local_name
+    rightid=@$remote_name
+    leftcert="$local_name"
+    rightcert="$remote_name"
+    leftrsasigkey=%cert"""),
+                 "pki_ca": Template("""\
+    left=%defaultroute
+    right=$remote_ip
+    leftid=@$local_name
+    rightid=@$remote_name
+    leftcert="ovs_certkey_$local_name"
+    leftrsasigkey=%cert
+    rightca=%same""")}
+
+    CERT_PREFIX = "ovs_cert_"
+    CERTKEY_PREFIX = "ovs_certkey_"
+
+    def __init__(self, libreswan_root_prefix):
+        self.IPSEC = libreswan_root_prefix + "/usr/sbin/ipsec"
+        self.IPSEC_CONF = libreswan_root_prefix + "/etc/ipsec.conf"
+        self.IPSEC_SECRETS = libreswan_root_prefix + "/etc/ipsec.secrets"
+        self.conf_file = None
+        self.secrets_file = None
+
+    def restart_ike_daemon(self):
+        """This function restarts LibreSwan."""
+        # Remove the stale information from the NSS database
+        self._nss_clear_database()
+
+        f = open(self.IPSEC_CONF, "w")
+        f.write(self.CONF_HEADER)
+        f.close()
+
+        f = open(self.IPSEC_SECRETS, "w")
+        f.write(FILE_HEADER)
+        f.close()
+
+        vlog.info("Restarting LibreSwan")
+        subprocess.call([self.IPSEC, "restart"])
+
+    def config_init(self):
+        self.conf_file = open(self.IPSEC_CONF, "w")
+        self.secrets_file = open(self.IPSEC_SECRETS, "w")
+        self.conf_file.write(self.CONF_HEADER)
+        self.secrets_file.write(FILE_HEADER)
+
+    def config_global(self, monitor):
+        """Configure the global state of IPsec tunnels."""
+        needs_refresh = False
+
+        if monitor.conf_in_use["pki"] != monitor.conf["pki"]:
+            # Clear old state
+            if monitor.conf_in_use["pki"]["certificate"]:
+                local_name = monitor.conf_in_use["pki"]["local_name"]
+                self._nss_delete_cert_and_key(self.CERTKEY_PREFIX + local_name)
+
+            if monitor.conf_in_use["pki"]["ca_cert"]:
+                self._nss_delete_cert(self.CERT_PREFIX + "cacert")
+
+            # Load new state
+            if monitor.conf["pki"]["certificate"]:
+                cert = monitor.conf["pki"]["certificate"]
+                key = monitor.conf["pki"]["private_key"]
+                name = monitor.conf["pki"]["local_name"]
+                name = self.CERTKEY_PREFIX + name
+                self._nss_import_cert_and_key(cert, key, name)
+
+            if monitor.conf["pki"]["ca_cert"]:
+                self._nss_import_cert(monitor.conf["pki"]["ca_cert"],
+                                      self.CERT_PREFIX + "cacert", 'CT,,')
+
+            monitor.conf_in_use["pki"] = copy.deepcopy(monitor.conf["pki"])
+            needs_refresh = True
+
+        # Configure the shunt policy
+        if monitor.conf["skb_mark"]:
+            skb_mark = monitor.conf["skb_mark"]
+            self.conf_file.write(self.SHUNT_POLICY.format(skb_mark))
+
+        # Will update conf_in_use later in the 'refresh' method
+        if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
+            needs_refresh = True
+
+        return needs_refresh
+
+    def config_tunnel(self, tunnel):
+        if tunnel.conf["psk"]:
+            self.secrets_file.write('%%any %s : PSK "%s"\n' %
+                            (tunnel.conf["remote_ip"], tunnel.conf["psk"]))
+            auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf)
+        elif tunnel.conf["remote_cert"]:
+            auth_section = self.auth_tmpl["pki_remote"].substitute(tunnel.conf)
+            self._nss_import_cert(tunnel.conf["remote_cert"],
+                                 self.CERT_PREFIX + tunnel.conf["remote_name"],
+                                 'P,P,P')
+        else:
+            auth_section = self.auth_tmpl["pki_ca"].substitute(tunnel.conf)
+
+        vals = tunnel.conf.copy()
+        vals["auth_section"] = auth_section
+        vals["version"] = tunnel.version
+        conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals)
+        self.conf_file.write(conf_text)
+
+    def config_fini(self):
+        self.secrets_file.close()
+        self.conf_file.close()
+        self.secrets_file = None
+        self.conf_file = None
+
+    def clear_tunnel_state(self, tunnel):
+        if tunnel.conf["remote_cert"]:
+            name = self.CERT_PREFIX + tunnel.conf["remote_name"]
+            self._nss_delete_cert(name)
+
+    def refresh(self, monitor):
+        vlog.info("Refreshing LibreSwan configuration")
+        subprocess.call([self.IPSEC, "auto", "--rereadsecrets"])
+        tunnels = set(monitor.tunnels.keys())
+
+        # Delete old connections
+        conns_dict = self.get_active_conns()
+        for ifname, conns in conns_dict.iteritems():
+            tunnel = monitor.tunnels.get(ifname)
+
+            for conn in conns:
+                # IPsec "connection" names must start with Interface name
+                if not conn.startswith(ifname):
+                    vlog.err("%s does not start with %s" % (conn, ifname))
+                    continue
+
+                # version number should be the first integer after
+                # interface name in IPsec "connection"
+                try:
+                    ver = int(re.findall(r'\d+', conn[len(ifname):])[0])
+                except ValueError:
+                    vlog.err("%s does not contain version number")
+                    continue
+                except IndexError:
+                    vlog.err("%s does not contain version number")
+                    continue
+
+                if not tunnel or tunnel.version != ver:
+                    vlog.info("%s is outdated %u" % (conn, ver))
+                    subprocess.call([self.IPSEC, "auto", "--delete", conn])
+                elif ifname in tunnels:
+                    tunnels.remove(ifname)
+
+        # Activate new connections
+        for name in tunnels:
+            ver = monitor.tunnels[name].version
+
+            if monitor.tunnels[name].conf["tunnel_type"] == "gre":
+                conn = "%s-%s" % (name, ver)
+                self._start_ipsec_connection(conn)
+            else:
+                conn_in = "%s-in-%s" % (name, ver)
+                conn_out = "%s-out-%s" % (name, ver)
+                self._start_ipsec_connection(conn_in)
+                self._start_ipsec_connection(conn_out)
+
+        # Update shunt policy if changed
+        if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
+            if monitor.conf["skb_mark"]:
+                subprocess.call([self.IPSEC, "auto", "--add",
+                            "--asynchronous", "prevent_unencrypted_gre"])
+                subprocess.call([self.IPSEC, "auto", "--add",
+                            "--asynchronous", "prevent_unencrypted_geneve"])
+                subprocess.call([self.IPSEC, "auto", "--add",
+                            "--asynchronous", "prevent_unencrypted_stt"])
+                subprocess.call([self.IPSEC, "auto", "--add",
+                            "--asynchronous", "prevent_unencrypted_vxlan"])
+            else:
+                subprocess.call([self.IPSEC, "auto", "--delete",
+                            "--asynchronous", "prevent_unencrypted_gre"])
+                subprocess.call([self.IPSEC, "auto", "--delete",
+                            "--asynchronous", "prevent_unencrypted_geneve"])
+                subprocess.call([self.IPSEC, "auto", "--delete",
+                            "--asynchronous", "prevent_unencrypted_stt"])
+                subprocess.call([self.IPSEC, "auto", "--delete",
+                            "--asynchronous", "prevent_unencrypted_vxlan"])
+            monitor.conf_in_use["skb_mark"] = monitor.conf["skb_mark"]
+
+    def get_active_conns(self):
+        """This function parses output from 'ipsec status' command.
+        It returns dictionary where <key> is interface name (as in OVSDB)
+        and <value> is another dictionary.  This another dictionary
+        uses LibreSwan connection name as <key> and more detailed
+        sample line from the parsed outpus as <value>. """
+
+        conns = {}
+        proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE)
+
+        while True:
+            line = proc.stdout.readline().strip()
+            if line == '':
+                break
+
+            m = re.search(r"#\d+: \"(.*)\".*", line)
+            if not m:
+                continue
+
+            conn = m.group(1)
+            m = re.match(r"(.*)(-in-\d+|-out-\d+|-\d+)", conn)
+            if not m:
+                continue
+
+            ifname = m.group(1)
+            if ifname not in conns:
+                conns[ifname] = {}
+            (conns[ifname])[conn] = line
+
+        return conns
+
+    def _start_ipsec_connection(self, conn):
+        # In a corner case, LibreSwan daemon restarts for some reason and
+        # the "ipsec auto --start" command is lost. Just retry to make sure
+        # the command is received by LibreSwan.
+        while True:
+            proc = subprocess.Popen([self.IPSEC, "auto", "--start",
+                                    "--asynchronous", conn],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            perr = str(proc.stderr.read())
+            pout = str(proc.stdout.read())
+            if not re.match(r".*Connection refused.*", perr) and \
+                    not re.match(r".*need --listen.*", pout):
+                break
+
+    def _nss_clear_database(self):
+        """Remove all OVS IPsec related state from the NSS database"""
+        try:
+            proc = subprocess.Popen(['certutil', '-L', '-d',
+                                    'sql:/etc/ipsec.d/'],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            lines = proc.stdout.readlines()
+
+            for line in lines:
+                s = line.strip().split()
+                if len(s) < 1:
+                    continue
+                name = s[0]
+                if name.startswith(self.CERT_PREFIX):
+                    self._nss_delete_cert(name)
+                elif name.startswith(self.CERTKEY_PREFIX):
+                    self._nss_delete_cert_and_key(name)
+
+        except Exception as e:
+            vlog.err("Failed to clear NSS database.\n" + str(e))
+
+    def _nss_import_cert(self, cert, name, cert_type):
+        """Cert_type is 'CT,,' for the CA certificate and 'P,P,P' for the
+        normal certificate."""
+        try:
+            proc = subprocess.Popen(['certutil', '-A', '-a', '-i', cert,
+                                    '-d', 'sql:/etc/ipsec.d/', '-n',
+                                    name, '-t', cert_type],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode:
+                raise Exception(proc.stderr.read())
+        except Exception as e:
+            vlog.err("Failed to import ceretificate into NSS.\n" + str(e))
+
+    def _nss_delete_cert(self, name):
+        try:
+            proc = subprocess.Popen(['certutil', '-D', '-d',
+                                    'sql:/etc/ipsec.d/', '-n', name],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode:
+                raise Exception(proc.stderr.read())
+        except Exception as e:
+            vlog.err("Failed to delete ceretificate from NSS.\n" + str(e))
+
+    def _nss_import_cert_and_key(self, cert, key, name):
+        try:
+            # Avoid deleting other files
+            path = os.path.abspath('/tmp/%s.p12' % name)
+            if not path.startswith('/tmp/'):
+                raise Exception("Illegal certificate name!")
+
+            # Create p12 file from pem files
+            proc = subprocess.Popen(['openssl', 'pkcs12', '-export',
+                                    '-in', cert, '-inkey', key, '-out',
+                                    path, '-name', name, '-passout', 'pass:'],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode:
+                raise Exception(proc.stderr.read())
+
+            # Load p12 file to the database
+            proc = subprocess.Popen(['pk12util', '-i', path, '-d',
+                                    'sql:/etc/ipsec.d/', '-W', ''],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode:
+                raise Exception(proc.stderr.read())
+
+        except Exception as e:
+            vlog.err("Import cert and key failed.\n" + str(e))
+        os.remove(path)
+
+    def _nss_delete_cert_and_key(self, name):
+        try:
+            # Delete certificate and private key
+            proc = subprocess.Popen(['certutil', '-F', '-d',
+                                    'sql:/etc/ipsec.d/', '-n', name],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode:
+                raise Exception(proc.stderr.read())
+
+        except Exception as e:
+            vlog.err("Delete cert and key failed.\n" + str(e))
+
+
+class IPsecTunnel(object):
+    """This is the base class for IPsec tunnel."""
+
+    unixctl_config_tmpl = Template("""\
+  Tunnel Type:    $tunnel_type
+  Remote IP:      $remote_ip
+  SKB mark:       $skb_mark
+  Local cert:     $certificate
+  Local name:     $local_name
+  Local key:      $private_key
+  Remote cert:    $remote_cert
+  Remote name:    $remote_name
+  CA cert:        $ca_cert
+  PSK:            $psk
+""")
+
+    unixctl_status_tmpl = Template("""\
+  Ofport:         $ofport
+  CFM state:      $cfm_state
+""")
+
+    def __init__(self, name, row):
+        self.name = name  # 'name' will not change because it is key in OVSDB
+        self.version = 0  # 'version' is increased on configuration changes
+        self.last_refreshed_version = -1
+        self.state = "INIT"
+        self.conf = {}
+        self.status = {}
+        self.update_conf(row)
+
+    def update_conf(self, row):
+        """This function updates IPsec tunnel configuration by using 'row'
+        from OVSDB interface table.  If configuration was actually changed
+        in OVSDB then this function returns True.  Otherwise, it returns
+        False."""
+        ret = False
+        options = row.options
+        remote_cert = options.get("remote_cert")
+        remote_name = options.get("remote_name")
+        if remote_cert:
+            remote_name = monitor._get_cn_from_cert(remote_cert)
+
+        new_conf = {
+            "ifname": self.name,
+            "tunnel_type": row.type,
+            "remote_ip": options.get("remote_ip"),
+            "skb_mark": monitor.conf["skb_mark"],
+            "certificate": monitor.conf["pki"]["certificate"],
+            "private_key": monitor.conf["pki"]["private_key"],
+            "ca_cert": monitor.conf["pki"]["ca_cert"],
+            "remote_cert": remote_cert,
+            "remote_name": remote_name,
+            "local_name": monitor.conf["pki"]["local_name"],
+            "psk": options.get("psk")}
+
+        if self.conf != new_conf:
+            # Configuration was updated in OVSDB.  Validate it and figure
+            # out what to do next with this IPsec tunnel.  Also, increment
+            # version number of this IPsec tunnel so that we could tell
+            # apart old and new tunnels in "ipsec status" output.
+            self.version += 1
+            ret = True
+            self.conf = new_conf
+
+            if self._is_valid_tunnel_conf():
+                self.state = "CONFIGURED"
+            else:
+                vlog.warn("%s contains invalid configuration%s" %
+                          (self.name, self.invalid_reason))
+                self.state = "INVALID"
+
+        new_status = {
+            "cfm_state": "Up" if row.cfm_fault == [False] else
+                         "Down" if row.cfm_fault == [True] else
+                         "Disabled",
+            "ofport": "Not assigned" if (row.ofport in [[], [-1]]) else
+                      row.ofport[0]}
+
+        if self.status != new_status:
+            # Tunnel has become unhealthy or ofport changed.  Simply log this.
+            vlog.dbg("%s changed status from %s to %s" %
+                     (self.name, str(self.status), str(new_status)))
+            self.status = new_status
+        return ret
+
+    def mark_for_removal(self):
+        """This function marks tunnel for removal."""
+        self.version += 1
+        self.state = "REMOVED"
+
+    def show(self, policies, securities, conns):
+        state = self.state
+        if self.state == "INVALID":
+            state += self.invalid_reason
+        header = "Interface name: %s v%u (%s)\n" % (self.name, self.version,
+                                                    state)
+        conf = self.unixctl_config_tmpl.substitute(self.conf)
+        status = self.unixctl_status_tmpl.substitute(self.status)
+        spds = "Kernel policies installed:\n"
+        remote_ip = self.conf["remote_ip"]
+        if remote_ip in policies:
+            for line in policies[remote_ip]:
+                spds += "  " + line + "\n"
+        sas = "Kernel security associations installed:\n"
+        if remote_ip in securities:
+            for line in securities[remote_ip]:
+                sas += "  " + line + "\n"
+        cons = "IPsec connections that are active:\n"
+        if self.name in conns:
+            for tname in conns[self.name]:
+                cons += "  " + conns[self.name][tname] + "\n"
+
+        return header + conf + status + spds + sas + cons + "\n"
+
+    def _is_valid_tunnel_conf(self):
+        """This function verifies if IPsec tunnel has valid configuration
+        set in 'conf'.  If it is valid, then it returns True.  Otherwise,
+        it returns False and sets the reason why configuration was considered
+        as invalid.
+
+        This function could be improved in future to also verify validness
+        of certificates themselves so that ovs-monitor-ipsec would not
+        pass malformed configuration to IKE daemon."""
+
+        self.invalid_reason = None
+
+        if not self.conf["remote_ip"]:
+            self.invalid_reason = ": 'remote_ip' is not set"
+            return False
+
+        if self.conf["psk"]:
+            if self.conf["certificate"] or self.conf["private_key"] \
+                    or self.conf["ca_cert"] or self.conf["remote_cert"] \
+                    or self.conf["remote_name"]:
+                self.invalid_reason = ": 'certificate', 'private_key', "\
+                                      "'ca_cert', 'remote_cert', and "\
+                                      "'remote_name' must be unset with PSK"
+                return False
+        # If configuring authentication with CA-signed certificate or
+        # self-signed certificate, the 'remote_name' should be specified at
+        # this point.  When using CA-signed certificate, the 'remote_name' is
+        # read from interface's options field. When using self-signed
+        # certificate, the 'remote_name' is extracted from the 'remote_cert'
+        # file.
+        elif self.conf["remote_name"]:
+            if not self.conf["certificate"]:
+                self.invalid_reason = ": must set 'certificate' as local"\
+                                      " certificate when using CA-signed"\
+                                      " certificate or self-signed"\
+                                      " certificate to authenticate peers"
+                return False
+            elif not self.conf["private_key"]:
+                self.invalid_reason = ": must set 'private_key' as local"\
+                                      " private key when using CA-signed"\
+                                      " certificate or self-signed"\
+                                      " certificate to authenticate peers"
+                return False
+            if not self.conf["remote_cert"] and not self.conf["ca_cert"]:
+                self.invalid_reason = ": must set 'remote_cert' when using"\
+                                      " self-signed certificate"\
+                                      " authentication or 'ca_cert' when"\
+                                      " using CA-signed certificate"\
+                                      " authentication"
+                return False
+        else:
+            self.invalid_reason = ": must choose a authentication method"
+            return False
+
+        return True
+
+
+class IPsecMonitor(object):
+    """This class monitors and configures IPsec tunnels"""
+
+    def __init__(self, root_prefix, ike_daemon):
+        self.IPSEC = root_prefix + "/usr/sbin/ipsec"
+        self.tunnels = {}
+
+        # Global configuration shared by all tunnels
+        self.conf = {
+            "pki": {
+                "private_key": None,
+                "certificate": None,
+                "ca_cert": None,
+                "local_name": None
+            },
+            "skb_mark": None
+        }
+        self.conf_in_use = copy.deepcopy(self.conf)
+
+        # Choose to either use StrongSwan or LibreSwan as IKE daemon
+        if ike_daemon == "strongswan":
+            self.ike_helper = StrongSwanHelper(root_prefix)
+        elif ike_daemon == "libreswan":
+            self.ike_helper = LibreSwanHelper(root_prefix)
+        else:
+            vlog.err("The IKE daemon should be strongswan or libreswan.")
+            sys.exit(1)
+
+        # Check whether ipsec command is available
+        if not os.path.isfile(self.IPSEC) or \
+                not os.access(self.IPSEC, os.X_OK):
+            vlog.err("IKE daemon is not installed in the system.")
+
+        self.ike_helper.restart_ike_daemon()
+
+    def is_tunneling_type_supported(self, tunnel_type):
+        """Returns True if we know how to configure IPsec for these
+        types of tunnels.  Otherwise, returns False."""
+        return tunnel_type in ["gre", "geneve", "vxlan", "stt"]
+
+    def is_ipsec_required(self, options_column):
+        """Return True if tunnel needs to be encrypted.  Otherwise,
+        returns False."""
+        return "psk" in options_column or \
+                "remote_name" in options_column or \
+                "remote_cert" in options_column
+
+    def add_tunnel(self, name, row):
+        """Adds a new tunnel that monitor will provision with 'name'."""
+        vlog.info("Tunnel %s appeared in OVSDB" % (name))
+        self.tunnels[name] = IPsecTunnel(name, row)
+
+    def update_tunnel(self, name, row):
+        """Updates configuration of already existing tunnel with 'name'."""
+        tunnel = self.tunnels[name]
+        if tunnel.update_conf(row):
+            vlog.info("Tunnel's '%s' configuration changed in OVSDB to %u" %
+                      (tunnel.name, tunnel.version))
+
+    def del_tunnel(self, name):
+        """Deletes tunnel by 'name'."""
+        vlog.info("Tunnel %s disappeared from OVSDB" % (name))
+        self.tunnels[name].mark_for_removal()
+
+    def update_conf(self, pki, skb_mark):
+        """Update the global configuration for IPsec tunnels"""
+        self.conf["pki"]["certificate"] = pki[0]
+        self.conf["pki"]["private_key"] = pki[1]
+        self.conf["pki"]["ca_cert"] = pki[2]
+        self.conf["pki"]["local_name"] = pki[3]
+
+        # Update skb_mark used in IPsec policies.
+        self.conf["skb_mark"] = skb_mark
+
+    def read_ovsdb_open_vswitch_table(self, data):
+        """This functions reads IPsec relevant configuration from Open_vSwitch
+        table."""
+        pki = [None, None, None, None]
+        skb_mark = None
+        is_valid = False
+
+        for row in data["Open_vSwitch"].rows.itervalues():
+            pki[0] = row.other_config.get("certificate")
+            pki[1] = row.other_config.get("private_key")
+            pki[2] = row.other_config.get("ca_cert")
+            skb_mark = row.other_config.get("ipsec_skb_mark")
+
+        # Test whether it's a valid configration
+        if pki[0] and pki[1]:
+            pki[3] = self._get_cn_from_cert(pki[0])
+            if pki[3]:
+                is_valid = True
+        elif not pki[0] and not pki[1] and not pki[2]:
+            is_valid = True
+
+        if not is_valid:
+            vlog.warn("The cert and key configuration is not valid. "
+                "The valid configuations are 1): certificate, private_key "
+                "and ca_cert are not set; or 2): certificate and "
+                "private_key are all set.")
+        else:
+            self.update_conf(pki, skb_mark)
+
+    def read_ovsdb_interface_table(self, data):
+        """This function reads the IPsec relevant configuration from Interface
+        table."""
+        ifaces = set()
+
+        for row in data["Interface"].rows.itervalues():
+            if not self.is_tunneling_type_supported(row.type):
+                continue
+            if not self.is_ipsec_required(row.options):
+                continue
+            if row.name in self.tunnels:
+                self.update_tunnel(row.name, row)
+            else:
+                self.add_tunnel(row.name, row)
+            ifaces.add(row.name)
+
+        # Mark for removal those tunnels that just disappeared from OVSDB
+        for tunnel in self.tunnels.keys():
+            if tunnel not in ifaces:
+                self.del_tunnel(tunnel)
+
+    def read_ovsdb(self, data):
+        """This function reads all configuration from OVSDB that
+        ovs-monitor-ipsec is interested in."""
+        self.read_ovsdb_open_vswitch_table(data)
+        self.read_ovsdb_interface_table(data)
+
+    def show(self, unix_conn, policies, securities):
+        """This function prints all tunnel state in 'unix_conn'.
+        It uses 'policies' and securities' received from Linux Kernel
+        to show if tunnels were actually configured by the IKE deamon."""
+        if not self.tunnels:
+            unix_conn.reply("No tunnels configured with IPsec")
+            return
+        s = ""
+        conns = self.ike_helper.get_active_conns()
+        for name, tunnel in self.tunnels.iteritems():
+            s += tunnel.show(policies, securities, conns)
+        unix_conn.reply(s)
+
+    def run(self):
+        """This function runs state machine that represents whole
+        IPsec configuration (i.e. merged together from individual
+        tunnel state machines).  It creates configuration files and
+        tells IKE daemon to update configuration."""
+        needs_refresh = False
+        removed_tunnels = []
+
+        self.ike_helper.config_init()
+
+        if self.ike_helper.config_global(self):
+            needs_refresh = True
+
+        for name, tunnel in self.tunnels.iteritems():
+            if tunnel.last_refreshed_version != tunnel.version:
+                tunnel.last_refreshed_version = tunnel.version
+                needs_refresh = True
+
+            if tunnel.state == "REMOVED" or tunnel.state == "INVALID":
+                removed_tunnels.append(name)
+            elif tunnel.state == "CONFIGURED":
+                self.ike_helper.config_tunnel(self.tunnels[name])
+
+        self.ike_helper.config_fini()
+
+        for name in removed_tunnels:
+            # LibreSwan needs to clear state from database
+            if hasattr(self.ike_helper, "clear_tunnel_state"):
+                self.ike_helper.clear_tunnel_state(self.tunnels[name])
+            del self.tunnels[name]
+
+        if needs_refresh:
+            self.ike_helper.refresh(self)
+
+    def _get_cn_from_cert(self, cert):
+        try:
+            proc = subprocess.Popen(['openssl', 'x509', '-noout', '-subject',
+                                    '-nameopt', 'RFC2253', '-in', cert],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode:
+                raise Exception(proc.stderr.read())
+            m = re.search(r"CN=(.+?),", proc.stdout.readline())
+            if not m:
+                raise Exception("No CN in the certificate subject.")
+        except Exception as e:
+            vlog.warn(str(e))
+            return None
+
+        return m.group(1)
+
+
+def unixctl_xfrm_policies(conn, unused_argv, unused_aux):
+    global xfrm
+    policies = xfrm.get_policies()
+    conn.reply(str(policies))
+
+
+def unixctl_xfrm_state(conn, unused_argv, unused_aux):
+    global xfrm
+    securities = xfrm.get_securities()
+    conn.reply(str(securities))
+
+
+def unixctl_ipsec_status(conn, unused_argv, unused_aux):
+    global monitor
+    conns = monitor.ike_helper.get_active_conns()
+    conn.reply(str(conns))
+
+
+def unixctl_show(conn, unused_argv, unused_aux):
+    global monitor
+    global xfrm
+    policies = xfrm.get_policies()
+    securities = xfrm.get_securities()
+    monitor.show(conn, policies, securities)
+
+
+def unixctl_refresh(conn, unused_argv, unused_aux):
+    global monitor
+    monitor.ike_helper.refresh(monitor)
+    conn.reply(None)
+
+
+def unixctl_exit(conn, unused_argv, unused_aux):
+    global monitor
+    global exiting
+    exiting = True
+
+    # Make sure persistent global states are cleared
+    monitor.update_conf([None, None, None, None], None)
+    # Make sure persistent tunnel states are cleared
+    for tunnel in monitor.tunnels.keys():
+        monitor.del_tunnel(tunnel)
+    monitor.run()
+
+    conn.reply(None)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("database", metavar="DATABASE",
+                        help="A socket on which ovsdb-server is listening.")
+    parser.add_argument("--root-prefix", metavar="DIR",
+                        help="Use DIR as alternate root directory"
+                        " (for testing).")
+    parser.add_argument("--ike-daemon", metavar="IKE-DAEMON",
+                        help="The IKE daemon used for IPsec tunnels"
+                        " (either libreswan or strongswan).")
+
+    ovs.vlog.add_args(parser)
+    ovs.daemon.add_args(parser)
+    args = parser.parse_args()
+    ovs.vlog.handle_args(args)
+    ovs.daemon.handle_args(args)
+
+    global monitor
+    global xfrm
+
+    root_prefix = args.root_prefix if args.root_prefix else ""
+    xfrm = XFRM(root_prefix)
+    monitor = IPsecMonitor(root_prefix, args.ike_daemon)
+
+    remote = args.database
+    schema_helper = ovs.db.idl.SchemaHelper()
+    schema_helper.register_columns("Interface",
+                                   ["name", "type", "options", "cfm_fault",
+                                    "ofport"])
+    schema_helper.register_columns("Open_vSwitch", ["other_config"])
+    idl = ovs.db.idl.Idl(remote, schema_helper)
+
+    ovs.daemon.daemonize()
+
+    ovs.unixctl.command_register("xfrm/policies", "", 0, 0,
+                                 unixctl_xfrm_policies, None)
+    ovs.unixctl.command_register("xfrm/state", "", 0, 0,
+                                 unixctl_xfrm_state, None)
+    ovs.unixctl.command_register("ipsec/status", "", 0, 0,
+                                 unixctl_ipsec_status, None)
+    ovs.unixctl.command_register("tunnels/show", "", 0, 0,
+                                 unixctl_show, None)
+    ovs.unixctl.command_register("refresh", "", 0, 0, unixctl_refresh, None)
+    ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
+
+    error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None)
+    if error:
+        ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
+
+    # Sequence number when OVSDB was processed last time
+    seqno = idl.change_seqno
+
+    while True:
+        unixctl_server.run()
+        if exiting:
+            break
+
+        idl.run()
+        if seqno != idl.change_seqno:
+            monitor.read_ovsdb(idl.tables)
+            seqno = idl.change_seqno
+
+        monitor.run()
+
+        poller = ovs.poller.Poller()
+        unixctl_server.wait(poller)
+        idl.wait(poller)
+        poller.block()
+
+    unixctl_server.close()
+    idl.close()
+
+
+if __name__ == '__main__':
+    try:
+        main()
+    except SystemExit:
+        # Let system.exit() calls complete normally
+        raise
+    except:
+        vlog.exception("traceback")
+        sys.exit(ovs.daemon.RESTART_EXIT_CODE)