22902853 Neutron/VPNaaS needs a workaround for 22902761
authorMark Fenwick <Mark.Fenwick@Oracle.COM>
Mon, 21 Mar 2016 07:01:01 -0700
changeset 5630 dff517d5c829
parent 5629 798c7e974fff
child 5634 34996de525cf
22902853 Neutron/VPNaaS needs a workaround for 22902761
components/openstack/neutron/files/neutron-l3-agent
components/openstack/neutron/files/services/vpn/device_drivers/solaris_ipsec.py
--- a/components/openstack/neutron/files/neutron-l3-agent	Sun Mar 20 10:19:28 2016 -0700
+++ b/components/openstack/neutron/files/neutron-l3-agent	Mon Mar 21 07:01:01 2016 -0700
@@ -102,8 +102,7 @@
         print "Use ipadm(1M) and dladm(1M) to remove it/them."
         print "Then use svcadm(1M) to clear the service."
         print "Use the following commands to remove:"
-        for interface in vpn_ifs:
-            ifn = interface.group(0)
+        for ifn in vpn_ifs:
             print "\t# ipadm delete-ip %s; dladm delete-iptun %s" % (ifn, ifn)
 
         return smf_include.SMF_EXIT_ERR_CONFIG
--- a/components/openstack/neutron/files/services/vpn/device_drivers/solaris_ipsec.py	Sun Mar 20 10:19:28 2016 -0700
+++ b/components/openstack/neutron/files/services/vpn/device_drivers/solaris_ipsec.py	Mon Mar 21 07:01:01 2016 -0700
@@ -203,23 +203,24 @@
 
 def get_vpn_interfaces():
     # OpenStack-created tunnel names use the convention of starting with
-    # "ost" followed by a number.
+    # "vpn" followed by a number.
+
+    vpn_tunnels = []
 
     cmd = ["/usr/sbin/ipadm", "show-if", "-p", "-o", "ifname"]
     p = Popen(cmd, stdout=PIPE, stderr=PIPE)
     output, error = p.communicate()
     if p.returncode != 0:
-        print "failed to retrieve IP interface names"
-        return smf_include.SMF_EXIT_ERR_FATAL
+        return vpn_tunnels
 
     ifnames = output.splitlines()
-    prog = re.compile('^ost\d+_\d+')
+    prog = re.compile('^vpn_\d+_site_\d+')
     for ifname in ifnames:
         if prog.search(ifname):
-            if ifname not in existing_tunnels:
-                existing_tunnels.append(ifname)
+            if ifname not in vpn_tunnels:
+                vpn_tunnels.append(ifname)
 
-    return filter(bool, existing_tunnels)
+    return vpn_tunnels
 
 
 def disable_services():
@@ -229,17 +230,14 @@
     ipsec_svc = "network/ipsec/policy"
     global restarting
     restarting = True
-    LOG.info("Flushing IPsec SAs.")
     cmd = ['/usr/bin/pfexec', '/usr/sbin/ipseckey', 'flush']
     try:
         stdout, stderr = processutils.execute(*cmd)
     except processutils.ProcessExecutionError as stderr:
-        LOG.debug("\"%s\"" % stderr)
+        pass
 
     rad_connection = rad.connect.connect_unix()
 
-    LOG.info(
-        "Disabling IPsec policy service: \"svc:/%s\"" % ipsec_svc)
     instance = rad_connection.get_object(
         smfb.Instance(),
         rad.client.ADRGlobPattern({'service': ipsec_svc,
@@ -252,14 +250,12 @@
                                    'instance': 'logger'}))
     instance.disable(False)
 
-    LOG.info("Disabling IKE service: \"svc:/%s:default\"" % ike_svc)
     instance = rad_connection.get_object(
         smfb.Instance(),
         rad.client.ADRGlobPattern({'service': ike_svc,
                                    'instance': 'default'}))
     instance.disable(False)
 
-    LOG.info("Disabling IKE service: \"svc:/%s:ikev2\"" % ike_svc)
     instance = rad_connection.get_object(
         smfb.Instance(),
         rad.client.ADRGlobPattern({'service': ike_svc,
@@ -273,39 +269,27 @@
     # Check to see if the VPN device_driver has been configured
     # by looking in the config file. If it has we may have to give the
     # driver a little more time to gracefully shutdown.
+    # This method is called from the smf(5) stop method. Suppress LOG
+    # output while shutting down.
     config = iniparse.RawConfigParser()
-    try:
-        config.read('/etc/neutron/vpn_agent.ini')
-    except:
-        LOG.warn("Unable to parse: vpn_agent.ini")
-        return
+    config.read('/etc/neutron/vpn_agent.ini')
 
     if config.has_section('vpnagent'):
         # If we have a VPNaaS device driver, we still can't tell if its been
         # configured or not. The easiest way to determine if VPNaaS is
         # configured is to check if there are any IP tunnels configured.
-        vpn_driver = []
-        try:
-            vpn_driver = config.get('vpnagent', 'vpn_device_driver')
-            LOG.info("The following VPNaaS device_driver is configured:")
-            LOG.info("vpn_device_driver = %s" % vpn_driver)
-            if get_vpn_interfaces():
-                LOG.warn("Shutting Down VPNaaS")
-                whack_ike_rules()
-                disable_services()
-                delete_tunnels()
-        except:
-            # There is no vpn_device_driver.
-            pass
+        ifs = get_vpn_interfaces()
+        if ifs:
+            whack_ike_rules()
+            disable_services()
+            delete_tunnels(ifs, False)
 
 
 def whack_ike_rules():
-        LOG.debug("Looking for IKE rules.")
         cmd = ['/usr/bin/pfexec', '/usr/sbin/ikeadm', '-n', 'dump', 'rule']
         try:
             status, stderr = processutils.execute(*cmd)
         except processutils.ProcessExecutionError as stderr:
-            LOG.warn("IKE daemon does not appear to be running.")
             return
 
         connection_ids = []
@@ -315,7 +299,6 @@
                 continue
             m = line.split('\'')
             connection_id = m[1]
-            LOG.debug("Active IKE rule found for: \"%s\"" % connection_id)
             connection_ids.append(connection_id)
 
         if not connection_ids:
@@ -344,7 +327,7 @@
     return JINJA_ENV.get_template(template_file)
 
 
-def delete_tunnels():
+def delete_tunnels(tunnels, chatty=True):
     """Delete tunnel interfaces using dladm(1m)/ipadm(1m).
 
        Tunneling in Solaris is done with actual tunnel interfaces, not
@@ -362,43 +345,47 @@
        tunnels are being deleted because we are shutting down, or
        we have new config.
     """
-    global existing_tunnels
-    if not existing_tunnels:
-        LOG.info("VPNaaS has just started, no tunnels to whack.")
+    if not tunnels:
+        if chatty:
+            LOG.info("VPNaaS has just started, no tunnels to whack.")
         return
 
-    for idstr in existing_tunnels:
+    for idstr in tunnels:
+        if chatty:
+            LOG.debug("Removing tunnel: \"%s\"" % idstr)
         cmd = ['/usr/bin/pfexec', 'ipadm', 'delete-ip', idstr]
         try:
             stdout, stderr = processutils.execute(*cmd)
         except processutils.ProcessExecutionError as stderr:
-            LOG.debug("\"%s\"" % stderr)
+            if chatty:
+                LOG.debug("\"%s\"" % stderr)
         cmd = ['/usr/bin/pfexec', 'dladm', 'delete-iptun', idstr]
         try:
             stdout, stderr = processutils.execute(*cmd)
         except processutils.ProcessExecutionError as stderr:
-            LOG.debug("\"%s\"" % stderr)
+            if chatty:
+                LOG.debug("\"%s\"" % stderr)
 
     # Remove all the VPN bypass rules that were added for local subnet
     # going out to remote subnet. These rules are all captured inside of
     # a anchor whose name is of the form vpn_{peer_cidr}
     pf = packetfilter.PacketFilter('_auto/neutron:l3:agent')
     anchors = pf.list_anchors()
-    LOG.debug("All anchors under _auto/neutron:l3:agent: %s" % anchors)
+    if chatty:
+        LOG.debug("All anchors under _auto/neutron:l3:agent: %s" % anchors)
     for anchor in anchors:
         if 'l3i' not in anchor:
             continue
         subanchors = anchor.split('/')[2:]
         l3i_anchors = pf.list_anchors(subanchors)
-        LOG.debug("All anchors under %s: %s" % (anchor, l3i_anchors))
+        if chatty:
+            LOG.debug("All anchors under %s: %s" % (anchor, l3i_anchors))
         for l3i_anchor in l3i_anchors:
             if 'vpn_' not in l3i_anchor:
                 continue
             l3i_subanchors = l3i_anchor.split('/')[2:]
             pf.remove_anchor(l3i_subanchors)
 
-    existing_tunnels = []
-
 
 @six.add_metaclass(abc.ABCMeta)
 class BaseSolaris():
@@ -1060,6 +1047,7 @@
         self.get_status()
         self.flush_sas()
         self.mark_connections(constants.DOWN)
+        LOG.debug("Disable IPsec policy and IKE SMF(5) services")
         disable_services()
 
     def flush_sas(self):
@@ -1514,11 +1502,31 @@
            tunnel is created between any pair of addresses, the tunnel name
            is constructed as follows:
 
-           ost[S]_[D]
+           vpn_x_site_y
+
+           Where x is a decimal index number which is unique for each local
+           external IP address used as the outer source for a tunnel. Typically
+           this will only be a single IP address as each neutron router only
+           has a single external IP address. But this could change in the
+           future.
+
+           The decimal index y is unique per IKE peer. There may be more than
+           one set of inner addresses on each tunnel.
 
-           Where S is the outer source address, represented as a decimal
-           number and D is the outer destination address represented as a
-           decimal number. The same convention is used for IPsec policy.
+           The output below shows the tunnels for a setup that has a local site
+           with two virtual subnets and two remote sites. One of the remote
+           sites has two remote subnets. The combination of two local subnets
+           and three remote subnets results in six ipsec-site-connections
+           and six tunnels on two data-link tunnels.
+
+           vpn_0_site_1      ip         ok   --  --
+              vpn_0_site_1/v4 static    ok   -- 192.168.90.1->192.168.40.1
+              vpn_0_site_1/v4a static   ok   -- 192.168.80.1->192.168.40.1
+           vpn_0_site_2      ip         ok   -- --
+              vpn_0_site_2/v4 static    ok   -- 192.168.90.1->192.168.100.1
+              vpn_0_site_2/v4a static   ok   -- 192.168.90.1->192.168.101.1
+              vpn_0_site_2/v4b static   ok   -- 192.168.80.1->192.168.100.1
+              vpn_0_site_2/v4c static   ok   -- 192.168.80.1->192.168.101.1
         """
         LOG.warn("Neutron: Syncing VPN configuration.")
         try:
@@ -1529,9 +1537,10 @@
             return
 
         router_ids = [vpnservice['router_id'] for vpnservice in vpnservices]
-        delete_tunnels()
+        global existing_tunnels
+        delete_tunnels(existing_tunnels)
+        existing_tunnels = []
 
-        global existing_tunnels
         # This is a list of one or more vpn-service objects.
         vpnservices = self.agent_rpc.get_vpn_services_on_host(
             context, self.host)
@@ -1544,15 +1553,30 @@
             pass
 
         # Add tunnel_id's
+        local_ips = {}
+        remote_ips = {}
+        vpn_cnt = 0
+        site_cnt = 0
+
         for vpnservice in vpnservices:
+            ex_ip = vpnservice['external_ip']
+            l_id = local_ips.get(ex_ip)
+
+            if not l_id:
+                l_id = "vpn_%d" % vpn_cnt
+                vpn_cnt += 1
+                local_ips[ex_ip] = l_id
+
             for ipsec_site_conn in vpnservice['ipsec_site_connections']:
-                local = vpnservice['external_ip']
-                remote = ipsec_site_conn['peer_address']
-                packedIP = socket.inet_aton(local)
-                l = struct.unpack("!L", packedIP)[0]
-                packedIP = socket.inet_aton(remote)
-                r = struct.unpack("!L", packedIP)[0]
-                tun_name = "ost%s_%s" % (l, r)
+                peer_ip = ipsec_site_conn['peer_address']
+                r_id = remote_ips.get(peer_ip)
+
+                if not r_id:
+                    site_cnt += 1
+                    r_id = "site_%d" % site_cnt
+                    remote_ips[peer_ip] = r_id
+
+                tun_name = "%s_%s" % (l_id, r_id)
                 ipsec_site_conn['tunnel_id'] = tun_name
                 LOG.debug("Added tunnel \"%s\" to vpn-service: %s" %
                           (tun_name, vpnservice['id']))