diff --git a/addons/address.py b/addons/address.py index 661eae0..74d4301 100644 --- a/addons/address.py +++ b/addons/address.py @@ -17,6 +17,7 @@ try: from ifupdown.netlink import netlink import ifupdown.ifupdownconfig as ifupdownConfig import ifupdown.ifupdownflags as ifupdownflags + import ifupdown.statemanager as statemanager except ImportError, e: raise ImportError (str(e) + "- required module not found") @@ -28,18 +29,17 @@ class address(moduleBase): 'attrs': { 'address' : {'help' : 'ipv4 or ipv6 addresses', - 'validvals' : [IPv4Network, IPv6Network], + 'validvals' : ['', ''], 'multiline' : True, 'example' : ['address 10.0.12.3/24', 'address 2000:1000:1000:1000:3::5/128']}, 'netmask' : {'help': 'netmask', - 'validvals' : [IPv4Address, ], 'example' : ['netmask 255.255.255.0'], 'compat' : True}, 'broadcast' : {'help': 'broadcast address', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ], 'example' : ['broadcast 10.0.1.255']}, 'scope' : {'help': 'scope', @@ -52,7 +52,7 @@ class address(moduleBase): 'preferred-lifetime 10']}, 'gateway' : {'help': 'default gateway', - 'validvals' : [IPv4Address, IPv6Address], + 'validvals' : ['', ''], 'example' : ['gateway 255.255.255.0']}, 'mtu' : { 'help': 'interface mtu', @@ -79,7 +79,7 @@ class address(moduleBase): 'clagd-vxlan-anycast-ip' : { 'help' : 'Anycast local IP address for ' + 'dual connected VxLANs', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ], 'example' : ['clagd-vxlan-anycast-ip 36.0.0.11']}}} def __init__(self, *args, **kargs): @@ -260,6 +260,30 @@ class address(moduleBase): return self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs) + def _add_delete_gateway(self, ifaceobj, gateways=[], prev_gw=[]): + vrf = ifaceobj.get_attr_value_first('vrf') + metric = ifaceobj.get_attr_value_first('metric') + for del_gw in list(set(prev_gw) - set(gateways)): + try: + self.ipcmd.route_del_gateway(ifaceobj.name, del_gw, vrf, metric) + except: + pass + for add_gw in list(set(gateways) - set(prev_gw)): + try: + self.ipcmd.route_add_gateway(ifaceobj.name, add_gw, vrf) + except: + pass + + def _get_prev_gateway(self, ifaceobj, gateways): + ipv = [] + saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifaceobj.name) + if not saved_ifaceobjs: + return ipv + prev_gateways = saved_ifaceobjs[0].get_attr_value('gateway') + if not prev_gateways: + return ipv + return prev_gateways + def _up(self, ifaceobj, ifaceobj_getfunc=None): if not self.ipcmd.link_exists(ifaceobj.name): return @@ -290,7 +314,7 @@ class address(moduleBase): mtu = ifaceobj.get_attr_value_first('mtu') if mtu: self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) - elif (not ifaceobj.link_kind and + elif (not (ifaceobj.name == 'lo') and not ifaceobj.link_kind and not (ifaceobj.link_privflags & ifaceLinkPrivFlags.BOND_SLAVE) and self.default_mtu): # logical devices like bridges and vlan devices rely on mtu @@ -308,7 +332,11 @@ class address(moduleBase): alias = ifaceobj.get_attr_value_first('alias') if alias: self.ipcmd.link_set_alias(ifaceobj.name, alias) - self.ipcmd.batch_commit() + try: + self.ipcmd.batch_commit() + except Exception as e: + self.logger.error('%s: %s' % (ifaceobj.name, str(e))) + ifaceobj.set_status(ifaceStatus.ERROR) hwaddress = self._get_hwaddress(ifaceobj) if hwaddress: @@ -340,9 +368,12 @@ class address(moduleBase): pass if addr_method != "dhcp": - self.ipcmd.route_add_gateway(ifaceobj.name, - ifaceobj.get_attr_value_first('gateway'), - ifaceobj.get_attr_value_first('vrf')) + gateways = ifaceobj.get_attr_value('gateway') + if not gateways: + gateways = [] + prev_gw = self._get_prev_gateway(ifaceobj, gateways) + self._add_delete_gateway(ifaceobj, gateways, prev_gw) + return def _down(self, ifaceobj, ifaceobj_getfunc=None): try: @@ -350,10 +381,6 @@ class address(moduleBase): return addr_method = ifaceobj.addr_method if addr_method != "dhcp": - self.ipcmd.route_del_gateway(ifaceobj.name, - ifaceobj.get_attr_value_first('gateway'), - ifaceobj.get_attr_value_first('vrf'), - ifaceobj.get_attr_value_first('metric')) if ifaceobj.get_attr_value_first('address-purge')=='no': addrlist = ifaceobj.get_attr_value('address') for addr in addrlist: diff --git a/addons/addressvirtual.py b/addons/addressvirtual.py index 73f2d4c..dd5f339 100644 --- a/addons/addressvirtual.py +++ b/addons/addressvirtual.py @@ -25,8 +25,8 @@ class addressvirtual(moduleBase): 'every mac ip address-virtual line', 'attrs' : { 'address-virtual' : - { 'help' : 'bridge router virtual mac and ip', - 'validvals' : [('', IPv4Network), ], + { 'help' : 'bridge router virtual mac and ips', + 'validvals' : ['',], 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.1/24 11.0.1.2/24']} }} @@ -125,6 +125,11 @@ class addressvirtual(moduleBase): %str(e)) pass + def _handle_vrf_slaves(self, macvlan_ifacename, ifaceobj): + vrfname = self.ipcmd.link_get_master(ifaceobj.name) + if vrfname: + self.ipcmd.link_set(macvlan_ifacename, 'master', vrfname) + def _get_macs_from_old_config(self, ifaceobj=None): """ This method returns a list of the mac addresses in the address-virtual attribute for the bridge. """ @@ -196,6 +201,16 @@ class addressvirtual(moduleBase): if lower_iface_mtu and lower_iface_mtu != self.ipcmd.link_get_mtu(macvlan_ifacename): self.ipcmd.link_set_mtu(macvlan_ifacename, lower_iface_mtu) + # handle vrf slaves + if (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE): + self._handle_vrf_slaves(macvlan_ifacename, ifaceobj) + + # Disable IPv6 duplicate address detection on VRR interfaces + for key, sysval in { 'accept_dad' : '0', 'dad_transmits' : '0' }.iteritems(): + syskey = 'net.ipv6.conf.%s.%s' % (macvlan_ifacename, key) + if self.sysctl_get(syskey) != sysval: + self.sysctl_set(syskey, sysval) + av_idx += 1 self.ipcmd.batch_commit() @@ -277,7 +292,8 @@ class addressvirtual(moduleBase): self._remove_address_config(ifaceobj, address_virtual_list) return - if ifaceobj.upperifaces: + if (ifaceobj.upperifaces and + not ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE): self.log_error('%s: invalid placement of address-virtual lines (must be configured under an interface with no upper interfaces or parent interfaces)' % (ifaceobj.name), ifaceobj) return diff --git a/addons/bond.py b/addons/bond.py index 35fa52f..5fb3a45 100644 --- a/addons/bond.py +++ b/addons/bond.py @@ -80,12 +80,14 @@ class bond(moduleBase): 'bond-ad-sys-mac-addr': {'help' : '802.3ad system mac address', 'default' : '00:00:00:00:00:00', + 'validvals': ['', ], 'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00'], 'deprecated' : True, 'new-attribute' : 'bond-ad-actor-system'}, 'bond-ad-actor-system': {'help' : '802.3ad system mac address', 'default' : '00:00:00:00:00:00', + 'validvals': ['', ], 'example' : ['bond-ad-actor-system 00:00:00:00:00:00'],}, 'bond-lacp-bypass-allow': {'help' : 'allow lacp bypass', @@ -96,6 +98,7 @@ class bond(moduleBase): {'help' : 'bond slaves', 'required' : True, 'multivalue' : True, + 'validvals': [''], 'example' : ['bond-slaves swp1 swp2', 'bond-slaves glob swp1-2', 'bond-slaves regex (swp[1|2)']}}} @@ -188,20 +191,6 @@ class bond(moduleBase): ifname=ifaceobj.name, attr=attrname) if attrval: - msg = ('%s: invalid value %s for attr %s.' - %(ifaceobj.name, attrval, attrname)) - optiondict = self.get_mod_attr(attrname) - if not optiondict: - return None - validvals = optiondict.get('validvals') - if validvals and attrval not in validvals: - raise Exception(msg + ' Valid values are %s' %str(validvals)) - validrange = optiondict.get('validrange') - if validrange: - if (int(attrval) < int(validrange[0]) or - int(attrval) > int(validrange[1])): - raise Exception(msg + ' Valid range is [%s,%s]' - %(validrange[0], validrange[1])) if attrname == 'bond-mode': attrval = bond._get_readable_bond_mode(attrval) if attrval == '802.3ad': diff --git a/addons/bridge.py b/addons/bridge.py index 803051f..fe51ef3 100644 --- a/addons/bridge.py +++ b/addons/bridge.py @@ -42,6 +42,7 @@ class bridge(moduleBase): {'help' : 'bridge ports', 'multivalue' : True, 'required' : True, + 'validvals': [''], 'example' : ['bridge-ports swp1.100 swp2.100 swp3.100', 'bridge-ports glob swp1-3.100', 'bridge-ports regex (swp[1|2|3].100)']}, @@ -85,12 +86,14 @@ class bridge(moduleBase): 'default' : '20'}, 'bridge-pathcosts' : { 'help' : 'bridge set port path costs', + 'validvals': [''], 'validrange' : ['0', '65535'], 'example' : ['under the bridge: bridge-pathcosts swp1=100 swp2=100', 'under the port (recommended): bridge-pathcosts 100'], 'default' : '100'}, 'bridge-portprios' : { 'help' : 'bridge port prios', + 'validvals': [''], 'validrange' : ['0', '65535'], 'example' : ['under the bridge: bridge-portprios swp1=32 swp2=32', 'under the port (recommended): bridge-portprios 32'], @@ -173,12 +176,13 @@ class bridge(moduleBase): 'example' : ['bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1']}, 'bridge-portmcrouter' : { 'help' : 'set port multicast routers', - 'validvals' : ['yes', 'no', '0', '1'], + 'validvals' : [''], 'default' : 'yes', 'example' : ['under the bridge: bridge-portmcrouter swp1=yes swp2=yes', 'under the port (recommended): bridge-portmcrouter yes']}, 'bridge-portmcfl' : { 'help' : 'port multicast fast leave.', + 'validvals': [''], 'validrange' : ['0', '65535'], 'default' : '0', 'example' : ['under the bridge: bridge-portmcfl swp1=0 swp2=0', @@ -193,6 +197,7 @@ class bridge(moduleBase): 'regex or \"all\" on bridge_ports,' + 'as it wouldnt work.', 'default' : '0', + 'validvals': [''], 'example' : ['bridge-waitport 4 swp1 swp2']}, 'bridge-maxwait' : { 'help' : 'forces to time seconds the maximum time ' + @@ -210,6 +215,7 @@ class bridge(moduleBase): 'If specified under the bridge the ports ' + 'inherit it unless overridden by a ' + 'bridge-vids attribute under the port', + 'validvals': [''], 'example' : ['bridge-vids 4000', 'bridge-vids 2000 2200-3000']}, 'bridge-pvid' : @@ -491,20 +497,14 @@ class bridge(moduleBase): result.append(a) return result - def _diff_vids(self, vids1, vids2): - vids_to_add = None - vids_to_del = None + def _compress_into_ranges(self, vids_ints): + return ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids_ints)] + + def _diff_vids(self, vids1_ints, vids2_ints): + vids_to_add = Set(vids1_ints).difference(vids2_ints) + vids_to_del = Set(vids2_ints).difference(vids1_ints) - vids1_ints = self._ranges_to_ints(vids1) - vids2_ints = self._ranges_to_ints(vids2) - vids1_diff = Set(vids1_ints).difference(vids2_ints) - vids2_diff = Set(vids2_ints).difference(vids1_ints) - if vids1_diff: - vids_to_add = ['%d' %start if start == end else '%d-%d' %(start, end) - for start, end in self._ints_to_ranges(vids1_diff)] - if vids2_diff: - vids_to_del = ['%d' %start if start == end else '%d-%d' %(start, end) - for start, end in self._ints_to_ranges(vids2_diff)] return (vids_to_del, vids_to_add) def _compare_vids(self, vids1, vids2, pvid=None): @@ -513,8 +513,8 @@ class bridge(moduleBase): vids1_ints = self._ranges_to_ints(vids1) vids2_ints = self._ranges_to_ints(vids2) set_diff = Set(vids1_ints).symmetric_difference(vids2_ints) - if pvid: - set_diff = set_diff.remove(pvid) + if pvid and int(pvid) in set_diff: + set_diff.remove(int(pvid)) if set_diff: return False else: @@ -548,7 +548,7 @@ class bridge(moduleBase): # CM-8161. Removed check for PERFMODE. Need the get in all cases # including reboot, so that we can configure the pvid correctly. - self._running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + self._running_vidinfo = self.ipcmd.bridge_port_vids_get_all_json() self._running_vidinfo_valid = True return self._running_vidinfo @@ -567,8 +567,6 @@ class bridge(moduleBase): return # Handle bridge vlan attrs - running_vidinfo = self._get_running_vidinfo() - # Install pvids if bridge_port_pvids: portlist = self.parse_port_list(ifaceobj.name, bridge_port_pvids) @@ -580,7 +578,8 @@ class bridge(moduleBase): for p in portlist: try: (port, pvid) = p.split('=') - running_pvid = running_vidinfo.get(port, {}).get('pvid') + pvid = int(pvid) + running_pvid = self._get_running_pvid(port) if running_pvid: if running_pvid == pvid: continue @@ -602,42 +601,23 @@ class bridge(moduleBase): try: (port, val) = p.split('=') vids = val.split(',') - if running_vidinfo.get(port): + vids_int = self._ranges_to_ints(vids) + running_vids = self._get_running_vids(port) + if running_vids: (vids_to_del, vids_to_add) = \ - self._diff_vids(vids, - running_vidinfo.get(port).get('vlan')) + self._diff_vids(vids_int, running_vids) if vids_to_del: - self.ipcmd.bridge_port_vids_del(port, vids_to_del) + self.ipcmd.bridge_port_vids_del(port, + self._compress_into_ranges(vids_to_del)) if vids_to_add: - self.ipcmd.bridge_port_vids_add(port, vids_to_add) + self.ipcmd.bridge_port_vids_add(port, + self._compress_into_ranges(vids_to_add)) else: - self.ipcmd.bridge_port_vids_add(port, vids) + self.ipcmd.bridge_port_vids_add(port, vids_int) except Exception, e: self.log_warn('%s: failed to set vid `%s` (%s)' %(ifaceobj.name, p, str(e))) - # install vids - # XXX: Commenting out this code for now because it was decided - # that this is not needed - #attrval = ifaceobj.get_attr_value_first('bridge-vids') - #if attrval: - # vids = re.split(r'[\s\t]\s*', attrval) - # if running_vidinfo.get(ifaceobj.name): - # (vids_to_del, vids_to_add) = \ - # self._diff_vids(vids, - # running_vidinfo.get(ifaceobj.name).get('vlan')) - # if vids_to_del: - # self.ipcmd.bridge_vids_del(ifaceobj.name, vids_to_del) - # if vids_to_add: - # self.ipcmd.bridge_vids_add(ifaceobj.name, vids_to_add) - # else: - # self.ipcmd.bridge_vids_add(ifaceobj.name, vids) - #else: - # running_vids = running_vidinfo.get(ifaceobj.name) - # if running_vids: - # self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids) - - def _is_running_stp_state_on(self, bridgename): """ Returns True if running stp state is on, else False """ @@ -784,26 +764,6 @@ class bridge(moduleBase): ret = False return ret - def _apply_bridge_vids(self, bportifaceobj, vids, running_vids, isbridge): - try: - if not self._check_vids(bportifaceobj, vids): - return - if running_vids: - (vids_to_del, vids_to_add) = \ - self._diff_vids(vids, running_vids) - if vids_to_del: - self.ipcmd.bridge_vids_del(bportifaceobj.name, - vids_to_del, isbridge) - if vids_to_add: - self.ipcmd.bridge_vids_add(bportifaceobj.name, - vids_to_add, isbridge) - else: - self.ipcmd.bridge_vids_add(bportifaceobj.name, vids, isbridge) - except Exception, e: - self.log_error('%s: failed to set vid `%s` (%s)' - %(bportifaceobj.name, str(vids), str(e)), - bportifaceobj) - def _apply_bridge_port_pvids(self, bportifaceobj, pvid, running_pvid): # Install pvids try: @@ -818,8 +778,88 @@ class bridge(moduleBase): self.log_error('%s: failed to set pvid `%s` (%s)' %(bportifaceobj.name, pvid, str(e)), bportifaceobj) - def _apply_bridge_vids_and_pvid(self, bportifaceobj, vids, running_vids, - pvid, running_pvid, isbridge): + def _get_running_pvid(self, ifacename): + pvid = 0 + + running_vidinfo = self._get_running_vidinfo() + for vinfo in running_vidinfo.get(ifacename, {}): + v = vinfo.get('vlan') + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + if pvid: + return pvid + return pvid + + def _get_running_vids(self, ifacename): + vids = [] + + running_vidinfo = self._get_running_vidinfo() + for vinfo in running_vidinfo.get(ifacename, {}): + v = vinfo.get('vlan') + ispvid = True if 'PVID' in vinfo.get('flags', []) else False + if ispvid: + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + if pvid == 1: + continue + vEnd = vinfo.get('vlanEnd') + if vEnd: + vids.extend(range(v, vEnd + 1)) + else: + vids.append(v) + return vids + + def _get_running_vids_n_pvid(self, ifacename): + vids = [] + pvid = 0 + + running_vidinfo = self._get_running_vidinfo() + for vinfo in running_vidinfo.get(ifacename, {}): + v = vinfo.get('vlan') + ispvid = True if 'PVID' in vinfo.get('flags', []) else False + if ispvid: + pvid = v if 'PVID' in vinfo.get('flags', []) else 0 + vEnd = vinfo.get('vlanEnd') + if vEnd: + vids.extend(range(v, vEnd + 1)) + else: + vids.append(v) + return (vids, pvid) + + def _get_running_vids_n_pvid_str(self, ifacename): + vids = [] + pvid = None + + (vids, pvid) = self._get_running_vids_n_pvid(ifacename) + + if vids: + ret_vids = self._compress_into_ranges(vids) + else: + ret_vids = None + + if pvid: + ret_pvid = '%s' %pvid + else: + ret_pvid = None + return (ret_vids, ret_pvid) + + def _get_running_vids_n_pvid_str2(self, ifacename): + vids = [] + pvid = None + + + running_vidinfo = self._get_running_vidinfo() + for vinfo in running_vidinfo.get(ifacename, {}): + v = vinfo.get('vlan') + if not pvid: + pvid = '%s' %v if 'PVID' in vinfo.get('flags', []) else None + vEnd = vinfo.get('vlanEnd') + if vEnd: + vids.append('%s-%s' %(v, vEnd)) + else: + vids.append('%s' %v) + return (vids, pvid) + + def _apply_bridge_vids_and_pvid(self, bportifaceobj, vids, pvid, + isbridge): """ This method is a combination of methods _apply_bridge_vids and _apply_bridge_port_pvids above. A combined function is found necessary to do the deletes first and the adds later @@ -827,24 +867,42 @@ class bridge(moduleBase): """ + vids_int = self._ranges_to_ints(vids) + pvid_int = int(pvid) if pvid else 0 + + vids_to_del = [] + vids_to_add = vids_int + pvid_to_del = None + pvid_to_add = pvid_int + try: if not self._check_vids(bportifaceobj, vids): return - vids_to_del = [] - vids_to_add = vids - pvid_to_del = None - pvid_to_add = pvid + (running_vids, running_pvid) = self._get_running_vids_n_pvid( + bportifaceobj.name) + + if not running_vids and not running_pvid: + # There cannot be a no running pvid. + # It might just not be in our cache: + # this can happen if at the time we were + # creating the bridge vlan cache, the port + # was not part of the bridge. And we need + # to make sure both vids and pvid is not in + # the cache, to declare that our cache may + # be stale. + running_pvid = 1 + running_vids = [1] if running_vids: (vids_to_del, vids_to_add) = \ - self._diff_vids(vids, running_vids) + self._diff_vids(vids_to_add, running_vids) if running_pvid: - if running_pvid != pvid and running_pvid != '0': + if running_pvid != pvid_int and running_pvid != 0: pvid_to_del = running_pvid - if (pvid_to_del and (pvid_to_del in vids) and + if (pvid_to_del and (pvid_to_del in vids_int) and (pvid_to_del not in vids_to_add)): # kernel deletes dont take into account # bridge vid flags and its possible that @@ -859,7 +917,7 @@ class bridge(moduleBase): # - new change is going to move the state to # pvid 101 # vid 100 102 - vids_to_add.append(pvid_to_del) + vids_to_add.add(pvid_to_del) except Exception, e: self.log_error('%s: failed to process vids/pvids' %bportifaceobj.name + ' vids = %s' %str(vids) + @@ -867,8 +925,11 @@ class bridge(moduleBase): bportifaceobj, raise_error=False) try: if vids_to_del: + if pvid_to_add in vids_to_del: + vids_to_del.remove(pvid_to_add) self.ipcmd.bridge_vids_del(bportifaceobj.name, - vids_to_del, isbridge) + self._compress_into_ranges( + vids_to_del), isbridge) except Exception, e: self.log_warn('%s: failed to del vid `%s` (%s)' %(bportifaceobj.name, str(vids_to_del), str(e))) @@ -884,15 +945,17 @@ class bridge(moduleBase): try: if vids_to_add: self.ipcmd.bridge_vids_add(bportifaceobj.name, - vids_to_add, isbridge) + self._compress_into_ranges( + vids_to_add), isbridge) except Exception, e: self.log_error('%s: failed to set vid `%s` (%s)' - %(bportifaceobj.name, str(vids_to_add), str(e)), - bportifaceobj, raise_error=False) + %(bportifaceobj.name, str(vids_to_add), + str(e)), bportifaceobj, raise_error=False) try: - if pvid_to_add: - self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid_to_add) + if pvid_to_add and pvid_to_add != running_pvid: + self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, + pvid_to_add) except Exception, e: self.log_error('%s: failed to set pvid `%s` (%s)' %(bportifaceobj.name, pvid_to_add, str(e)), @@ -901,7 +964,6 @@ class bridge(moduleBase): def _apply_bridge_vlan_aware_port_settings_all(self, bportifaceobj, bridge_vids=None, bridge_pvid=None): - running_vidinfo = self._get_running_vidinfo() vids = None pvids = None vids_final = [] @@ -938,10 +1000,7 @@ class bridge(moduleBase): pvid_final = None self._apply_bridge_vids_and_pvid(bportifaceobj, vids_final, - running_vidinfo.get(bportifaceobj.name, {}).get('vlan'), - pvid_final, - running_vidinfo.get(bportifaceobj.name, {}).get('pvid'), - False) + pvid_final, False) def _apply_bridge_port_settings(self, bportifaceobj, bridgename=None, bridgeifaceobj=None): @@ -971,9 +1030,9 @@ class bridge(moduleBase): self.log_error(str(e), bportifaceobj) def _apply_bridge_port_settings_all(self, ifaceobj, - ifaceobj_getfunc=None): + ifaceobj_getfunc=None, + bridge_vlan_aware=False): err = False - bridge_vlan_aware = ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes' if (ifaceobj.get_attr_value_first('bridge-port-vids') and ifaceobj.get_attr_value_first('bridge-port-pvids')): @@ -1005,6 +1064,7 @@ class bridge(moduleBase): if not bridgeports: self.logger.debug('%s: cannot find bridgeports' %ifaceobj.name) return + self.ipcmd.batch_start() for bport in bridgeports: # Use the brctlcmd bulk set method: first build a dictionary # and then call set @@ -1037,6 +1097,7 @@ class bridge(moduleBase): err = True self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) pass + self.ipcmd.bridge_batch_commit() if err: raise Exception('%s: errors applying port settings' %ifaceobj.name) @@ -1097,14 +1158,21 @@ class bridge(moduleBase): raise Exception(str(e)) try: + bridge_vlan_aware = False if ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes': + bridge_vlan_aware = True if (bridge_just_created or not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)): self.ipcmd.link_set(ifaceobj.name, 'vlan_filtering', '1', False, "bridge") if not bridge_just_created: ifaceobj.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | bridgeFlags.PORT_PROCESSED_OVERRIDE - + elif (not bridge_just_created and + ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'no' + and self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)): + self.ipcmd.link_set(ifaceobj.name, 'vlan_filtering', '0', + False, "bridge") + bridge_vlan_aware = False except Exception, e: raise Exception(str(e)) @@ -1129,12 +1197,12 @@ class bridge(moduleBase): # disable ipv6 for ports that were added to bridge self.handle_ipv6(running_ports, '1', ifaceobj=ifaceobj) self._apply_bridge_port_settings_all(ifaceobj, - ifaceobj_getfunc=ifaceobj_getfunc) + ifaceobj_getfunc=ifaceobj_getfunc, + bridge_vlan_aware=bridge_vlan_aware) except Exception, e: err = True errstr = str(e) pass - #self._flush_running_vidinfo() finally: if ifaceobj.link_type != ifaceLinkType.LINK_NA: for p in running_ports: @@ -1165,12 +1233,11 @@ class bridge(moduleBase): def _query_running_vidinfo_compat(self, ifaceobjrunning, ports): running_attrs = {} - running_vidinfo = self._get_running_vidinfo() if ports: running_bridge_port_vids = '' for p in ports: try: - running_vids = running_vidinfo.get(p, {}).get('vlan') + running_vids = self._get_runing_vids(p) if running_vids: running_bridge_port_vids += ' %s=%s' %(p, ','.join(running_vids)) @@ -1178,41 +1245,36 @@ class bridge(moduleBase): pass running_attrs['bridge-port-vids'] = running_bridge_port_vids - running_bridge_port_pvids = '' + running_bridge_port_pvid = '' for p in ports: try: - running_pvids = running_vidinfo.get(p, {}).get('pvid') - if running_pvids: - running_bridge_port_pvids += ' %s=%s' %(p, - running_pvids) + running_pvid = self._get_runing_pvid(p) + if running_pvid: + running_bridge_port_pvid += ' %s=%s' %(p, + running_pvid) except Exception: pass - running_attrs['bridge-port-pvids'] = running_bridge_port_pvids + running_attrs['bridge-port-pvids'] = running_bridge_port_pvid - running_bridge_vids = running_vidinfo.get(ifaceobjrunning.name, - {}).get('vlan') + running_bridge_vids = self._get_running_vids(ifaceobjrunning.name) if running_bridge_vids: - running_attrs['bridge-vids'] = ','.join(running_bridge_vids) + running_attrs['bridge-vids'] = ','.join(self._compress_into_ranges(running_bridge_vids)) return running_attrs def _query_running_vidinfo(self, ifaceobjrunning, ifaceobj_getfunc, bridgeports=None): running_attrs = {} - running_vidinfo = self._get_running_vidinfo() - if not running_vidinfo: - return running_attrs # 'bridge-vids' under the bridge is all about 'vids' on the port. # so query the ports running_bridgeport_vids = [] running_bridgeport_pvids = [] for bport in bridgeports: - vids = running_vidinfo.get(bport, {}).get('vlan') + (vids, pvid) = self._get_running_vids_n_pvid_str(bport) if vids: running_bridgeport_vids.append(' '.join(vids)) - pvids = running_vidinfo.get(bport, {}).get('pvid') - if pvids: - running_bridgeport_pvids.append(pvids) + if pvid: + running_bridgeport_pvids.append(pvid) bridge_vids = None if running_bridgeport_vids: @@ -1226,26 +1288,29 @@ class bridge(moduleBase): (vidval, freq) = Counter(running_bridgeport_pvids).most_common()[0] if freq == len(bridgeports) and vidval != '1': running_attrs['bridge-pvid'] = vidval - bridge_pvid = vidval.split() + bridge_pvid = vidval.split()[0] # Go through all bridge ports and find their vids for bport in bridgeports: bportifaceobj = ifaceobj_getfunc(bport) if not bportifaceobj: continue - bport_vids = None - bport_pvids = None - vids = running_vidinfo.get(bport, {}).get('vlan') + bport_vids = [] + bport_pvid = None + (vids, pvid) = self._get_running_vids_n_pvid_str(bport) if vids and vids != bridge_vids: bport_vids = vids - pvids = running_vidinfo.get(bport, {}).get('pvid') - if pvids and pvids[0] != bridge_pvid: - bport_pvids = pvids - if not bport_vids and bport_pvids and bport_pvids[0] != '1': - bportifaceobj[0].replace_config('bridge-access', bport_pvids[0]) + if pvid and pvid != bridge_pvid: + bport_pvid = pvid + if bport_vids and bport_pvid in bport_vids: + bport_vids.remove(bport_pvid) + if (not bport_vids and bport_pvid and bport_pvid != '1'): + bportifaceobj[0].replace_config('bridge-access', bport_pvid) + bportifaceobj[0].delete_config('bridge-pvid') + bportifaceobj[0].delete_config('bridge-vids') else: - if bport_pvids and bport_pvids[0] != '1': - bportifaceobj[0].replace_config('bridge-pvid', bport_pvids[0]) + if bport_pvid and bport_pvid != '1': + bportifaceobj[0].replace_config('bridge-pvid', bport_pvid) else: # delete any stale bridge-vids under ports bportifaceobj[0].delete_config('bridge-pvid') @@ -1291,6 +1356,7 @@ class bridge(moduleBase): if stp == 'yes' and userspace_stp: skip_kernel_stp_attrs = 1 + bool2str = {'0': 'no', '1': 'yes'} # pick all other attributes for k,v in tmpbridgeattrdict.items(): if not v: @@ -1302,7 +1368,11 @@ class bridge(moduleBase): # only include igmp attributes if kernel stp is off continue attrname = 'bridge-' + k - if v != self.get_mod_subattr(attrname, 'default'): + mod_default = self.get_mod_subattr(attrname, 'default') + if v != mod_default: + # convert '0|1' running values to 'no|yes' + if v in bool2str.keys() and bool2str[v] == mod_default: + continue bridgeattrdict[attrname] = [v] if bridge_vlan_aware: @@ -1323,7 +1393,8 @@ class bridge(moduleBase): if skip_kernel_stp_attrs: return bridgeattrdict - if ports: + # Do this only for vlan-UNAWARE-bridge + if ports and not bridge_vlan_aware: portconfig = {'bridge-pathcosts' : '', 'bridge-portprios' : ''} for p, v in ports.items(): @@ -1354,7 +1425,6 @@ class bridge(moduleBase): def _query_check_bridge_vidinfo(self, ifaceobj, ifaceobjcurr): err = 0 - running_vidinfo = self._get_running_vidinfo() attrval = ifaceobj.get_attr_value_first('bridge-port-vids') if attrval: running_bridge_port_vids = '' @@ -1368,7 +1438,7 @@ class bridge(moduleBase): try: (port, val) = p.split('=') vids = val.split(',') - running_vids = running_vidinfo.get(port, {}).get('vlan') + running_vids = self._get_running_vids(port) if running_vids: if not self._compare_vids(vids, running_vids): err += 1 @@ -1400,7 +1470,7 @@ class bridge(moduleBase): for p in portlist: try: (port, pvid) = p.split('=') - running_pvid = running_vidinfo.get(port, {}).get('pvid') + running_pvid = self._get_running_vids(port) if running_pvid and running_pvid == pvid: running_bridge_port_pvids += ' %s' %p else: @@ -1417,24 +1487,7 @@ class bridge(moduleBase): ifaceobjcurr.update_config_with_status('bridge-port-pvids', running_bridge_port_pvids, 0) - # XXX: No need to check for bridge-vids on the bridge - # This is used by the ports. The vids on the bridge - # come from the vlan interfaces on the bridge. - # attrval = ifaceobj.get_attr_value_first('bridge-vids') - #if attrval: - # vids = re.split(r'[\s\t]\s*', attrval) - # running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan') - # if running_vids: - # if self._compare_vids(vids, running_vids): - # ifaceobjcurr.update_config_with_status('bridge-vids', - # attrval, 0) - # else: - # ifaceobjcurr.update_config_with_status('bridge-vids', - # ','.join(running_vids), 1) - # else: - # ifaceobjcurr.update_config_with_status('bridge-vids', attrval, - # 1) if attrval: ifaceobjcurr.update_config_with_status('bridge-vids', attrval, -1) @@ -1588,57 +1641,67 @@ class bridge(moduleBase): def _query_check_bridge_port_vidinfo(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc, bridgename): - running_vidinfo = self._get_running_vidinfo() - attr_name = 'bridge-access' - vids = ifaceobj.get_attr_value_first(attr_name) - if vids: - running_pvids = running_vidinfo.get(ifaceobj.name, - {}).get('pvid') - running_vids = running_vidinfo.get(ifaceobj.name, - {}).get('vlan') - if (not running_pvids or running_pvids != vids or - running_vids): + vid = ifaceobj.get_attr_value_first(attr_name) + if vid: + (running_vids, running_pvid) = self._get_running_vids_n_pvid_str( + ifaceobj.name) + if (not running_pvid or running_pvid != vid or + (running_vids and running_vids[0] != vid)): ifaceobjcurr.update_config_with_status(attr_name, - running_pvids, 1) - else: - ifaceobjcurr.update_config_with_status(attr_name, vids, 0) - return + running_pvid, 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, vid, 0) + return - running_pvid = running_vidinfo.get(ifaceobj.name, - {}).get('pvid') + (running_vids, running_pvid) = self._get_running_vids_n_pvid_str( + ifaceobj.name) attr_name = 'bridge-pvid' - pvid = self._get_bridge_pvid(bridgename, ifaceobj_getfunc) + pvid = ifaceobj.get_attr_value_first('bridge-pvid') if pvid: - if running_pvid and running_pvid == pvid: - ifaceobjcurr.update_config_with_status(attr_name, - running_pvid, 0) - else: - ifaceobjcurr.update_config_with_status(attr_name, - running_pvid, 1) - elif not running_pvid or running_pvid != '1': - ifaceobjcurr.status = ifaceStatus.ERROR - ifaceobjcurr.status_str = 'bridge pvid error' + if running_pvid and running_pvid == pvid: + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 0) + else: + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 1) + elif (not (ifaceobj.flags & iface.HAS_SIBLINGS) or + ((ifaceobj.flags & iface.HAS_SIBLINGS) and + (ifaceobj.flags & iface.OLDEST_SIBLING))): + # if the interface has multiple iface sections, + # we check the below only for the oldest sibling + # or the last iface section + pvid = self._get_bridge_pvid(bridgename, ifaceobj_getfunc) + if pvid: + if not running_pvid or running_pvid != pvid: + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge pvid error' + elif not running_pvid or running_pvid != '1': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge pvid error' attr_name = 'bridge-vids' vids = ifaceobj.get_attr_value_first(attr_name) if vids: vids = re.split(r'[\s\t]\s*', vids) - running_vids = running_vidinfo.get(ifaceobj.name, - {}).get('vlan') - if not running_vids or not self._compare_vids(vids, running_vids): + if not running_vids or not self._compare_vids(vids, running_vids, + running_pvid): ifaceobjcurr.update_config_with_status(attr_name, - ' '.join(running_vids), 1) + ' '.join(running_vids), 1) else: ifaceobjcurr.update_config_with_status(attr_name, - ' '.join(running_vids), 0) - else: + ' '.join(vids), 0) + elif (not (ifaceobj.flags & iface.HAS_SIBLINGS) or + ((ifaceobj.flags & iface.HAS_SIBLINGS) and + (ifaceobj.flags & iface.OLDEST_SIBLING))): + # if the interface has multiple iface sections, + # we check the below only for the oldest sibling + # or the last iface section + # check if it matches the bridge vids bridge_vids = self._get_bridge_vids(bridgename, ifaceobj_getfunc) - running_vids = running_vidinfo.get(ifaceobj.name, - {}).get('vlan') if (bridge_vids and (not running_vids or - not self._compare_vids(bridge_vids, running_vids, pvid))): + not self._compare_vids(bridge_vids, running_vids, running_pvid))): ifaceobjcurr.status = ifaceStatus.ERROR ifaceobjcurr.status_str = 'bridge vid error' @@ -1663,9 +1726,9 @@ class bridge(moduleBase): ifaceobj_getfunc, bridgename) for attr, dstattr in {'bridge-pathcosts' : 'pathcost', - 'bridge-portprios' : 'priority', - 'bridge-portmcrouter' : 'mcrouter', - 'bridge-portmcfl' : 'mcfl' }.items(): + 'bridge-portprios' : 'portprio', + 'bridge-portmcrouter' : 'portmcrouter', + 'bridge-portmcfl' : 'portmcfl' }.items(): attrval = ifaceobj.get_attr_value_first(attr) if not attrval: continue @@ -1674,7 +1737,7 @@ class bridge(moduleBase): running_attrval = self.brctlcmd.get_bridgeport_attr( bridgename, ifaceobj.name, dstattr) - if dstattr == 'mcrouter': + if dstattr == 'portmcrouter': if not utils.is_binary_bool(attrval) and running_attrval: running_attrval = utils.get_yesno_boolean( utils.get_boolean_from_string(running_attrval)) @@ -1720,6 +1783,7 @@ class bridge(moduleBase): def _query_running_bridge_port(self, ifaceobjrunning, ifaceobj_getfunc=None): + bridgename = self.ipcmd.bridge_port_get_bridge_name( ifaceobjrunning.name) bridge_vids = None @@ -1731,11 +1795,8 @@ class bridge(moduleBase): if not self.ipcmd.bridge_is_vlan_aware(bridgename): return - running_vidinfo = self._get_running_vidinfo() - bridge_port_vids = running_vidinfo.get(ifaceobjrunning.name, - {}).get('vlan') - bridge_port_pvid = running_vidinfo.get(ifaceobjrunning.name, - {}).get('pvid') + (bridge_port_vids, bridge_port_pvid) = self._get_running_vids_n_pvid_str( + ifaceobjrunning.name) bridgeifaceobjlist = ifaceobj_getfunc(bridgename) if bridgeifaceobjlist: @@ -1834,7 +1895,7 @@ class bridge(moduleBase): if not op_handler: return self._init_command_handlers() - self._flush_running_vidinfo() + #self._flush_running_vidinfo() if operation == 'query-checkcurr': op_handler(self, ifaceobj, query_ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/addons/bridgevlan.py b/addons/bridgevlan.py index 80245cc..ef3cd02 100644 --- a/addons/bridgevlan.py +++ b/addons/bridgevlan.py @@ -25,7 +25,7 @@ class bridgevlan(moduleBase): 'bridge-igmp-querier-src' : { 'help' : 'bridge igmp querier src. Must be ' + 'specified under the vlan interface', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ], 'example' : ['bridge-igmp-querier-src 172.16.101.1']}}} def __init__(self, *args, **kargs): diff --git a/addons/dhcp.py b/addons/dhcp.py index 62dbe78..22c5d88 100644 --- a/addons/dhcp.py +++ b/addons/dhcp.py @@ -13,6 +13,9 @@ try: from ifupdownaddons.dhclient import dhclient from ifupdownaddons.iproute2 import iproute2 import ifupdown.ifupdownflags as ifupdownflags + from ifupdown.utils import utils + import time + from ifupdown.netlink import netlink except ImportError, e: raise ImportError (str(e) + "- required module not found") @@ -65,6 +68,9 @@ class dhcp(moduleBase): self.dhclientcmd.stop6(ifaceobj.name) except: pass + #add delay before starting IPv6 dhclient to + #make sure the configured interface/link is up. + time.sleep(2) self.dhclientcmd.start6(ifaceobj.name, wait=wait, cmd_prefix=dhclient_cmd_prefix) except Exception, e: @@ -76,7 +82,10 @@ class dhcp(moduleBase): if (vrf and self.vrf_exec_cmd_prefix and self.ipcmd.link_exists(vrf)): dhclient_cmd_prefix = '%s %s' %(self.vrf_exec_cmd_prefix, vrf) - self.dhclientcmd.release(ifaceobj.name, dhclient_cmd_prefix) + if ifaceobj.addr_family == 'inet6': + self.dhclientcmd.release6(ifaceobj.name, dhclient_cmd_prefix) + else: + self.dhclientcmd.release(ifaceobj.name, dhclient_cmd_prefix) self.ipcmd.link_down(ifaceobj.name) def _query_check(self, ifaceobj, ifaceobjcurr): @@ -108,6 +117,7 @@ class dhcp(moduleBase): _run_ops = {'up' : _up, 'down' : _down, + 'pre-down' : _down, 'query-checkcurr' : _query_check, 'query-running' : _query_running } diff --git a/addons/ethtool.py b/addons/ethtool.py index 7ee564d..736d4ee 100644 --- a/addons/ethtool.py +++ b/addons/ethtool.py @@ -97,11 +97,12 @@ class ethtool(moduleBase,utilsBase): (ifaceobj.name in self.ifaceobjs_modified_configs)): continue - # if we are not the oldest and we have no configs, do not change anything - # the only way a non-oldest sibling would change values is if it - # had configured settings - if (not ((ifaceobj.flags & iface.HAS_SIBLINGS) and - (ifaceobj.flags & iface.OLDEST_SIBLING)) and + # If we have siblings AND are not the oldest AND we have no configs, + # do not change anything. The only way a non-oldest sibling would + # change values is if it had configured settings. iface stanzas may + # not be squashed if addr_config_squash is not set so we still need this. + if ((ifaceobj.flags & iface.HAS_SIBLINGS) and + not (ifaceobj.flags & iface.OLDEST_SIBLING) and not config_val): continue @@ -242,6 +243,10 @@ class ethtool(moduleBase,utilsBase): # to see the defaults, we should implement another flag (--with-defaults) if default_val == running_attr: continue + + # do not proceed if speed = 0 + if attr == 'speed' and running_attr and running_attr == '0': + return if running_attr: ifaceobj.update_config('link-%s'%attr, running_attr) @@ -249,8 +254,6 @@ class ethtool(moduleBase,utilsBase): def _query(self, ifaceobj, **kwargs): """ add default policy attributes supported by the module """ - if ifaceobj.link_kind: - return for attr in ['speed', 'duplex', 'autoneg']: if ifaceobj.get_attr_value_first('link-%s'%attr): continue @@ -293,6 +296,8 @@ class ethtool(moduleBase,utilsBase): of interfaces. status is success if the running state is same as user required state in ifaceobj. error otherwise. """ + if ifaceobj.link_kind: + return op_handler = self._run_ops.get(operation) if not op_handler: return diff --git a/addons/mstpctl.py b/addons/mstpctl.py index 442c63f..8cafb20 100644 --- a/addons/mstpctl.py +++ b/addons/mstpctl.py @@ -30,7 +30,7 @@ class mstpctl(moduleBase): 'new-attribute': 'bridge-ports'}, 'mstpctl-stp' : {'help': 'bridge stp yes/no', - 'validvals' : ['yes', 'no'], + 'validvals' : ['yes', 'no', 'on', 'off'], 'compat' : True, 'default' : 'no', 'deprecated': True, @@ -79,6 +79,7 @@ class mstpctl(moduleBase): 'example' : ['mstpctl-forcevers rstp']}, 'mstpctl-portpathcost' : { 'help' : 'bridge port path cost', + 'validvals': [''], 'validrange' : ['0', '65535'], 'default' : '0', 'jsonAttr' : 'adminExtPortCost', @@ -89,7 +90,7 @@ class mstpctl(moduleBase): { 'help' : 'bridge port p2p detection mode', 'default' : 'auto', 'jsonAttr' : 'adminPointToPoint', - 'validvals' : ['yes', 'no', 'auto'], + 'validvals' : [''], 'required' : False, 'example' : ['under the bridge: mstpctl-portp2p swp1=yes swp2=no', 'under the port (recommended): mstpctl-portp2p yes']}, @@ -98,7 +99,7 @@ class mstpctl(moduleBase): 'enable/disable port ability to take root role of the port', 'default' : 'no', 'jsonAttr' : 'restrictedRole', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'required' : False, 'example' : ['under the bridge: mstpctl-portrestrrole swp1=yes swp2=no', 'under the port (recommended): mstpctl-portrestrrole yes']}, @@ -107,7 +108,7 @@ class mstpctl(moduleBase): 'enable/disable port ability to propagate received topology change notification of the port', 'default' : 'no', 'jsonAttr' : 'restrictedTcn', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'required' : False, 'example' : ['under the bridge: mstpctl-portrestrtcn swp1=yes swp2=no', 'under the port (recommended): mstpctl-portrestrtcn yes']}, @@ -116,7 +117,7 @@ class mstpctl(moduleBase): 'enable/disable bpduguard', 'default' : 'no', 'jsonAttr' : 'bpduGuardPort', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'required' : False, 'example' : ['under the bridge: mstpctl-bpduguard swp1=yes swp2=no', 'under the port (recommended): mstpctl-bpduguard yes']}, @@ -124,6 +125,7 @@ class mstpctl(moduleBase): { 'help' : 'port priority for MSTI instance', 'default' : '128', + 'validvals': [''], 'validrange' : ['0', '240'], 'required' : False, 'example' : ['under the bridge: mstpctl-treeportprio swp1=128 swp2=128', @@ -136,7 +138,7 @@ class mstpctl(moduleBase): 'example' : ['mstpctl-hello 2']}, 'mstpctl-portnetwork' : { 'help' : 'enable/disable bridge assurance capability for a port', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'default' : 'no', 'jsonAttr' : 'networkPort', 'required' : False, @@ -144,7 +146,7 @@ class mstpctl(moduleBase): 'under the port (recommended): mstpctl-portnetwork yes']}, 'mstpctl-portadminedge' : { 'help' : 'enable/disable initial edge state of the port', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'default' : 'no', 'jsonAttr' : 'adminEdgePort', 'required' : False, @@ -152,7 +154,7 @@ class mstpctl(moduleBase): 'under the port (recommended): mstpctl-portadminedge yes']}, 'mstpctl-portautoedge' : { 'help' : 'enable/disable auto transition to/from edge state of the port', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'default' : 'yes', 'jsonAttr' : 'autoEdgePort', 'required' : False, @@ -166,7 +168,7 @@ class mstpctl(moduleBase): { 'help' : 'enable/disable bpdu filter on a port. ' + 'syntax varies when defined under a bridge ' + 'vs under a port', - 'validvals' : ['yes', 'no'], + 'validvals' : [''], 'jsonAttr' : 'bpduFilterPort', 'default' : 'no', 'required' : False, @@ -312,6 +314,8 @@ class mstpctl(moduleBase): self.logger.warn('%s' %str(e)) pass + if self.ipcmd.bridge_is_vlan_aware(ifaceobj.name): + return # set bridge port attributes for attrname, dstattrname in self._port_attrs_map.items(): config_val = ifaceobj.get_attr_value_first(attrname) @@ -560,7 +564,7 @@ class mstpctl(moduleBase): except Exception, e: self.log_error(str(e), ifaceobj) - def _query_running_attrs(self, ifaceobjrunning): + def _query_running_attrs(self, ifaceobjrunning, bridge_vlan_aware=False): bridgeattrdict = {} tmpbridgeattrdict = self.mstpctlcmd.get_bridge_attrs(ifaceobjrunning.name) @@ -574,12 +578,16 @@ class mstpctl(moduleBase): ports = v.keys() continue attrname = 'mstpctl-' + k - if v and v != self.get_mod_subattr(attrname, 'default'): + if (v and v != self.get_mod_subattr(attrname, 'default') + and attrname != 'mstpctl-maxhops'): bridgeattrdict[attrname] = [v] ports = self.brctlcmd.get_bridge_ports(ifaceobjrunning.name) - if ports: - portconfig = {'mstpctl-portnetwork' : '', + # Do this only for vlan-UNAWARE-bridge + if ports and not bridge_vlan_aware: + portconfig = {'mstpctl-portautoedge' : '', + 'mstpctl-portbpdufilter' : '', + 'mstpctl-portnetwork' : '', 'mstpctl-portpathcost' : '', 'mstpctl-portadminedge' : '', 'mstpctl-portautoedge' : '', @@ -591,6 +599,16 @@ class mstpctl(moduleBase): 'mstpctl-treeportcost' : ''} for p in ports: + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portautoedge') + if v and v != 'no': + portconfig['mstpctl-portautoedge'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portbpdufilter') + if v and v != 'no': + portconfig['mstpctl-portbpdufilter'] += ' %s=%s' %(p, v) + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, p, 'portnetwork') if v and v != 'no': @@ -634,11 +652,17 @@ class mstpctl(moduleBase): # 'default'): # portconfig['mstpctl-treeportprio'] += ' %s=%s' %(p, v) - #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, - # p, 'treecost') - #if v and v != self.get_mod_subattr('mstpctl-treeportcost', - # 'default'): - # portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v) + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portpathcost') + if v and v != self.get_mod_subattr('mstpctl-portpathcost', + 'default'): + portconfig['mstpctl-portpathcost'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'treeportcost') + if v and v != self.get_mod_subattr('mstpctl-treeportcost', + 'default'): + portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v) bridgeattrdict.update({k : [v] for k, v in portconfig.items() if v}) @@ -893,11 +917,25 @@ class mstpctl(moduleBase): # if userspace stp not set, return if self.sysctl_get('net.bridge.bridge-stp-user-space') != '1': return + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'portautoedge') + if v and v != self.get_mod_subattr('mstpctl-portautoedge', + 'default'): + ifaceobjrunning.update_config('mstpctl-portautoedge', v) + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'portbpdufilter') + if v and v != 'no': + ifaceobjrunning.update_config('mstpctl-portbpdufilter', v) + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, ifaceobjrunning.name, 'portnetwork') if v and v != 'no': - ifaceobjrunning.update_config('mstpctl-network', v) + ifaceobjrunning.update_config('mstpctl-portnetwork', v) # XXX: Can we really get path cost of a port ??? #v = self.mstpctlcmd.get_portpathcost(ifaceobjrunning.name, p) @@ -921,7 +959,7 @@ class mstpctl(moduleBase): ifaceobjrunning.update_config('mstpctl-portrestrrole', v) v = self.mstpctlcmd.get_bridgeport_attr(bridgename, - ifaceobjrunning.name, 'restrtcn') + ifaceobjrunning.name, 'portrestrtcn') if v and v != 'no': ifaceobjrunning.update_config('mstpctl-portrestrtcn', v) @@ -953,8 +991,12 @@ class mstpctl(moduleBase): # Check if mstp really knows about this bridge if not self.mstpctlcmd.mstpbridge_exists(ifaceobjrunning.name): return + bridge_vlan_aware = False + if ifaceobjrunning.get_attr_value_first('bridge-vlan-aware') == 'yes': + bridge_vlan_aware = True ifaceobjrunning.update_config_dict(self._query_running_attrs( - ifaceobjrunning)) + ifaceobjrunning, + bridge_vlan_aware)) def _query_running(self, ifaceobjrunning, **extra_args): if self.brctlcmd.bridge_exists(ifaceobjrunning.name): diff --git a/addons/vlan.py b/addons/vlan.py index ec2e2e5..fff1d07 100644 --- a/addons/vlan.py +++ b/addons/vlan.py @@ -25,7 +25,7 @@ class vlan(moduleBase): 'attrs' : { 'vlan-raw-device' : {'help' : 'vlan raw device', - 'validvals' : ['' ,]}, + 'validvals': ['']}, 'vlan-id' : {'help' : 'vlan id', 'validrange' : ['0', '4096']}}} @@ -176,22 +176,24 @@ class vlan(moduleBase): else: ifaceobjcurr.update_config_with_status('vlan-raw-device', vlanrawdev, 0) - if vlanid != ifaceobj.get_attr_value_first('vlan-id'): + vlanid_config = ifaceobj.get_attr_value_first('vlan-id') + if not vlanid_config: + vlanid_config = str(self._get_vlan_id(ifaceobj)) + if vlanid != vlanid_config: ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 1) else: - ifaceobjcurr.update_config_with_status('vlan-id', - vlanid, 0) + ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 0) self._bridge_vid_check(ifaceobj, ifaceobjcurr, vlanrawdev, vlanid) def _query_running(self, ifaceobjrunning): if not self.ipcmd.link_exists(ifaceobjrunning.name): return - if not self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name): + (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name) + if not vlanid: return # If vlan name is not in the dot format, get the # vlan dev and vlan id if not '.' in ifaceobjrunning.name: - (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name) ifaceobjrunning.update_config_dict({(k, v) for k, v in {'vlan-raw-device' : vlanrawdev, 'vlan-id' : vlanid}.items() diff --git a/addons/vrf.py b/addons/vrf.py index 36b222e..f353ed5 100644 --- a/addons/vrf.py +++ b/addons/vrf.py @@ -9,6 +9,8 @@ import signal import errno import fcntl import atexit +import re +from sets import Set from ifupdown.iface import * from ifupdown.utils import utils import ifupdown.policymanager as policymanager @@ -33,9 +35,11 @@ class vrf(moduleBase): 'creating a vrf device. ' + 'Table id is either \'auto\' or '+ '\'valid routing table id\'', + 'validvals': ['auto', ''], 'example': ['vrf-table auto', 'vrf-table 1001']}, 'vrf': {'help' : 'vrf the interface is part of.', + 'validvals': [''], 'example': ['vrf blue']}}} iproute2_vrf_filename = '/etc/iproute2/rt_tables.d/ifupdown2_vrf_map.conf' @@ -54,9 +58,18 @@ class vrf(moduleBase): self.bondcmd = None self.dhclientcmd = None self.name = self.__class__.__name__ - if ifupdownflags.flags.PERFMODE: - # if perf mode is set, remove vrf map file. - # start afresh. PERFMODE is set at boot + self.vrf_mgmt_devname = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-mgmt-devname') + + if (ifupdownflags.flags.PERFMODE and + not (self.vrf_mgmt_devname and os.path.exists('/sys/class/net/%s' + %self.vrf_mgmt_devname))): + # if perf mode is set (PERFMODE is set at boot), and this is the first + # time we are calling ifup at boot (check for mgmt vrf existance at + # boot, make sure this is really the first invocation at boot. + # ifup is called with PERFMODE at boot multiple times (once for mgmt vrf + # and the second time with all auto interfaces). We want to delete + # the map file only the first time. This is to avoid accidently + # deleting map file with a valid mgmt vrf entry if os.path.exists(self.iproute2_vrf_filename): try: self.logger.info('vrf: removing file %s' @@ -85,6 +98,15 @@ class vrf(moduleBase): #self.logger.info("vrf: ip -6 rule cache") #self.logger.info(self.ip6_rule_cache) + self.l3mdev_checked = False + self.l3mdev4_rule = False + if self._l3mdev_rule(self.ip_rule_cache): + self.l3mdev4_rule = True + self.l3mdev_checked = True + self.l3mdev6_rule = False + if self._l3mdev_rule(self.ip6_rule_cache): + self.l3mdev6_rule = True + self.l3mdev_checked = True self._iproute2_vrf_map_initialized = False self.iproute2_vrf_map = {} self.iproute2_vrf_map_fd = None @@ -100,9 +122,10 @@ class vrf(moduleBase): self.vrf_fix_local_table = True self.vrf_count = 0 - self.vrf_mgmt_devname = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-mgmt-devname') self.vrf_helper = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-helper') + self.warn_on_vrf_map_write_err = True + def _iproute2_vrf_map_initialize(self, writetodisk=True): if self._iproute2_vrf_map_initialized: return @@ -168,18 +191,35 @@ class vrf(moduleBase): self._iproute2_vrf_map_initialized = True self.vrf_count = len(self.iproute2_vrf_map) + def _iproute2_map_warn(self, errstr): + if self.warn_on_vrf_map_write_err: + if not os.path.exists('/etc/iproute2/rt_tables.d/'): + self.logger.info('unable to save iproute2 vrf to table ' + + 'map (%s)\n' %errstr) + self.logger.info('cannot find /etc/iproute2/rt_tables.d.' + + ' pls check if your iproute2 version' + + ' supports rt_tables.d') + else: + self.logger.warn('unable to open iproute2 vrf to table ' + + 'map (%s)\n' %errstr) + self.warn_on_vrf_map_write_err = False + def _iproute2_vrf_map_sync_to_disk(self): if (ifupdownflags.flags.DRYRUN or not self.iproute2_vrf_map_sync_to_disk): return self.logger.info('vrf: syncing table map to %s' %self.iproute2_vrf_filename) - with open(self.iproute2_vrf_filename, 'w') as f: - f.write(self.iproute2_vrf_filehdr %(self.vrf_table_id_start, - self.vrf_table_id_end)) - for t, v in self.iproute2_vrf_map.iteritems(): - f.write('%s %s\n' %(t, v)) - f.flush() + try: + with open(self.iproute2_vrf_filename, 'w') as f: + f.write(self.iproute2_vrf_filehdr %(self.vrf_table_id_start, + self.vrf_table_id_end)) + for t, v in self.iproute2_vrf_map.iteritems(): + f.write('%s %s\n' %(t, v)) + f.flush() + except Exception, e: + self._iproute2_map_warn(str(e)) + pass def _iproute2_vrf_map_open(self, sync_vrfs=False, append=False): self.logger.info('vrf: syncing table map to %s' @@ -192,8 +232,7 @@ class vrf(moduleBase): '%s' %fmode) fcntl.fcntl(self.iproute2_vrf_map_fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) except Exception, e: - self.log_warn('vrf: error opening %s (%s)' - %(self.iproute2_vrf_filename, str(e))) + self._iproute2_map_warn(str(e)) return if not append: @@ -294,7 +333,7 @@ class vrf(moduleBase): return True def _up_vrf_slave_without_master(self, ifacename, vrfname, ifaceobj, - ifaceobj_getfunc): + vrf_master_objs): """ If we have a vrf slave that has dhcp configured, bring up the vrf master now. This is needed because vrf has special handling in dhclient hook which requires the vrf master to be present """ @@ -307,10 +346,6 @@ class vrf(moduleBase): self.logger.info('%s: vrf master %s exists returning' %(ifacename, vrf_master)) return - vrf_master_objs = ifaceobj_getfunc(vrf_master) - if not vrf_master_objs: - self.logger.warn('%s: vrf master ifaceobj not found' %ifacename) - return self.logger.info('%s: bringing up vrf master %s' %(ifacename, vrf_master)) for mobj in vrf_master_objs: @@ -362,14 +397,31 @@ class vrf(moduleBase): if not upper or upper != vrfname: self._handle_existing_connections(ifaceobj, vrfname) self.ipcmd.link_set(ifacename, 'master', vrfname) - elif ifupdownflags.flags.ALL and ifaceobj: - self._up_vrf_slave_without_master(ifacename, vrfname, ifaceobj, - ifaceobj_getfunc) + elif ifaceobj: + vrf_master_objs = ifaceobj_getfunc(vrfname) + if not vrf_master_objs: + # this is the case where vrf is assigned to an interface + # but user has not provided a vrf interface. + # people expect you to warn them but go ahead with the + # rest of the config on that interface + netlink.link_set_updown(ifacename, "up") + self.log_error('vrf master ifaceobj %s not found' + %vrfname) + return + if (ifupdownflags.flags.ALL or + (ifupdownflags.flags.CLASS and + ifaceobj.classes and vrf_master_objs[0].classes and + Set(ifaceobj.classes).intersection(vrf_master_objs[0].classes))): + self._up_vrf_slave_without_master(ifacename, vrfname, + ifaceobj, + vrf_master_objs) + else: + master_exists = False else: master_exists = False if master_exists: netlink.link_set_updown(ifacename, "up") - elif ifupdownflags.flags.ALL: + else: self.log_error('vrf %s not around, skipping vrf config' %(vrfname), ifaceobj) except Exception, e: @@ -404,6 +456,22 @@ class vrf(moduleBase): vrf_dev_name) utils.exec_command(rule_cmd) + def _l3mdev_rule(self, ip_rules): + for rule in ip_rules: + if not re.search(r"\d.*from\s+all\s+lookup\s+\W?l3mdev-table\W?", + rule): + continue + return True + return False + + def _rule_cache_fill(self): + ip_rules = utils.exec_command('/sbin/ip rule show').splitlines() + self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules] + self.l3mdev4_rule = self._l3mdev_rule(self.ip_rule_cache) + ip_rules = utils.exec_command('/sbin/ip -6 rule show').splitlines() + self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules] + self.l3mdev6_rule = self._l3mdev_rule(self.ip6_rule_cache) + def _add_vrf_rules(self, vrf_dev_name, vrf_table): pref = 200 ip_rule_out_format = '%s: from all %s %s lookup %s' @@ -426,34 +494,54 @@ class vrf(moduleBase): self.logger.info('%s: %s' % (vrf_dev_name, str(e))) pass + if not self.l3mdev_checked: + self._rule_cache_fill() + self.l3mdev_checked = True #Example ip rule #200: from all oif blue lookup blue #200: from all iif blue lookup blue rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) - if rule not in self.ip_rule_cache: + if not self.l3mdev4_rule and rule not in self.ip_rule_cache: rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) - if rule not in self.ip_rule_cache: + if not self.l3mdev4_rule and rule not in self.ip_rule_cache: rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) - if rule not in self.ip6_rule_cache: + if not self.l3mdev6_rule and rule not in self.ip6_rule_cache: rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) - if rule not in self.ip6_rule_cache: + if not self.l3mdev6_rule and rule not in self.ip6_rule_cache: rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, vrf_dev_name) utils.exec_command(rule_cmd) + def _is_address_virtual_slaves(self, vrfobj, config_vrfslaves, + vrfslave): + # Address virtual lines on a vrf slave will create + # macvlan devices on the vrf slave and enslave them + # to the vrf master. This function checks if the + # vrf slave is such a macvlan interface. + # XXX: additional possible checks that can be done here + # are: + # - check if it is also a macvlan device of the + # format -v created by the + # address virtual module + vrfslave_lowers = self.ipcmd.link_get_lowers(vrfslave) + if vrfslave_lowers: + if vrfslave_lowers[0] in config_vrfslaves: + return True + return False + def _add_vrf_slaves(self, ifaceobj, ifaceobj_getfunc=None): running_slaves = self.ipcmd.link_get_lowers(ifaceobj.name) config_slaves = ifaceobj.lowerifaces @@ -481,6 +569,9 @@ class vrf(moduleBase): if del_slaves: for s in del_slaves: try: + if self._is_address_virtual_slaves(ifaceobj, + config_slaves, s): + continue sobj = None if ifaceobj_getfunc: sobj = ifaceobj_getfunc(s) diff --git a/addons/vrrpd.py b/addons/vrrpd.py index 575e992..6d1ee7d 100644 --- a/addons/vrrpd.py +++ b/addons/vrrpd.py @@ -35,7 +35,7 @@ class vrrpd(moduleBase): 'example' : ['vrrp-priority 20']}, 'vrrp-virtual-ip' : {'help': 'set vrrp virtual ip', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ], 'example' : ['vrrp-virtual-ip 10.0.1.254']}}} def __init__(self, *args, **kargs): diff --git a/addons/vxlan.py b/addons/vxlan.py index c4d05d4..954e931 100644 --- a/addons/vxlan.py +++ b/addons/vxlan.py @@ -17,20 +17,20 @@ class vxlan(moduleBase): 'attrs' : { 'vxlan-id' : {'help' : 'vxlan id', - 'validrange' : ['0', '4096'], + 'validrange' : ['1', '16777214'], 'required' : True, 'example': ['vxlan-id 100']}, 'vxlan-local-tunnelip' : {'help' : 'vxlan local tunnel ip', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ''], 'example': ['vxlan-local-tunnelip 172.16.20.103']}, 'vxlan-svcnodeip' : {'help' : 'vxlan id', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ''], 'example': ['vxlan-svcnodeip 172.16.22.125']}, 'vxlan-remoteip' : {'help' : 'vxlan remote ip', - 'validvals' : [IPv4Address, ], + 'validvals' : ['', ''], 'example': ['vxlan-remoteip 172.16.22.127']}, 'vxlan-learning' : {'help' : 'vxlan learning yes/no', @@ -66,19 +66,66 @@ class vxlan(moduleBase): return True return False - def _up(self, ifaceobj): + def _vxlan_create(self, ifaceobj): vxlanid = ifaceobj.get_attr_value_first('vxlan-id') if vxlanid: - self.ipcmd.link_create_vxlan(ifaceobj.name, vxlanid, - localtunnelip=ifaceobj.get_attr_value_first('vxlan-local-tunnelip'), - svcnodeip=ifaceobj.get_attr_value_first('vxlan-svcnodeip'), - remoteips=ifaceobj.get_attr_value('vxlan-remoteip'), - learning=utils.get_onoff_bool(ifaceobj.get_attr_value_first('vxlan-learning')), - ageing=ifaceobj.get_attr_value_first('vxlan-ageing'), - anycastip=self._clagd_vxlan_anycast_ip) + anycastip = self._clagd_vxlan_anycast_ip + group = ifaceobj.get_attr_value_first('vxlan-svcnodeip') + local = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') + ageing = ifaceobj.get_attr_value_first('vxlan-ageing') + learning = utils.get_onoff_bool(ifaceobj.get_attr_value_first('vxlan-learning')) + + if self.ipcmd.link_exists(ifaceobj.name): + vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobj.name) + # on ifreload do not overwrite anycast_ip to individual ip + # if clagd has modified + if vxlanattrs: + running_localtunnelip = vxlanattrs.get('local') + if (anycastip and running_localtunnelip and + anycastip == running_localtunnelip): + local = running_localtunnelip + + netlink.link_add_vxlan(ifaceobj.name, vxlanid, + local=local, + learning=learning, + ageing=ageing, + group=group) + + remoteips = ifaceobj.get_attr_value('vxlan-remoteip') + if not systemUtils.is_service_running(None, '/var/run/vxrd.pid'): + # figure out the diff for remotes and do the bridge fdb updates + # only if provisioned by user and not by vxrd + cur_peers = set(self.ipcmd.get_vxlan_peers(ifaceobj.name, group)) + if remoteips: + new_peers = set(remoteips) + del_list = cur_peers.difference(new_peers) + add_list = new_peers.difference(cur_peers) + else: + del_list = cur_peers + add_list = [] + + try: + for addr in del_list: + self.ipcmd.bridge_fdb_del(ifaceobj.name, + '00:00:00:00:00:00', + None, True, addr) + except: + pass + + try: + for addr in add_list: + self.ipcmd.bridge_fdb_append(ifaceobj.name, + '00:00:00:00:00:00', + None, True, addr) + except: + pass + if ifaceobj.addr_method == 'manual': netlink.link_set_updown(ifaceobj.name, "up") + def _up(self, ifaceobj): + self._vxlan_create(ifaceobj) + def _down(self, ifaceobj): try: self.ipcmd.link_delete(ifaceobj.name) @@ -167,6 +214,9 @@ class vxlan(moduleBase): attrval = vxlanattrs.get('vxlanid') if attrval: ifaceobjrunning.update_config('vxlan-id', vxlanattrs.get('vxlanid')) + else: + # if there is no vxlan id, this is not a vxlan port + return attrval = vxlanattrs.get('local') if attrval: ifaceobjrunning.update_config('vxlan-local-tunnelip', attrval) diff --git a/config/addons.conf b/config/addons.conf index 73fc699..cbd71ff 100644 --- a/config/addons.conf +++ b/config/addons.conf @@ -19,7 +19,7 @@ post-up,vxrd pre-down,usercmds pre-down,ethtool pre-down,vxrd -down,dhcp +pre-down,dhcp down,addressvirtual down,address down,usercmds diff --git a/debian/changelog b/debian/changelog index b296b71..c9a799a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +ifupdown2 (1.1-cl3u4) RELEASED; urgency=medium + + * Performance improvements + * New. Enabled: sbin: start-networking: support hotplug class from init script + * New. Enabled: support for classical numerical bond modes + * New. Enabled: extend ifquery support for mstpctl addons + * New. Enabled: each addon may perform semantic and syntax checks by implementing a custom method + * Closes: CM-11745. Support for address-virtual lines under a vrf slave + * Closes: CM-11718. Defaults for link attributes were not applied + * Closes: CM-11511. Disable IPv6 duplicate address detection on VRR interfaces + * Closes: CM-11485. Fix ifquery to extract vlan-id from iface if not preset + * Closes: CM-8623. Fix for ifquery -c bridge pvid error on a valid config + + -- dev-support Fri, 29 Jul 2016 08:55:50 -0700 + ifupdown2 (1.1-cl3u3) RELEASED; urgency=medium * Closes: CM-11214. Interface configuration parsing error when keyword vlan is the interface name. diff --git a/ifupdown/ifupdownflags.py b/ifupdown/ifupdownflags.py index 58ca55e..4be891a 100644 --- a/ifupdown/ifupdownflags.py +++ b/ifupdown/ifupdownflags.py @@ -10,6 +10,7 @@ class ifupdownFlags(): def __init__(self): self.ALL = False + self.CLASS = False self.FORCE = False self.DRYRUN = False self.NOWAIT = False diff --git a/ifupdown/ifupdownmain.py b/ifupdown/ifupdownmain.py index 15c39ed..4d31cd9 100644 --- a/ifupdown/ifupdownmain.py +++ b/ifupdown/ifupdownmain.py @@ -27,6 +27,8 @@ from graph import * from exceptions import * from sets import Set +from ipaddr import IPNetwork, IPv4Network, IPv6Network, IPAddress, IPv4Address, IPv6Address + """ .. module:: ifupdownmain :synopsis: main module for ifupdown package @@ -41,7 +43,6 @@ _success_sym = '(%s)' %_tickmark _error_sym = '(%s)' %_crossmark class ifupdownMainFlags(): - IFACE_CLASS = False COMPAT_EXEC_SCRIPTS = False STATEMANAGER_ENABLE = True STATEMANAGER_UPDATE = True @@ -270,6 +271,29 @@ class ifupdownMain(ifupdownBase): # This makes config available to addon modules ifupdownConfig.config = self.config + self.validate_keywords = { + '': self._keyword_mac, + '': self._keyword_text, + '': self._keyword_ipv4, + '': self._keyword_ipv6, + '': self._keyword_ip, + '': self._keyword_number, + '': self._keyword_interface, + '': self._keyword_ipv4_vrf_text, + '': self._keyword_number_ipv4_list, + '': self._keyword_interface_list, + '': self._keyword_ipv4_prefixlen, + '': self._keyword_ipv6_prefixlen, + '': self._keyword_ip_prefixlen, + '': self._keyword_number_range_list, + '': self._keyword_interface_range_list, + '': self._keyword_mac_ip_prefixlen_list, + '': self._keyword_number_interface_list, + '': self._keyword_interface_yes_no_list, + '': self._keyword_interface_yes_no_0_1_list, + '': self._keyword_interface_yes_no_auto_list, + } + def link_master_slave_ignore_error(self, errorstr): # If link master slave flag is set, # there may be cases where the lowerdev may not be @@ -722,6 +746,355 @@ class ifupdownMain(ifupdownBase): ifaceobj.flags |= ifaceobj.OLDEST_SIBLING self.ifaceobjdict[ifaceobj.name].append(ifaceobj) + def _keyword_text(self, value, validrange=None): + return isinstance(value, str) and len(value) > 0 + + def _keyword_mac(self, value, validrange=None): + if value.strip().startswith('ether'): + value = value.strip()[6:] + return re.match('[0-9a-f]{1,2}([-:])[0-9a-f]{1,2}(\\1[0-9a-f]{1,2}){4}$', + value.lower()) + + def _keyword_check_list(self, _list, obj, limit=None): + try: + if limit and limit > 0: + for i in xrange(0, limit): + obj(_list[i]) + return len(_list) == limit + else: + for elem in _list: + obj(elem) + return True + except Exception as e: + self.logger.debug('keyword: check list: %s' % str(e)) + return False + + def _keyword_ipv4(self, value, validrange=None): + return self._keyword_check_list(value.split(), IPv4Address, limit=1) + + def _keyword_ipv4_prefixlen(self, value, validrange=None): + return self._keyword_check_list(value.split(), IPv4Network, limit=1) + + def _keyword_ipv6(self, value, validrange=None): + return self._keyword_check_list(value.split(), IPv6Address, limit=1) + + def _keyword_ipv6_prefixlen(self, value, validrange=None): + return self._keyword_check_list(value.split(), IPv6Network, limit=1) + + def _keyword_ip(self, value, validrange=None): + return self._keyword_check_list(value.split(), IPAddress, limit=1) + + def _keyword_ip_prefixlen(self, value, validrange=None): + return self._keyword_check_list(value.split(), IPNetwork, limit=1) + + def _keyword_mac_ip_prefixlen_list(self, value, validrange=None): + """ + [ ...] + ex: address-virtual 00:11:22:33:44:01 11.0.1.1/24 11.0.1.2/24 + """ + try: + res = value.split() + if len(res) < 2: + return False + if not self._keyword_mac(res[0]): + return False + for ip in res[1:]: + if not self._keyword_ip_prefixlen(ip): + return False + return True + except Exception as e: + self.logger.debug('keyword: mac ipaddr prefixlen: %s' % str(e)) + return False + + def _keyword_number_ipv4_list(self, value, validrange=None): + """ + = [= ...] + ex: bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1 + """ + try: + elements = value.split(' ') + if not elements: + return False + for elem in elements: + v = elem.split('=') + int(v[0]) + IPv4Address(v[1]) + return True + except Exception as e: + self.logger.debug('keyword: number ipv4: %s' % str(e)) + return False + + def _keyword_interface(self, ifacename, validrange=None): + return self.get_ifaceobjs(ifacename) + + def _keyword_ipv4_vrf_text(self, value, validrange=None): + """ + "vrf" + ex: clagd-backup-ip 10.10.10.42 vrf blue + """ + values = value.split() + size = len(values) + + if size > 3 or size < 1: + return False + try: + IPv4Address(values[0]) + if size > 1: + if values[1] != 'vrf': + return False + if size > 2: + if not self._keyword_text(values[2]): + return False + return True + except Exception as e: + self.logger.debug('keyword: ipv4 vrf text: %s' % str(e)) + return False + + def _keyword_interface_list_with_value(self, value, validvals): + values = value.split() + try: + if len(values) == 1: + if values[0] in validvals: + return True + for v in values: + iface_value = v.split('=') + size = len(iface_value) + if size != 2: + if iface_value[0] == 'glob' or iface_value[0] == 'regex': + continue + return False + if not iface_value[1] in validvals: + return False + return True + except Exception as e: + self.logger.debug('keyword: interface list with value: %s' % str(e)) + return False + + def _keyword_interface_yes_no_list(self, value, validrange=None): + """ + | ( = [= ...] ) + ex: mstpctl-portrestrrole swp1=yes swp2=no + """ + return self._keyword_interface_list_with_value(value, ['yes', 'no']) + + def _keyword_interface_yes_no_auto_list(self, value, validrange=None): + """ + | + ( = [= ...] ) + ex: mstpctl-portp2p swp1=yes swp2=no swp3=auto + """ + return self._keyword_interface_list_with_value(value, + ['yes', 'no', 'auto']) + + def _keyword_interface_yes_no_0_1_list(self, value, validrange=None): + """ + | + ( = [= ...] ) + ex: bridge-portmcrouter swp1=yes swp2=yes swp3=1 + """ + return self._keyword_interface_list_with_value(value, + ['yes', 'no', '1', '0']) + + def _keyword_interface_range_list(self, value, validrange): + """ + | ( = [ =number> ...] ) + ex: mstpctl-portpathcost swp1=0 swp2=1 + """ + values = value.split() + try: + if len(values) == 1: + try: + n = int(values[0]) + if n < int(validrange[0]) or n > int( + validrange[1]): + raise invalidValueError('value of out range "%s":' + ' valid attribute range: %s' + % (values[0], + '-'.join(validrange))) + return True + except invalidValueError as e: + raise e + except Exception as e: + self.logger.debug('keyword: interface range list: %s' + % str(e)) + return False + for v in values: + iface_value = v.split('=') + size = len(iface_value) + if size != 2: + return False + number = int(iface_value[1]) + if number < int(validrange[0]) or number > int( + validrange[1]): + raise invalidValueError( + 'value of out range "%s" for iface "%s":' + ' valid attribute range: %s' + % (iface_value[1], + iface_value[0], + '-'.join(validrange))) + return True + except invalidValueError as e: + raise e + except Exception as e: + self.logger.debug('keyword: interface range list: %s' % str(e)) + return False + + def _keyword_interface_list(self, value, validrange=None): + """ + [glob|regex] [ [glob|regex] ...] + ex: bridge-ports swp1 swp2 glob swp3-5.100 regex (swp[6|7|8].100) + """ + interface_list = value.split() + size = len(interface_list) + i = 0 + while i < size: + if interface_list[i] == 'glob' or interface_list[i] == 'regex': + i += 1 + else: + if not self._keyword_interface(interface_list[i]): + return False + i += 1 + return True + + def _keyword_number_range_list(self, value, validrange=None): + """ + [-] + ex: bridge-vids 42 100-200 + """ + number_list = value.split() + try: + i = 0 + while i < len(number_list): + if '-' in number_list[i]: + range = number_list[i].split('-') + a = int(range[0]) + b = int(range[1]) + if a > b: + return False + else: + int(number_list[i]) + i += 1 + return True + except Exception as e: + self.logger.debug('keyword: number range list: %s' % str(e)) + return False + + def _keyword_number_interface_list(self, value, validrange=None): + """ + [... [ ... ]] + bridge-waitport 42 swp1 swp2 swp3 9 swp4 + """ + interface_list = value.split() + if not interface_list: + return False + try: + int(interface_list[0]) + prev = True + for elem in interface_list[1:]: + try: + int(elem) + if prev: + return False + prev = True + except: + prev = False + return not prev + except Exception as e: + self.logger.debug('keyword: number interface list: %s' % str(e)) + return False + + def _keyword_number(self, value, validrange=None): + try: + int(value) + return True + except Exception as e: + self.logger.debug('keyword: number: %s' % str(e)) + return False + + def _is_keyword(self, value): + if isinstance(value, tuple): + return True + keyword_found = value in self.validate_keywords + if value.startswith('<') and value.endswith('>') and not keyword_found: + raise Exception('%s: invalid keyword, please make sure to use' + ' a valid keyword see `ifquery -s`' % value) + return keyword_found + + def _check_validvals_value(self, attrname, value, validvals, validrange): + if validvals and value not in validvals: + is_valid = False + for keyword in validvals: + if self._is_keyword(keyword): + if validrange: + if self.validate_keywords[keyword](value, validrange): + return {'result': True} + else: + if self.validate_keywords[keyword](value): + return {'result': True} + if not is_valid: + return { + 'result': False, + 'message': 'invalid value "%s": valid attribute values: %s' + % (value, validvals) + } + elif validrange: + if len(validrange) != 2: + raise Exception('%s: invalid range in addon configuration' + % '-'.join(validrange)) + _value = int(value) + if _value < int(validrange[0]) or _value > int(validrange[1]): + return { + 'result': False, + 'message': 'value of out range "%s": ' + 'valid attribute range: %s' + % (value, '-'.join(validrange)) + } + return {'result': True} + + def _check_validvals(self, ifacename, module_name, attrs): + ifaceobj = self.get_ifaceobjs(ifacename) + if not ifaceobj: + return + success = True + for attrname, attrvalue in ifaceobj[0].config.items(): + try: + attrname_dict = attrs.get(attrname, {}) + validvals = attrname_dict.get('validvals', []) + validrange = attrname_dict.get('validrange', []) + for value in attrvalue: + res = self._check_validvals_value(attrname, + value, + validvals, + validrange) + if not res['result']: + self.logger.warn('%s: %s: %s' % + (ifacename, attrname, res['message'])) + success = False + except Exception as e: + self.logger.warn('addon \'%s\': %s: %s' % (module_name, + attrname, + str(e))) + success = False + return success + + def _module_syntax_check(self, filtered_ifacenames): + result = True + for ifacename in filtered_ifacenames: + for module in self.modules.values(): + try: + if hasattr(module, '_modinfo'): + if not self._check_validvals(ifacename, + module.__class__.__name__, + module._modinfo.get('attrs', {})): + result = False + if hasattr(module, 'syntax_check') and callable(module.syntax_check): + if not module.syntax_check(self.get_ifaceobjs(ifacename)): + result = False + except Exception, e: + self.logger.warn('%s: %s' % (ifacename, str(e))) + result = False + return result + def _iface_configattr_syntax_checker(self, attrname, attrval): for m, mdict in self.module_attrs.items(): if not mdict: @@ -929,7 +1302,7 @@ class ifupdownMain(ifupdownBase): else ifaceSchedulerFlags.POSTORDER, followdependents=followdependents, skipupperifaces=skipupperifaces, - sort=True if (sort or self.flags.IFACE_CLASS) else False) + sort=True if (sort or ifupdownflags.flags.CLASS) else False) return ifaceScheduler.get_sched_status() def _render_ifacename(self, ifacename): @@ -1115,7 +1488,7 @@ class ifupdownMain(ifupdownBase): self.set_type(type) if allow_classes: - self.flags.IFACE_CLASS = True + ifupdownflags.flags.CLASS = True if not self.flags.ADDONS_ENABLE: self.flags.STATEMANAGER_UPDATE = False if auto: @@ -1150,6 +1523,8 @@ class ifupdownMain(ifupdownBase): # return here because we want to make sure most # errors above are caught and reported. if syntaxcheck: + if not self._module_syntax_check(filtered_ifacenames): + raise Exception() if not iface_read_ret: raise Exception() elif self._any_iface_errors(filtered_ifacenames): @@ -1179,7 +1554,7 @@ class ifupdownMain(ifupdownBase): self.set_type(type) if allow_classes: - self.flags.IFACE_CLASS = True + ifupdownflags.flags.CLASS = True if not self.flags.ADDONS_ENABLE: self.flags.STATEMANAGER_UPDATE = False if auto: @@ -1249,7 +1624,7 @@ class ifupdownMain(ifupdownBase): self._ifaceobj_squash_internal = False if allow_classes: - self.flags.IFACE_CLASS = True + ifupdownflags.flags.CLASS = True if self.flags.STATEMANAGER_ENABLE and ops[0] == 'query-savedstate': return self.statemanager.dump_pretty(ifacenames) self.flags.STATEMANAGER_UPDATE = False @@ -1355,6 +1730,8 @@ class ifupdownMain(ifupdownBase): # return here because we want to make sure most # errors above are caught and reported. if syntaxcheck: + if not self._module_syntax_check(interfaces_to_up): + raise Exception() if not iface_read_ret: raise Exception() elif self._any_iface_errors(interfaces_to_up): @@ -1437,6 +1814,8 @@ class ifupdownMain(ifupdownBase): # return here because we want to make sure most # errors above are caught and reported. if syntaxcheck: + if not self._module_syntax_check(new_filtered_ifacenames): + raise Exception() if not iface_read_ret: raise Exception() elif self._any_iface_errors(new_filtered_ifacenames): @@ -1669,7 +2048,9 @@ class ifupdownMain(ifupdownBase): for i in ifacenames: for ifaceobj in self.get_ifaceobjs(i): if ((not running and self.is_ifaceobj_noconfig(ifaceobj)) or - (running and not ifaceobj.is_config_present())): + (running and not ifaceobj.is_config_present() and + not self.is_iface_builtin_byname(i) and + not ifaceobj.upperifaces)): continue ifaceobjs.append(ifaceobj) if (ifupdownflags.flags.WITH_DEPENDS and diff --git a/ifupdown/netlink.py b/ifupdown/netlink.py index fda8039..3a7e7b2 100644 --- a/ifupdown/netlink.py +++ b/ifupdown/netlink.py @@ -5,7 +5,6 @@ # try: - from ifupdownaddons.utilsbase import utilsBase from nlmanager.nlmanager import NetlinkManager import ifupdown.ifupdownflags as ifupdownflags @@ -14,21 +13,25 @@ except ImportError, e: class Netlink(utilsBase): + VXLAN_UDP_PORT = 4789 + def __init__(self, *args, **kargs): utilsBase.__init__(self, *args, **kargs) self._nlmanager_api = NetlinkManager() def get_iface_index(self, ifacename): - self.logger.info('netlink: %s: get iface index' % ifacename) + self.logger.info('%s: netlink: %s: get iface index' + % (ifacename, ifacename)) if ifupdownflags.flags.DRYRUN: return try: return self._nlmanager_api.get_iface_index(ifacename) except Exception as e: - raise Exception('netlink: %s: cannot get ifindex: %s' - % (ifacename, str(e))) + raise Exception('%s: netlink: %s: cannot get ifindex: %s' + % (ifacename, ifacename, str(e))) def link_add_vlan(self, vlanrawdevice, ifacename, vlanid): - self.logger.info('netlink: %s: creating vlan %s' % (vlanrawdevice, vlanid)) + self.logger.info('%s: netlink: ip link add link %s name %s type vlan id %s' + % (ifacename, vlanrawdevice, ifacename, vlanid)) if ifupdownflags.flags.DRYRUN: return ifindex = self.get_iface_index(vlanrawdevice) try: @@ -38,8 +41,8 @@ class Netlink(utilsBase): % (vlanrawdevice, vlanid, str(e))) def link_add_macvlan(self, ifacename, macvlan_ifacename): - self.logger.info('netlink: %s: creating macvlan %s' - % (ifacename, macvlan_ifacename)) + self.logger.info('%s: netlink: ip link add link %s name %s type macvlan mode private' + % (ifacename, ifacename, macvlan_ifacename)) if ifupdownflags.flags.DRYRUN: return ifindex = self.get_iface_index(ifacename) try: @@ -49,7 +52,8 @@ class Netlink(utilsBase): % (ifacename, macvlan_ifacename, str(e))) def link_set_updown(self, ifacename, state): - self.logger.info('netlink: set link %s %s' % (ifacename, state)) + self.logger.info('%s: netlink: ip link set dev %s %s' + % (ifacename, ifacename, state)) if ifupdownflags.flags.DRYRUN: return try: return self._nlmanager_api.link_set_updown(ifacename, state) @@ -58,7 +62,8 @@ class Netlink(utilsBase): % (ifacename, state, str(e))) def link_set_protodown(self, ifacename, state): - self.logger.info('netlink: set link %s protodown %s' % (ifacename, state)) + self.logger.info('%s: netlink: set link %s protodown %s' + % (ifacename, ifacename, state)) if ifupdownflags.flags.DRYRUN: return try: return self._nlmanager_api.link_set_protodown(ifacename, state) @@ -67,8 +72,8 @@ class Netlink(utilsBase): % (ifacename, state, str(e))) def link_add_bridge_vlan(self, ifacename, vlanid): - self.logger.info('netlink: %s: creating bridge vlan %s' - % (ifacename, vlanid)) + self.logger.info('%s: netlink: bridge vlan add vid %s dev %s' + % (ifacename, vlanid, ifacename)) if ifupdownflags.flags.DRYRUN: return ifindex = self.get_iface_index(ifacename) try: @@ -78,8 +83,8 @@ class Netlink(utilsBase): % (ifacename, vlanid, str(e))) def link_del_bridge_vlan(self, ifacename, vlanid): - self.logger.info('netlink: %s: removing bridge vlan %s' - % (ifacename, vlanid)) + self.logger.info('%s: netlink: bridge vlan del vid %s dev %s' + % (ifacename, vlanid, ifacename)) if ifupdownflags.flags.DRYRUN: return ifindex = self.get_iface_index(ifacename) try: @@ -88,4 +93,27 @@ class Netlink(utilsBase): raise Exception('netlink: %s: cannot remove bridge vlan %s: %s' % (ifacename, vlanid, str(e))) + def link_add_vxlan(self, ifacename, vxlanid, local=None, dstport=VXLAN_UDP_PORT, + group=None, learning='on', ageing=None): + cmd = 'ip link add %s type vxlan id %s dstport %s' % (ifacename, + vxlanid, + dstport) + cmd += ' local %s' % local if local else '' + cmd += ' ageing %s' % ageing if ageing else '' + cmd += ' remote %s' % group if group else ' noremote' + cmd += ' nolearning' if learning == 'off' else '' + self.logger.info('%s: netlink: %s' % (ifacename, cmd)) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_add_vxlan(ifacename, + vxlanid, + dstport=dstport, + local=local, + group=group, + learning=learning, + ageing=ageing) + except Exception as e: + raise Exception('netlink: %s: cannot create vxlan %s: %s' + % (ifacename, vxlanid, str(e))) + netlink = Netlink() diff --git a/ifupdown/networkinterfaces.py b/ifupdown/networkinterfaces.py index bc7749d..e99cc85 100644 --- a/ifupdown/networkinterfaces.py +++ b/ifupdown/networkinterfaces.py @@ -60,8 +60,11 @@ class networkInterfaces(): self.interfacesfileiobuf = interfacesfileiobuf self.interfacesfileformat = interfacesfileformat self._filestack = [self.interfacesfile] - self._template_engine = templateEngine(template_engine, - template_lookuppath) + + self._template_engine = None + self._template_engine_name = template_engine + self._template_engine_path = template_lookuppath + self._currentfile_has_template = False self._ws_split_regex = re.compile(r'[\s\t]\s*') @@ -385,22 +388,26 @@ class networkInterfaces(): def read_filedata(self, filedata): self._currentfile_has_template = False # run through template engine - try: - rendered_filedata = self._template_engine.render(filedata) - if rendered_filedata is filedata: - self._currentfile_has_template = False - else: - self._currentfile_has_template = True - except Exception, e: - self._parse_error(self._currentfile, -1, - 'failed to render template (%s). ' %str(e) + - 'Continue without template rendering ...') - rendered_filedata = None - pass - if rendered_filedata: - self.process_interfaces(rendered_filedata) - else: - self.process_interfaces(filedata) + if filedata and '%' in filedata: + try: + if not self._template_engine: + self._template_engine = templateEngine( + self._template_engine_name, + self._template_engine_path) + rendered_filedata = self._template_engine.render(filedata) + if rendered_filedata is filedata: + self._currentfile_has_template = False + else: + self._currentfile_has_template = True + except Exception, e: + self._parse_error(self._currentfile, -1, + 'failed to render template (%s). Continue without template rendering ...' + % str(e)) + rendered_filedata = None + if rendered_filedata: + self.process_interfaces(rendered_filedata) + return + self.process_interfaces(filedata) def read_file(self, filename, fileiobuf=None): if fileiobuf: diff --git a/ifupdown/scheduler.py b/ifupdown/scheduler.py index 09ac1a6..c79278f 100644 --- a/ifupdown/scheduler.py +++ b/ifupdown/scheduler.py @@ -411,6 +411,23 @@ class ifaceScheduler(): if continueonfailure: ifupdownobj.logger.warn('%s' %str(e)) + @classmethod + def _dump_dependency_info(cls, ifupdownobj, ifacenames, + dependency_graph=None, indegrees=None): + ifupdownobj.logger.info('{\n') + ifupdownobj.logger.info('\nifaceobjs:') + for iname in ifacenames: + iobjs = ifupdownobj.get_ifaceobjs(iname) + for iobj in iobjs: + iobj.dump(ifupdownobj.logger) + if (dependency_graph): + ifupdownobj.logger.info('\nDependency Graph:') + ifupdownobj.logger.info(dependency_graph) + if (indegrees): + ifupdownobj.logger.info('\nIndegrees:') + ifupdownobj.logger.info(indegrees) + ifupdownobj.logger.info('}\n') + @classmethod def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops, dependency_graph, indegrees=None): @@ -421,6 +438,10 @@ class ifaceScheduler(): indegrees = OrderedDict() for ifacename in dependency_graph.keys(): indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename) + + #cls._dump_dependency_info(ifupdownobj, ifacenames, + # dependency_graph, indegrees) + ifacenames_all_sorted = graph.topological_sort_graphs_all( dependency_graph, indegrees) # if ALL was set, return all interfaces diff --git a/ifupdown/template.py b/ifupdown/template.py index d315ddd..4397834 100644 --- a/ifupdown/template.py +++ b/ifupdown/template.py @@ -38,9 +38,10 @@ class templateEngine(): self.tclassargs['lookup'] = lc( directories=template_lookuppath.split(':')) except Exception, e: - self.logger.warn('unable to set template lookup path' + - ' %s (%s)' %(template_lookuppath, str(e))) - pass + self.logger.warn('unable to set template lookup path' + ' %s (%s): are you sure \'python-mako\'' + 'is installed?' + % (template_lookuppath, str(e))) self.render = self._render_mako else: self.logger.info('skip template processing.., ' + diff --git a/ifupdownaddons/dhclient.py b/ifupdownaddons/dhclient.py index 81b45f4..1aeb0d0 100644 --- a/ifupdownaddons/dhclient.py +++ b/ifupdownaddons/dhclient.py @@ -98,7 +98,6 @@ class dhclient(utilsBase): def release6(self, ifacename, cmd_prefix=None): cmd = ['/sbin/dhclient', '-6', '-r', '-pf', - '/run/dhclient6.%s.pid' %ifacename, '-lf', - '/var/lib/dhcp/dhclient6.%s.leases' %ifacename, + '/run/dhclient6.%s.pid' %ifacename, '%s' %ifacename] self._run_dhclient_cmd(cmd, cmd_prefix) diff --git a/ifupdownaddons/iproute2.py b/ifupdownaddons/iproute2.py index b1f4b6d..624849c 100644 --- a/ifupdownaddons/iproute2.py +++ b/ifupdownaddons/iproute2.py @@ -61,6 +61,7 @@ class iproute2(utilsBase): warn = True linkout = {} + vxrd_running = False if iproute2._cache_fill_done and not refresh: return try: # if ifacename already present, return @@ -72,6 +73,10 @@ class iproute2(utilsBase): cmdout = self.link_show(ifacename=ifacename) if not cmdout: return + # read vxrd.pid and cache the running state before going through + # every interface in the system + if systemUtils.is_service_running(None, '/var/run/vxrd.pid'): + vxrd_running = True for c in cmdout.splitlines(): citems = c.split() ifnamenlink = citems[1].split('@') @@ -118,10 +123,11 @@ class iproute2(utilsBase): vattrs['ageing'] = citems[j + 1] elif citems[j] == 'nolearning': vattrs['learning'] = 'off' - # get vxlan peer nodes - peers = self.get_vxlan_peers(ifname, vattrs['svcnode']) - if peers: - vattrs['remote'] = peers + # get vxlan peer nodes if provisioned by user and not by vxrd + if not vxrd_running: + peers = self.get_vxlan_peers(ifname, vattrs['svcnode']) + if peers: + vattrs['remote'] = peers linkattrs['linkinfo'] = vattrs break elif citems[i] == 'vrf' and citems[i + 1] == 'table': @@ -133,6 +139,8 @@ class iproute2(utilsBase): elif citems[i] == 'vrf_slave': linkattrs['kind'] = 'vrf_slave' break + elif citems[i] == 'macvlan' and citems[i + 1] == 'mode': + linkattrs['kind'] = 'macvlan' except Exception as e: if warn: self.logger.debug('%s: parsing error: id, mtu, state, link/ether, vlan, dummy, vxlan, local, remote, ageing, nolearning, vrf, table, vrf_slave are reserved keywords: %s' % (ifname, str(e))) @@ -284,6 +292,21 @@ class iproute2(utilsBase): self.ipbatch = False self.ipbatch_pause = False + def bridge_batch_commit(self): + if not self.ipbatchbuf: + self.ipbatchbuf = '' + self.ipbatch = False + self.ipbatch_pause = False + return + try: + utils.exec_command('bridge -force -batch -', stdin=self.ipbatchbuf) + except: + raise + finally: + self.ipbatchbuf = '' + self.ipbatch = False + self.ipbatch_pause = False + def addr_show(self, ifacename=None): if ifacename: if not self.link_exists(ifacename): @@ -597,30 +620,6 @@ class iproute2(utilsBase): else: utils.exec_command('ip %s' % cmd) - if not systemUtils.is_service_running(None, '/var/run/vxrd.pid'): - #figure out the diff for remotes and do the bridge fdb updates - #only if provisioned by user and not by vxrd - cur_peers = set(self.get_vxlan_peers(name, svcnodeip)) - if remoteips: - new_peers = set(remoteips) - del_list = cur_peers.difference(new_peers) - add_list = new_peers.difference(cur_peers) - else: - del_list = cur_peers - add_list = [] - - try: - for addr in del_list: - self.bridge_fdb_del(name, '00:00:00:00:00:00', None, True, addr) - except: - pass - - try: - for addr in add_list: - self.bridge_fdb_append(name, '00:00:00:00:00:00', None, True, addr) - except: - pass - # XXX: update linkinfo correctly self._cache_update([name], {}) @@ -733,35 +732,46 @@ class iproute2(utilsBase): brvlanlines = bridgeout.splitlines() brportname=None for l in brvlanlines[1:]: - if l and l[0] not in [' ', '\t']: - brportname = None - l=l.strip() - if not l: - brportname=None - continue - if 'PVID' in l: - attrs = l.split() - brportname = attrs[0] - brvlaninfo[brportname] = {'pvid' : attrs[1], - 'vlan' : []} - elif brportname: - if 'Egress Untagged' not in l: - brvlaninfo[brportname]['vlan'].append(l) - elif not brportname: + if l and not l.startswith(' ') and not l.startswith('\t'): attrs = l.split() - if attrs[1] == 'None' or 'Egress Untagged' in attrs[1]: - continue - brportname = attrs[0] - brvlaninfo[brportname] = {'vlan' : [attrs[1]]} + brportname = attrs[0].strip() + brvlaninfo[brportname] = {'pvid' : None, 'vlan' : []} + l = ' '.join(attrs[1:]) + if not brportname or not l: + continue + l = l.strip() + if 'PVID' in l: + brvlaninfo[brportname]['pvid'] = l.split()[0] + elif 'Egress Untagged' not in l: + brvlaninfo[brportname]['vlan'].append(l) return brvlaninfo + def bridge_port_vids_get_all_json(self): + brvlaninfo = {} + bridgeout = utils.exec_command('/sbin/bridge -c -json vlan show') + if not bridgeout: return brvlaninfo + try: + vlan_json_dict = json.loads(bridgeout, encoding="utf-8") + except Exception, e: + self.logger.info('json loads failed with (%s)' %str(e)) + return {} + return vlan_json_dict + def bridge_port_pvid_add(self, bridgeportname, pvid): - utils.exec_command('bridge vlan add vid %s untagged pvid dev %s' % - (pvid, bridgeportname)) + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch('vlan add vid %s untagged pvid dev %s' % + (pvid, bridgeportname)) + else: + utils.exec_command('bridge vlan add vid %s untagged pvid dev %s' % + (pvid, bridgeportname)) def bridge_port_pvid_del(self, bridgeportname, pvid): - utils.exec_command('bridge vlan del vid %s untagged pvid dev %s' % - (pvid, bridgeportname)) + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch('vlan del vid %s untagged pvid dev %s' % + (pvid, bridgeportname)) + else: + utils.exec_command('bridge vlan del vid %s untagged pvid dev %s' % + (pvid, bridgeportname)) def bridge_port_pvids_get(self, bridgeportname): return self.read_file_oneline('/sys/class/net/%s/brport/pvid' @@ -769,13 +779,21 @@ class iproute2(utilsBase): def bridge_vids_add(self, bridgeportname, vids, bridge=True): target = 'self' if bridge else '' - [utils.exec_command('bridge vlan add vid %s dev %s %s' % - (v, bridgeportname, target)) for v in vids] + if self.ipbatch and not self.ipbatch_pause: + [self.add_to_batch('vlan add vid %s dev %s %s' % + (v, bridgeportname, target)) for v in vids] + else: + [utils.exec_command('bridge vlan add vid %s dev %s %s' % + (v, bridgeportname, target)) for v in vids] def bridge_vids_del(self, bridgeportname, vids, bridge=True): target = 'self' if bridge else '' - [utils.exec_command('bridge vlan del vid %s dev %s %s' % - (v, bridgeportname, target)) for v in vids] + if self.ipbatch and not self.ipbatch_pause: + [self.add_to_batch('vlan del vid %s dev %s %s' % + (v, bridgeportname, target)) for v in vids] + else: + [utils.exec_command('bridge vlan del vid %s dev %s %s' % + (v, bridgeportname, target)) for v in vids] def bridge_fdb_add(self, dev, address, vlan=None, bridge=True, remote=None): target = 'self' if bridge else '' diff --git a/ifupdownaddons/modulebase.py b/ifupdownaddons/modulebase.py index fe3d9e5..7799129 100644 --- a/ifupdownaddons/modulebase.py +++ b/ifupdownaddons/modulebase.py @@ -53,10 +53,12 @@ class moduleBase(object): if not self.ignore_error(str): if self.logger.getEffectiveLevel() == logging.DEBUG: traceback.print_stack() - if ifaceobj: - ifaceobj.set_status(ifaceStatus.ERROR) if raise_error: + if ifaceobj: + ifaceobj.set_status(ifaceStatus.ERROR) raise Exception(str) + else: + self.logger.error(str) else: pass @@ -325,7 +327,7 @@ class moduleBase(object): def _get_reserved_vlan_range(self): start = end = 0 - get_resvvlan = '/usr/share/python-ifupdown2/get_reserved_vlan_range.sh' + get_resvvlan = '/var/lib/ifupdown2/hooks/get_reserved_vlan_range.sh' if not os.path.exists(get_resvvlan): return (start, end) try: diff --git a/ifupdownaddons/mstpctlutil.py b/ifupdownaddons/mstpctlutil.py index 078ea9b..468244f 100644 --- a/ifupdownaddons/mstpctlutil.py +++ b/ifupdownaddons/mstpctlutil.py @@ -16,6 +16,8 @@ class mstpctlutil(utilsBase): """ This class contains helper methods to interact with mstpd using mstputils commands """ + _DEFAULT_PORT_PRIO = '128' + _cache_fill_done = False _bridgeattrmap = {'bridgeid' : 'bridge-id', @@ -34,7 +36,9 @@ class mstpctlutil(utilsBase): 'bpduguard' : 'bpdu-guard-port', 'portautoedge' : 'auto-edge-port', 'portnetwork' : 'network-port', - 'portbpdufilter' : 'bpdufilter-port'} + 'portbpdufilter' : 'bpdufilter-port', + 'portpathcost' : 'external-port-cost', + 'treeportcost' : 'internal-port-cost'} def __init__(self, *args, **kargs): utilsBase.__init__(self, *args, **kargs) @@ -68,6 +72,13 @@ class mstpctlutil(utilsBase): pass return bridgeattrs + def _extract_bridge_port_prio(self, portid): + try: + return str(int(portid[0], 16) * 16) + except: + pass + return mstpctlutil._DEFAULT_PORT_PRIO + def _get_mstpctl_bridgeport_attr_from_cache(self, bridgename): attrs = MSTPAttrsCache.get(bridgename) if not attrs: @@ -87,6 +98,7 @@ class mstpctlutil(utilsBase): # by bridgename, portname, and json attribute for portid in mstpctl_bridge_cache[portname].keys(): mstpctl_bridgeport_attrs_dict[portname] = {} + mstpctl_bridgeport_attrs_dict[portname]['treeportprio'] = self._extract_bridge_port_prio(portid) for jsonAttr in mstpctl_bridge_cache[portname][portid].keys(): jsonVal = mstpctl_bridge_cache[portname][portid][jsonAttr] mstpctl_bridgeport_attrs_dict[portname][jsonAttr] = str(jsonVal) @@ -117,11 +129,26 @@ class mstpctlutil(utilsBase): except Exception, e: self.logger.warn(str(e)) + def _get_bridge_port_attr_with_prio(self, + bridgename, + bridgeportname, + attrname): + attrvalue_curr = self.get_bridgeport_attr(bridgename, + bridgeportname, attrname) + if attrname == 'treeportprio': + try: + attrs = self._get_mstpctl_bridgeport_attr_from_cache(bridgename) + attrvalue_curr = attrs[bridgeportname]['treeportprio'] + except: + pass + return attrvalue_curr + def set_bridgeport_attr(self, bridgename, bridgeportname, attrname, attrvalue, check=True): if check: - attrvalue_curr = self.get_bridgeport_attr(bridgename, - bridgeportname, attrname) + attrvalue_curr = self._get_bridge_port_attr_with_prio(bridgename, + bridgeportname, + attrname) if attrvalue_curr and attrvalue_curr == attrvalue: return if attrname == 'treeportcost' or attrname == 'treeportprio': diff --git a/nlmanager/nllistener.py b/nlmanager/nllistener.py index 4e6c190..6d196fb 100644 --- a/nlmanager/nllistener.py +++ b/nlmanager/nllistener.py @@ -68,7 +68,7 @@ class NetlinkListener(Thread): log.info("%s: shutting down" % self) return - # Only block for 1 second so we can wake up to see + # Only block for 1 second so we can wake up to see if shutdown_event is set try: (readable, writeable, exceptional) = select(my_sockets, [], my_sockets, 1) except Exception as e: @@ -80,18 +80,14 @@ class NetlinkListener(Thread): set_alarm = False set_tx_socket_rxed_ack_alarm = False - missed_a_packet = False for s in readable: - data = None + data = [] try: data = s.recv(4096) - - # If recv() failed, we missed a packet except Exception as e: log.error('recv() error: ' + str(e)) - missed_a_packet = True continue total_length = len(data) @@ -100,31 +96,28 @@ class NetlinkListener(Thread): # Extract the length, etc from the header (length, msgtype, flags, seq, pid) = unpack(header_PACK, data[:header_LEN]) - if manager.debug_listener: - log.info('%s: RXed %s seq %d, pid %d, %d bytes (%d total)' % - (socket_string[s], NetlinkPacket.type_to_string[msgtype], - seq, pid, length, total_length)) - - # 99% of the time when we see an ERROR the error code is - # zero which means ACK + log.debug('%s %s: RXed %s seq %d, pid %d, %d bytes (%d total)' % + (self, socket_string[s], NetlinkPacket.type_to_string[msgtype], + seq, pid, length, total_length)) possible_ack = False if msgtype == NLMSG_DONE: possible_ack = True elif msgtype == NLMSG_ERROR: - # TODO - change this > to = ? - error_code = int(unpack('>H', data[header_LEN:header_LEN+2])[0]) + possible_ack = True + + # The error code is a signed negative number. + error_code = abs(unpack('=i', data[header_LEN:header_LEN+4])[0]) + msg = Error(msgtype, True) + msg.decode_packet(length, flags, seq, pid, data) if error_code: - log.debug("%s: RXed NLMSG_ERROR code %d" % (socket_string[s], error_code)) - else: - possible_ack = True + log.debug("%s %s: RXed NLMSG_ERROR code %s (%d)" % (self, socket_string[s], msg.error_to_string.get(error_code), error_code)) if possible_ack and seq == manager.target_seq and pid == manager.target_pid: - if manager.target_seq_pid_debug: - log.debug("%s: Setting RXed ACK alarm for seq %d, pid %d" % - (socket_string[s], seq, pid)) + log.debug("%s %s: Setting RXed ACK alarm for seq %d, pid %d" % + (self, socket_string[s], seq, pid)) set_tx_socket_rxed_ack_alarm = True # Put the message on the manager's netlinkq @@ -142,11 +135,11 @@ class NetlinkListener(Thread): # as a reminder to add support for them. else: if msgtype in NetlinkPacket.type_to_string: - log.warning('%s: RXed unsupported message %s (type %d)' % - (socket_string[s], NetlinkPacket.type_to_string[msgtype], msgtype)) + log.warning('%s %s: RXed unsupported message %s (type %d)' % + (self, socket_string[s], NetlinkPacket.type_to_string[msgtype], msgtype)) else: - log.warning('%s: RXed unknown message type %d' % - (socket_string[s], msgtype)) + log.warning('%s %s: RXed unknown message type %d' % + (self, socket_string[s], msgtype)) # Track the previous PID sequence number for RX and TX sockets if s == self.rx_socket: @@ -155,8 +148,7 @@ class NetlinkListener(Thread): prev_seq = manager.tx_socket_prev_seq if pid in prev_seq and prev_seq[pid] and prev_seq[pid] != seq and (prev_seq[pid]+1 != seq): - log.info('%s: Went from seq %d to %d' % (socket_string[s], prev_seq[pid], seq)) - missed_a_packet = True + log.debug('%s %s: went from seq %d to %d' % (self, socket_string[s], prev_seq[pid], seq)) prev_seq[pid] = seq data = data[length:] @@ -209,17 +201,25 @@ class NetlinkManagerWithListener(NetlinkManager): def signal_term_handler(self, signal, frame): log.info("NetlinkManagerWithListener: Caught SIGTERM") + + if self.listener: + self.listener.shutdown_event.set() + self.shutdown_flag = True # For NetlinkManager shutdown self.shutdown_event.set() self.alarm.set() def signal_int_handler(self, signal, frame): log.info("NetlinkManagerWithListener: Caught SIGINT") + + if self.listener: + self.listener.shutdown_event.set() + self.shutdown_flag = True # For NetlinkManager shutdown self.shutdown_event.set() self.alarm.set() - def tx_nlpacket_ack_on_listener(self, nlpacket): + def tx_nlpacket_get_response(self, nlpacket): """ TX the message and wait for an ack """ @@ -233,6 +233,11 @@ class NetlinkManagerWithListener(NetlinkManager): if not self.tx_socket: self.tx_socket_allocate() + + log.debug('%s TX: TXed %s seq %d, pid %d, %d bytes' % + (self, NetlinkPacket.type_to_string[nlpacket.msgtype], + nlpacket.seq, nlpacket.pid, nlpacket.length)) + self.tx_socket.sendall(nlpacket.message) # Wait for NetlinkListener to RX an ACK or DONE for this (seq, pid) @@ -243,34 +248,38 @@ class NetlinkManagerWithListener(NetlinkManager): # various netlink message types. Odds are our child class will redefine these # to do more than log a message. def rx_rtm_newlink(self, msg): - log.info("RX RTM_NEWLINK for %s, state %s" % (msg.get_attribute_value(msg.IFLA_IFNAME), "up" if msg.is_up() else "down")) + log.debug("RXed RTM_NEWLINK seq %d, pid %d, %d bytes, for %s, state %s" % + (msg.seq, msg.pid, msg.length, msg.get_attribute_value(msg.IFLA_IFNAME), "up" if msg.is_up() else "down")) def rx_rtm_dellink(self, msg): - log.info("RX RTM_DELLINK for %s, state %s" % (msg.get_attribute_value(msg.IFLA_IFNAME), "up" if msg.is_up() else "down")) + log.debug("RXed RTM_DELLINK seq %d, pid %d, %d bytes, for %s, state %s" % + (msg.seq, msg.pid, msg.length, msg.get_attribute_value(msg.IFLA_IFNAME), "up" if msg.is_up() else "down")) def rx_rtm_newaddr(self, msg): - log.info("RX RTM_NEWADDR for %s/%d on %s" % - (msg.get_attribute_value(msg.IFA_ADDRESS), msg.prefixlen, self.ifname_by_index.get(msg.ifindex))) + log.debug("RXed RTM_NEWADDR seq %d, pid %d, %d bytes, for %s/%d on %s" % + (msg.seq, msg.pid, msg.length, msg.get_attribute_value(msg.IFA_ADDRESS), msg.prefixlen, self.ifname_by_index.get(msg.ifindex))) def rx_rtm_deladdr(self, msg): - log.info("RX RTM_DELADDR for %s/%d on %s" % - (msg.get_attribute_value(msg.IFA_ADDRESS), msg.prefixlen, self.ifname_by_index.get(msg.ifindex))) + log.debug("RXed RTM_DELADDR seq %d, pid %d, %d bytes, for %s/%d on %s" % + (msg.seq, msg.pid, msg.length, msg.get_attribute_value(msg.IFA_ADDRESS), msg.prefixlen, self.ifname_by_index.get(msg.ifindex))) def rx_rtm_newneigh(self, msg): - log.info("RX RTM_NEWNEIGH for %s on %s" % (msg.get_attribute_value(msg.NDA_DST), self.ifname_by_index.get(msg.ifindex))) + log.debug("RXed RTM_NEWNEIGH seq %d, pid %d, %d bytes, for %s on %s" % + (msg.seq, msg.pid, msg.length, msg.get_attribute_value(msg.NDA_DST), self.ifname_by_index.get(msg.ifindex))) def rx_rtm_delneigh(self, msg): - log.info("RX RTM_DELNEIGH for %s on %s" % (msg.get_attribute_value(msg.NDA_DST), self.ifname_by_index.get(msg.ifindex))) + log.debug("RXed RTM_DELNEIGH seq %d, pid %d, %d bytes, for %s on %s" % + (msg.seq, msg.pid, msg.length, msg.get_attribute_value(msg.NDA_DST), self.ifname_by_index.get(msg.ifindex))) def rx_rtm_newroute(self, msg): - log.info("RX RTM_NEWROUTE for %s%s" % - (msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) + log.debug("RXed RTM_NEWROUTE seq %d, pid %d, %d bytes, for %s%s" % + (msg.seq, msg.pid, msg.length, msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) def rx_rtm_delroute(self, msg): - log.info("RX RTM_DELROUTE for %s%s" % - (msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) + log.debug("RXed RTM_DELROUTE seq %d, pid %d, %d bytes, for %s%s" % + (msg.seq, msg.pid, msg.length, msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) - # Note that tx_nlpacket_ack_on_listener will block until NetlinkListener has RXed + # Note that tx_nlpacket_get_response will block until NetlinkListener has RXed # an Ack/DONE for the message we TXed def get_all_addresses(self): family = socket.AF_UNSPEC @@ -284,7 +293,7 @@ class NetlinkManagerWithListener(NetlinkManager): if debug: self.debug_seq_pid[(addr.seq, addr.pid)] = True - self.tx_nlpacket_ack_on_listener(addr) + self.tx_nlpacket_get_response(addr) def get_all_links(self): family = socket.AF_UNSPEC @@ -298,7 +307,7 @@ class NetlinkManagerWithListener(NetlinkManager): if debug: self.debug_seq_pid[(link.seq, link.pid)] = True - self.tx_nlpacket_ack_on_listener(link) + self.tx_nlpacket_get_response(link) def get_all_neighbors(self): family = socket.AF_UNSPEC @@ -312,7 +321,7 @@ class NetlinkManagerWithListener(NetlinkManager): if debug: self.debug_seq_pid[(neighbor.seq, neighbor.pid)] = True - self.tx_nlpacket_ack_on_listener(neighbor) + self.tx_nlpacket_get_response(neighbor) def get_all_routes(self): family = socket.AF_UNSPEC @@ -326,7 +335,7 @@ class NetlinkManagerWithListener(NetlinkManager): if debug: self.debug_seq_pid[(route.seq, route.pid)] = True - self.tx_nlpacket_ack_on_listener(route) + self.tx_nlpacket_get_response(route) def nested_attributes_match(self, msg, attr_filter): """ diff --git a/nlmanager/nlmanager.py b/nlmanager/nlmanager.py index 431f252..6406677 100644 --- a/nlmanager/nlmanager.py +++ b/nlmanager/nlmanager.py @@ -15,7 +15,11 @@ class NetlinkError(Exception): pass -class NetlinkNoAddressError(Exception): +class NetlinkNoAddressError(NetlinkError): + pass + + +class NetlinkInterruptedSystemCall(NetlinkError): pass @@ -137,13 +141,15 @@ class NetlinkManager(object): # packet via the decode_packet call...so avoid printing # two messages for one packet. if not nlpacket.debug: - log.info("TXed %12s, pid %d, seq %d, %d bytes" % + log.debug("TXed %12s, pid %d, seq %d, %d bytes" % (nlpacket.get_type_string(), nlpacket.pid, nlpacket.seq, nlpacket.length)) header_PACK = NetlinkPacket.header_PACK header_LEN = NetlinkPacket.header_LEN null_read = 0 - MAX_NULL_READS = 30 + nle_intr_count = 0 + MAX_NULL_READS = 3 + MAX_ERROR_NLE_INTR = 3 msgs = [] # Now listen to our socket and wait for the reply @@ -154,21 +160,51 @@ class NetlinkManager(object): return msgs # Only block for 1 second so we can wake up to see if self.shutdown_flag is True - (readable, writeable, exceptional) = select([self.tx_socket, ], [], [self.tx_socket, ], 1) + try: + (readable, writeable, exceptional) = select([self.tx_socket, ], [], [self.tx_socket, ], 1) + except Exception as e: + # 4 is Interrupted system call + if isinstance(e.args, tuple) and e[0] == 4: + nle_intr_count += 1 + log.info("select() Interrupted system call %d/%d" % (nle_intr_count, MAX_ERROR_NLE_INTR)) - if not readable: + if nle_intr_count >= MAX_ERROR_NLE_INTR: + raise NetlinkInterruptedSystemCall(error_str) + else: + continue + else: + raise + + if readable: + null_read = 0 + else: null_read += 1 # Safety net to make sure we do not spend too much time in # this while True loop if null_read >= MAX_NULL_READS: - log.warning('Socket was not readable for %d attempts' % null_read) + log.info('Socket was not readable for %d attempts' % null_read) return msgs - - continue + else: + continue for s in readable: - data = s.recv(4096) + data = [] + + try: + data = s.recv(4096) + except Exception as e: + # 4 is Interrupted system call + if isinstance(e.args, tuple) and e[0] == 4: + nle_intr_count += 1 + log.info("%s: recv() Interrupted system call %d/%d" % (s, nle_intr_count, MAX_ERROR_NLE_INTR)) + + if nle_intr_count >= MAX_ERROR_NLE_INTR: + raise NetlinkInterruptedSystemCall(error_str) + else: + continue + else: + raise if not data: log.info('RXed zero length data, the socket is closed') @@ -204,26 +240,44 @@ class NetlinkManager(object): msg = Error(msgtype, nlpacket.debug) msg.decode_packet(length, flags, seq, pid, data) - debug_str += ", error code %s" % msg.error_to_string.get(error_code) - # 0 is NLE_SUCCESS...everything else is a true error if error_code: - if error_code == Error.NLE_NOADDR: - raise NetlinkNoAddressError(debug_str) + error_code_str = msg.error_to_string.get(error_code) + + if error_code_str != 'None': + error_str = 'Operation failed with \'%s\' (%s)' % (error_code_str, debug_str) else: - raise NetlinkError(debug_str) + error_str = 'Operation failed with code %s (%s)' % (error_code, debug_str) + + if error_code == Error.NLE_NOADDR: + raise NetlinkNoAddressError(error_str) + elif error_code == Error.NLE_INTR: + nle_intr_count += 1 + log.info("%s: RXed NLE_INTR Interrupted system call %d/%d" % (s, nle_intr_count, MAX_ERROR_NLE_INTR)) + + if nle_intr_count >= MAX_ERROR_NLE_INTR: + raise NetlinkInterruptedSystemCall(error_str) + else: + if error_code_str == 'None': + try: + # os.strerror might raise ValueError + raise NetlinkError('Operation failed with \'%s\' (%s)' % (os.strerror(error_code), debug_str)) + except ValueError: + pass + raise NetlinkError(error_str) else: - log.info(debug_str + '...this is an ACK') + log.debug('%s code NLE_SUCCESS...this is an ACK' % debug_str) return msgs # No ACK...create a nlpacket object and append it to msgs else: + nle_intr_count = 0 # If debugs are enabled we will print the contents of the # packet via the decode_packet call...so avoid printing # two messages for one packet. if not nlpacket.debug: - log.info(debug_str) + log.debug(debug_str) if msgtype == RTM_NEWLINK or msgtype == RTM_DELLINK: msg = Link(msgtype, nlpacket.debug) @@ -444,7 +498,7 @@ class NetlinkManager(object): debug = RTM_NEWLINK in self.debug link = Link(RTM_NEWLINK, debug) - link.flags = NLM_F_CREATE | NLM_F_REQUEST + link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) link.add_attribute(Link.IFLA_IFNAME, ifname) link.add_attribute(Link.IFLA_LINK, ifindex) @@ -453,7 +507,7 @@ class NetlinkManager(object): Link.IFLA_INFO_DATA: ifla_info_data }) link.build_message(self.sequence.next(), self.pid) - return self.tx_nlpacket(link) + return self.tx_nlpacket_get_response(link) def link_add_vlan(self, ifindex, ifname, vlanid): """ @@ -512,7 +566,7 @@ class NetlinkManager(object): Link.IFLA_BRIDGE_VLAN_INFO: (vflags, vlanid) }) link.build_message(self.sequence.next(), self.pid) - return self.tx_nlpacket(link) + return self.tx_nlpacket_get_response(link) def link_add_bridge_vlan(self, ifindex, vlanid, pvid=False, untagged=False, master=False): self._link_bridge_vlan(RTM_SETLINK, ifindex, vlanid, pvid, untagged, master) @@ -536,11 +590,11 @@ class NetlinkManager(object): if_change = Link.IFF_UP link = Link(RTM_NEWLINK, debug) - link.flags = NLM_F_REQUEST + link.flags = NLM_F_REQUEST | NLM_F_ACK link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, if_flags, if_change) link.add_attribute(Link.IFLA_IFNAME, ifname) link.build_message(self.sequence.next(), self.pid) - return self.tx_nlpacket(link) + return self.tx_nlpacket_get_response(link) def link_set_protodown(self, ifname, state): """ @@ -552,12 +606,12 @@ class NetlinkManager(object): debug = RTM_NEWLINK in self.debug link = Link(RTM_NEWLINK, debug) - link.flags = NLM_F_REQUEST + link.flags = NLM_F_REQUEST | NLM_F_ACK link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, 0, 0) link.add_attribute(Link.IFLA_IFNAME, ifname) link.add_attribute(Link.IFLA_PROTO_DOWN, protodown) link.build_message(self.sequence.next(), self.pid) - return self.tx_nlpacket(link) + return self.tx_nlpacket_get_response(link) # ========= # Neighbors @@ -567,23 +621,54 @@ class NetlinkManager(object): service_hdr_flags = 0 nbr = Neighbor(RTM_NEWNEIGH, debug) - nbr.flags = NLM_F_CREATE | NLM_F_REQUEST + nbr.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK nbr.family = afi nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST) nbr.add_attribute(Neighbor.NDA_DST, ip) nbr.add_attribute(Neighbor.NDA_LLADDR, mac) nbr.build_message(self.sequence.next(), self.pid) - return self.tx_nlpacket(nbr) + return self.tx_nlpacket_get_response(nbr) def neighbor_del(self, afi, ifindex, ip, mac): debug = RTM_DELNEIGH in self.debug service_hdr_flags = 0 nbr = Neighbor(RTM_DELNEIGH, debug) - nbr.flags = NLM_F_REQUEST + nbr.flags = NLM_F_REQUEST | NLM_F_ACK nbr.family = afi nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST) nbr.add_attribute(Neighbor.NDA_DST, ip) nbr.add_attribute(Neighbor.NDA_LLADDR, mac) nbr.build_message(self.sequence.next(), self.pid) - return self.tx_nlpacket(nbr) + return self.tx_nlpacket_get_response(nbr) + + def link_add_vxlan(self, ifname, vxlanid, dstport=None, local=None, + group=None, learning='on', ageing=None): + + debug = RTM_NEWLINK in self.debug + + info_data = {Link.IFLA_VXLAN_ID: int(vxlanid)} + if dstport: + info_data[Link.IFLA_VXLAN_PORT] = int(dstport) + if local: + info_data[Link.IFLA_VXLAN_LOCAL] = local + if group: + info_data[Link.IFLA_VXLAN_GROUP] = group + + learning = 0 if learning == 'off' else 1 + info_data[Link.IFLA_VXLAN_LEARNING] = learning + + if ageing: + info_data[Link.IFLA_VXLAN_AGEING] = int(ageing) + + link = Link(RTM_NEWLINK, debug) + link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK + link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) + link.add_attribute(Link.IFLA_IFNAME, ifname) + link.add_attribute(Link.IFLA_LINKINFO, { + Link.IFLA_INFO_KIND: "vxlan", + Link.IFLA_INFO_DATA: info_data + }) + + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(link) diff --git a/nlmanager/nlpacket.py b/nlmanager/nlpacket.py index bce6044..f4539aa 100644 --- a/nlmanager/nlpacket.py +++ b/nlmanager/nlpacket.py @@ -39,6 +39,9 @@ from struct import pack, unpack, calcsize log = logging.getLogger(__name__) +# Interface name buffer size #define IFNAMSIZ 16 (kernel source) +IF_NAME_SIZE = 15 # 15 because python doesn't have \0 + # Netlink message types NLMSG_NOOP = 0x01 NLMSG_ERROR = 0x02 @@ -185,6 +188,15 @@ class Attribute(object): def __str__(self): return self.string + def set_value(self, value): + self.value = value + + def set_nested(self, nested): + self.nested = nested + + def set_net_byteorder(self, net_byteorder): + self.net_byteorder = net_byteorder + def pad_bytes_needed(self, length): """ Return the number of bytes that should be added to align on a 4-byte boundry @@ -205,6 +217,10 @@ class Attribute(object): return raw def encode(self): + + if not self.LEN: + raise Exception('Please define an encode() method in your child attribute class, or do not use AttributeGeneric') + length = self.HEADER_LEN + self.LEN attr_type_with_flags = self.atype @@ -327,6 +343,17 @@ class AttributeString(Attribute): raise +class AttributeStringInterfaceName(AttributeString): + + def __init__(self, atype, string, logger): + AttributeString.__init__(self, atype, string, logger) + + def set_value(self, value): + if value and len(value) > IF_NAME_SIZE: + raise Exception('interface name exceeds max length of %d' % IF_NAME_SIZE) + self.value = value + + class AttributeIPAddress(Attribute): def __init__(self, atype, string, family, logger): @@ -447,6 +474,14 @@ class AttributeGeneric(Attribute): raise +class AttributeOneByteValue(AttributeGeneric): + + def __init__(self, atype, string, logger): + Attribute.__init__(self, atype, string, logger) + self.PACK = '=B' + self.LEN = calcsize(self.PACK) + + class AttributeIFLA_AF_SPEC(Attribute): """ value will be a dictionary such as: @@ -706,7 +741,7 @@ class AttributeIFLA_LINKINFO(Attribute): kind = self.value[Link.IFLA_INFO_KIND] - if kind not in ('vlan', 'macvlan'): + if kind not in ('vlan', 'macvlan', 'vxlan'): raise Exception('Unsupported IFLA_INFO_KIND %s' % kind) # For now this assumes that all data will be packed in the native endian @@ -758,6 +793,67 @@ class AttributeIFLA_LINKINFO(Attribute): else: self.log.debug('Add support for encoding IFLA_INFO_DATA macvlan sub-attribute type %d' % info_data_type) + elif kind == 'vxlan': + if info_data_type in (Link.IFLA_VXLAN_ID, + Link.IFLA_VXLAN_LINK, + Link.IFLA_VXLAN_AGEING, + Link.IFLA_VXLAN_LIMIT, + Link.IFLA_VXLAN_PORT_RANGE): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(8) # length + sub_attr_payload.append(info_data_type) + + sub_attr_pack_layout.append('L') + sub_attr_payload.append(info_data_value) + + elif info_data_type in (Link.IFLA_VXLAN_GROUP, + Link.IFLA_VXLAN_LOCAL): + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(8) # length + sub_attr_payload.append(info_data_type) + + sub_attr_pack_layout.append('L') + + reorder = unpack('H", info_data_value) + sub_attr_payload.append(unpack("