def __init__(self, router_name, router_link_manager, service_transmitter, **kwargs): self._update_period = kwargs.pop('update_period', config.rip_update_period) self._inf_timeout = kwargs.pop('inf_timeout', config.rip_inf_timeout) self._remove_timeout = kwargs.pop('remove_timeout', config.rip_remove_timeout) super(RIPService, self).__init__() self._router_name = router_name self._link_manager = router_link_manager self._service_transmitter = service_transmitter self._logger = logging.getLogger("RIPService.router={0}".format( self._router_name)) self._dynamic_routing_table = DynamicRoutingTable( {router_name: RIPService.RIPRouteToDestination(router_name, 0)}) # If working thread will be able to acquire the lock, then it should # terminate himself. self._exit_lock = threading.RLock() self._exit_lock.acquire() self._working_thread = threading.Thread(target=self._work) self._working_thread.start()
class RIPService(object): protocol = 520 inf_distance = 16 @total_ordering class RIPRouteToDestination(RouteToDestination): def __init__(self, next_router=None, distance=None): super(RIPService.RIPRouteToDestination, self).\ __init__(next_router=next_router) self.distance = distance def __eq__(self, other): return (super(RIPService.RIPRouteToDestination, self). __eq__(other) and self.distance == other.distance) def __lt__(self, other): if (super(RIPService.RIPRouteToDestination, self). __eq__(other)): return self.distance < other.distance else: return super(RIPService.RIPRouteToDestination, self).\ __lt__(other) def __repr__(self): return "RIPRouteToDestination(next_router={0}, dist={1})".\ format(self.next_router, self.distance) def __init__(self, router_name, router_link_manager, service_transmitter, **kwargs): self._update_period = kwargs.pop('update_period', config.rip_update_period) self._inf_timeout = kwargs.pop('inf_timeout', config.rip_inf_timeout) self._remove_timeout = kwargs.pop('remove_timeout', config.rip_remove_timeout) super(RIPService, self).__init__() self._router_name = router_name self._link_manager = router_link_manager self._service_transmitter = service_transmitter self._logger = logging.getLogger("RIPService.router={0}".format( self._router_name)) self._dynamic_routing_table = DynamicRoutingTable( {router_name: RIPService.RIPRouteToDestination(router_name, 0)}) # If working thread will be able to acquire the lock, then it should # terminate himself. self._exit_lock = threading.RLock() self._exit_lock.acquire() self._working_thread = threading.Thread(target=self._work) self._working_thread.start() # TODO def terminate(self): # Release exit lock and wait until working thread will not terminate. self._exit_lock.release() self._working_thread.join() def dynamic_routing_table(self): return self._dynamic_routing_table def _work(self): def update_connected_routers_info(): new_connected_routers = \ connected_routers - prev_connected_routers new_disconnected_routers = \ prev_connected_routers - connected_routers if new_connected_routers: self._logger.debug("Connected to routers: {0}".format( list(new_connected_routers))) if new_disconnected_routers: self._logger.debug("Disconnected from routers: {0}".format( list(new_disconnected_routers))) # Remove disconnected routers from list. for router_name in new_disconnected_routers: assert router_name in connected_rrs_info del connected_rrs_info[router_name] # Set distance to infinity for destination routers route to which # leaded through disconnected routers. for to_router, dest_router_info in dest_routers_info.iteritems(): if dest_router_info.next_router in new_disconnected_routers: dest_router_info.dist = RIPService.inf_distance self._logger.debug( "Remove route: Due to disconnection: " "{dest}:(d={dist}, n={next})".format( dest=to_router, dist=dest_router_info.dist, next=dest_router_info.next_router)) # Add connected hosts to according list. for router_name in new_connected_routers: router_name not in connected_rrs_info connected_rrs_info[router_name] = \ ConnectedRouterInfo(Timer(self._update_period, start_time=time.time() - self._update_period - 1.0)) assert connected_rrs_info[router_name].timer.is_expired() # Update routing information for directly connected destination # routers. for router_name in new_connected_routers: dest_routers_info[router_name] = DestRouterInfo( dist=1, next_router=router_name, timer=DummyTimer()) self._logger.debug( "Add route: Directly connected: " "{dest}:(d={dist}, n={next})".format( dest=router_name, dist=dest_routers_info[router_name].dist, next=dest_routers_info[router_name].next_router)) table_changed = new_connected_routers or new_disconnected_routers if table_changed: update_routing_table() def connected_routers_with_timeout(): for rr_name, connected_rr_info in connected_rrs_info.iteritems(): if connected_rr_info.timer.is_expired(): yield rr_name def set_infinite_timeout_distances(): routing_table_updated = False for dest, dest_router_info in dest_routers_info.iteritems(): if (dest_router_info.dist < RIPService.inf_distance and dest_router_info.timer.is_expired()): dest_router_info.dist = RIPService.inf_distance dest_router_info.timer = Timer(self._remove_timeout) routing_table_updated = True self._logger.debug( "Remove route: Due to timeout: " "{dest}:(d={dist}, n={next})".format( dest=dest, dist=dest_router_info.dist, next=dest_router_info.next_router)) if routing_table_updated: update_routing_table() def remove_timeout_distances(): routing_table_updated = False for dest, dest_router_info in dest_routers_info.items(): if (dest_router_info.dist == RIPService.inf_distance and dest_router_info.timer.is_expired()): del dest_routers_info[dest] routing_table_updated = True self._logger.debug( "Due to big timeout removing target: {dest}".format( dest=dest)) # TODO: Not actually needed. if routing_table_updated: update_routing_table() def distances_for_sending(to_router): distances = [] for dest, dest_router_info in dest_routers_info.iteritems(): if dest_router_info.next_router == to_router: # Rule 1A from [vasilev04netsoft]: # For router R: if packets to destination router X are sent # through router G, then distance to router X that being # sent to router G is infinity. # # R ----------------- G - ... - X # X:(inf, G) -> # d = (RIPService.inf_distance, dest) distances.append(d) else: d = (dest_router_info.dist, dest) distances.append(d) return distances def send_distances(routers): for to_router in routers: # Prepare data to send. distances = distances_for_sending(to_router) raw_data = RIPData(distances).serialize() # Send data. self._service_transmitter.send_data(to_router, raw_data) # Mark time data was sent. connected_rrs_info[to_router].timer.restart() # TODO: Assume that computer is not slow. assert not connected_rrs_info[to_router].timer.is_expired() def handle_receive(): while True: result = self._service_transmitter.receive_data(block=False) if result is None: break src, raw_data = result rip_data = RIPData.deserialize(raw_data) self._logger.debug( "Received vector from {0}:\n {1}".format( src, pprint.pformat(rip_data.distances))) routing_table_updated = False for dist, dest in rip_data.distances: dist = min(dist + 1, RIPService.inf_distance) if dest not in dest_routers_info: # Route to new router. if dist < RIPService.inf_distance: dest_routers_info[dest] = DestRouterInfo( dist=dist, next_router=src, timer=Timer(self._inf_timeout)) routing_table_updated = True self._logger.debug( "Received route to new router: " "{dest}:(d={dist}, n={next})".format( dest=dest, dist=dist, next=src)) else: # Ignore. pass elif dist < dest_routers_info[dest].dist: # Received shorter then all known path to router. dest_routers_info[dest].dist = dist dest_routers_info[dest].next_router = src dest_routers_info[dest].timer = \ Timer(self._inf_timeout) routing_table_updated = True self._logger.debug( "Found shorter path: " "{dest}:(d={dist}, n={next})".format( dest=dest, dist=dist, next=src)) elif (dest_routers_info[dest].next_router == src and dest_routers_info[dest].dist != dist): # Received route update from source. dest_routers_info[dest].dist = dist timer = Timer(self._inf_timeout) \ if dist < RIPService.inf_distance \ else Timer(self._remove_timeout) dest_routers_info[dest].timer = timer routing_table_updated = True self._logger.debug( "Received route update from source: " "{dest}:(d={dist}, n={next})".format( dest=dest, dist=dist, next=src)) else: if dist < RIPService.inf_distance: # Update timer. dest_routers_info[dest].timer.restart() else: # Don't update timer for infinite paths. pass if routing_table_updated: update_routing_table() def update_routing_table(): old_routing_table = self._dynamic_routing_table.table().copy() new_routing_table = {} for dest, dest_rr_info in dest_routers_info.iteritems(): if dest_rr_info.dist < RIPService.inf_distance: assert dest not in new_routing_table new_routing_table[dest] = \ RIPService.RIPRouteToDestination( dest_rr_info.next_router, dest_rr_info.dist) if old_routing_table != new_routing_table: self._logger.debug("New routing table:\n {0}".format( pprint.pformat(new_routing_table))) self._dynamic_routing_table.update(new_routing_table) self._logger.info("Working thread started") DestRouterInfo = recordtype( 'DestRouterInfo', 'dist next_router timer') ConnectedRouterInfo = recordtype('ConnectedRouterInfo', 'timer') # {destination router name: DestRouterInfo()} # `timer' member is for last time information about destination router # was updated, dest_routers_info = {self._router_name: DestRouterInfo(dist=0, next_router=self._router_name, timer=DummyTimer())} # {connected router: ConnectedRouterInfo()} # `timer' member if for last time information packet was sent to # router} connected_rrs_info = {} connected_routers = frozenset() while True: if self._exit_lock.acquire(False): # Obtained exit lock. Terminate. self._exit_lock.release() self._logger.info("Exit working thread") return # Prepare data for detecting changes in current router connectivity. prev_connected_routers = connected_routers connected_routers = \ frozenset(self._link_manager.connected_routers()) # Remove information about disconnected routers and add information # about newly connected routers. update_connected_routers_info() # Send distances information to all who needs it. send_distances(list(connected_routers_with_timeout())) # Update distances according to received packets. handle_receive() set_infinite_timeout_distances() remove_timeout_distances() time.sleep(config.thread_sleep_time)