示例#1
0
文件: dodag.py 项目: abdelwas/Ryu-RPL
    def __init__(self, instanceID, version, G, MOP, Prf, DTSN, dodagID, rplnode,\
                 advertised_prefixes=[], of = of_zero(), active=False, is_root=False):
        """ Creates DODAG Object identified by instanceID, dodagID, version
        """
        super(dodag, self).__init__()
        
        
        self.of = of #Instance of objective function object
        self.__lock               = RLock()
        self.instanceID           = instanceID
        self.version              = Lollipop(version)
        self.dodagID              = dodagID
        self.G                    = G
        self.MOP                  = MOP
        self.Prf                  = Prf
        self.DTSN                 = Lollipop(DTSN)
        self.active               = active
        self.advertised_prefixes  = advertised_prefixes
        self.last_DAOSequence     = Lollipop()  # used during DAO - DAO_ACK exchanges
        self.last_PathSequence    = Lollipop()
        self.DAO_ACK_source       = None
        self.DAO_ACK_source_iface = None
        self.DAO_trans_retry      = 0
        self.downward_routes      = set()  # set of tuple in the form of (destination, prefix_len, prefix)
        self.preferred_parent     = None
        
        
        

        # cleanup purposes
        self.no_path_routes       = set()  # store the routes for which we received a No-Path DAO
        self.no_path_routes_trans = 0  # how many times the No-Path DAO have been transmitted

        # there are open flow ports, rpl_node ip and datapath
        self.rplnode = rplnode
#        self.ports = ports 
#        self.ip = ip
#        self.datapath = datapath
        
        if is_root:
            self.is_dodagRoot = True
            self.rank = ROOT_RANK 
            if IPAddress(dodagID).is_link_local():
                raise Exception("DODAG ID must be a routable address (not a link local address)")
            self.dodagID = dodagID
        else:
            self.rank = INFINITE_RANK

        # import objective function related function
        self.compute_rank_increase = self.of.compute_rank_increase
        self.OCP                   = of.OCP

        self.last_dio = time()

        self.setDIOtimer()

        if self.is_dodagRoot:
            self.DIOtimer.hear_inconsistent()
示例#2
0
文件: dodag.py 项目: abdelwas/Ryu-RPL
class DODAG(object):
    """A base class for a DODAG object
    """
    dodagID                = r"\0" * 16
    is_dodagRoot           = False
    DODAGVersionNumber     = 0
    last_DAOSequence       = 0  # used during DAO - DAO_ACK exchanges
    last_PathSequence      = 0
    lowest_rank_advertized = INFINITE_RANK
    rank                   = INFINITE_RANK

    # learned from the DODAG configuration option
    authenticated      = 0
    PCS                = DEFAULT_PATH_CONTROL_SIZE
    DIOIntDoublings    = DEFAULT_DIO_INTERVAL_DOUBLINGS
    DIOIntMin          = DEFAULT_DIO_INTERVAL_MIN
    DIORedundancyConst = DEFAULT_DIO_REDUNDANCY_CONSTANT
    MaxRankIncrease    = DEFAULT_MAX_RANK_INCREASE
    MinHopRankIncrease = DEFAULT_MIN_HOP_RANK_INCREASE
    OCP                = 0
    DftLft             = 0xff  # represents Infinity
    LftUnit            = 0xffff
    
    def __init__(self, instanceID, version, G, MOP, Prf, DTSN, dodagID, rplnode,\
                 advertised_prefixes=[], of = of_zero(), active=False, is_root=False):
        """ Creates DODAG Object identified by instanceID, dodagID, version
        """
        super(dodag, self).__init__()
        
        
        self.of = of #Instance of objective function object
        self.__lock               = RLock()
        self.instanceID           = instanceID
        self.version              = Lollipop(version)
        self.dodagID              = dodagID
        self.G                    = G
        self.MOP                  = MOP
        self.Prf                  = Prf
        self.DTSN                 = Lollipop(DTSN)
        self.active               = active
        self.advertised_prefixes  = advertised_prefixes
        self.last_DAOSequence     = Lollipop()  # used during DAO - DAO_ACK exchanges
        self.last_PathSequence    = Lollipop()
        self.DAO_ACK_source       = None
        self.DAO_ACK_source_iface = None
        self.DAO_trans_retry      = 0
        self.downward_routes      = set()  # set of tuple in the form of (destination, prefix_len, prefix)
        self.preferred_parent     = None
        
        
        

        # cleanup purposes
        self.no_path_routes       = set()  # store the routes for which we received a No-Path DAO
        self.no_path_routes_trans = 0  # how many times the No-Path DAO have been transmitted

        # there are open flow ports, rpl_node ip and datapath
        self.rplnode = rplnode
#        self.ports = ports 
#        self.ip = ip
#        self.datapath = datapath
        
        if is_root:
            self.is_dodagRoot = True
            self.rank = ROOT_RANK 
            if IPAddress(dodagID).is_link_local():
                raise Exception("DODAG ID must be a routable address (not a link local address)")
            self.dodagID = dodagID
        else:
            self.rank = INFINITE_RANK

        # import objective function related function
        self.compute_rank_increase = self.of.compute_rank_increase
        self.OCP                   = of.OCP

        self.last_dio = time()

        self.setDIOtimer()

        if self.is_dodagRoot:
            self.DIOtimer.hear_inconsistent()


    def _build_rpl_pkt(self, src_ip, dst_ip, src_mac, dst_mac, rpl):
        
        pkt = packet.Packet()
        e = ethernet.ethernet(ethertype=ether.ETH_TYPE_IPV6,
                                           dst = dst_mac,
                                           src = src_mac)
                                           
        #TODO: hop_limit=1                        
        i6 = ipv6.ipv6(dst=dst_ip,src=src_ip, nxt=inet.IPPROTO_ICMPV6)
                                           
                   
        pkt.add_protocol(e)
        pkt.add_protocol(i6)
        pkt.add_protocol(rpl)

        return pkt            

    def _send_rpl_pkt(self, out_port=[], dst_ip=[], dst_mac =[], rpl):
        """
        out_port: list of output port numbers
        dst_ip: list of destination ip addresses for each output port
        dst_mac: list of dst mac of each output port
        """
        assert len(out_port) == len(dst_ip)
        assert len(dst_ip) == len(dst_mac)
        # unicast or multicast
        for i in range(out_port):
            port = out_port[i]
            dstip = dst_ip[i]
            dstmac = dst_mac[i]
            p = self.rplnode.ports[port]
            pkt =  self._build_rpl_pkt(dodag,src_ip = self.rplnode.ip, 
                             dst_ip = dstip, 
                             src_mac = p['hw_addr'], 
                             dst_mac = dstmac, rpl= rpl)
        
            send_packet(self.rplnode.datapath, port, pkt)
            
    def _broadcast_rpl_pkt(self, rpl):
        # broadcast
        dstip = ALL_RPL_NODES
        dstmac = BROADCAST_MAC
        for port in self.rplnode.ports.keys():
            p = self.rplnode.ports[port]
            pkt =  self._build_rpl_pkt(dodag,src_ip = self.rplnode.ip, 
                             dst_ip = dstip, 
                             src_mac = p['hw_addr'], 
                             dst_mac = dstmac, rpl= rpl)
        
            send_packet(self.rplnode.datapath, port, pkt)        
    
    def sendDIO(self, out_port=[], dst_ip=[], dst_mac =[], dodag_shutdown=False):
        """ Send a DIO message
        port: port number to send on
        dst_ip: destination ippv6
        dst_mac: destination mac
        with the following Options:
        1. Prefix Information
        2. DODAG Configuration
        3. DAO messages are sent after short interval if the sending node
            is not the root of this DODAG or it is not poisoned
        Called by: DIS message handler, if the DIS is unicast, posion, and setDIOtime
        """
        # create RPL options
        rploption = []
        
        # create Dodag Configuration Option
        dodag_conf = icmpv6_rpl.rpl_option_dodag_conf(length=0, # the rpl serialize method know what to do with len
                 flags=0, a = self.authenticated, pcs = self.PCS,
                 diointdouble = self.DIOIntDoublings, diointmin = self.DIOIntMin, 
                 dioredun = self.DIORedundancyConst,
                 maxrankinc = self.MaxRankIncrease, minhoprankinc = self.MinHopRankIncrease,
                 ocp = self.OCP, reserved=0,
                 def_lifetime = self.DftLft, lifetime_unit = self.LftUnit)

        rploption.append(dodag_conf)
        
        # Create Prefix Information Option for every advertised prefix
        for prefix in self.advertised_prefixes:
            prefix_info = icmpv6_rpl.rpl_option_pi(length=0, pre_length=64, 
                                                   l=0, a=1,r=0,
                     reserved1=0, valid_lifetime=RPL_PREFIX_TIME_INF, pref_lifetime=RPL_PREFIX_TIME_INF,
                     reserved2=0, prefix=prefix)
            rploption.append(prefix_info)
        
        # Create RPL_DIO message
        diomsg = icmpv6_rpl.rpl_dio(rplinstanceid=self.instanceID,
                                    version=self.version.get_val(),
                                    rank=self.rank,g=self.G,o=0,mop=self.MOP,
                                    prf=self.Prf,dtsn=self.DTSN.get_val(),
                                    flags=0,reserved=0,
                                    dodagid=self.dodagID,option=rploption)
        
        # Create Dodag RPL Control message        
        rplmsg = icmpv6_rpl.rpl_control(code=icmpv6_rpl.RPL_DIO, rplctrl=diomsg)
    
        # Create ICMPv6 protocol message        
        rpl = icmpv6.icmpv6(type_= icmpv6_rpl.ICMPv6_RPL, code=icmpv6_rpl.RPL_DIO,
                      csum=0, data= rplmsg) 
        
        if len(out_port) and len(dst_ip) and len(dst_mac):
            self._send_rpl_pkt(out_port, dst_ip, dst_mac, rpl)
        else:
            self._broadcast_rpl_pkt(rpl)

        # DAO message are sent after a short interval  when DIO messages are
        # sent
        if not self.is_dodagRoot and not dodag_shutdown:
            self.setDAOtimer()

        del DIO_message
        
        
            
    def sendDAO(self, out_port=[], dst_ip=[], dst_mac =[], retransmit=False, nopath=False):
        """ Send a DAO message
        1. If not a retrasmit increment the DAO seq
        2. By default DAO is sent to the preferred parent
        3. Contains the options: RPL Target (from downward routes), 
           and Transit Information
           (the Parent Address field is not needed because the node is in Storing Mode)
        4. If it's broadcast don't set the K flag (Dont ask for DAO_ACK)
        5. If unicast set the K flag, and activate the DAO_ACK gaurd timer 
           using setDAO_ACKtimer
        6. The DODAG checks which target it has path for and which it doesn't
           and constructs  RPL Target and Transit Information accordingly.
           If poison is needed, we advertise all targets as if we have no
           path for.
        Called By: SendTwoDAOs, if DAO_ACK gaurd timer expires (retrunsmission), poison   
        """
        assert self.active or nopath

        logger.info("sending DAO message for %s (version %d)" % (self.dodagID, self.version.get_val()))

        if not retransmit: self.last_DAOSequence += 1

        # if no destination is specified, find the DAO parent
        if len(dst_ip) == 0:
            # here, destination is None if no DAO parent exists
            destination = self.preferred_parent
            try:
                outport = [destination.port]
                dstip = [destination.ip]
            except:
                dstip = None
                outport = None
            assert dstip != ALL_RPL_NODES
                   
        # create RPL options
        rploption = []
        
        with self.__lock:                   
            for address in self.rplnode.address_cache:
                rpltargetopt = icmpv6_rpl.rpl_option_rpltarget(length=0, flags=0, 
                                        prefix_length=128, target_prefix = address)
                rploption.add(rpltargetopt)                        

        # the Parent Address field is not needed because the node is in Storing Mode
        rpltiopt = icmpv6_rpl.rpl_option_ti(length=0, e=0, flags=0,path_control=0,
                                            path_seq=self.last_PathSequence.get_val(),
                                            path_lifetime=0x00 if nopath else self.DftLft, 
                                            parent_addr = None)

        no_path_targets_opt = []
        no_path_transit_inf_opt = None
#
        if dstip == ALL_RPL_NODES:
            logger.debug("sending DAO message to All-RPL-Nodes multicast address: %s" % ALL_RPL_NODES)
            kval = 0

        elif dstip is None:
            logger.debug("DAO dest_ip is not set")
            return

        else: #destination is the prefererred parent
            # because the K flag is set, we set a timer for receiving a DAO-ACK
            kval =1
            self.DAO_ACK_source = dstip
            self.DAO_ACK_source_port = outport
            self.setDAO_ACKtimer()

            logger.debug("sending DAO message to a Link-Local address (Not really): %s" % dstip)

            with self.__lock:
                
                # build the RPL Target Options from the list of downward routes
                targets = [tuple(route.target.split("/")) for route in self.downward_routes]
                

                for (prefix, preflen) in targets:
                    rpltargetopt = icmpv6_rpl.rpl_option_rpltarget(length=0, flags=0, 
                                        prefix_length=int(preflen), target_prefix = prefix)
                    rploption.add(rpltargetopt) 


                if self.no_path_routes_trans < DEFAULT_DAO_NO_PATH_TRANS and self.no_path_routes:
                    logger.debug("advertising additional routes that need to be removed")
                    self.no_path_routes_trans += 1

                    # there is no need to propagate the No-Path information when an alternative path exists locally
                    reachable_targets = set([route.target for route in self.downward_routes])
                    no_path_targets = [tuple(route.target.split("/")) for route in self.no_path_routes if route.target not in reachable_targets]

                       
                    if no_path_targets:
                        for (prefix, preflen) in no_path_targets:
                            rpltargetopt = icmpv6_rpl.rpl_option_rpltarget(length=0, flags=0, 
                                        prefix_length=int(preflen), target_prefix = prefix)
                            no_path_targets_opt.append(rpltargetopt)            

                                                                                    
                        no_path_transit_inf_opt = icmpv6_rpl.rpl_option_ti(length=0, e=0, flags=0,path_control=0,
                                            path_seq=self.last_PathSequence.get_val(),
                                            path_lifetime=0x00, 
                                            parent_addr = None)                                                           
                else:
                    self.no_path_routes = set()

        if nopath:
            rploption +=  no_path_targets_opt + transit_inf_opt
        else:
            rploption +=  transit_inf_opt + no_path_targets_opt + no_path_transit_inf_opt

        rpl = icmpv6_rpl.rpl_dao(rplinstanceid=self.instanceID,k=kval,
                d=0 ,flags=0,reserved=0, daosequence = self.last_DAOSequence.get_val(),
                dodagid=self.dodagID,option = rpltopion)              
                      
        if len(out_port) and len(dst_ip) and len(dst_mac):
            self._send_rpl_pkt(out_port, dst_ip, dst_mac, rpl)
        else:
            self._broadcast_rpl_pkt(rpl)  
    
    def sendTwoDAOs(self):
        """Send a multicast DAO, for the node's own destination and a unicast
        DAO to announce all downwards routes known to the node
        (only if the DODAG is currently active)
        Called by: setDAOtimer
        """
        if self.active:
            self.sendDAO(dst_ip=[ALL_RPL_NODES], retransmit=True)
            self.sendDAO()
    
    def sendDAO_ACK(self, out_port, dst_ip, dst_mac, DAOsequence, dodagID = 0):
        """ send a DAO ACK message
        Applicable only for one dst as it is requested in unicast DAO ONLY
        Called by: DAO Handler if the K bit in the received DAO is set"""
        logger.info("sending DAO-ACK to %s" % dst_ip)

       
        rpl = icmpv6_rpl.rpl_dao_ack(rplinstanceid=self.instanceID,d=0,
                reserved=0,daosequence=DAOsequence,status=0,
                dodagid=dodagID)                            
                 

        self._send_rpl_pkt([out_port], [dst_ip], [dst_mac], rpl)      
    
    def setDIOtimer(self):
        """Broadcast DIO after Trickle timer
        Called by: creation of the DODAG object
        """
        try:
            self.DIOtimer.cancel()
        except:
            pass

        self.DIOtimer = trickleTimer(self.sendDIO, {}, \
                                         Imin=0.001 * 2 ** self.DIOIntMin, \
                                         Imax=self.DIOIntDoublings, \
                                         k=self.DIORedundancyConst)
        self.DIOtimer.start()
    
    def setDAOtimer(self):
        """ Send delayed DAO 
        Called by: SendDAO, DAO handler if downward routes are updated,
        DIO handler if a parent node changed DTSN,
        DIO handler if not consistent condition
        """
        try:
            if not self.DAOtimer.is_alive():
                self.DAOtimer = Timer(DEFAULT_DAO_DELAY, self.sendTwoDAOs)
            else:
                return
        except AttributeError:
            self.DAOtimer = Timer(DEFAULT_DAO_DELAY, self.sendTwoDAOs)

        self.DAOtimer.start()
    
    def setDAO_ACKtimer(self):
        """Gaurd timer for DAO ACK
        Called by: sendDAO after sending the DAO message (waiting for ACK)
        """
        self.cancelDAO_ACKtimer()

        if self.DAO_trans_retry >= DEFAULT_DAO_MAX_TRANS_RETRY:
            self.DAO_trans_retry = 0

            # the destination (self.DAO_ACK_source) seems unreachable
            # hence, it should be removed, and if it was the DIO parent, a new
            # DIO parent must be found
            self.rplnode.neigh_cache.remove_node_by_address(self, self.DAO_ACK_source)
            updated = self.rplnode.neigh_cache.update_DIO_parent()
            if updated: self.DIOtimer.hear_inconsistent()

            return

        self.DAO_trans_retry += 1


        self.DAO_ACKtimer = Timer(DEFAULT_DAO_ACK_DELAY, self.sendDAO, kwargs={'iface':self.DAO_ACK_source_iface, 'destination':self.DAO_ACK_source, 'retransmit':True})
        self.DAO_ACKtimer.start()
    
    def cancelDAO_ACKtimer(self):
        """cancel the DAO ACK timer (when the DAO ACK is received)
        Called by: setDAO_ACKtimer to reset the timer,
        and DAO_ACK handler if the DAO_ACK corresponging to a DAO is received
        """
        try:
            self.DAO_ACKtimer.cancel()
        except:
            pass
    
    
    def downward_route_add(self,route):
        """ register a downward route 
        CAlled by: DAO handler parsing RPL Target Option (depending on path_lifetime)
        """
        with self.__lock:
            if not self.rplnode.address_cache.is_assigned(route.target.split("/")[0]):
                self.downward_routes.add(route)


    def downward_route_del(self,route):
        """ remove a downward route 
        Called by: downward_routes_remove_by_nexthop and
        DAO handler if the path is no-path (path_lifetime is null 0)
        """
        with self.__lock:
            if route in self.downward_routes:
                self.downward_routes.remove(route)
                self.no_path_routes_trans = 0
                self.no_path_routes.add(route)


    def downward_routes_reset(self):
        """ remove all downward routes 
        Called by: DIO handler if the sending node is a parent and increased 
        its DTSN (request DAO message)"""
        logger.debug("Removing all downward routes for this DODAG (%s)" % self.dodagID)
        with self.__lock:
            self.rplnode.route_cache.remove_routes(self.downward_routes)
            self.downward_routes = set()

    def downward_routes_remove_by_nexthop(self,address):
        """ remove all routes through a partular next_hop node 
        if the DAO_ACk maximum retransmission is reached
        or in DIO handler if the parent node advertised infinite RANK
        Called by: remove_node_by_address in the neighbor cache
        """
        logger.debug("Removing all downward routes going through %s" % address)

        updated = False

        with self.__lock:
            for route in self.downward_routes.copy():
                if address == route.nexthop:
                    self.downward_route_del(route)
                    if self.active:
                        updated += self.rplnode.route_cache.remove_route(route)

        return updated


    def downward_routes_get(self):
        """ retrieve a copy of a route 
        Called by: neighborcache.set_preferred parnet
        # moving to a new DODAG version or a completely different
        # DODAG, hence some downward routes might need to be
        # removed, while some others might need to be added
        """
        with self.__lock:
            return self.downward_routes.copy()


    def get_filtered_downward_routes(self):
        """ Filter downward redundant routess: for example 
        # one hop route takes precedence over multiphop routes
        Called by: DAO handler
        """
        new_routes = {}
        removed_routes = []

        with self.__lock:
            for route in self.downward_routes:
                    if route.target not in new_routes:
                        new_routes[route.target] = (route.nexthop, route.nexthop_iface, route.onehop)
                        continue

                    (current_nexthop, current_nexthop_iface, current_onehop) = new_routes[route.target]

                    # only one route can be one hop to a destination
                    # (unless a node uses two link local addresses)
                    assert not (current_onehop and route.onehop)

                    # one hop route takes precedence over multiphop routes
                    if current_onehop:
                        removed_routes.append(route)
                        continue

                    # one hop route takes precedence over multihop routes
                    if route.onehop:
                        removed_routes.append(Route(route.target, current_nexthop, current_nexthop_iface, current_onehop))
                        new_routes[route.target] = (route.nexthop, route.nexthop_iface, route.onehop)
                        continue

                    node = gv.neigh_cache.get_node(route.nexthop_iface, route.nexthop, self)
                    current_node = gv.neigh_cache.get_node(current_nexthop_iface, current_nexthop, self)

                    assert id(node) != id(current_node)

                    if not node:
                        removed_routes.append(route)
                        continue

                    if not current_node:
                        removed_routes.append(Route(route.target, current_nexthop, current_nexthop_iface, current_onehop))
                        new_routes[route.target] = (route.nexthop, route.nexthop_iface, route.onehop)
                        continue

                    if self.DAGRank(node.rank) >= self.DAGRank(current_node.rank):
                        removed_routes.append(route)
                    else:
                        new_routes[route.target] = (route.nexthop, route.nexthop_iface, route.onehop)
                        removed_routes.append(Route(route.target, current_nexthop, current_nexthop_iface, current_onehop))

            new_routes = [Route(target, nexthop, nexthop_iface, onehop) for (target, (nexthop, nexthop_iface, onehop)) in new_routes.items()]

            return (removed_routes, new_routes)
                    

    def DAGRank(self,rank):
        """ implements the GAGRank  function in the standard 
        Called by: get_filtered_downward_routes to prune worse routes
        , neighbor chase to compute_DIO_parents and set_prefered 
        and objective function (of_zero for example) to compare two parents
        """
        return floor(float(rank)/ self.MinHopRankIncrease)


    def poison(self, shutdown=False):
        """ poisons the DODAG
        1. If the RPL_Node is shutting  down (not avail) to indicate that
        downward routes are not available anymore:
            a. send multicast DAO with no-path
            b. send a unicast DAO with no-path
        2. Set rank to INFINITE
        3. Send DIO with new updated data (infinite rank)
        Called by: dodag cache to remove old DODAG with updated version
        , cleanning up the rpl_node
        """
        logger.debug("Poisoning DODAG %s (version %d)" % (self.dodagID, self.version.get_val()))
        # send a No-PATH DAO message only when the RPL router stops and the
        # downward routes won't be accessible anymore
        if shutdown:
            self.sendDAO(dst_ip=[ALL_RPL_NODES], nopath=True)
            if self.preferred_parent:
                self.sendDAO(nopath=True)
        self.rank = INFINITE_RANK
        self.sendDIO(dodag_shutdown=True)
    
    
    def __eq__(self, other):
        """
        Compare DODAG Versions between two DODAGs
        """
        return self.instanceID == other.instanceID and \
               self.version    == other.version and \
               self.dodagID    == other.dodagID

    def __del__(self):
        # identify neighbors that need to be removed
        self.cleanup()
        super(dodag, self).__del__()     
        

    def cleanup(self):
        """Distroy Objects."""
        # disable all the running timers
        try: self.DIOtimer.cancel()
        except: pass
        try: self.DAOtimer.cancel()
        except: pass
        try: self.DAO_ACKtimer.cancel()
        except: pass

        del self.DIOtimer
        try: del self.DAOtimer
        except: pass
        try: del self.DAO_ACKtimer
        except: pass

        self.rplnode.neigh_cache.remove_nodes_by_dodag(self)