summaryrefslogtreecommitdiff
path: root/haircontrol/discovery.py
diff options
context:
space:
mode:
Diffstat (limited to 'haircontrol/discovery.py')
-rw-r--r--haircontrol/discovery.py125
1 files changed, 99 insertions, 26 deletions
diff --git a/haircontrol/discovery.py b/haircontrol/discovery.py
index 3180ace..70bdc6a 100644
--- a/haircontrol/discovery.py
+++ b/haircontrol/discovery.py
@@ -192,30 +192,103 @@ class Discovery:
i.disconnect()
def compute_neighbourhood(self):
- print("**********compute_neighbourhood************")
- # net.equipments[<mgmt_ip>].ifaces[<ifname>].mac_seen[i]
-
- # Fake algorithm
- random_e = '172.16.0.254'
- for (mgmt_ip,e) in self.net.equipments.items():
- for (ifname,i) in e.ifaces.items():
- last = None
- for mac_seen in i.mac_seen:
- last = mac_seen
- if last and last in self.net.mac2ip:
- random_e = self.net.mac2ip[last]
- i.direct_neighbours.append(random_e)
-
-# for (mgmt_ip,e) in self.net.equipments.items():
-# print("%s (%s)"%(mgmt_ip,e.name))
-# for (ifname,i) in e.ifaces.items():
-# print("\t%s"%i.name)
-# for mac_seen in i.mac_seen:
-# print("\t\tseen %s"%mac_seen)
-# for remote_mgmtip in i.direct_neighbours:
-# e = self.net.equipments[remote_mgmtip]
-# print("\t\tdirect_neighbour %s (%s)"%(remote_mgmtip, e.name))
-
-
- print("**********compute_neighbourhood************")
+ ### 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 fail
+ # 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. Also signal
+ # that equipment relative to IP has a known 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 not in mac_to_ip: continue
+ ip = mac_to_ip[mac]
+ if ip not in with_uplink:
+ set_uplink(ip, 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(equipment.mgmtip)
+ 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:
+ if ip not in with_uplink: set_uplink(ip, exit_iface)