예제 #1
0
    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'])
예제 #2
0
    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]
예제 #3
0
    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'])
예제 #4
0
    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]
예제 #5
0
    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)
예제 #6
0
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))
예제 #7
0
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))
예제 #8
0
    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'])
예제 #9
0
파일: qspn.py 프로젝트: erdincay/netsukuku
    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]
예제 #10
0
    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)
예제 #11
0
    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]
예제 #12
0
    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)
예제 #13
0
    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]
예제 #14
0
    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)
예제 #15
0
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)
예제 #16
0
파일: qspn.py 프로젝트: erdincay/netsukuku
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)
예제 #17
0
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
예제 #18
0
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)
예제 #19
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)
예제 #20
0
 def setUp(self):
     self.events = Event(['A', 'B'])
예제 #21
0
 def setUp(self):
     self.events = Event(['A', 'B'])
예제 #22
0
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
예제 #23
0
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
예제 #24
0
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
예제 #25
0
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))
예제 #26
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)
예제 #27
0
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))