def test_free_reverse_fail(self): ap = AddrPool(minaddr=1, maxaddr=1024, reverse=True) try: ap.free(0) except KeyError: pass
def test_alloc_aligned(self): ap = AddrPool(minaddr=1, maxaddr=1024) for i in range(1024): ap.alloc() try: ap.alloc() except KeyError: pass
def test_locate(self): ap = AddrPool() f = ap.alloc() base1, bit1, is_allocated1 = ap.locate(f) base2, bit2, is_allocated2 = ap.locate(f + 1) assert base1 == base2 assert bit2 == bit1 + 1 assert is_allocated1 assert not is_allocated2 assert ap.allocated == 1
def setup(self): self.ap = AddrPool() self.iftmp = 'pr2x{0}' self.ifaces = [] self.ifd = self.get_ifname() create_link(self.ifd, kind='dummy') self.ip = IPDB(mode=self.mode)
def __init__(self, ifname, port=68): RawSocket.__init__(self, ifname, listen_udp_port(port)) self.port = port # Create xid pool # # Every allocated xid will be released automatically after 1024 # alloc() calls, there is no need to call free(). Minimal xid == 16 self.xid_pool = AddrPool(minaddr=16, release=1024)
def setup(self): self.ip = IPRoute() self.ap = AddrPool() self.iftmp = 'pr2x{0}' try: self.dev, idx = self.create() self.ifaces = [idx] except IndexError: pass
def test_setaddr_allocated(self): ap = AddrPool() f = ap.alloc() base, bit, is_allocated = ap.locate(f + 1) assert not is_allocated assert ap.allocated == 1 ap.setaddr(f + 1, 'allocated') base, bit, is_allocated = ap.locate(f + 1) assert is_allocated assert ap.allocated == 2 ap.free(f + 1) base, bit, is_allocated = ap.locate(f + 1) assert not is_allocated assert ap.allocated == 1
class TestExplicit(object): ip = None mode = 'explicit' def setup(self): self.ap = AddrPool() self.iftmp = 'pr2x{0}' self.ifaces = [] self.ifd = self.get_ifname() create_link(self.ifd, kind='dummy') self.ip = IPDB(mode=self.mode) def get_ifname(self): naddr = self.ap.alloc() ifname = self.iftmp.format(naddr) self.ifaces.append(ifname) return ifname def teardown(self): for name in self.ifaces: try: with self.ip.interfaces[name] as i: i.remove() except: pass self.ip.release() self.ifaces = [] def test_simple(self): assert len(list(self.ip.interfaces.keys())) > 0 def test_empty_transaction(self): assert 'lo' in self.ip.interfaces with self.ip.interfaces.lo as i: assert isinstance(i.mtu, int) def test_idx_len(self): assert len(self.ip.by_name.keys()) == len(self.ip.by_index.keys()) def test_idx_set(self): assert set(self.ip.by_name.values()) == set(self.ip.by_index.values()) def test_idx_types(self): assert all(isinstance(i, int) for i in self.ip.by_index.keys()) assert all(isinstance(i, basestring) for i in self.ip.by_name.keys()) def test_ips(self): for name in self.ip.by_name: assert len(self.ip.interfaces[name]['ipaddr']) == \ len(get_ip_addr(name)) def test_reprs(self): assert isinstance(repr(self.ip.interfaces.lo.ipaddr), basestring) assert isinstance(repr(self.ip.interfaces.lo), basestring) def test_dotkeys(self): # self.ip.lo hint for ipython assert 'lo' in dir(self.ip.interfaces) assert 'lo' in self.ip.interfaces assert self.ip.interfaces.lo == self.ip.interfaces['lo'] # create attribute self.ip.interfaces['newitem'] = True self.ip.interfaces.newattr = True self.ip.interfaces.newitem = None assert self.ip.interfaces.newitem == self.ip.interfaces['newitem'] assert self.ip.interfaces.newitem is None # delete attribute del self.ip.interfaces.newitem del self.ip.interfaces.newattr assert 'newattr' not in dir(self.ip.interfaces) def test_vlan_slave_bridge(self): # https://github.com/svinota/pyroute2/issues/58 # based on the code by Petr Horáček dXname = self.get_ifname() vXname = self.get_ifname() vYname = self.get_ifname() brname = self.get_ifname() require_user('root') dX = self.ip.create(ifname=dXname, kind='dummy').commit() vX = self.ip.create(ifname=vXname, kind='vlan', link=dX, vlan_id=101).commit() vY = self.ip.create(ifname=vYname, kind='vlan', link=dX, vlan_id=102).commit() with self.ip.create(ifname=brname, kind='bridge') as i: i.add_port(vX) i.add_port(vY['index']) assert vX['index'] in self.ip.interfaces[brname]['ports'] assert vY['index'] in self.ip.interfaces[brname].ports assert vX['link'] == dX['index'] assert vY['link'] == dX['index'] assert vX['master'] == self.ip.interfaces[brname]['index'] assert vY['master'] == self.ip.interfaces[brname].index def _test_commit_hook_positive(self): require_user('root') # test callback, that adds an address by itself -- # just to check the possibility def cb(interface, snapshot, transaction): self.ip.nl.addr('add', self.ip.interfaces[self.ifd].index, address='172.16.22.1', mask=24) # register callback and check CB chain length self.ip.interfaces[self.ifd].register_commit_hook(cb) assert len(self.ip.interfaces[self.ifd]._commit_hooks) == 1 # create a transaction and commit it if self.ip.interfaces[self.ifd]._mode == 'explicit': self.ip.interfaces[self.ifd].begin() self.ip.interfaces[self.ifd].add_ip('172.16.21.1/24') self.ip.interfaces[self.ifd].commit() # added address should be there assert ('172.16.21.1', 24) in \ self.ip.interfaces[self.ifd].ipaddr # and the one, added by the callback, too assert ('172.16.22.1', 24) in \ self.ip.interfaces[self.ifd].ipaddr # unregister callback self.ip.interfaces[self.ifd].unregister_commit_hook(cb) assert len(self.ip.interfaces[self.ifd]._commit_hooks) == 0 def _test_commit_hook_negative(self): require_user('root') # test exception to differentiate class CBException(Exception): pass # test callback, that always fail def cb(interface, snapshot, transaction): raise CBException() # register callback and check CB chain length self.ip.interfaces[self.ifd].register_commit_hook(cb) assert len(self.ip.interfaces[self.ifd]._commit_hooks) == 1 # create a transaction and commit it; should fail # 'cause of the callback if self.ip.interfaces[self.ifd]._mode == 'explicit': self.ip.interfaces[self.ifd].begin() self.ip.interfaces[self.ifd].add_ip('172.16.21.1/24') try: self.ip.interfaces[self.ifd].commit() except CBException: pass # added address should be removed assert ('172.16.21.1', 24) not in \ self.ip.interfaces[self.ifd].ipaddr # unregister callback self.ip.interfaces[self.ifd].unregister_commit_hook(cb) assert len(self.ip.interfaces[self.ifd]._commit_hooks) == 0 def test_review(self): assert len(self.ip.interfaces.lo._tids) == 0 if self.ip.interfaces.lo._mode == 'explicit': self.ip.interfaces.lo.begin() self.ip.interfaces.lo.add_ip('172.16.21.1/24') r = self.ip.interfaces.lo.review() assert len(r['+ipaddr']) == 1 assert len(r['-ipaddr']) == 0 assert len(r['+ports']) == 0 assert len(r['-ports']) == 0 # +/-ipaddr, +/-ports assert len([i for i in r if r[i] is not None]) == 4 self.ip.interfaces.lo.drop() def test_rename(self): require_user('root') ifA = self.get_ifname() ifB = self.get_ifname() self.ip.create(ifname=ifA, kind='dummy').commit() if self.ip.interfaces[ifA]._mode == 'explicit': self.ip.interfaces[ifA].begin() self.ip.interfaces[ifA].ifname = ifB self.ip.interfaces[ifA].commit() assert ifB in self.ip.interfaces assert ifA not in self.ip.interfaces if self.ip.interfaces[ifB]._mode == 'explicit': self.ip.interfaces[ifB].begin() self.ip.interfaces[ifB].ifname = ifA self.ip.interfaces[ifB].commit() assert ifB not in self.ip.interfaces assert ifA in self.ip.interfaces def test_routes(self): require_user('root') assert '172.16.0.0/24' not in self.ip.routes # create a route with self.ip.routes.add({'dst': '172.16.0.0/24', 'gateway': '127.0.0.1'}) as r: pass assert '172.16.0.0/24' in self.ip.routes.keys() assert grep('ip ro', pattern='172.16.0.0/24.*127.0.0.1') # change the route with self.ip.routes['172.16.0.0/24'] as r: r.gateway = '127.0.0.2' assert self.ip.routes['172.16.0.0/24'].gateway == '127.0.0.2' assert grep('ip ro', pattern='172.16.0.0/24.*127.0.0.2') # delete the route with self.ip.routes['172.16.0.0/24'] as r: r.remove() assert '172.16.0.0/24' not in self.ip.routes.keys() assert not grep('ip ro', pattern='172.16.0.0/24') def test_route_metrics(self): require_user('root') assert '172.16.0.0/24' not in self.ip.routes.keys() # create a route self.ip.routes.add({'dst': '172.16.0.0/24', 'gateway': '127.0.0.1', 'metrics': {'mtu': 1360}}).commit() assert grep('ip ro', pattern='172.16.0.0/24.*mtu 1360') # change metrics with self.ip.routes['172.16.0.0/24'] as r: r.metrics.mtu = 1400 assert self.ip.routes['172.16.0.0/24']['metrics']['mtu'] == 1400 assert grep('ip ro', pattern='172.16.0.0/24.*mtu 1400') # delete the route with self.ip.routes['172.16.0.0/24'] as r: r.remove() assert '172.16.0.0/24' not in self.ip.routes.keys() assert not grep('ip ro', pattern='172.16.0.0/24') def _test_shadow(self, kind): ifA = self.get_ifname() a = self.ip.create(ifname=ifA, kind=kind).commit() if a._mode == 'explicit': a.begin() a.shadow().commit() assert ifA in self.ip.interfaces assert not grep('ip link', pattern=ifA) b = self.ip.create(ifname=ifA, kind=kind).commit() assert a == b assert grep('ip link', pattern=ifA) def test_shadow_bond(self): require_user('root') require_bond() self._test_shadow('bond') def test_shadow_bridge(self): require_user('root') require_bridge() self._test_shadow('bridge') def test_shadow_dummy(self): require_user('root') self._test_shadow('dummy') def test_updown(self): require_user('root') if self.ip.interfaces[self.ifd]._mode == 'explicit': self.ip.interfaces[self.ifd].begin() self.ip.interfaces[self.ifd].up() self.ip.interfaces[self.ifd].commit() assert self.ip.interfaces[self.ifd].flags & 1 if self.ip.interfaces[self.ifd]._mode == 'explicit': self.ip.interfaces[self.ifd].begin() self.ip.interfaces[self.ifd].down() self.ip.interfaces[self.ifd].commit() assert not (self.ip.interfaces[self.ifd].flags & 1) def test_fail_ipaddr(self): require_user('root') ifA = self.get_ifname() i = self.ip.create(ifname=ifA, kind='dummy').commit() assert not len(i.ipaddr) if i._mode == 'explicit': i.begin() i.add_ip('123.456.789.1024/153') try: i.commit() except socket.error as e: if not e.args[0].startswith('illegal IP'): raise assert not len(i.ipaddr) if i._mode == 'explicit': i.begin() i.remove().commit() assert ifA not in self.ip.interfaces def test_json_dump(self): require_user('root') ifA = self.get_ifname() ifB = self.get_ifname() # set up the interface with self.ip.create(kind='dummy', ifname=ifA) as i: i.add_ip('172.16.0.1/24') i.add_ip('172.16.0.2/24') i.up() # make a backup backup = self.ip.interfaces[ifA].dump() assert isinstance(backup, dict) # remove index -- make it portable del backup['index'] # serialize backup = json.dumps(backup) # remove the interface with self.ip.interfaces[ifA] as i: i.remove() # create again, but with different name self.ip.create(kind='dummy', ifname=ifB).commit() # load the backup # 1. prepare to the restore: bring it down with self.ip.interfaces[ifB] as i: i.down() # 2. please notice, the interface will be renamed after the backup t = self.ip.interfaces[ifB].load(json.loads(backup)) self.ip.interfaces[ifB].commit(transaction=t) # check :) assert ifA in self.ip.interfaces assert ifB not in self.ip.interfaces assert ('172.16.0.1', 24) in self.ip.interfaces[ifA].ipaddr assert ('172.16.0.2', 24) in self.ip.interfaces[ifA].ipaddr assert self.ip.interfaces[ifA].flags & 1 def test_freeze(self): require_user('root') interface = self.ip.interfaces[self.ifd] # set up the interface with interface as i: i.add_ip('172.16.0.1/24') i.add_ip('172.16.1.1/24') i.up() # check assert ('172.16.0.1', 24) in interface.ipaddr assert ('172.16.1.1', 24) in interface.ipaddr assert interface.flags & 1 # assert routine def probe(): # The freeze results are dynamic: it is not a real freeze, # it is a restore routine. So it takes time for results # to stabilize err = None for _ in range(3): err = None interface.ipaddr.set_target((('172.16.0.1', 24), ('172.16.1.1', 24))) interface.ipaddr.target.wait() try: assert ('172.16.0.1', 24) in interface.ipaddr assert ('172.16.1.1', 24) in interface.ipaddr assert interface.flags & 1 break except AssertionError as e: err = e continue except Exception as e: err = e break if err is not None: interface.unfreeze() i2.close() raise err # freeze interface.freeze() # change the interface somehow i2 = IPRoute() i2.addr('delete', interface.index, '172.16.0.1', 24) i2.addr('delete', interface.index, '172.16.1.1', 24) probe() # unfreeze self.ip.interfaces[self.ifd].unfreeze() try: i2.addr('delete', interface.index, '172.16.0.1', 24) i2.addr('delete', interface.index, '172.16.1.1', 24) except: pass finally: i2.close() # should be up, but w/o addresses interface.ipaddr.set_target(set()) interface.ipaddr.target.wait(3) assert ('172.16.0.1', 24) not in self.ip.interfaces[self.ifd].ipaddr assert ('172.16.1.1', 24) not in self.ip.interfaces[self.ifd].ipaddr assert self.ip.interfaces[self.ifd].flags & 1 def test_snapshots(self): require_user('root') ifB = self.get_ifname() # set up the interface with self.ip.interfaces[self.ifd] as i: i.add_ip('172.16.0.1/24') i.up() # check it assert ('172.16.0.1', 24) in self.ip.interfaces[self.ifd].ipaddr assert self.ip.interfaces[self.ifd].flags & 1 # make a snapshot s = self.ip.interfaces[self.ifd].snapshot() i = self.ip.interfaces[self.ifd] # check it assert i.last_snapshot_id() == s # unset the interface with self.ip.interfaces[self.ifd] as i: i.del_ip('172.16.0.1/24') i.down() # we can not rename the interface while it is up, # so do it in two turns with self.ip.interfaces[self.ifd] as i: i.ifname = ifB # check it assert ifB in self.ip.interfaces assert self.ifd not in self.ip.interfaces y = self.ip.interfaces[ifB] assert i == y assert ('172.16.0.1', 24) not in y.ipaddr assert not (y.flags & 1) # revert snapshot y.revert(s).commit() # check it assert ifB not in self.ip.interfaces assert self.ifd in self.ip.interfaces assert ('172.16.0.1', 24) in self.ip.interfaces[self.ifd].ipaddr assert self.ip.interfaces[self.ifd].flags & 1 def _test_ipv(self, ipv, kind): require_user('root') ifA = self.get_ifname() i = self.ip.create(kind=kind, ifname=ifA).commit() if self.ip.interfaces[ifA]._mode == 'explicit': self.ip.interfaces[ifA].begin() if ipv == 4: addr = '172.16.0.1/24' elif ipv == 6: addr = 'fdb3:84e5:4ff4:55e4::1/64' else: raise Exception('bad IP version') i.add_ip(addr).commit() pre_target = addr.split('/') target = (pre_target[0], int(pre_target[1])) assert target in i['ipaddr'] def test_ipv4_dummy(self): self._test_ipv(4, 'dummy') def test_ipv4_bond(self): self._test_ipv(4, 'bond') def test_ipv4_bridge(self): self._test_ipv(4, 'bridge') def test_ipv6_dummy(self): self._test_ipv(6, 'dummy') def test_ipv6_bond(self): self._test_ipv(6, 'bond') def test_ipv6_bridge(self): self._test_ipv(6, 'bridge') def test_create_tuntap_fail(self): try: self.ip.create(ifname='fAiL', kind='tuntap', mode='fail').commit() except: assert not grep('ip link', pattern='fAiL') return raise Exception('tuntap create succeded') def test_create_tuntap(self): require_user('root') ifA = self.get_ifname() self.ip.create(ifname=ifA, kind='tuntap', mode='tap', uid=1, gid=1).commit() assert ifA in self.ip.interfaces assert grep('ip link', pattern=ifA) def test_ovs_kind_aliases(self): require_user('root') require_executable('ovs-vsctl') ifA = self.get_ifname() ifB = self.get_ifname() self.ip.create(ifname=ifA, kind='ovs-bridge').commit() self.ip.create(ifname=ifB, kind='openvswitch').commit() assert ifA in self.ip.interfaces assert ifB in self.ip.interfaces assert grep('ip link', pattern=ifA) assert grep('ip link', pattern=ifB) def test_create_veth(self): require_user('root') ifA = self.get_ifname() ifB = self.get_ifname() self.ip.create(ifname=ifA, kind='veth', peer=ifB).commit() assert ifA in self.ip.interfaces assert ifB in self.ip.interfaces def test_create_fail(self): require_user('root') ifA = self.get_ifname() # create with mac 11:22:33:44:55:66 should fail i = self.ip.create(kind='dummy', ifname=ifA, address='11:22:33:44:55:66') try: i.commit() except NetlinkError: pass assert i._mode == 'invalid' assert ifA not in self.ip.interfaces def test_create_dqn(self): require_user('root') ifA = self.get_ifname() i = self.ip.create(kind='dummy', ifname=ifA) i.add_ip('172.16.0.1/255.255.255.0') i.commit() assert ('172.16.0.1', 24) in self.ip.interfaces[ifA].ipaddr assert '172.16.0.1/24' in get_ip_addr(interface=ifA) def test_create_double_reuse(self): require_user('root') ifA = self.get_ifname() # create an interface i1 = self.ip.create(kind='dummy', ifname=ifA).commit() try: # this call should fail on the very first step: # `bala` interface already exists self.ip.create(kind='dummy', ifname=ifA) except CreateException: pass # add `reuse` keyword -- now should pass i2 = self.ip.create(kind='dummy', ifname=ifA, reuse=True).commit() # assert that we have got references to the same interface assert i1 == i2 def _create_double(self, kind): require_user('root') ifA = self.get_ifname() self.ip.create(kind=kind, ifname=ifA).commit() try: self.ip.create(kind=kind, ifname=ifA).commit() except CreateException: pass def test_create_double_dummy(self): self._create_double('dummy') def test_create_double_bridge(self): self._create_double('bridge') def test_create_double_bond(self): self._create_double('bond') def test_create_plain(self): require_user('root') ifA = self.get_ifname() i = self.ip.create(kind='dummy', ifname=ifA) i.add_ip('172.16.0.1/24') i.commit() assert ('172.16.0.1', 24) in self.ip.interfaces[ifA].ipaddr assert '172.16.0.1/24' in get_ip_addr(interface=ifA) def test_create_and_remove(self): require_user('root') ifA = self.get_ifname() with self.ip.create(kind='dummy', ifname=ifA) as i: i.add_ip('172.16.0.1/24') assert ('172.16.0.1', 24) in self.ip.interfaces[ifA].ipaddr assert '172.16.0.1/24' in get_ip_addr(interface=ifA) with self.ip.interfaces[ifA] as i: i.remove() assert ifA not in self.ip.interfaces def test_dqn_mask(self): require_user('root') iface = self.ip.interfaces[self.ifd] with iface as i: i.add_ip('172.16.0.1/24') i.add_ip('172.16.0.2', mask=24) i.add_ip('172.16.0.3/255.255.255.0') i.add_ip('172.16.0.4', mask='255.255.255.0') assert ('172.16.0.1', 24) in iface.ipaddr assert ('172.16.0.2', 24) in iface.ipaddr assert ('172.16.0.3', 24) in iface.ipaddr assert ('172.16.0.4', 24) in iface.ipaddr def _create_master(self, kind, **kwarg): ifM = self.get_ifname() ifP1 = self.get_ifname() ifP2 = self.get_ifname() self.ip.create(kind='dummy', ifname=ifP1).commit() self.ip.create(kind='dummy', ifname=ifP2).commit() with self.ip.create(kind=kind, ifname=ifM, **kwarg) as i: i.add_port(self.ip.interfaces[ifP1]) i.add_ip('172.16.0.1/24') with self.ip.interfaces[ifM] as i: i.add_port(self.ip.interfaces[ifP2]) i.add_ip('172.16.0.2/24') assert ('172.16.0.1', 24) in self.ip.interfaces[ifM].ipaddr assert ('172.16.0.2', 24) in self.ip.interfaces[ifM].ipaddr assert '172.16.0.1/24' in get_ip_addr(interface=ifM) assert '172.16.0.2/24' in get_ip_addr(interface=ifM) assert self.ip.interfaces[ifP1].if_master == \ self.ip.interfaces[ifM].index assert self.ip.interfaces[ifP2].if_master == \ self.ip.interfaces[ifM].index with self.ip.interfaces[ifM] as i: i.del_port(self.ip.interfaces[ifP1]) i.del_port(self.ip.interfaces[ifP2]) i.del_ip('172.16.0.1/24') i.del_ip('172.16.0.2/24') assert ('172.16.0.1', 24) not in self.ip.interfaces[ifM].ipaddr assert ('172.16.0.2', 24) not in self.ip.interfaces[ifM].ipaddr assert '172.16.0.1/24' not in get_ip_addr(interface=ifM) assert '172.16.0.2/24' not in get_ip_addr(interface=ifM) assert self.ip.interfaces[ifP1].if_master is None assert self.ip.interfaces[ifP2].if_master is None def test_create_bridge(self): require_user('root') require_bridge() self._create_master('bridge') def test_create_bond(self): require_user('root') require_bond() self._create_master('bond') def test_create_team(self): require_user('root') require_executable('teamd') require_executable('teamdctl') self._create_master('team') def test_create_ovs(self): require_user('root') require_executable('ovs-vsctl') self._create_master('openvswitch') def test_create_bond2(self): require_user('root') require_bond() self._create_master('bond', bond_mode=2) def _create_macvx_mode(self, kind, mode): require_user('root') ifL = self.get_ifname() ifV = self.get_ifname() ifdb = self.ip.interfaces self.ip.create(kind='dummy', ifname=ifL).commit() self.ip.create(**{'kind': kind, 'link': ifdb[ifL], 'ifname': ifV, '%s_mode' % kind: mode}).commit() ip2 = IPDB() ifdb = ip2.interfaces try: assert ifdb[ifV].link == ifdb[ifL].index assert ifdb[ifV]['%s_mode' % kind] == mode except Exception: raise finally: ip2.release() def test_create_macvtap_vepa(self): return self._create_macvx_mode('macvtap', 'vepa') def test_create_macvtap_bridge(self): return self._create_macvx_mode('macvtap', 'bridge') def test_create_macvlan_vepa(self): return self._create_macvx_mode('macvlan', 'vepa') def test_create_macvlan_bridge(self): return self._create_macvx_mode('macvlan', 'bridge') def test_create_gre(self): require_user('root') ifL = self.get_ifname() ifV = self.get_ifname() with self.ip.create(kind='dummy', ifname=ifL) as i: i.add_ip('172.16.0.1/24') i.up() self.ip.create(kind='gre', ifname=ifV, gre_local='172.16.0.1', gre_remote='172.16.0.2', gre_ttl=16).commit() ip2 = IPDB() ifdb = ip2.interfaces try: assert ifdb[ifV].gre_local == '172.16.0.1' assert ifdb[ifV].gre_remote == '172.16.0.2' assert ifdb[ifV].gre_ttl == 16 except Exception: raise finally: ip2.release() def test_create_vxlan(self): require_user('root') ifL = self.get_ifname() ifV = self.get_ifname() ifdb = self.ip.interfaces self.ip.create(kind='dummy', ifname=ifL).commit() self.ip.create(kind='vxlan', ifname=ifV, vxlan_link=ifdb[ifL], vxlan_id=101, vxlan_group='239.1.1.1').commit() ip2 = IPDB() ifdb = ip2.interfaces try: assert ifdb[ifV].vxlan_link == ifdb[ifL].index assert ifdb[ifV].vxlan_group == '239.1.1.1' assert ifdb[ifV].vxlan_id == 101 except Exception: raise finally: ip2.release() def test_create_vlan_by_interface(self): require_user('root') require_8021q() ifL = self.get_ifname() ifV = self.get_ifname() self.ip.create(kind='dummy', ifname=ifL).commit() self.ip.create(kind='vlan', ifname=ifV, link=self.ip.interfaces[ifL], vlan_id=101).commit() assert self.ip.interfaces[ifV].link == \ self.ip.interfaces[ifL].index def test_create_vlan_by_index(self): require_user('root') require_8021q() ifL = self.get_ifname() ifV = self.get_ifname() self.ip.create(kind='dummy', ifname=ifL).commit() self.ip.create(kind='vlan', ifname=ifV, link=self.ip.interfaces[ifL].index, vlan_id=101).commit() assert self.ip.interfaces[ifV].link == \ self.ip.interfaces[ifL].index def test_remove_secondaries(self): require_user('root') ifA = self.get_ifname() with self.ip.create(kind='dummy', ifname=ifA) as i: i.add_ip('172.16.0.1', 24) i.add_ip('172.16.0.2', 24) assert ifA in self.ip.interfaces assert ('172.16.0.1', 24) in self.ip.interfaces[ifA].ipaddr assert ('172.16.0.2', 24) in self.ip.interfaces[ifA].ipaddr assert '172.16.0.1/24' in get_ip_addr(interface=ifA) assert '172.16.0.2/24' in get_ip_addr(interface=ifA) if i._mode == 'explicit': i.begin() i.del_ip('172.16.0.1', 24) i.del_ip('172.16.0.2', 24) i.commit() assert ('172.16.0.1', 24) not in self.ip.interfaces[ifA].ipaddr assert ('172.16.0.2', 24) not in self.ip.interfaces[ifA].ipaddr assert '172.16.0.1/24' not in get_ip_addr(interface=ifA) assert '172.16.0.2/24' not in get_ip_addr(interface=ifA)
def test_free(self): ap = AddrPool(minaddr=1, maxaddr=1024) f = ap.alloc() ap.free(f)
def fix_message(self, msg): pass # 8<----------------------------------------------------------- # Singleton, containing possible modifiers to the NetlinkSocket # bind() call. # # Normally, you can open only one netlink connection for one # process, but there is a hack. Current PID_MAX_LIMIT is 2^22, # so we can use the rest to modify the pid field. # # See also libnl library, lib/socket.c:generate_local_port() sockets = AddrPool(minaddr=0x0, maxaddr=0x3ff, reverse=True) # 8<----------------------------------------------------------- class LockProxy(object): def __init__(self, factory, key): self.factory = factory self.refcount = 0 self.key = key self.internal = threading.Lock() self.lock = factory.klass() def acquire(self, *argv, **kwarg): with self.internal:
class NetlinkMixin(object): ''' Generic netlink socket ''' def __init__(self, family=NETLINK_GENERIC, port=None, pid=None, fileno=None): # # That's a trick. Python 2 is not able to construct # sockets from an open FD. # # So raise an exception, if the major version is < 3 # and fileno is not None. # # Do NOT use fileno in a core pyroute2 functionality, # since the core should be both Python 2 and 3 # compatible. # super(NetlinkMixin, self).__init__() if fileno is not None and sys.version_info[0] < 3: raise NotImplementedError('fileno parameter is not supported ' 'on Python < 3.2') # 8<----------------------------------------- self.addr_pool = AddrPool(minaddr=0x000000ff, maxaddr=0x0000ffff) self.epid = None self.port = 0 self.fixed = True self.family = family self._fileno = fileno self.backlog = {0: []} self.callbacks = [] # [(predicate, callback, args), ...] self.pthread = None self.closed = False self.capabilities = {'create_bridge': True, 'create_bond': True, 'create_dummy': True, 'provide_master': config.kernel[0] > 2} self.backlog_lock = threading.Lock() self.read_lock = threading.Lock() self.change_master = threading.Event() self.lock = LockFactory() self._sock = None self._ctrl_read, self._ctrl_write = os.pipe() self.buffer_queue = Queue() self.qsize = 0 self.log = [] self.get_timeout = 30 self.get_timeout_exception = None if pid is None: self.pid = os.getpid() & 0x3fffff self.port = port self.fixed = self.port is not None elif pid == 0: self.pid = os.getpid() else: self.pid = pid # 8<----------------------------------------- self.groups = 0 self.marshal = Marshal() # 8<----------------------------------------- # Set defaults self.post_init() def clone(self): return type(self)(family=self.family) def close(self): try: os.close(self._ctrl_write) os.close(self._ctrl_read) except OSError: # ignore the case when it is closed already pass def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def release(self): log.warning("The `release()` call is deprecated") log.warning("Use `close()` instead") self.close() def register_callback(self, callback, predicate=lambda x: True, args=None): ''' Register a callback to run on a message arrival. Callback is the function that will be called with the message as the first argument. Predicate is the optional callable object, that returns True or False. Upon True, the callback will be called. Upon False it will not. Args is a list or tuple of arguments. Simplest example, assume ipr is the IPRoute() instance:: # create a simplest callback that will print messages def cb(msg): print(msg) # register callback for any message: ipr.register_callback(cb) More complex example, with filtering:: # Set object's attribute after the message key def cb(msg, obj): obj.some_attr = msg["some key"] # Register the callback only for the loopback device, index 1: ipr.register_callback(cb, lambda x: x.get('index', None) == 1, (self, )) Please note: you do **not** need to register the default 0 queue to invoke callbacks on broadcast messages. Callbacks are iterated **before** messages get enqueued. ''' if args is None: args = [] self.callbacks.append((predicate, callback, args)) def unregister_callback(self, callback): ''' Remove the first reference to the function from the callback register ''' cb = tuple(self.callbacks) for cr in cb: if cr[1] == callback: self.callbacks.pop(cb.index(cr)) return def register_policy(self, policy, msg_class=None): ''' Register netlink encoding/decoding policy. Can be specified in two ways: `nlsocket.register_policy(MSG_ID, msg_class)` to register one particular rule, or `nlsocket.register_policy({MSG_ID1: msg_class})` to register several rules at once. E.g.:: policy = {RTM_NEWLINK: ifinfmsg, RTM_DELLINK: ifinfmsg, RTM_NEWADDR: ifaddrmsg, RTM_DELADDR: ifaddrmsg} nlsocket.register_policy(policy) One can call `register_policy()` as many times, as one want to -- it will just extend the current policy scheme, not replace it. ''' if isinstance(policy, int) and msg_class is not None: policy = {policy: msg_class} assert isinstance(policy, dict) for key in policy: self.marshal.msg_map[key] = policy[key] return self.marshal.msg_map def unregister_policy(self, policy): ''' Unregister policy. Policy can be: - int -- then it will just remove one policy - list or tuple of ints -- remove all given - dict -- remove policies by keys from dict In the last case the routine will ignore dict values, it is implemented so just to make it compatible with `get_policy_map()` return value. ''' if isinstance(policy, int): policy = [policy] elif isinstance(policy, dict): policy = list(policy) assert isinstance(policy, (tuple, list, set)) for key in policy: del self.marshal.msg_map[key] return self.marshal.msg_map def get_policy_map(self, policy=None): ''' Return policy for a given message type or for all message types. Policy parameter can be either int, or a list of ints. Always return dictionary. ''' if policy is None: return self.marshal.msg_map if isinstance(policy, int): policy = [policy] assert isinstance(policy, (list, tuple, set)) ret = {} for key in policy: ret[key] = self.marshal.msg_map[key] return ret def sendto(self, *argv, **kwarg): return self._sendto(*argv, **kwarg) def recv(self, *argv, **kwarg): return self._recv(*argv, **kwarg) def async_recv(self): poll = select.poll() poll.register(self._sock, select.POLLIN | select.POLLPRI) poll.register(self._ctrl_read, select.POLLIN | select.POLLPRI) sockfd = self._sock.fileno() while True: events = poll.poll() for (fd, event) in events: if fd == sockfd: try: self.buffer_queue.put(self._sock.recv(1024 * 1024)) except Exception as e: self.buffer_queue.put(e) else: return def put(self, msg, msg_type, msg_flags=NLM_F_REQUEST, addr=(0, 0), msg_seq=0, msg_pid=None): ''' Construct a message from a dictionary and send it to the socket. Parameters: - msg -- the message in the dictionary format - msg_type -- the message type - msg_flags -- the message flags to use in the request - addr -- `sendto()` addr, default `(0, 0)` - msg_seq -- sequence number to use - msg_pid -- pid to use, if `None` -- use os.getpid() Example:: s = IPRSocket() s.bind() s.put({'index': 1}, RTM_GETLINK) s.get() s.close() Please notice, that the return value of `s.get()` can be not the result of `s.put()`, but any broadcast message. To fix that, use `msg_seq` -- the response must contain the same `msg['header']['sequence_number']` value. ''' if msg_seq != 0: self.lock[msg_seq].acquire() try: if msg_seq not in self.backlog: self.backlog[msg_seq] = [] if not isinstance(msg, nlmsg): msg_class = self.marshal.msg_map[msg_type] msg = msg_class(msg) if msg_pid is None: msg_pid = self.epid or os.getpid() msg['header']['type'] = msg_type msg['header']['flags'] = msg_flags msg['header']['sequence_number'] = msg_seq msg['header']['pid'] = msg_pid self.sendto_gate(msg, addr) except: raise finally: if msg_seq != 0: self.lock[msg_seq].release() def sendto_gate(self, msg, addr): msg.encode() self.sendto(msg.buf.getvalue(), addr) def get(self, bufsize=DEFAULT_RCVBUF, msg_seq=0, terminate=None): ''' Get parsed messages list. If `msg_seq` is given, return only messages with that `msg['header']['sequence_number']`, saving all other messages into `self.backlog`. The routine is thread-safe. The `bufsize` parameter can be: - -1: bufsize will be calculated from the first 4 bytes of the network data - 0: bufsize will be calculated from SO_RCVBUF sockopt - int >= 0: just a bufsize ''' ctime = time.time() with self.lock[msg_seq]: if bufsize == -1: # get bufsize from the network data bufsize = struct.unpack("I", self.recv(4, MSG_PEEK))[0] elif bufsize == 0: # get bufsize from SO_RCVBUF bufsize = self.getsockopt(SOL_SOCKET, SO_RCVBUF) // 2 ret = [] enough = False while not enough: # 8<----------------------------------------------------------- # # This stage changes the backlog, so use mutex to # prevent side changes self.backlog_lock.acquire() ## # Stage 1. BEGIN # # 8<----------------------------------------------------------- # # Check backlog and return already collected # messages. # if msg_seq == 0 and self.backlog[0]: # Zero queue. # # Load the backlog, if there is valid # content in it ret.extend(self.backlog[0]) self.backlog[0] = [] # And just exit self.backlog_lock.release() break elif self.backlog.get(msg_seq, None): # Any other msg_seq. # # Collect messages up to the terminator. # Terminator conditions: # * NLMSG_ERROR != 0 # * NLMSG_DONE # * terminate() function (if defined) # * not NLM_F_MULTI # # Please note, that if terminator not occured, # more `recv()` rounds CAN be required. for msg in tuple(self.backlog[msg_seq]): # Drop the message from the backlog, if any self.backlog[msg_seq].remove(msg) # If there is an error, raise exception if msg['header'].get('error', None) is not None: self.backlog[0].extend(self.backlog[msg_seq]) del self.backlog[msg_seq] # The loop is done self.backlog_lock.release() raise msg['header']['error'] # If it is the terminator message, say "enough" # and requeue all the rest into Zero queue if (msg['header']['type'] == NLMSG_DONE) or \ (terminate is not None and terminate(msg)): # The loop is done enough = True # If it is just a normal message, append it to # the response if not enough: ret.append(msg) # But finish the loop on single messages if not msg['header']['flags'] & NLM_F_MULTI: # but not multi -- so end the loop enough = True # Enough is enough, requeue the rest and delete # our backlog if enough: self.backlog[0].extend(self.backlog[msg_seq]) del self.backlog[msg_seq] break # Next iteration self.backlog_lock.release() else: # Stage 1. END # # 8<------------------------------------------------------- # # Stage 2. BEGIN # # 8<------------------------------------------------------- # # Receive the data from the socket and put the messages # into the backlog # self.backlog_lock.release() ## # # Control the timeout. We should not be within the # function more than TIMEOUT seconds. All the locks # MUST be released here. # if time.time() - ctime > self.get_timeout: if self.get_timeout_exception: raise self.get_timeout_exception() else: return ret # if self.read_lock.acquire(False): self.change_master.clear() # If the socket is free to read from, occupy # it and wait for the data # # This is a time consuming process, so all the # locks, except the read lock must be released data = self.recv(bufsize) # Parse data msgs = self.marshal.parse(data) # Reset ctime -- timeout should be measured # for every turn separately ctime = time.time() # current = self.buffer_queue.qsize() delta = current - self.qsize if delta > 10: delay = min(3, max(0.01, float(current) / 60000)) message = ("Packet burst: the reader thread " "priority is increased, beware of " "delays on netlink calls\n\tCounters: " "delta=%s qsize=%s delay=%s " % (delta, current, delay)) if delay < 1: log.debug(message) else: log.warning(message) time.sleep(delay) self.qsize = current # We've got the data, lock the backlog again self.backlog_lock.acquire() for msg in msgs: seq = msg['header']['sequence_number'] if seq not in self.backlog: if msg['header']['type'] == NLMSG_ERROR: # Drop orphaned NLMSG_ERROR messages continue seq = 0 # 8<----------------------------------------------- # Callbacks section for cr in self.callbacks: try: if cr[0](msg): cr[1](msg, *cr[2]) except: log.warning("Callback fail: %s" % (cr)) log.warning(traceback.format_exc()) # 8<----------------------------------------------- self.backlog[seq].append(msg) # We finished with the backlog, so release the lock self.backlog_lock.release() # Now wake up other threads self.change_master.set() # Finally, release the read lock: all data processed self.read_lock.release() else: # If the socket is occupied and there is still no # data for us, wait for the next master change or # for a timeout self.change_master.wait(1) # 8<------------------------------------------------------- # # Stage 2. END # # 8<------------------------------------------------------- return ret def nlm_request(self, msg, msg_type, msg_flags=NLM_F_REQUEST | NLM_F_DUMP, terminate=None, exception_catch=Exception, exception_handler=None): def do_try(): msg_seq = self.addr_pool.alloc() with self.lock[msg_seq]: try: msg.reset() self.put(msg, msg_type, msg_flags, msg_seq=msg_seq) ret = self.get(msg_seq=msg_seq, terminate=terminate) return ret except Exception: raise finally: # Ban this msg_seq for 0xff rounds # # It's a long story. Modern kernels for RTM_SET.* # operations always return NLMSG_ERROR(0) == success, # even not setting NLM_F_MULTY flag on other response # messages and thus w/o any NLMSG_DONE. So, how to detect # the response end? One can not rely on NLMSG_ERROR on # old kernels, but we have to support them too. Ty, we # just ban msg_seq for several rounds, and NLMSG_ERROR, # being received, will become orphaned and just dropped. # # Hack, but true. self.addr_pool.free(msg_seq, ban=0xff) while True: try: return do_try() except exception_catch as e: if exception_handler and not exception_handler(e): continue raise except Exception: raise
class DHCP4Socket(RawSocket): ''' Parameters: * ifname -- interface name to work on This raw socket binds to an interface and installs BPF filter to get only its UDP port. It can be used in poll/select and provides also the context manager protocol, so can be used in `with` statements. It does not provide any DHCP state machine, and does not inspect DHCP packets, it is totally up to you. No default values are provided here, except `xid` -- DHCP transaction ID. If `xid` is not provided, DHCP4Socket generates it for outgoing messages. ''' def __init__(self, ifname, port=68): RawSocket.__init__(self, ifname, listen_udp_port(port)) self.port = port # Create xid pool # # Every allocated xid will be released automatically after 1024 # alloc() calls, there is no need to call free(). Minimal xid == 16 self.xid_pool = AddrPool(minaddr=16, release=1024) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def put(self, msg=None, dport=67): ''' Put DHCP message. Parameters: * msg -- dhcp4msg instance * dport -- DHCP server port If `msg` is not provided, it is constructed as default BOOTREQUEST + DHCPDISCOVER. Examples:: sock.put(dhcp4msg({'op': BOOTREQUEST, 'chaddr': 'ff:11:22:33:44:55', 'options': {'message_type': DHCPREQUEST, 'parameter_list': [1, 3, 6, 12, 15], 'requested_ip': '172.16.101.2', 'server_id': '172.16.101.1'}})) The method returns dhcp4msg that was sent, so one can get from there `xid` (transaction id) and other details. ''' # DHCP layer dhcp = msg or dhcp4msg({'chaddr': self.l2addr}) # dhcp transaction id if dhcp['xid'] is None: dhcp['xid'] = self.xid_pool.alloc() data = dhcp.encode().buf # UDP layer udp = udpmsg({'sport': self.port, 'dport': dport, 'len': 8 + len(data)}) udph = udp4_pseudo_header({'dst': '255.255.255.255', 'len': 8 + len(data)}) udp['csum'] = self.csum(udph.encode().buf + udp.encode().buf + data) udp.reset() # IPv4 layer ip4 = ip4msg({'len': 20 + 8 + len(data), 'proto': 17, 'dst': '255.255.255.255'}) ip4['csum'] = self.csum(ip4.encode().buf) ip4.reset() # MAC layer eth = ethmsg({'dst': 'ff:ff:ff:ff:ff:ff', 'src': self.l2addr, 'type': 0x800}) data = eth.encode().buf +\ ip4.encode().buf +\ udp.encode().buf +\ data self.send(data) dhcp.reset() return dhcp def get(self): ''' Get the next incoming packet from the socket and try to decode it as IPv4 DHCP. No analysis is done here, only MAC/IPv4/UDP headers are stripped out, and the rest is interpreted as DHCP. ''' (data, addr) = self.recvfrom(4096) eth = ethmsg(buf=data).decode() ip4 = ip4msg(buf=data, offset=eth.offset).decode() udp = udpmsg(buf=data, offset=ip4.offset).decode() return dhcp4msg(buf=data, offset=udp.offset).decode()
class DHCP4Socket(RawSocket): ''' Parameters: * ifname -- interface name to work on This raw socket binds to an interface and installs BPF filter to get only its UDP port. It can be used in poll/select and provides also the context manager protocol, so can be used in `with` statements. It does not provide any DHCP state machine, and does not inspect DHCP packets, it is totally up to you. No default values are provided here, except `xid` -- DHCP transaction ID. If `xid` is not provided, DHCP4Socket generates it for outgoing messages. ''' def __init__(self, ifname, port=68): RawSocket.__init__(self, ifname, listen_udp_port(port)) self.port = port # Create xid pool # # Every allocated xid will be released automatically after 1024 # alloc() calls, there is no need to call free(). Minimal xid == 16 self.xid_pool = AddrPool(minaddr=16, release=1024) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def put(self, msg=None, dport=67): ''' Put DHCP message. Parameters: * msg -- dhcp4msg instance * dport -- DHCP server port If `msg` is not provided, it is constructed as default BOOTREQUEST + DHCPDISCOVER. Examples:: sock.put(dhcp4msg({'op': BOOTREQUEST, 'chaddr': 'ff:11:22:33:44:55', 'options': {'message_type': DHCPREQUEST, 'parameter_list': [1, 3, 6, 12, 15], 'requested_ip': '172.16.101.2', 'server_id': '172.16.101.1'}})) The method returns dhcp4msg that was sent, so one can get from there `xid` (transaction id) and other details. ''' # DHCP layer dhcp = msg or dhcp4msg({'chaddr': self.l2addr}) # dhcp transaction id if dhcp['xid'] is None: dhcp['xid'] = self.xid_pool.alloc() data = dhcp.encode().buf # UDP layer udp = udpmsg({ 'sport': self.port, 'dport': dport, 'len': 8 + len(data) }) udph = udp4_pseudo_header({ 'dst': '255.255.255.255', 'len': 8 + len(data) }) udp['csum'] = self.csum(udph.encode().buf + udp.encode().buf + data) udp.reset() # IPv4 layer ip4 = ip4msg({ 'len': 20 + 8 + len(data), 'proto': 17, 'dst': '255.255.255.255' }) ip4['csum'] = self.csum(ip4.encode().buf) ip4.reset() # MAC layer eth = ethmsg({ 'dst': 'ff:ff:ff:ff:ff:ff', 'src': self.l2addr, 'type': 0x800 }) data = eth.encode().buf +\ ip4.encode().buf +\ udp.encode().buf +\ data self.send(data) dhcp.reset() return dhcp def get(self): ''' Get the next incoming packet from the socket and try to decode it as IPv4 DHCP. No analysis is done here, only MAC/IPv4/UDP headers are stripped out, and the rest is interpreted as DHCP. ''' (data, addr) = self.recvfrom(4096) eth = ethmsg(buf=data).decode() ip4 = ip4msg(buf=data, offset=eth.offset).decode() udp = udpmsg(buf=data, offset=ip4.offset).decode() return dhcp4msg(buf=data, offset=udp.offset).decode()
def __init__(self, family=NETLINK_GENERIC, port=None, pid=None, fileno=None, sndbuf=1048576, rcvbuf=1048576, all_ns=False, async_qsize=None, nlm_generator=None): # # That's a trick. Python 2 is not able to construct # sockets from an open FD. # # So raise an exception, if the major version is < 3 # and fileno is not None. # # Do NOT use fileno in a core pyroute2 functionality, # since the core should be both Python 2 and 3 # compatible. # super(NetlinkMixin, self).__init__() if fileno is not None and sys.version_info[0] < 3: raise NotImplementedError('fileno parameter is not supported ' 'on Python < 3.2') # 8<----------------------------------------- self.config = {'family': family, 'port': port, 'pid': pid, 'fileno': fileno, 'sndbuf': sndbuf, 'rcvbuf': rcvbuf, 'all_ns': all_ns, 'async_qsize': async_qsize, 'nlm_generator': nlm_generator} # 8<----------------------------------------- self.addr_pool = AddrPool(minaddr=0x000000ff, maxaddr=0x0000ffff) self.epid = None self.port = 0 self.fixed = True self.family = family self._fileno = fileno self._sndbuf = sndbuf self._rcvbuf = rcvbuf self.backlog = {0: []} self.callbacks = [] # [(predicate, callback, args), ...] self.pthread = None self.closed = False self.uname = config.uname self.capabilities = {'create_bridge': config.kernel > [3, 2, 0], 'create_bond': config.kernel > [3, 2, 0], 'create_dummy': True, 'provide_master': config.kernel[0] > 2} self.backlog_lock = threading.Lock() self.read_lock = threading.Lock() self.sys_lock = threading.RLock() self.change_master = threading.Event() self.lock = LockFactory() self._sock = None self._ctrl_read, self._ctrl_write = os.pipe() if async_qsize is None: async_qsize = config.async_qsize self.async_qsize = async_qsize if nlm_generator is None: nlm_generator = config.nlm_generator self.nlm_generator = nlm_generator self.buffer_queue = Queue(maxsize=async_qsize) self.qsize = 0 self.log = [] self.get_timeout = 30 self.get_timeout_exception = None self.all_ns = all_ns if pid is None: self.pid = os.getpid() & 0x3fffff self.port = port self.fixed = self.port is not None elif pid == 0: self.pid = os.getpid() else: self.pid = pid # 8<----------------------------------------- self.groups = 0 self.marshal = Marshal() # 8<----------------------------------------- if not nlm_generator: def nlm_request(*argv, **kwarg): return tuple(self._genlm_request(*argv, **kwarg)) def get(*argv, **kwarg): return tuple(self._genlm_get(*argv, **kwarg)) self._genlm_request = self.nlm_request self._genlm_get = self.get self.nlm_request = nlm_request self.get = get # Set defaults self.post_init()
class NetlinkMixin(object): ''' Generic netlink socket ''' def __init__(self, family=NETLINK_GENERIC, port=None, pid=None, fileno=None, sndbuf=1048576, rcvbuf=1048576, all_ns=False, async_qsize=None, nlm_generator=None): # # That's a trick. Python 2 is not able to construct # sockets from an open FD. # # So raise an exception, if the major version is < 3 # and fileno is not None. # # Do NOT use fileno in a core pyroute2 functionality, # since the core should be both Python 2 and 3 # compatible. # super(NetlinkMixin, self).__init__() if fileno is not None and sys.version_info[0] < 3: raise NotImplementedError('fileno parameter is not supported ' 'on Python < 3.2') # 8<----------------------------------------- self.config = {'family': family, 'port': port, 'pid': pid, 'fileno': fileno, 'sndbuf': sndbuf, 'rcvbuf': rcvbuf, 'all_ns': all_ns, 'async_qsize': async_qsize, 'nlm_generator': nlm_generator} # 8<----------------------------------------- self.addr_pool = AddrPool(minaddr=0x000000ff, maxaddr=0x0000ffff) self.epid = None self.port = 0 self.fixed = True self.family = family self._fileno = fileno self._sndbuf = sndbuf self._rcvbuf = rcvbuf self.backlog = {0: []} self.callbacks = [] # [(predicate, callback, args), ...] self.pthread = None self.closed = False self.uname = config.uname self.capabilities = {'create_bridge': config.kernel > [3, 2, 0], 'create_bond': config.kernel > [3, 2, 0], 'create_dummy': True, 'provide_master': config.kernel[0] > 2} self.backlog_lock = threading.Lock() self.read_lock = threading.Lock() self.sys_lock = threading.RLock() self.change_master = threading.Event() self.lock = LockFactory() self._sock = None self._ctrl_read, self._ctrl_write = os.pipe() if async_qsize is None: async_qsize = config.async_qsize self.async_qsize = async_qsize if nlm_generator is None: nlm_generator = config.nlm_generator self.nlm_generator = nlm_generator self.buffer_queue = Queue(maxsize=async_qsize) self.qsize = 0 self.log = [] self.get_timeout = 30 self.get_timeout_exception = None self.all_ns = all_ns if pid is None: self.pid = os.getpid() & 0x3fffff self.port = port self.fixed = self.port is not None elif pid == 0: self.pid = os.getpid() else: self.pid = pid # 8<----------------------------------------- self.groups = 0 self.marshal = Marshal() # 8<----------------------------------------- if not nlm_generator: def nlm_request(*argv, **kwarg): return tuple(self._genlm_request(*argv, **kwarg)) def get(*argv, **kwarg): return tuple(self._genlm_get(*argv, **kwarg)) self._genlm_request = self.nlm_request self._genlm_get = self.get self.nlm_request = nlm_request self.get = get # Set defaults self.post_init() def __del__(self): try: self.close() except Exception: pass def post_init(self): pass def clone(self): return type(self)(**self.config) def close(self, code=errno.ECONNRESET): if code > 0 and self.pthread: self.buffer_queue.put(struct.pack('IHHQIQQ', 28, 2, 0, 0, code, 0, 0)) try: os.close(self._ctrl_write) os.close(self._ctrl_read) except OSError: # ignore the case when it is closed already pass def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def release(self): log.warning("The `release()` call is deprecated") log.warning("Use `close()` instead") self.close() def register_callback(self, callback, predicate=lambda x: True, args=None): ''' Register a callback to run on a message arrival. Callback is the function that will be called with the message as the first argument. Predicate is the optional callable object, that returns True or False. Upon True, the callback will be called. Upon False it will not. Args is a list or tuple of arguments. Simplest example, assume ipr is the IPRoute() instance:: # create a simplest callback that will print messages def cb(msg): print(msg) # register callback for any message: ipr.register_callback(cb) More complex example, with filtering:: # Set object's attribute after the message key def cb(msg, obj): obj.some_attr = msg["some key"] # Register the callback only for the loopback device, index 1: ipr.register_callback(cb, lambda x: x.get('index', None) == 1, (self, )) Please note: you do **not** need to register the default 0 queue to invoke callbacks on broadcast messages. Callbacks are iterated **before** messages get enqueued. ''' if args is None: args = [] self.callbacks.append((predicate, callback, args)) def unregister_callback(self, callback): ''' Remove the first reference to the function from the callback register ''' cb = tuple(self.callbacks) for cr in cb: if cr[1] == callback: self.callbacks.pop(cb.index(cr)) return def register_policy(self, policy, msg_class=None): ''' Register netlink encoding/decoding policy. Can be specified in two ways: `nlsocket.register_policy(MSG_ID, msg_class)` to register one particular rule, or `nlsocket.register_policy({MSG_ID1: msg_class})` to register several rules at once. E.g.:: policy = {RTM_NEWLINK: ifinfmsg, RTM_DELLINK: ifinfmsg, RTM_NEWADDR: ifaddrmsg, RTM_DELADDR: ifaddrmsg} nlsocket.register_policy(policy) One can call `register_policy()` as many times, as one want to -- it will just extend the current policy scheme, not replace it. ''' if isinstance(policy, int) and msg_class is not None: policy = {policy: msg_class} assert isinstance(policy, dict) for key in policy: self.marshal.msg_map[key] = policy[key] return self.marshal.msg_map def unregister_policy(self, policy): ''' Unregister policy. Policy can be: - int -- then it will just remove one policy - list or tuple of ints -- remove all given - dict -- remove policies by keys from dict In the last case the routine will ignore dict values, it is implemented so just to make it compatible with `get_policy_map()` return value. ''' if isinstance(policy, int): policy = [policy] elif isinstance(policy, dict): policy = list(policy) assert isinstance(policy, (tuple, list, set)) for key in policy: del self.marshal.msg_map[key] return self.marshal.msg_map def get_policy_map(self, policy=None): ''' Return policy for a given message type or for all message types. Policy parameter can be either int, or a list of ints. Always return dictionary. ''' if policy is None: return self.marshal.msg_map if isinstance(policy, int): policy = [policy] assert isinstance(policy, (list, tuple, set)) ret = {} for key in policy: ret[key] = self.marshal.msg_map[key] return ret def sendto(self, *argv, **kwarg): return self._sendto(*argv, **kwarg) def recv(self, *argv, **kwarg): return self._recv(*argv, **kwarg) def recv_into(self, *argv, **kwarg): return self._recv_into(*argv, **kwarg) def recv_ft(self, *argv, **kwarg): return self._recv(*argv, **kwarg) def async_recv(self): poll = select.poll() poll.register(self._sock, select.POLLIN | select.POLLPRI) poll.register(self._ctrl_read, select.POLLIN | select.POLLPRI) sockfd = self._sock.fileno() while True: events = poll.poll() for (fd, event) in events: if fd == sockfd: try: data = bytearray(64000) self._sock.recv_into(data, 64000) self.buffer_queue.put_nowait(data) except Exception as e: self.buffer_queue.put(e) return else: return def put(self, msg, msg_type, msg_flags=NLM_F_REQUEST, addr=(0, 0), msg_seq=0, msg_pid=None): ''' Construct a message from a dictionary and send it to the socket. Parameters: - msg -- the message in the dictionary format - msg_type -- the message type - msg_flags -- the message flags to use in the request - addr -- `sendto()` addr, default `(0, 0)` - msg_seq -- sequence number to use - msg_pid -- pid to use, if `None` -- use os.getpid() Example:: s = IPRSocket() s.bind() s.put({'index': 1}, RTM_GETLINK) s.get() s.close() Please notice, that the return value of `s.get()` can be not the result of `s.put()`, but any broadcast message. To fix that, use `msg_seq` -- the response must contain the same `msg['header']['sequence_number']` value. ''' if msg_seq != 0: self.lock[msg_seq].acquire() try: if msg_seq not in self.backlog: self.backlog[msg_seq] = [] if not isinstance(msg, nlmsg): msg_class = self.marshal.msg_map[msg_type] msg = msg_class(msg) if msg_pid is None: msg_pid = self.epid or os.getpid() msg['header']['type'] = msg_type msg['header']['flags'] = msg_flags msg['header']['sequence_number'] = msg_seq msg['header']['pid'] = msg_pid self.sendto_gate(msg, addr) except: raise finally: if msg_seq != 0: self.lock[msg_seq].release() def sendto_gate(self, msg, addr): raise NotImplementedError() def get(self, bufsize=DEFAULT_RCVBUF, msg_seq=0, terminate=None, callback=None): ''' Get parsed messages list. If `msg_seq` is given, return only messages with that `msg['header']['sequence_number']`, saving all other messages into `self.backlog`. The routine is thread-safe. The `bufsize` parameter can be: - -1: bufsize will be calculated from the first 4 bytes of the network data - 0: bufsize will be calculated from SO_RCVBUF sockopt - int >= 0: just a bufsize ''' ctime = time.time() with self.lock[msg_seq]: if bufsize == -1: # get bufsize from the network data bufsize = struct.unpack("I", self.recv(4, MSG_PEEK))[0] elif bufsize == 0: # get bufsize from SO_RCVBUF bufsize = self.getsockopt(SOL_SOCKET, SO_RCVBUF) // 2 tmsg = None enough = False backlog_acquired = False try: while not enough: # 8<----------------------------------------------------------- # # This stage changes the backlog, so use mutex to # prevent side changes self.backlog_lock.acquire() backlog_acquired = True ## # Stage 1. BEGIN # # 8<----------------------------------------------------------- # # Check backlog and return already collected # messages. # if msg_seq == 0 and self.backlog[0]: # Zero queue. # # Load the backlog, if there is valid # content in it for msg in self.backlog[0]: yield msg self.backlog[0] = [] # And just exit break elif msg_seq != 0 and len(self.backlog.get(msg_seq, [])): # Any other msg_seq. # # Collect messages up to the terminator. # Terminator conditions: # * NLMSG_ERROR != 0 # * NLMSG_DONE # * terminate() function (if defined) # * not NLM_F_MULTI # # Please note, that if terminator not occured, # more `recv()` rounds CAN be required. for msg in tuple(self.backlog[msg_seq]): # Drop the message from the backlog, if any self.backlog[msg_seq].remove(msg) # If there is an error, raise exception if msg['header'].get('error', None) is not None: self.backlog[0].extend(self.backlog[msg_seq]) del self.backlog[msg_seq] # The loop is done raise msg['header']['error'] # If it is the terminator message, say "enough" # and requeue all the rest into Zero queue if terminate is not None: tmsg = terminate(msg) if isinstance(tmsg, nlmsg): yield msg if (msg['header']['type'] == NLMSG_DONE) or tmsg: # The loop is done enough = True # If it is just a normal message, append it to # the response if not enough: # finish the loop on single messages if not msg['header']['flags'] & NLM_F_MULTI: enough = True yield msg # Enough is enough, requeue the rest and delete # our backlog if enough: self.backlog[0].extend(self.backlog[msg_seq]) del self.backlog[msg_seq] break # Next iteration self.backlog_lock.release() backlog_acquired = False else: # Stage 1. END # # 8<------------------------------------------------------- # # Stage 2. BEGIN # # 8<------------------------------------------------------- # # Receive the data from the socket and put the messages # into the backlog # self.backlog_lock.release() backlog_acquired = False ## # # Control the timeout. We should not be within the # function more than TIMEOUT seconds. All the locks # MUST be released here. # if (msg_seq != 0) and \ (time.time() - ctime > self.get_timeout): # requeue already received for that msg_seq self.backlog[0].extend(self.backlog[msg_seq]) del self.backlog[msg_seq] # throw an exception if self.get_timeout_exception: raise self.get_timeout_exception() else: return # if self.read_lock.acquire(False): try: self.change_master.clear() # If the socket is free to read from, occupy # it and wait for the data # # This is a time consuming process, so all the # locks, except the read lock must be released data = self.recv_ft(bufsize) # Parse data msgs = self.marshal.parse(data, msg_seq, callback) # Reset ctime -- timeout should be measured # for every turn separately ctime = time.time() # current = self.buffer_queue.qsize() delta = current - self.qsize delay = 0 if delta > 10: delay = min(3, max(0.01, float(current) / 60000)) message = ("Packet burst: " "delta=%s qsize=%s delay=%s" % (delta, current, delay)) if delay < 1: log.debug(message) else: log.warning(message) time.sleep(delay) self.qsize = current # We've got the data, lock the backlog again with self.backlog_lock: for msg in msgs: msg['header']['stats'] = Stats(current, delta, delay) seq = msg['header']['sequence_number'] if seq not in self.backlog: if msg['header']['type'] == \ NLMSG_ERROR: # Drop orphaned NLMSG_ERROR # messages continue seq = 0 # 8<----------------------------------- # Callbacks section for cr in self.callbacks: try: if cr[0](msg): cr[1](msg, *cr[2]) except: # FIXME # # Usually such code formatting # means that the method should # be refactored to avoid such # indentation. # # Plz do something with it. # lw = log.warning lw("Callback fail: %s" % (cr)) lw(traceback.format_exc()) # 8<----------------------------------- self.backlog[seq].append(msg) # Now wake up other threads self.change_master.set() finally: # Finally, release the read lock: all data # processed self.read_lock.release() else: # If the socket is occupied and there is still no # data for us, wait for the next master change or # for a timeout self.change_master.wait(1) # 8<------------------------------------------------------- # # Stage 2. END # # 8<------------------------------------------------------- finally: if backlog_acquired: self.backlog_lock.release() def nlm_request(self, msg, msg_type, msg_flags=NLM_F_REQUEST | NLM_F_DUMP, terminate=None, callback=None): msg_seq = self.addr_pool.alloc() with self.lock[msg_seq]: retry_count = 0 while True: try: self.put(msg, msg_type, msg_flags, msg_seq=msg_seq) for msg in self.get(msg_seq=msg_seq, terminate=terminate, callback=callback): yield msg break except NetlinkError as e: if e.code != 16: raise if retry_count >= 30: raise print('Error 16, retry {}.'.format(retry_count)) time.sleep(0.3) retry_count += 1 continue except Exception: raise finally: # Ban this msg_seq for 0xff rounds # # It's a long story. Modern kernels for RTM_SET.* # operations always return NLMSG_ERROR(0) == success, # even not setting NLM_F_MULTY flag on other response # messages and thus w/o any NLMSG_DONE. So, how to detect # the response end? One can not rely on NLMSG_ERROR on # old kernels, but we have to support them too. Ty, we # just ban msg_seq for several rounds, and NLMSG_ERROR, # being received, will become orphaned and just dropped. # # Hack, but true. self.addr_pool.free(msg_seq, ban=0xff)
class TestIPRoute(object): def setup(self): self.ip = IPRoute() self.ap = AddrPool() self.iftmp = 'pr2x{0}' try: self.dev, idx = self.create() self.ifaces = [idx] except IndexError: pass def get_ifname(self): return self.iftmp.format(self.ap.alloc()) def create(self, kind='dummy'): name = self.get_ifname() create_link(name, kind=kind) idx = self.ip.link_lookup(ifname=name)[0] return (name, idx) def teardown(self): if hasattr(self, 'ifaces'): for dev in self.ifaces: try: self.ip.link('delete', index=dev) except: pass self.ip.close() def _test_nla_operators(self): require_user('root') self.ip.addr('add', self.ifaces[0], address='172.16.0.1', mask=24) self.ip.addr('add', self.ifaces[0], address='172.16.0.2', mask=24) r = [x for x in self.ip.get_addr() if x['index'] == self.ifaces[0]] complement = r[0] - r[1] intersection = r[0] & r[1] assert complement.get_attr('IFA_ADDRESS') == '172.16.0.1' assert complement.get_attr('IFA_LABEL') is None assert complement['prefixlen'] == 0 assert complement['index'] == 0 assert intersection.get_attr('IFA_ADDRESS') is None assert intersection.get_attr('IFA_LABEL') == self.dev assert intersection['prefixlen'] == 24 assert intersection['index'] == self.ifaces[0] def test_add_addr(self): require_user('root') self.ip.addr('add', self.ifaces[0], address='172.16.0.1', mask=24) assert '172.16.0.1/24' in get_ip_addr() def _create(self, kind): name = self.get_ifname() self.ip.link_create(ifname=name, kind=kind) devs = self.ip.link_lookup(ifname=name) assert devs self.ifaces.extend(devs) def test_create_dummy(self): require_user('root') self._create('dummy') def test_create_bond(self): require_user('root') self._create('bond') def test_create_bridge(self): require_user('root') self._create('bridge') def test_neigh_real_links(self): links = set([x['index'] for x in self.ip.get_links()]) neigh = set([x['ifindex'] for x in self.ip.get_neighbors()]) assert neigh < links def test_mass_ipv6(self): # # Achtung! This test is time consuming. # It is really time consuming, I'm not not # kidding you. Beware. # require_user('root') base = 'fdb3:84e5:4ff4:55e4::{0}' limit = int(os.environ.get('PYROUTE2_SLIMIT', '0x800'), 16) # add addresses for idx in range(limit): self.ip.addr('add', self.ifaces[0], base.format(hex(idx)[2:]), 48) # assert addresses in two steps, to ease debug addrs = self.ip.get_addr(10) assert len(addrs) >= limit # clean up addresses # # it is not required, but if you don't do that, # you'll get this on the interface removal: # # >> kernel:BUG: soft lockup - CPU#0 stuck for ... # # so, not to scare people, remove addresses gracefully # one by one # # it also verifies all the addresses are in place for idx in reversed(range(limit)): self.ip.addr('delete', self.ifaces[0], base.format(hex(idx)[2:]), 48) def test_fail_not_permitted(self): try: self.ip.addr('add', 1, address='172.16.0.1', mask=24) except NetlinkError as e: if e.code != 1: # Operation not permitted raise finally: try: self.ip.addr('delete', 1, address='172.16.0.1', mask=24) except: pass def test_fail_no_such_device(self): require_user('root') dev = sorted([i['index'] for i in self.ip.get_links()])[-1] + 10 try: self.ip.addr('add', dev, address='172.16.0.1', mask=24) except NetlinkError as e: if e.code != 19: # No such device raise def test_remove_link(self): require_user('root') try: self.ip.link_remove(self.ifaces[0]) except NetlinkError: pass assert len(self.ip.link_lookup(ifname=self.dev)) == 0 def test_get_route(self): if not self.ip.get_default_routes(table=254): return rts = self.ip.get_routes(family=socket.AF_INET, dst='8.8.8.8', table=254) assert len(rts) > 0 def test_flush_routes(self): require_user('root') self.ip.link('set', index=self.ifaces[0], state='up') self.ip.addr('add', self.ifaces[0], address='172.16.0.2', mask=24) self.ip.route('add', prefix='172.16.1.0', mask=24, gateway='172.16.0.1', table=100) self.ip.route('add', prefix='172.16.2.0', mask=24, gateway='172.16.0.1', table=100) assert grep('ip route show table 100', pattern='172.16.1.0/24.*172.16.0.1') assert grep('ip route show table 100', pattern='172.16.2.0/24.*172.16.0.1') self.ip.flush_routes(table=100) assert not grep('ip route show table 100', pattern='172.16.1.0/24.*172.16.0.1') assert not grep('ip route show table 100', pattern='172.16.2.0/24.*172.16.0.1') def test_route_table_2048(self): require_user('root') self.ip.link('set', index=self.ifaces[0], state='up') self.ip.addr('add', self.ifaces[0], address='172.16.0.2', mask=24) self.ip.route('add', prefix='172.16.1.0', mask=24, gateway='172.16.0.1', table=2048) assert grep('ip route show table 2048', pattern='172.16.1.0/24.*172.16.0.1') remove_link('bala') def test_symbolic_flags_ifaddrmsg(self): require_user('root') self.ip.link('set', index=self.ifaces[0], state='up') self.ip.addr('add', self.ifaces[0], '172.16.1.1', 24) addr = [x for x in self.ip.get_addr() if x.get_attr('IFA_LOCAL') == '172.16.1.1'][0] assert 'IFA_F_PERMANENT' in addr.flags2names(addr['flags']) def test_symbolic_flags_ifinfmsg(self): require_user('root') self.ip.link('set', index=self.ifaces[0], flags=['IFF_UP']) iface = self.ip.get_links(self.ifaces[0])[0] assert iface['flags'] & 1 assert 'IFF_UP' in iface.flags2names(iface['flags']) self.ip.link('set', index=self.ifaces[0], flags=['!IFF_UP']) assert not (self.ip.get_links(self.ifaces[0])[0]['flags'] & 1) def test_updown_link(self): require_user('root') try: self.ip.link_up(*self.ifaces) except NetlinkError: pass assert self.ip.get_links(*self.ifaces)[0]['flags'] & 1 try: self.ip.link_down(*self.ifaces) except NetlinkError: pass assert not (self.ip.get_links(*self.ifaces)[0]['flags'] & 1) def test_callbacks_positive(self): require_user('root') dev = self.ifaces[0] self.cb_counter = 0 self.ip.register_callback(_callback, lambda x: x.get('index', None) == dev, (self, )) self.test_updown_link() assert self.cb_counter > 0 self.ip.unregister_callback(_callback) def test_callbacks_negative(self): require_user('root') self.cb_counter = 0 self.ip.register_callback(_callback, lambda x: x.get('index', None) == -1, (self, )) self.test_updown_link() assert self.cb_counter == 0 self.ip.unregister_callback(_callback) def test_rename_link(self): require_user('root') dev = self.ifaces[0] try: self.ip.link_rename(dev, 'bala') except NetlinkError: pass assert len(self.ip.link_lookup(ifname='bala')) == 1 try: self.ip.link_rename(dev, self.dev) except NetlinkError: pass assert len(self.ip.link_lookup(ifname=self.dev)) == 1 def test_rules(self): assert len(get_ip_rules('-4')) == \ len(self.ip.get_rules(socket.AF_INET)) assert len(get_ip_rules('-6')) == \ len(self.ip.get_rules(socket.AF_INET6)) def test_addr(self): assert len(get_ip_addr()) == len(self.ip.get_addr()) def test_links(self): assert len(get_ip_link()) == len(self.ip.get_links()) def test_one_link(self): lo = self.ip.get_links(1)[0] assert lo.get_attr('IFLA_IFNAME') == 'lo' def test_default_routes(self): assert len(get_ip_default_routes()) == \ len(self.ip.get_default_routes(family=socket.AF_INET, table=254)) def test_routes(self): assert len(get_ip_route()) == \ len(self.ip.get_routes(family=socket.AF_INET, table=255))
def test_setaddr_free(self): ap = AddrPool() f = ap.alloc() base, bit, is_allocated = ap.locate(f + 1) assert not is_allocated assert ap.allocated == 1 ap.setaddr(f + 1, 'free') base, bit, is_allocated = ap.locate(f + 1) assert not is_allocated assert ap.allocated == 1 ap.setaddr(f, 'free') base, bit, is_allocated = ap.locate(f) assert not is_allocated assert ap.allocated == 0 try: ap.free(f) except KeyError: pass
def test_reverse(self): ap = AddrPool(minaddr=1, maxaddr=1024, reverse=True) for i in range(512): assert ap.alloc() > ap.alloc()
def __init__(self, family=NETLINK_GENERIC, port=None, pid=None, fileno=None): # # That's a trick. Python 2 is not able to construct # sockets from an open FD. # # So raise an exception, if the major version is < 3 # and fileno is not None. # # Do NOT use fileno in a core pyroute2 functionality, # since the core should be both Python 2 and 3 # compatible. # super(NetlinkMixin, self).__init__() if fileno is not None and sys.version_info[0] < 3: raise NotImplementedError('fileno parameter is not supported ' 'on Python < 3.2') # 8<----------------------------------------- self.addr_pool = AddrPool(minaddr=0x000000ff, maxaddr=0x0000ffff) self.epid = None self.port = 0 self.fixed = True self.family = family self._fileno = fileno self.backlog = {0: []} self.callbacks = [] # [(predicate, callback, args), ...] self.pthread = None self.closed = False self.capabilities = {'create_bridge': True, 'create_bond': True, 'create_dummy': True, 'provide_master': config.kernel[0] > 2} self.backlog_lock = threading.Lock() self.read_lock = threading.Lock() self.change_master = threading.Event() self.lock = LockFactory() self._sock = None self._ctrl_read, self._ctrl_write = os.pipe() self.buffer_queue = Queue() self.qsize = 0 self.log = [] self.get_timeout = 30 self.get_timeout_exception = None if pid is None: self.pid = os.getpid() & 0x3fffff self.port = port self.fixed = self.port is not None elif pid == 0: self.pid = os.getpid() else: self.pid = pid # 8<----------------------------------------- self.groups = 0 self.marshal = Marshal() # 8<----------------------------------------- # Set defaults self.post_init()