class NeighborEngine(object): """ This module is responsible for maintaining this router's link-state. It runs the HELLO protocol with the router's neighbors and notifies outbound when the list of neighbors-in-good-standing (the link-state) changes. """ def __init__(self, container): self.container = container self.id = self.container.id self.area = self.container.area self.last_hello_time = 0.0 self.hello_interval = container.config.hello_interval self.hello_max_age = container.config.hello_max_age self.hellos = {} self.link_state_changed = False self.link_state = LinkState(None, self.id, self.area, 0, []) def tick(self, now): self._expire_hellos(now) if now - self.last_hello_time >= self.hello_interval: self.last_hello_time = now self.container.send('_peer', MessageHELLO(None, self.id, self.area, self.hellos.keys())) if self.link_state_changed: self.link_state_changed = False self.link_state.bump_sequence() self.container.local_link_state_changed(self.link_state) def handle_hello(self, msg, now): if msg.id == self.id: return self.hellos[msg.id] = now if msg.is_seen(self.id): if self.link_state.add_peer(msg.id): self.link_state_changed = True self.container.log(INFO, "New neighbor established: %s" % msg.id) ## ## TODO - Use this function to detect area boundaries ## def _expire_hellos(self, now): to_delete = [] for key, last_seen in self.hellos.items(): if now - last_seen > self.hello_max_age: to_delete.append(key) for key in to_delete: self.hellos.pop(key) if self.link_state.del_peer(key): self.link_state_changed = True self.container.log(INFO, "Neighbor lost: %s" % key)
def test_link_state(self): ls = LinkState(None, 'R1', 'area', 1, ['R2', 'R3']) self.assertEqual(ls.id, 'R1') self.assertEqual(ls.area, 'area') self.assertEqual(ls.ls_seq, 1) self.assertEqual(ls.peers, ['R2', 'R3']) ls.bump_sequence() self.assertEqual(ls.id, 'R1') self.assertEqual(ls.area, 'area') self.assertEqual(ls.ls_seq, 2) self.assertEqual(ls.peers, ['R2', 'R3']) result = ls.add_peer('R4') self.assertTrue(result) self.assertEqual(ls.peers, ['R2', 'R3', 'R4']) result = ls.add_peer('R2') self.assertFalse(result) self.assertEqual(ls.peers, ['R2', 'R3', 'R4']) result = ls.del_peer('R3') self.assertTrue(result) self.assertEqual(ls.peers, ['R2', 'R4']) result = ls.del_peer('R5') self.assertFalse(result) self.assertEqual(ls.peers, ['R2', 'R4']) encoded = ls.to_dict() new_ls = LinkState(encoded) self.assertEqual(new_ls.id, 'R1') self.assertEqual(new_ls.area, 'area') self.assertEqual(new_ls.ls_seq, 2) self.assertEqual(new_ls.peers, ['R2', 'R4'])
def __init__(self, container): self.container = container self.id = self.container.id self.area = self.container.area self.last_hello_time = 0.0 self.hello_interval = container.config.hello_interval self.hello_max_age = container.config.hello_max_age self.hellos = {} self.link_state_changed = False self.link_state = LinkState(None, self.id, self.area, 0, [])
def __init__(self, container, max_routers): self.container = container self.my_id = container.id self.max_routers = max_routers self.link_state = LinkState(None, self.my_id, 0, []) self.link_state_changed = False self.recompute_topology = False self.last_topology_change = 0 self.flux_mode = False self.nodes = {} # id => RouterNode self.nodes_by_link_id = {} # link-id => node-id self.maskbits = [] self.next_maskbit = 1 # Reserve bit '0' to represent this router for i in range(max_routers): self.maskbits.append(None) self.maskbits[0] = True self.neighbor_max_age = self.container.config.helloMaxAge self.ls_max_age = self.container.config.remoteLsMaxAge self.flux_interval = self.container.config.raIntervalFlux * 2 self.container.router_adapter.get_agent().add_implementation( self, "router.node")
def __init__(self, parent, node_id, instance): self.parent = parent self.adapter = parent.container.router_adapter self.log = parent.container.log self.id = node_id self.instance = instance self.maskbit = self.parent._allocate_maskbit() self.neighbor_refresh_time = 0.0 self.peer_link_id = None self.link_state = LinkState(None, self.id, 0, []) self.next_hop_router = None self.valid_origins = None self.mobile_addresses = [] self.mobile_address_sequence = 0 self.need_ls_request = True self.need_mobile_request = False self.keep_alive_count = 0 self.adapter.add_router("amqp:/_topo/0/%s/qdrouter" % self.id, self.maskbit) self.log(LOG_TRACE, "Node %s created: maskbit=%d" % (self.id, self.maskbit)) self.adapter.get_agent().add_implementation(self, "router.node")
def __init__(self, container, max_routers): self.container = container self.my_id = container.id self.max_routers = max_routers self.link_state = LinkState(None, self.my_id, 0, {}) self.link_state_changed = False self.recompute_topology = False self.last_topology_change = 0 self.flux_mode = False self.nodes = {} # id => RouterNode self.nodes_by_link_id = {} # link-id => node-id self.maskbits = [] self.next_maskbit = 1 # Reserve bit '0' to represent this router for i in range(max_routers): self.maskbits.append(None) self.maskbits[0] = True self.neighbor_max_age = self.container.config.helloMaxAge self.ls_max_age = self.container.config.remoteLsMaxAge self.flux_interval = self.container.config.raIntervalFlux * 2 self.container.router_adapter.get_agent().add_implementation(self, "router.node")
class RouterNode(object): """ RouterNode is used to track remote routers in the router network. """ def __init__(self, parent, node_id, instance): self.parent = parent self.adapter = parent.container.router_adapter self.log = parent.container.log self.id = node_id self.instance = instance self.maskbit = self.parent._allocate_maskbit() self.neighbor_refresh_time = 0.0 self.peer_link_id = None self.link_state = LinkState(None, self.id, 0, {}) self.next_hop_router = None self.cost = None self.valid_origins = None self.mobile_addresses = [] self.mobile_address_sequence = 0 self.need_ls_request = True self.need_mobile_request = False self.keep_alive_count = 0 self.adapter.add_router("amqp:/_topo/0/%s/qdrouter" % self.id, self.maskbit) self.log(LOG_TRACE, "Node %s created: maskbit=%d" % (self.id, self.maskbit)) self.adapter.get_agent().add_implementation(self, "router.node") def refresh_entity(self, attributes): """Refresh management attributes""" attributes.update({ "id": self.id, "instance": self.instance, # Boot number, integer "linkState": [ls for ls in self.link_state.peers], # List of neighbour nodes "nextHop": self.next_hop_router and self.next_hop_router.id, "validOrigins": self.valid_origins, "address": Address.topological(self.id, area=self.parent.container.area), "routerLink": self.peer_link_id, "cost": self.cost }) def _logify(self, addr): cls = addr[0] phase = None if cls == 'M': phase = addr[1] return "%s;class=%c;phase=%c" % (addr[2:], cls, phase) return "%s;class=%c" % (addr[1:], cls) def set_link_id(self, link_id): if self.peer_link_id == link_id: return False self.peer_link_id = link_id self.next_hop_router = None self.adapter.set_link(self.maskbit, link_id) self.adapter.remove_next_hop(self.maskbit) self.log(LOG_TRACE, "Node %s link set: link_id=%r" % (self.id, link_id)) return True def remove_link(self): if self.peer_link_id != None: self.peer_link_id = None self.adapter.remove_link(self.maskbit) self.log(LOG_TRACE, "Node %s link removed" % self.id) def delete(self): self.adapter.get_agent().remove_implementation(self) self.unmap_all_addresses() self.adapter.del_router(self.maskbit) self.parent._free_maskbit(self.maskbit) self.log(LOG_TRACE, "Node %s deleted" % self.id) def set_next_hop(self, next_hop): if self.id == next_hop.id: return if self.next_hop_router and self.next_hop_router.id == next_hop.id: return self.next_hop_router = next_hop self.adapter.set_next_hop(self.maskbit, next_hop.maskbit) self.log(LOG_TRACE, "Node %s next hop set: %s" % (self.id, next_hop.id)) def set_valid_origins(self, valid_origins): if self.valid_origins == valid_origins: return self.valid_origins = valid_origins vo_mb = [self.parent.nodes[N].maskbit for N in valid_origins] self.adapter.set_valid_origins(self.maskbit, vo_mb) self.log(LOG_TRACE, "Node %s valid origins: %r" % (self.id, valid_origins)) def set_cost(self, cost): if self.cost == cost: return self.cost = cost self.adapter.set_cost(self.maskbit, cost) self.log(LOG_TRACE, "Node %s cost: %d" % (self.id, cost)) def remove_next_hop(self): if self.next_hop_router: self.next_hop_router = None self.adapter.remove_next_hop(self.maskbit) self.log(LOG_TRACE, "Node %s next hop removed" % self.id) def is_neighbor(self): return self.peer_link_id != None def request_link_state(self): """ Set the link-state-requested flag so we can send this node a link-state request at the most opportune time. """ self.need_ls_request = True def link_state_requested(self): """ Return True iff we need to request this node's link state AND the node is reachable. There's no point in sending it a request if we don't know how to reach it. """ if self.need_ls_request and (self.peer_link_id != None or self.next_hop_router != None): self.need_ls_request = False return True return False def mobile_address_request(self): self.need_mobile_request = True def mobile_address_requested(self): if self.need_mobile_request and (self.peer_link_id != None or self.next_hop_router != None): self.need_mobile_request = False return True return False def map_address(self, addr): self.mobile_addresses.append(addr) self.adapter.map_destination(addr, self.maskbit) self.log(LOG_DEBUG, "Remote destination %s mapped to router %s" % (self._logify(addr), self.id)) def unmap_address(self, addr): self.mobile_addresses.remove(addr) self.adapter.unmap_destination(addr, self.maskbit) self.log(LOG_DEBUG, "Remote destination %s unmapped from router %s" % (self._logify(addr), self.id)) def unmap_all_addresses(self): self.mobile_address_sequence = 0 for addr in self.mobile_addresses: self.unmap_address(addr) def overwrite_addresses(self, addrs): added = [] deleted = [] for a in addrs: if a not in self.mobile_addresses: added.append(a) for a in self.mobile_addresses: if a not in addrs: deleted.append(a) for a in added: self.map_address(a) for a in deleted: self.unmap_address(a) def update_instance(self, instance): if instance == None: return False if self.instance == None: self.instance = instance return False if self.instance == instance: return False self.instance = instance self.link_state.del_all_peers() self.unmap_all_addresses() self.log(LOG_INFO, "Detected Restart of Router Node %s" % self.id) return True
class NodeTracker(object): """ This module is responsible for tracking the set of router nodes that are known to this router. It tracks whether they are neighbor or remote and whether they are reachable. This module is also responsible for assigning a unique mask bit value to each router. The mask bit is used in the main router to represent sets of valid destinations for addresses. """ def __init__(self, container, max_routers): self.container = container self.my_id = container.id self.max_routers = max_routers self.link_state = LinkState(None, self.my_id, 0, {}) self.link_state_changed = False self.recompute_topology = False self.last_topology_change = 0 self.flux_mode = False self.nodes = {} # id => RouterNode self.nodes_by_link_id = {} # link-id => node-id self.maskbits = [] self.next_maskbit = 1 # Reserve bit '0' to represent this router for i in range(max_routers): self.maskbits.append(None) self.maskbits[0] = True self.neighbor_max_age = self.container.config.helloMaxAge self.ls_max_age = self.container.config.remoteLsMaxAge self.flux_interval = self.container.config.raIntervalFlux * 2 self.container.router_adapter.get_agent().add_implementation(self, "router.node") def refresh_entity(self, attributes): """Refresh management attributes""" attributes.update({ "id": self.my_id, "instance": self.container.instance, # Boot number, integer "linkState": [ls for ls in self.link_state.peers], # List of neighbour nodes "nextHop": "(self)", "validOrigins": [], "address": Address.topological(self.my_id, area=self.container.area), "lastTopoChange" : self.last_topology_change }) def _do_expirations(self, now): """ Run through the list of routers and check for expired conditions """ for node_id, node in self.nodes.items(): ## ## If the node is a neighbor, check the neighbor refresh time to see ## if we've waited too long for a refresh. If so, disconnect the link ## and remove the node from the local link state. ## if node.is_neighbor(): if now - node.neighbor_refresh_time > self.neighbor_max_age: node.remove_link() if self.link_state.del_peer(node_id): self.link_state_changed = True ## ## Check the age of the node's link state. If it's too old, clear it out. ## if now - node.link_state.last_seen > self.ls_max_age: if node.link_state.has_peers(): node.link_state.del_all_peers() self.recompute_topology = True ## ## If the node has empty link state, check to see if it appears in any other ## node's link state. If it does not, then delete the node. ## if not node.link_state.has_peers() and not node.is_neighbor(): delete_node = True for _id, _n in self.nodes.items(): if _id != node_id: if _n.link_state.is_peer(node_id): delete_node = False break if delete_node: ## ## The keep_alive_count is set to zero when a new node is first ## discovered. Since we can learn about a node before we receive ## its link state, the keep_alive_count is used to prevent the ## node from being deleted before we can learn more about it. ## node.keep_alive_count += 1 if node.keep_alive_count > 2: node.delete() self.nodes.pop(node_id) def tick(self, now): send_ra = False ## ## Expire neighbors and link state ## self._do_expirations(now) ## ## Enter flux mode if things are changing ## if self.link_state_changed or self.recompute_topology: self.last_topology_change = int(round(now)) if not self.flux_mode: self.flux_mode = True self.container.log(LOG_TRACE, "Entered Router Flux Mode") ## ## Handle local link state changes ## if self.link_state_changed: self.link_state_changed = False self.link_state.bump_sequence() self.recompute_topology = True send_ra = True self.container.log_ls(LOG_TRACE, "Local Link State: %r" % self.link_state) ## ## Recompute the topology ## if self.recompute_topology: self.recompute_topology = False collection = {self.my_id : self.link_state} for node_id, node in self.nodes.items(): collection[node_id] = node.link_state next_hops, costs, valid_origins = self.container.path_engine.calculate_routes(collection) self.container.log_ls(LOG_TRACE, "Computed next hops: %r" % next_hops) self.container.log_ls(LOG_TRACE, "Computed costs: %r" % costs) self.container.log_ls(LOG_TRACE, "Computed valid origins: %r" % valid_origins) ## ## Update the next hops and valid origins for each node ## for node_id, next_hop_id in next_hops.items(): node = self.nodes[node_id] next_hop = self.nodes[next_hop_id] vo = valid_origins[node_id] cost = costs[node_id] node.set_next_hop(next_hop) node.set_valid_origins(vo) node.set_cost(cost) ## ## Send link-state requests and mobile-address requests to the nodes ## that have pending requests and are reachable ## for node_id, node in self.nodes.items(): if node.link_state_requested(): self.container.link_state_engine.send_lsr(node_id) if node.mobile_address_requested(): self.container.mobile_address_engine.send_mar(node_id, node.mobile_address_sequence) ## ## If local changes have been made to the list of mobile addresses, send ## an unsolicited mobile-address-update to all routers. ## mobile_seq = self.container.mobile_address_engine.tick(now) self.container.link_state_engine.set_mobile_seq(mobile_seq) ## ## Send an immediate RA if our link state changed ## if send_ra: self.container.link_state_engine.send_ra(now) def neighbor_refresh(self, node_id, instance, link_id, cost, now): """ Invoked when the hello protocol has received positive confirmation of continued bi-directional connectivity with a neighbor router. """ ## ## If the node id is not known, create a new RouterNode to track it. ## if node_id not in self.nodes: self.nodes[node_id] = RouterNode(self, node_id, instance) node = self.nodes[node_id] ## ## Set the link_id to indicate this is a neighbor router. If the link_id ## changed, update the index and add the neighbor to the local link state. ## if node.set_link_id(link_id): self.nodes_by_link_id[link_id] = node node.request_link_state() if self.link_state.add_peer(node_id, cost): self.link_state_changed = True ## ## Update the refresh time for later expiration checks ## node.neighbor_refresh_time = now ## ## If the instance was updated (i.e. the neighbor restarted suddenly), ## schedule a topology recompute and a link-state-request to that router. ## if node.update_instance(instance): self.recompute_topology = True node.request_link_state() def link_lost(self, link_id): """ Invoked when an inter-router link is dropped. """ self.container.log_ls(LOG_INFO, "Link to Neighbor Router Lost - link_tag=%d" % link_id) node_id = self.link_id_to_node_id(link_id) if node_id: self.nodes_by_link_id.pop(link_id) node = self.nodes[node_id] node.remove_link() if self.link_state.del_peer(node_id): self.link_state_changed = True def in_flux_mode(self, now): result = (now - self.last_topology_change) <= self.flux_interval if not result and self.flux_mode: self.flux_mode = False self.container.log(LOG_TRACE, "Exited Router Flux Mode") return result def ra_received(self, node_id, ls_seq, mobile_seq, instance, now): """ Invoked when a router advertisement is received from another router. """ ## ## If the node id is not known, create a new RouterNode to track it. ## if node_id not in self.nodes: self.nodes[node_id] = RouterNode(self, node_id, instance) node = self.nodes[node_id] ## ## If the instance was updated (i.e. the router restarted suddenly), ## schedule a topology recompute and a link-state-request to that router. ## if node.update_instance(instance): self.recompute_topology = True node.request_link_state() ## ## Update the last seen time to now to control expiration of the link state. ## node.link_state.last_seen = now ## ## Check the link state sequence. Send a link state request if our records are ## not up to date. ## if node.link_state.ls_seq < ls_seq: self.container.link_state_engine.send_lsr(node_id) ## ## Check the mobile sequence. Send a mobile-address-request if we are ## behind the advertized sequence. ## if node.mobile_address_sequence < mobile_seq: node.mobile_address_request() def router_learned(self, node_id): """ Invoked when we learn about another router by any means """ if node_id not in self.nodes and node_id != self.my_id: self.nodes[node_id] = RouterNode(self, node_id, None) def link_state_received(self, node_id, link_state, instance, now): """ Invoked when a link state update is received from another router. """ ## ## If the node id is not known, create a new RouterNode to track it. ## if node_id not in self.nodes: self.nodes[node_id] = RouterNode(self, node_id, instance) node = self.nodes[node_id] ## ## If the new link state is more up-to-date than the stored link state, ## update it and schedule a topology recompute. ## if link_state.ls_seq > node.link_state.ls_seq: node.link_state = link_state node.link_state.last_seen = now self.recompute_topology = True ## ## Look through the new link state for references to nodes that we don't ## know about. Schedule link state requests for those nodes to be sent ## after we next recompute the topology. ## for peer in node.link_state.peers: if peer not in self.nodes: self.router_learned(peer) def router_node(self, node_id): return self.nodes[node_id] def link_id_to_node_id(self, link_id): if link_id in self.nodes_by_link_id: return self.nodes_by_link_id[link_id].id return None def _allocate_maskbit(self): if self.next_maskbit == None: raise Exception("Exceeded Maximum Router Count") result = self.next_maskbit self.next_maskbit = None self.maskbits[result] = True for n in range(result + 1, self.max_routers): if self.maskbits[n] == None: self.next_maskbit = n break return result def _free_maskbit(self, i): self.maskbits[i] = None if self.next_maskbit == None or i < self.next_maskbit: self.next_maskbit = i
class RouterNode(object): """ RouterNode is used to track remote routers in the router network. """ def __init__(self, parent, node_id, instance): self.parent = parent self.adapter = parent.container.router_adapter self.log = parent.container.log self.id = node_id self.instance = instance self.maskbit = self.parent._allocate_maskbit() self.neighbor_refresh_time = 0.0 self.peer_link_id = None self.link_state = LinkState(None, self.id, 0, []) self.next_hop_router = None self.valid_origins = None self.mobile_addresses = [] self.mobile_address_sequence = 0 self.need_ls_request = True self.need_mobile_request = False self.keep_alive_count = 0 self.adapter.add_router("amqp:/_topo/0/%s/qdrouter" % self.id, self.maskbit) self.log(LOG_TRACE, "Node %s created: maskbit=%d" % (self.id, self.maskbit)) self.adapter.get_agent().add_implementation(self, "router.node") def refresh_entity(self, attributes): """Refresh management attributes""" attributes.update({ "routerId": self.id, "instance": self.instance, # Boot number, integer "linkState": [ls for ls in self.link_state.peers], # List of neighbour nodes "nextHop": self.next_hop_router and self.next_hop_router.id, "validOrigins": self.valid_origins, "address": Address.topological(self.id, area=self.parent.container.area), "routerLink": self.peer_link_id }) def _logify(self, addr): cls = addr[0] phase = None if cls == 'M': phase = addr[1] return "%s;class=%c;phase=%c" % (addr[2:], cls, phase) return "%s;class=%c" % (addr[1:], cls) def set_link_id(self, link_id): if self.peer_link_id == link_id: return False self.peer_link_id = link_id self.next_hop_router = None self.adapter.set_link(self.maskbit, link_id) self.adapter.remove_next_hop(self.maskbit) self.log(LOG_TRACE, "Node %s link set: link_id=%r" % (self.id, link_id)) return True def remove_link(self): if self.peer_link_id != None: self.peer_link_id = None self.adapter.remove_link(self.maskbit) self.log(LOG_TRACE, "Node %s link removed" % self.id) def delete(self): self.adapter.get_agent().remove_implementation(self) self.unmap_all_addresses() self.adapter.del_router(self.maskbit) self.parent._free_maskbit(self.maskbit) self.log(LOG_TRACE, "Node %s deleted" % self.id) def set_next_hop(self, next_hop): if self.id == next_hop.id: return if self.next_hop_router and self.next_hop_router.id == next_hop.id: return self.next_hop_router = next_hop self.adapter.set_next_hop(self.maskbit, next_hop.maskbit) self.log(LOG_TRACE, "Node %s next hop set: %s" % (self.id, next_hop.id)) def set_valid_origins(self, valid_origins): if self.valid_origins == valid_origins: return self.valid_origins = valid_origins vo_mb = [self.parent.nodes[N].maskbit for N in valid_origins] self.adapter.set_valid_origins(self.maskbit, vo_mb) self.log(LOG_TRACE, "Node %s valid origins: %r" % (self.id, valid_origins)) def remove_next_hop(self): if self.next_hop_router: self.next_hop_router = None self.adapter.remove_next_hop(self.maskbit) self.log(LOG_TRACE, "Node %s next hop removed" % self.id) def is_neighbor(self): return self.peer_link_id != None def request_link_state(self): """ Set the link-state-requested flag so we can send this node a link-state request at the most opportune time. """ self.need_ls_request = True def link_state_requested(self): """ Return True iff we need to request this node's link state AND the node is reachable. There's no point in sending it a request if we don't know how to reach it. """ if self.need_ls_request and (self.peer_link_id != None or self.next_hop_router != None): self.need_ls_request = False return True return False def mobile_address_request(self): self.need_mobile_request = True def mobile_address_requested(self): if self.need_mobile_request and (self.peer_link_id != None or self.next_hop_router != None): self.need_mobile_request = False return True return False def map_address(self, addr): self.mobile_addresses.append(addr) phase = '0' if addr[0] == 'M': phase = addr[1] self.adapter.map_destination(phase, addr, self.maskbit) self.log( LOG_DEBUG, "Remote destination %s mapped to router %s" % (self._logify(addr), self.id)) def unmap_address(self, addr): self.mobile_addresses.remove(addr) self.adapter.unmap_destination(addr, self.maskbit) self.log( LOG_DEBUG, "Remote destination %s unmapped from router %s" % (self._logify(addr), self.id)) def unmap_all_addresses(self): self.mobile_address_sequence = 0 for addr in self.mobile_addresses: self.unmap_address(addr) def overwrite_addresses(self, addrs): added = [] deleted = [] for a in addrs: if a not in self.mobile_addresses: added.append(a) for a in self.mobile_addresses: if a not in addrs: deleted.append(a) for a in added: self.map_address(a) for a in deleted: self.unmap_address(a) def update_instance(self, instance): if instance == None: return False if self.instance == None: self.instance = instance return False if self.instance == instance: return False self.instance = instance self.link_state.del_all_peers() self.unmap_all_addresses() self.log(LOG_TRACE, "Node %s detected restart" % self.id) return True
class NodeTracker(object): """ This module is responsible for tracking the set of router nodes that are known to this router. It tracks whether they are neighbor or remote and whether they are reachable. This module is also responsible for assigning a unique mask bit value to each router. The mask bit is used in the main router to represent sets of valid destinations for addresses. """ def __init__(self, container, max_routers): self.container = container self.my_id = container.id self.max_routers = max_routers self.link_state = LinkState(None, self.my_id, 0, []) self.link_state_changed = False self.recompute_topology = False self.last_topology_change = 0 self.flux_mode = False self.nodes = {} # id => RouterNode self.nodes_by_link_id = {} # link-id => node-id self.maskbits = [] self.next_maskbit = 1 # Reserve bit '0' to represent this router for i in range(max_routers): self.maskbits.append(None) self.maskbits[0] = True self.neighbor_max_age = self.container.config.helloMaxAge self.ls_max_age = self.container.config.remoteLsMaxAge self.flux_interval = self.container.config.raIntervalFlux * 2 self.container.router_adapter.get_agent().add_implementation( self, "router.node") def refresh_entity(self, attributes): """Refresh management attributes""" attributes.update({ "routerId": self.my_id, "instance": self.container.instance, # Boot number, integer "linkState": [ls for ls in self.link_state.peers], # List of neighbour nodes "nextHop": "(self)", "validOrigins": [], "address": Address.topological(self.my_id, area=self.container.area) }) def _do_expirations(self, now): """ Run through the list of routers and check for expired conditions """ for node_id, node in self.nodes.items(): ## ## If the node is a neighbor, check the neighbor refresh time to see ## if we've waited too long for a refresh. If so, disconnect the link ## and remove the node from the local link state. ## if node.is_neighbor(): if now - node.neighbor_refresh_time > self.neighbor_max_age: node.remove_link() if self.link_state.del_peer(node_id): self.link_state_changed = True ## ## Check the age of the node's link state. If it's too old, clear it out. ## if now - node.link_state.last_seen > self.ls_max_age: if node.link_state.has_peers(): node.link_state.del_all_peers() self.recompute_topology = True ## ## If the node has empty link state, check to see if it appears in any other ## node's link state. If it does not, then delete the node. ## if not node.link_state.has_peers() and not node.is_neighbor(): delete_node = True for _id, _n in self.nodes.items(): if _id != node_id: if _n.link_state.is_peer(node_id): delete_node = False break if delete_node: ## ## The keep_alive_count is set to zero when a new node is first ## discovered. Since we can learn about a node before we receive ## its link state, the keep_alive_count is used to prevent the ## node from being deleted before we can learn more about it. ## node.keep_alive_count += 1 if node.keep_alive_count > 2: node.delete() self.nodes.pop(node_id) def tick(self, now): send_ra = False ## ## Expire neighbors and link state ## self._do_expirations(now) ## ## Enter flux mode if things are changing ## if self.link_state_changed or self.recompute_topology: self.last_topology_change = now if not self.flux_mode: self.flux_mode = True self.container.log(LOG_TRACE, "Entered Router Flux Mode") ## ## Handle local link state changes ## if self.link_state_changed: self.link_state_changed = False self.link_state.bump_sequence() self.recompute_topology = True send_ra = True self.container.log_ls(LOG_TRACE, "Local Link State: %r" % self.link_state) ## ## Recompute the topology ## if self.recompute_topology: self.recompute_topology = False collection = {self.my_id: self.link_state} for node_id, node in self.nodes.items(): collection[node_id] = node.link_state next_hops, valid_origins = self.container.path_engine.calculate_routes( collection) self.container.log_ls(LOG_TRACE, "Computed next hops: %r" % next_hops) self.container.log_ls(LOG_TRACE, "Computed valid origins: %r" % valid_origins) ## ## Update the next hops and valid origins for each node ## for node_id, next_hop_id in next_hops.items(): node = self.nodes[node_id] next_hop = self.nodes[next_hop_id] vo = valid_origins[node_id] node.set_next_hop(next_hop) node.set_valid_origins(vo) ## ## Send link-state requests and mobile-address requests to the nodes ## that have pending requests and are reachable ## for node_id, node in self.nodes.items(): if node.link_state_requested(): self.container.link_state_engine.send_lsr(node_id) if node.mobile_address_requested(): self.container.mobile_address_engine.send_mar( node_id, node.mobile_address_sequence) ## ## If local changes have been made to the list of mobile addresses, send ## an unsolicited mobile-address-update to all routers. ## mobile_seq = self.container.mobile_address_engine.tick(now) self.container.link_state_engine.set_mobile_seq(mobile_seq) ## ## Send an immediate RA if our link state changed ## if send_ra: self.container.link_state_engine.send_ra(now) def neighbor_refresh(self, node_id, instance, link_id, now): """ Invoked when the hello protocol has received positive confirmation of continued bi-directional connectivity with a neighbor router. """ ## ## If the node id is not known, create a new RouterNode to track it. ## if node_id not in self.nodes: self.nodes[node_id] = RouterNode(self, node_id, instance) node = self.nodes[node_id] ## ## Set the link_id to indicate this is a neighbor router. If the link_id ## changed, update the index and add the neighbor to the local link state. ## if node.set_link_id(link_id): self.nodes_by_link_id[link_id] = node node.request_link_state() if self.link_state.add_peer(node_id): self.link_state_changed = True ## ## Update the refresh time for later expiration checks ## node.neighbor_refresh_time = now ## ## If the instance was updated (i.e. the neighbor restarted suddenly), ## schedule a topology recompute and a link-state-request to that router. ## if node.update_instance(instance): self.recompute_topology = True node.request_link_state() def link_lost(self, link_id): """ Invoked when an inter-router link is dropped. """ self.container.log_ls(LOG_INFO, "Router Link Lost - link_id=%d" % link_id) node_id = self.link_id_to_node_id(link_id) if node_id: self.nodes_by_link_id.pop(link_id) node = self.nodes[node_id] node.remove_link() if self.link_state.del_peer(node_id): self.link_state_changed = True def in_flux_mode(self, now): result = (now - self.last_topology_change) <= self.flux_interval if not result and self.flux_mode: self.flux_mode = False self.container.log(LOG_TRACE, "Exited Router Flux Mode") return result def ra_received(self, node_id, ls_seq, mobile_seq, instance, now): """ Invoked when a router advertisement is received from another router. """ ## ## If the node id is not known, create a new RouterNode to track it. ## if node_id not in self.nodes: self.nodes[node_id] = RouterNode(self, node_id, instance) node = self.nodes[node_id] ## ## If the instance was updated (i.e. the router restarted suddenly), ## schedule a topology recompute and a link-state-request to that router. ## if node.update_instance(instance): self.recompute_topology = True node.request_link_state() ## ## Update the last seen time to now to control expiration of the link state. ## node.link_state.last_seen = now ## ## Check the link state sequence. Send a link state request if our records are ## not up to date. ## if node.link_state.ls_seq < ls_seq: self.container.link_state_engine.send_lsr(node_id) ## ## Check the mobile sequence. Send a mobile-address-request if we are ## behind the advertized sequence. ## if node.mobile_address_sequence < mobile_seq: node.mobile_address_request() def router_learned(self, node_id): """ Invoked when we learn about another router by any means """ if node_id not in self.nodes and node_id != self.my_id: self.nodes[node_id] = RouterNode(self, node_id, None) def link_state_received(self, node_id, link_state, instance, now): """ Invoked when a link state update is received from another router. """ ## ## If the node id is not known, create a new RouterNode to track it. ## if node_id not in self.nodes: self.nodes[node_id] = RouterNode(self, node_id, instance) node = self.nodes[node_id] ## ## If the new link state is more up-to-date than the stored link state, ## update it and schedule a topology recompute. ## if link_state.ls_seq > node.link_state.ls_seq: node.link_state = link_state node.link_state.last_seen = now self.recompute_topology = True ## ## Look through the new link state for references to nodes that we don't ## know about. Schedule link state requests for those nodes to be sent ## after we next recompute the topology. ## for peer in node.link_state.peers: if peer not in self.nodes: self.router_learned(peer) def router_node(self, node_id): return self.nodes[node_id] def link_id_to_node_id(self, link_id): if link_id in self.nodes_by_link_id: return self.nodes_by_link_id[link_id].id return None def _allocate_maskbit(self): if self.next_maskbit == None: raise Exception("Exceeded Maximum Router Count") result = self.next_maskbit self.next_maskbit = None self.maskbits[result] = True for n in range(result + 1, self.max_routers): if self.maskbits[n] == None: self.next_maskbit = n break return result def _free_maskbit(self, i): self.maskbits[i] = None if self.next_maskbit == None or i < self.next_maskbit: self.next_maskbit = i