def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.service = {} self.remotable_funcs = [self.pid_getall] self.events = Event(['P2P_HOOKED'])
def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.neigh.events.listen('NEIGH_NEW', self.etp_new_changed) self.neigh.events.listen('NEIGH_REM_CHGED', self.etp_new_changed) self.neigh.events.listen('NEIGH_DELETED', self.etp_new_dead) self.events = Event(['ETP_EXECUTED', 'NET_COLLISION']) self.remotable_funcs = [self.etp_exec]
def __init__(self, max_neigh=settings.MAX_NEIGH, xtimemod=xtime): """ max_neigh: maximum number of neighbours we can have """ self.max_neigh = max_neigh # variation on neighbours' rtt greater than this will be notified self.rtt_variation_threshold = 0.1 # ip_table self.ip_table = {} # Remote client instances table self.ntk_client = {} # ip : rpc.TCPClient(ipstr) # IP => ID translation table self.translation_table = {} # IP => netid self.netid_table = {} # the events we raise self.events = Event(['NEIGH_NEW', 'NEIGH_DELETED', 'NEIGH_REM_CHGED']) # time module self.xtime = xtimemod self.remotable_funcs = [self.ip_change]
def __init__(self, neigh, maproute): self.maproute = maproute self.neigh = neigh self.multipath = settings.MULTIPATH self.events = Event(['KRNL_NEIGH_NEW']) self.route_new = apply_wakeup_on_event(self.route_new, events=[(self.neigh.events, 'NEIGH_NEW'), (self.events, 'KRNL_NEIGH_NEW')]) self.maproute.events.listen('ROUTE_NEW', self.route_new) self.maproute.events.listen('ROUTE_DELETED', self.route_deleted) self.maproute.events.listen('ROUTE_REM_CHGED', self.route_rem_changed) self.neigh.events.listen('NEIGH_NEW', self.neigh_new) self.neigh.events.listen('NEIGH_DELETED', self.neigh_deleted) self.neigh.events.listen('NEIGH_REM_CHGED', self.neigh_rem_changed)
class TestEvent(unittest.TestCase): def setUp(self): self.events = Event(['A', 'B']) def testAddEvent(self): '''Test adding a new event''' self.events.add(['C']) self.failUnless(self.events.events == ['A', 'B', 'C']) def testEventListenFailure(self): '''Test listening of unregistered event''' self.assertRaises(EventError, self.events.listen, 'D', partial(ev_listener, 'D')) def testEventSendFailure(self): '''Test sending of unregistered event''' self.assertRaises(EventError, self.events.send, 'D', 'Message...') def testEventListen(self): '''Test event listening''' self.events.listen('A', partial(ev_listener, 'A')) self.events.send('A', (1,2,3,4)) self.failUnlessEqual(DICT_EV['A'], (1,2,3,4))
class TestEvent(unittest.TestCase): def setUp(self): self.events = Event(['A', 'B']) def testAddEvent(self): '''Test adding a new event''' self.events.add(['C']) self.failUnless(self.events.events == ['A', 'B', 'C']) def testEventListenFailure(self): '''Test listening of unregistered event''' self.assertRaises(EventError, self.events.listen, 'D', partial(ev_listener, 'D')) def testEventSendFailure(self): '''Test sending of unregistered event''' self.assertRaises(EventError, self.events.send, 'D', 'Message...') def testEventListen(self): '''Test event listening''' self.events.listen('A', partial(ev_listener, 'A')) self.events.send('A', (1, 2, 3, 4)) self.failUnlessEqual(DICT_EV['A'], (1, 2, 3, 4))
def __init__(self, levels, gsize, dataclass, me=None): """Initialise the map If me = None, then self.me is set to a random nip (ntk ip) """ self.levels = levels # Number of levels self.gsize = gsize # How many nodes are contained in a gnode self.dataclass = dataclass self.me = me # Ourself. self.me[lvl] is the ID of our # (g)node of level lvl # Choose a random nip if me is None: self.me = self.nip_rand() # The member self.node[l][i] is a node of level l and its ID is i self.node = [[None] * self.gsize for i in xrange(self.levels)] # Number of nodes of each level, that is: # self.node_nb[i] = number of (g)nodes inside the gnode self.me[i+1] self.node_nb = [0] * self.levels self.events = Event(['NODE_NEW', 'NODE_DELETED', 'ME_CHANGED'])
def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.neigh.events.listen("NEIGH_NEW", self.etp_new_changed) self.neigh.events.listen("NEIGH_REM_CHGED", self.etp_new_changed) self.neigh.events.listen("NEIGH_DELETED", self.etp_new_dead) self.events = Event(["ETP_EXECUTED", "NET_COLLISION"]) self.remotable_funcs = [self.etp_exec]
def __init__(self, broadcast, xtime): """ broadcast: an instance of the RPCBroadcast class to manage broadcast sending xtime: a wrap.xtime module """ self.xtime = xtime self.broadcast = broadcast # how many bouquet we have already sent self.bouquet_numb = 0 # when we sent the broadcast packets self.bcast_send_time = 0 # when the replies arrived self.bcast_arrival_time = {} # max_bouquet: how many packets does each bouquet contain? self.max_bouquet = settings.MAX_BOUQUET # max_wait_time: the maximum time we can wait for a reply, in seconds self.max_wait_time = settings.MAX_WAIT_TIME # max_neigh: maximum number of neighbours we can have self.max_neigh = settings.MAX_NEIGH # our neighbours self.neigh = Neighbour(self.max_neigh, self.xtime) # Send a SCAN_DONE event each time a sent bouquet has been completely # collected self.events = Event(['SCAN_DONE']) # Our netid. It's a random id used to detect network collisions. self.netid = -1 # If set to True, this module will reply to radar queries sent by our # neighbours. self.do_reply = False self.remotable_funcs = [self.reply, self.time_register] self.ntkd_id = randint(0, 2**32 - 1)
def __init__(self, radar, maproute, etp, coordnode, nics): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.etp = etp self.coordnode= coordnode self.nics = nics self.events = Event(['HOOKED']) etp.events.listen('ETP_EXECUTED', self.communicating_vessels) etp.events.listen('NET_COLLISION', self.hook) self.remotable_funcs = [self.communicating_vessels, self.highest_free_nodes]
def __init__(self, broadcast, xtime): """ broadcast: an instance of the RPCBroadcast class to manage broadcast sending xtime: a wrap.xtime module """ self.xtime = xtime self.broadcast = broadcast # how many bouquet we have already sent self.bouquet_numb = 0 # when we sent the broadcast packets self.bcast_send_time = 0 # when the replies arrived self.bcast_arrival_time = {} # max_bouquet: how many packets does each bouquet contain? self.max_bouquet = settings.MAX_BOUQUET # max_wait_time: the maximum time we can wait for a reply, in seconds self.max_wait_time = settings.MAX_WAIT_TIME # max_neigh: maximum number of neighbours we can have self.max_neigh = settings.MAX_NEIGH # our neighbours self.neigh = Neighbour(self.max_neigh, self.xtime) # Send a SCAN_DONE event each time a sent bouquet has been completely # collected self.events = Event(['SCAN_DONE']) # Our netid. It's a random id used to detect network collisions. self.netid = -1 # If set to True, this module will reply to radar queries sent by our # neighbours. self.do_reply = False self.remotable_funcs = [self.reply, self.time_register] self.ntkd_id = randint(0, 2**32-1)
class Map(object): __slots__ = ['levels', 'gsize', 'dataclass', 'me', 'node', 'node_nb', 'events'] def __init__(self, levels, gsize, dataclass, me=None): """Initialise the map If me = None, then self.me is set to a random nip (ntk ip) """ self.levels = levels # Number of levels self.gsize = gsize # How many nodes are contained in a gnode self.dataclass = dataclass self.me = me # Ourself. self.me[lvl] is the ID of our # (g)node of level lvl # Choose a random nip if me is None: self.me = self.nip_rand() # The member self.node[l][i] is a node of level l and its ID is i self.node = [[None] * self.gsize for i in xrange(self.levels)] # Number of nodes of each level, that is: # self.node_nb[i] = number of (g)nodes inside the gnode self.me[i+1] self.node_nb = [0] * self.levels self.events = Event(['NODE_NEW', 'NODE_DELETED', 'ME_CHANGED']) def node_get(self, lvl, id): """Returns from the map a node of level `lvl' and id `id'. An instance of type `self.dataclass' will always be returned: if it doesn't exist, it is created""" if self.node[lvl][id] is None: self.node[lvl][id] = self.dataclass(lvl, id) return self.node[lvl][id] def node_add(self, lvl, id, silent=0): self.node_get(lvl, id) self.node_nb[lvl] += 1 if not silent: self.events.send('NODE_NEW', (lvl, id)) def node_del(self, lvl, id, silent=0): ''' Delete node 'id` at level 'lvl` ''' if self.node_nb[lvl] > 0: self.node_nb[lvl] -= 1 if not silent: self.events.send('NODE_DELETED', (lvl, id)) self.node[lvl][id]=None def free_nodes_nb(self, lvl): """Returns the number of free nodes of level `lvl'""" return self.gsize-self.node_nb[lvl] def free_nodes_list(self, lvl): """Returns the list of free nodes of level `lvl'""" return [nid for nid in xrange(self.gsize) if self.node_get(lvl, nid).is_free()] def is_in_level(self, nip, lvl): """Does the node nip belongs to our gnode of level `lvl'?""" return nip[:-lvl-1] == self.me[:-lvl-1] def lvlid_to_nip(self, lvl, id): """Converts a (lvl, id) pair, referring to this map, to its equivalent netsukuku ip""" nip = self.me[:] nip[lvl] = id for l in reversed(xrange(lvl)): nip[l] = 0 return nip def ip_to_nip(self, ip): """Converts the given ip to a nip (Netsukuku IP) A nip is a list [a_0, a_1, ..., a_{n-1}], where n = self.levels and such that a_{n-1}*g^{n-1}+a_{n-2}*g^(n-2)+...+a_0 = ip, where g = self.gsize""" g = self.gsize return [(ip % g**(l+1)) / g**l for l in xrange(self.levels)] def nip_to_ip(self, nip): """The reverse of ip_to_nip""" g=self.gsize return sum([nip[l] * g**l for l in xrange(self.levels)]) def nip_cmp(self, nipA, nipB): """Returns the first level where nipA and nipB differs. The search start from the end of the nip """ for lvl in reversed(xrange(self.levels)): if nipA[lvl] != nipB[lvl]: return lvl return -1 def nip_rand(self): """Returns a random netsukuku ip""" return [randint(0, self.gsize-1) for i in xrange(self.levels)] def level_reset(self, level): """Resets the specified level, without raising any event""" self.node[level] = [None] * self.gsize self.node_nb[level] = 0 def map_reset(self): """Silently resets the whole map""" for l in xrange(self.levels): self.level_reset(l) def me_change(self, new_me): """Changes self.me""" old_me = self.me[:] self.me = new_me self.events.send('ME_CHANGED', (old_me, new_me)) def map_data_pack(self): '''Pack the data map''' return (self.me, [[self.node[lvl][id] for id in xrange(self.gsize)] for lvl in xrange(self.levels)], [self.node_nb[lvl] for lvl in xrange(self.levels)]) def map_data_merge(self, (nip, plist, nblist)): lvl = self.nip_cmp(nip, self.me) for l in xrange(lvl, self.levels): self.node_nb[l] = nblist[l] for id in xrange(self.gsize): self.node[l][id] = plist[l][id] for l in xrange(0, lvl): self.level_reset(l)
class Etp(object): """Extended Tracer Packet""" def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.neigh.events.listen("NEIGH_NEW", self.etp_new_changed) self.neigh.events.listen("NEIGH_REM_CHGED", self.etp_new_changed) self.neigh.events.listen("NEIGH_DELETED", self.etp_new_dead) self.events = Event(["ETP_EXECUTED", "NET_COLLISION"]) self.remotable_funcs = [self.etp_exec] @microfunc(True) def etp_new_dead(self, neigh): """Builds and sends a new ETP for the worsened link case.""" ## Create R def gw_is_neigh((dst, gw, rem)): return gw == neigh.id R = self.maproute.bestroutes_get(gw_is_neigh) ## ## Update the map self.maproute.routeneigh_del(neigh) ## if is_listlist_empty(R): # R is empty, that is we don't have routes passing by `gw'. # Therefore, nothing to update, nothing to do. return None ## Create R2 def rem_or_none(r): if r is not None: return r.rem return DeadRem() R2 = [ [(dst, rem_or_none(self.maproute.node_get(lvl, dst).best_route())) for (dst, gw, rem) in R[lvl]] for lvl in xrange(self.maproute.levels) ] ## ## Forward the ETP to the neighbours flag_of_interest = 1 TP = [[self.maproute.me[0], NullRem()]] # Tracer Packet included in block_lvl = 0 # the first block of the ETP etp = (R2, [[block_lvl, TP]], flag_of_interest) self.etp_forward(etp, [neigh.id]) ## @microfunc(True) def etp_new_changed(self, neigh, oldrem=None): """Builds and sends a new ETP for the changed link case If oldrem=None, the node `neigh' is considered new.""" ## Update the map if oldrem is None: self.maproute.routeneigh_add(neigh) else: self.maproute.routeneigh_rem(neigh) ## ## Create R def gw_isnot_neigh((dst, gw, rem)): return gw != neigh.id R = self.maproute.bestroutes_get(gw_isnot_neigh) if is_listlist_empty(R): # R is empty: no need to proceed return None def takeoff_gw((dst, gw, rem)): return (dst, rem) def takeoff_gw_lvl(L): return map(takeoff_gw, L) R = map(takeoff_gw_lvl, R) ## ## Send the ETP to `neigh' flag_of_interest = 1 TP = [[self.maproute.me[0], NullRem()]] etp = (R, [[0, TP]], flag_of_interest) neigh.ntkd.etp.etp_exec(self.maproute.me, *etp) ## @microfunc() def etp_exec(self, sender_nip, R, TPL, flag_of_interest): """Executes a received ETP sender_nip: sender ntk ip (see map.py) R : the set of routes of the ETP TPL: the tracer packet of the path covered until now by this ETP. This TP may have covered different levels. In general, TPL is a list of blocks. Each block is a (lvl, TP) pair, where lvl is the level of the block and TP is the tracer packet composed during the transit in the level `lvl'. TP is a list of (hop, rem) pairs. flag_of_interest: a boolean """ gwnip = sender_nip neigh = self.neigh.ip_to_neigh(self.maproute.nip_to_ip(gwnip)) gw = neigh.id gwrem = neigh.rem ## Collision check colliding, R = self.collision_check(gwnip, neigh, R) if colliding: # collision detected. rehook. self.events.send("NET_COLLISION", ([nr for nr in self.neigh.neigh_list() if nr.netid == neigh.netid],)) return None # drop the packet ## ## Group rule level = self.maproute.nip_cmp(self.maproute.me, gwnip) for block in TPL: lvl = block[0] # the level of the block if lvl < level: block[0] = level blockrem = sum([rem for hop, rem in block[1]], NullRem()) block[1] = [[gwnip[level], blockrem]] R[lvl] = [] ### Collapse blocks of the same level # Note: we're assuming the two blocks with the same level are one after # another. TPL2 = [TPL[0]] for block in TPL[1:]: if block[0] == TPL2[-1][0]: TPL2[-1][1] += block[1] else: TPL2.append(block) TPL = TPL2 ### ### Remove dups def remove_contiguos_dups_in_TP(L): L2 = [] prec = [None, NullRem()] for x in L: if x[0] != prec[0]: prec = x L2.append(x) else: prec[1] += x[1] return L2 for block in TPL: block[1] = remove_contiguos_dups_in_TP(block[1]) ### ## ## ATP rule for block in TPL: if self.maproute.me[block[0]] in block[1]: return # drop the pkt ## ## The rem of the first block is useless. TPL[0][1][0][1] = NullRem() ## old_node_nb = self.maproute.node_nb[:] ## Update the map from the TPL tprem = gwrem TPL_is_interesting = False for block in reversed(TPL): lvl = block[0] for dst, rem in reversed(block[1]): if self.maproute.route_change(lvl, dst, gw, tprem): TPL_is_interesting = True tprem += rem # TODO: sometimes rem is an integer ## ## Update the map from R for lvl in xrange(self.maproute.levels): for dst, rem in R[lvl]: if not self.maproute.route_rem(lvl, dst, gw, rem + tprem): self.maproute.route_change(lvl, dst, gw, rem + tprem) ## ## S S = [ [(dst, r.rem) for dst, rem in R[lvl] for r in [self.maproute.node_get(lvl, dst).best_route()] if r.gw != gw] for lvl in xrange(self.maproute.levels) ] # -- # Step 5 omitted, see qspn.pdf, 4.1 Extended Tracer Packet: # """If the property (g) holds, then the step 5 can be omitted...""" # -- # if flag_of_interest: # if not is_listlist_empty(S): # # Sflag_of_interest=0 # TP = [(self.maproute.me[0], NullRem())] # etp = (S, [(0, TP)], Sflag_of_interest) # neigh.ntkd.etp.etp_exec(self.maproute.me, *etp) ## ## R2 R2 = [ [(dst, rem) for dst, rem in R[lvl] if dst not in [d for d, r in S[lvl]]] for lvl in xrange(self.maproute.levels) ] ## ## Continue to forward the ETP if it is interesting if not is_listlist_empty(R2) or TPL_is_interesting: if TPL[-1][0] != 0: # The last block isn't of level 0. Let's add a new block TP = [[self.maproute.me[0], gwrem]] TPL.append([0, TP]) else: # The last block is of level 0. We can append our ID TPL[-1][1].append([self.maproute.me[0], gwrem]) etp = (R2, TPL, flag_of_interest) self.etp_forward(etp, [neigh.id]) ## self.events.send("ETP_EXECUTED", (old_node_nb, self.maproute.node_nb[:])) def etp_forward(self, etp, exclude): """Forwards the `etp' to all our neighbours, excluding those contained in `exclude' `Exclude' is a list of "Neighbour.id"s""" for nr in self.neigh.neigh_list(): if nr.id not in exclude: nr.ntkd.etp.etp_exec(self.maproute.me, *etp) def collision_check(self, gwnip, neigh, R): """ Checks if we are colliding with the network of `neigh'. It returns True if we are colliding and we are going to rehook. !NOTE! the set R will be modified: all the colliding routes will be removed. """ if neigh.netid == self.radar.netid or self.radar.netid == -1: self.radar.netid = neigh.netid return (False, R) # all ok # uhm... we are in different networks ## Calculate the size of the two nets mynetsz = reduce(add, self.maproute.node_nb) ngnetsz = reduce(add, map(len, R)) if mynetsz > ngnetsz or (mynetsz == ngnetsz and self.radar.netid > neigh.netid): # we don't care if we are colliding or not. We can simply # ignore colliding routes, the rest will be done by the other # net. ### Remove colliding routes from R R = [ [(dst, rem) for dst, rem in R[lvl] if self.maproute.node_get(lvl, dst).is_empty()] for lvl in xrange(self.maproute.levels) ] ### return (False, R) ## # We are the smaller net. ## Check if we are colliding with another (g)node of the neighbour ## net level = self.maproute.nip_cmp(self.maproute.me, gwnip) + 1 if level < self.maproute.levels: for dst, rem in R[level]: if dst == self.maproute.me[level]: # we are colliding! LET'S REHOOK return (True, R) ## ## Remove colliding routes directly from our map for lvl in xrange(self.maproute.levels): for dst, rem in R[lvl]: self.maproute.node_get(lvl, dst).route_reset() ## return (False, R)
class Radar(object): __slots__ = [ 'bouquet_numb', 'bcast_send_time', 'xtime', 'bcast_arrival_time', 'max_bouquet', 'max_wait_time', 'broadcast', 'neigh', 'events', 'netid', 'do_reply', 'remotable_funcs', 'ntkd_id', 'radar_id', 'max_neigh' ] def __init__(self, broadcast, xtime): """ broadcast: an instance of the RPCBroadcast class to manage broadcast sending xtime: a wrap.xtime module """ self.xtime = xtime self.broadcast = broadcast # how many bouquet we have already sent self.bouquet_numb = 0 # when we sent the broadcast packets self.bcast_send_time = 0 # when the replies arrived self.bcast_arrival_time = {} # max_bouquet: how many packets does each bouquet contain? self.max_bouquet = settings.MAX_BOUQUET # max_wait_time: the maximum time we can wait for a reply, in seconds self.max_wait_time = settings.MAX_WAIT_TIME # max_neigh: maximum number of neighbours we can have self.max_neigh = settings.MAX_NEIGH # our neighbours self.neigh = Neighbour(self.max_neigh, self.xtime) # Send a SCAN_DONE event each time a sent bouquet has been completely # collected self.events = Event(['SCAN_DONE']) # Our netid. It's a random id used to detect network collisions. self.netid = -1 # If set to True, this module will reply to radar queries sent by our # neighbours. self.do_reply = False self.remotable_funcs = [self.reply, self.time_register] self.ntkd_id = randint(0, 2**32 - 1) def run(self, started=0): if not started: micro(self.run, (1, )) else: while True: self.radar() def radar(self): """ Send broadcast packets and store the results in neigh """ self.radar_id = randint(0, 2**32 - 1) logging.debug('radar scan %s' % self.radar_id) # we're sending the broadcast packets NOW self.bcast_send_time = self.xtime.time() # send all packets in the bouquet for i in xrange(self.max_bouquet): self.broadcast.radar.reply(self.ntkd_id, self.radar_id) # then wait self.xtime.swait(self.max_wait_time * 1000) # update the neighbours' ip_table self.neigh.store(self.get_all_avg_rtt()) # Send the event self.bouquet_numb += 1 self.events.send('SCAN_DONE', (self.bouquet_numb, )) # We're done. Reset. self.radar_reset() def radar_reset(self): ''' Clean the objects needed by radar()''' # Clean some stuff self.bcast_arrival_time = {} # Reset the broadcast sockets self.broadcast.reset() def reply(self, _rpc_caller, ntkd_id, radar_id): """ As answer we'll return our netid """ if self.do_reply and ntkd_id != self.ntkd_id: rpc.BcastClient(devs=[_rpc_caller.dev], xtimemod=self.xtime).radar.time_register( radar_id, self.netid) return self.netid def time_register(self, _rpc_caller, radar_id, netid): """save each node's rtt""" if radar_id != self.radar_id: # drop. It isn't a reply to our current bouquet return ip = str_to_ip(_rpc_caller.ip) net_device = _rpc_caller.dev # this is the rtt time_elapsed = int((self.xtime.time() - self.bcast_send_time) / 2) # let's store it in the bcast_arrival_time table if ip in self.bcast_arrival_time: if net_device in self.bcast_arrival_time[ip]: self.bcast_arrival_time[ip][net_device].append(time_elapsed) else: self.bcast_arrival_time[ip][net_device] = [time_elapsed] else: self.bcast_arrival_time[ip] = {} self.bcast_arrival_time[ip][net_device] = [time_elapsed] logging.info("Radar: new IP %s detected", ip_to_str(ip)) self.neigh.netid_table[ip] = netid def get_avg_rtt(self, ip): """ ip: an ip; Calculates the average rtt of IP for each device Returns the ordered list [(dev, avgrtt)], the first element has the best average rtt. """ devlist = [] # for each NIC for dev in self.bcast_arrival_time[ip]: avg = sum(self.bcast_arrival_time[ip][dev]) / len( self.bcast_arrival_time[ip][dev]) devlist.append((dev, avg)) # sort the devices, the best is the first def second_element((x, y)): return y devlist.sort(key=second_element) return devlist def get_all_avg_rtt(self): """ Calculate the average rtt of all the ips """ all_avg = {} # for each ip for ip in self.bcast_arrival_time: devs = self.get_avg_rtt(ip) all_avg[ip] = Neigh(bestdev=devs[0], devs=dict(devs)) return all_avg
class Etp(object): """Extended Tracer Packet""" def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.neigh.events.listen('NEIGH_NEW', self.etp_new_changed) self.neigh.events.listen('NEIGH_REM_CHGED', self.etp_new_changed) self.neigh.events.listen('NEIGH_DELETED', self.etp_new_dead) self.events = Event(['ETP_EXECUTED', 'NET_COLLISION']) self.remotable_funcs = [self.etp_exec] @microfunc(True) def etp_new_dead(self, neigh): """Builds and sends a new ETP for the worsened link case.""" ## Create R def gw_is_neigh((dst, gw, rem)): return gw == neigh.id R = self.maproute.bestroutes_get(gw_is_neigh) ## ## Update the map self.maproute.routeneigh_del(neigh) ## if is_listlist_empty(R): # R is empty, that is we don't have routes passing by `gw'. # Therefore, nothing to update, nothing to do. return None ## Create R2 def rem_or_none(r): if r is not None: return r.rem return DeadRem() R2 = [[(dst, rem_or_none(self.maproute.node_get(lvl, dst).best_route())) for (dst, gw, rem) in R[lvl]] for lvl in xrange(self.maproute.levels)] ## ## Forward the ETP to the neighbours flag_of_interest = 1 TP = [[self.maproute.me[0], NullRem()]] # Tracer Packet included in block_lvl = 0 # the first block of the ETP etp = (R2, [[block_lvl, TP]], flag_of_interest) self.etp_forward(etp, [neigh.id]) ## @microfunc(True) def etp_new_changed(self, neigh, oldrem=None): """Builds and sends a new ETP for the changed link case If oldrem=None, the node `neigh' is considered new.""" ## Update the map if oldrem is None: self.maproute.routeneigh_add(neigh) else: self.maproute.routeneigh_rem(neigh) ## ## Create R def gw_isnot_neigh((dst, gw, rem)): return gw != neigh.id R = self.maproute.bestroutes_get(gw_isnot_neigh) if is_listlist_empty(R): # R is empty: no need to proceed return None def takeoff_gw((dst, gw, rem)): return (dst, rem) def takeoff_gw_lvl(L): return map(takeoff_gw, L) R = map(takeoff_gw_lvl, R) ## ## Send the ETP to `neigh' flag_of_interest = 1 TP = [[self.maproute.me[0], NullRem()]] etp = (R, [[0, TP]], flag_of_interest) neigh.ntkd.etp.etp_exec(self.maproute.me, *etp) ## @microfunc() def etp_exec(self, sender_nip, R, TPL, flag_of_interest): """Executes a received ETP sender_nip: sender ntk ip (see map.py) R : the set of routes of the ETP TPL: the tracer packet of the path covered until now by this ETP. This TP may have covered different levels. In general, TPL is a list of blocks. Each block is a (lvl, TP) pair, where lvl is the level of the block and TP is the tracer packet composed during the transit in the level `lvl'. TP is a list of (hop, rem) pairs. flag_of_interest: a boolean """ gwnip = sender_nip neigh = self.neigh.ip_to_neigh(self.maproute.nip_to_ip(gwnip)) gw = neigh.id gwrem = neigh.rem ## Collision check colliding, R = self.collision_check(gwnip, neigh, R) if colliding: # collision detected. rehook. self.events.send('NET_COLLISION', ([ nr for nr in self.neigh.neigh_list() if nr.netid == neigh.netid ], )) return None # drop the packet ## ## Group rule level = self.maproute.nip_cmp(self.maproute.me, gwnip) for block in TPL: lvl = block[0] # the level of the block if lvl < level: block[0] = level blockrem = sum([rem for hop, rem in block[1]], NullRem()) block[1] = [[gwnip[level], blockrem]] R[lvl] = [] ### Collapse blocks of the same level #Note: we're assuming the two blocks with the same level are one after # another. TPL2 = [TPL[0]] for block in TPL[1:]: if block[0] == TPL2[-1][0]: TPL2[-1][1] += block[1] else: TPL2.append(block) TPL = TPL2 ### ### Remove dups def remove_contiguos_dups_in_TP(L): L2 = [] prec = [None, NullRem()] for x in L: if x[0] != prec[0]: prec = x L2.append(x) else: prec[1] += x[1] return L2 for block in TPL: block[1] = remove_contiguos_dups_in_TP(block[1]) ### ## ## ATP rule for block in TPL: if self.maproute.me[block[0]] in block[1]: return # drop the pkt ## ## The rem of the first block is useless. TPL[0][1][0][1] = NullRem() ## old_node_nb = self.maproute.node_nb[:] ## Update the map from the TPL tprem = gwrem TPL_is_interesting = False for block in reversed(TPL): lvl = block[0] for dst, rem in reversed(block[1]): if self.maproute.route_change(lvl, dst, gw, tprem): TPL_is_interesting = True tprem += rem # TODO: sometimes rem is an integer ## ## Update the map from R for lvl in xrange(self.maproute.levels): for dst, rem in R[lvl]: if not self.maproute.route_rem(lvl, dst, gw, rem + tprem): self.maproute.route_change(lvl, dst, gw, rem + tprem) ## ## S S = [[(dst, r.rem) for dst, rem in R[lvl] for r in [self.maproute.node_get(lvl, dst).best_route()] if r.gw != gw] for lvl in xrange(self.maproute.levels)] #-- # Step 5 omitted, see qspn.pdf, 4.1 Extended Tracer Packet: # """If the property (g) holds, then the step 5 can be omitted...""" #-- #if flag_of_interest: # if not is_listlist_empty(S): # # Sflag_of_interest=0 # TP = [(self.maproute.me[0], NullRem())] # etp = (S, [(0, TP)], Sflag_of_interest) # neigh.ntkd.etp.etp_exec(self.maproute.me, *etp) ## ## R2 R2 = [[(dst, rem) for dst, rem in R[lvl] if dst not in [d for d, r in S[lvl]]] for lvl in xrange(self.maproute.levels)] ## ## Continue to forward the ETP if it is interesting if not is_listlist_empty(R2) or TPL_is_interesting: if TPL[-1][0] != 0: # The last block isn't of level 0. Let's add a new block TP = [[self.maproute.me[0], gwrem]] TPL.append([0, TP]) else: # The last block is of level 0. We can append our ID TPL[-1][1].append([self.maproute.me[0], gwrem]) etp = (R2, TPL, flag_of_interest) self.etp_forward(etp, [neigh.id]) ## self.events.send('ETP_EXECUTED', (old_node_nb, self.maproute.node_nb[:])) def etp_forward(self, etp, exclude): """Forwards the `etp' to all our neighbours, excluding those contained in `exclude' `Exclude' is a list of "Neighbour.id"s""" for nr in self.neigh.neigh_list(): if nr.id not in exclude: nr.ntkd.etp.etp_exec(self.maproute.me, *etp) def collision_check(self, gwnip, neigh, R): """ Checks if we are colliding with the network of `neigh'. It returns True if we are colliding and we are going to rehook. !NOTE! the set R will be modified: all the colliding routes will be removed. """ if neigh.netid == self.radar.netid or self.radar.netid == -1: self.radar.netid = neigh.netid return (False, R) # all ok # uhm... we are in different networks ## Calculate the size of the two nets mynetsz = reduce(add, self.maproute.node_nb) ngnetsz = reduce(add, map(len, R)) if mynetsz > ngnetsz or \ (mynetsz == ngnetsz and self.radar.netid > neigh.netid): # we don't care if we are colliding or not. We can simply # ignore colliding routes, the rest will be done by the other # net. ### Remove colliding routes from R R = [[(dst, rem) for dst, rem in R[lvl] if self.maproute.node_get(lvl, dst).is_empty()] for lvl in xrange(self.maproute.levels)] ### return (False, R) ## # We are the smaller net. ## Check if we are colliding with another (g)node of the neighbour ## net level = self.maproute.nip_cmp(self.maproute.me, gwnip) + 1 if level < self.maproute.levels: for dst, rem in R[level]: if dst == self.maproute.me[level]: # we are colliding! LET'S REHOOK return (True, R) ## ## Remove colliding routes directly from our map for lvl in xrange(self.maproute.levels): for dst, rem in R[lvl]: self.maproute.node_get(lvl, dst).route_reset() ## return (False, R)
class Neighbour(object): """ This class manages all neighbours """ __slots__ = [ 'max_neigh', 'rtt_variation_threshold', 'ip_table', 'ntk_client', 'translation_table', 'netid_table', 'events', 'remotable_funcs', 'xtime' ] def __init__(self, max_neigh=settings.MAX_NEIGH, xtimemod=xtime): """ max_neigh: maximum number of neighbours we can have """ self.max_neigh = max_neigh # variation on neighbours' rtt greater than this will be notified self.rtt_variation_threshold = 0.1 # ip_table self.ip_table = {} # Remote client instances table self.ntk_client = {} # ip : rpc.TCPClient(ipstr) # IP => ID translation table self.translation_table = {} # IP => netid self.netid_table = {} # the events we raise self.events = Event(['NEIGH_NEW', 'NEIGH_DELETED', 'NEIGH_REM_CHGED']) # time module self.xtime = xtimemod self.remotable_funcs = [self.ip_change] def neigh_list(self): """ return the list of neighbours """ nlist = [] for key, val in self.ip_table.items(): nlist.append( Neigh(bestdev=val.bestdev, devs=val.devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key])) return nlist def ip_to_id(self, ipn): """ if ipn is in the translation table, return the associated id; if it isn't, insert it into the translation table assigning a new id, if the table isn't full """ if ipn in self.translation_table: return self.translation_table[ipn] new_id = self._find_hole_in_tt() if new_id: self.translation_table[ipn] = new_id return new_id else: return False def ip_to_neigh(self, ip): """ ip: neighbour's ip return a Neigh object from an ip """ if ip not in self.translation_table: return None else: return Neigh(bestdev=self.ip_table[ip].bestdev, devs=self.ip_table[ip].devs, idn=self.translation_table[ip], ip=ip, netid=self.netid_table[ip], ntkd=self.ntk_client[ip]) def id_to_ip(self, id): """Returns the IP associated to `id'. If not found, returns None """ for ip in self.translation_table: if self.translation_table[ip] == id: return ip return None def id_to_neigh(self, id): """Returns a Neigh object from an id""" return self.ip_to_neigh(self.id_to_ip(id)) def _truncate(self, ip_table): """ip_table: an {IP => NodeInfo}; we want the best (with the lowest rtt) max_neigh nodes only to remain in the table """ # auxiliary function, to take rtt from {IP => NodeInfo} def interesting(x): return x[1].bestdev[1] # remember who we are truncating truncated = [] # the new table, without truncated rows ip_table_trunc = {} counter = 0 # we're cycling through ip_table, ordered by rtt for key, val in sorted(ip_table.items(), reverse=False, key=interesting): # if we haven't still reached max_neigh entries in the new ip_table if counter < self.max_neigh: # add the current row into ip_table ip_table_trunc[key] = val else: # otherwise just drop it # but, if old ip_table contained this row, we should notify # our listeners about this: if key in self.ip_table: # remember we are truncating this row truncated.append(key) # delete the entry self.delete(key, remove_from_iptable=False) counter += 1 # return the new ip_table and the list of truncated entries return (ip_table_trunc, truncated) def _find_hole_in_tt(self): """Find the first available index in translation_table""" for i in xrange(1, self.max_neigh + 1): if i not in self.translation_table.values(): return i return False def store(self, ip_table): """Substitute the old ip_table with the new and notify about the changes ip_table: the new ip_table; """ # the rows deleted during truncation died_ip_list = [] ip_table, died_ip_list = self._truncate(ip_table) # first of all we cycle through the old ip_table # looking for nodes that aren't in the new one for key in self.ip_table: # if we find a row that isn't in the new ip_table and whose # deletion hasn't already been notified (raising an event) # during truncation if not key in ip_table and not key in died_ip_list: self.delete(key, remove_from_iptable=False) # now we cycle through the new ip_table # looking for nodes who weren't in the old one # or whose rtt has sensibly changed for key in ip_table: # if a node has been added if not key in self.ip_table: # generate an id and add the entry in translation_table self.ip_to_id(key) # create a TCP connection to the neighbour self.ntk_client[key] = rpc.TCPClient(ip_to_str(key)) # send a message notifying we added a node self.events.send('NEIGH_NEW', (Neigh(bestdev=ip_table[key].bestdev, devs=ip_table[key].devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key]), )) else: # otherwise (if the node already was in old ip_table) check if # its rtt has changed more than rtt_variation new_rtt = ip_table[key].bestdev[1] old_rtt = self.ip_table[key].bestdev[1] rtt_variation = abs(new_rtt - old_rtt) / float(old_rtt) if rtt_variation > self.rtt_variation_threshold: # send a message notifying the node's rtt changed self.events.send( 'NEIGH_REM_CHGED', (Neigh(bestdev=self.ip_table[key].bestdev, devs=self.ip_table[key].devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key]), Rtt(new_rtt))) # finally, update the ip_table self.ip_table = ip_table def readvertise(self): """Sends a NEIGH_NEW event for each stored neighbour""" for key in self.ip_table: self.events.send('NEIGH_NEW', (Neigh(bestdev=self.ip_table[key].bestdev, devs=self.ip_table[key].devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key]), )) def delete(self, ip, remove_from_iptable=True): """Deletes an entry from the ip_table""" logging.info("Deleting neighbour %s", ip_to_str(ip)) if remove_from_iptable: del self.ip_table[ip] # close the connection ( if any ) if self.ntk_client[ip].connected: self.ntk_client[ip].close() # delete the entry from the translation table... old_id = self.translation_table.pop(ip) # ...and from the netid_table old_netid = self.netid_table.pop(ip) # send a message notifying we deleted the entry self.events.send('NEIGH_DELETED', (Neigh(bestdev=None, devs=None, idn=old_id, ip=ip, netid=old_netid, ntkd=self.ntk_client[ip]), )) del self.ntk_client[ip] def ip_change(self, oldip, newip): """Adds `newip' in the Neighbours as a copy of `oldip', then it removes `oldip'. The relative events are raised.""" logging.info("New IP of neighbour %s is now %s " % (ip_to_str(oldip), ip_to_str(newip))) self.ip_table[newip] = self.ip_table[oldip] self.translation_table[newip] = self.translation_table[oldip] self.netid_table[newip] = self.netid_table[oldip] # we have to create a new TCP connection self.ntk_client[newip] = rpc.TCPClient(ip_to_str(newip)) self.events.send('NEIGH_NEW', (Neigh(bestdev=self.ip_table[newip].bestdev, devs=self.ip_table[newip].devs, idn=self.translation_table[newip], ip=newip, netid=self.netid_table[newip], ntkd=self.ntk_client[newip]), )) self.delete(oldip)
def setUp(self): self.events = Event(['A', 'B'])
class P2PAll(object): """Class of all the registered P2P services""" __slots__ = ['radar', 'neigh', 'maproute', 'service', 'remotable_funcs', 'events'] def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.service = {} self.remotable_funcs = [self.pid_getall] self.events = Event(['P2P_HOOKED']) def listen_hook_ev(self, hook): hook.events.listen('HOOKED', self.p2p_hook) def pid_add(self, pid): self.service[pid] = P2P(self.radar, self.maproute, pid) return self.service[pid] def pid_del(self, pid): if pid in self.service: del self.service[pid] def pid_get(self, pid): if pid not in self.service: return self.pid_add(pid) else: return self.service[pid] def pid_getall(self): return [(s, self.service[s].mapp2p.map_data_pack()) for s in self.service] def p2p_register(self, p2p): """Used to add for the first time a P2P instance of a module in the P2PAll dictionary.""" # It's possible that the stub P2P instance `self.pid_get(p2p.pid)' # created by pid_add() has an update map of participants, which has # been accumulated during the time. Copy this map in the `p2p' # instance to be sure. map_pack = self.pid_get(p2p.pid).mapp2p.map_data_pack() p2p.mapp2p.map_data_merge(map_pack) self.service[p2p.pid] = p2p def participant_add(self, pid, pIP): self.pid_get(pid).participant_add(pIP) @microfunc() def p2p_hook(self, *args): """P2P hooking procedure It gets the P2P maps from our nearest neighbour""" ## Find our nearest neighbour minlvl = self.maproute.levels minnr = None for nr in self.neigh.neigh_list(): lvl = self.maproute.nip_cmp(self.maproute.me, self.maproute.ip_to_nip(nr.ip)) if lvl < minlvl: minlvl = lvl minnr = nr ## if minnr == None: # nothing to do return nrmaps_pack = minnr.ntkd.p2p.pid_getall() for (pid, map_pack) in nrmaps_pack: self.pid_get(pid).mapp2p.map_data_merge(map_pack) for s in self.service: if self.service[s].participant: self.service[s].participate() self.events.send('P2P_HOOKED', ()) def __getattr__(self, str): if str[:4] == "PID_": return self.pid_get(int(str[4:])) raise AttributeError
class P2PAll(object): """Class of all the registered P2P services""" __slots__ = [ 'radar', 'neigh', 'maproute', 'service', 'remotable_funcs', 'events' ] def __init__(self, radar, maproute): self.radar = radar self.neigh = radar.neigh self.maproute = maproute self.service = {} self.remotable_funcs = [self.pid_getall] self.events = Event(['P2P_HOOKED']) def listen_hook_ev(self, hook): hook.events.listen('HOOKED', self.p2p_hook) def pid_add(self, pid): self.service[pid] = P2P(self.radar, self.maproute, pid) return self.service[pid] def pid_del(self, pid): if pid in self.service: del self.service[pid] def pid_get(self, pid): if pid not in self.service: return self.pid_add(pid) else: return self.service[pid] def pid_getall(self): return [(s, self.service[s].mapp2p.map_data_pack()) for s in self.service] def p2p_register(self, p2p): """Used to add for the first time a P2P instance of a module in the P2PAll dictionary.""" # It's possible that the stub P2P instance `self.pid_get(p2p.pid)' # created by pid_add() has an update map of participants, which has # been accumulated during the time. Copy this map in the `p2p' # instance to be sure. map_pack = self.pid_get(p2p.pid).mapp2p.map_data_pack() p2p.mapp2p.map_data_merge(map_pack) self.service[p2p.pid] = p2p def participant_add(self, pid, pIP): self.pid_get(pid).participant_add(pIP) @microfunc() def p2p_hook(self, *args): """P2P hooking procedure It gets the P2P maps from our nearest neighbour""" ## Find our nearest neighbour minlvl = self.maproute.levels minnr = None for nr in self.neigh.neigh_list(): lvl = self.maproute.nip_cmp(self.maproute.me, self.maproute.ip_to_nip(nr.ip)) if lvl < minlvl: minlvl = lvl minnr = nr ## if minnr == None: # nothing to do return nrmaps_pack = minnr.ntkd.p2p.pid_getall() for (pid, map_pack) in nrmaps_pack: self.pid_get(pid).mapp2p.map_data_merge(map_pack) for s in self.service: if self.service[s].participant: self.service[s].participate() self.events.send('P2P_HOOKED', ()) def __getattr__(self, str): if str[:4] == "PID_": return self.pid_get(int(str[4:])) raise AttributeError
class Radar(object): __slots__ = [ 'bouquet_numb', 'bcast_send_time', 'xtime', 'bcast_arrival_time', 'max_bouquet', 'max_wait_time', 'broadcast', 'neigh', 'events', 'netid', 'do_reply', 'remotable_funcs', 'ntkd_id', 'radar_id', 'max_neigh'] def __init__(self, broadcast, xtime): """ broadcast: an instance of the RPCBroadcast class to manage broadcast sending xtime: a wrap.xtime module """ self.xtime = xtime self.broadcast = broadcast # how many bouquet we have already sent self.bouquet_numb = 0 # when we sent the broadcast packets self.bcast_send_time = 0 # when the replies arrived self.bcast_arrival_time = {} # max_bouquet: how many packets does each bouquet contain? self.max_bouquet = settings.MAX_BOUQUET # max_wait_time: the maximum time we can wait for a reply, in seconds self.max_wait_time = settings.MAX_WAIT_TIME # max_neigh: maximum number of neighbours we can have self.max_neigh = settings.MAX_NEIGH # our neighbours self.neigh = Neighbour(self.max_neigh, self.xtime) # Send a SCAN_DONE event each time a sent bouquet has been completely # collected self.events = Event(['SCAN_DONE']) # Our netid. It's a random id used to detect network collisions. self.netid = -1 # If set to True, this module will reply to radar queries sent by our # neighbours. self.do_reply = False self.remotable_funcs = [self.reply, self.time_register] self.ntkd_id = randint(0, 2**32-1) def run(self, started=0): if not started: micro(self.run, (1,)) else: while True: self.radar() def radar(self): """ Send broadcast packets and store the results in neigh """ self.radar_id = randint(0, 2**32-1) logging.debug('radar scan %s' % self.radar_id) # we're sending the broadcast packets NOW self.bcast_send_time = self.xtime.time() # send all packets in the bouquet for i in xrange(self.max_bouquet): self.broadcast.radar.reply(self.ntkd_id, self.radar_id) # then wait self.xtime.swait(self.max_wait_time * 1000) # update the neighbours' ip_table self.neigh.store(self.get_all_avg_rtt()) # Send the event self.bouquet_numb += 1 self.events.send('SCAN_DONE', (self.bouquet_numb,)) # We're done. Reset. self.radar_reset() def radar_reset(self): ''' Clean the objects needed by radar()''' # Clean some stuff self.bcast_arrival_time = {} # Reset the broadcast sockets self.broadcast.reset() def reply(self, _rpc_caller, ntkd_id, radar_id): """ As answer we'll return our netid """ if self.do_reply and ntkd_id != self.ntkd_id: rpc.BcastClient(devs=[_rpc_caller.dev], xtimemod=self.xtime).radar.time_register(radar_id, self.netid) return self.netid def time_register(self, _rpc_caller, radar_id, netid): """save each node's rtt""" if radar_id != self.radar_id: # drop. It isn't a reply to our current bouquet return ip = str_to_ip(_rpc_caller.ip) net_device = _rpc_caller.dev # this is the rtt time_elapsed = int((self.xtime.time() - self.bcast_send_time) / 2) # let's store it in the bcast_arrival_time table if ip in self.bcast_arrival_time: if net_device in self.bcast_arrival_time[ip]: self.bcast_arrival_time[ip][net_device].append(time_elapsed) else: self.bcast_arrival_time[ip][net_device] = [time_elapsed] else: self.bcast_arrival_time[ip] = {} self.bcast_arrival_time[ip][net_device] = [time_elapsed] logging.info("Radar: new IP %s detected", ip_to_str(ip)) self.neigh.netid_table[ip] = netid def get_avg_rtt(self, ip): """ ip: an ip; Calculates the average rtt of IP for each device Returns the ordered list [(dev, avgrtt)], the first element has the best average rtt. """ devlist = [] # for each NIC for dev in self.bcast_arrival_time[ip]: avg = sum(self.bcast_arrival_time[ip][dev]) / len(self.bcast_arrival_time[ip][dev]) devlist.append( (dev, avg) ) # sort the devices, the best is the first def second_element((x,y)): return y devlist.sort(key=second_element) return devlist def get_all_avg_rtt(self): """ Calculate the average rtt of all the ips """ all_avg = {} # for each ip for ip in self.bcast_arrival_time: devs = self.get_avg_rtt(ip) all_avg[ip] = Neigh(bestdev=devs[0], devs=dict(devs)) return all_avg
class KrnlRoute(object): def __init__(self, neigh, maproute): self.maproute = maproute self.neigh = neigh self.multipath = settings.MULTIPATH self.events = Event(['KRNL_NEIGH_NEW']) self.route_new = apply_wakeup_on_event(self.route_new, events=[(self.neigh.events, 'NEIGH_NEW'), (self.events, 'KRNL_NEIGH_NEW')]) self.maproute.events.listen('ROUTE_NEW', self.route_new) self.maproute.events.listen('ROUTE_DELETED', self.route_deleted) self.maproute.events.listen('ROUTE_REM_CHGED', self.route_rem_changed) self.neigh.events.listen('NEIGH_NEW', self.neigh_new) self.neigh.events.listen('NEIGH_DELETED', self.neigh_deleted) self.neigh.events.listen('NEIGH_REM_CHGED', self.neigh_rem_changed) @microfunc(True) def route_new(self, lvl, dst, gw, rem, event_wait=None): if not self.multipath and self.maproute.node_get( lvl, dst).nroutes_synced() >= 1: # We don't have multipath and we've already set one route. return nip = self.maproute.lvlid_to_nip(lvl, dst) ip = self.maproute.nip_to_ip(nip) ipstr = ip_to_str(ip) neigh = self.neigh.id_to_neigh(gw) dev = neigh.bestdev[0] gwipstr = ip_to_str(neigh.ip) neigh_node = self.maproute.node_get( *self.maproute.routeneigh_get(neigh)) if neigh_node.nroutes() > 1: # Let's wait to add the neighbour first while 1: ev_neigh = event_wait[(self.neigh.events, 'NEIGH_NEW')]() if neigh == ev_neigh[0]: # found break if neigh_node.routes_tobe_synced > 0: # The routes to neigh are still to be synced, let's wait while 1: ev_neigh = event_wait[(self.events, 'KRNL_NEIGH_NEW')]() if neigh == ev_neigh[0]: # found break # We can add the route in the kernel KRoute.add(ipstr, lvl_to_bits(lvl), dev, gwipstr) self.maproute.node_get(lvl, dst).routes_tobe_synced -= 1 @microfunc(True) def route_deleted(self, lvl, dst, gw): nip = self.maproute.lvlid_to_nip(lvl, dst) ip = self.maproute.nip_to_ip(nip) ipstr = ip_to_str(ip) neigh = self.neigh.id_to_neigh(gw) dev = neigh.bestdev[0] gwipstr = ip_to_str(neigh.ip) KRoute.delete(ipstr, lvl_to_bits(lvl), gateway=gwipstr) def route_rem_changed(self, lvl, dst, gw, rem, oldrem): pass @microfunc(True) def neigh_new(self, neigh): ipstr = ip_to_str(neigh.ip) dev = neigh.bestdev[0] gwipstr = ipstr KRoute.add(ipstr, lvl_to_bits(0), dev, gwipstr) self.events.send('KRNL_NEIGH_NEW', (neigh, )) def neigh_rem_changed(self, neigh, oldrem=None): pass @microfunc(True) def neigh_deleted(self, neigh): ipstr = ip_to_str(neigh.ip) KRoute.delete(ipstr, lvl_to_bits(0))
class Neighbour(object): """ This class manages all neighbours """ __slots__ = ['max_neigh', 'rtt_variation_threshold', 'ip_table', 'ntk_client', 'translation_table', 'netid_table', 'events', 'remotable_funcs', 'xtime'] def __init__(self, max_neigh=settings.MAX_NEIGH, xtimemod=xtime): """ max_neigh: maximum number of neighbours we can have """ self.max_neigh = max_neigh # variation on neighbours' rtt greater than this will be notified self.rtt_variation_threshold = 0.1 # ip_table self.ip_table = {} # Remote client instances table self.ntk_client = {} # ip : rpc.TCPClient(ipstr) # IP => ID translation table self.translation_table = {} # IP => netid self.netid_table = {} # the events we raise self.events = Event(['NEIGH_NEW', 'NEIGH_DELETED', 'NEIGH_REM_CHGED']) # time module self.xtime = xtimemod self.remotable_funcs = [self.ip_change] def neigh_list(self): """ return the list of neighbours """ nlist = [] for key, val in self.ip_table.items(): nlist.append(Neigh(bestdev=val.bestdev, devs=val.devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key])) return nlist def ip_to_id(self, ipn): """ if ipn is in the translation table, return the associated id; if it isn't, insert it into the translation table assigning a new id, if the table isn't full """ if ipn in self.translation_table: return self.translation_table[ipn] new_id = self._find_hole_in_tt() if new_id: self.translation_table[ipn] = new_id return new_id else: return False def ip_to_neigh(self, ip): """ ip: neighbour's ip return a Neigh object from an ip """ if ip not in self.translation_table: return None else: return Neigh(bestdev=self.ip_table[ip].bestdev, devs=self.ip_table[ip].devs, idn=self.translation_table[ip], ip=ip, netid=self.netid_table[ip], ntkd=self.ntk_client[ip]) def id_to_ip(self, id): """Returns the IP associated to `id'. If not found, returns None """ for ip in self.translation_table: if self.translation_table[ip] == id: return ip return None def id_to_neigh(self, id): """Returns a Neigh object from an id""" return self.ip_to_neigh(self.id_to_ip(id)) def _truncate(self, ip_table): """ip_table: an {IP => NodeInfo}; we want the best (with the lowest rtt) max_neigh nodes only to remain in the table """ # auxiliary function, to take rtt from {IP => NodeInfo} def interesting(x): return x[1].bestdev[1] # remember who we are truncating truncated = [] # the new table, without truncated rows ip_table_trunc = {} counter = 0 # we're cycling through ip_table, ordered by rtt for key, val in sorted(ip_table.items(), reverse=False, key=interesting): # if we haven't still reached max_neigh entries in the new ip_table if counter < self.max_neigh: # add the current row into ip_table ip_table_trunc[key] = val else: # otherwise just drop it # but, if old ip_table contained this row, we should notify # our listeners about this: if key in self.ip_table: # remember we are truncating this row truncated.append(key) # delete the entry self.delete(key, remove_from_iptable=False) counter += 1 # return the new ip_table and the list of truncated entries return (ip_table_trunc, truncated) def _find_hole_in_tt(self): """Find the first available index in translation_table""" for i in xrange(1, self.max_neigh + 1): if i not in self.translation_table.values(): return i return False def store(self, ip_table): """Substitute the old ip_table with the new and notify about the changes ip_table: the new ip_table; """ # the rows deleted during truncation died_ip_list = [] ip_table, died_ip_list = self._truncate(ip_table) # first of all we cycle through the old ip_table # looking for nodes that aren't in the new one for key in self.ip_table: # if we find a row that isn't in the new ip_table and whose # deletion hasn't already been notified (raising an event) # during truncation if not key in ip_table and not key in died_ip_list: self.delete(key, remove_from_iptable=False) # now we cycle through the new ip_table # looking for nodes who weren't in the old one # or whose rtt has sensibly changed for key in ip_table: # if a node has been added if not key in self.ip_table: # generate an id and add the entry in translation_table self.ip_to_id(key) # create a TCP connection to the neighbour self.ntk_client[key] = rpc.TCPClient(ip_to_str(key)) # send a message notifying we added a node self.events.send('NEIGH_NEW', (Neigh(bestdev=ip_table[key].bestdev, devs=ip_table[key].devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key]),)) else: # otherwise (if the node already was in old ip_table) check if # its rtt has changed more than rtt_variation new_rtt = ip_table[key].bestdev[1] old_rtt = self.ip_table[key].bestdev[1] rtt_variation = abs(new_rtt - old_rtt) / float(old_rtt) if rtt_variation > self.rtt_variation_threshold: # send a message notifying the node's rtt changed self.events.send('NEIGH_REM_CHGED', (Neigh(bestdev=self.ip_table[key].bestdev, devs=self.ip_table[key].devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key]), Rtt(new_rtt))) # finally, update the ip_table self.ip_table = ip_table def readvertise(self): """Sends a NEIGH_NEW event for each stored neighbour""" for key in self.ip_table: self.events.send('NEIGH_NEW', (Neigh(bestdev=self.ip_table[key].bestdev, devs=self.ip_table[key].devs, idn=self.translation_table[key], ip=key, netid=self.netid_table[key], ntkd=self.ntk_client[key]),)) def delete(self, ip, remove_from_iptable=True): """Deletes an entry from the ip_table""" logging.info("Deleting neighbour %s", ip_to_str(ip)) if remove_from_iptable: del self.ip_table[ip] # close the connection ( if any ) if self.ntk_client[ip].connected: self.ntk_client[ip].close() # delete the entry from the translation table... old_id = self.translation_table.pop(ip) # ...and from the netid_table old_netid = self.netid_table.pop(ip) # send a message notifying we deleted the entry self.events.send('NEIGH_DELETED', (Neigh(bestdev=None, devs=None, idn=old_id, ip=ip, netid=old_netid, ntkd=self.ntk_client[ip]),)) del self.ntk_client[ip] def ip_change(self, oldip, newip): """Adds `newip' in the Neighbours as a copy of `oldip', then it removes `oldip'. The relative events are raised.""" logging.info("New IP of neighbour %s is now %s " % (ip_to_str(oldip), ip_to_str(newip))) self.ip_table[newip] = self.ip_table[oldip] self.translation_table[newip] = self.translation_table[oldip] self.netid_table[newip] = self.netid_table[oldip] # we have to create a new TCP connection self.ntk_client[newip] = rpc.TCPClient(ip_to_str(newip)) self.events.send('NEIGH_NEW', (Neigh(bestdev=self.ip_table[newip].bestdev, devs=self.ip_table[newip].devs, idn=self.translation_table[newip], ip=newip, netid=self.netid_table[newip], ntkd=self.ntk_client[newip]),)) self.delete(oldip)
class KrnlRoute(object): def __init__(self, neigh, maproute): self.maproute = maproute self.neigh = neigh self.multipath = settings.MULTIPATH self.events = Event(['KRNL_NEIGH_NEW']) self.route_new = apply_wakeup_on_event(self.route_new, events=[(self.neigh.events, 'NEIGH_NEW'), (self.events, 'KRNL_NEIGH_NEW')]) self.maproute.events.listen('ROUTE_NEW', self.route_new) self.maproute.events.listen('ROUTE_DELETED', self.route_deleted) self.maproute.events.listen('ROUTE_REM_CHGED', self.route_rem_changed) self.neigh.events.listen('NEIGH_NEW', self.neigh_new) self.neigh.events.listen('NEIGH_DELETED', self.neigh_deleted) self.neigh.events.listen('NEIGH_REM_CHGED', self.neigh_rem_changed) @microfunc(True) def route_new(self, lvl, dst, gw, rem, event_wait=None): if not self.multipath and self.maproute.node_get(lvl, dst).nroutes_synced() >= 1: # We don't have multipath and we've already set one route. return nip = self.maproute.lvlid_to_nip(lvl, dst) ip = self.maproute.nip_to_ip(nip) ipstr = ip_to_str(ip) neigh = self.neigh.id_to_neigh(gw) dev = neigh.bestdev[0] gwipstr = ip_to_str(neigh.ip) neigh_node = self.maproute.node_get(*self.maproute.routeneigh_get(neigh)) if neigh_node.nroutes() > 1: # Let's wait to add the neighbour first while 1: ev_neigh = event_wait[(self.neigh.events, 'NEIGH_NEW')]() if neigh == ev_neigh[0]: # found break if neigh_node.routes_tobe_synced > 0: # The routes to neigh are still to be synced, let's wait while 1: ev_neigh = event_wait[(self.events, 'KRNL_NEIGH_NEW')]() if neigh == ev_neigh[0]: # found break # We can add the route in the kernel KRoute.add(ipstr, lvl_to_bits(lvl), dev, gwipstr) self.maproute.node_get(lvl, dst).routes_tobe_synced-=1 @microfunc(True) def route_deleted(self, lvl, dst, gw): nip = self.maproute.lvlid_to_nip(lvl, dst) ip = self.maproute.nip_to_ip(nip) ipstr = ip_to_str(ip) neigh = self.neigh.id_to_neigh(gw) dev = neigh.bestdev[0] gwipstr = ip_to_str(neigh.ip) KRoute.delete(ipstr, lvl_to_bits(lvl), gateway=gwipstr) def route_rem_changed(self, lvl, dst, gw, rem, oldrem): pass @microfunc(True) def neigh_new(self, neigh): ipstr = ip_to_str(neigh.ip) dev = neigh.bestdev[0] gwipstr = ipstr KRoute.add(ipstr, lvl_to_bits(0), dev, gwipstr) self.events.send('KRNL_NEIGH_NEW', (neigh,)) def neigh_rem_changed(self, neigh, oldrem=None): pass @microfunc(True) def neigh_deleted(self, neigh): ipstr = ip_to_str(neigh.ip) KRoute.delete(ipstr, lvl_to_bits(0))