components/openstack/neutron/files/agent/evs_l3_agent.py
author Danek Duvall <danek.duvall@oracle.com>
Tue, 07 Apr 2015 13:31:20 -0700
branchs11-update
changeset 4072 db0cec748ec0
parent 3435 25a421888935
child 4430 5858809d8d01
permissions -rw-r--r--
PSARC 2015/110 OpenStack service updates for Juno PSARC 2014/302 oslo.messaging - OpenStack RPC and notifications PSARC 2014/303 concurrent.futures - high-level Python interface for asynchronous execution PSARC 2014/304 networkx - Python module for complex networks PSARC 2014/305 taskflow - Python module for task execution PSARC 2014/329 pycadf - Python interface for CADF (cloud auditing) PSARC 2014/330 posix_ipc - POSIX IPC primitives for Python PSARC 2014/331 oauthlib - Python implementation of OAuth request-signing logic PSARC 2015/058 oslo - OpenStack common libraries (context, db, i18n, middleware, serialization, utils, vmware) PSARC 2015/059 glance_store - Glance storage library PSARC 2015/060 ipaddr - an IPv4/IPv6 manipulation library in Python PSARC 2015/061 simplegeneric - single-dispatch generic Python functions PSARC 2015/062 wsme - Web Services Made Easy PSARC 2015/063 retrying - General purpose Python retrying library PSARC 2015/065 osprofiler - an OpenStack cross-project profiling library PSARC 2015/066 OpenStack client for Sahara (Hadoop as a Service) PSARC 2015/067 keystonemiddleware - Middleware for OpenStack Identity PSARC 2015/068 pyScss - Compiler for the SCSS flavor of the Sass language PSARC 2015/069 django-pyscss - pyScss support for Django PSARC 2015/073 barbicanclient - OpenStack client for Barbican (Key Management) PSARC 2015/074 pysendfile - Python interface to sendfile PSARC 2015/097 ldappool - a connection pool for python-ldap PSARC 2015/098 rfc3986 - URI reference validation module for Python PSARC 2015/102 iniparse - python .ini file parsing module 20667775 OpenStack service updates for Juno (Umbrella) 17511386 sqlalchemy-migrate should lose its bypass-gen tags once sqlalchemy is in the CBE 18293987 /usr/bin/alembic should be shipped 18293992 boto's demo scripts aren't delivered executable 18377642 py.test has a requirement on py 18615101 Horizon should prevent network, subnet, and port names with hyphens in them 18772068 instance failed to launch with NoValidHost but no reason 18887457 openstack shouldn't deliver .po files 18905324 hostname.xml should set config/ignore_dhcp_hostname = true 18961031 Duplicate names for role-create and user-create are allowed 19015363 Users should not be allowed to attempt to create volumes when quota exceed 19044301 boto's dependencies need work 19050335 user appears logged in but unauthorised after horizon reboot 19065699 cinderclient-34 lost in recent upgrade 19131218 solaris.css: 'Delete Interface' button in Router pop-up menu broken 19131507 solaris.css: 'Project Limits' section of Launch Instance pop-up menu broken 19144215 Instance manipulation buttons greyed out after all instances terminated 19249066 heat stack-preview doesn't appear to do anything 19313272 Need bottom slidebar in horizon for small browser windows 19439030 'nova migration-list' returns python error 19462265 The Python module oslo.messaging should be added to Userland 19462397 The Python module futures should be added to Userland 19476604 The Python module networkx should be added to Userland 19476953 The Python module taskflow should be added to Userland 19519227 The Python module pycadf should be added to Userland 19582394 The Python module posix_ipc should be added to Userland 19596691 instance failed to launch, cinder hit resource busy in stmfadm 19598430 The Python module oauthlib should be added to Userland 19649055 FC connection fails when the target_lun is assigned 0 19815780 nova package should have dependencies on brand-solaris and brand-solaris-kz 19883623 Image snapshots are missing 'instance_uuid' property 19887874 horizon should set up apache log rotation 19888859 six should enable its tests now. 19987962 Cinder lists additional volumes attached to instance with linuxy device names 20027791 horizon should be migrated to Apache 2.4 20046570 rabbitmq & rad-evs-controller should be added to group package 20052466 remove _ai_health_check() from driver.py now that 18857274 is integrated 20164815 The Python module django-pyscss should be added to Userland 20173049 The Python module retrying should be added to Userland 20174489 The Python module WSME should be added to Userland 20176001 The Python module keystonemiddleware should be added to Userland 20182039 The Python module pysendfile should be added to Userland 20200162 The Python module pyScss should be added to Userland 20222184 horizon doesn't send start request on shutdown instance 20312312 The Python module python-saharaclient should be added to Userland 20388250 problem in SERVICE/GLANCE 20433402 The fix for 20388250 is incomplete 20514287 wrong vnic label name used for dhcp vnic in evs 20596802 The Python module oslo.middleware should be added to Userland 20596803 The Python module barbicanclient should be added to Userland 20596804 The Python module oslo.context should be added to Userland 20596805 The Python module iniparse should be added to Userland 20596806 The Python module oslo.vmware should be added to Userland 20596807 The Python module osprofiler should be added to Userland 20596808 The Python module oslo.i18n should be added to Userland 20596809 The Python module oslo.utils should be added to Userland 20596811 The Python module ipaddr should be added to Userland 20596812 The Python module glance_store should be added to Userland 20596813 The Python module oslo.serialization should be added to Userland 20596814 The Python module oslo.db should be added to Userland 20596815 The Python module simplegeneric should be added to Userland 20602690 The Python module ldappool should be added to Userland 20602722 The Python module rfc3986 should be added to Userland 20638369 compilemessages.py requires GNU msgfmt without calling gmsgfmt 20715741 cinder 2014.2.2 20715742 glance 2014.2.2 20715743 heat 2014.2.2 20715744 horizon 2014.2.2 20715745 keystone 2014.2.2 20715746 neutron 2014.2.2 20715747 nova 2014.2.2 20715748 swift 2.2.2 20715749 alembic 0.7.4 20715750 amqp 1.4.6 20715751 boto 2.34.0 20715752 ceilometerclient 1.0.12 20715753 cinderclient 1.1.1 20715754 cliff 1.9.0 20715756 django 1.4.19 20739229 Update django to 1.4.20 20715757 django_compressor 1.4 20715758 django_openstack_auth 1.1.9 20715759 eventlet 0.15.2 20715761 glanceclient 0.15.0 20715762 greenlet 0.4.5 20715763 heatclient 0.2.12 20715764 keystoneclient 1.0.0 20715765 kombu 3.0.7 20715766 mysql 1.2.5 20715767 netaddr 0.7.13 20715769 netifaces 0.10.4 20715770 neutronclient 2.3.10 20715771 novaclient 2.20.0 20715772 oslo.config 1.6.0 20715773 py 1.4.26 20715774 pyflakes 0.8.1 20715775 pytest 2.6.4 20715776 pytz 2014.10 20715777 requests 2.6.0 20715778 simplejson 3.6.5 20715779 six 1.9.0 20715780 sqlalchemy-migrate 0.9.1 20715781 sqlalchemy 0.9.8 20715782 stevedore 1.2.0 20715783 swiftclient 2.3.1 20715784 tox 1.8.1 20715785 troveclient 1.0.8 20715786 virtualenv 12.0.7 20715787 websockify 0.6.0 20739215 problem in PYTHON-MOD/DJANGO 20739295 problem in PYTHON-MOD/DJANGO 20816861 zone-vnc-console instance goes in to maintenance 20829672 support flat network type in neutron

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

# Copyright 2012 VMware, Inc.  All rights reserved.
#
# Copyright (c) 2014, 2015, 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 neutron.agent.common import config
from neutron.agent import l3_agent
from neutron.agent.linux import utils
from neutron.agent.solaris import interface
from neutron.agent.solaris import net_lib
from neutron.agent.solaris import ra
from neutron.common import constants as l3_constants
from neutron.common import utils as common_utils
from neutron.openstack.common import log as logging


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


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)

    def _router_added(self, router_id, router):
        ri = l3_agent.RouterInfo(router_id, None,
                                 self.conf.use_namespaces, router)
        self.router_info[router_id] = ri

        if self.conf.enable_metadata_proxy:
            self._spawn_metadata_proxy(ri.router_id, ri.ns_name)

    def _router_removed(self, router_id):
        ri = self.router_info.get(router_id)
        if ri is None:
            LOG.warn(_("Info for router %s were not found. "
                       "Skipping router removal"), router_id)
            return
        ri.router['gw_port'] = None
        ri.router[l3_constants.INTERFACE_KEY] = []
        ri.router[l3_constants.FLOATINGIP_KEY] = []
        self.process_router(ri)
        if self.conf.enable_metadata_proxy:
            self._destroy_metadata_proxy(ri.router_id, ri.ns_name)

        del self.router_info[router_id]

    def _get_metadata_proxy_callback(self, router_id):
        """Need to override this since we need to pass the absolute
        path to neutron-ns-metadata-proxy binary.
        """
        def callback(pid_file):
            metadata_proxy_socket = cfg.CONF.metadata_proxy_socket
            proxy_cmd = ['/usr/lib/neutron/neutron-ns-metadata-proxy',
                         '--pid_file=%s' % pid_file,
                         '--metadata_proxy_socket=%s' % metadata_proxy_socket,
                         '--router_id=%s' % router_id,
                         '--state_path=%s' % self.conf.state_path,
                         '--metadata_port=%s' % self.conf.metadata_port]
            proxy_cmd.extend(config.get_log_args(
                cfg.CONF, 'neutron-ns-metadata-proxy-%s.log' %
                router_id))
            return proxy_cmd

        return callback

    def external_gateway_snat_rules(self, ex_gw_ip, internal_cidrs,
                                    interface_name):
        rules = []
        for cidr in internal_cidrs:
            rules.append('map %s %s -> %s/32' %
                         (interface_name, cidr, ex_gw_ip))
        return rules

    def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
                                  interface_name, action):
        assert not ri.router['distributed']

        # 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 ri.ipfilters_manager.ipv4['nat']
                          if rule.startswith('map')]
        ri.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,
                                                             internal_cidrs,
                                                             interface_name)
                    ri.ipfilters_manager.add_nat_rules(rules)
                    break

    @common_utils.exception_logger()
    def process_router(self, ri):
        # TODO(mrsmith) - we shouldn't need to check here
        if 'distributed' not in ri.router:
            ri.router['distributed'] = False
        ex_gw_port = self._get_ex_gw_port(ri)
        internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
        existing_port_ids = set([p['id'] for p in ri.internal_ports])
        current_port_ids = set([p['id'] for p in internal_ports
                                if p['admin_state_up']])
        new_ports = [p for p in internal_ports if
                     p['id'] in current_port_ids and
                     p['id'] not in existing_port_ids]
        old_ports = [p for p in ri.internal_ports if
                     p['id'] not in current_port_ids]
        new_ipv6_port = False
        old_ipv6_port = False
        for p in new_ports:
            self._set_subnet_info(p)
            self.internal_network_added(ri, p)
            ri.internal_ports.append(p)
            if (not new_ipv6_port and
                    netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
                new_ipv6_port = True

        for p in old_ports:
            self.internal_network_removed(ri, p)
            ri.internal_ports.remove(p)
            if (not old_ipv6_port and
                    netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
                old_ipv6_port = True

        if new_ipv6_port or old_ipv6_port:
            # refresh ndpd daemon after filling in ndpd.conf
            # with the right entries
            ra.enable_ipv6_ra(ri.router_id,
                              internal_ports,
                              self.get_internal_device_name)

        # remove any internal stale router interfaces (i.e., l3i.. VNICs)
        existing_devices = net_lib.Datalink.show_vnic()
        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(id) for
                                 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)

        # TODO(salv-orlando): RouterInfo would be a better place for
        # this logic too
        ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
                         ri.ex_gw_port and ri.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

            self._set_subnet_info(ex_gw_port)
            if not ri.ex_gw_port:
                self.external_gateway_added(ri, ex_gw_port, interface_name)
            elif not _gateway_ports_equal(ex_gw_port, ri.ex_gw_port):
                self.external_gateway_updated(ri, ex_gw_port, interface_name)
        elif not ex_gw_port and ri.ex_gw_port:
            self.external_gateway_removed(ri, ri.ex_gw_port, interface_name)

        # Remove any external stale router interfaces (i.e., l3e.. VNICs)
        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 static routes for router
        self.routes_updated(ri)

        # Process SNAT rules for external gateway
        if (not ri.router['distributed'] or
                ex_gw_port and self.get_gw_port_host(ri.router) == self.host):
            # Get IPv4 only internal CIDRs
            internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports
                              if netaddr.IPNetwork(p['ip_cidr']).version == 4]
            ri.perform_snat_action(self._handle_router_snat_rules,
                                   internal_cidrs, interface_name)

        # Process SNAT/DNAT rules for floating IPs
        fip_statuses = {}
        if ex_gw_port:
            existing_floating_ips = ri.floating_ips
            fip_statuses = self.process_router_floating_ips(ri, ex_gw_port)
            # Identify floating IPs which were disabled
            ri.floating_ips = set(fip_statuses.keys())
            for fip_id in existing_floating_ips - ri.floating_ips:
                fip_statuses[fip_id] = l3_constants.FLOATINGIP_STATUS_DOWN
            # Update floating IP status on the neutron server
            self.plugin_rpc.update_floatingip_statuses(
                self.context, ri.router_id, fip_statuses)

        # Update ex_gw_port and enable_snat on the router info cache
        ri.ex_gw_port = ex_gw_port
        ri.enable_snat = ri.router.get('enable_snat')

    def process_router_floating_ips(self, ri, ex_gw_port):
        """Configure the router's floating IPs
        Configures floating ips using ipnat(1m) on the router's gateway device.

        Cleans up floating ips that should not longer be configured.
        """
        ifname = self.get_external_device_name(ex_gw_port['id'])
        ipintf = net_lib.IPInterface(ifname)
        ipaddr_list = ipintf.ipaddr_list()['static']

        existing_cidrs = set(ipaddr_list)
        new_cidrs = set()

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

        # Loop once to ensure that floating ips are configured.
        fip_statuses = {}
        for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
            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' % (ifname, fixed_cidr, fip_cidr)

            if fip_cidr not in existing_cidrs:
                try:
                    ipintf.create_address(fip_cidr)
                    ri.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)
            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]
        ri.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

    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 external_gateway_added(self, ri, 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.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.conf.external_network_datalink,
                               mac_address=mac_address, vid=vid)
            else:
                # This is to handle HA by Solaris Cluster and is similar to
                # the code we already have for the DHCP Agent. So, when
                # the 1st L3 agent is down and the second L3 agent tries to
                # connect its VNIC to EVS, we will end up in "vport in use"
                # error. So, we need to reset the vport before we connect
                # the VNIC to EVS.
                cmd = ['/usr/sbin/evsadm', 'show-vport', '-f',
                       'vport=%s' % ex_gw_port['id'], '-co',
                       'evs,vport,status']
                stdout = utils.execute(cmd)
                evsname, vportname, status = stdout.strip().split(':')
                tenant_id = ex_gw_port['tenant_id']
                if status == 'used':
                    cmd = ['/usr/sbin/evsadm', 'reset-vport', '-T', tenant_id,
                           '%s/%s' % (evsname, vportname)]
                    utils.execute(cmd)

                # next remove protection setting on the VPort to allow
                # multiple floating IPs to be configured on the l3e*
                # interface
                evsvport = "%s/%s" % (ex_gw_port['network_id'],
                                      ex_gw_port['id'])
                cmd = ['/usr/sbin/evsadm', 'set-vportprop', '-T',
                       tenant_id, '-p', 'protection=none', evsvport]
                utils.execute(cmd)
                dl.connect_vnic(evsvport, tenant_id)

        self.driver.init_l3(external_dlname, [ex_gw_port['ip_cidr']])

        # TODO(gmoodalb): wrap route(1m) command within a class in net_lib.py
        gw_ip = ex_gw_port['subnet']['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])
            ri.remove_route = True
            if 'entry exists' in stdout:
                ri.remove_route = False

            # for each of the internal ports, add Policy Based
            # Routing (PBR) rule
            for port in ri.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['subnet']['cidr'])]
                ipversion = netaddr.IPNetwork(port['subnet']['cidr']).version
                ri.ipfilters_manager.add_ipf_rules(rules, ipversion)

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

    def external_gateway_removed(self, ri, ex_gw_port, external_dlname):
        gw_ip = ex_gw_port['subnet']['gateway_ip']
        if gw_ip:
            # remove PBR rules
            for port in ri.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['subnet']['cidr'])]
                ipversion = netaddr.IPNetwork(port['subnet']['cidr']).version
                ri.ipfilters_manager.remove_ipf_rules(rules, ipversion)

            if ri.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)

        # remove the EVS VPort associated with external network
        cmd = ['/usr/sbin/evsadm', 'remove-vport',
               '-T', ex_gw_port['tenant_id'],
               '%s/%s' % (ex_gw_port['network_id'], ex_gw_port['id'])]
        try:
            utils.execute(cmd)
        except Exception as err:
            LOG.error(_("Failed to delete the EVS VPort associated with "
                        "external network: %s") % err)

    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 internal_network_added(self, ri, 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)
        self.driver.init_l3(internal_dlname, [port['ip_cidr']])

        # 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'])
        ri.ipfilters_manager.add_ippool(block_pname, None)
        if self.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')
            ri.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['subnet']['cidr']
        block_subnets = []
        allow_subnets = []
        for internal_port in ri.internal_ports:
            if internal_port['mac_address'] == port['mac_address']:
                continue
            if (self.conf.allow_forwarding_between_networks and
                    internal_port['tenant_id'] == port['tenant_id']):
                allow_subnets.append(internal_port['subnet']['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')
                ri.ipfilters_manager.add_ippool(iport_allow_pname,
                                                [port_subnet])
            else:
                block_subnets.append(internal_port['subnet']['cidr'])
                iport_block_pname = \
                    self._get_ippool_name(internal_port['mac_address'])
                ri.ipfilters_manager.add_ippool(iport_block_pname,
                                                [port_subnet])
        # update the new port's pool with other ports' subnet
        ri.ipfilters_manager.add_ippool(block_pname, block_subnets)
        if self.conf.allow_forwarding_between_networks:
            ri.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.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 = ri.ex_gw_port
        ex_gw_ip = (ex_gw_port['subnet']['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
        ri.ipfilters_manager.add_ipf_rules(rules, ipversion)

        # if metadata proxy is enabled, then add the necessary
        # IP NAT rules to forward the metadata requests to the
        # metadata proxy server
        if self.conf.enable_metadata_proxy and ipversion == 4:
            # TODO(gmoodalb): when IP Filter allows redirection of packets
            # to loopback IP address, then we need to add an IPF rule allowing
            # only packets destined to 127.0.0.1:9697 to
            # neutron-ns-metadata-proxy server
            rules = ['rdr %s 169.254.169.254/32 port 80 -> %s port %d tcp' %
                     (internal_dlname, port['fixed_ips'][0]['ip_address'],
                      self.conf.metadata_port)]
            ri.ipfilters_manager.add_nat_rules(rules)

    def internal_network_removed(self, ri, port):
        internal_dlname = self.get_internal_device_name(port['id'])
        port_subnet = port['subnet']['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.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 = ri.ex_gw_port
        ex_gw_ip = (ex_gw_port['subnet']['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']['cidr']).version
        ri.ipfilters_manager.remove_ipf_rules(rules, ipversion)

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

        for internal_port in ri.internal_ports:
            if (self.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')
                ri.ipfilters_manager.remove_ippool(iport_allow_pname,
                                                   [port_subnet])
            else:
                iport_block_pname = \
                    self._get_ippool_name(internal_port['mac_address'])
                ri.ipfilters_manager.remove_ippool(iport_block_pname,
                                                   [port_subnet])

        # if metadata proxy is enabled, then remove the IP NAT rules that
        # were added while adding the internal network
        if self.conf.enable_metadata_proxy and ipversion == 4:
            rules = ['rdr %s 169.254.169.254/32 port 80 -> %s port %d tcp' %
                     (internal_dlname, port['fixed_ips'][0]['ip_address'],
                      self.conf.metadata_port)]
            ri.ipfilters_manager.remove_nat_rules(rules)

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

        # remove the EVS VPort associated with internal network
        cmd = ['/usr/sbin/evsadm', 'remove-vport', '-T', port['tenant_id'],
               '%s/%s' % (port['network_id'], port['id'])]
        try:
            utils.execute(cmd)
        except Exception as err:
            LOG.error(_("Failed to delete the EVS VPort associated with "
                        "internal network: %s") % err)

    def routes_updated(self, ri):
        pass