host: Add tools for discovering hosts
parent
0f27bc502e
commit
d4dfb3cafd
|
@ -0,0 +1,209 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
long_ = long
|
||||||
|
except NameError:
|
||||||
|
long_ = int
|
||||||
|
try:
|
||||||
|
range_ = xrange
|
||||||
|
except NameError:
|
||||||
|
range_ = range
|
||||||
|
|
||||||
|
|
||||||
|
class Neighbor(object):
|
||||||
|
|
||||||
|
def __init__(self, ipaddr=None, macaddr=None):
|
||||||
|
self.ipaddr = ipaddr
|
||||||
|
self.macaddr = macaddr
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str('{klass}({ipaddr!r}, {macaddr!r})'.format(
|
||||||
|
klass=self.__class__.__name__,
|
||||||
|
ipaddr=self.ipaddr,
|
||||||
|
macaddr=self.macaddr,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
class NeighborTable(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.entries = []
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for n in self.entries:
|
||||||
|
yield n
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
for n in self:
|
||||||
|
if item in (n.ipaddr, n.macaddr):
|
||||||
|
return n
|
||||||
|
raise KeyError(item)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.entries = []
|
||||||
|
try:
|
||||||
|
o = subprocess.check_output(['ip', 'neighbor', 'show'])
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return
|
||||||
|
for line in reversed(o.splitlines()):
|
||||||
|
parts = line.strip().decode().split()
|
||||||
|
try:
|
||||||
|
i = parts[0]
|
||||||
|
m = MacAddress(parts[4]).with_colons()
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
continue
|
||||||
|
self.entries.append(Neighbor(i, m))
|
||||||
|
|
||||||
|
|
||||||
|
class MacAddress(object):
|
||||||
|
|
||||||
|
def __init__(self, macaddr):
|
||||||
|
if hasattr(macaddr, 'encode'):
|
||||||
|
self.value = self.parse(macaddr)
|
||||||
|
elif hasattr(macaddr, 'value'):
|
||||||
|
self.value = macaddr.value
|
||||||
|
else:
|
||||||
|
self.value = long_(macaddr)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.with_colons())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, macaddr):
|
||||||
|
if '.' in macaddr:
|
||||||
|
parts = cls.parse_dotted(macaddr)
|
||||||
|
elif ':' in macaddr:
|
||||||
|
parts = cls.parse_colons(macaddr)
|
||||||
|
elif '-' in macaddr:
|
||||||
|
parts = cls.parse_hyphens(macaddr)
|
||||||
|
else:
|
||||||
|
parts = (macaddr[i:i+2] for i in range_(0, len(macaddr), 2))
|
||||||
|
value = 0
|
||||||
|
for part in parts:
|
||||||
|
value <<= 8
|
||||||
|
value += int(part, 16)
|
||||||
|
return value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_colons(cls, macaddr):
|
||||||
|
return macaddr.split(':')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_dotted(cls, macaddr):
|
||||||
|
parts = macaddr.split('.')
|
||||||
|
if len(parts) != 3:
|
||||||
|
raise ValueError('Invalid MAC address {}'.format(macaddr))
|
||||||
|
for part in parts:
|
||||||
|
yield part[:2]
|
||||||
|
yield part[2:]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_hyphens(cls, macaddr):
|
||||||
|
return macaddr.split('-')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _packed(self):
|
||||||
|
return bytearray(struct.pack(str('>Q'), self.value))[2:]
|
||||||
|
|
||||||
|
def with_colons(self):
|
||||||
|
return ':'.join('{:02x}'.format(x) for x in self._packed)
|
||||||
|
|
||||||
|
def with_dots(self):
|
||||||
|
p = list(self._packed)
|
||||||
|
parts = (p[i:i+2] for i in range_(0, len(p), 2))
|
||||||
|
return '.'.join('{:04x}'.format((x << 8) + y) for x, y in parts)
|
||||||
|
|
||||||
|
def with_hyphens(self):
|
||||||
|
return '-'.join('{:02X}'.format(x) for x in self._packed)
|
||||||
|
|
||||||
|
|
||||||
|
class Host(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ping(host, port=7):
|
||||||
|
try:
|
||||||
|
ai = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
|
||||||
|
socket.SOCK_DGRAM)
|
||||||
|
except socket.gaierror:
|
||||||
|
return
|
||||||
|
for af, socktype, proto, __, sa in ai:
|
||||||
|
sock = None
|
||||||
|
try:
|
||||||
|
sock = socket.socket(af, socktype, proto)
|
||||||
|
sock.sendto(b'\x00', sa)
|
||||||
|
except socket.error:
|
||||||
|
return
|
||||||
|
finally:
|
||||||
|
if sock is not None:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def discover(cls, hint):
|
||||||
|
def try_mac():
|
||||||
|
try:
|
||||||
|
return cls.from_macaddr(hint)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def try_name():
|
||||||
|
try:
|
||||||
|
return cls.from_hostname(hint)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if ':' in hint:
|
||||||
|
return try_mac() or try_name()
|
||||||
|
else:
|
||||||
|
return try_name() or try_mac()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_hostname(cls, value):
|
||||||
|
try:
|
||||||
|
ai = socket.getaddrinfo(value, None, 0, 0, 0, socket.AI_CANONNAME)
|
||||||
|
except socket.gaierror as e:
|
||||||
|
raise ValueError(str(e))
|
||||||
|
ipaddr = None
|
||||||
|
hostname = None
|
||||||
|
for __, __, __, name, addr in ai:
|
||||||
|
if addr[0] != '::1' and not addr[0].startswith('127.'):
|
||||||
|
ipaddr = addr[0]
|
||||||
|
if name and not name.startswith('localhost'):
|
||||||
|
hostname = name
|
||||||
|
if hostname and ipaddr:
|
||||||
|
break
|
||||||
|
if not ipaddr:
|
||||||
|
return
|
||||||
|
cls.ping(ipaddr)
|
||||||
|
try:
|
||||||
|
neighbor = NeighborTable()[ipaddr]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
host = cls()
|
||||||
|
host.macaddr = neighbor.macaddr
|
||||||
|
host.ipaddr = ipaddr
|
||||||
|
host.hostname = hostname
|
||||||
|
return host
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_macaddr(cls, value):
|
||||||
|
m = MacAddress(value)
|
||||||
|
host = cls()
|
||||||
|
host.macaddr = m.with_colons()
|
||||||
|
try:
|
||||||
|
neighbor = NeighborTable()[host.macaddr]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
host.ipaddr = neighbor.ipaddr
|
||||||
|
try:
|
||||||
|
ni = socket.getnameinfo((host.ipaddr, 0), 0)
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
host.hostname = ni[0]
|
||||||
|
return host
|
Loading…
Reference in New Issue