"Fossies" - the Fresh Open Source Software Archive

Member "neutron-14.0.3/neutron/agent/linux/ip_lib.py" (22 Oct 2019, 54979 Bytes) of package /linux/misc/openstack/neutron-14.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ip_lib.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.0.2_vs_14.0.3.

    1 # Copyright 2012 OpenStack Foundation
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 import errno
   17 import re
   18 import time
   19 
   20 import eventlet
   21 import netaddr
   22 from neutron_lib import constants
   23 from neutron_lib import exceptions
   24 from oslo_config import cfg
   25 from oslo_log import log as logging
   26 from oslo_utils import excutils
   27 from pyroute2.netlink import rtnl
   28 from pyroute2.netlink.rtnl import ifaddrmsg
   29 from pyroute2.netlink.rtnl import ifinfmsg
   30 from pyroute2 import NetlinkError
   31 from pyroute2 import netns
   32 
   33 from neutron._i18n import _
   34 from neutron.agent.common import utils
   35 from neutron.common import ipv6_utils
   36 from neutron.common import utils as common_utils
   37 from neutron.privileged.agent.linux import ip_lib as privileged
   38 
   39 LOG = logging.getLogger(__name__)
   40 
   41 
   42 IP_NONLOCAL_BIND = 'net.ipv4.ip_nonlocal_bind'
   43 
   44 LOOPBACK_DEVNAME = 'lo'
   45 FB_TUNNEL_DEVICE_NAMES = ['gre0', 'gretap0', 'tunl0', 'erspan0', 'sit0',
   46                           'ip6tnl0', 'ip6gre0']
   47 IP_RULE_TABLES = {'default': 253,
   48                   'main': 254,
   49                   'local': 255}
   50 
   51 # Rule indexes: pyroute2.netlink.rtnl
   52 # Rule names: https://www.systutorials.com/docs/linux/man/8-ip-rule/
   53 # NOTE(ralonsoh): 'masquerade' type is printed as 'nat' in 'ip rule' command
   54 IP_RULE_TYPES = {0: 'unspecified',
   55                  1: 'unicast',
   56                  6: 'blackhole',
   57                  7: 'unreachable',
   58                  8: 'prohibit',
   59                  10: 'nat'}
   60 
   61 IP_ADDRESS_SCOPE = {rtnl.rtscopes['RT_SCOPE_UNIVERSE']: 'global',
   62                     rtnl.rtscopes['RT_SCOPE_SITE']: 'site',
   63                     rtnl.rtscopes['RT_SCOPE_LINK']: 'link',
   64                     rtnl.rtscopes['RT_SCOPE_HOST']: 'host'}
   65 
   66 IP_ADDRESS_SCOPE_NAME = {v: k for k, v in IP_ADDRESS_SCOPE.items()}
   67 
   68 SYS_NET_PATH = '/sys/class/net'
   69 DEFAULT_GW_PATTERN = re.compile(r"via (\S+)")
   70 METRIC_PATTERN = re.compile(r"metric (\S+)")
   71 DEVICE_NAME_PATTERN = re.compile(r"(\d+?): (\S+?):.*")
   72 
   73 
   74 def remove_interface_suffix(interface):
   75     """Remove a possible "<if>@<endpoint>" suffix from an interface' name.
   76 
   77     This suffix can appear in some kernel versions, and intends on specifying,
   78     for example, a veth's pair. However, this interface name is useless to us
   79     as further 'ip' commands require that the suffix be removed.
   80     """
   81     # If '@' is not present, this will do nothing.
   82     return interface.partition("@")[0]
   83 
   84 
   85 class AddressNotReady(exceptions.NeutronException):
   86     message = _("Failure waiting for address %(address)s to "
   87                 "become ready: %(reason)s")
   88 
   89 
   90 class InvalidArgument(exceptions.NeutronException):
   91     message = _("Invalid value %(value)s for parameter %(parameter)s "
   92                 "provided.")
   93 
   94 
   95 class SubProcessBase(object):
   96     def __init__(self, namespace=None,
   97                  log_fail_as_error=True):
   98         self.namespace = namespace
   99         self.log_fail_as_error = log_fail_as_error
  100         try:
  101             self.force_root = cfg.CONF.ip_lib_force_root
  102         except cfg.NoSuchOptError:
  103             # Only callers that need to force use of the root helper
  104             # need to register the option.
  105             self.force_root = False
  106 
  107     def _run(self, options, command, args):
  108         if self.namespace:
  109             return self._as_root(options, command, args)
  110         elif self.force_root:
  111             # Force use of the root helper to ensure that commands
  112             # will execute in dom0 when running under XenServer/XCP.
  113             return self._execute(options, command, args, run_as_root=True)
  114         else:
  115             return self._execute(options, command, args)
  116 
  117     def _as_root(self, options, command, args, use_root_namespace=False):
  118         namespace = self.namespace if not use_root_namespace else None
  119 
  120         return self._execute(options, command, args, run_as_root=True,
  121                              namespace=namespace)
  122 
  123     def _execute(self, options, command, args, run_as_root=False,
  124                  namespace=None):
  125         opt_list = ['-%s' % o for o in options]
  126         ip_cmd = add_namespace_to_cmd(['ip'], namespace)
  127         cmd = ip_cmd + opt_list + [command] + list(args)
  128         return utils.execute(cmd, run_as_root=run_as_root,
  129                              log_fail_as_error=self.log_fail_as_error)
  130 
  131     def set_log_fail_as_error(self, fail_with_error):
  132         self.log_fail_as_error = fail_with_error
  133 
  134     def get_log_fail_as_error(self):
  135         return self.log_fail_as_error
  136 
  137 
  138 class IPWrapper(SubProcessBase):
  139     def __init__(self, namespace=None):
  140         super(IPWrapper, self).__init__(namespace=namespace)
  141         self.netns = IpNetnsCommand(self)
  142 
  143     def device(self, name):
  144         return IPDevice(name, namespace=self.namespace)
  145 
  146     def get_devices_info(self, exclude_loopback=True,
  147                          exclude_fb_tun_devices=True):
  148         devices = get_devices_info(self.namespace)
  149 
  150         retval = []
  151         for device in devices:
  152             if (exclude_loopback and device['name'] == LOOPBACK_DEVNAME or
  153                     exclude_fb_tun_devices and
  154                     device['name'] in FB_TUNNEL_DEVICE_NAMES):
  155                 continue
  156             retval.append(device)
  157         return retval
  158 
  159     def get_devices(self, exclude_loopback=True, exclude_fb_tun_devices=True):
  160         retval = []
  161         try:
  162             devices = privileged.get_device_names(self.namespace)
  163         except privileged.NetworkNamespaceNotFound:
  164             return retval
  165 
  166         for name in devices:
  167             if (exclude_loopback and name == LOOPBACK_DEVNAME or
  168                     exclude_fb_tun_devices and name in FB_TUNNEL_DEVICE_NAMES):
  169                 continue
  170             retval.append(IPDevice(name, namespace=self.namespace))
  171         return retval
  172 
  173     def get_device_by_ip(self, ip):
  174         """Get the IPDevice from system which has ip configured.
  175 
  176         @param ip: look for the device holding this ip. If this is None,
  177                    None is returned.
  178         @type ip: str.
  179         """
  180         if not ip:
  181             return None
  182 
  183         cidr = common_utils.ip_to_cidr(ip)
  184         kwargs = {'address': common_utils.cidr_to_ip(cidr)}
  185         if not common_utils.is_cidr_host(cidr):
  186             kwargs['mask'] = common_utils.cidr_mask_length(cidr)
  187         devices = get_devices_with_ip(self.namespace, **kwargs)
  188         if not devices:
  189             # Search by broadcast address.
  190             broadcast = common_utils.cidr_broadcast_address(cidr)
  191             if broadcast:
  192                 devices = get_devices_with_ip(self.namespace,
  193                                               broadcast=broadcast)
  194 
  195         if devices:
  196             return IPDevice(devices[0]['name'], namespace=self.namespace)
  197 
  198     def add_tuntap(self, name, mode='tap'):
  199         privileged.create_interface(
  200             name, self.namespace, "tuntap", mode=mode)
  201         return IPDevice(name, namespace=self.namespace)
  202 
  203     def add_veth(self, name1, name2, namespace2=None):
  204         peer = {'ifname': name2}
  205 
  206         if namespace2 is None:
  207             namespace2 = self.namespace
  208         else:
  209             self.ensure_namespace(namespace2)
  210             peer['net_ns_fd'] = namespace2
  211 
  212         privileged.create_interface(
  213             name1, self.namespace, 'veth', peer=peer)
  214 
  215         return (IPDevice(name1, namespace=self.namespace),
  216                 IPDevice(name2, namespace=namespace2))
  217 
  218     def add_macvtap(self, name, src_dev, mode='bridge'):
  219         privileged.create_interface(name,
  220                                     self.namespace,
  221                                     "macvtap",
  222                                     physical_interface=src_dev,
  223                                     mode=mode)
  224         return IPDevice(name, namespace=self.namespace)
  225 
  226     def del_veth(self, name):
  227         """Delete a virtual interface between two namespaces."""
  228         privileged.delete_interface(name, self.namespace)
  229 
  230     def add_dummy(self, name):
  231         """Create a Linux dummy interface with the given name."""
  232         privileged.create_interface(name, self.namespace, "dummy")
  233         return IPDevice(name, namespace=self.namespace)
  234 
  235     def ensure_namespace(self, name):
  236         if not self.netns.exists(name):
  237             ip = self.netns.add(name)
  238             lo = ip.device(LOOPBACK_DEVNAME)
  239             lo.link.set_up()
  240         else:
  241             ip = IPWrapper(namespace=name)
  242         return ip
  243 
  244     def namespace_is_empty(self):
  245         return not self.get_devices()
  246 
  247     def garbage_collect_namespace(self):
  248         """Conditionally destroy the namespace if it is empty."""
  249         if self.namespace and self.netns.exists(self.namespace):
  250             if self.namespace_is_empty():
  251                 self.netns.delete(self.namespace)
  252                 return True
  253         return False
  254 
  255     def add_device_to_namespace(self, device):
  256         if self.namespace:
  257             device.link.set_netns(self.namespace)
  258 
  259     def add_vlan(self, name, physical_interface, vlan_id):
  260         privileged.create_interface(name,
  261                                     self.namespace,
  262                                     "vlan",
  263                                     physical_interface=physical_interface,
  264                                     vlan_id=vlan_id)
  265         return IPDevice(name, namespace=self.namespace)
  266 
  267     def add_vxlan(self, name, vni, group=None, dev=None, ttl=None, tos=None,
  268                   local=None, srcport=None, dstport=None, proxy=False):
  269         kwargs = {'vxlan_id': vni}
  270         if group:
  271             kwargs['vxlan_group'] = group
  272         if dev:
  273             kwargs['physical_interface'] = dev
  274         if ttl:
  275             kwargs['vxlan_ttl'] = ttl
  276         if tos:
  277             kwargs['vxlan_tos'] = tos
  278         if local:
  279             kwargs['vxlan_local'] = local
  280         if proxy:
  281             kwargs['vxlan_proxy'] = proxy
  282         # tuple: min,max
  283         if srcport:
  284             if len(srcport) == 2 and srcport[0] <= srcport[1]:
  285                 kwargs['vxlan_port_range'] = (str(srcport[0]), str(srcport[1]))
  286             else:
  287                 raise exceptions.NetworkVxlanPortRangeError(
  288                     vxlan_range=srcport)
  289         if dstport:
  290             kwargs['vxlan_port'] = dstport
  291         privileged.create_interface(name, self.namespace, "vxlan", **kwargs)
  292         return (IPDevice(name, namespace=self.namespace))
  293 
  294 
  295 class IPDevice(SubProcessBase):
  296     def __init__(self, name, namespace=None, kind='link'):
  297         super(IPDevice, self).__init__(namespace=namespace)
  298         self._name = name
  299         self.kind = kind
  300         self.link = IpLinkCommand(self)
  301         self.addr = IpAddrCommand(self)
  302         self.route = IpRouteCommand(self)
  303         self.neigh = IpNeighCommand(self)
  304 
  305     def __eq__(self, other):
  306         return (other is not None and self.name == other.name and
  307                 self.namespace == other.namespace)
  308 
  309     def __str__(self):
  310         return self.name
  311 
  312     def __repr__(self):
  313         return "<IPDevice(name=%s, namespace=%s)>" % (self._name,
  314                                                       self.namespace)
  315 
  316     def exists(self):
  317         """Return True if the device exists in the namespace."""
  318         return privileged.interface_exists(self.name, self.namespace)
  319 
  320     def delete_addr_and_conntrack_state(self, cidr):
  321         """Delete an address along with its conntrack state
  322 
  323         This terminates any active connections through an IP.
  324 
  325         :param cidr: the IP address for which state should be removed.
  326             This can be passed as a string with or without /NN.
  327             A netaddr.IPAddress or netaddr.Network representing the IP address
  328             can also be passed.
  329         """
  330         self.addr.delete(cidr)
  331         self.delete_conntrack_state(cidr)
  332 
  333     def delete_conntrack_state(self, cidr):
  334         """Delete conntrack state rules
  335 
  336         Deletes both rules (if existing), the destination and the reply one.
  337         """
  338         ip_str = str(netaddr.IPNetwork(cidr).ip)
  339         ip_wrapper = IPWrapper(namespace=self.namespace)
  340 
  341         # Delete conntrack state for ingress traffic
  342         # If 0 flow entries have been deleted
  343         # conntrack -D will return 1
  344         try:
  345             ip_wrapper.netns.execute(["conntrack", "-D", "-d", ip_str],
  346                                      check_exit_code=True,
  347                                      extra_ok_codes=[1])
  348 
  349         except RuntimeError:
  350             LOG.exception("Failed deleting ingress connection state of"
  351                           " floatingip %s", ip_str)
  352 
  353         # Delete conntrack state for egress traffic
  354         try:
  355             ip_wrapper.netns.execute(["conntrack", "-D", "-q", ip_str],
  356                                      check_exit_code=True,
  357                                      extra_ok_codes=[1])
  358         except RuntimeError:
  359             LOG.exception("Failed deleting egress connection state of"
  360                           " floatingip %s", ip_str)
  361 
  362     def delete_socket_conntrack_state(self, cidr, dport, protocol):
  363         ip_str = str(netaddr.IPNetwork(cidr).ip)
  364         ip_wrapper = IPWrapper(namespace=self.namespace)
  365         cmd = ["conntrack", "-D", "-d", ip_str, '-p', protocol,
  366                '--dport', dport]
  367         try:
  368             ip_wrapper.netns.execute(cmd, check_exit_code=True,
  369                                      extra_ok_codes=[1])
  370 
  371         except RuntimeError:
  372             LOG.exception("Failed deleting ingress connection state of "
  373                           "socket %(ip)s:%(port)s", {'ip': ip_str,
  374                                                      'port': dport})
  375 
  376     def disable_ipv6(self):
  377         if not ipv6_utils.is_enabled_and_bind_by_default():
  378             return
  379         sysctl_name = re.sub(r'\.', '/', self.name)
  380         cmd = ['net.ipv6.conf.%s.disable_ipv6=1' % sysctl_name]
  381         return sysctl(cmd, namespace=self.namespace)
  382 
  383     @property
  384     def name(self):
  385         if self._name:
  386             return self._name[:constants.DEVICE_NAME_MAX_LEN]
  387         return self._name
  388 
  389     @name.setter
  390     def name(self, name):
  391         self._name = name
  392 
  393 
  394 class IpCommandBase(object):
  395     COMMAND = ''
  396 
  397     def __init__(self, parent):
  398         self._parent = parent
  399 
  400     def _run(self, options, args):
  401         return self._parent._run(options, self.COMMAND, args)
  402 
  403     def _as_root(self, options, args, use_root_namespace=False):
  404         return self._parent._as_root(options,
  405                                      self.COMMAND,
  406                                      args,
  407                                      use_root_namespace=use_root_namespace)
  408 
  409 
  410 class IpDeviceCommandBase(IpCommandBase):
  411     @property
  412     def name(self):
  413         return self._parent.name
  414 
  415     @property
  416     def kind(self):
  417         return self._parent.kind
  418 
  419 
  420 class IpLinkCommand(IpDeviceCommandBase):
  421     COMMAND = 'link'
  422 
  423     def set_address(self, mac_address):
  424         privileged.set_link_attribute(
  425             self.name, self._parent.namespace, address=mac_address)
  426 
  427     def set_allmulticast_on(self):
  428         privileged.set_link_flags(
  429             self.name, self._parent.namespace, ifinfmsg.IFF_ALLMULTI)
  430 
  431     def set_mtu(self, mtu_size):
  432         try:
  433             privileged.set_link_attribute(
  434                 self.name, self._parent.namespace, mtu=mtu_size)
  435         except NetlinkError as e:
  436             if e.code == errno.EINVAL:
  437                 raise InvalidArgument(parameter="MTU", value=mtu_size)
  438             raise
  439 
  440     def set_up(self):
  441         privileged.set_link_attribute(
  442             self.name, self._parent.namespace, state='up')
  443 
  444     def set_down(self):
  445         privileged.set_link_attribute(
  446             self.name, self._parent.namespace, state='down')
  447 
  448     def set_netns(self, namespace):
  449         privileged.set_link_attribute(
  450             self.name, self._parent.namespace, net_ns_fd=namespace)
  451         self._parent.namespace = namespace
  452 
  453     def set_name(self, name):
  454         privileged.set_link_attribute(
  455             self.name, self._parent.namespace, ifname=name)
  456         self._parent.name = name
  457 
  458     def set_alias(self, alias_name):
  459         privileged.set_link_attribute(
  460             self.name, self._parent.namespace, ifalias=alias_name)
  461 
  462     def create(self):
  463         privileged.create_interface(self.name, self._parent.namespace,
  464                                     self.kind)
  465 
  466     def delete(self):
  467         privileged.delete_interface(self.name, self._parent.namespace)
  468 
  469     @property
  470     def address(self):
  471         return self.attributes.get('link/ether')
  472 
  473     @property
  474     def state(self):
  475         return self.attributes.get('state')
  476 
  477     @property
  478     def allmulticast(self):
  479         return self.attributes.get('allmulticast')
  480 
  481     @property
  482     def mtu(self):
  483         return self.attributes.get('mtu')
  484 
  485     @property
  486     def qdisc(self):
  487         return self.attributes.get('qdisc')
  488 
  489     @property
  490     def qlen(self):
  491         return self.attributes.get('qlen')
  492 
  493     @property
  494     def alias(self):
  495         return self.attributes.get('alias')
  496 
  497     @property
  498     def attributes(self):
  499         return privileged.get_link_attributes(self.name,
  500                                               self._parent.namespace)
  501 
  502 
  503 class IpAddrCommand(IpDeviceCommandBase):
  504     COMMAND = 'addr'
  505 
  506     def add(self, cidr, scope='global', add_broadcast=True):
  507         add_ip_address(cidr, self.name, self._parent.namespace, scope,
  508                        add_broadcast)
  509 
  510     def delete(self, cidr):
  511         delete_ip_address(cidr, self.name, self._parent.namespace)
  512 
  513     def flush(self, ip_version):
  514         flush_ip_addresses(ip_version, self.name, self._parent.namespace)
  515 
  516     def list(self, scope=None, to=None, filters=None, ip_version=None):
  517         """Get device details of a device named <self.name>."""
  518         def filter_device(device, filters):
  519             # Accepted filters: dynamic, permanent, tentative, dadfailed.
  520             for filter in filters:
  521                 if filter == 'permanent' and device['dynamic']:
  522                     return False
  523                 elif not device[filter]:
  524                     return False
  525             return True
  526 
  527         kwargs = {}
  528         if to:
  529             cidr = common_utils.ip_to_cidr(to)
  530             kwargs = {'address': common_utils.cidr_to_ip(cidr)}
  531             if not common_utils.is_cidr_host(cidr):
  532                 kwargs['mask'] = common_utils.cidr_mask_length(cidr)
  533         if scope:
  534             kwargs['scope'] = IP_ADDRESS_SCOPE_NAME[scope]
  535         if ip_version:
  536             kwargs['family'] = common_utils.get_socket_address_family(
  537                 ip_version)
  538 
  539         devices = get_devices_with_ip(self._parent.namespace, name=self.name,
  540                                       **kwargs)
  541         if not filters:
  542             return devices
  543 
  544         filtered_devices = []
  545         for device in (device for device in devices
  546                        if filter_device(device, filters)):
  547             filtered_devices.append(device)
  548 
  549         return filtered_devices
  550 
  551     def wait_until_address_ready(self, address, wait_time=30):
  552         """Wait until an address is no longer marked 'tentative'
  553 
  554         raises AddressNotReady if times out or address not present on interface
  555         """
  556         def is_address_ready():
  557             try:
  558                 addr_info = self.list(to=address)[0]
  559             except IndexError:
  560                 raise AddressNotReady(
  561                     address=address,
  562                     reason=_('Address not present on interface'))
  563             if not addr_info['tentative']:
  564                 return True
  565             if addr_info['dadfailed']:
  566                 raise AddressNotReady(
  567                     address=address, reason=_('Duplicate address detected'))
  568             return False
  569         errmsg = _("Exceeded %s second limit waiting for "
  570                    "address to leave the tentative state.") % wait_time
  571         common_utils.wait_until_true(
  572             is_address_ready, timeout=wait_time, sleep=0.20,
  573             exception=AddressNotReady(address=address, reason=errmsg))
  574 
  575 
  576 class IpRouteCommand(IpDeviceCommandBase):
  577     COMMAND = 'route'
  578 
  579     def __init__(self, parent, table=None):
  580         super(IpRouteCommand, self).__init__(parent)
  581         self._table = table
  582 
  583     def table(self, table):
  584         """Return an instance of IpRouteCommand which works on given table"""
  585         return IpRouteCommand(self._parent, table)
  586 
  587     def _table_args(self, override=None):
  588         if override:
  589             return ['table', override]
  590         return ['table', self._table] if self._table else []
  591 
  592     def _dev_args(self):
  593         return ['dev', self.name] if self.name else []
  594 
  595     def add_gateway(self, gateway, metric=None, table=None):
  596         ip_version = common_utils.get_ip_version(gateway)
  597         args = ['replace', 'default', 'via', gateway]
  598         if metric:
  599             args += ['metric', metric]
  600         args += self._dev_args()
  601         args += self._table_args(table)
  602         self._as_root([ip_version], tuple(args))
  603 
  604     def _run_as_root_detect_device_not_found(self, options, args):
  605         try:
  606             return self._as_root(options, tuple(args))
  607         except RuntimeError as rte:
  608             with excutils.save_and_reraise_exception() as ctx:
  609                 if "Cannot find device" in str(rte):
  610                     ctx.reraise = False
  611                     raise exceptions.DeviceNotFoundError(device_name=self.name)
  612 
  613     def delete_gateway(self, gateway, table=None):
  614         ip_version = common_utils.get_ip_version(gateway)
  615         args = ['del', 'default',
  616                 'via', gateway]
  617         args += self._dev_args()
  618         args += self._table_args(table)
  619         self._run_as_root_detect_device_not_found([ip_version], args)
  620 
  621     def _parse_routes(self, ip_version, output, **kwargs):
  622         for line in output.splitlines():
  623             parts = line.split()
  624 
  625             # Format of line is: "<cidr>|default [<key> <value>] ..."
  626             route = {k: v for k, v in zip(parts[1::2], parts[2::2])}
  627             route['cidr'] = parts[0]
  628             # Avoids having to explicitly pass around the IP version
  629             if route['cidr'] == 'default':
  630                 route['cidr'] = constants.IP_ANY[ip_version]
  631 
  632             # ip route drops things like scope and dev from the output if it
  633             # was specified as a filter.  This allows us to add them back.
  634             if self.name:
  635                 route['dev'] = self.name
  636             if self._table:
  637                 route['table'] = self._table
  638             # Callers add any filters they use as kwargs
  639             route.update(kwargs)
  640 
  641             yield route
  642 
  643     def list_routes(self, ip_version, **kwargs):
  644         args = ['list']
  645         args += self._dev_args()
  646         args += self._table_args()
  647         for k, v in kwargs.items():
  648             args += [k, v]
  649 
  650         output = self._run([ip_version], tuple(args))
  651         return [r for r in self._parse_routes(ip_version, output, **kwargs)]
  652 
  653     def list_onlink_routes(self, ip_version):
  654         routes = self.list_routes(ip_version, scope='link')
  655         return [r for r in routes if 'src' not in r]
  656 
  657     def add_onlink_route(self, cidr):
  658         self.add_route(cidr, scope='link')
  659 
  660     def delete_onlink_route(self, cidr):
  661         self.delete_route(cidr, scope='link')
  662 
  663     def get_gateway(self, scope=None, filters=None, ip_version=None):
  664         options = [ip_version] if ip_version else []
  665 
  666         args = ['list']
  667         args += self._dev_args()
  668         args += self._table_args()
  669         if filters:
  670             args += filters
  671 
  672         retval = None
  673 
  674         if scope:
  675             args += ['scope', scope]
  676 
  677         route_list_lines = self._run(options, tuple(args)).split('\n')
  678         default_route_line = next((x.strip() for x in
  679                                    route_list_lines if
  680                                    x.strip().startswith('default')), None)
  681         if default_route_line:
  682             retval = dict()
  683             gateway = DEFAULT_GW_PATTERN.search(default_route_line)
  684             if gateway:
  685                 retval.update(gateway=gateway.group(1))
  686             metric = METRIC_PATTERN.search(default_route_line)
  687             if metric:
  688                 retval.update(metric=int(metric.group(1)))
  689 
  690         return retval
  691 
  692     def flush(self, ip_version, table=None, **kwargs):
  693         args = ['flush']
  694         args += self._table_args(table)
  695         for k, v in kwargs.items():
  696             args += [k, v]
  697         self._as_root([ip_version], tuple(args))
  698 
  699     def add_route(self, cidr, via=None, table=None, **kwargs):
  700         ip_version = common_utils.get_ip_version(cidr)
  701         args = ['replace', cidr]
  702         if via:
  703             args += ['via', via]
  704         args += self._dev_args()
  705         args += self._table_args(table)
  706         for k, v in kwargs.items():
  707             args += [k, v]
  708         self._run_as_root_detect_device_not_found([ip_version], args)
  709 
  710     def delete_route(self, cidr, via=None, table=None, **kwargs):
  711         ip_version = common_utils.get_ip_version(cidr)
  712         args = ['del', cidr]
  713         if via:
  714             args += ['via', via]
  715         args += self._dev_args()
  716         args += self._table_args(table)
  717         for k, v in kwargs.items():
  718             args += [k, v]
  719         self._run_as_root_detect_device_not_found([ip_version], args)
  720 
  721 
  722 class IPRoute(SubProcessBase):
  723     def __init__(self, namespace=None, table=None):
  724         super(IPRoute, self).__init__(namespace=namespace)
  725         self.name = None
  726         self.route = IpRouteCommand(self, table=table)
  727 
  728 
  729 class IpNeighCommand(IpDeviceCommandBase):
  730     COMMAND = 'neigh'
  731 
  732     def add(self, ip_address, mac_address, **kwargs):
  733         add_neigh_entry(ip_address,
  734                         mac_address,
  735                         self.name,
  736                         self._parent.namespace,
  737                         **kwargs)
  738 
  739     def delete(self, ip_address, mac_address, **kwargs):
  740         delete_neigh_entry(ip_address,
  741                            mac_address,
  742                            self.name,
  743                            self._parent.namespace,
  744                            **kwargs)
  745 
  746     def dump(self, ip_version, **kwargs):
  747         return dump_neigh_entries(ip_version,
  748                                   self.name,
  749                                   self._parent.namespace,
  750                                   **kwargs)
  751 
  752     def flush(self, ip_version, ip_address):
  753         """Flush neighbour entries
  754 
  755         Given address entry is removed from neighbour cache (ARP or NDP). To
  756         flush all entries pass string 'all' as an address.
  757 
  758         :param ip_version: Either 4 or 6 for IPv4 or IPv6 respectively
  759         :param ip_address: The prefix selecting the neighbours to flush
  760         """
  761         # NOTE(haleyb): There is no equivalent to 'flush' in pyroute2
  762         self._as_root([ip_version], ('flush', 'to', ip_address))
  763 
  764 
  765 class IpNetnsCommand(IpCommandBase):
  766     COMMAND = 'netns'
  767 
  768     def add(self, name):
  769         create_network_namespace(name)
  770         wrapper = IPWrapper(namespace=name)
  771         wrapper.netns.execute(['sysctl', '-w',
  772                                'net.ipv4.conf.all.promote_secondaries=1'])
  773         return wrapper
  774 
  775     def delete(self, name):
  776         delete_network_namespace(name)
  777 
  778     def execute(self, cmds, addl_env=None, check_exit_code=True,
  779                 log_fail_as_error=True, extra_ok_codes=None,
  780                 run_as_root=False):
  781         ns_params = []
  782         if self._parent.namespace:
  783             run_as_root = True
  784             ns_params = ['ip', 'netns', 'exec', self._parent.namespace]
  785 
  786         env_params = []
  787         if addl_env:
  788             env_params = (['env'] +
  789                           ['%s=%s' % pair for pair in addl_env.items()])
  790         cmd = ns_params + env_params + list(cmds)
  791         return utils.execute(cmd, check_exit_code=check_exit_code,
  792                              extra_ok_codes=extra_ok_codes,
  793                              log_fail_as_error=log_fail_as_error,
  794                              run_as_root=run_as_root)
  795 
  796     def exists(self, name):
  797         return network_namespace_exists(name)
  798 
  799 
  800 def vlan_in_use(segmentation_id, namespace=None):
  801     """Return True if VLAN ID is in use by an interface, else False."""
  802     interfaces = get_devices_info(namespace)
  803     vlans = {interface.get('vlan_id') for interface in interfaces
  804              if interface.get('vlan_id')}
  805     return segmentation_id in vlans
  806 
  807 
  808 def vxlan_in_use(segmentation_id, namespace=None):
  809     """Return True if VXLAN VNID is in use by an interface, else False."""
  810     interfaces = get_devices_info(namespace)
  811     vxlans = {interface.get('vxlan_id') for interface in interfaces
  812               if interface.get('vxlan_id')}
  813     return segmentation_id in vxlans
  814 
  815 
  816 def device_exists(device_name, namespace=None):
  817     """Return True if the device exists in the namespace."""
  818     return IPDevice(device_name, namespace=namespace).exists()
  819 
  820 
  821 def device_exists_with_ips_and_mac(device_name, ip_cidrs, mac, namespace=None):
  822     """Return True if the device with the given IP addresses and MAC address
  823     exists in the namespace.
  824     """
  825     try:
  826         device = IPDevice(device_name, namespace=namespace)
  827         if mac and mac != device.link.address:
  828             return False
  829         device_ip_cidrs = [ip['cidr'] for ip in device.addr.list()]
  830         for ip_cidr in ip_cidrs:
  831             if ip_cidr not in device_ip_cidrs:
  832                 return False
  833     except RuntimeError:
  834         return False
  835     else:
  836         return True
  837 
  838 
  839 def get_device_mac(device_name, namespace=None):
  840     """Return the MAC address of the device."""
  841     return IPDevice(device_name, namespace=namespace).link.address
  842 
  843 
  844 def get_device_mtu(device_name, namespace=None):
  845     """Return the MTU value of the device."""
  846     return IPDevice(device_name, namespace=namespace).link.mtu
  847 
  848 
  849 NetworkNamespaceNotFound = privileged.NetworkNamespaceNotFound
  850 NetworkInterfaceNotFound = privileged.NetworkInterfaceNotFound
  851 IpAddressAlreadyExists = privileged.IpAddressAlreadyExists
  852 
  853 
  854 def add_ip_address(cidr, device, namespace=None, scope='global',
  855                    add_broadcast=True):
  856     """Add an IP address.
  857 
  858     :param cidr: IP address to add, in CIDR notation
  859     :param device: Device name to use in adding address
  860     :param namespace: The name of the namespace in which to add the address
  861     :param scope: scope of address being added
  862     :param add_broadcast: should broadcast address be added
  863     """
  864     net = netaddr.IPNetwork(cidr)
  865     broadcast = None
  866     if add_broadcast and net.version == 4:
  867         # NOTE(slaweq): in case if cidr is /32 net.broadcast is None so
  868         # same IP address as cidr should be set as broadcast
  869         broadcast = str(net.broadcast or net.ip)
  870     privileged.add_ip_address(
  871         net.version, str(net.ip), net.prefixlen,
  872         device, namespace, scope, broadcast)
  873 
  874 
  875 def delete_ip_address(cidr, device, namespace=None):
  876     """Delete an IP address.
  877 
  878     :param cidr: IP address to delete, in CIDR notation
  879     :param device: Device name to use in deleting address
  880     :param namespace: The name of the namespace in which to delete the address
  881     """
  882     net = netaddr.IPNetwork(cidr)
  883     privileged.delete_ip_address(
  884         net.version, str(net.ip), net.prefixlen, device, namespace)
  885 
  886 
  887 def flush_ip_addresses(ip_version, device, namespace=None):
  888     """Flush all IP addresses.
  889 
  890     :param ip_version: IP version of addresses to flush
  891     :param device: Device name to use in flushing addresses
  892     :param namespace: The name of the namespace in which to flush the addresses
  893     """
  894     privileged.flush_ip_addresses(ip_version, device, namespace)
  895 
  896 
  897 def get_routing_table(ip_version, namespace=None):
  898     """Return a list of dictionaries, each representing a route.
  899 
  900     @param ip_version: the routes of version to return, for example 4
  901     @param namespace
  902     @return: a list of dictionaries, each representing a route.
  903     The dictionary format is: {'destination': cidr,
  904                                'nexthop': ip,
  905                                'device': device_name,
  906                                'scope': scope}
  907     """
  908     # oslo.privsep turns lists to tuples in its IPC code. Change it back
  909     return list(privileged.get_routing_table(ip_version, namespace))
  910 
  911 
  912 # NOTE(haleyb): These neighbour functions live outside the IpNeighCommand
  913 # class since not all callers require it.
  914 def add_neigh_entry(ip_address, mac_address, device, namespace=None, **kwargs):
  915     """Add a neighbour entry.
  916 
  917     :param ip_address: IP address of entry to add
  918     :param mac_address: MAC address of entry to add
  919     :param device: Device name to use in adding entry
  920     :param namespace: The name of the namespace in which to add the entry
  921     """
  922     ip_version = common_utils.get_ip_version(ip_address)
  923     privileged.add_neigh_entry(ip_version,
  924                                ip_address,
  925                                mac_address,
  926                                device,
  927                                namespace,
  928                                **kwargs)
  929 
  930 
  931 def delete_neigh_entry(ip_address, mac_address, device, namespace=None,
  932                        **kwargs):
  933     """Delete a neighbour entry.
  934 
  935     :param ip_address: IP address of entry to delete
  936     :param mac_address: MAC address of entry to delete
  937     :param device: Device name to use in deleting entry
  938     :param namespace: The name of the namespace in which to delete the entry
  939     """
  940     ip_version = common_utils.get_ip_version(ip_address)
  941     privileged.delete_neigh_entry(ip_version,
  942                                   ip_address,
  943                                   mac_address,
  944                                   device,
  945                                   namespace,
  946                                   **kwargs)
  947 
  948 
  949 def dump_neigh_entries(ip_version, device=None, namespace=None, **kwargs):
  950     """Dump all neighbour entries.
  951 
  952     :param ip_version: IP version of entries to show (4 or 6)
  953     :param device: Device name to use in dumping entries
  954     :param namespace: The name of the namespace in which to dump the entries
  955     :param kwargs: Callers add any filters they use as kwargs
  956     :return: a list of dictionaries, each representing a neighbour.
  957     The dictionary format is: {'dst': ip_address,
  958                                'lladdr': mac_address,
  959                                'device': device_name}
  960     """
  961     return list(privileged.dump_neigh_entries(ip_version,
  962                                               device,
  963                                               namespace,
  964                                               **kwargs))
  965 
  966 
  967 def create_network_namespace(namespace, **kwargs):
  968     """Create a network namespace.
  969 
  970     :param namespace: The name of the namespace to create
  971     :param kwargs: Callers add any filters they use as kwargs
  972     """
  973     privileged.create_netns(namespace, **kwargs)
  974 
  975 
  976 def delete_network_namespace(namespace, **kwargs):
  977     """Delete a network namespace.
  978 
  979     :param namespace: The name of the namespace to delete
  980     :param kwargs: Callers add any filters they use as kwargs
  981     """
  982     privileged.remove_netns(namespace, **kwargs)
  983 
  984 
  985 def list_network_namespaces(**kwargs):
  986     """List all network namespace entries.
  987 
  988     :param kwargs: Callers add any filters they use as kwargs
  989     """
  990     if cfg.CONF.AGENT.use_helper_for_ns_read:
  991         return privileged.list_netns(**kwargs)
  992     else:
  993         return netns.listnetns(**kwargs)
  994 
  995 
  996 def network_namespace_exists(namespace, try_is_ready=False, **kwargs):
  997     """Check if a network namespace exists.
  998 
  999     :param namespace: The name of the namespace to check
 1000     :param try_is_ready: Try to open the namespace to know if the namespace
 1001                          is ready to be operated.
 1002     :param kwargs: Callers add any filters they use as kwargs
 1003     """
 1004     if not try_is_ready:
 1005         output = list_network_namespaces(**kwargs)
 1006         return namespace in output
 1007 
 1008     try:
 1009         privileged.open_namespace(namespace)
 1010         return True
 1011     except (RuntimeError, OSError):
 1012         pass
 1013     return False
 1014 
 1015 
 1016 def ensure_device_is_ready(device_name, namespace=None):
 1017     dev = IPDevice(device_name, namespace=namespace)
 1018     try:
 1019         # Ensure the device has a MAC address and is up, even if it is already
 1020         # up. If the device doesn't exist, a RuntimeError will be raised.
 1021         if not dev.link.address:
 1022             LOG.error("Device %s cannot be used as it has no MAC "
 1023                       "address", device_name)
 1024             return False
 1025         dev.link.set_up()
 1026     except RuntimeError:
 1027         return False
 1028     return True
 1029 
 1030 
 1031 def iproute_arg_supported(command, arg):
 1032     command += ['help']
 1033     stdout, stderr = utils.execute(command, check_exit_code=False,
 1034                                    return_stderr=True, log_fail_as_error=False)
 1035     return any(arg in line for line in stderr.split('\n'))
 1036 
 1037 
 1038 def _arping(ns_name, iface_name, address, count, log_exception):
 1039     # Due to a Linux kernel bug*, it's advised to spread gratuitous updates
 1040     # more, injecting an interval between consequent packets that is longer
 1041     # than 1s which is currently hardcoded** in arping. To achieve that, we
 1042     # call arping tool the 'count' number of times, each issuing a single ARP
 1043     # update, and wait between iterations.
 1044     #
 1045     # *  https://patchwork.ozlabs.org/patch/760372/
 1046     # ** https://github.com/iputils/iputils/pull/86
 1047     first = True
 1048     # Since arping is used to send gratuitous ARP, a response is
 1049     # not expected. In some cases (no response) and with some
 1050     # platforms (>=Ubuntu 14.04), arping exit code can be 1.
 1051     extra_ok_codes = [1]
 1052     ip_wrapper = IPWrapper(namespace=ns_name)
 1053     for i in range(count):
 1054         if not first:
 1055             # hopefully enough for kernel to get out of locktime loop
 1056             time.sleep(2)
 1057             # On the second (and subsequent) arping calls, we can get a
 1058             # "bind: Cannot assign requested address" error since
 1059             # the IP address might have been deleted concurrently.
 1060             # We will log an error below if this isn't the case, so
 1061             # no need to have execute() log one as well.
 1062             extra_ok_codes = [1, 2]
 1063         first = False
 1064 
 1065         # some Linux kernels* don't honour REPLYs. Send both gratuitous REQUEST
 1066         # and REPLY packets (REQUESTs are left for backwards compatibility for
 1067         # in case if some network peers, vice versa, honor REPLYs and not
 1068         # REQUESTs)
 1069         #
 1070         # * https://patchwork.ozlabs.org/patch/763016/
 1071         for arg in ('-U', '-A'):
 1072             arping_cmd = ['arping', arg, '-I', iface_name, '-c', 1,
 1073                           # Pass -w to set timeout to ensure exit if interface
 1074                           # removed while running
 1075                           '-w', 1.5, address]
 1076             try:
 1077                 ip_wrapper.netns.execute(arping_cmd,
 1078                                          extra_ok_codes=extra_ok_codes)
 1079             except Exception as exc:
 1080                 # Since this is spawned in a thread and executed 2 seconds
 1081                 # apart, something may have been deleted while we were
 1082                 # sleeping. Downgrade message to info and return early
 1083                 # unless it was the first try.
 1084                 exists = device_exists_with_ips_and_mac(iface_name,
 1085                                                         [address],
 1086                                                         mac=None,
 1087                                                         namespace=ns_name)
 1088                 msg = _("Failed sending gratuitous ARP to %(addr)s on "
 1089                         "%(iface)s in namespace %(ns)s: %(err)s")
 1090                 logger_method = LOG.exception
 1091                 if not (log_exception and (first or exists)):
 1092                     logger_method = LOG.info
 1093                 logger_method(msg, {'addr': address,
 1094                                     'iface': iface_name,
 1095                                     'ns': ns_name,
 1096                                     'err': exc})
 1097                 if not exists:
 1098                     LOG.info("Interface %(iface)s or address %(addr)s "
 1099                              "in namespace %(ns)s was deleted concurrently",
 1100                              {'iface': iface_name,
 1101                               'addr': address,
 1102                               'ns': ns_name})
 1103                     return
 1104 
 1105 
 1106 def send_ip_addr_adv_notif(
 1107         ns_name, iface_name, address, count=3, log_exception=True):
 1108     """Send advance notification of an IP address assignment.
 1109 
 1110     If the address is in the IPv4 family, send gratuitous ARP.
 1111 
 1112     If the address is in the IPv6 family, no advance notification is
 1113     necessary, since the Neighbor Discovery Protocol (NDP), Duplicate
 1114     Address Discovery (DAD), and (for stateless addresses) router
 1115     advertisements (RAs) are sufficient for address resolution and
 1116     duplicate address detection.
 1117 
 1118     :param ns_name: Namespace name which GARPs are gonna be sent from.
 1119     :param iface_name: Name of interface which GARPs are gonna be sent from.
 1120     :param address: Advertised IP address.
 1121     :param count: (Optional) How many GARPs are gonna be sent. Default is 3.
 1122     :param log_exception: (Optional) True if possible failures should be logged
 1123                           on exception level. Otherwise they are logged on
 1124                           WARNING level. Default is True.
 1125     """
 1126     def arping():
 1127         _arping(ns_name, iface_name, address, count, log_exception)
 1128 
 1129     if count > 0 and netaddr.IPAddress(address).version == 4:
 1130         eventlet.spawn_n(arping)
 1131 
 1132 
 1133 def sysctl(cmd, namespace=None, log_fail_as_error=True):
 1134     """Run sysctl command 'cmd'
 1135 
 1136     @param cmd: a list containing the sysctl command to run
 1137     @param namespace: network namespace to run command in
 1138     @param log_fail_as_error: failure logged as LOG.error
 1139 
 1140     execute() doesn't return the exit status of the command it runs,
 1141     it returns stdout and stderr. Setting check_exit_code=True will cause
 1142     it to raise a RuntimeError if the exit status of the command is
 1143     non-zero, which in sysctl's case is an error. So we're normalizing
 1144     that into zero (success) and one (failure) here to mimic what
 1145     "echo $?" in a shell would be.
 1146 
 1147     This is all because sysctl is too verbose and prints the value you
 1148     just set on success, unlike most other utilities that print nothing.
 1149 
 1150     execute() will have dumped a message to the logs with the actual
 1151     output on failure, so it's not lost, and we don't need to print it
 1152     here.
 1153     """
 1154     cmd = ['sysctl', '-w'] + cmd
 1155     ip_wrapper = IPWrapper(namespace=namespace)
 1156     try:
 1157         ip_wrapper.netns.execute(cmd, run_as_root=True,
 1158                                  log_fail_as_error=log_fail_as_error)
 1159     except RuntimeError as rte:
 1160         LOG.warning(
 1161             "Setting %(cmd)s in namespace %(ns)s failed: %(err)s.",
 1162             {'cmd': cmd,
 1163              'ns': namespace,
 1164              'err': rte})
 1165         return 1
 1166 
 1167     return 0
 1168 
 1169 
 1170 def add_namespace_to_cmd(cmd, namespace=None):
 1171     """Add an optional namespace to the command."""
 1172 
 1173     return ['ip', 'netns', 'exec', namespace] + cmd if namespace else cmd
 1174 
 1175 
 1176 def get_ipv6_lladdr(mac_addr):
 1177     return '%s/64' % netaddr.EUI(mac_addr).ipv6_link_local()
 1178 
 1179 
 1180 def get_ip_nonlocal_bind(namespace=None):
 1181     """Get kernel option value of ip_nonlocal_bind in given namespace."""
 1182     cmd = ['sysctl', '-bn', IP_NONLOCAL_BIND]
 1183     ip_wrapper = IPWrapper(namespace)
 1184     return int(ip_wrapper.netns.execute(cmd, run_as_root=True))
 1185 
 1186 
 1187 def set_ip_nonlocal_bind(value, namespace=None, log_fail_as_error=True):
 1188     """Set sysctl knob of ip_nonlocal_bind to given value."""
 1189     cmd = ['%s=%d' % (IP_NONLOCAL_BIND, value)]
 1190     return sysctl(cmd, namespace=namespace,
 1191                   log_fail_as_error=log_fail_as_error)
 1192 
 1193 
 1194 def set_ip_nonlocal_bind_for_namespace(namespace, value, root_namespace=False):
 1195     """Set ip_nonlocal_bind but don't raise exception on failure."""
 1196     failed = set_ip_nonlocal_bind(value, namespace=namespace,
 1197                                   log_fail_as_error=False)
 1198     if failed and root_namespace:
 1199         # Somewhere in the 3.19 kernel timeframe ip_nonlocal_bind was
 1200         # changed to be a per-namespace attribute.  To be backwards
 1201         # compatible we need to try both if at first we fail.
 1202         LOG.debug('Namespace (%s) does not support setting %s, '
 1203                   'trying in root namespace', namespace, IP_NONLOCAL_BIND)
 1204         return set_ip_nonlocal_bind(value)
 1205     if failed:
 1206         LOG.warning(
 1207             "%s will not be set to %d in the root namespace in order to "
 1208             "not break DVR, which requires this value be set to 1. This "
 1209             "may introduce a race between moving a floating IP to a "
 1210             "different network node, and the peer side getting a "
 1211             "populated ARP cache for a given floating IP address.",
 1212             IP_NONLOCAL_BIND, value)
 1213 
 1214 
 1215 def get_ipv6_forwarding(device, namespace=None):
 1216     """Get kernel value of IPv6 forwarding for device in given namespace."""
 1217     cmd = ['sysctl', '-b', "net.ipv6.conf.%s.forwarding" % device]
 1218     ip_wrapper = IPWrapper(namespace)
 1219     return int(ip_wrapper.netns.execute(cmd, run_as_root=True))
 1220 
 1221 
 1222 def _parse_ip_rule(rule, ip_version):
 1223     """Parse a pyroute2 rule and returns a dictionary
 1224 
 1225     Parameters contained in the returned dictionary:
 1226     - priority: rule priority
 1227     - from: source IP address
 1228     - to: (optional) destination IP address
 1229     - type: rule type (see RULE_TYPES)
 1230     - table: table name or number (see RULE_TABLES)
 1231     - fwmark: (optional) FW mark
 1232     - iif: (optional) input interface name
 1233     - oif: (optional) output interface name
 1234 
 1235      :param rule: pyroute2 rule dictionary
 1236      :param ip_version: IP version (4, 6)
 1237      :return: dictionary with IP rule information
 1238     """
 1239     parsed_rule = {'priority': str(rule['attrs'].get('FRA_PRIORITY', 0))}
 1240     from_ip = rule['attrs'].get('FRA_SRC')
 1241     if from_ip:
 1242         parsed_rule['from'] = common_utils.ip_to_cidr(
 1243             from_ip, prefix=rule['src_len'])
 1244         if common_utils.is_cidr_host(parsed_rule['from']):
 1245             parsed_rule['from'] = common_utils.cidr_to_ip(parsed_rule['from'])
 1246     else:
 1247         parsed_rule['from'] = constants.IP_ANY[ip_version]
 1248     to_ip = rule['attrs'].get('FRA_DST')
 1249     if to_ip:
 1250         parsed_rule['to'] = common_utils.ip_to_cidr(
 1251             to_ip, prefix=rule['dst_len'])
 1252         if common_utils.is_cidr_host(parsed_rule['to']):
 1253             parsed_rule['to'] = common_utils.cidr_to_ip(parsed_rule['to'])
 1254     parsed_rule['type'] = IP_RULE_TYPES[rule['action']]
 1255     table_num = rule['attrs']['FRA_TABLE']
 1256     for table_name in (name for (name, index) in
 1257                        IP_RULE_TABLES.items() if index == table_num):
 1258         parsed_rule['table'] = table_name
 1259         break
 1260     else:
 1261         parsed_rule['table'] = str(table_num)
 1262     fwmark = rule['attrs'].get('FRA_FWMARK')
 1263     if fwmark:
 1264         fwmask = rule['attrs'].get('FRA_FWMASK')
 1265         parsed_rule['fwmark'] = '{0:#x}/{1:#x}'.format(fwmark, fwmask)
 1266     iifname = rule['attrs'].get('FRA_IIFNAME')
 1267     if iifname:
 1268         parsed_rule['iif'] = iifname
 1269     oifname = rule['attrs'].get('FRA_OIFNAME')
 1270     if oifname:
 1271         parsed_rule['oif'] = oifname
 1272 
 1273     return parsed_rule
 1274 
 1275 
 1276 def list_ip_rules(namespace, ip_version):
 1277     """List all IP rules in a namespace
 1278 
 1279     :param namespace: namespace name
 1280     :param ip_version: IP version (4, 6)
 1281     :return: list of dictionaries with the rules information
 1282     """
 1283     rules = privileged.list_ip_rules(namespace, ip_version)
 1284     return [_parse_ip_rule(rule, ip_version) for rule in rules]
 1285 
 1286 
 1287 def _make_pyroute2_args(ip, iif, table, priority, to):
 1288     """Returns a dictionary of arguments to be used in pyroute rule commands
 1289 
 1290     :param ip: (string) source IP or CIDR address (IPv4, IPv6)
 1291     :param iif: (string) input interface name
 1292     :param table: (string, int) table number (as an int or a string) or table
 1293                   name ('default', 'main', 'local')
 1294     :param priority: (string, int) rule priority
 1295     :param to: (string) destination IP or CIDR address (IPv4, IPv6)
 1296     :return: a dictionary with the kwargs needed in pyroute rule commands
 1297     """
 1298     ip_version = common_utils.get_ip_version(ip)
 1299     # In case we need to add a rule based on an incoming interface, no
 1300     # IP address is given; the rule default source ("from") address is
 1301     # "all".
 1302     cmd_args = {'family': common_utils.get_socket_address_family(ip_version)}
 1303     if iif:
 1304         cmd_args['iifname'] = iif
 1305     else:
 1306         cmd_args['src'] = common_utils.cidr_to_ip(ip)
 1307         cmd_args['src_len'] = common_utils.cidr_mask(ip)
 1308     if to:
 1309         cmd_args['dst'] = common_utils.cidr_to_ip(to)
 1310         cmd_args['dst_len'] = common_utils.cidr_mask(to)
 1311     if table:
 1312         cmd_args['table'] = IP_RULE_TABLES.get(table) or int(table)
 1313     if priority:
 1314         cmd_args['priority'] = int(priority)
 1315     return cmd_args
 1316 
 1317 
 1318 def _exist_ip_rule(rules, ip, iif, table, priority, to):
 1319     """Check if any rule matches the conditions"""
 1320     for rule in rules:
 1321         if iif and rule.get('iif') != iif:
 1322             continue
 1323         if not iif and rule['from'] != ip:
 1324             continue
 1325         if table and rule.get('table') != str(table):
 1326             continue
 1327         if priority and rule['priority'] != str(priority):
 1328             continue
 1329         if to and rule.get('to') != to:
 1330             continue
 1331         break
 1332     else:
 1333         return False
 1334     return True
 1335 
 1336 
 1337 def add_ip_rule(namespace, ip, iif=None, table=None, priority=None, to=None):
 1338     """Create an IP rule in a namespace
 1339 
 1340     :param namespace: (string) namespace name
 1341     :param ip: (string) source IP or CIDR address (IPv4, IPv6)
 1342     :param iif: (Optional) (string) input interface name
 1343     :param table: (Optional) (string, int) table number
 1344     :param priority: (Optional) (string, int) rule priority
 1345     :param to: (Optional) (string) destination IP or CIDR address (IPv4, IPv6)
 1346     """
 1347     ip_version = common_utils.get_ip_version(ip)
 1348     rules = list_ip_rules(namespace, ip_version)
 1349     if _exist_ip_rule(rules, ip, iif, table, priority, to):
 1350         return
 1351     cmd_args = _make_pyroute2_args(ip, iif, table, priority, to)
 1352     privileged.add_ip_rule(namespace, **cmd_args)
 1353 
 1354 
 1355 def delete_ip_rule(namespace, ip, iif=None, table=None, priority=None,
 1356                    to=None):
 1357     """Delete an IP rule in a namespace
 1358 
 1359     :param namespace: (string) namespace name
 1360     :param ip: (string) source IP or CIDR address (IPv4, IPv6)
 1361     :param iif: (Optional) (string) input interface name
 1362     :param table: (Optional) (string, int) table number
 1363     :param priority: (Optional) (string, int) rule priority
 1364     :param to: (Optional) (string) destination IP or CIDR address (IPv4, IPv6)
 1365     """
 1366     cmd_args = _make_pyroute2_args(ip, iif, table, priority, to)
 1367     privileged.delete_ip_rule(namespace, **cmd_args)
 1368 
 1369 
 1370 def get_attr(pyroute2_obj, attr_name):
 1371     """Get an attribute from a PyRoute2 object"""
 1372     rule_attrs = pyroute2_obj.get('attrs', [])
 1373     for attr in (attr for attr in rule_attrs if attr[0] == attr_name):
 1374         return attr[1]
 1375 
 1376 
 1377 def _parse_link_device(namespace, device, **kwargs):
 1378     """Parse pytoute2 link device information
 1379 
 1380     For each link device, the IP address information is retrieved and returned
 1381     in a dictionary.
 1382     IP address scope: http://linux-ip.net/html/tools-ip-address.html
 1383     """
 1384     retval = []
 1385     name = get_attr(device, 'IFLA_IFNAME')
 1386     ip_addresses = privileged.get_ip_addresses(namespace,
 1387                                                index=device['index'],
 1388                                                **kwargs)
 1389     for ip_address in ip_addresses:
 1390         ip = get_attr(ip_address, 'IFA_ADDRESS')
 1391         ip_length = ip_address['prefixlen']
 1392         cidr = common_utils.ip_to_cidr(ip, prefix=ip_length)
 1393         flags = get_attr(ip_address, 'IFA_FLAGS')
 1394         dynamic = not bool(flags & ifaddrmsg.IFA_F_PERMANENT)
 1395         tentative = bool(flags & ifaddrmsg.IFA_F_TENTATIVE)
 1396         dadfailed = bool(flags & ifaddrmsg.IFA_F_DADFAILED)
 1397         scope = IP_ADDRESS_SCOPE[ip_address['scope']]
 1398         retval.append({'name': name,
 1399                        'cidr': cidr,
 1400                        'scope': scope,
 1401                        'broadcast': get_attr(ip_address, 'IFA_BROADCAST'),
 1402                        'dynamic': dynamic,
 1403                        'tentative': tentative,
 1404                        'dadfailed': dadfailed})
 1405     return retval
 1406 
 1407 
 1408 def get_devices_with_ip(namespace, name=None, **kwargs):
 1409     link_args = {}
 1410     if name:
 1411         link_args['ifname'] = name
 1412     devices = privileged.get_link_devices(namespace, **link_args)
 1413     retval = []
 1414     for parsed_ips in (_parse_link_device(namespace, device, **kwargs)
 1415                        for device in devices):
 1416         retval += parsed_ips
 1417     return retval
 1418 
 1419 
 1420 def get_devices_info(namespace, **kwargs):
 1421     devices = privileged.get_link_devices(namespace, **kwargs)
 1422     retval = {}
 1423     for device in devices:
 1424         ret = {'index': device['index'],
 1425                'name': get_attr(device, 'IFLA_IFNAME'),
 1426                'operstate': get_attr(device, 'IFLA_OPERSTATE'),
 1427                'linkmode': get_attr(device, 'IFLA_LINKMODE'),
 1428                'mtu': get_attr(device, 'IFLA_MTU'),
 1429                'promiscuity': get_attr(device, 'IFLA_PROMISCUITY'),
 1430                'mac': get_attr(device, 'IFLA_ADDRESS'),
 1431                'broadcast': get_attr(device, 'IFLA_BROADCAST')}
 1432         ifla_link = get_attr(device, 'IFLA_LINK')
 1433         if ifla_link:
 1434             ret['parent_index'] = ifla_link
 1435         ifla_linkinfo = get_attr(device, 'IFLA_LINKINFO')
 1436         if ifla_linkinfo:
 1437             ret['kind'] = get_attr(ifla_linkinfo, 'IFLA_INFO_KIND')
 1438             ifla_data = get_attr(ifla_linkinfo, 'IFLA_INFO_DATA')
 1439             if ret['kind'] == 'vxlan':
 1440                 ret['vxlan_id'] = get_attr(ifla_data, 'IFLA_VXLAN_ID')
 1441                 ret['vxlan_group'] = get_attr(ifla_data, 'IFLA_VXLAN_GROUP')
 1442                 ret['vxlan_link_index'] = get_attr(ifla_data,
 1443                                                    'IFLA_VXLAN_LINK')
 1444             elif ret['kind'] == 'vlan':
 1445                 ret['vlan_id'] = get_attr(ifla_data, 'IFLA_VLAN_ID')
 1446         retval[device['index']] = ret
 1447 
 1448     for device in retval.values():
 1449         if device.get('parent_index'):
 1450             parent_device = retval.get(device['parent_index'])
 1451             if parent_device:
 1452                 device['parent_name'] = parent_device['name']
 1453         elif device.get('vxlan_link_index'):
 1454             device['vxlan_link_name'] = (
 1455                 retval[device['vxlan_link_index']]['name'])
 1456 
 1457     return list(retval.values())