# Copyright (c) 2015 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. from neutron_lib import constants as lib_constants from oslo_log import log as logging from neutron.agent.l3 import dvr_local_router from neutron.agent.l3 import dvr_snat_ns from neutron.agent.l3 import router_info as router from neutron.agent.linux import ip_lib from neutron.agent.linux import iptables_manager from neutron.common import utils as common_utils LOG = logging.getLogger(__name__) class DvrEdgeRouter(dvr_local_router.DvrLocalRouter): def __init__(self, host, *args, **kwargs): super(DvrEdgeRouter, self).__init__(host, *args, **kwargs) self.snat_namespace = dvr_snat_ns.SnatNamespace( self.router_id, self.agent_conf, self.driver, self.use_ipv6) self.snat_iptables_manager = None def get_gw_ns_name(self): return self.snat_namespace.name def external_gateway_added(self, ex_gw_port, interface_name): super(DvrEdgeRouter, self).external_gateway_added( ex_gw_port, interface_name) if self._is_this_snat_host(): self._create_dvr_gateway(ex_gw_port, interface_name) # NOTE: When a router is created without a gateway the routes get # added to the router namespace, but if we wanted to populate # the same routes to the snat namespace after the gateway port # is added, we need to call routes_updated here. self.routes_updated([], self.router['routes']) elif self.snat_namespace.exists(): # This is the case where the snat was moved manually or # rescheduled to a different agent when the agent was dead. LOG.debug("SNAT was moved or rescheduled to a different host " "and does not match with the current host. This is " "a stale namespace %s and will be cleared from the " "current dvr_snat host.", self.snat_namespace.name) self.external_gateway_removed(ex_gw_port, interface_name) def _list_centralized_floating_ip_cidrs(self): # Compute a list of addresses this gw 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 if ip.get(lib_constants.DVR_SNAT_BOUND)] def external_gateway_updated(self, ex_gw_port, interface_name): if not self._is_this_snat_host(): # no centralized SNAT gateway for this node/agent LOG.debug("not hosting snat for router: %s", self.router['id']) if self.snat_namespace.exists(): LOG.debug("SNAT was rescheduled to host %s. Clearing snat " "namespace.", self.router.get('gw_port_host')) return self.external_gateway_removed( ex_gw_port, interface_name) return if not self.snat_namespace.exists(): # SNAT might be rescheduled to this agent; need to process like # newly created gateway return self.external_gateway_added(ex_gw_port, interface_name) else: preserve_ips = self._list_centralized_floating_ip_cidrs() self._external_gateway_added(ex_gw_port, interface_name, self.snat_namespace.name, preserve_ips) def _external_gateway_removed(self, ex_gw_port, interface_name): super(DvrEdgeRouter, self).external_gateway_removed(ex_gw_port, interface_name) if not self._is_this_snat_host() and not self.snat_namespace.exists(): # no centralized SNAT gateway for this node/agent LOG.debug("not hosting snat for router: %s", self.router['id']) return self.driver.unplug(interface_name, namespace=self.snat_namespace.name, prefix=router.EXTERNAL_DEV_PREFIX) def external_gateway_removed(self, ex_gw_port, interface_name): self._external_gateway_removed(ex_gw_port, interface_name) if self.snat_namespace.exists(): self.snat_namespace.delete() def internal_network_added(self, port): super(DvrEdgeRouter, self).internal_network_added(port) # TODO(gsagie) some of this checks are already implemented # in the base class, think how to avoid re-doing them if not self._is_this_snat_host(): return sn_port = self.get_snat_port_for_internal_port(port) if not sn_port: return ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(self.router['id']) interface_name = self._get_snat_int_device_name(sn_port['id']) self._internal_network_added( ns_name, sn_port['network_id'], sn_port['id'], sn_port['fixed_ips'], sn_port['mac_address'], interface_name, lib_constants.SNAT_INT_DEV_PREFIX, mtu=sn_port.get('mtu')) def _dvr_internal_network_removed(self, port): super(DvrEdgeRouter, self)._dvr_internal_network_removed(port) if not self.ex_gw_port: return sn_port = self.get_snat_port_for_internal_port(port, self.snat_ports) if not sn_port: return if not self._is_this_snat_host(): return snat_interface = self._get_snat_int_device_name(sn_port['id']) ns_name = self.snat_namespace.name prefix = lib_constants.SNAT_INT_DEV_PREFIX if ip_lib.device_exists(snat_interface, namespace=ns_name): self.driver.unplug(snat_interface, namespace=ns_name, prefix=prefix) def _plug_snat_port(self, port): interface_name = self._get_snat_int_device_name(port['id']) self._internal_network_added( self.snat_namespace.name, port['network_id'], port['id'], port['fixed_ips'], port['mac_address'], interface_name, lib_constants.SNAT_INT_DEV_PREFIX, mtu=port.get('mtu')) def _create_dvr_gateway(self, ex_gw_port, gw_interface_name): snat_ns = self._create_snat_namespace() # connect snat_ports to br_int from SNAT namespace for port in self.get_snat_interfaces(): self._plug_snat_port(port) self._external_gateway_added(ex_gw_port, gw_interface_name, snat_ns.name, preserve_ips=[]) self.snat_iptables_manager = iptables_manager.IptablesManager( namespace=snat_ns.name, use_ipv6=self.use_ipv6) self._initialize_address_scope_iptables(self.snat_iptables_manager) def _create_snat_namespace(self): """Create SNAT namespace.""" # TODO(mlavalle): in the near future, this method should contain the # code in the L3 agent that creates a gateway for a dvr. The first step # is to move the creation of the snat namespace here self.snat_namespace.create() return self.snat_namespace def _get_snat_int_device_name(self, port_id): long_name = lib_constants.SNAT_INT_DEV_PREFIX + port_id return long_name[:self.driver.DEV_NAME_LEN] def _is_this_snat_host(self): host = self.router.get('gw_port_host') if not host: LOG.debug("gw_port_host missing from router: %s", self.router['id']) return host == self.host def _handle_router_snat_rules(self, ex_gw_port, interface_name): super(DvrEdgeRouter, self)._handle_router_snat_rules( ex_gw_port, interface_name) if not self._is_this_snat_host(): return if not self.get_ex_gw_port(): return if not self.snat_iptables_manager: LOG.debug("DVR router: no snat rules to be handled") return with self.snat_iptables_manager.defer_apply(): self._empty_snat_chains(self.snat_iptables_manager) # NOTE: float-snat should be added for the # centralized floating-ips supported by the # snat namespace. self.snat_iptables_manager.ipv4['nat'].add_rule( 'snat', '-j $float-snat') self._add_snat_rules(ex_gw_port, self.snat_iptables_manager, interface_name) def update_routing_table(self, operation, route): if self.get_ex_gw_port() and self._is_this_snat_host(): ns_name = self.snat_namespace.name # NOTE: For now let us apply the static routes both in SNAT # namespace and Router Namespace, to reduce the complexity. if self.snat_namespace.exists(): super(DvrEdgeRouter, self)._update_routing_table( operation, route, namespace=ns_name) else: LOG.error("The SNAT namespace %s does not exist for " "the router.", ns_name) super(DvrEdgeRouter, self).update_routing_table(operation, route) def delete(self): super(DvrEdgeRouter, self).delete() if self.snat_namespace.exists(): self.snat_namespace.delete() def process_address_scope(self): super(DvrEdgeRouter, self).process_address_scope() if not self._is_this_snat_host(): return if not self.snat_iptables_manager: LOG.debug("DVR router: no snat rules to be handled") return # Prepare address scope iptables rule for dvr snat interfaces internal_ports = self.get_snat_interfaces() ports_scopemark = self._get_port_devicename_scopemark( internal_ports, self._get_snat_int_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 (lib_constants.IP_VERSION_4, lib_constants.IP_VERSION_6): ports_scopemark[ip_version].update( external_port_scopemark[ip_version]) with self.snat_iptables_manager.defer_apply(): self._add_address_scope_mark( self.snat_iptables_manager, ports_scopemark) def _delete_stale_external_devices(self, interface_name): if not self.snat_namespace.exists(): return ns_ip = ip_lib.IPWrapper(namespace=self.snat_namespace.name) for d in ns_ip.get_devices(): if (d.name.startswith(router.EXTERNAL_DEV_PREFIX) and d.name != interface_name): LOG.debug('Deleting stale external router device: %s', d.name) self.driver.unplug( d.name, namespace=self.snat_namespace.name, prefix=router.EXTERNAL_DEV_PREFIX) def get_snat_external_device_interface_name(self, ex_gw_port): long_name = router.EXTERNAL_DEV_PREFIX + ex_gw_port['id'] return long_name[:self.driver.DEV_NAME_LEN] def get_centralized_fip_cidr_set(self): """Returns the fip_cidr set for centralized floatingips.""" ex_gw_port = self.get_ex_gw_port() # Don't look for centralized FIP cidrs if gw_port not exists or # this is not snat host if (not ex_gw_port or not self._is_this_snat_host() or not self.snat_namespace.exists()): return set() interface_name = self.get_snat_external_device_interface_name( ex_gw_port) return set([addr['cidr'] for addr in ip_lib.get_devices_with_ip( self.snat_namespace.name, name=interface_name)]) def get_router_cidrs(self, device): """Over-ride the get_router_cidrs function to return the list. This function is overridden to provide the complete list of floating_ip cidrs that the router hosts. This includes the centralized floatingip cidr list and the regular floatingip cidr list that are bound to fip namespace. """ fip_cidrs = super(DvrEdgeRouter, self).get_router_cidrs(device) centralized_cidrs = self.get_centralized_fip_cidr_set() return fip_cidrs | centralized_cidrs def remove_centralized_floatingip(self, fip_cidr): """Function to handle the centralized Floatingip remove.""" if not self.get_ex_gw_port(): return if not self._is_this_snat_host(): return interface_name = self.get_snat_external_device_interface_name( self.get_ex_gw_port()) device = ip_lib.IPDevice( interface_name, namespace=self.snat_namespace.name) device.delete_addr_and_conntrack_state(fip_cidr) self.process_floating_ip_nat_rules_for_centralized_floatingip() def add_centralized_floatingip(self, fip, fip_cidr): """Function to handle the centralized Floatingip addition.""" if not self.get_ex_gw_port(): return if not self._is_this_snat_host(): return interface_name = self.get_snat_external_device_interface_name( self.get_ex_gw_port()) try: ip_lib.add_ip_address(fip_cidr, interface_name, namespace=self.snat_namespace.name) except ip_lib.IpAddressAlreadyExists: pass except RuntimeError: LOG.warning("Unable to configure IP address for centralized " "floating IP: %s", fip['id']) return lib_constants.FLOATINGIP_STATUS_ERROR self.process_floating_ip_nat_rules_for_centralized_floatingip() # Send a GARP message on the external interface for the # centralized floatingip configured. ip_lib.send_ip_addr_adv_notif(self.snat_namespace.name, interface_name, fip['floating_ip_address']) return lib_constants.FLOATINGIP_STATUS_ACTIVE def _centralized_floating_forward_rules(self, floating_ip, fixed_ip): to_source = '-s %s/32 -j SNAT --to-source %s' % (fixed_ip, floating_ip) if self.snat_iptables_manager.random_fully: to_source += ' --random-fully' 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', to_source)] def _set_floating_ip_nat_rules_for_centralized_floatingip(self, fip): if fip.get(lib_constants.DVR_SNAT_BOUND): fixed = fip['fixed_ip_address'] fip_ip = fip['floating_ip_address'] for chain, rule in self._centralized_floating_forward_rules( fip_ip, fixed): self.snat_iptables_manager.ipv4['nat'].add_rule( chain, rule, tag='floating_ip') def process_floating_ip_nat_rules_for_centralized_floatingip(self): self.snat_iptables_manager.ipv4['nat'].clear_rules_by_tag( 'floating_ip') floating_ips = self.get_floating_ips() for fip in floating_ips: self._set_floating_ip_nat_rules_for_centralized_floatingip(fip) self.snat_iptables_manager.apply() def process_floating_ip_nat_rules(self): if self._is_this_snat_host(): self.process_floating_ip_nat_rules_for_centralized_floatingip() # Cover mixed dvr_snat and compute node, aka a dvr_snat node has both # centralized and distributed floating IPs. super(DvrEdgeRouter, self).process_floating_ip_nat_rules()