from haircontrol.data import * from haircontrol.inspectors import * class Discovery: def __init__(self): #, inspector): self.net = EtherDomain() self.dummy_inspector = DummyInspector() self.linux_inspector = LinuxInspector() self.ubnt_inspector = UbntInspector() self.toughswitch_inspector = ToughSwitchInspector() self.edgemax_inspector = EdgeMaxInspector() self.netonix_inspector = NetonixInspector() self.mikrotik_inspector = MikrotikInspector() self.openwrt_inspector = OpenWRTInspector() self.inspector_by_mac = { # Mikrotik 'd4:ca:6d': self.mikrotik_inspector, 'e4:8d:8c': self.mikrotik_inspector, # Netonix 'ec:13:b2': self.netonix_inspector, 'ec:13:b3': self.netonix_inspector, # PC '0c:c4:7a': self.linux_inspector, # SuperMicro '52:54:00': self.linux_inspector, # VM # TP-Link '10:fe:ed': self.openwrt_inspector, '14:cc:20': self.openwrt_inspector, '30:b5:c2': self.openwrt_inspector, '54:e6:fc': self.openwrt_inspector, '60:e3:27': self.openwrt_inspector, '64:66:b3': self.openwrt_inspector, '64:70:02': self.openwrt_inspector, '90:f6:52': self.openwrt_inspector, 'a0:f3:c1': self.openwrt_inspector, 'b0:48:7a': self.openwrt_inspector, 'c0:4a:00': self.openwrt_inspector, 'c4:6e:1f': self.openwrt_inspector, 'c4:e9:84': self.openwrt_inspector, 'f4:ec:38': self.openwrt_inspector, 'f8:1a:67': self.openwrt_inspector, 'f8:d1:11': self.openwrt_inspector, # Ubnt '00:15:6d': self.ubnt_inspector, '00:27:22': self.ubnt_inspector, '04:18:d6:07': self.toughswitch_inspector, '04:18:d6': self.ubnt_inspector, '06:18:d6': self.ubnt_inspector, # Non globaly unique ?! '24:a4:3c:05': self.toughswitch_inspector, '24:a4:3c:06': self.toughswitch_inspector, '24:a4:3c:07': self.toughswitch_inspector, '24:a4:3c:3c': self.toughswitch_inspector, '24:a4:3c:3d': self.toughswitch_inspector, '24:a4:3c:b3': self.toughswitch_inspector, '24:a4:3c': self.ubnt_inspector, '44:d9:e7': self.edgemax_inspector, 'dc:9f:db:80': self.toughswitch_inspector, 'dc:9f:db:81': self.toughswitch_inspector, 'dc:9f:db': self.ubnt_inspector, } def inspector(self, mac): if mac: return self.inspector_by_mac.get(mac[:11]) \ or self.inspector_by_mac.get(mac[:8]) \ or self.dummy_inspector else: return self.dummy_inspector def discover_static_hinting(self, name_ip_tuples): for name, ip in name_ip_tuples: self.net.add_equipment(Equipment(name, ip)) def discover_lldp_hinting(self, e_lldp): self.net.add_equipment(e_lldp) self.linux_inspector.connect(e_lldp) # Learn local interfaces of e_lldp result = self.linux_inspector.command('ip-link') for (ifname, mac) in result: if ifname not in [ 'lo' ]: # XXX configurable filter e_lldp.add_iface(ifname, mac) # Create equipments and ifaces from LLDP neighbour discovery result = self.linux_inspector.command('lldpctl') for (local_ifname, local_mac, remote_name, remote_ipmgmt, ports) in result: e = Equipment(remote_name, remote_ipmgmt) self.net.add_equipment(e) # lldp returns logical port, not physicial port for remote bridges for remote_ifname in ports: if remote_ifname not in [ 'br0' ]: # XXX configurable filter e.add_seen_mac(remote_ifname, local_mac) self.linux_inspector.disconnect() def discover_from_root(self, e_root): self.net.add_equipment(e_root) self.linux_inspector.connect(e_root) # Learn root neighbours (directly or indirectly connected via trasparent bridges) result = self.linux_inspector.command('ip-neigh') for (ip, ifname, mac) in result: self.net.index_mac_ip(mac, ip) e_root.add_seen_mac(ifname, mac) self.linux_inspector.disconnect() # Create/Update Equipment object for all neighbours # (could be already created by hinting) for iface in e_root.ifaces.values(): local_ifname = iface.name local_mac = iface.mac for remote_mac in iface.mac_seen: remote_ip = self.net.mac2ip.get(remote_mac) if remote_ip: e = self.net.equipments.get(remote_ip) if e and not e.ifaces: e.add_iface(None, remote_mac) elif not e: e = Equipment('?', remote_ip) e.add_iface('?', remote_mac) self.net.add_equipment(e) # Inspect all non-already inspected equipement done = False while not done: done = True for ip,e in self.net.equipments.items(): if not e.inspected: done = False # Find the right inspector from equipment's first iface mac address if e.ifaces: e_first_mac = next(iter(e.ifaces.values())).mac i = self.inspector(e_first_mac) else: # XXX Custom hack if e.mgmtip.startswith('172.16.1'): i = self.ubnt_inspector elif e.mgmtip == '172.16.30.23': i = self.edgemax_inspector elif e.mgmtip.startswith('172.16.3'): i = self.toughswitch_inspector else: i = self.dummy_inspector i.connect(e) # Inspect antennas if isinstance(i, UbntInspector): # Learn local interfaces result = i.command('status.cgi') for (ifname, mac) in result: if ifname not in [ 'lo', 'wifi0', 'br0' ]: # XXX configurable filter e.add_iface(ifname, mac) # Learn bridge tables result = i.command('brmacs.cgi') for (ifname, mac) in result: e.add_seen_mac(ifname, mac) # Inspect switches elif isinstance(i, ToughSwitchInspector): result = i.command('ip-link') switch_mac = '?' for (ifname, mac) in result: if ifname == 'br0': # XXX configurable filter switch_mac = mac result = i.command('mactable_data.cgi') for (ifname, mac) in result: e.add_iface(ifname, switch_mac) # XXX many non-usefull calls e.add_seen_mac(ifname, mac) elif isinstance(i, EdgeMaxInspector): switch_mac = '?' result = i.command('show-version') for (key, value) in result: if key == 'Burned In MAC Address': switch_mac = value result = i.command('mac-addr-table') for (mac, ifname) in result: e.add_iface(ifname, switch_mac) # XXX many non-usefull calls e.add_seen_mac(ifname, mac) else: print("Notice: Nothing inspected on %s"%e) i.disconnect() def compute_neighbourhood(self): ### Configuration # GATEWAY_IP and GATEWAY_IFACE_NAME define information about # the gateway, i.e., the center of the network. # # IGNORE_IP lists all IP that should not appear in the network # representation. gateway_ip = '172.16.0.254' gateway_iface_name = 'eth1' ignore_ip = ['172.16.0.253'] ### State variables # FIXME: mac2ip() method is incomplete so it sometimes fails # finding the IP from the MAC of some interface. Meanwhile, we # build our own database. mac_to_ip = {} for (ip, equipment) in self.net.equipments.items(): for interface in equipment.ifaces.values(): mac_to_ip[interface.mac] = ip equipments = { ip: eq for ip, eq in self.net.equipments.items() if (ip not in ignore_ip) and (ip != gateway_ip) } outer_net = [] # IP belonging to external levels. with_uplink = [] # IP already attached to an interface. ### Helper functions def is_outer_interface (interface): # Return True when INTERFACE is looking towards leaves of # the network. return all(mac not in mac_to_ip or # Ignore unknown eq mac_to_ip[mac] in outer_net for mac in interface.mac_seen) def is_at_next_level (equipment): # Return True when EQUIPMENT is adjacent to outer network. # This happens when it doesn't belong to outer network and # when all but one of its interfaces can only see MAC from # outer network. Return False otherwise. Assume EQUIPMENT # is not the gateway. if equipment.mgmtip in outer_net: return False exit_interface_flag = False for interface in equipment.ifaces.values(): if is_outer_interface(interface): continue if exit_interface_flag: return False exit_interface_flag = True return True def set_uplink (ip, interface): # Set IP as a direct neighbour of INTERFACE, if it doesn't # have an uplink already. if ip not in with_uplink: interface.direct_neighbours.append(ip) with_uplink.append(ip) def link_to_outer_net (equipment): # Link EQUIPMENT with outer network. Orphans in the # outer network seen by an interface are linked to it. for interface in equipment.ifaces.values(): if is_outer_interface(interface): for mac in interface.mac_seen: if mac in mac_to_ip: set_uplink(mac_to_ip[mac], interface) ### Main # Walk the the network from the outside, i.e., leaves, to the # inside, i.e., the gateway, peeling the onion one layer at # a time. # # We know that an equipment is the current level when all but # one of its interfaces only see MAC from inferior levels (the # interface left being attached to uplink). This assumes that # all direct children of an equipment, at the very least, are # seen by the equipment. Unknown equipment, i.e., a MAC that # cannot be associated to an equipment in the network, is # ignored altogether. # # Once an equipment is known to be at the current level, we # attach it to the external network by linking every orphan # there to the equipment interface seeing it, if any. # # At the end of the process, we do the same for the gateway. # This requires a special step since there is no guarantee # that gateway sees all of its direct neighbours. However, all # orphans left in outer network are necessarily directly # attached to it. while equipments: current_level = [] for (ip, equipment) in equipments.items(): if is_at_next_level(equipment): link_to_outer_net(equipment) current_level.append(ip) outer_net.extend(current_level) for ip in current_level: del equipments[ip] exit_iface = self.net.equipments[gateway_ip].ifaces[gateway_iface_name] for ip in outer_net: set_uplink(ip, exit_iface)