components/openstack/neutron/files/agent/evs_l3_agent.py
author Devjani Ray <devjani.ray@oracle.com>
Fri, 20 May 2016 17:42:29 -0400
branchs11u3-sru
changeset 6035 c9748fcc32de
parent 4647 f1f27134bd1c
child 6444 bf62eba2612a
permissions -rw-r--r--
PSARC 2015/535 OpenStack service updates for Kilo PSARC 2015/458 aioeventlet - asyncio event loop scheduling callbacks in eventlet PSARC 2015/460 msgpack - C/Python bindings for MessagePack (de)serializer data PSARC 2015/466 openstackclient - OpenStack Command-line Client PSARC 2015/467 oslo.versionedobjects - Oslo Versioned Objects library PSARC 2015/468 pint - A physical quantities module PSARC 2015/469 pysaml2 - A pure Python implementation of SAML2 PSARC 2015/471 semantic_version - A library implementing the 'SemVer' scheme PSARC 2015/472 testresources - PyUnit extension for managing expensive test resources PSARC 2015/473 testscenarios - Extensions to Python unittest to support scenarios PSARC 2015/474 trollius - Port of the Tulip project (asyncio module, PEP 3156) on Python 2 PSARC 2015/475 urllib3 - HTTP library with thread-safe connection pooling, file post, and more PSARC 2015/520 oslo.concurrency - Oslo Concurrency library PSARC 2015/521 oslo.log - Oslo Logging Configuration library PSARC 2015/529 oslo.policy - Oslo Policy library PSARC 2015/530 psutil - Python system and process utilities PSARC 2015/538 fixtures - Python module to support reusable state for writing clean tests PSARC 2015/539 sqlparse - An SQL parser module for Python PSARC 2016/017 extras - Useful extra utilities for Python PSARC 2016/018 linecache2 - Port of the standard linecache module PSARC 2016/019 python-mimeparse - Basic functions for parsing mime-types PSARC 2016/020 testtools - Extensions to the Python unit testing framework PSARC 2016/021 traceback2 - Port of the standard traceback module PSARC 2016/014 OpenStack Cinder NFS driver for Solaris PSARC/2016/010 cloudbase-init: Portable cloud image initialization PSARC/2016/130 Solaris OpenStack Puppet Extensions PSARC/2016/172 Making OpenStack Nova's image cache sharable PSARC/2016/001 OpenStack Puppet Modules PSARC/2016/016 Rename/Refactor Puppet and Puppet Module Packages PSARC/2015/368 Common Puppet Modules PSARC 2015/357 OpenStack Nova support for kernel zone suspend/resume 22384068 OpenStack service updates for Kilo (Umbrella) 23205460 Fix for 23192887 breaks Juno to Kilo upgrade with instances and floating IPs 23192887 Upgrade from Juno to Kilo fails (neutron-upgrade) due to typo 22878181 Neutron database tables not upgraded properly from Juno to Kilo schema 22935140 Kilo upgrade adds deprecated settings for rabbit and qpid 22935039 Kilo upgrade conf file migration errors 23027746 Metadata access broken with too many networks 23040216 extra zfssa_ prefix in the zfssa_iscsi.pp backend manifest 22992961 Update saz-memcached to 2.8.1 22992956 Update puppetlabs-stdlib to 4.11.0 22992951 Update puppetlabs-ntp to 4.1.2 22992946 Update puppetlabs-mysql to 3.6.2 22992926 Update puppetlabs-apache to 1.8.1 22992933 Update puppetlabs-inifile to 1.4.3 22999085 apache puppet module doesn't support ssl on Solaris 22985076 neutron_network provider always sets --shared on new networks 22813139 add zfssa cinder puppet modules 22902222 add NFS cinder puppet modules 22918553 update vpnaas and l3 agent puppet modules 22902853 Neutron/VPNaaS needs a workaround for 22902761 22827759 nova-compute still trips over itself when rad:local restarts 20990774 nova image cache bloats clone archives to godzilla size 22750945 Revert resize same host branded zones results in error status 18733958 nova tried to create x86 instance on SPARC 22220227 failure to apply zonecfg in attach_volume can leave debris in zonecfg 22935198 puppetlabs-mysql should define basedir in params.pp 22911268 Update puppetlabs-rabbitmq to 5.3.1 22852949 problem in PYTHON-MOD/DJANGO 22852962 problem in PYTHON-MOD/DJANGO 22819808 target_provision_state should not be set to AVAILABLE 22491714 Request to integrate OpenStack Puppet modules 22713569 nova-conductor doesn't handle RPC timeout during live-migration well 22695176 Miscellaneous package cleanup for Kilo 22694904 Some of the OpenStack patches can be cleaned up 22694680 Dependencies in several OpenStack service packages can be improved 22694592 Several configuration files should be more aligned with the upstream 22575858 problem in SERVICE/SWIFT 22047789 puppet package name and dependencies are confusing 22664785 Puppet module files should be owned by puppet 21460057 Add cloudbase-init to Solaris 21974208 The Python module msgpack should be added to Userland 22010630 The Python trollius module should be added to Userland 22011755 The Python module pint should be added to Userland 22012256 The Python aioeventlet module should be added to Userland 22012282 The Python oslo.versionedobjects module should be added to Userland 22012317 The Python semantic_version module should be added to Userland 22012321 The Python testresources module should be added to Userland 22012329 The Python testscenarios module should be added to Userland 22012336 The Python urllib3 module should be added to Userland 22012343 The Python openstackclient module should be added to Userland 22299389 The Python oslo.concurrency module should be added to Userland 22299409 The Python oslo.log module should be added to Userland 22299418 The Python oslo.policy module should be added to Userland 22299469 The Python psutil module should be added to Userland 22337793 The Python sqlparse module should be added to Userland 22338325 The Python fixtures module should be added to Userland 22535728 The Python testtools module should be added to Userland 22535739 The Python extras module should be added to Userland 22535748 The Python linecache2 module should be added to Userland 22535753 The Python traceback2 module should be added to Userland 22535760 The Python python-mimeparse module should be added to Userland 18961001 Image filtering does not function as expected 21678935 NFS for Cinder in Solaris OpenStack 22548630 derived manifest should not enforce presence of global when installing from UAR 22629795 problem in SERVICE/KEYSTONE 22151922 zones_suspend_path needs update based on post-PSARC discussion 21660603 passlib dependency needs to be added to Nova 22188197 puppetlabs-rabbitmq needs a patch to handle rabbitmqadmin 21756542 problem in SERVICE/SWIFT 21978756 addrconf addresses must be created for stateless and slaac Neutron subnets 21978743 ndpd.conf entries are incorrectly formatted for IPv6 subnets 21919000 neutron-dhcp-agent and neutron-server have timing issues 21918991 database times out when attempting various actions 21682493 Neutron fails due to mysql transaction locks when creating multiple instances 22024767 Remove annoying "Arguments dropped when creating context" logging 21691386 Request to integrate common puppet modules into Userland 21630128 Neutron needs to support updating subnet DNS configuration 21761279 Driver erroneously includes trailing space in zone.install() arguments 21438537 After update, openstack/keystone/keystone-token-flush not running 21630538 Nova driver should support suspend/resume 21542088 VM's display_name is used instead of hostname to set the hostname for VM 19774239 Nova should support setting the Admin Password 21439855 Console SMF instance remains after nova instance is deleted 21348400 Issues encountered via unit testing 21341088 Parsing manifest/profiles fails if multiple criteria present 21303465 edit image window has no ZFS disk format option 21091598 ceilometerclient's Makefile needs to point to its own PROJECT_URL 21164329 saharaclient COMPONENT_BUGDB points to the wrong subcomponent 20431382 keystone should include a periodic token cleanup job 21299660 enable no-gateway check box now that we support it 21135855 Enable gateway-less external networks 20230409 remove _get_zone_auto_install_state from driver.py 21022556 optional dependencies on rabbitmq need work in OpenStack services 22568587 heat denial of service through template-validate 22157556 RabbitMQ Warning: Mochiweb enabled and Erlang version 17

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 VMware, Inc.  All rights reserved.
#
# Copyright (c) 2014, 2016, Oracle and/or its affiliates. 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.
#

"""
Based off generic l3_agent (neutron/agent/l3_agent) code
"""

import errno
import netaddr

from oslo.config import cfg
from oslo_log import log as logging

from neutron.agent.l3 import agent as l3_agent
from neutron.agent.l3 import router_info as router
from neutron.agent.linux import utils
from neutron.agent.solaris import interface
from neutron.agent.solaris import ipfilters_manager
from neutron.agent.solaris import net_lib
from neutron.agent.solaris import ra
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import constants as l3_constants
from neutron.common import exceptions as n_exc
from neutron.common import utils as common_utils
from oslo_utils import importutils
from oslo_log import log as logging

import neutron_vpnaas.services.vpn.agent as neutron_vpnaas
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn import vpn_service

LOG = logging.getLogger(__name__)
INTERNAL_DEV_PREFIX = 'l3i'
EXTERNAL_DEV_PREFIX = 'l3e'
FLOATING_IP_CIDR_SUFFIX = '/32'


class SolarisRouterInfo(router.RouterInfo):

    def __init__(self, router_id, router, agent_conf, interface_driver,
                 use_ipv6=False):
        super(SolarisRouterInfo, self).__init__(router_id, router, agent_conf,
                                                interface_driver, use_ipv6)
        self.ipfilters_manager = ipfilters_manager.IPfiltersManager()
        self.iptables_manager = None
        self.remove_route = False

    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.NDPD(self.router_id, self.get_internal_device_name)

    def get_internal_device_name(self, port_id):
        # Because of the way how dnsmasq works on Solaris, the length
        # of datalink name cannot exceed 16 (includes terminating nul
        # character). So, the linkname can only have 15 characters and
        # the last two characters are set aside for '_0'. So, we only
        # have 13 characters left.
        dname = (INTERNAL_DEV_PREFIX + port_id)[:13]
        dname += '_0'
        return dname.replace('-', '_')

    def get_external_device_name(self, port_id):
        # please see the comment above
        dname = (EXTERNAL_DEV_PREFIX + port_id)[:13]
        dname += '_0'
        return dname.replace('-', '_')

    def routes_updated(self):
        pass

    def _get_existing_devices(self):
        return net_lib.Datalink.show_link()

    def internal_network_added(self, port):
        internal_dlname = self.get_internal_device_name(port['id'])
        # driver just returns if datalink and IP interface already exists
        self.driver.plug(port['tenant_id'], port['network_id'], port['id'],
                         internal_dlname)
        ip_cidrs = common_utils.fixed_ip_cidrs(port['fixed_ips'])
        self.driver.init_l3(internal_dlname, ip_cidrs)

        # Since we support shared router model, we need to block the new
        # internal port from reaching other tenant's ports
        block_pname = self._get_ippool_name(port['mac_address'])
        self.ipfilters_manager.add_ippool(block_pname, None)
        if self.agent_conf.allow_forwarding_between_networks:
            # If allow_forwarding_between_networks is set, then we need to
            # allow forwarding of packets between same tenant's ports.
            allow_pname = self._get_ippool_name(port['mac_address'], '0')
            self.ipfilters_manager.add_ippool(allow_pname, None)

        # walk through the other internal ports and retrieve their
        # cidrs and at the same time add the new internal port's
        # cidr to them
        port_subnet = port['subnets'][0]['cidr']
        block_subnets = []
        allow_subnets = []
        for internal_port in self.internal_ports:
            if internal_port['mac_address'] == port['mac_address']:
                continue
            if (self.agent_conf.allow_forwarding_between_networks and
                    internal_port['tenant_id'] == port['tenant_id']):
                allow_subnets.append(internal_port['subnets'][0]['cidr'])
                # we need to add the port's subnet to this internal_port's
                # allowed_subnet_pool
                iport_allow_pname = \
                    self._get_ippool_name(internal_port['mac_address'], '0')
                self.ipfilters_manager.add_ippool(iport_allow_pname,
                                                  [port_subnet])
            else:
                block_subnets.append(internal_port['subnets'][0]['cidr'])
                iport_block_pname = \
                    self._get_ippool_name(internal_port['mac_address'])
                self.ipfilters_manager.add_ippool(iport_block_pname,
                                                  [port_subnet])
        # update the new port's pool with other ports' subnet
        self.ipfilters_manager.add_ippool(block_pname, block_subnets)
        if self.agent_conf.allow_forwarding_between_networks:
            self.ipfilters_manager.add_ippool(allow_pname, allow_subnets)

        # now setup the IPF rules
        rules = ['block in quick on %s from %s to pool/%d' %
                 (internal_dlname, port_subnet, block_pname)]
        # pass in packets between networks that belong to same tenant
        if self.agent_conf.allow_forwarding_between_networks:
            rules.append('pass in quick on %s from %s to pool/%d' %
                         (internal_dlname, port_subnet, allow_pname))
        # if the external gateway is already setup for the shared router,
        # then we need to add Policy Based Routing (PBR) for this internal
        # network
        ex_gw_port = self.ex_gw_port
        ex_gw_ip = (ex_gw_port['subnets'][0]['gateway_ip']
                    if ex_gw_port else None)
        if ex_gw_ip:
            external_dlname = self.get_external_device_name(ex_gw_port['id'])
            rules.append('pass in on %s to %s:%s from any to !%s' %
                         (internal_dlname, external_dlname, ex_gw_ip,
                          port_subnet))

        ipversion = netaddr.IPNetwork(port_subnet).version
        self.ipfilters_manager.add_ipf_rules(rules, ipversion)
        if self.agent_conf.enable_metadata_proxy and ipversion == 4:
            rdr_rule = ['rdr %s 169.254.169.254/32 port 80 -> %s port %d tcp' %
                        (internal_dlname, port['fixed_ips'][0]['ip_address'],
                         self.agent_conf.metadata_port)]
            self.ipfilters_manager.add_nat_rules(rdr_rule)

    def internal_network_removed(self, port):
        internal_dlname = self.get_internal_device_name(port['id'])
        port_subnet = port['subnets'][0]['cidr']
        # remove all the IP filter rules that we added during
        # internal network addition
        block_pname = self._get_ippool_name(port['mac_address'])
        rules = ['block in quick on %s from %s to pool/%d' %
                 (internal_dlname, port_subnet, block_pname)]
        if self.agent_conf.allow_forwarding_between_networks:
            allow_pname = self._get_ippool_name(port['mac_address'], '0')
            rules.append('pass in quick on %s from %s to pool/%d' %
                         (internal_dlname, port_subnet, allow_pname))

        # remove all the IP filter rules that we added during
        # external network addition
        ex_gw_port = self.ex_gw_port
        ex_gw_ip = (ex_gw_port['subnets'][0]['gateway_ip']
                    if ex_gw_port else None)
        if ex_gw_ip:
            external_dlname = self.get_external_device_name(ex_gw_port['id'])
            rules.append('pass in on %s to %s:%s from any to !%s' %
                         (internal_dlname, external_dlname, ex_gw_ip,
                          port_subnet))
        ipversion = netaddr.IPNetwork(port['subnets'][0]['cidr']).version
        self.ipfilters_manager.remove_ipf_rules(rules, ipversion)

        # remove the ippool
        self.ipfilters_manager.remove_ippool(block_pname, None)
        if self.agent_conf.allow_forwarding_between_networks:
            self.ipfilters_manager.remove_ippool(allow_pname, None)

        for internal_port in self.internal_ports:
            if (self.agent_conf.allow_forwarding_between_networks and
                    internal_port['tenant_id'] == port['tenant_id']):
                iport_allow_pname = \
                    self._get_ippool_name(internal_port['mac_address'], '0')
                self.ipfilters_manager.remove_ippool(iport_allow_pname,
                                                     [port_subnet])
            else:
                iport_block_pname = \
                    self._get_ippool_name(internal_port['mac_address'])
                self.ipfilters_manager.remove_ippool(iport_block_pname,
                                                     [port_subnet])
        if self.agent_conf.enable_metadata_proxy and ipversion == 4:
            rdr_rule = ['rdr %s 169.254.169.254/32 port 80 -> %s port %d tcp' %
                        (internal_dlname, port['fixed_ips'][0]['ip_address'],
                         self.agent_conf.metadata_port)]
            self.ipfilters_manager.remove_nat_rules(rdr_rule)

        if net_lib.Datalink.datalink_exists(internal_dlname):
            self.driver.fini_l3(internal_dlname)
            self.driver.unplug(internal_dlname)

    def _process_internal_ports(self):
        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)
            self.internal_ports.append(p)
            enable_ra = enable_ra or self._port_has_ipv6_subnet(p)

        for p in old_ports:
            self.internal_network_removed(p)
            self.internal_ports.remove(p)
            enable_ra = enable_ra or self._port_has_ipv6_subnet(p)

#         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'])
#                 self.driver.init_l3(interface_name, ip_cidrs=ip_cidrs,
#                         namespace=self.ns_name)
#                 enable_ra = enable_ra or self._port_has_ipv6_subnet(p)

        # Enable RA
        if enable_ra:
            self.radvd.enable(internal_ports)

        # remove any internal stale router interfaces (i.e., l3i.. VNICs)
        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)
            self.driver.fini_l3(stale_dev)
            self.driver.unplug(stale_dev)

    def _get_ippool_name(self, mac_address, suffix=None):
        # Generate a unique-name for ippool(1m) from that last 3
        # bytes of mac-address. It is called pool name, but it is
        # actually a 32 bit integer
        name = mac_address.split(':')[3:]
        if suffix:
            name.append(suffix)
        return int("".join(name), 16)

    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

        ipintf = net_lib.IPInterface(interface_name)
        ipaddr_list = ipintf.ipaddr_list()['static']

        existing_cidrs = set(ipaddr_list)
        new_cidrs = set()

        existing_nat_rules = [nat_rule for nat_rule in
                              self.ipfilters_manager.ipv4['nat']]
        new_nat_rules = []

        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']
            fip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX
            new_cidrs.add(fip_cidr)
            fixed_cidr = str(fip['fixed_ip_address']) + '/32'
            nat_rule = 'bimap %s %s -> %s' % (interface_name, fixed_cidr,
                                              fip_cidr)

            if fip_cidr not in existing_cidrs:
                try:
                    ipintf.create_address(fip_cidr)
                    self.ipfilters_manager.add_nat_rules([nat_rule])
                except Exception as err:
                    # TODO(gmoodalb): If we fail in add_nat_rules(), then
                    # we need to remove the fip_cidr address

                    # any exception occurred here should cause the floating IP
                    # to be set in error state
                    fip_statuses[fip['id']] = (
                        l3_constants.FLOATINGIP_STATUS_ERROR)
                    LOG.warn(_("Unable to configure IP address for "
                               "floating IP: %s: %s") % (fip['id'], err))
                    continue
            fip_statuses[fip['id']] = (
                l3_constants.FLOATINGIP_STATUS_ACTIVE)
            LOG.debug("Floating ip %(id)s added, status %(status)s",
                      {'id': fip['id'],
                       'status': fip_statuses.get(fip['id'])})

            new_nat_rules.append(nat_rule)

        # remove all the old NAT rules
        old_nat_rules = list(set(existing_nat_rules) - set(new_nat_rules))
        # Filter out 'bimap' NAT rules as we don't want to remove NAT rules
        # that were added for Metadata server
        old_nat_rules = [rule for rule in old_nat_rules if "bimap" in rule]
        self.ipfilters_manager.remove_nat_rules(old_nat_rules)

        # Clean up addresses that no longer belong on the gateway interface.
        for ip_cidr in existing_cidrs - new_cidrs:
            if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX):
                ipintf.delete_address(ip_cidr)
        return fip_statuses

    # Todo(gmoodalb): need to do more work on ipv6 gateway
    def external_gateway_added(self, ex_gw_port, external_dlname):

        if not net_lib.Datalink.datalink_exists(external_dlname):
            dl = net_lib.Datalink(external_dlname)
            # determine the network type of the external network
            evsname = ex_gw_port['network_id']
            cmd = ['/usr/sbin/evsadm', 'show-evs', '-co', 'l2type,vid',
                   '-f', 'evs=%s' % evsname]
            try:
                stdout = utils.execute(cmd)
            except Exception as err:
                LOG.error(_("Failed to retrieve the network type for "
                            "the external network, and it is required "
                            "to create an external gateway port: %s") % err)
                return
            output = stdout.splitlines()[0].strip()
            l2type, vid = output.split(':')
            if l2type != 'flat' and l2type != 'vlan':
                LOG.error(_("External network should be either Flat or "
                            "VLAN based, and it is required to "
                            "create an external gateway port"))
                return
            elif (l2type == 'vlan' and
                  self.agent_conf.get("external_network_datalink", None)):
                LOG.warning(_("external_network_datalink is deprecated in "
                              "Juno and will be removed in the next release "
                              "of Solaris OpenStack. Please use the evsadm "
                              "set-controlprop subcommand to setup the "
                              "uplink-port for an external network"))
                # proceed with the old-style of doing things
                mac_address = ex_gw_port['mac_address']
                dl.create_vnic(self.agent_conf.external_network_datalink,
                               mac_address=mac_address, vid=vid)
            else:
                self.driver.plug(ex_gw_port['tenant_id'],
                                 ex_gw_port['network_id'],
                                 ex_gw_port['id'], external_dlname)

        ip_cidrs = common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips'])
        self.driver.init_l3(external_dlname, ip_cidrs)

        gw_ip = ex_gw_port['subnets'][0]['gateway_ip']
        if gw_ip:
            cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'add', 'default',
                   gw_ip]
            stdout = utils.execute(cmd, extra_ok_codes=[errno.EEXIST])
            if 'entry exists' not in stdout:
                self.remove_route = True

            # for each of the internal ports, add Policy Based
            # Routing (PBR) rule
            for port in self.internal_ports:
                internal_dlname = self.get_internal_device_name(port['id'])
                rules = ['pass in on %s to %s:%s from any to !%s' %
                         (internal_dlname, external_dlname, gw_ip,
                          port['subnets'][0]['cidr'])]
                ipversion = \
                    netaddr.IPNetwork(port['subnets'][0]['cidr']).version
                self.ipfilters_manager.add_ipf_rules(rules, ipversion)

    def external_gateway_updated(self, ex_gw_port, external_dlname):
        # There is nothing to do on Solaris
        pass

    def external_gateway_removed(self, ex_gw_port, external_dlname):
        gw_ip = ex_gw_port['subnets'][0]['gateway_ip']
        if gw_ip:
            # remove PBR rules
            for port in self.internal_ports:
                internal_dlname = self.get_internal_device_name(port['id'])
                rules = ['pass in on %s to %s:%s from any to !%s' %
                         (internal_dlname, external_dlname, gw_ip,
                          port['subnets'][0]['cidr'])]
                ipversion = \
                    netaddr.IPNetwork(port['subnets'][0]['cidr']).version
                self.ipfilters_manager.remove_ipf_rules(rules, ipversion)

            if self.remove_route:
                cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'delete',
                       'default', gw_ip]
                utils.execute(cmd, check_exit_code=False)

        if net_lib.Datalink.datalink_exists(external_dlname):
            self.driver.fini_l3(external_dlname)
            self.driver.unplug(external_dlname)

    def _process_external_gateway(self, ex_gw_port):
        # 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:
            def _gateway_ports_equal(port1, port2):
                def _get_filtered_dict(d, ignore):
                    return dict((k, v) for k, v in d.iteritems()
                                if k not in ignore)

                keys_to_ignore = set(['binding:host_id'])
                port1_filtered = _get_filtered_dict(port1, keys_to_ignore)
                port2_filtered = _get_filtered_dict(port2, keys_to_ignore)
                return port1_filtered == port2_filtered

            if not self.ex_gw_port:
                self.external_gateway_added(ex_gw_port, interface_name)
            elif not _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)

        # Remove any external stale router interfaces (i.e., l3e.. VNICs)
        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)
            self.driver.fini_l3(stale_dev)
            self.driver.unplug(stale_dev)

        # Process SNAT rules for external gateway
        self.perform_snat_action(self._handle_router_snat_rules,
                                 interface_name)

    def external_gateway_snat_rules(self, ex_gw_ip, interface_name):
        rules = []
        ip_cidrs = []
        for port in self.internal_ports:
            if netaddr.IPNetwork(port['subnets'][0]['cidr']).version == 4:
                ip_cidrs.extend(common_utils.fixed_ip_cidrs(port['fixed_ips']))

        for ip_cidr in ip_cidrs:
            rules.append('map %s %s -> %s/32' %
                         (interface_name, ip_cidr, ex_gw_ip))
        return rules

    def _handle_router_snat_rules(self, ex_gw_port, interface_name, action):
        # Remove all the old SNAT rules
        # This is safe because if use_namespaces is set as False
        # then the agent can only configure one router, otherwise
        # each router's SNAT rules will be in their own namespace

        # get only the SNAT rules
        old_snat_rules = [rule for rule in self.ipfilters_manager.ipv4['nat']
                          if rule.startswith('map')]
        self.ipfilters_manager.remove_nat_rules(old_snat_rules)

        # And add them back if the action is add_rules
        if action == 'add_rules' and ex_gw_port:
            # 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:
                    rules = self.external_gateway_snat_rules(ex_gw_ip,
                                                             interface_name)
                    self.ipfilters_manager.add_nat_rules(rules)
                    break

    def process_external(self, agent):
        existing_floating_ips = self.floating_ips
        try:
            ex_gw_port = self.get_ex_gw_port()
            self._process_external_gateway(ex_gw_port)
            # TODO(Carl) Return after setting existing_floating_ips and
            # still call update_fip_statuses?
            if not ex_gw_port:
                return

            # Once NAT rules for floating IPs are safely in place
            # configure their addresses on the external gateway port
            interface_name = self.get_external_device_name(ex_gw_port['id'])
            fip_statuses = self.configure_fip_addresses(interface_name)
        except (n_exc.FloatingIpSetupException,
                n_exc.IpTablesApplyException) as e:
                # All floating IPs must be put in error state
                LOG.exception(e)
                fip_statuses = self.put_fips_in_error_state()

        agent.update_fip_statuses(self, existing_floating_ips, fip_statuses)


class EVSL3NATAgent(l3_agent.L3NATAgentWithStateReport):
    OPTS = [
        cfg.StrOpt('external_network_datalink', default='net0',
                   help=_("Name of the datalink that connects to "
                          "an external network.")),
        cfg.BoolOpt('allow_forwarding_between_networks', default=False,
                    help=_("Allow forwarding of packets between tenant's "
                           "networks")),
    ]

    def __init__(self, host, conf=None):
        cfg.CONF.register_opts(self.OPTS)
        cfg.CONF.register_opts(interface.OPTS)
        super(EVSL3NATAgent, self).__init__(host=host, conf=conf)
        cfg.CONF.register_opts(neutron_vpnaas.vpn_agent_opts, 'vpnagent')
        self.service = vpn_service.VPNService(self)
        self.device_drivers = self.service.load_device_drivers(host)

    def _router_added(self, router_id, router):
        args = []
        kwargs = {
            'router_id': router_id,
            'router': router,
            'use_ipv6': self.use_ipv6,
            'agent_conf': self.conf,
            'interface_driver': self.driver,
        }
        ri = SolarisRouterInfo(*args, **kwargs)
        registry.notify(resources.ROUTER, events.BEFORE_CREATE,
                        self, router=ri)

        self.router_info[router_id] = ri

        ri.initialize(self.process_monitor)