agdsn / hades

AG DSN Authentication and Authorization Infrastructure
MIT License
8 stars 3 forks source link

Replace custom c extension with pure-python pyroute2 #1

Closed janLo closed 4 years ago

janLo commented 9 years ago

There exist a pyroute2 that wraps the complete rtnl protocol using pure-python ctypes. I think that will be a more flexible solution over the custom c extension.

See http://docs.pyroute2.org/iproute.html#pyroute2.iproute.IPRouteMixin.get_neighbors

sebschrader commented 9 years ago

I'm aware of pyroute2. It uses the rtnetlink(7) interface however, which although more modern and generic as compared to the ioctl-based arp(7) interface, does not allow querying a specific IP address and does always return the whole neighbor cache (you can only restrict it to a interface).

I plan to implement the arp(7) in pure python using ctypes.

janLo commented 9 years ago

I think this should not be an issue, you can simply filter it

import pyroute2

def get_mac_1(ip_addr):
    ipr = pyroute2.IPRoute()
    arp_table = dict(
            [(entry.get_attr("NDA_DST"), entry.get_attr("NDA_LLADDR"))
                 for entry in ipr.get_neighbors(2)])
    return arp_table.get(ip_addr, None)                

def get_mac_2(ip_addr):
    ipr = pyroute2.IPRoute()
    for entry in ipr.get_neighbors(2):
        if entry.get_attr("NDA_DST") == ip_addr:
            return entry.get_attr("NDA_LLADDR")

Or, of you think that will be a performance issue, use the IPDB interface. With that ou get a instance that listens for netlink broadcasts to get arp-cache updates. So you don't have to pull the full table every time:

import pyroute2

ipdb = pyroute2.IPDB()

[...]

def get_mac_3(ip_addr, ipdb, ifname=None):
    if ifname is None:
        for iface in ipdb.neighbors:
            if ip_addr in ipdb.neighbors[iface]:
                return ipdb.neighbors[iface][ip_addr].get_attr('NDA_LLADDR')
    else:
        index = ipdb.by_name[ifname].index
        if ip_addr in ipdb.neighbors[index]:
            return ipdb.neighbors[index][ip_addr].get_attr('NDA_LLADDR')
svinota commented 9 years ago

JFYI:

# get records to a dst
ip.get_neighbours(dst='10.0.0.1')

# get records on an interface
ip.get_neighbours(ifindex=2)

# get records on an interface AND to a dst:
ip.get_neighbours(ifindex=2, dst='10.0.0.1')

# use own filter predicate:
ip.get_neighbours(match=lambda x: …)

(This code in the master is not mature yet, but will be covered by tests soon)

Unfortunately, it will not work well if there are thousands of records in the cache: it's a bit dumb to get thousands of records just to filter out one or several of them.

In that case one can use filtering on IPDB caches (ipdb.interfaces[…].neighbours), which are updated asynchronously in the background and thus are able to cope with any number of records, the only limit is the memory:

In [4]: list(ip.interfaces.wlo1.neighbours)
Out[4]: ['10.0.0.1', '239.255.255.250']

In [5]: ip.interfaces.wlo1.neighbours['10.0.0.1']
Out[5]: 
{'__pad': (),
 'attrs': [['NDA_DST', '10.0.0.1'],
  ['NDA_LLADDR', 'f8:d1:11:bd:3f:ba'],
  ['NDA_PROBES', 4],
  ['NDA_CACHEINFO',
   {'ndm_confirmed': 702,
    'ndm_refcnt': 1,
    'ndm_updated': 1082,
    'ndm_used': 1691781}]],
 'event': 'RTM_NEWNEIGH',
 'family': 2,
 'flags': 0,
 'header': {'error': None,
  'flags': 2,
  'length': 76,
  'pid': 12826,
  'sequence_number': 257,
  'type': 28},
 'ifindex': 3,
 'ndm_type': 1,
 'state': 2}

Or ioctl, yep.

sebschrader commented 4 years ago

Close as won't fix.