diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index 3abc4c4..8e6e083 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -6,8 +6,6 @@ import socket -from ipaddr import IPNetwork, IPv4Network, IPv6Network - try: from ifupdown2.lib.addon import Addon from ifupdown2.nlmanager.nlmanager import Link @@ -18,6 +16,8 @@ try: from ifupdown2.ifupdownaddons.dhclient import dhclient from ifupdown2.ifupdownaddons.modulebase import moduleBase + import ifupdown2.nlmanager.ipnetwork as ipnetwork + import ifupdown2.ifupdown.statemanager as statemanager import ifupdown2.ifupdown.policymanager as policymanager import ifupdown2.ifupdown.ifupdownflags as ifupdownflags @@ -32,6 +32,8 @@ except (ImportError, ModuleNotFoundError): from ifupdownaddons.dhclient import dhclient from ifupdownaddons.modulebase import moduleBase + import nlmanager.ipnetwork as ipnetwork + import ifupdown.statemanager as statemanager import ifupdown.policymanager as policymanager import ifupdown.ifupdownflags as ifupdownflags @@ -189,7 +191,7 @@ class address(Addon, moduleBase): self.default_mtu = self.__policy_get_default_mtu() self.max_mtu = self.__policy_get_max_mtu() - self.default_loopback_addresses = (IPNetwork('127.0.0.1/8'), IPNetwork('::1/128')) + self.default_loopback_addresses = (ipnetwork.IPNetwork('127.0.0.1/8'), ipnetwork.IPNetwork('::1/128')) self.l3_intf_arp_accept = utils.get_boolean_from_string( policymanager.policymanager_api.get_module_globals( @@ -312,8 +314,8 @@ class address(Addon, moduleBase): return utils.is_addr_ip_allowed_on(ifaceobj, syntax_check=syntax_check) return True - def _syntax_check_multiple_gateway(self, family, found, addr, type_obj): - if type(IPNetwork(addr)) == type_obj: + def _syntax_check_multiple_gateway(self, family, found, addr, version): + if ipnetwork.IPNetwork(addr).version == version: if found: raise Exception('%s: multiple gateways for %s family' % (addr, family)) @@ -327,11 +329,9 @@ class address(Addon, moduleBase): gateways = ifaceobj.get_attr_value('gateway') for addr in gateways if gateways else []: try: - if self._syntax_check_multiple_gateway('inet', inet, addr, - IPv4Network): + if self._syntax_check_multiple_gateway('inet', inet, addr, 4): inet = True - if self._syntax_check_multiple_gateway('inet6', inet6, addr, - IPv6Network): + if self._syntax_check_multiple_gateway('inet6', inet6, addr, 6): inet6 = True except Exception as e: self.logger.warning('%s: address: %s' % (ifaceobj.name, str(e))) @@ -389,7 +389,7 @@ class address(Addon, moduleBase): self.iproute2.bridge_fdb_del(bridgename, hwaddress, vlan) def __get_ip_addr_with_attributes(self, ifaceobj_list, ifname): - user_config_ip_addrs_list = list() + user_config_ip_addrs_list = [] try: for ifaceobj in ifaceobj_list: @@ -408,14 +408,14 @@ class address(Addon, moduleBase): # convert the ip from string to IPNetwork object if "/" in addr: - addr_obj = IPNetwork(addr) + addr_obj = ipnetwork.IPNetwork(addr) else: netmask = ifaceobj.get_attr_value_n("netmask", index) if netmask: - addr_obj = IPNetwork("%s/%s" % (addr, netmask)) + addr_obj = ipnetwork.IPNetwork(addr, netmask) else: - addr_obj = IPNetwork(addr) + addr_obj = ipnetwork.IPNetwork(addr) for attr_name in ("broadcast", "scope", "preferred-lifetime"): attr_value = ifaceobj.get_attr_value_n(attr_name, index) @@ -425,7 +425,7 @@ class address(Addon, moduleBase): pointopoint = ifaceobj.get_attr_value_n("pointopoint", index) try: if pointopoint: - addr_attributes["pointopoint"] = IPNetwork(pointopoint) + addr_attributes["pointopoint"] = ipnetwork.IPNetwork(pointopoint) except Exception as e: self.logger.warning("%s: pointopoint %s: %s" % (ifaceobj.name, pointopoint, str(e))) @@ -465,9 +465,9 @@ class address(Addon, moduleBase): for ifaceobj in ifaceobjlist: anycast_addr = ifaceobj.get_attr_value_first("clagd-vxlan-anycast-ip") if anycast_addr: - anycast_ip_addr = IPNetwork(anycast_addr) + anycast_ip_addr = ipnetwork.IPNetwork(anycast_addr) - return str(anycast_ip_addr) if anycast_ip_addr else None + return anycast_ip_addr def process_addresses(self, ifaceobj, ifaceobj_getfunc=None, force_reapply=False): squash_addr_config = ifupdownconfig.config.get("addr_config_squash", "0") == "1" @@ -500,27 +500,22 @@ class address(Addon, moduleBase): # lets purge addresses not in the config anycast_ip = None - running_ip_addrs = self.cache.get_ifupdown2_addresses_list(ifaceobj_list, ifname) + running_ip_addrs = self.cache.get_managed_ip_addresses(ifname, ifaceobj_list) if ifaceobj.link_privflags & ifaceLinkPrivFlags.LOOPBACK: anycast_ip = self.__add_loopback_anycast_ip_to_running_ip_addr_list(ifaceobj_list) - # user_ip4, user_ip6 and ordered_user_configured_ips IP addresses are now represented - # in string format. Comparaisons between IPNetwork object are not reliable, i.e.: - # IPNetwork("2001:aa::2/64") == IPNetwork("2001:aa::150/64") user_ip4, user_ip6, ordered_user_configured_ips = self.order_user_configured_addrs(user_config_ip_addrs_list) - running_ip_addrs_str = self.get_ipnetwork_object_list_in_string_format(running_ip_addrs) - if ordered_user_configured_ips == running_ip_addrs or self.compare_running_ips_and_user_config(user_ip4, user_ip6, running_ip_addrs): if force_reapply: self.__add_ip_addresses_with_attributes(ifaceobj, ifname, user_config_ip_addrs_list) return try: # if primary address is not same, there is no need to keep any, reset all addresses. - if ordered_user_configured_ips and running_ip_addrs_str and ordered_user_configured_ips[0] != running_ip_addrs_str[0]: + if ordered_user_configured_ips and running_ip_addrs and ordered_user_configured_ips[0] != running_ip_addrs[0]: self.logger.info("%s: primary ip changed (from %s to %s) we need to purge all ip addresses and re-add them" - % (ifname, ordered_user_configured_ips[0], running_ip_addrs_str[0])) + % (ifname, ordered_user_configured_ips[0], running_ip_addrs[0])) skip_addrs = [] else: skip_addrs = ordered_user_configured_ips @@ -528,21 +523,16 @@ class address(Addon, moduleBase): if anycast_ip: skip_addrs.append(anycast_ip) - for index, addr in enumerate(running_ip_addrs_str): + for addr in running_ip_addrs: if addr in skip_addrs: continue - # we still have to send the IPNetwork object - # to the netlink "addr_del" API - self.netlink.addr_del(ifname, running_ip_addrs[index]) + self.netlink.addr_del(ifname, addr) except Exception as e: self.log_warn(str(e)) if not user_config_ip_addrs_list: return self.__add_ip_addresses_with_attributes(ifaceobj, ifname, user_config_ip_addrs_list) - def get_ipnetwork_object_list_in_string_format(self, obj_list): - return [str(obj) for obj in obj_list] - def compare_running_ips_and_user_config(self, user_ip4, user_ip6, running_addrs): """ We need to compare the user config ips and the running ips. @@ -595,9 +585,9 @@ class address(Addon, moduleBase): for a, _ in user_config_addrs: if a.version == 6: - ip6.append(str(a)) + ip6.append(a) else: - ip4.append(str(a)) + ip4.append(a) return ip4, ip6, ip4 + ip6 @@ -1029,7 +1019,7 @@ class address(Addon, moduleBase): else: ifaceobj_list = [ifaceobj] - for addr in self.cache.get_ifupdown2_addresses_list(ifaceobj_list, ifaceobj.name): + for addr in self.cache.get_managed_ip_addresses(ifaceobj.name, ifaceobj_list): self.netlink.addr_del(ifaceobj.name, addr) gateways = ifaceobj.get_attr_value('gateway') @@ -1063,21 +1053,6 @@ class address(Addon, moduleBase): self.logger.debug('%s : %s' %(ifaceobj.name, str(e))) pass - def _get_iface_addresses(self, ifaceobj): - addrlist = ifaceobj.get_attr_value('address') - outaddrlist = [] - - if not addrlist: return None - for addrindex in range(0, len(addrlist)): - addr = addrlist[addrindex] - netmask = ifaceobj.get_attr_value_n('netmask', addrindex) - if netmask: - prefixlen = IPNetwork('%s' %addr + - '/%s' %netmask).prefixlen - addr = addr + '/%s' %prefixlen - outaddrlist.append(addr) - return outaddrlist - def _get_bridge_fdbs(self, bridgename, vlan): fdbs = self._bridge_fdb_query_cache.get(bridgename) if not fdbs: @@ -1188,21 +1163,25 @@ class address(Addon, moduleBase): 0) self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, 'alias', self.cache.get_link_alias) + self._query_sysctl(ifaceobj, ifaceobjcurr) - # compare addresses - if addr_method in ["dhcp", "ppp"]: - return + self._query_check_address(ifaceobj, ifaceobjcurr, ifaceobj_getfunc) + + def _query_check_address(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc): + """ ifquery-check: attribute: "address" """ + if ifaceobj.addr_method in ["dhcp", "ppp"]: + return if ifaceobj_getfunc: ifaceobj_list = ifaceobj_getfunc(ifaceobj.name) else: ifaceobj_list = [ifaceobj] - intf_running_addrs = self.cache.get_ifupdown2_addresses_list(ifaceobj_list, ifaceobj.name) - user_config_addrs = self.cache.get_user_config_ip_addrs_with_attrs_in_ipnetwork_format([ifaceobj], details=False) + intf_running_addrs = self.cache.get_managed_ip_addresses(ifaceobj.name, ifaceobj_list) + user_config_addrs = self.cache.get_user_configured_addresses([ifaceobj]) try: - clagd_vxlan_anycast_ip = IPNetwork(ifaceobj.get_attr_value_first('clagd-vxlan-anycast-ip')) + clagd_vxlan_anycast_ip = ipnetwork.IPNetwork(ifaceobj.get_attr_value_first("clagd-vxlan-anycast-ip")) if clagd_vxlan_anycast_ip in intf_running_addrs: user_config_addrs.append(clagd_vxlan_anycast_ip) @@ -1225,25 +1204,21 @@ class address(Addon, moduleBase): except: pass - # if any ip address is left in 'intf_running_addrs' it means - # that they used to be configured by ifupdown2 but not anymore - # but are still on the intf, so we need to mark them as fail - # we will only mark them as failure on the first sibling + # if any ip address is left in 'intf_running_addrs' it means that they + # used to be configured by ifupdown2 but not anymore. The entry was + # removed from the configuration file but the IP is still configured on + # the device, so we need to mark them as FAIL (we will only mark them + # as failure on the first sibling). if ifaceobj.flags & iface.HAS_SIBLINGS: if not ifaceobj.flags & iface.YOUNGEST_SIBLING: return - all_stanza_user_config_ip = self.cache.get_user_config_ip_addrs_with_attrs_in_ipnetwork_format( - ifaceobj_list, - details=False - ) + all_stanza_user_config_ip = self.cache.get_user_configured_addresses(ifaceobj_list) for address in intf_running_addrs: if address not in all_stanza_user_config_ip: ifaceobjcurr.update_config_with_status('address', str(address), 1) - return - def query_running_ipv6_addrgen(self, ifaceobjrunning): ipv6_addrgen = self.cache.get_link_ipv6_addrgen_mode(ifaceobjrunning.name) @@ -1263,7 +1238,7 @@ class address(Addon, moduleBase): # If dhcp is configured on the interface, we skip it return - intf_running_addrs = self.cache.get_addresses_list(ifaceobjrunning.name) or [] + intf_running_addrs = self.cache.get_ip_addresses(ifaceobjrunning.name) or [] if self.cache.link_is_loopback(ifaceobjrunning.name): for default_addr in self.default_loopback_addresses: @@ -1275,7 +1250,7 @@ class address(Addon, moduleBase): ifaceobjrunning.addr_method = 'loopback' for addr in intf_running_addrs: - ifaceobjrunning.update_config('address', str(addr)) + ifaceobjrunning.update_config('address', addr) mtu = self.cache.get_link_mtu_str(ifaceobjrunning.name) if (mtu and diff --git a/ifupdown2/addons/addressvirtual.py b/ifupdown2/addons/addressvirtual.py index 1eb668e..c762fb8 100644 --- a/ifupdown2/addons/addressvirtual.py +++ b/ifupdown2/addons/addressvirtual.py @@ -6,10 +6,10 @@ import os import glob +import ipaddress import subprocess from collections import deque -from ipaddr import IPNetwork, IPv6Network try: from ifupdown2.lib.addon import Addon @@ -20,6 +20,8 @@ try: from ifupdown2.ifupdownaddons.modulebase import moduleBase + import ifupdown2.nlmanager.ipnetwork as ipnetwork + import ifupdown2.ifupdown.statemanager as statemanager import ifupdown2.ifupdown.policymanager as policymanager import ifupdown2.ifupdown.ifupdownflags as ifupdownflags @@ -33,6 +35,8 @@ except (ImportError, ModuleNotFoundError): from ifupdownaddons.modulebase import moduleBase + import nlmanager.ipnetwork as ipnetwork + import ifupdown.statemanager as statemanager import ifupdown.policymanager as policymanager import ifupdown.ifupdownflags as ifupdownflags @@ -193,20 +197,25 @@ class addressvirtual(Addon, moduleBase): # try: self.logger.info('%s: checking route entry ...' %ifaceobj.name) - ip = IPNetwork(addr) + + # here we need to convert the ip address using the standard IPNetwork + # object from the ipaddress not the custom IPNetwork object from + # python3-nlmanager, because the standard IPNetwork will automatically + # convert our ip address with prefixlen: + # >>> ipaddress.ip_network("10.10.10.242/10", False) + # IPv4Network('10.0.0.0/10') + ip = ipaddress.ip_network(addr, False) # we don't support ip6 route fix yet - if type(ip) == IPv6Network: + if ip.version == 6: return - route_prefix = '%s/%d' %(ip.network, ip.prefixlen) - if ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE: vrf_master = self.cache.get_master(ifaceobj.name) else: vrf_master = None - dev = self.iproute2.ip_route_get_dev(route_prefix, vrf_master=vrf_master) + dev = self.iproute2.ip_route_get_dev(ip.with_prefixlen, vrf_master=vrf_master) if dev and dev != ifaceobj.name: self.logger.info('%s: preferred routing entry ' %ifaceobj.name + @@ -616,8 +625,8 @@ class addressvirtual(Addon, moduleBase): ip6 = [] for ip_addr in ip_addrs.split(): - ip_network_obj = IPNetwork(ip_addr) - is_ip6 = isinstance(ip_network_obj, IPv6Network) + ip_network_obj = ipnetwork.IPNetwork(ip_addr) + is_ip6 = ip_network_obj.version == 6 if is_ip6: ip6.append(ip_addr) @@ -765,7 +774,7 @@ class addressvirtual(Addon, moduleBase): ip_network_obj_list = [] for ip in av_attrs[1:]: - ip_network_obj_list.append(str(IPNetwork(ip))) + ip_network_obj_list.append(ipnetwork.IPNetwork(ip)) config["ips"] = ip_network_obj_list user_config_list.append(config) @@ -849,16 +858,14 @@ class addressvirtual(Addon, moduleBase): ip6 = [] for ip in user_addrs or []: - obj = IPNetwork(ip) - - if type(obj) == IPv6Network: - ip6.append(obj) + if ip.version == 6: + ip6.append(ip) else: - ip4.append(obj) + ip4.append(ip) running_ipobj = [] for ip in running_addrs or []: - running_ipobj.append(IPNetwork(ip)) + running_ipobj.append(ip) return running_ipobj == (ip4 + ip6) @@ -905,11 +912,11 @@ class addressvirtual(Addon, moduleBase): # Check mac and ip address rhwaddress = ip4_macvlan_hwaddress = self.cache.get_link_address(macvlan_ifacename) - raddrs = ip4_running_addrs =[str(ip) for ip in self.cache.get_ifupdown2_addresses_list( + raddrs = ip4_running_addrs = self.cache.get_managed_ip_addresses( ifname=macvlan_ifacename, ifaceobj_list=[ifaceobj], with_address_virtual=True - )] + ) if not is_vrr: ips = config.get("ips") @@ -932,9 +939,9 @@ class addressvirtual(Addon, moduleBase): address_virtual_value = "%s %s" % (rhwaddress, " ".join(raddrs)) else: address_virtual_value = rhwaddress - ifaceobjcurr.update_config_with_status(attr_name, address_virtual_value, 1) - except: + except Exception as e: + self.logger.debug("addressvirtual: %s" % str(e)) if raddrs: address_virtual_value = "%s %s" % (rhwaddress, " ".join(raddrs)) else: @@ -958,12 +965,11 @@ class addressvirtual(Addon, moduleBase): ip4_running_addrs, ip4_config.get("ips") ) and self._check_addresses_in_bridge(ifaceobj, ip4_macvlan_hwaddress): - ip6_running_addrs = [str(ip) for ip in self.cache.get_ifupdown2_addresses_list( + ip6_running_addrs = self.cache.get_managed_ip_addresses( ifname=ip6_macvlan_ifname, - details=False, ifaceobj_list=[ifaceobj], with_address_virtual=True - )] + ) # check all ip6 if self.compare_user_config_vs_running_state( @@ -993,11 +999,11 @@ class addressvirtual(Addon, moduleBase): for av in address_virtuals: macvlan_ifacename = os.path.basename(av) rhwaddress = self.cache.get_link_address(macvlan_ifacename) - raddress = [str(ip) for ip in self.cache.get_ifupdown2_addresses_list( - ifaceobj_list=ifaceobj_getfunc(ifaceobjrunning.name) or [], + raddress = self.cache.get_managed_ip_addresses( ifname=ifaceobjrunning.name, + ifaceobj_list=ifaceobj_getfunc(ifaceobjrunning.name) or [], with_address_virtual=True - )] + ) raddress = list(set(raddress)) diff --git a/ifupdown2/addons/vrf.py b/ifupdown2/addons/vrf.py index 07dd3eb..da32fc8 100644 --- a/ifupdown2/addons/vrf.py +++ b/ifupdown2/addons/vrf.py @@ -802,16 +802,11 @@ class vrf(Addon, moduleBase): def _kill_ssh_connections(self, ifacename, ifaceobj): try: - running_addrs_list = [str(ip) for ip in self.cache.get_ifupdown2_addresses_list( - ifaceobj_list=[ifaceobj], + iplist = [str(ip.ip) for ip in self.cache.get_managed_ip_addresses( ifname=ifacename, + ifaceobj_list=[ifaceobj], )] - if not running_addrs_list: - return - - iplist = [i.split('/', 1)[0] for i in running_addrs_list] - if not iplist: return proc=[] diff --git a/ifupdown2/addons/vxlan.py b/ifupdown2/addons/vxlan.py index e005b30..b0ef0f2 100644 --- a/ifupdown2/addons/vxlan.py +++ b/ifupdown2/addons/vxlan.py @@ -4,10 +4,8 @@ # Author: Roopa Prabhu, roopa@cumulusnetworks.com # - -from ipaddr import IPNetwork, IPAddress, IPv4Address, IPv4Network, AddressValueError - try: + import ifupdown2.nlmanager.ipnetwork as ipnetwork import ifupdown2.ifupdown.policymanager as policymanager import ifupdown2.ifupdown.ifupdownflags as ifupdownflags @@ -21,6 +19,7 @@ try: from ifupdown2.ifupdownaddons.cache import * from ifupdown2.ifupdownaddons.modulebase import moduleBase except (ImportError, ModuleNotFoundError): + import nlmanager.ipnetwork as ipnetwork import ifupdown.policymanager as policymanager import ifupdown.ifupdownflags as ifupdownflags @@ -148,7 +147,7 @@ class vxlan(Addon, moduleBase): def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip): try: - if local_ip and anycast_ip and IPNetwork(local_ip) == IPNetwork(anycast_ip): + if local_ip and anycast_ip and ipnetwork.IPNetwork(local_ip) == ipnetwork.IPNetwork(anycast_ip): self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)' % (ifname, local_ip)) return False @@ -376,7 +375,7 @@ class vxlan(Addon, moduleBase): running_localtunnelip = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL) if self._clagd_vxlan_anycast_ip and running_localtunnelip: - anycastip = IPAddress(self._clagd_vxlan_anycast_ip) + anycastip = ipnetwork.IPNetwork(self._clagd_vxlan_anycast_ip) if anycastip == running_localtunnelip: local = running_localtunnelip @@ -388,14 +387,13 @@ class vxlan(Addon, moduleBase): if local: try: - local = IPv4Address(local) - except AddressValueError: - try: - local_ip = IPv4Network(local).ip + local = ipnetwork.IPv4Address(local) + + if local.initialized_with_prefixlen: self.logger.warning("%s: vxlan-local-tunnelip %s: netmask ignored" % (ifname, local)) - local = local_ip - except: - raise Exception("%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format" % (ifname, local)) + + except Exception as e: + raise Exception("%s: invalid vxlan-local-tunnelip %s: %s" % (ifname, local, str(e))) cached_ifla_vxlan_local = cached_vxlan_ifla_info_data.get(Link.IFLA_VXLAN_LOCAL) @@ -469,16 +467,15 @@ class vxlan(Addon, moduleBase): if group: try: - group = IPv4Address(group) - except AddressValueError: - try: - group_ip = IPv4Network(group).ip - self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group)) - group = group_ip - except: - raise Exception("%s: invalid vxlan-svcnodeip %s: must be in ipv4 format" % (ifname, group)) + group = ipnetwork.IPv4Address(group) - if group.is_multicast: + if group.initialized_with_prefixlen: + self.logger.warning("%s: vxlan-svcnodeip %s: netmask ignored" % (ifname, group)) + + except Exception as e: + raise Exception("%s: invalid vxlan-svcnodeip %s: %s" % (ifname, group, str(e))) + + if group.ip.is_multicast: self.logger.warning("%s: vxlan-svcnodeip %s: invalid group address, " "for multicast IP please use attribute \"vxlan-mcastgrp\"" % (ifname, group)) # if svcnodeip is used instead of mcastgrp we warn the user @@ -489,16 +486,15 @@ class vxlan(Addon, moduleBase): elif mcast_grp: try: - mcast_grp = IPv4Address(mcast_grp) - except AddressValueError: - try: - group_ip = IPv4Network(mcast_grp).ip - self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp)) - mcast_grp = group_ip - except: - raise Exception("%s: invalid vxlan-mcastgrp %s: must be in ipv4 format" % (ifname, mcast_grp)) + mcast_grp = ipnetwork.IPv4Address(mcast_grp) - if not mcast_grp.is_multicast: + if mcast_grp.initialized_with_prefixlen: + self.logger.warning("%s: vxlan-mcastgrp %s: netmask ignored" % (ifname, mcast_grp)) + + except Exception as e: + raise Exception("%s: invalid vxlan-mcastgrp %s: %s" % (ifname, mcast_grp, str(e))) + + if not mcast_grp.ip.is_multicast: self.logger.warning("%s: vxlan-mcastgrp %s: invalid group address, " "for non-multicast IP please use attribute \"vxlan-svcnodeip\"" % (ifname, mcast_grp)) @@ -522,7 +518,7 @@ class vxlan(Addon, moduleBase): if group != cached_ifla_vxlan_group: if not group: - group = IPAddress("0.0.0.0") + group = ipnetwork.IPNetwork("0.0.0.0") attribute_name = "vxlan-svcnodeip/vxlan-mcastgrp" self.logger.info("%s: set %s %s" % (ifname, attribute_name, group)) @@ -699,7 +695,7 @@ class vxlan(Addon, moduleBase): if remoteips: try: for remoteip in remoteips: - IPv4Address(remoteip) + ipnetwork.IPv4Address(remoteip) except Exception as e: self.log_error('%s: vxlan-remoteip: %s' % (ifaceobj.name, str(e))) @@ -707,9 +703,13 @@ class vxlan(Addon, moduleBase): # figure out the diff for remotes and do the bridge fdb updates # only if provisioned by user and not by an vxlan external # controller. - peers = self.iproute2.get_vxlan_peers(ifaceobj.name, group) - if local and remoteips and local in remoteips: - remoteips.remove(local) + local_str = str(local) + + if local_str and remoteips and local_str in remoteips: + remoteips.remove(local_str) + + peers = self.iproute2.get_vxlan_peers(ifaceobj.name, str(group.ip) if group else None) + cur_peers = set(peers) if remoteips: new_peers = set(remoteips) @@ -783,8 +783,8 @@ class vxlan(Addon, moduleBase): ('vxlan-ttl', Link.IFLA_VXLAN_TTL, int), ('vxlan-port', Link.IFLA_VXLAN_PORT, int), ('vxlan-ageing', Link.IFLA_VXLAN_AGEING, int), - ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, IPv4Address), - ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, IPv4Address), + ('vxlan-mcastgrp', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address), + ('vxlan-svcnodeip', Link.IFLA_VXLAN_GROUP, ipnetwork.IPv4Address), ('vxlan-physdev', Link.IFLA_VXLAN_LINK, lambda x: self.cache.get_ifindex(x)), ('vxlan-learning', Link.IFLA_VXLAN_LEARNING, lambda boolean_str: utils.get_boolean_from_string(boolean_str)), ): @@ -814,7 +814,7 @@ class vxlan(Addon, moduleBase): attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') if not attrval: attrval = self._vxlan_local_tunnelip - # TODO: vxlan._vxlan_local_tunnelip should be a IPNetwork obj + # TODO: vxlan._vxlan_local_tunnelip should be a ipnetwork.IPNetwork obj ifaceobj.update_config('vxlan-local-tunnelip', attrval) if str(running_attrval) == self._clagd_vxlan_anycast_ip: @@ -826,7 +826,7 @@ class vxlan(Addon, moduleBase): ifaceobjcurr, 'vxlan-local-tunnelip', str(attrval), - str(running_attrval) + str(running_attrval.ip) if running_attrval else None ) # @@ -844,7 +844,7 @@ class vxlan(Addon, moduleBase): ifaceobjcurr, 'vxlan-remoteip', ifaceobj.get_attr_value('vxlan-remoteip'), - self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode)) + self.iproute2.get_vxlan_peers(ifaceobj.name, str(cached_svcnode.ip) if cached_svcnode else None) ) def _query_running(self, ifaceobjrunning): diff --git a/ifupdown2/addons/xfrm.py b/ifupdown2/addons/xfrm.py index 8dfb5b5..b64727f 100644 --- a/ifupdown2/addons/xfrm.py +++ b/ifupdown2/addons/xfrm.py @@ -8,8 +8,6 @@ import os import glob import socket -from ipaddr import IPNetwork, IPv6Network - try: from ifupdown2.lib.addon import Addon diff --git a/ifupdown2/ifupdown/ifupdownmain.py b/ifupdown2/ifupdown/ifupdownmain.py index 9026cf9..f8b5be4 100644 --- a/ifupdown2/ifupdown/ifupdownmain.py +++ b/ifupdown2/ifupdown/ifupdownmain.py @@ -15,13 +15,13 @@ import pprint from collections import OrderedDict -from ipaddr import IPNetwork, IPv4Network, IPv6Network, IPAddress, IPv4Address, IPv6Address - try: import ifupdown2.lib.nlcache as nlcache import ifupdown2.ifupdownaddons.mstpctlutil + import ifupdown2.nlmanager.ipnetwork as ipnetwork + import ifupdown2.ifupdown.policymanager import ifupdown2.ifupdown.statemanager as statemanager import ifupdown2.ifupdown.ifupdownflags as ifupdownflags @@ -38,6 +38,8 @@ except (ImportError, ModuleNotFoundError): import ifupdownaddons.mstpctlutil + import nlmanager.ipnetwork as ipnetwork + import ifupdown.ifupdownflags import ifupdown.policymanager import ifupdown.statemanager as statemanager @@ -886,22 +888,22 @@ class ifupdownMain: return False def _keyword_ipv4(self, value, validrange=None): - return self._keyword_check_list(value.split(), IPv4Address, limit=1) + return self._keyword_check_list(value.split(), ipnetwork.IPv4Address, limit=1) def _keyword_ipv4_prefixlen(self, value, validrange=None): - return self._keyword_check_list(value.split(), IPv4Network, limit=1) + return self._keyword_check_list(value.split(), ipnetwork.IPv4Network, limit=1) def _keyword_ipv6(self, value, validrange=None): - return self._keyword_check_list(value.split(), IPv6Address, limit=1) + return self._keyword_check_list(value.split(), ipnetwork.IPv6Address, limit=1) def _keyword_ipv6_prefixlen(self, value, validrange=None): - return self._keyword_check_list(value.split(), IPv6Network, limit=1) + return self._keyword_check_list(value.split(), ipnetwork.IPv6Network, limit=1) def _keyword_ip(self, value, validrange=None): - return self._keyword_check_list(value.split(), IPAddress, limit=1) + return self._keyword_check_list(value.split(), ipnetwork.IPAddress, limit=1) def _keyword_ip_prefixlen(self, value, validrange=None): - return self._keyword_check_list(value.split(), IPNetwork, limit=1) + return self._keyword_check_list(value.split(), ipnetwork.IPNetwork, limit=1) def _keyword_mac_ip_prefixlen_list(self, value, validrange=None): """ @@ -933,7 +935,7 @@ class ifupdownMain: for elem in elements: v = elem.split('=') int(v[0]) - IPv4Address(v[1]) + ipnetwork.IPv4Address(v[1]) return True except Exception as e: self.logger.debug('keyword: number ipv4: %s' % str(e)) @@ -953,7 +955,7 @@ class ifupdownMain: if size > 3 or size < 1: return False try: - IPv4Address(values[0]) + ipnetwork.IPv4Address(values[0]) if size > 1: if values[1] != 'vrf': return False diff --git a/ifupdown2/ifupdown/scheduler.py b/ifupdown2/ifupdown/scheduler.py index 0f89832..d0c241e 100644 --- a/ifupdown2/ifupdown/scheduler.py +++ b/ifupdown2/ifupdown/scheduler.py @@ -106,6 +106,7 @@ class ifaceScheduler(): err = 1 #import traceback #traceback.print_exc() + ifupdownobj.logger.error(str(e)) # Continue with rest of the modules pass diff --git a/ifupdown2/ifupdown/utils.py b/ifupdown2/ifupdown/utils.py index e519636..fa7965c 100644 --- a/ifupdown2/ifupdown/utils.py +++ b/ifupdown2/ifupdown/utils.py @@ -16,7 +16,6 @@ import logging import subprocess from functools import partial -from ipaddr import IPNetwork, IPAddress try: from ifupdown2.ifupdown.iface import * @@ -306,45 +305,6 @@ class utils(): else: return 'cmd \'%s\' failed: returned %d' % (cmd, cmd_returncode) - @classmethod - def get_normalized_ip_addr(cls, ifacename, ipaddrs): - if not ipaddrs: return None - if isinstance(ipaddrs, list): - addrs = [] - for ip in ipaddrs: - if not ip: - continue - try: - addrs.append(str(IPNetwork(ip)) if '/' in ip else str(IPAddress(ip))) - except Exception as e: - cls.logger.warning('%s: %s' % (ifacename, e)) - return addrs - else: - try: - return str(IPNetwork(ipaddrs)) if '/' in ipaddrs else str(IPAddress(ipaddrs)) - except Exception as e: - cls.logger.warning('%s: %s' % (ifacename, e)) - return ipaddrs - - @classmethod - def get_ip_objs(cls, module_name, ifname, addrs_list): - addrs_obj_list = [] - for a in addrs_list or []: - try: - addrs_obj_list.append(IPNetwork(a) if '/' in a else IPAddress(a)) - except Exception as e: - cls.logger.warning('%s: %s: %s' % (module_name, ifname, str(e))) - return addrs_obj_list - - @classmethod - def get_ip_obj(cls, module_name, ifname, addr): - if addr: - try: - return IPNetwork(addr) if '/' in addr else IPAddress(addr) - except Exception as e: - cls.logger.warning('%s: %s: %s' % (module_name, ifname, str(e))) - return None - @classmethod def is_addr_ip_allowed_on(cls, ifaceobj, syntax_check=False): if cls.vlan_aware_bridge_address_support is None: diff --git a/ifupdown2/lib/iproute2.py b/ifupdown2/lib/iproute2.py index 89d13c5..b2cc98d 100644 --- a/ifupdown2/lib/iproute2.py +++ b/ifupdown2/lib/iproute2.py @@ -25,14 +25,16 @@ import re import shlex import signal +import ipaddress import subprocess -from ipaddr import IPNetwork try: from ifupdown2.lib.sysfs import Sysfs from ifupdown2.lib.base_objects import Cache, Requirements + import ifupdown2.nlmanager.ipnetwork as ipnetwork + from ifupdown2.ifupdown.utils import utils from ifupdown2.ifupdown.iface import ifaceLinkPrivFlags from ifupdown2.nlmanager.nlpacket import Link @@ -40,6 +42,8 @@ except (ImportError, ModuleNotFoundError): from lib.sysfs import Sysfs from lib.base_objects import Cache, Requirements + import nlmanager.ipnetwork as ipnetwork + from ifupdown.utils import utils from ifupdown.iface import ifaceLinkPrivFlags from nlmanager.nlpacket import Link @@ -282,7 +286,7 @@ class IPRoute2(Cache, Requirements): ] if svcnodeip: - if svcnodeip.is_multicast: + if svcnodeip.ip.is_multicast: cmd.append("group %s" % svcnodeip) else: cmd.append("remote %s" % svcnodeip) @@ -325,7 +329,6 @@ class IPRoute2(Cache, Requirements): self.logger.error(str(e)) finally: utils.disable_subprocess_signal_forwarding(signal.SIGINT) - return cur_peers ### @@ -447,27 +450,24 @@ class IPRoute2(Cache, Requirements): ip6 = [] for ip in user_addrs or []: - obj = IPNetwork(ip) - - if obj.version == 6: - ip6.append(str(obj)) + if ip.version == 6: + ip6.append(ip) else: - ip4.append(str(obj)) + ip4.append(ip) running_ipobj = [] for ip in running_addrs or []: - running_ipobj.append(str(ip)) + running_ipobj.append(ip) return running_ipobj == (ip4 + ip6) def add_addresses(self, ifacobj, ifname, address_list, purge_existing=False, metric=None, with_address_virtual=False): if purge_existing: - running_address_list = self.cache.get_ifupdown2_addresses_list( - [ifacobj], + running_address_list = self.cache.get_managed_ip_addresses( ifname, + [ifacobj], with_address_virtual=with_address_virtual ) - address_list = utils.get_normalized_ip_addr(ifname, address_list) if self.__compare_user_config_vs_running_state(running_address_list, address_list): return @@ -695,7 +695,7 @@ class IPRoute2(Cache, Requirements): ip_route_del = [] for ip in ips: - ip_network_obj = IPNetwork(ip) + ip_network_obj = ipaddress.ip_network(ip) if ip_network_obj.version == 6: route_prefix = '%s/%d' % (ip_network_obj.network, ip_network_obj.prefixlen) diff --git a/ifupdown2/lib/nlcache.py b/ifupdown2/lib/nlcache.py index 26a9fa3..ceb49b0 100644 --- a/ifupdown2/lib/nlcache.py +++ b/ifupdown2/lib/nlcache.py @@ -30,10 +30,10 @@ import struct import signal import inspect import logging +import ipaddress import threading import traceback -from ipaddr import IPNetwork from logging import DEBUG, WARNING from collections import OrderedDict @@ -62,6 +62,7 @@ try: RT_SCOPES, \ INFINITY_LIFE_TIME + import ifupdown2.nlmanager.ipnetwork as ipnetwork import ifupdown2.nlmanager.nlpacket as nlpacket import ifupdown2.nlmanager.nllistener as nllistener import ifupdown2.nlmanager.nlmanager as nlmanager @@ -91,6 +92,7 @@ except (ImportError, ModuleNotFoundError): RT_SCOPES, \ INFINITY_LIFE_TIME + import nlmanager.ipnetwork as ipnetwork import nlmanager.nlpacket as nlpacket import nlmanager.nllistener as nllistener import nlmanager.nlmanager as nlmanager @@ -1551,7 +1553,8 @@ class _NetlinkCache: return label, ifindex - def __check_and_replace_address(self, address_list, new_addr): + @staticmethod + def __check_and_replace_address(address_list, new_addr): """ Check if new_addr is in address_list, if found we replace the occurrence with the new and update object "new_addr" @@ -1561,10 +1564,10 @@ class _NetlinkCache: :param new_addr: :return: """ - ip_with_prefix = new_addr.get_attribute_value(Address.IFA_ADDRESS).with_prefixlen + ip_with_prefix = new_addr.get_attribute_value(Address.IFA_ADDRESS) for index, addr in enumerate(address_list): - if addr.get_attribute_value(Address.IFA_ADDRESS).with_prefixlen == ip_with_prefix: + if addr.get_attribute_value(Address.IFA_ADDRESS) == ip_with_prefix: address_list[index] = new_addr return True @@ -1629,11 +1632,11 @@ class _NetlinkCache: for cache_addr in self._addr_cache[ifname][addr.version]: try: - if cache_addr.attributes[Address.IFA_ADDRESS].value.with_prefixlen == addr.with_prefixlen: + if cache_addr.attributes[Address.IFA_ADDRESS].value == addr: obj_to_remove = cache_addr except: try: - if cache_addr.attributes[Address.IFA_LOCAL].value.with_prefixlen == addr.with_prefixlen: + if cache_addr.attributes[Address.IFA_LOCAL].value == addr: obj_to_remove = cache_addr except: return @@ -1684,28 +1687,18 @@ class _NetlinkCache: except ValueError as e: log.debug('nlcache: remove_address: exception: %s' % e) - def get_addresses_list(self, ifname): + def get_ip_addresses(self, ifname: str) -> list: addresses = [] try: with self._cache_lock: intf_addresses = self._addr_cache[ifname] - for addr in intf_addresses.get(4, []): - addresses.append(addr.attributes[Address.IFA_ADDRESS].value) - for addr in intf_addresses.get(6, []): - addresses.append(addr.attributes[Address.IFA_ADDRESS].value) - return addresses - except (KeyError, AttributeError): - return addresses - def get_addresses_objects_list(self, ifname): - addresses = [] - try: - with self._cache_lock: - intf_addresses = self._addr_cache[ifname] for addr in intf_addresses.get(4, []): - addresses.append(addr) + addresses.append(addr.attributes[Address.IFA_ADDRESS].value) + for addr in intf_addresses.get(6, []): - addresses.append(addr) + addresses.append(addr.attributes[Address.IFA_ADDRESS].value) + return addresses except (KeyError, AttributeError): return addresses @@ -1773,143 +1766,94 @@ class _NetlinkCache: ############################################################################ ############################################################################ - def get_ifupdown2_addresses_list(self, ifaceobj_list, ifname, with_address_virtual=False): - """ - With the new live cache, we store every intf's addresses even if they - werent configured by ifupdown2. We need to filter those out to avoid - problems - - To do so we look at the previous configuration made by ifupdown2 - (with the help of the statemanager) together with the addresses - specified by the user in /etc/network/interfaces, these addresses - are then compared to the running state of the intf - """ - if not ifaceobj_list: - ifaceobj_list = [] - - config_addrs = set( - self.get_user_config_ip_addrs_with_attrs_in_ipnetwork_format( - ifaceobj_list, - with_address_virtual=with_address_virtual, - details=False - ) - ) - - for previous_state_addr in self.get_user_config_ip_addrs_with_attrs_in_ipnetwork_format( - statemanager.statemanager_api.get_ifaceobjs(ifname), - with_address_virtual=with_address_virtual, - details=False - ): - config_addrs.add(previous_state_addr) - - ifupdown2_addresses = [] - - for addr in self.get_addresses_objects_list(ifname): - ip_addr = addr.attributes[Address.IFA_ADDRESS].value - if ip_addr in config_addrs: - ifupdown2_addresses.append(ip_addr) - elif not addr.scope & Route.RT_SCOPE_LINK: - ifupdown2_addresses.append(ip_addr) - - return ifupdown2_addresses - - def get_user_config_ip_addrs_with_attrs_in_ipnetwork_format(self, ifaceobj_list, with_address_virtual=False, details=True): - """ - if details=True: - This function will return a OrderedDict of user addresses (from /e/n/i) - An OrderedDict is necessary because the addresses order is important (primary etc) - - if details=False: - Function will return an ordered list of ip4 followed by ip6 as configured in /e/n/i. - - all of the IP object were created by IPNetwork. - """ - if not ifaceobj_list: - return {} if details else [] - + @staticmethod + def get_user_configured_addresses(ifaceobj_list: list, with_address_virtual=False) -> list: ip4 = [] ip6 = [] for ifaceobj in ifaceobj_list: - addresses = ifaceobj.get_attr_value('address') + addresses = ifaceobj.get_attr_value("address") if addresses: for addr_index, addr in enumerate(addresses): - if '/' in addr: - ip_network_obj = IPNetwork(addr) + if "/" in addr: + ip_network_obj = ipnetwork.IPNetwork(addr) else: # if netmask is specified under the stanza we need to use to # create the IPNetwork objects, otherwise let IPNetwork figure # out the correct netmask for ip4 & ip6 - netmask = ifaceobj.get_attr_value_n('netmask', addr_index) - - if netmask: - ip_network_obj = IPNetwork('%s/%s' % (addr, netmask)) - else: - ip_network_obj = IPNetwork(addr) - - if not details: - # if no details=False we don't need to go further and our lists - # will only store the IPNetwork object and nothing else - if ip_network_obj.version == 6: - ip6.append(ip_network_obj) - else: - ip4.append(ip_network_obj) - continue - - addr_attributes = {} - - for attr in ['broadcast', 'pointopoint', 'scope', 'preferred-lifetime']: - attr_value = ifaceobj.get_attr_value_n(attr, addr_index) - - if attr_value: - addr_attributes[attr] = attr_value + ip_network_obj = ipnetwork.IPNetwork(addr, ifaceobj.get_attr_value_n("netmask", addr_index)) if ip_network_obj.version == 6: - ip6.append((ip_network_obj, addr_attributes)) + ip6.append(ip_network_obj) else: - ip4.append((ip_network_obj, addr_attributes)) + ip4.append(ip_network_obj) if not with_address_virtual: continue # # address-virtual and vrrp ips also needs to be accounted for # - addresses_virtual = ifaceobj.get_attr_value('address-virtual') - vrrp = ifaceobj.get_attr_value('vrrp') + addresses_virtual = ifaceobj.get_attr_value("address-virtual") + vrrp = ifaceobj.get_attr_value("vrrp") for attr_config in (addresses_virtual, vrrp): for addr_virtual_entry in attr_config or []: for addr in addr_virtual_entry.split(): try: - ip_network_obj = IPNetwork(addr) + ip_network_obj = ipnetwork.IPNetwork(addr) if ip_network_obj.version == 6: - if not details: - ip6.append(ip_network_obj) - else: - ip6.append((ip_network_obj, {})) + ip6.append(ip_network_obj) else: - if not details: - ip4.append(ip_network_obj) - else: - ip4.append((ip_network_obj, {})) + ip4.append(ip_network_obj) except: continue # always return ip4 first, followed by ip6 - if not details: - return ip4 + ip6 - else: - user_config_addresses = OrderedDict() + return ip4 + ip6 - for addr, addr_details in ip4: - user_config_addresses[addr] = addr_details + def get_managed_ip_addresses(self, ifname: str, ifaceobj_list: list, with_address_virtual: bool = False): + """ + Get all ip addresses managed by ifupdown2. As a network manager we need + to be able to detect manually added ip addresses (with iproute2 for + example). + We only addressed added by the kernel (scope: link). - for addr, addr_details in ip6: - user_config_addresses[addr] = addr_details + Args: + ifname: str + ifaceobj_list: list of ifaceobj + with_address_virtual: boolean (to include vrrp and address-virtual) + + Returns: List of ipnetwork.IPNetwork objects + """ + config_addrs = set( + self.get_user_configured_addresses( + ifaceobj_list, + with_address_virtual=with_address_virtual, + ) + ) + + previous_state_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifname) + + if previous_state_ifaceobjs: + for previous_state_addr in self.get_user_configured_addresses( + previous_state_ifaceobjs, + with_address_virtual=with_address_virtual, + ): + config_addrs.add(previous_state_addr) + + managed_addresses = [] + + for addr in self.get_ip_addresses(ifname): + if addr in config_addrs: + managed_addresses.append(addr) + + elif not addr.scope & Route.RT_SCOPE_LINK: + managed_addresses.append(addr) + + return managed_addresses - return user_config_addresses ############################################################################ ############################################################################ ############################################################################ @@ -2943,7 +2887,7 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject info_data[nlpacket.Link.IFLA_VXLAN_AGEING] = int(ageing) if group: - if group.is_multicast: + if group.ip.is_multicast: cmd.append("group %s" % group) else: cmd.append("remote %s" % group) @@ -3199,7 +3143,7 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject * errors returned from a flush request */ """ - for addr in self.cache.get_addresses_list(ifname): + for addr in self.cache.get_ip_addresses(ifname): try: self.addr_del(ifname, addr) except: diff --git a/ifupdown2/nlmanager/ipnetwork.py b/ifupdown2/nlmanager/ipnetwork.py new file mode 100644 index 0000000..feb9069 --- /dev/null +++ b/ifupdown2/nlmanager/ipnetwork.py @@ -0,0 +1,150 @@ +# Copyright (C) 2019, 2020 Cumulus Networks, Inc. all rights reserved +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# https://www.gnu.org/licenses/gpl-2.0-standalone.html +# +# Author: +# Julien Fortin, julien@cumulusnetworks.com +# + +import ipaddress + + +class IPNetwork: + + __INIT_WITH_PREFIXLEN = 0b01 + + def __init__(self, ip, prefixlen=None, scope=0): + self.__scope = scope + self.__flags = 0 + + if isinstance(ip, int): + self._ip = ipaddress.ip_address(ip) + ip = str(self._ip) + else: + if not prefixlen: + try: + ip, prefixlen = ip.split("/") + except ValueError: + prefixlen = None + + self._ip = ipaddress.ip_address(ip) + + if not prefixlen: + self.__prefixlen = 32 if self.ip.version == 4 else 128 + else: + try: + self.__prefixlen = int(prefixlen) + except ValueError: + if isinstance(prefixlen, str) and "." in prefixlen: + self.__prefixlen = ipaddress.ip_network("{}/{}".format(ip, prefixlen), strict=False).prefixlen + else: + raise + + self.__flags |= self.__INIT_WITH_PREFIXLEN + + def __hash__(self): + return int(self._ip) ^ self.__prefixlen ^ self.version + + def __eq__(self, other) -> bool: + return other \ + and self.version == other.version \ + and self._ip == other.ip \ + and self.__prefixlen == other.prefixlen + + def __repr__(self): + return "{}/{}".format(self._ip, self.__prefixlen) + + @property + def ip(self): + return self._ip + + @property + def packed(self): + return self._ip.packed + + @property + def is_multicast(self): + return self._ip.is_multicast + + @property + def prefixlen(self) -> int: + return self.__prefixlen + + @property + def version(self) -> int: + return self._ip.version + + @property + def scope(self) -> int: + return self.__scope + + @property + def initialized_with_prefixlen(self) -> int: + return self.__flags & self.__INIT_WITH_PREFIXLEN + + def ignore_prefixlen(self): + self.__prefixlen = 32 if self.ip.version == 4 else 128 + + +class IPv4Network(IPNetwork): + def __init__(self, *args, **kwargs): + super(IPv4Network, self).__init__(*args, **kwargs) + + if self.version != 4: + self._ip = ipaddress.IPv4Address(self._ip) + + +class IPv6Network(IPNetwork): + def __init__(self, *args, **kwargs): + super(IPv6Network, self).__init__(*args, **kwargs) + + if self.version != 6: + self._ip = ipaddress.IPv6Address(self._ip) + + +class IPAddress(IPNetwork): + def __init__(self, ip, prefixlen=None, *args, **kwargs): + + if isinstance(ip, int): + raise NotImplementedError + + if prefixlen is not None: + self.__raise_exception("{}/{}".format(ip, prefixlen)) + elif "/" in ip: + self.__raise_exception(ip) + + super(IPAddress, self).__init__(ip, prefixlen, *args, **kwargs) + self.ignore_prefixlen() + + def __repr__(self): + return self._ip + + def __raise_exception(self, ip): + raise ValueError( + "'%s' does not appear to be an IPv4 or IPv6 address" + % ip + ) + + +class IPv4Address(IPv4Network): + def __init__(self, *args, **kwargs): + super(IPv4Address, self).__init__(*args, **kwargs) + self.ignore_prefixlen() + + def __repr__(self): + return str(self._ip) + diff --git a/ifupdown2/nlmanager/nlmanager.py b/ifupdown2/nlmanager/nlmanager.py index be11e44..e9d3d2e 100644 --- a/ifupdown2/nlmanager/nlmanager.py +++ b/ifupdown2/nlmanager/nlmanager.py @@ -26,7 +26,6 @@ # from collections import OrderedDict -from ipaddr import IPv4Address, IPv6Address from .nlpacket import * from select import select from struct import pack, unpack @@ -371,14 +370,12 @@ class NetlinkManager(object): data = data[length:] def ip_to_afi(self, ip): - type_ip = type(ip) - - if type_ip == IPv4Address: + if ip.version == 4: return socket.AF_INET - elif type_ip == IPv6Address: + elif ip.version == 6: return socket.AF_INET6 else: - raise Exception("%s is an invalid IP type" % type_ip) + raise Exception("%s is an invalid IP type" % type(ip)) def request_dump(self, rtm_type, family, debug): """ @@ -498,9 +495,7 @@ class NetlinkManager(object): def route_get(self, ip, debug=False): """ - ip must be one of the following: - - IPv4Address - - IPv6Address + ip must be ipnetwork.IPNetwork """ # Transmit a RTM_GETROUTE to query for the route we want route = Route(RTM_GETROUTE, debug, use_color=self.use_color) diff --git a/ifupdown2/nlmanager/nlpacket.py b/ifupdown2/nlmanager/nlpacket.py index c2c4933..8897338 100644 --- a/ifupdown2/nlmanager/nlpacket.py +++ b/ifupdown2/nlmanager/nlpacket.py @@ -31,7 +31,6 @@ import socket import logging import struct -from ipaddr import IPNetwork, IPv4Address, IPv6Address, IPAddress from binascii import hexlify from pprint import pformat from socket import AF_UNSPEC, AF_INET, AF_INET6, AF_BRIDGE, htons @@ -39,6 +38,9 @@ from string import printable from struct import pack, unpack, calcsize +from . import ipnetwork + + log = logging.getLogger(__name__) SYSLOG_EXTRA_DEBUG = 5 @@ -1036,12 +1038,12 @@ class Attribute(object): @staticmethod def decode_ipv4_address_attribute(data, _=None): - return IPv4Address(unpack(">L", data[4:8])[0]) + return ipnetwork.IPNetwork(unpack(">L", data[4:8])[0]) @staticmethod def decode_ipv6_address_attribute(data, _=None): (data1, data2) = unpack(">QQ", data[4:20]) - return IPv6Address(data1 << 64 | data2) + return ipnetwork.IPNetwork(data1 << 64 | data2) @staticmethod def decode_bond_ad_info_attribute(data, info_data_end): @@ -1131,9 +1133,7 @@ class Attribute(object): sub_attr_pack_layout.append("BBBB") if info_data_value: - sub_attr_payload.extend(socket.inet_aton(str(info_data_value))) - # force concert to string (in case of IPv4Address but really this - # should already be in the integer format to save some cycles + sub_attr_payload.extend(info_data_value.packed) else: sub_attr_payload.extend([0, 0, 0, 0]) @@ -1331,8 +1331,6 @@ class AttributeIPAddress(Attribute): def __init__(self, atype, string, family, logger): Attribute.__init__(self, atype, string, logger) - self.value_int = None - self.value_int_str = None self.family = family if self.family == AF_INET: @@ -1349,41 +1347,37 @@ class AttributeIPAddress(Attribute): self.LEN = calcsize(self.PACK) - def set_value(self, value): - if value is None: - self.value = None - else: - self.value = IPNetwork(value) - def decode(self, parent_msg, data): self.decode_length_type(data) try: - if self.family in (AF_INET, AF_BRIDGE): - self.value = IPv4Address(unpack(self.PACK, self.data[4:])[0]) - - elif self.family == AF_INET6: - (data1, data2) = unpack(self.PACK, self.data[4:]) - self.value = IPv6Address(data1 << 64 | data2) + try: + prefixlen = parent_msg.prefixlen + except AttributeError: + prefixlen = None + try: + scope = parent_msg.scope + except AttributeError: + scope = 0 if isinstance(parent_msg, Route): if self.atype == Route.RTA_SRC: - self.value = IPNetwork('%s/%s' % (self.value, parent_msg.src_len)) + self.value = ipnetwork.IPNetwork(self.value, parent_msg.src_len) elif self.atype == Route.RTA_DST: - self.value = IPNetwork('%s/%s' % (self.value, parent_msg.dst_len)) + self.value = ipnetwork.IPNetwork(self.value, parent_msg.dst_len) else: - try: - self.value = IPNetwork('%s/%s' % (self.value, parent_msg.prefixlen)) - except AttributeError: - self.value = IPNetwork('%s' % self.value) + if self.family in (AF_INET, AF_BRIDGE): + self.value = ipnetwork.IPNetwork(unpack(self.PACK, self.data[4:])[0], prefixlen, scope) - self.value_int = int(self.value) - self.value_int_str = str(self.value_int) + elif self.family == AF_INET6: + (data1, data2) = unpack(self.PACK, self.data[4:]) + self.value = ipnetwork.IPNetwork(data1 << 64 | data2, prefixlen, scope) + + else: + self.log.debug("AttributeIPAddress: decode: unsupported address family ({})".format(self.family)) except struct.error: self.value = None - self.value_int = None - self.value_int_str = None self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) raise @@ -1435,9 +1429,8 @@ class AttributeMACAddress(Attribute): try: # GRE interface uses a 4-byte IP address for this attribute if self.length == 8: - self.value = IPv4Address(unpack('>L', self.data[4:])[0]) - self.value_int = int(self.value) - self.value_int_str = str(self.value_int) + self.value = ipnetwork.IPNetwork(unpack('>L', self.data[4:])[0]) + # MAC Address elif self.length == 10: (data1, data2) = unpack(self.PACK, self.data[4:]) @@ -1445,9 +1438,8 @@ class AttributeMACAddress(Attribute): self.value = mac_int_to_str(self.raw) # GREv6 interface uses a 16-byte IP address for this attribute elif self.length == 20: - self.value = IPv6Address(unpack('>L', self.data[16:])[0]) - self.value_int = int(self.value) - self.value_int_str = str(self.value_int) + self.value = ipnetwork.IPNetwork(unpack('>L', self.data[16:])[0]) + else: raise Exception("Length of MACAddress attribute not supported: %d" % self.length) @@ -1476,8 +1468,6 @@ class AttributeMplsLabel(Attribute): def __init__(self, atype, string, family, logger): Attribute.__init__(self, atype, string, logger) - self.value_int = None - self.value_int_str = None self.family = family self.PACK = '>HBB' @@ -1490,13 +1480,9 @@ class AttributeMplsLabel(Attribute): self.traffic_class = ((label_low_tc_s & 0xf) >> 1) self.label = (label_high << 4) | (label_low_tc_s >> 4) self.value = self.label - self.value_int = self.value - self.value_int_str = str(self.value_int) except struct.error: self.value = None - self.value_int = None - self.value_int_str = None self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) raise @@ -1948,14 +1934,14 @@ struct rtnexthop { if self.family == AF_INET: if len(data) < self.IPV4_LEN: break - nexthop = IPv4Address(unpack('>L', data[:self.IPV4_LEN])[0]) + nexthop = ipnetwork.IPNetwork(unpack('>L', data[:self.IPV4_LEN])[0]) self.value.append((nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops)) elif self.family == AF_INET6: if len(data) < self.IPV6_LEN: break (data1, data2) = unpack('>QQ', data[:self.IPV6_LEN]) - nexthop = IPv6Address(data1 << 64 | data2) + nexthop = ipnetwork.IPNetwork(data1 << 64 | data2) self.value.append((nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops)) data = data[(rtnh_len-self.RTNH_LEN-self.HEADER_LEN):] @@ -4470,10 +4456,10 @@ class AttributeMDBA_MDB(Attribute): info = [ifindex,state,flags,vid] proto = unpack('=H',sub_attr_data[28:30])[0] if proto == htons(ETH_P_IP): - ip_addr = IPv4Address(unpack('>L', sub_attr_data[12:16])[0]) + ip_addr = ipnetwork.IPNetwork(unpack('>L', sub_attr_data[12:16])[0]) else: (data1, data2) = unpack('>QQ',sub_attr_data[12:28]) - ip_addr = IPv6Address(data1 << 64 | data2) + ip_addr = ipnetwork.IPNetwork(data1 << 64 | data2) info.append(ip_addr) @@ -4508,10 +4494,10 @@ class AttributeMDBA_MDB(Attribute): info = list(info) proto = unpack('=H',sub_attr_data[28:30])[0] if proto == 8: - ip_addr = IPv4Address(unpack('>L', sub_attr_data[12:16])[0]) + ip_addr = ipnetwork.IPNetwork(unpack('>L', sub_attr_data[12:16])[0]) else: (data1, data2) = unpack('>QQ',sub_attr_data[12:28]) - ip_addr = IPv6Address(data1 << 64 | data2) + ip_addr = ipnetwork.IPNetwork(data1 << 64 | data2) info.append(ip_addr) self.value[MDB.MDBA_MDB_ENTRY][MDB.MDBA_MDB_ENTRY_INFO] = info @@ -4616,7 +4602,7 @@ class AttributeMDBA_SET_ENTRY(Attribute): if proto == htons(ETH_P_IP): self.PACK = '=IBBHLxxxxxxxxxxxxHxx' reorder = unpack('