From e984522f55bc1e024b4f5225903a1592219e1f86 Mon Sep 17 00:00:00 2001
From: Tim Rozet <trozet@redhat.com>
Date: Wed, 31 Aug 2016 15:33:03 -0400
Subject: Modifies L3 Agent for FDIO Deployments

L3 Agent needs to use OVS driver for external interfaces and NSDriver
for Internal interfaces.

JIRA: APEX-252

opnfv-tht-pr: 73

Change-Id: Ic96e30421a1f1308c358a644e5602ca738091695
Signed-off-by: Tim Rozet <trozet@redhat.com>
---
 build/neutron/agent/interface/interface.py | 552 ++++++++++++++++
 build/neutron/agent/l3/namespaces.py       | 142 ++++
 build/neutron/agent/l3/router_info.py      | 996 +++++++++++++++++++++++++++++
 3 files changed, 1690 insertions(+)
 create mode 100644 build/neutron/agent/interface/interface.py
 create mode 100644 build/neutron/agent/l3/namespaces.py
 create mode 100644 build/neutron/agent/l3/router_info.py

(limited to 'build/neutron')

diff --git a/build/neutron/agent/interface/interface.py b/build/neutron/agent/interface/interface.py
new file mode 100644
index 00000000..709fd677
--- /dev/null
+++ b/build/neutron/agent/interface/interface.py
@@ -0,0 +1,552 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    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 abc
+import eventlet
+import netaddr
+from oslo_config import cfg
+from oslo_log import log as logging
+import six
+
+from neutron._i18n import _, _LE, _LI, _LW
+from neutron.agent.common import ovs_lib
+from neutron.agent.linux import ip_lib
+from neutron.agent.linux import utils
+from neutron.common import constants as n_const
+from neutron.common import exceptions
+from neutron.common import ipv6_utils
+
+
+LOG = logging.getLogger(__name__)
+
+OPTS = [
+    cfg.StrOpt('ovs_integration_bridge',
+               default='br-int',
+               help=_('Name of Open vSwitch bridge to use')),
+    cfg.BoolOpt('ovs_use_veth',
+                default=False,
+                help=_('Uses veth for an OVS interface or not. '
+                       'Support kernels with limited namespace support '
+                       '(e.g. RHEL 6.5) so long as ovs_use_veth is set to '
+                       'True.')),
+    cfg.IntOpt('network_device_mtu',
+               deprecated_for_removal=True,
+               help=_('MTU setting for device. This option will be removed in '
+                      'Newton. Please use the system-wide segment_mtu setting '
+                      'which the agents will take into account when wiring '
+                      'VIFs.')),
+]
+
+
+@six.add_metaclass(abc.ABCMeta)
+class LinuxInterfaceDriver(object):
+
+    # from linux IF_NAMESIZE
+    DEV_NAME_LEN = 14
+    DEV_NAME_PREFIX = n_const.TAP_DEVICE_PREFIX
+
+    def __init__(self, conf):
+        self.conf = conf
+        if self.conf.network_device_mtu:
+            self._validate_network_device_mtu()
+
+    def _validate_network_device_mtu(self):
+        if (ipv6_utils.is_enabled() and
+            self.conf.network_device_mtu < n_const.IPV6_MIN_MTU):
+            LOG.error(_LE("IPv6 protocol requires a minimum MTU of "
+                          "%(min_mtu)s, while the configured value is "
+                          "%(current_mtu)s"), {'min_mtu': n_const.IPV6_MIN_MTU,
+                          'current_mtu': self.conf.network_device_mtu})
+            raise SystemExit(1)
+
+    @property
+    def use_gateway_ips(self):
+        """Whether to use gateway IPs instead of unique IP allocations.
+
+        In each place where the DHCP agent runs, and for each subnet for
+        which DHCP is handling out IP addresses, the DHCP port needs -
+        at the Linux level - to have an IP address within that subnet.
+        Generally this needs to be a unique Neutron-allocated IP
+        address, because the subnet's underlying L2 domain is bridged
+        across multiple compute hosts and network nodes, and for HA
+        there may be multiple DHCP agents running on that same bridged
+        L2 domain.
+
+        However, if the DHCP ports - on multiple compute/network nodes
+        but for the same network - are _not_ bridged to each other,
+        they do not need each to have a unique IP address.  Instead
+        they can all share the same address from the relevant subnet.
+        This works, without creating any ambiguity, because those
+        ports are not all present on the same L2 domain, and because
+        no data within the network is ever sent to that address.
+        (DHCP requests are broadcast, and it is the network's job to
+        ensure that such a broadcast will reach at least one of the
+        available DHCP servers.  DHCP responses will be sent _from_
+        the DHCP port address.)
+
+        Specifically, for networking backends where it makes sense,
+        the DHCP agent allows all DHCP ports to use the subnet's
+        gateway IP address, and thereby to completely avoid any unique
+        IP address allocation.  This behaviour is selected by running
+        the DHCP agent with a configured interface driver whose
+        'use_gateway_ips' property is True.
+
+        When an operator deploys Neutron with an interface driver that
+        makes use_gateway_ips True, they should also ensure that a
+        gateway IP address is defined for each DHCP-enabled subnet,
+        and that the gateway IP address doesn't change during the
+        subnet's lifetime.
+        """
+        return False
+
+    def init_l3(self, device_name, ip_cidrs, namespace=None,
+                preserve_ips=None, clean_connections=False):
+        """Set the L3 settings for the interface using data from the port.
+
+        ip_cidrs: list of 'X.X.X.X/YY' strings
+        preserve_ips: list of ip cidrs that should not be removed from device
+        clean_connections: Boolean to indicate if we should cleanup connections
+          associated to removed ips
+        """
+        preserve_ips = preserve_ips or []
+        device = ip_lib.IPDevice(device_name, namespace=namespace)
+
+        # The LLA generated by the operating system is not known to
+        # Neutron, so it would be deleted if we added it to the 'previous'
+        # list here
+        default_ipv6_lla = ip_lib.get_ipv6_lladdr(device.link.address)
+        previous = {addr['cidr'] for addr in device.addr.list(
+            filters=['permanent'])} - {default_ipv6_lla}
+
+        # add new addresses
+        for ip_cidr in ip_cidrs:
+
+            net = netaddr.IPNetwork(ip_cidr)
+            # Convert to compact IPv6 address because the return values of
+            # "ip addr list" are compact.
+            if net.version == 6:
+                ip_cidr = str(net)
+            if ip_cidr in previous:
+                previous.remove(ip_cidr)
+                continue
+
+            device.addr.add(ip_cidr)
+
+        # clean up any old addresses
+        for ip_cidr in previous:
+            if ip_cidr not in preserve_ips:
+                if clean_connections:
+                    device.delete_addr_and_conntrack_state(ip_cidr)
+                else:
+                    device.addr.delete(ip_cidr)
+
+    def init_router_port(self,
+                         device_name,
+                         ip_cidrs,
+                         namespace,
+                         preserve_ips=None,
+                         extra_subnets=None,
+                         clean_connections=False):
+        """Set the L3 settings for a router interface using data from the port.
+
+        ip_cidrs: list of 'X.X.X.X/YY' strings
+        preserve_ips: list of ip cidrs that should not be removed from device
+        clean_connections: Boolean to indicate if we should cleanup connections
+          associated to removed ips
+        extra_subnets: An iterable of cidrs to add as routes without address
+        """
+        LOG.debug("init_router_port: device_name(%s), namespace(%s)",
+                  device_name, namespace)
+        self.init_l3(device_name=device_name,
+                     ip_cidrs=ip_cidrs,
+                     namespace=namespace,
+                     preserve_ips=preserve_ips or [],
+                     clean_connections=clean_connections)
+
+        device = ip_lib.IPDevice(device_name, namespace=namespace)
+
+        # Manage on-link routes (routes without an associated address)
+        new_onlink_cidrs = set(s['cidr'] for s in extra_subnets or [])
+
+        v4_onlink = device.route.list_onlink_routes(n_const.IP_VERSION_4)
+        v6_onlink = device.route.list_onlink_routes(n_const.IP_VERSION_6)
+        existing_onlink_cidrs = set(r['cidr'] for r in v4_onlink + v6_onlink)
+
+        for route in new_onlink_cidrs - existing_onlink_cidrs:
+            LOG.debug("adding onlink route(%s)", route)
+            device.route.add_onlink_route(route)
+        for route in (existing_onlink_cidrs - new_onlink_cidrs -
+                      set(preserve_ips or [])):
+            LOG.debug("deleting onlink route(%s)", route)
+            device.route.delete_onlink_route(route)
+
+    def add_ipv6_addr(self, device_name, v6addr, namespace, scope='global'):
+        device = ip_lib.IPDevice(device_name,
+                                 namespace=namespace)
+        net = netaddr.IPNetwork(v6addr)
+        device.addr.add(str(net), scope)
+
+    def delete_ipv6_addr(self, device_name, v6addr, namespace):
+        device = ip_lib.IPDevice(device_name,
+                                 namespace=namespace)
+        device.delete_addr_and_conntrack_state(v6addr)
+
+    def delete_ipv6_addr_with_prefix(self, device_name, prefix, namespace):
+        """Delete the first listed IPv6 address that falls within a given
+        prefix.
+        """
+        device = ip_lib.IPDevice(device_name, namespace=namespace)
+        net = netaddr.IPNetwork(prefix)
+        for address in device.addr.list(scope='global', filters=['permanent']):
+            ip_address = netaddr.IPNetwork(address['cidr'])
+            if ip_address in net:
+                device.delete_addr_and_conntrack_state(address['cidr'])
+                break
+
+    def get_ipv6_llas(self, device_name, namespace):
+        device = ip_lib.IPDevice(device_name,
+                                 namespace=namespace)
+
+        return device.addr.list(scope='link', ip_version=6)
+
+    def check_bridge_exists(self, bridge):
+        if not ip_lib.device_exists(bridge):
+            raise exceptions.BridgeDoesNotExist(bridge=bridge)
+
+    def get_device_name(self, port):
+        return (self.DEV_NAME_PREFIX + port.id)[:self.DEV_NAME_LEN]
+
+    @staticmethod
+    def configure_ipv6_ra(namespace, dev_name):
+        """Configure acceptance of IPv6 route advertisements on an intf."""
+        # Learn the default router's IP address via RAs
+        ip_lib.IPWrapper(namespace=namespace).netns.execute(
+            ['sysctl', '-w', 'net.ipv6.conf.%s.accept_ra=2' % dev_name])
+
+    @abc.abstractmethod
+    def plug_new(self, network_id, port_id, device_name, mac_address,
+                 bridge=None, namespace=None, prefix=None, mtu=None):
+        """Plug in the interface only for new devices that don't exist yet."""
+
+    def plug(self, network_id, port_id, device_name, mac_address,
+             bridge=None, namespace=None, prefix=None, mtu=None):
+        if not ip_lib.device_exists(device_name,
+                                    namespace=namespace):
+            try:
+                self.plug_new(network_id, port_id, device_name, mac_address,
+                              bridge, namespace, prefix, mtu)
+            except TypeError:
+                self.plug_new(network_id, port_id, device_name, mac_address,
+                              bridge, namespace, prefix)
+        else:
+            LOG.info(_LI("Device %s already exists"), device_name)
+
+    @abc.abstractmethod
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        """Unplug the interface."""
+
+    @property
+    def bridged(self):
+        """Whether the DHCP port is bridged to the VM TAP interfaces.
+
+        When the DHCP port is bridged to the TAP interfaces for the
+        VMs for which it is providing DHCP service - as is the case
+        for most Neutron network implementations - the DHCP server
+        only needs to listen on the DHCP port, and will still receive
+        DHCP requests from all the relevant VMs.
+
+        If the DHCP port is not bridged to the relevant VM TAP
+        interfaces, the DHCP server needs to listen explicitly on
+        those TAP interfaces, and to treat those as aliases of the
+        DHCP port where the IP subnet is defined.
+        """
+        return True
+
+
+class NullDriver(LinuxInterfaceDriver):
+    def plug_new(self, network_id, port_id, device_name, mac_address,
+                 bridge=None, namespace=None, prefix=None, mtu=None):
+        pass
+
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        pass
+
+class NSDriver(LinuxInterfaceDriver):
+    """Device independent driver enabling creation of a non device specific
+    interface in network spaces.  Attachment to the device is not performed.
+    """
+    MAX_TIME_FOR_DEVICE_EXISTENCE = 30
+
+    @classmethod
+    def _device_is_created_in_time(cls, device_name):
+        """See if device is created, within time limit."""
+        attempt = 0
+        while attempt < NSDriver.MAX_TIME_FOR_DEVICE_EXISTENCE:
+            if ip_lib.device_exists(device_name):
+                return True
+            attempt += 1
+            eventlet.sleep(1)
+        LOG.error(_LE("Device %(dev)s was not created in %(time)d seconds"),
+                  {'dev': device_name,
+                   'time': NSDriver.MAX_TIME_FOR_DEVICE_EXISTENCE})
+        return False
+
+    def _configure_mtu(self, ns_dev, mtu=None):
+        # Need to set MTU, after added to namespace. See review
+        # https://review.openstack.org/327651
+        try:
+            # Note: network_device_mtu will be deprecated in future
+            mtu_override = self.conf.network_device_mtu
+        except cfg.NoSuchOptError:
+            LOG.warning(_LW("Config setting for MTU deprecated - any "
+                            "override will be ignored."))
+            mtu_override = None
+        if mtu_override:
+            mtu = mtu_override
+            LOG.debug("Overriding MTU to %d", mtu)
+        if mtu:
+            ns_dev.link.set_mtu(mtu)
+        else:
+            LOG.debug("No MTU provided - skipping setting value")
+
+    def plug(self, network_id, port_id, device_name, mac_address,
+             bridge=None, namespace=None, prefix=None, mtu=None):
+
+        # Overriding this, we still want to add an existing device into the
+        # namespace.
+        self.plug_new(network_id, port_id, device_name, mac_address,
+                      bridge, namespace, prefix, mtu)
+
+    def plug_new(self, network_id, port_id, device_name, mac_address,
+                 bridge=None, namespace=None, prefix=None, mtu=None):
+
+        ip = ip_lib.IPWrapper()
+        ns_dev = ip.device(device_name)
+
+        LOG.debug("Plugging dev: '%s' into namespace: '%s' ",
+                  device_name, namespace)
+
+        # Wait for device creation
+        if not self._device_is_created_in_time(device_name):
+            return
+
+        ns_dev.link.set_address(mac_address)
+
+        if namespace:
+            namespace_obj = ip.ensure_namespace(namespace)
+            namespace_obj.add_device_to_namespace(ns_dev)
+
+        self._configure_mtu(ns_dev, mtu)
+
+        ns_dev.link.set_up()
+
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        # Device removal is done externally. Just remove the namespace
+        LOG.debug("Removing namespace: '%s'", namespace)
+        ip_lib.IPWrapper(namespace).garbage_collect_namespace()
+
+
+class OVSInterfaceDriver(LinuxInterfaceDriver):
+    """Driver for creating an internal interface on an OVS bridge."""
+
+    DEV_NAME_PREFIX = n_const.TAP_DEVICE_PREFIX
+
+    def __init__(self, conf):
+        super(OVSInterfaceDriver, self).__init__(conf)
+        if self.conf.ovs_use_veth:
+            self.DEV_NAME_PREFIX = 'ns-'
+
+    def _get_tap_name(self, dev_name, prefix=None):
+        if self.conf.ovs_use_veth:
+            dev_name = dev_name.replace(prefix or self.DEV_NAME_PREFIX,
+                                        n_const.TAP_DEVICE_PREFIX)
+        return dev_name
+
+    def _ovs_add_port(self, bridge, device_name, port_id, mac_address,
+                      internal=True):
+        attrs = [('external_ids', {'iface-id': port_id,
+                                   'iface-status': 'active',
+                                   'attached-mac': mac_address})]
+        if internal:
+            attrs.insert(0, ('type', 'internal'))
+
+        ovs = ovs_lib.OVSBridge(bridge)
+        ovs.replace_port(device_name, *attrs)
+
+    def plug_new(self, network_id, port_id, device_name, mac_address,
+                 bridge=None, namespace=None, prefix=None, mtu=None):
+        """Plug in the interface."""
+        if not bridge:
+            bridge = self.conf.ovs_integration_bridge
+
+        self.check_bridge_exists(bridge)
+
+        ip = ip_lib.IPWrapper()
+        tap_name = self._get_tap_name(device_name, prefix)
+
+        if self.conf.ovs_use_veth:
+            # Create ns_dev in a namespace if one is configured.
+            root_dev, ns_dev = ip.add_veth(tap_name,
+                                           device_name,
+                                           namespace2=namespace)
+            root_dev.disable_ipv6()
+        else:
+            ns_dev = ip.device(device_name)
+
+        internal = not self.conf.ovs_use_veth
+        self._ovs_add_port(bridge, tap_name, port_id, mac_address,
+                           internal=internal)
+
+        ns_dev.link.set_address(mac_address)
+
+        # Add an interface created by ovs to the namespace.
+        if not self.conf.ovs_use_veth and namespace:
+            namespace_obj = ip.ensure_namespace(namespace)
+            namespace_obj.add_device_to_namespace(ns_dev)
+
+        # NOTE(ihrachys): the order here is significant: we must set MTU after
+        # the device is moved into a namespace, otherwise OVS bridge does not
+        # allow to set MTU that is higher than the least of all device MTUs on
+        # the bridge
+        mtu = self.conf.network_device_mtu or mtu
+        if mtu:
+            ns_dev.link.set_mtu(mtu)
+            if self.conf.ovs_use_veth:
+                root_dev.link.set_mtu(mtu)
+        else:
+            LOG.warning(_LW("No MTU configured for port %s"), port_id)
+
+        ns_dev.link.set_up()
+        if self.conf.ovs_use_veth:
+            root_dev.link.set_up()
+
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        """Unplug the interface."""
+        if not bridge:
+            bridge = self.conf.ovs_integration_bridge
+
+        tap_name = self._get_tap_name(device_name, prefix)
+        self.check_bridge_exists(bridge)
+        ovs = ovs_lib.OVSBridge(bridge)
+
+        try:
+            ovs.delete_port(tap_name)
+            if self.conf.ovs_use_veth:
+                device = ip_lib.IPDevice(device_name, namespace=namespace)
+                device.link.delete()
+                LOG.debug("Unplugged interface '%s'", device_name)
+        except RuntimeError:
+            LOG.error(_LE("Failed unplugging interface '%s'"),
+                      device_name)
+
+
+class IVSInterfaceDriver(LinuxInterfaceDriver):
+    """Driver for creating an internal interface on an IVS bridge."""
+
+    DEV_NAME_PREFIX = n_const.TAP_DEVICE_PREFIX
+
+    def __init__(self, conf):
+        super(IVSInterfaceDriver, self).__init__(conf)
+        self.DEV_NAME_PREFIX = 'ns-'
+
+    def _get_tap_name(self, dev_name, prefix=None):
+        dev_name = dev_name.replace(prefix or self.DEV_NAME_PREFIX,
+                                    n_const.TAP_DEVICE_PREFIX)
+        return dev_name
+
+    def _ivs_add_port(self, device_name, port_id, mac_address):
+        cmd = ['ivs-ctl', 'add-port', device_name]
+        utils.execute(cmd, run_as_root=True)
+
+    def plug_new(self, network_id, port_id, device_name, mac_address,
+                 bridge=None, namespace=None, prefix=None, mtu=None):
+        """Plug in the interface."""
+        ip = ip_lib.IPWrapper()
+        tap_name = self._get_tap_name(device_name, prefix)
+
+        root_dev, ns_dev = ip.add_veth(tap_name, device_name)
+        root_dev.disable_ipv6()
+
+        self._ivs_add_port(tap_name, port_id, mac_address)
+
+        ns_dev = ip.device(device_name)
+        ns_dev.link.set_address(mac_address)
+
+        mtu = self.conf.network_device_mtu or mtu
+        if mtu:
+            ns_dev.link.set_mtu(mtu)
+            root_dev.link.set_mtu(mtu)
+        else:
+            LOG.warning(_LW("No MTU configured for port %s"), port_id)
+
+        if namespace:
+            namespace_obj = ip.ensure_namespace(namespace)
+            namespace_obj.add_device_to_namespace(ns_dev)
+
+        ns_dev.link.set_up()
+        root_dev.link.set_up()
+
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        """Unplug the interface."""
+        tap_name = self._get_tap_name(device_name, prefix)
+        try:
+            cmd = ['ivs-ctl', 'del-port', tap_name]
+            utils.execute(cmd, run_as_root=True)
+            device = ip_lib.IPDevice(device_name, namespace=namespace)
+            device.link.delete()
+            LOG.debug("Unplugged interface '%s'", device_name)
+        except RuntimeError:
+            LOG.error(_LE("Failed unplugging interface '%s'"),
+                      device_name)
+
+
+class BridgeInterfaceDriver(LinuxInterfaceDriver):
+    """Driver for creating bridge interfaces."""
+
+    DEV_NAME_PREFIX = 'ns-'
+
+    def plug_new(self, network_id, port_id, device_name, mac_address,
+                 bridge=None, namespace=None, prefix=None, mtu=None):
+        """Plugin the interface."""
+        ip = ip_lib.IPWrapper()
+
+        # Enable agent to define the prefix
+        tap_name = device_name.replace(prefix or self.DEV_NAME_PREFIX,
+                                       n_const.TAP_DEVICE_PREFIX)
+        # Create ns_veth in a namespace if one is configured.
+        root_veth, ns_veth = ip.add_veth(tap_name, device_name,
+                                         namespace2=namespace)
+        root_veth.disable_ipv6()
+        ns_veth.link.set_address(mac_address)
+
+        mtu = self.conf.network_device_mtu or mtu
+        if mtu:
+            root_veth.link.set_mtu(mtu)
+            ns_veth.link.set_mtu(mtu)
+        else:
+            LOG.warning(_LW("No MTU configured for port %s"), port_id)
+
+        root_veth.link.set_up()
+        ns_veth.link.set_up()
+
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+        """Unplug the interface."""
+        device = ip_lib.IPDevice(device_name, namespace=namespace)
+        try:
+            device.link.delete()
+            LOG.debug("Unplugged interface '%s'", device_name)
+        except RuntimeError:
+            LOG.error(_LE("Failed unplugging interface '%s'"),
+                      device_name)
diff --git a/build/neutron/agent/l3/namespaces.py b/build/neutron/agent/l3/namespaces.py
new file mode 100644
index 00000000..aa282052
--- /dev/null
+++ b/build/neutron/agent/l3/namespaces.py
@@ -0,0 +1,142 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+#    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 functools
+
+from oslo_log import log as logging
+from oslo_utils import excutils
+
+from neutron.agent.linux.interface import OVSInterfaceDriver
+from neutron._i18n import _LE, _LW
+from neutron.agent.linux import ip_lib
+
+LOG = logging.getLogger(__name__)
+
+NS_PREFIX = 'qrouter-'
+INTERNAL_DEV_PREFIX = 'qr-'
+EXTERNAL_DEV_PREFIX = 'qg-'
+# TODO(Carl) It is odd that this file needs this.  It is a dvr detail.
+ROUTER_2_FIP_DEV_PREFIX = 'rfp-'
+
+
+def build_ns_name(prefix, identifier):
+    """Builds a namespace name from the given prefix and identifier
+
+    :param prefix: The prefix which must end with '-' for legacy reasons
+    :param identifier: The id associated with the namespace
+    """
+    return prefix + identifier
+
+
+def get_prefix_from_ns_name(ns_name):
+    """Parses prefix from prefix-identifier
+
+    :param ns_name: The name of a namespace
+    :returns: The prefix ending with a '-' or None if there is no '-'
+    """
+    dash_index = ns_name.find('-')
+    if 0 <= dash_index:
+        return ns_name[:dash_index + 1]
+
+
+def get_id_from_ns_name(ns_name):
+    """Parses identifier from prefix-identifier
+
+    :param ns_name: The name of a namespace
+    :returns: Identifier or None if there is no - to end the prefix
+    """
+    dash_index = ns_name.find('-')
+    if 0 <= dash_index:
+        return ns_name[dash_index + 1:]
+
+
+def check_ns_existence(f):
+    @functools.wraps(f)
+    def wrapped(self, *args, **kwargs):
+        if not self.exists():
+            LOG.warning(_LW('Namespace %(name)s does not exists. Skipping '
+                            '%(func)s'),
+                        {'name': self.name, 'func': f.__name__})
+            return
+        try:
+            return f(self, *args, **kwargs)
+        except RuntimeError:
+            with excutils.save_and_reraise_exception() as ctx:
+                if not self.exists():
+                    LOG.debug('Namespace %(name)s was concurrently deleted',
+                              self.name)
+                    ctx.reraise = False
+    return wrapped
+
+
+class Namespace(object):
+
+    def __init__(self, name, agent_conf, driver, use_ipv6):
+        self.name = name
+        self.ip_wrapper_root = ip_lib.IPWrapper()
+        self.agent_conf = agent_conf
+        self.driver = driver
+        self.use_ipv6 = use_ipv6
+
+    def create(self):
+        ip_wrapper = self.ip_wrapper_root.ensure_namespace(self.name)
+        cmd = ['sysctl', '-w', 'net.ipv4.ip_forward=1']
+        ip_wrapper.netns.execute(cmd)
+        if self.use_ipv6:
+            cmd = ['sysctl', '-w', 'net.ipv6.conf.all.forwarding=1']
+            ip_wrapper.netns.execute(cmd)
+
+    def delete(self):
+        try:
+            self.ip_wrapper_root.netns.delete(self.name)
+        except RuntimeError:
+            msg = _LE('Failed trying to delete namespace: %s')
+            LOG.exception(msg, self.name)
+
+    def exists(self):
+        return self.ip_wrapper_root.netns.exists(self.name)
+
+
+class RouterNamespace(Namespace):
+
+    def __init__(self, router_id, agent_conf, driver, use_ipv6, ovs_driver):
+        self.router_id = router_id
+        self.ovs_driver = ovs_driver
+        name = self._get_ns_name(router_id)
+        super(RouterNamespace, self).__init__(
+            name, agent_conf, driver, use_ipv6)
+
+    @classmethod
+    def _get_ns_name(cls, router_id):
+        return build_ns_name(NS_PREFIX, router_id)
+
+    @check_ns_existence
+    def delete(self):
+        ns_ip = ip_lib.IPWrapper(namespace=self.name)
+        for d in ns_ip.get_devices(exclude_loopback=True):
+            if d.name.startswith(INTERNAL_DEV_PREFIX):
+                # device is on default bridge
+                self.driver.unplug(d.name, namespace=self.name,
+                                   prefix=INTERNAL_DEV_PREFIX)
+            elif d.name.startswith(ROUTER_2_FIP_DEV_PREFIX):
+                ns_ip.del_veth(d.name)
+            elif d.name.startswith(EXTERNAL_DEV_PREFIX):
+                self.ovs_driver.unplug(
+                    d.name,
+                    bridge=self.agent_conf.external_network_bridge,
+                    namespace=self.name,
+                    prefix=EXTERNAL_DEV_PREFIX)
+
+        super(RouterNamespace, self).delete()
diff --git a/build/neutron/agent/l3/router_info.py b/build/neutron/agent/l3/router_info.py
new file mode 100644
index 00000000..0ddd1db5
--- /dev/null
+++ b/build/neutron/agent/l3/router_info.py
@@ -0,0 +1,996 @@
+# Copyright (c) 2014 OpenStack Foundation
+#
+#    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 collections
+import netaddr
+from oslo_log import log as logging
+
+from neutron._i18n import _, _LE, _LW
+from neutron.agent.l3 import namespaces
+from neutron.agent.linux import ip_lib
+from neutron.agent.linux import iptables_manager
+from neutron.agent.linux import ra
+from neutron.common import constants as l3_constants
+from neutron.common import exceptions as n_exc
+from neutron.common import ipv6_utils
+from neutron.common import utils as common_utils
+from neutron.ipam import utils as ipam_utils
+from neutron.agent.linux.interface import OVSInterfaceDriver
+
+LOG = logging.getLogger(__name__)
+INTERNAL_DEV_PREFIX = namespaces.INTERNAL_DEV_PREFIX
+EXTERNAL_DEV_PREFIX = namespaces.EXTERNAL_DEV_PREFIX
+
+FLOATINGIP_STATUS_NOCHANGE = object()
+ADDRESS_SCOPE_MARK_MASK = "0xffff0000"
+ADDRESS_SCOPE_MARK_ID_MIN = 1024
+ADDRESS_SCOPE_MARK_ID_MAX = 2048
+DEFAULT_ADDRESS_SCOPE = "noscope"
+
+
+class RouterInfo(object):
+
+    def __init__(self,
+                 router_id,
+                 router,
+                 agent_conf,
+                 interface_driver,
+                 use_ipv6=False):
+        self.ovs_driver = OVSInterfaceDriver(agent_conf)
+        self.router_id = router_id
+        self.ex_gw_port = None
+        self._snat_enabled = None
+        self.fip_map = {}
+        self.internal_ports = []
+        self.floating_ips = set()
+        # Invoke the setter for establishing initial SNAT action
+        self.router = router
+        self.use_ipv6 = use_ipv6
+        ns = namespaces.RouterNamespace(
+            router_id, agent_conf, interface_driver, use_ipv6, self.ovs_driver)
+        self.router_namespace = ns
+        self.ns_name = ns.name
+        self.available_mark_ids = set(range(ADDRESS_SCOPE_MARK_ID_MIN,
+                                            ADDRESS_SCOPE_MARK_ID_MAX))
+        self._address_scope_to_mark_id = {
+            DEFAULT_ADDRESS_SCOPE: self.available_mark_ids.pop()}
+        self.iptables_manager = iptables_manager.IptablesManager(
+            use_ipv6=use_ipv6,
+            namespace=self.ns_name)
+        self.routes = []
+        self.agent_conf = agent_conf
+        self.driver = interface_driver
+        # radvd is a neutron.agent.linux.ra.DaemonMonitor
+        self.radvd = None
+
+    def initialize(self, process_monitor):
+        """Initialize the router on the system.
+
+        This differs from __init__ in that this method actually affects the
+        system creating namespaces, starting processes, etc.  The other merely
+        initializes the python object.  This separates in-memory object
+        initialization from methods that actually go do stuff to the system.
+
+        :param process_monitor: The agent's process monitor instance.
+        """
+        self.process_monitor = process_monitor
+        self.radvd = ra.DaemonMonitor(self.router_id,
+                                      self.ns_name,
+                                      process_monitor,
+                                      self.get_internal_device_name,
+                                      self.agent_conf)
+
+        self.router_namespace.create()
+
+    @property
+    def router(self):
+        return self._router
+
+    @router.setter
+    def router(self, value):
+        self._router = value
+        if not self._router:
+            return
+        # enable_snat by default if it wasn't specified by plugin
+        self._snat_enabled = self._router.get('enable_snat', True)
+
+    def get_internal_device_name(self, port_id):
+        return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
+
+    def get_external_device_name(self, port_id):
+        return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
+
+    def get_external_device_interface_name(self, ex_gw_port):
+        return self.get_external_device_name(ex_gw_port['id'])
+
+    def _update_routing_table(self, operation, route, namespace):
+        cmd = ['ip', 'route', operation, 'to', route['destination'],
+               'via', route['nexthop']]
+        ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
+        ip_wrapper.netns.execute(cmd, check_exit_code=False)
+
+    def update_routing_table(self, operation, route):
+        self._update_routing_table(operation, route, self.ns_name)
+
+    def routes_updated(self, old_routes, new_routes):
+        adds, removes = common_utils.diff_list_of_dict(old_routes,
+                                                       new_routes)
+        for route in adds:
+            LOG.debug("Added route entry is '%s'", route)
+            # remove replaced route from deleted route
+            for del_route in removes:
+                if route['destination'] == del_route['destination']:
+                    removes.remove(del_route)
+            #replace success even if there is no existing route
+            self.update_routing_table('replace', route)
+        for route in removes:
+            LOG.debug("Removed route entry is '%s'", route)
+            self.update_routing_table('delete', route)
+
+    def get_ex_gw_port(self):
+        return self.router.get('gw_port')
+
+    def get_floating_ips(self):
+        """Filter Floating IPs to be hosted on this agent."""
+        return self.router.get(l3_constants.FLOATINGIP_KEY, [])
+
+    def floating_forward_rules(self, floating_ip, fixed_ip):
+        return [('PREROUTING', '-d %s/32 -j DNAT --to-destination %s' %
+                 (floating_ip, fixed_ip)),
+                ('OUTPUT', '-d %s/32 -j DNAT --to-destination %s' %
+                 (floating_ip, fixed_ip)),
+                ('float-snat', '-s %s/32 -j SNAT --to-source %s' %
+                 (fixed_ip, floating_ip))]
+
+    def floating_mangle_rules(self, floating_ip, fixed_ip, internal_mark):
+        mark_traffic_to_floating_ip = (
+            'floatingip', '-d %s -j MARK --set-xmark %s' % (
+                floating_ip, internal_mark))
+        mark_traffic_from_fixed_ip = (
+            'FORWARD', '-s %s -j $float-snat' % fixed_ip)
+        return [mark_traffic_to_floating_ip, mark_traffic_from_fixed_ip]
+
+    def get_address_scope_mark_mask(self, address_scope=None):
+        if not address_scope:
+            address_scope = DEFAULT_ADDRESS_SCOPE
+
+        if address_scope not in self._address_scope_to_mark_id:
+            self._address_scope_to_mark_id[address_scope] = (
+                self.available_mark_ids.pop())
+
+        mark_id = self._address_scope_to_mark_id[address_scope]
+        # NOTE: Address scopes use only the upper 16 bits of the 32 fwmark
+        return "%s/%s" % (hex(mark_id << 16), ADDRESS_SCOPE_MARK_MASK)
+
+    def get_port_address_scope_mark(self, port):
+        """Get the IP version 4 and 6 address scope mark for the port
+
+        :param port: A port dict from the RPC call
+        :returns: A dict mapping the address family to the address scope mark
+        """
+        port_scopes = port.get('address_scopes', {})
+
+        address_scope_mark_masks = (
+            (int(k), self.get_address_scope_mark_mask(v))
+            for k, v in port_scopes.items())
+        return collections.defaultdict(self.get_address_scope_mark_mask,
+                                       address_scope_mark_masks)
+
+    def process_floating_ip_nat_rules(self):
+        """Configure NAT rules for the router's floating IPs.
+
+        Configures iptables rules for the floating ips of the given router
+        """
+        # Clear out all iptables rules for floating ips
+        self.iptables_manager.ipv4['nat'].clear_rules_by_tag('floating_ip')
+
+        floating_ips = self.get_floating_ips()
+        # Loop once to ensure that floating ips are configured.
+        for fip in floating_ips:
+            # Rebuild iptables rules for the floating ip.
+            fixed = fip['fixed_ip_address']
+            fip_ip = fip['floating_ip_address']
+            for chain, rule in self.floating_forward_rules(fip_ip, fixed):
+                self.iptables_manager.ipv4['nat'].add_rule(chain, rule,
+                                                           tag='floating_ip')
+
+        self.iptables_manager.apply()
+
+    def process_floating_ip_address_scope_rules(self):
+        """Configure address scope related iptables rules for the router's
+         floating IPs.
+        """
+
+        # Clear out all iptables rules for floating ips
+        self.iptables_manager.ipv4['mangle'].clear_rules_by_tag('floating_ip')
+        all_floating_ips = self.get_floating_ips()
+        ext_scope = self._get_external_address_scope()
+        # Filter out the floating ips that have fixed ip in the same address
+        # scope. Because the packets for them will always be in one address
+        # scope, no need to manipulate MARK/CONNMARK for them.
+        floating_ips = [fip for fip in all_floating_ips
+                        if fip.get('fixed_ip_address_scope') != ext_scope]
+        if floating_ips:
+            ext_scope_mark = self.get_address_scope_mark_mask(ext_scope)
+            ports_scopemark = self._get_address_scope_mark()
+            devices_in_ext_scope = {
+                device for device, mark
+                in ports_scopemark[l3_constants.IP_VERSION_4].items()
+                if mark == ext_scope_mark}
+            # Add address scope for floatingip egress
+            for device in devices_in_ext_scope:
+                self.iptables_manager.ipv4['mangle'].add_rule(
+                    'float-snat',
+                    '-o %s -j MARK --set-xmark %s'
+                    % (device, ext_scope_mark),
+                    tag='floating_ip')
+
+        # Loop once to ensure that floating ips are configured.
+        for fip in floating_ips:
+            # Rebuild iptables rules for the floating ip.
+            fip_ip = fip['floating_ip_address']
+            # Send the floating ip traffic to the right address scope
+            fixed_ip = fip['fixed_ip_address']
+            fixed_scope = fip.get('fixed_ip_address_scope')
+            internal_mark = self.get_address_scope_mark_mask(fixed_scope)
+            mangle_rules = self.floating_mangle_rules(
+                fip_ip, fixed_ip, internal_mark)
+            for chain, rule in mangle_rules:
+                self.iptables_manager.ipv4['mangle'].add_rule(
+                    chain, rule, tag='floating_ip')
+
+    def process_snat_dnat_for_fip(self):
+        try:
+            self.process_floating_ip_nat_rules()
+        except Exception:
+            # TODO(salv-orlando): Less broad catching
+            msg = _('L3 agent failure to setup NAT for floating IPs')
+            LOG.exception(msg)
+            raise n_exc.FloatingIpSetupException(msg)
+
+    def _add_fip_addr_to_device(self, fip, device):
+        """Configures the floating ip address on the device.
+        """
+        try:
+            ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address'])
+            device.addr.add(ip_cidr)
+            return True
+        except RuntimeError:
+            # any exception occurred here should cause the floating IP
+            # to be set in error state
+            LOG.warning(_LW("Unable to configure IP address for "
+                            "floating IP: %s"), fip['id'])
+
+    def add_floating_ip(self, fip, interface_name, device):
+        raise NotImplementedError()
+
+    def remove_floating_ip(self, device, ip_cidr):
+        device.delete_addr_and_conntrack_state(ip_cidr)
+
+    def move_floating_ip(self, fip):
+        return l3_constants.FLOATINGIP_STATUS_ACTIVE
+
+    def remove_external_gateway_ip(self, device, ip_cidr):
+        device.delete_addr_and_conntrack_state(ip_cidr)
+
+    def get_router_cidrs(self, device):
+        return set([addr['cidr'] for addr in device.addr.list()])
+
+    def process_floating_ip_addresses(self, interface_name):
+        """Configure IP addresses on router's external gateway interface.
+
+        Ensures addresses for existing floating IPs and cleans up
+        those that should not longer be configured.
+        """
+
+        fip_statuses = {}
+        if interface_name is None:
+            LOG.debug('No Interface for floating IPs router: %s',
+                      self.router['id'])
+            return fip_statuses
+
+        device = ip_lib.IPDevice(interface_name, namespace=self.ns_name)
+        existing_cidrs = self.get_router_cidrs(device)
+        new_cidrs = set()
+
+        floating_ips = self.get_floating_ips()
+        # Loop once to ensure that floating ips are configured.
+        for fip in floating_ips:
+            fip_ip = fip['floating_ip_address']
+            ip_cidr = common_utils.ip_to_cidr(fip_ip)
+            new_cidrs.add(ip_cidr)
+            fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ACTIVE
+            if ip_cidr not in existing_cidrs:
+                fip_statuses[fip['id']] = self.add_floating_ip(
+                    fip, interface_name, device)
+                LOG.debug('Floating ip %(id)s added, status %(status)s',
+                          {'id': fip['id'],
+                           'status': fip_statuses.get(fip['id'])})
+            elif (fip_ip in self.fip_map and
+                  self.fip_map[fip_ip] != fip['fixed_ip_address']):
+                LOG.debug("Floating IP was moved from fixed IP "
+                          "%(old)s to %(new)s",
+                          {'old': self.fip_map[fip_ip],
+                           'new': fip['fixed_ip_address']})
+                fip_statuses[fip['id']] = self.move_floating_ip(fip)
+            elif fip_statuses[fip['id']] == fip['status']:
+                # mark the status as not changed. we can't remove it because
+                # that's how the caller determines that it was removed
+                fip_statuses[fip['id']] = FLOATINGIP_STATUS_NOCHANGE
+        fips_to_remove = (
+            ip_cidr for ip_cidr in existing_cidrs - new_cidrs
+            if common_utils.is_cidr_host(ip_cidr))
+        for ip_cidr in fips_to_remove:
+            LOG.debug("Removing floating ip %s from interface %s in "
+                      "namespace %s", ip_cidr, interface_name, self.ns_name)
+            self.remove_floating_ip(device, ip_cidr)
+
+        return fip_statuses
+
+    def configure_fip_addresses(self, interface_name):
+        try:
+            return self.process_floating_ip_addresses(interface_name)
+        except Exception:
+            # TODO(salv-orlando): Less broad catching
+            msg = _('L3 agent failure to setup floating IPs')
+            LOG.exception(msg)
+            raise n_exc.FloatingIpSetupException(msg)
+
+    def put_fips_in_error_state(self):
+        fip_statuses = {}
+        for fip in self.router.get(l3_constants.FLOATINGIP_KEY, []):
+            fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ERROR
+        return fip_statuses
+
+    def delete(self, agent):
+        self.router['gw_port'] = None
+        self.router[l3_constants.INTERFACE_KEY] = []
+        self.router[l3_constants.FLOATINGIP_KEY] = []
+        self.process_delete(agent)
+        self.disable_radvd()
+        self.router_namespace.delete()
+
+    def _internal_network_updated(self, port, subnet_id, prefix, old_prefix,
+                                  updated_cidrs):
+        interface_name = self.get_internal_device_name(port['id'])
+        if prefix != l3_constants.PROVISIONAL_IPV6_PD_PREFIX:
+            fixed_ips = port['fixed_ips']
+            for fixed_ip in fixed_ips:
+                if fixed_ip['subnet_id'] == subnet_id:
+                    v6addr = common_utils.ip_to_cidr(fixed_ip['ip_address'],
+                                                     fixed_ip.get('prefixlen'))
+                    if v6addr not in updated_cidrs:
+                        self.driver.add_ipv6_addr(interface_name, v6addr,
+                                                  self.ns_name)
+        else:
+            self.driver.delete_ipv6_addr_with_prefix(interface_name,
+                                                     old_prefix,
+                                                     self.ns_name)
+
+    def _internal_network_added(self, ns_name, network_id, port_id,
+                                fixed_ips, mac_address,
+                                interface_name, prefix, mtu=None):
+        LOG.debug("adding internal network: prefix(%s), port(%s)",
+                  prefix, port_id)
+        self.driver.plug(network_id, port_id, interface_name, mac_address,
+                         namespace=ns_name,
+                         prefix=prefix, mtu=mtu)
+
+        ip_cidrs = common_utils.fixed_ip_cidrs(fixed_ips)
+        self.driver.init_router_port(
+            interface_name, ip_cidrs, namespace=ns_name)
+        for fixed_ip in fixed_ips:
+            ip_lib.send_ip_addr_adv_notif(ns_name,
+                                          interface_name,
+                                          fixed_ip['ip_address'],
+                                          self.agent_conf)
+
+    def internal_network_added(self, port):
+        network_id = port['network_id']
+        port_id = port['id']
+        fixed_ips = port['fixed_ips']
+        mac_address = port['mac_address']
+
+        interface_name = self.get_internal_device_name(port_id)
+
+        self._internal_network_added(self.ns_name,
+                                     network_id,
+                                     port_id,
+                                     fixed_ips,
+                                     mac_address,
+                                     interface_name,
+                                     INTERNAL_DEV_PREFIX,
+                                     mtu=port.get('mtu'))
+
+    def internal_network_removed(self, port):
+        interface_name = self.get_internal_device_name(port['id'])
+        LOG.debug("removing internal network: port(%s) interface(%s)",
+                  port['id'], interface_name)
+        if ip_lib.device_exists(interface_name, namespace=self.ns_name):
+            self.driver.unplug(interface_name, namespace=self.ns_name,
+                               prefix=INTERNAL_DEV_PREFIX)
+
+    def _get_existing_devices(self):
+        ip_wrapper = ip_lib.IPWrapper(namespace=self.ns_name)
+        ip_devs = ip_wrapper.get_devices(exclude_loopback=True)
+        return [ip_dev.name for ip_dev in ip_devs]
+
+    @staticmethod
+    def _get_updated_ports(existing_ports, current_ports):
+        updated_ports = dict()
+        current_ports_dict = {p['id']: p for p in current_ports}
+        for existing_port in existing_ports:
+            current_port = current_ports_dict.get(existing_port['id'])
+            if current_port:
+                if (sorted(existing_port['fixed_ips'],
+                           key=common_utils.safe_sort_key) !=
+                        sorted(current_port['fixed_ips'],
+                               key=common_utils.safe_sort_key)):
+                    updated_ports[current_port['id']] = current_port
+        return updated_ports
+
+    @staticmethod
+    def _port_has_ipv6_subnet(port):
+        if 'subnets' in port:
+            for subnet in port['subnets']:
+                if (netaddr.IPNetwork(subnet['cidr']).version == 6 and
+                    subnet['cidr'] != l3_constants.PROVISIONAL_IPV6_PD_PREFIX):
+                    return True
+
+    def enable_radvd(self, internal_ports=None):
+        LOG.debug('Spawning radvd daemon in router device: %s', self.router_id)
+        if not internal_ports:
+            internal_ports = self.internal_ports
+        self.radvd.enable(internal_ports)
+
+    def disable_radvd(self):
+        LOG.debug('Terminating radvd daemon in router device: %s',
+                  self.router_id)
+        self.radvd.disable()
+
+    def internal_network_updated(self, interface_name, ip_cidrs):
+        self.driver.init_router_port(
+            interface_name,
+            ip_cidrs=ip_cidrs,
+            namespace=self.ns_name)
+
+    def address_scope_mangle_rule(self, device_name, mark_mask):
+        return '-i %s -j MARK --set-xmark %s' % (device_name, mark_mask)
+
+    def address_scope_filter_rule(self, device_name, mark_mask):
+        return '-o %s -m mark ! --mark %s -j DROP' % (
+            device_name, mark_mask)
+
+    def _process_internal_ports(self, pd):
+        existing_port_ids = set(p['id'] for p in self.internal_ports)
+
+        internal_ports = self.router.get(l3_constants.INTERFACE_KEY, [])
+        current_port_ids = set(p['id'] for p in internal_ports
+                               if p['admin_state_up'])
+
+        new_port_ids = current_port_ids - existing_port_ids
+        new_ports = [p for p in internal_ports if p['id'] in new_port_ids]
+        old_ports = [p for p in self.internal_ports
+                     if p['id'] not in current_port_ids]
+        updated_ports = self._get_updated_ports(self.internal_ports,
+                                                internal_ports)
+
+        enable_ra = False
+        for p in new_ports:
+            self.internal_network_added(p)
+            LOG.debug("appending port %s to internal_ports cache", p)
+            self.internal_ports.append(p)
+            enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
+            for subnet in p['subnets']:
+                if ipv6_utils.is_ipv6_pd_enabled(subnet):
+                    interface_name = self.get_internal_device_name(p['id'])
+                    pd.enable_subnet(self.router_id, subnet['id'],
+                                     subnet['cidr'],
+                                     interface_name, p['mac_address'])
+
+        for p in old_ports:
+            self.internal_network_removed(p)
+            LOG.debug("removing port %s from internal_ports cache", p)
+            self.internal_ports.remove(p)
+            enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
+            for subnet in p['subnets']:
+                if ipv6_utils.is_ipv6_pd_enabled(subnet):
+                    pd.disable_subnet(self.router_id, subnet['id'])
+
+        updated_cidrs = []
+        if updated_ports:
+            for index, p in enumerate(internal_ports):
+                if not updated_ports.get(p['id']):
+                    continue
+                self.internal_ports[index] = updated_ports[p['id']]
+                interface_name = self.get_internal_device_name(p['id'])
+                ip_cidrs = common_utils.fixed_ip_cidrs(p['fixed_ips'])
+                LOG.debug("updating internal network for port %s", p)
+                updated_cidrs += ip_cidrs
+                self.internal_network_updated(interface_name, ip_cidrs)
+                enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
+
+        # Check if there is any pd prefix update
+        for p in internal_ports:
+            if p['id'] in (set(current_port_ids) & set(existing_port_ids)):
+                for subnet in p.get('subnets', []):
+                    if ipv6_utils.is_ipv6_pd_enabled(subnet):
+                        old_prefix = pd.update_subnet(self.router_id,
+                                                      subnet['id'],
+                                                      subnet['cidr'])
+                        if old_prefix:
+                            self._internal_network_updated(p, subnet['id'],
+                                                           subnet['cidr'],
+                                                           old_prefix,
+                                                           updated_cidrs)
+                            enable_ra = True
+
+        # Enable RA
+        if enable_ra:
+            self.enable_radvd(internal_ports)
+
+        existing_devices = self._get_existing_devices()
+        current_internal_devs = set(n for n in existing_devices
+                                    if n.startswith(INTERNAL_DEV_PREFIX))
+        current_port_devs = set(self.get_internal_device_name(port_id)
+                                for port_id in current_port_ids)
+        stale_devs = current_internal_devs - current_port_devs
+        for stale_dev in stale_devs:
+            LOG.debug('Deleting stale internal router device: %s',
+                      stale_dev)
+            pd.remove_stale_ri_ifname(self.router_id, stale_dev)
+            self.driver.unplug(stale_dev,
+                               namespace=self.ns_name,
+                               prefix=INTERNAL_DEV_PREFIX)
+
+    def _list_floating_ip_cidrs(self):
+        # Compute a list of addresses this router is supposed to have.
+        # This avoids unnecessarily removing those addresses and
+        # causing a momentarily network outage.
+        floating_ips = self.get_floating_ips()
+        return [common_utils.ip_to_cidr(ip['floating_ip_address'])
+                for ip in floating_ips]
+
+    def _plug_external_gateway(self, ex_gw_port, interface_name, ns_name):
+        self.ovs_driver.plug(ex_gw_port['network_id'],
+                         ex_gw_port['id'],
+                         interface_name,
+                         ex_gw_port['mac_address'],
+                         bridge=self.agent_conf.external_network_bridge,
+                         namespace=ns_name,
+                         prefix=EXTERNAL_DEV_PREFIX,
+                         mtu=ex_gw_port.get('mtu'))
+
+    def _get_external_gw_ips(self, ex_gw_port):
+        gateway_ips = []
+        if 'subnets' in ex_gw_port:
+            gateway_ips = [subnet['gateway_ip']
+                           for subnet in ex_gw_port['subnets']
+                           if subnet['gateway_ip']]
+        if self.use_ipv6 and not self.is_v6_gateway_set(gateway_ips):
+            # No IPv6 gateway is available, but IPv6 is enabled.
+            if self.agent_conf.ipv6_gateway:
+                # ipv6_gateway configured, use address for default route.
+                gateway_ips.append(self.agent_conf.ipv6_gateway)
+        return gateway_ips
+
+    def _add_route_to_gw(self, ex_gw_port, device_name,
+                         namespace, preserve_ips):
+        # Note: ipv6_gateway is an ipv6 LLA
+        # and so doesn't need a special route
+        for subnet in ex_gw_port.get('subnets', []):
+            is_gateway_not_in_subnet = (subnet['gateway_ip'] and
+                                        not ipam_utils.check_subnet_ip(
+                                                subnet['cidr'],
+                                                subnet['gateway_ip']))
+            if is_gateway_not_in_subnet:
+                preserve_ips.append(subnet['gateway_ip'])
+                device = ip_lib.IPDevice(device_name, namespace=namespace)
+                device.route.add_route(subnet['gateway_ip'], scope='link')
+
+    def _external_gateway_added(self, ex_gw_port, interface_name,
+                                ns_name, preserve_ips):
+        LOG.debug("External gateway added: port(%s), interface(%s), ns(%s)",
+                  ex_gw_port, interface_name, ns_name)
+        self._plug_external_gateway(ex_gw_port, interface_name, ns_name)
+
+        # Build up the interface and gateway IP addresses that
+        # will be added to the interface.
+        ip_cidrs = common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips'])
+
+        gateway_ips = self._get_external_gw_ips(ex_gw_port)
+        enable_ra_on_gw = False
+        if self.use_ipv6 and not self.is_v6_gateway_set(gateway_ips):
+            # There is no IPv6 gw_ip, use RouterAdvt for default route.
+            enable_ra_on_gw = True
+
+        self._add_route_to_gw(ex_gw_port, device_name=interface_name,
+                              namespace=ns_name, preserve_ips=preserve_ips)
+        self.ovs_driver.init_router_port(
+            interface_name,
+            ip_cidrs,
+            namespace=ns_name,
+            extra_subnets=ex_gw_port.get('extra_subnets', []),
+            preserve_ips=preserve_ips,
+            clean_connections=True)
+
+        device = ip_lib.IPDevice(interface_name, namespace=ns_name)
+        for ip in gateway_ips or []:
+            device.route.add_gateway(ip)
+
+        if enable_ra_on_gw:
+            self.driver.configure_ipv6_ra(ns_name, interface_name)
+
+        for fixed_ip in ex_gw_port['fixed_ips']:
+            ip_lib.send_ip_addr_adv_notif(ns_name,
+                                          interface_name,
+                                          fixed_ip['ip_address'],
+                                          self.agent_conf)
+
+    def is_v6_gateway_set(self, gateway_ips):
+        """Check to see if list of gateway_ips has an IPv6 gateway.
+        """
+        # Note - don't require a try-except here as all
+        # gateway_ips elements are valid addresses, if they exist.
+        return any(netaddr.IPAddress(gw_ip).version == 6
+                   for gw_ip in gateway_ips)
+
+    def external_gateway_added(self, ex_gw_port, interface_name):
+        preserve_ips = self._list_floating_ip_cidrs()
+        self._external_gateway_added(
+            ex_gw_port, interface_name, self.ns_name, preserve_ips)
+
+    def external_gateway_updated(self, ex_gw_port, interface_name):
+        preserve_ips = self._list_floating_ip_cidrs()
+        self._external_gateway_added(
+            ex_gw_port, interface_name, self.ns_name, preserve_ips)
+
+    def external_gateway_removed(self, ex_gw_port, interface_name):
+        LOG.debug("External gateway removed: port(%s), interface(%s)",
+                  ex_gw_port, interface_name)
+        device = ip_lib.IPDevice(interface_name, namespace=self.ns_name)
+        for ip_addr in ex_gw_port['fixed_ips']:
+            self.remove_external_gateway_ip(device,
+                                            common_utils.ip_to_cidr(
+                                                ip_addr['ip_address'],
+                                                ip_addr['prefixlen']))
+        self.ovs_driver.unplug(interface_name,
+                           bridge=self.agent_conf.external_network_bridge,
+                           namespace=self.ns_name,
+                           prefix=EXTERNAL_DEV_PREFIX)
+
+    @staticmethod
+    def _gateway_ports_equal(port1, port2):
+        return port1 == port2
+
+    def _process_external_gateway(self, ex_gw_port, pd):
+        # TODO(Carl) Refactor to clarify roles of ex_gw_port vs self.ex_gw_port
+        ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
+                         self.ex_gw_port and self.ex_gw_port['id'])
+
+        interface_name = None
+        if ex_gw_port_id:
+            interface_name = self.get_external_device_name(ex_gw_port_id)
+        if ex_gw_port:
+            if not self.ex_gw_port:
+                self.external_gateway_added(ex_gw_port, interface_name)
+                pd.add_gw_interface(self.router['id'], interface_name)
+            elif not self._gateway_ports_equal(ex_gw_port, self.ex_gw_port):
+                self.external_gateway_updated(ex_gw_port, interface_name)
+        elif not ex_gw_port and self.ex_gw_port:
+            self.external_gateway_removed(self.ex_gw_port, interface_name)
+            pd.remove_gw_interface(self.router['id'])
+
+        existing_devices = self._get_existing_devices()
+        stale_devs = [dev for dev in existing_devices
+                      if dev.startswith(EXTERNAL_DEV_PREFIX)
+                      and dev != interface_name]
+        for stale_dev in stale_devs:
+            LOG.debug('Deleting stale external router device: %s', stale_dev)
+            pd.remove_gw_interface(self.router['id'])
+            self.ovs_driver.unplug(stale_dev,
+                               bridge=self.agent_conf.external_network_bridge,
+                               namespace=self.ns_name,
+                               prefix=EXTERNAL_DEV_PREFIX)
+
+        # Process SNAT rules for external gateway
+        gw_port = self._router.get('gw_port')
+        self._handle_router_snat_rules(gw_port, interface_name)
+
+    def _prevent_snat_for_internal_traffic_rule(self, interface_name):
+        return (
+            'POSTROUTING', '! -i %(interface_name)s '
+                           '! -o %(interface_name)s -m conntrack ! '
+                           '--ctstate DNAT -j ACCEPT' %
+                           {'interface_name': interface_name})
+
+    def external_gateway_nat_fip_rules(self, ex_gw_ip, interface_name):
+        dont_snat_traffic_to_internal_ports_if_not_to_floating_ip = (
+            self._prevent_snat_for_internal_traffic_rule(interface_name))
+        # Makes replies come back through the router to reverse DNAT
+        ext_in_mark = self.agent_conf.external_ingress_mark
+        snat_internal_traffic_to_floating_ip = (
+            'snat', '-m mark ! --mark %s/%s '
+                    '-m conntrack --ctstate DNAT '
+                    '-j SNAT --to-source %s'
+                    % (ext_in_mark, l3_constants.ROUTER_MARK_MASK, ex_gw_ip))
+        return [dont_snat_traffic_to_internal_ports_if_not_to_floating_ip,
+                snat_internal_traffic_to_floating_ip]
+
+    def external_gateway_nat_snat_rules(self, ex_gw_ip, interface_name):
+        snat_normal_external_traffic = (
+            'snat', '-o %s -j SNAT --to-source %s' %
+                    (interface_name, ex_gw_ip))
+        return [snat_normal_external_traffic]
+
+    def external_gateway_mangle_rules(self, interface_name):
+        mark = self.agent_conf.external_ingress_mark
+        mark_packets_entering_external_gateway_port = (
+            'mark', '-i %s -j MARK --set-xmark %s/%s' %
+                    (interface_name, mark, l3_constants.ROUTER_MARK_MASK))
+        return [mark_packets_entering_external_gateway_port]
+
+    def _empty_snat_chains(self, iptables_manager):
+        iptables_manager.ipv4['nat'].empty_chain('POSTROUTING')
+        iptables_manager.ipv4['nat'].empty_chain('snat')
+        iptables_manager.ipv4['mangle'].empty_chain('mark')
+        iptables_manager.ipv4['mangle'].empty_chain('POSTROUTING')
+
+    def _add_snat_rules(self, ex_gw_port, iptables_manager,
+                        interface_name):
+        self.process_external_port_address_scope_routing(iptables_manager)
+
+        if ex_gw_port:
+            # ex_gw_port should not be None in this case
+            # NAT rules are added only if ex_gw_port has an IPv4 address
+            for ip_addr in ex_gw_port['fixed_ips']:
+                ex_gw_ip = ip_addr['ip_address']
+                if netaddr.IPAddress(ex_gw_ip).version == 4:
+                    if self._snat_enabled:
+                        rules = self.external_gateway_nat_snat_rules(
+                            ex_gw_ip, interface_name)
+                        for rule in rules:
+                            iptables_manager.ipv4['nat'].add_rule(*rule)
+
+                    rules = self.external_gateway_nat_fip_rules(
+                        ex_gw_ip, interface_name)
+                    for rule in rules:
+                        iptables_manager.ipv4['nat'].add_rule(*rule)
+                    rules = self.external_gateway_mangle_rules(interface_name)
+                    for rule in rules:
+                        iptables_manager.ipv4['mangle'].add_rule(*rule)
+
+                    break
+
+    def _handle_router_snat_rules(self, ex_gw_port, interface_name):
+        self._empty_snat_chains(self.iptables_manager)
+
+        self.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat')
+
+        self._add_snat_rules(ex_gw_port,
+                             self.iptables_manager,
+                             interface_name)
+
+    def _process_external_on_delete(self, agent):
+        fip_statuses = {}
+        try:
+            ex_gw_port = self.get_ex_gw_port()
+            self._process_external_gateway(ex_gw_port, agent.pd)
+            if not ex_gw_port:
+                return
+
+            interface_name = self.get_external_device_interface_name(
+                ex_gw_port)
+            fip_statuses = self.configure_fip_addresses(interface_name)
+
+        except (n_exc.FloatingIpSetupException):
+                # All floating IPs must be put in error state
+                LOG.exception(_LE("Failed to process floating IPs."))
+                fip_statuses = self.put_fips_in_error_state()
+        finally:
+            self.update_fip_statuses(agent, fip_statuses)
+
+    def process_external(self, agent):
+        fip_statuses = {}
+        try:
+            with self.iptables_manager.defer_apply():
+                ex_gw_port = self.get_ex_gw_port()
+                self._process_external_gateway(ex_gw_port, agent.pd)
+                if not ex_gw_port:
+                    return
+
+                # Process SNAT/DNAT rules and addresses for floating IPs
+                self.process_snat_dnat_for_fip()
+
+            # Once NAT rules for floating IPs are safely in place
+            # configure their addresses on the external gateway port
+            interface_name = self.get_external_device_interface_name(
+                ex_gw_port)
+            fip_statuses = self.configure_fip_addresses(interface_name)
+
+        except (n_exc.FloatingIpSetupException,
+                n_exc.IpTablesApplyException):
+                # All floating IPs must be put in error state
+                LOG.exception(_LE("Failed to process floating IPs."))
+                fip_statuses = self.put_fips_in_error_state()
+        finally:
+            self.update_fip_statuses(agent, fip_statuses)
+
+    def update_fip_statuses(self, agent, fip_statuses):
+        # Identify floating IPs which were disabled
+        existing_floating_ips = self.floating_ips
+        self.floating_ips = set(fip_statuses.keys())
+        for fip_id in existing_floating_ips - self.floating_ips:
+            fip_statuses[fip_id] = l3_constants.FLOATINGIP_STATUS_DOWN
+        # filter out statuses that didn't change
+        fip_statuses = {f: stat for f, stat in fip_statuses.items()
+                        if stat != FLOATINGIP_STATUS_NOCHANGE}
+        if not fip_statuses:
+            return
+        LOG.debug('Sending floating ip statuses: %s', fip_statuses)
+        # Update floating IP status on the neutron server
+        agent.plugin_rpc.update_floatingip_statuses(
+            agent.context, self.router_id, fip_statuses)
+
+    def _get_port_devicename_scopemark(self, ports, name_generator):
+        devicename_scopemark = {l3_constants.IP_VERSION_4: dict(),
+                                l3_constants.IP_VERSION_6: dict()}
+        for p in ports:
+            device_name = name_generator(p['id'])
+            ip_cidrs = common_utils.fixed_ip_cidrs(p['fixed_ips'])
+            port_as_marks = self.get_port_address_scope_mark(p)
+            for ip_version in {ip_lib.get_ip_version(cidr)
+                               for cidr in ip_cidrs}:
+                devicename_scopemark[ip_version][device_name] = (
+                    port_as_marks[ip_version])
+
+        return devicename_scopemark
+
+    def _get_address_scope_mark(self):
+        # Prepare address scope iptables rule for internal ports
+        internal_ports = self.router.get(l3_constants.INTERFACE_KEY, [])
+        ports_scopemark = self._get_port_devicename_scopemark(
+            internal_ports, self.get_internal_device_name)
+
+        # Prepare address scope iptables rule for external port
+        external_port = self.get_ex_gw_port()
+        if external_port:
+            external_port_scopemark = self._get_port_devicename_scopemark(
+                [external_port], self.get_external_device_name)
+            for ip_version in (l3_constants.IP_VERSION_4,
+                               l3_constants.IP_VERSION_6):
+                ports_scopemark[ip_version].update(
+                    external_port_scopemark[ip_version])
+        return ports_scopemark
+
+    def _add_address_scope_mark(self, iptables_manager, ports_scopemark):
+        external_device_name = None
+        external_port = self.get_ex_gw_port()
+        if external_port:
+            external_device_name = self.get_external_device_name(
+                external_port['id'])
+
+        # Process address scope iptables rules
+        for ip_version in (l3_constants.IP_VERSION_4,
+                           l3_constants.IP_VERSION_6):
+            scopemarks = ports_scopemark[ip_version]
+            iptables = iptables_manager.get_tables(ip_version)
+            iptables['mangle'].empty_chain('scope')
+            iptables['filter'].empty_chain('scope')
+            dont_block_external = (ip_version == l3_constants.IP_VERSION_4
+                                   and self._snat_enabled and external_port)
+            for device_name, mark in scopemarks.items():
+                # Add address scope iptables rule
+                iptables['mangle'].add_rule(
+                    'scope',
+                    self.address_scope_mangle_rule(device_name, mark))
+                if dont_block_external and device_name == external_device_name:
+                    continue
+                iptables['filter'].add_rule(
+                    'scope',
+                    self.address_scope_filter_rule(device_name, mark))
+
+    def process_ports_address_scope_iptables(self):
+        ports_scopemark = self._get_address_scope_mark()
+        self._add_address_scope_mark(self.iptables_manager, ports_scopemark)
+
+    def _get_external_address_scope(self):
+        external_port = self.get_ex_gw_port()
+        if not external_port:
+            return
+
+        scopes = external_port.get('address_scopes', {})
+        return scopes.get(str(l3_constants.IP_VERSION_4))
+
+    def process_external_port_address_scope_routing(self, iptables_manager):
+        if not self._snat_enabled:
+            return
+
+        external_port = self.get_ex_gw_port()
+        if not external_port:
+            return
+
+        external_devicename = self.get_external_device_name(
+            external_port['id'])
+
+        # Saves the originating address scope by saving the packet MARK to
+        # the CONNMARK for new connections so that returning traffic can be
+        # match to it.
+        rule = ('-o %s -m connmark --mark 0x0/0xffff0000 '
+                '-j CONNMARK --save-mark '
+                '--nfmask 0xffff0000 --ctmask 0xffff0000' %
+                external_devicename)
+
+        iptables_manager.ipv4['mangle'].add_rule('POSTROUTING', rule)
+
+        address_scope = self._get_external_address_scope()
+        if not address_scope:
+            return
+
+        # Prevents snat within the same address scope
+        rule = '-o %s -m connmark --mark %s -j ACCEPT' % (
+            external_devicename,
+            self.get_address_scope_mark_mask(address_scope))
+        iptables_manager.ipv4['nat'].add_rule('snat', rule)
+
+    def process_address_scope(self):
+        with self.iptables_manager.defer_apply():
+            self.process_ports_address_scope_iptables()
+            self.process_floating_ip_address_scope_rules()
+
+    @common_utils.exception_logger()
+    def process_delete(self, agent):
+        """Process the delete of this router
+
+        This method is the point where the agent requests that this router
+        be deleted. This is a separate code path from process in that it
+        avoids any changes to the qrouter namespace that will be removed
+        at the end of the operation.
+
+        :param agent: Passes the agent in order to send RPC messages.
+        """
+        LOG.debug("process router delete")
+        if self.router_namespace.exists():
+            self._process_internal_ports(agent.pd)
+            agent.pd.sync_router(self.router['id'])
+            self._process_external_on_delete(agent)
+        else:
+            LOG.warning(_LW("Can't gracefully delete the router %s: "
+                            "no router namespace found."), self.router['id'])
+
+    @common_utils.exception_logger()
+    def process(self, agent):
+        """Process updates to this router
+
+        This method is the point where the agent requests that updates be
+        applied to this router.
+
+        :param agent: Passes the agent in order to send RPC messages.
+        """
+        LOG.debug("process router updates")
+        self._process_internal_ports(agent.pd)
+        agent.pd.sync_router(self.router['id'])
+        self.process_external(agent)
+        self.process_address_scope()
+        # Process static routes for router
+        self.routes_updated(self.routes, self.router['routes'])
+        self.routes = self.router['routes']
+
+        # Update ex_gw_port and enable_snat on the router info cache
+        self.ex_gw_port = self.get_ex_gw_port()
+        self.fip_map = dict([(fip['floating_ip_address'],
+                              fip['fixed_ip_address'])
+                             for fip in self.get_floating_ips()])
+        # TODO(Carl) FWaaS uses this.  Why is it set after processing is done?
+        self.enable_snat = self.router.get('enable_snat')
-- 
cgit