We'll look forward to it even if it takes a while. Thank you. (If it does turn out that you don't want to work on this anymore, please let me know. I will get someone else to finish it up.)
On Mon, Aug 13, 2018 at 06:41:42AM -0400, Qiuyu Xiao wrote: > Thanks for the review! I will address your comments and post the next > revision. But it might take a while because I need to deal with school stuff. > > Thanks, > Qiuyu > > > On Aug 13, 2018, at 2:33 AM, Ansis Atteka <[email protected]> wrote: > > > > On Tue, 7 Aug 2018 at 09:43, Qiuyu Xiao <[email protected] > > <mailto:[email protected]>> wrote: > >> > >> This patch reintroduces ovs-monitor-ipsec daemon that > >> was previously removed by commit 2b02d770 ("openvswitch: > >> Allow external IPsec tunnel management.") > >> > >> After this patch, there are no IPsec flavored tunnels anymore. > >> IPsec is enabled by setting up the right values in: > >> 1. OVSDB:Interface:options column; > >> 2. OVSDB:Open_vSwitch:other_config column; > >> 3. OpenFlow pipeline. > >> > >> GRE, VXLAN, GENEVE, and STT IPsec tunnels are supported. LibreSwan and > >> StrongSwan IKE daemons are supported. User can choose pre-shared key, > >> self-signed peer certificate, or CA-signed certificate as authentication > >> method. > > s/mehod/methods > >> > >> Signed-off-by: Qiuyu Xiao <[email protected] > >> <mailto:[email protected]>> > >> Signed-off-by: Ansis Atteka <[email protected] <mailto:[email protected]>> > >> Co-authored-by: Ansis Atteka <[email protected] <mailto:[email protected]>> > >> --- > > > > I have two high level comments that we privately discussed earlier on > > Friday: > > 1. the local_ip should be wildcardable. Otherwise, if routes change, > > then then packets may leak out unencrypted before local_ip gets > > explicitly updated by administrator as well. > > 2. the strongSwan/Ubuntu and libreswan/Fedora compatibility issue due > > to integrity check. I know that this could be strongswan or libreswan > > bug, but perhaps we could use some alternate configuration that works? > > Did you find one? > > > > Other than that see small implementation details > > > >> Makefile.am | 1 + > >> ipsec/automake.mk | 10 + > >> ipsec/ovs-monitor-ipsec | 1173 +++++++++++++++++++++++++++++++++++++++ > >> 3 files changed, 1184 insertions(+) > >> create mode 100644 ipsec/automake.mk > >> create mode 100755 ipsec/ovs-monitor-ipsec > >> > >> diff --git a/Makefile.am b/Makefile.am > >> index 788972804..aeb2d108f 100644 > >> --- a/Makefile.am > >> +++ b/Makefile.am > >> @@ -481,6 +481,7 @@ include tests/automake.mk > >> include include/automake.mk > >> include third-party/automake.mk > >> include debian/automake.mk > >> +include ipsec/automake.mk > >> include vswitchd/automake.mk > >> include ovsdb/automake.mk > >> include rhel/automake.mk > >> diff --git a/ipsec/automake.mk b/ipsec/automake.mk > >> new file mode 100644 > >> index 000000000..1e530cb42 > >> --- /dev/null > >> +++ b/ipsec/automake.mk > >> @@ -0,0 +1,10 @@ > >> +# Copyright (C) 2017 Nicira, Inc. > >> +# > >> +# Copying and distribution of this file, with or without modification, > >> +# are permitted in any medium without royalty provided the copyright > >> +# 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 > >> diff --git a/ipsec/ovs-monitor-ipsec b/ipsec/ovs-monitor-ipsec > >> new file mode 100755 > >> index 000000000..163b04004 > >> --- /dev/null > >> +++ b/ipsec/ovs-monitor-ipsec > >> @@ -0,0 +1,1173 @@ > >> +#!/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 > >> +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" > >> +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} > >> + > >> +""" > >> +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 > >> + > >> +""" > >> + > >> + auth_tmpl = {"psk": Template("""\ > >> + left=$local_ip > >> + right=$remote_ip > >> + authby=psk"""), > >> + "pki_remote": Template("""\ > >> + left=$local_ip > >> + right=$remote_ip > >> + leftid=$local_name > >> + rightid=$remote_name > >> + leftcert=$certificate > >> + rightcert=$remote_cert"""), > >> + "pki_ca": Template("""\ > >> + left=$local_ip > >> + 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 start_ike_daemon(self): > >> + """This function starts 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("Starting StrongSwan") > >> + subprocess.call([self.IPSEC, "start"]) > >> + > >> + 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+).*", 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(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('%s %s : PSK "%s"\n' % > >> + (tunnel.conf["local_ip"], > >> tunnel.conf["remote_ip"], > > > > local_ip should be wildcardable, if user wants to. Otherwise, if > > routes change then packets may sneak out in plain. > >> + tunnel.conf["psk"])) > >> + auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf) > >> + else: > >> + self.secrets_file.write("%s %s : RSA %s\n" % > >> + (tunnel.conf["local_ip"], > >> 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) > >> + > >> + auth_tmpl = {"psk": Template("""\ > >> + left=$local_ip > >> + right=$remote_ip > >> + authby=secret"""), > >> + "pki_remote": Template("""\ > >> + left=$local_ip > >> + right=$remote_ip > >> + leftid=@$local_name > >> + rightid=@$remote_name > >> + leftcert="$local_name" > >> + rightcert="$remote_name" > >> + leftrsasigkey=%cert"""), > >> + "pki_ca": Template("""\ > >> + left=$local_ip > >> + right=$remote_ip > >> + leftid=@$local_name > >> + rightid=@$remote_name > >> + leftcert="$local_name" > >> + leftrsasigkey=%cert > >> + rightca=%same""")} > >> + > >> + 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 start_ike_daemon(self): > >> + """This function starts LibreSwan.""" > >> + 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("Starting LibreSwan") > >> + subprocess.call([self.IPSEC, "start"]) > > Should you start or restart here? > > > > With libreswan. is there possibility of having some stale config after > > restart that may need to be wiped out? > > > > > >> + > >> + 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"]: > >> + > >> self._delete_local_certs_and_key(monitor.conf_in_use["pki"]) > >> + > >> + # Load new state > >> + if monitor.conf["pki"]["certificate"]: > >> + self._import_local_certs_and_key(monitor.conf["pki"]) > >> + > >> + monitor.conf_in_use["pki"] = > >> copy.deepcopy(monitor.conf["pki"]) > >> + needs_refresh = True > >> + > >> + # Configure the shunt policy > >> + if monitor.conf["skb_mark"]: > >> + > >> self.conf_file.write(SHUNT_POLICY.format(monitor.conf["skb_mark"])) > >> + > >> + if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]: > >> + 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('%s %s : PSK "%s"\n' % > >> + (tunnel.conf["local_ip"], > >> 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._import_remote_cert(tunnel.conf["remote_cert"], > >> + tunnel.conf["remote_name"]) > >> + 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"]: > >> + self._delete_remote_cert(tunnel.conf["remote_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 > >> + conn_in = "%s-in-%s" % (name, ver) > >> + conn_out = "%s-out-%s" % (name, ver) > >> + > >> + # 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_in], > >> + 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 > >> + > >> + while True: > >> + proc = subprocess.Popen([self.IPSEC, "auto", "--start", > >> + "--asynchronous", conn_out], > >> + 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 > >> + > >> + # Activate shunt policy if configured > >> + if monitor.conf["skb_mark"]: > > Should you call these commands only if skb_mark changed? Also, I am > > surprised you need to invoke them in the first place - would adding > > auto=start for libreswan shunt policies make this happen > > automatically(just like with strongSwan)? Maybe libreswan does not > > support that? > >> + subprocess.call([self.IPSEC, "auto", "--start", > >> + "--asynchronous", "prevent_unencrypted_gre"]) > >> + subprocess.call([self.IPSEC, "auto", "--start", > >> + "--asynchronous", > >> "prevent_unencrypted_geneve"]) > >> + subprocess.call([self.IPSEC, "auto", "--start", > >> + "--asynchronous", "prevent_unencrypted_stt"]) > >> + subprocess.call([self.IPSEC, "auto", "--start", > >> + "--asynchronous", > >> "prevent_unencrypted_vxlan"]) > >> + > >> + 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+)", conn) > >> + if not m: > >> + continue > >> + > >> + ifname = m.group(1) > >> + if ifname not in conns: > >> + conns[ifname] = {} > >> + (conns[ifname])[conn] = line > >> + > >> + return conns > >> + > >> + def _delete_remote_cert(self, remote_name): > >> + """Delete remote certiticate from the NSS database.""" > >> + try: > >> + proc = subprocess.Popen(['certutil', '-D', '-d', > >> + 'sql:/etc/ipsec.d/', '-n', > >> remote_name], > >> + stdout=subprocess.PIPE, > >> + stderr=subprocess.PIPE) > >> + proc.wait() > >> + if proc.returncode: > >> + raise Exception(proc.stderr.read()) > >> + except Exception as e: > >> + vlog.err("Delete remote certificate failed.\n" + str(e)) > > s/Delete remote certificate failed/Failed to delete remote > > ceretificate XXX from NSS: +str(e) > > > >> + > >> + def _import_remote_cert(self, cert, remote_name): > >> + """Import remote certificate to the NSS database.""" > >> + try: > >> + proc = subprocess.Popen(['certutil', '-A', '-a', '-i', cert, > >> + '-d', 'sql:/etc/ipsec.d/', '-n', > >> + remote_name, '-t', 'P,P,P'], > >> + stdout=subprocess.PIPE, > >> + stderr=subprocess.PIPE) > >> + proc.wait() > >> + if proc.returncode: > >> + raise Exception(proc.stderr.read()) > >> + except Exception as e: > >> + vlog.err("Import remote certificate failed.\n" + str(e)) > > I would write "Failed to import remote certificate XXX into NSS:" + str(e) > > > >> + > >> + def _delete_local_certs_and_key(self, pki): > >> + """Delete certs and key from the NSS database.""" > >> + name = pki["local_name"] > >> + cacert = pki["ca_cert"] > >> + > >> + 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()) > >> + > >> + if cacert: > >> + proc = subprocess.Popen(['certutil', '-D', '-d', > >> + 'sql:/etc/ipsec.d/', '-n', > >> 'cacert'], > >> + stdout=subprocess.PIPE, > >> + stderr=subprocess.PIPE) > >> + proc.wait() > > Sorry, may have forgotten python, but if in the next line you invoke > > .wait(), is there a reason you can't merge this into .call()? Same > > elsewhere. > > > > Also, can these commands block for long time? > > > >> + if proc.returncode: > >> + raise Exception(proc.stderr.read()) > >> + except Exception as e: > >> + vlog.err("Delete certs and key failed.\n" + str(e)) > >> + > >> + def _import_local_certs_and_key(self, pki): > >> + """Import certs and key to the NSS database.""" > >> + cert = pki["certificate"] > >> + key = pki["private_key"] > >> + name = pki["local_name"] > >> + cacert = pki["ca_cert"] > >> + > >> + try: > >> + # Create p12 file from pem files > >> + proc = subprocess.Popen(['openssl', 'pkcs12', '-export', > >> + '-in', cert, '-inkey', key, '-out', > >> + '/tmp/%s.p12' % name, '-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', '/tmp/%s.p12' % > >> name, > >> + '-d', 'sql:/etc/ipsec.d/', '-W', ''], > >> + stdout=subprocess.PIPE, > >> + stderr=subprocess.PIPE) > >> + proc.wait() > >> + if proc.returncode: > >> + raise Exception(proc.stderr.read()) > >> + > >> + if cacert: > >> + proc = subprocess.Popen(['certutil', '-A', '-a', '-i', > >> + cacert, '-d', 'sql:/etc/ipsec.d/', > >> + '-n', 'cacert', '-t', 'CT,,'], > >> + 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)) > >> + subprocess.call(['rm', '-f', '/tmp/%s.p12' % name]) > > I would use os.remove(). > > > > Also is it possible for name to contain "../.." that would allow to > > navigate outside /tmp and delete other files? > >> + > >> + > >> +class IPsecTunnel(object): > >> + """This is the base class for IPsec tunnel.""" > >> + > >> + unixctl_config_tmpl = Template("""\ > >> + Tunnel Type: $tunnel_type > >> + Local IP: $local_ip > >> + 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"), > >> + "local_ip": options.get("local_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 not self.conf["local_ip"]: > > Can we make local_ip wilcardable again? > > > >> + self.invalid_reason = ": 'local_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 > >> + elif self.conf["remote_name"]: > > I am a little bit confused about this remote_name check that must > > evaluate to true here. Does this mean that no authentication method > > was specified or that remote_name could not be retrieved (at least to > > me the invalid_reason seems incorrectly set without giving hint to > > user what is missing)? > > > >> + if not self.conf["certificate"]: > >> + self.invalid_reason = ": must set 'certificate' with PKI" > >> + return False > >> + elif not self.conf["private_key"]: > >> + self.invalid_reason = ": must set 'private_key' with PKI" > >> + return False > >> + if not self.conf["remote_cert"] and not self.conf["ca_cert"]: > >> + self.invalid_reason = ": must set 'remote_cert' or > >> 'ca_cert'" > >> + 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): > >> + 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 > > An easier method to pick right IKE driver would be to pass a specifc, > > hardcoded argument to ovs-monitor-ipsec daemon from systemd unit file > > or debian init.d script. > > > > Althugh, that would prevent to easily switch between libreswan on > > Ubuntu or strongSwan on Fedora. > > > > Here are the concerns I have with the current method you are proposing: > > 1. on Ubuntu you terminate ovs-monitor-ipsec if it could not find > > ipsec utility; and the would let the monitor process to restart it in > > loop > > 2. on Fedora you simply give up (inconsistent wrt Ubuntu) > > 3. if `ipsec` changes STDOUT format then ovs-monitor-ipsec may not > > work due to this small detail. > > > >> + try: > >> + proc = subprocess.Popen([self.IPSEC, "--version"], > >> + stdout=subprocess.PIPE) > >> + line = proc.stdout.readline().strip().split(" ") > >> + > >> + if len(line) >= 2 and line[1] in ["strongSwan", "Libreswan"]: > >> + if line[1] == "strongSwan": > >> + self.ike_helper = StrongSwanHelper(root_prefix) > >> + else: > >> + self.ike_helper = LibreSwanHelper(root_prefix) > >> + else: > >> + raise Exception("IKE daemon is not installed. Please > >> install " > >> + "the supported daemon (StrongSwan or > >> LibreSwan).") > >> + except Exception as e: > >> + vlog.err(str(e)) > > I think in few cases this would cause unhandled exceptions to be > > dumped in log file, no? > > > >> + sys.exit(1) > >> + > >> + self.ike_helper.start_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""" > >> + # Update PKI certs and key > > I think the # comment is redundant > >> + 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).") > >> + > >> + 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) > >> + > >> + 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) > >> -- > >> 2.18.0 > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
