class Rpl(object): DEFAULT_DIO_INTERVAL_MIN = 14 DEFAULT_DIO_INTERVAL_DOUBLINGS = 9 DEFAULT_DIO_REDUNDANCY_CONSTANT = 3 # locally-defined constants DEFAULT_DIS_INTERVAL_SECONDS = 60 def __init__(self, mote): # store params self.mote = mote # singletons (quicker access, instead of recreating every time) self.engine = SimEngine.SimEngine.SimEngine() self.settings = SimEngine.SimSettings.SimSettings() self.log = SimEngine.SimLog.SimLog().log # local variables self.dodagId = None self.of = RplOF0(self) self.trickle_timer = TrickleTimer( i_min=pow(2, self.DEFAULT_DIO_INTERVAL_MIN), i_max=self.DEFAULT_DIO_INTERVAL_DOUBLINGS, k=self.DEFAULT_DIO_REDUNDANCY_CONSTANT, callback=self._send_DIO) self.parentChildfromDAOs = { } # dictionary containing parents of each node self._tx_stat = {} # indexed by mote_id self.dis_mode = self._get_dis_mode() #======================== public ========================================== # getters/setters def get_rank(self): return self.of.rank def getDagRank(self): if self.of.rank is None: return None else: return int(self.of.rank / d.RPL_MINHOPRANKINCREASE) def addParentChildfromDAOs(self, parent_addr, child_addr): self.parentChildfromDAOs[child_addr] = parent_addr def getPreferredParent(self): # FIXME: when we implement IPv6 address or MAC address, we should # define the return type of this method. Currently, this method can # return a node ID, a MAC address, or an IPv6 address since they are # all the same value for a certain mote. return self.of.get_preferred_parent() # admin def start(self): if self.mote.dagRoot: self.dodagId = self.mote.get_ipv6_global_addr() self.of = RplOFNone(self) self.of.set_rank(d.RPL_MINHOPRANKINCREASE) self.trickle_timer.start() # now start a new RPL instance; reset the timer as per Section 8.3 of # RFC 6550 self.trickle_timer.reset() else: if self.dis_mode != 'disabled': # the destination address of the first DIS is determined based # on self.dis_mode if self.dis_mode == 'dis_unicast': # join_proxy is a possible parent dstIp = d.IPV6_ALL_RPL_NODES_ADDRESS elif self.dis_mode == 'dis_broadcast': dstIp = d.IPV6_ALL_RPL_NODES_ADDRESS else: raise NotImplementedError() self.send_DIS(dstIp) self.start_dis_timer() def indicate_tx(self, cell, dstMac, isACKed): self.of.update_etx(cell, dstMac, isACKed) def indicate_preferred_parent_change(self, old_preferred, new_preferred): # log self.log( SimEngine.SimLog.LOG_RPL_CHURN, { "_mote_id": self.mote.id, "rank": self.of.rank, "preferredParent": new_preferred }) # trigger DAO self._schedule_sendDAO(firstDAO=True) # use the new parent as our clock source self.mote.tsch.clock.sync(new_preferred) # trigger 6P ADD if parent changed self.mote.sf.indication_parent_change(old_preferred, new_preferred) # reset trickle timer to inform new rank quickly self.trickle_timer.reset() def local_repair(self): assert ((self.of.rank is None) or (self.of.rank == d.RPL_INFINITE_RANK)) self.log(SimEngine.SimLog.LOG_RPL_LOCAL_REPAIR, {"_mote_id": self.mote.id}) self._send_DIO() # sending a DIO with the infinite rank self.dodagId = None self.trickle_timer.stop() self.mote.tsch.stopSendingEBs() # start the DIS timer self.start_dis_timer() # === DIS def action_receiveDIS(self, packet): self.log(SimEngine.SimLog.LOG_RPL_DIS_RX, { "_mote_id": self.mote.id, "packet": packet, }) if self.dodagId is None: # ignore DIS pass else: if self.mote.is_my_ipv6_addr(packet['net']['dstIp']): # unicast DIS; send unicast DIO back to the source self._send_DIO(packet['net']['srcIp']) elif packet['net']['dstIp'] == d.IPV6_ALL_RPL_NODES_ADDRESS: # broadcast DIS self.trickle_timer.reset() else: # shouldn't happen assert False def _get_dis_mode(self): if 'dis_unicast' in self.settings.rpl_extensions: assert 'dis_broadcast' not in self.settings.rpl_extensions return 'dis_unicast' elif 'dis_broadcast' in self.settings.rpl_extensions: assert 'dis_unicast' not in self.settings.rpl_extensions return 'dis_broadcast' else: return 'disabled' def start_dis_timer(self): self.engine.scheduleIn(delay=self.DEFAULT_DIS_INTERVAL_SECONDS, cb=self.handle_dis_timer, uniqueTag=str(self.mote.id) + 'dis', intraSlotOrder=d.INTRASLOTORDER_STACKTASKS) def stop_dis_timer(self): self.engine.removeFutureEvent(str(self.mote.id) + 'dis') def handle_dis_timer(self): self.send_DIS(d.IPV6_ALL_RPL_NODES_ADDRESS) self.start_dis_timer() def send_DIS(self, dstIp): assert dstIp is not None dis = { 'type': d.PKT_TYPE_DIS, 'net': { 'srcIp': str(self.mote.get_ipv6_link_local_addr()), 'dstIp': dstIp, 'packet_length': d.PKT_LEN_DIS }, 'app': {} } self.log(SimEngine.SimLog.LOG_RPL_DIS_TX, { "_mote_id": self.mote.id, "packet": dis, }) self.mote.sixlowpan.sendPacket(dis) # === DIO def _send_DIO(self, dstIp=None): if self.dodagId is None: # seems we performed local repair return dio = self._create_DIO(dstIp) # log self.log(SimEngine.SimLog.LOG_RPL_DIO_TX, { "_mote_id": self.mote.id, "packet": dio, }) self.mote.sixlowpan.sendPacket(dio) def _create_DIO(self, dstIp=None): assert self.dodagId is not None if dstIp is None: dstIp = d.IPV6_ALL_RPL_NODES_ADDRESS if self.of.rank is None: rank = d.RPL_INFINITE_RANK else: rank = self.of.rank # create newDIO = { 'type': d.PKT_TYPE_DIO, 'app': { 'rank': rank, 'dodagId': self.dodagId, }, 'net': { 'srcIp': self.mote.get_ipv6_link_local_addr(), 'dstIp': dstIp, 'packet_length': d.PKT_LEN_DIO } } return newDIO def action_receiveDIO(self, packet): assert packet['type'] == d.PKT_TYPE_DIO # abort if I'm not sync'ed (I cannot decrypt the DIO) if not self.mote.tsch.getIsSync(): return # abort if I'm not join'ed (I cannot decrypt the DIO) if not self.mote.secjoin.getIsJoined(): return # abort if I'm the DAGroot (I don't need to parse a DIO) if self.mote.dagRoot: return # log self.log(SimEngine.SimLog.LOG_RPL_DIO_RX, { "_mote_id": self.mote.id, "packet": packet, }) # handle the infinite rank if packet['app']['rank'] == d.RPL_INFINITE_RANK: if self.dodagId is None: # ignore this DIO return else: # if the DIO has the infinite rank, reset the Trickle timer self.trickle_timer.reset() # feed our OF with the received DIO self.of.update(packet) # record dodagId if ((self.dodagId is None) and (self.getPreferredParent() is not None)): # join the RPL network self.dodagId = packet['app']['dodagId'] self.mote.add_ipv6_prefix(d.IPV6_DEFAULT_PREFIX) self.trickle_timer.start() self.trickle_timer.reset() self.stop_dis_timer() # === DAO def _schedule_sendDAO(self, firstDAO=False): """ Schedule to send a DAO sometimes in the future. """ assert self.mote.dagRoot is False # abort if DAO disabled if self.settings.rpl_daoPeriod == 0: # secjoin never completes if downward traffic is not supported by # DAO assert self.settings.secjoin_enabled is False # start sending EBs and application packets. self.mote.tsch.startSendingEBs() self.mote.app.startSendingData() return asnNow = self.engine.getAsn() if firstDAO: asnDiff = 1 else: asnDiff = int( math.ceil( random.uniform(0.8 * self.settings.rpl_daoPeriod, 1.2 * self.settings.rpl_daoPeriod) / self.settings.tsch_slotDuration)) # schedule sending a DAO self.engine.scheduleAtAsn( asn=asnNow + asnDiff, cb=self._action_sendDAO, uniqueTag=(self.mote.id, '_action_sendDAO'), intraSlotOrder=d.INTRASLOTORDER_STACKTASKS, ) def _action_sendDAO(self): """ Enqueue a DAO and schedule next one. """ if self.of.get_preferred_parent() is None: # stop sending DAO return # enqueue self._action_enqueueDAO() # the root now knows a source route to me # I can serve as join proxy: start sending DIOs and EBs # I can send data back-and-forth with an app self.mote.tsch.startSendingEBs() # mote self.mote.app.startSendingData() # mote # schedule next DAO self._schedule_sendDAO() def _action_enqueueDAO(self): """ enqueue a DAO into TSCH queue """ assert not self.mote.dagRoot assert self.dodagId != None # abort if not ready yet if self.mote.clear_to_send_EBs_DATA() == False: return parent_mac_addr = netaddr.EUI(self.of.get_preferred_parent()) prefix = netaddr.IPAddress(d.IPV6_DEFAULT_PREFIX) parent_ipv6_addr = str(parent_mac_addr.ipv6(prefix)) # create newDAO = { 'type': d.PKT_TYPE_DAO, 'app': { 'parent_addr': parent_ipv6_addr, }, 'net': { 'srcIp': self.mote.get_ipv6_global_addr(), 'dstIp': self.dodagId, # to DAGroot 'packet_length': d.PKT_LEN_DAO, }, } # log self.log(SimEngine.SimLog.LOG_RPL_DAO_TX, { "_mote_id": self.mote.id, "packet": newDAO, }) # remove other possible DAOs from the queue self.mote.tsch.remove_packets_in_tx_queue(type=d.PKT_TYPE_DAO) # send self.mote.sixlowpan.sendPacket(newDAO) def action_receiveDAO(self, packet): """ DAGroot receives DAO, store parent/child relationship for source route calculation. """ assert self.mote.dagRoot # log self.log(SimEngine.SimLog.LOG_RPL_DAO_RX, { "_mote_id": self.mote.id, "packet": packet, }) # store parent/child relationship for source route calculation self.addParentChildfromDAOs(parent_addr=packet['app']['parent_addr'], child_addr=packet['net']['srcIp']) # source route def computeSourceRoute(self, dst_addr): assert self.mote.dagRoot try: sourceRoute = [] cur_addr = dst_addr while self.mote.is_my_ipv6_addr(cur_addr) is False: sourceRoute += [cur_addr] cur_addr = self.parentChildfromDAOs[cur_addr] if cur_addr in sourceRoute: # routing loop is detected; cannot return an effective # source-routing header returnVal = None break except KeyError: returnVal = None else: # reverse (so goes from source to destination) sourceRoute.reverse() returnVal = sourceRoute return returnVal
class Rpl(object): DEFAULT_DIO_INTERVAL_MIN = 14 DEFAULT_DIO_INTERVAL_DOUBLINGS = 9 DEFAULT_DIO_REDUNDANCY_CONSTANT = 3 # locally-defined constants DEFAULT_DIS_INTERVAL_SECONDS = 60 def __init__(self, mote): # store params self.mote = mote # singletons (quicker access, instead of recreating every time) self.engine = SimEngine.SimEngine.SimEngine() self.settings = SimEngine.SimSettings.SimSettings() self.log = SimEngine.SimLog.SimLog().log # local variables self.of = RplOF0(self) self.trickle_timer = TrickleTimer( i_min=pow(2, self.DEFAULT_DIO_INTERVAL_MIN), i_max=self.DEFAULT_DIO_INTERVAL_DOUBLINGS, k=self.DEFAULT_DIO_REDUNDANCY_CONSTANT, callback=self._send_DIO) self.parentChildfromDAOs = { } # dictionary containing parents of each node self._tx_stat = {} # indexed by mote_id self.dis_mode = self._get_dis_mode() #======================== public ========================================== # getters/setters def get_rank(self): return self.of.get_rank() def getDagRank(self): return int(self.of.get_rank() / d.RPL_MINHOPRANKINCREASE) def addParentChildfromDAOs(self, parent_id, child_id): assert type(parent_id) is int assert type(child_id) is int self.parentChildfromDAOs[child_id] = parent_id def getPreferredParent(self): # FIXME: when we implement IPv6 address or MAC address, we should # define the return type of this method. Currently, this method can # return a node ID, a MAC address, or an IPv6 address since they are # all the same value for a certain mote. return self.of.get_preferred_parent() # admin def start(self): if self.mote.dagRoot: self.of = RplOFNone(self) self.of.set_rank(d.RPL_MINHOPRANKINCREASE) self.trickle_timer.start() # now start a new RPL instance; reset the timer as per Section 8.3 of # RFC 6550 self.trickle_timer.reset() else: # start sending DIS self._send_DIS() def indicate_tx(self, cell, dstMac, isACKed): self.of.update_etx(cell, dstMac, isACKed) def indicate_preferred_parent_change(self, old_preferred, new_preferred): # log self.log( SimEngine.SimLog.LOG_RPL_CHURN, { "_mote_id": self.mote.id, "rank": self.of.get_rank(), "preferredParent": new_preferred }) # print('-------- change parent for mote:', self.mote.id) # trigger DAO self._schedule_sendDAO(firstDAO=True) # use the new parent as our clock source self.mote.tsch.clock.sync(new_preferred) # trigger 6P ADD if parent changed self.mote.sf.indication_parent_change(old_preferred, new_preferred) # added Fadoua: I need reset the counters of DATA packets # at this level after changing the preferred parent # in this way you remove old statistics and make sure that # they are not taken into consideration for the next running time src = self.mote.id dst = old_preferred if ((src != None) and (dst != None)): couple = str( (dst, src) ) # I have done this to be conform to the format of tuple in the settings.count dictionary that I used at the reception side of (sixlowpan file) if (couple in self.settings.count.keys()): if (self.settings.count[couple] > d.MAX_DATA_PKTS_THROUGHT_SHARED_CELL): del self.settings.count[couple] # reset trickle timer to inform new rank quickly self.trickle_timer.reset() def update_preferred_parent(self, reason): # added Fadoua self.of._update_preferred_parent(reason) def get_list_of_old_parents(self): # added Fadoua return self.of.get_list_of_old_parents() # === DIS def action_receiveDIS(self, packet): if packet['net']['dstIp'] == self.mote.id: # unicast DIS; send unicast DIO back to the source self._send_DIO(packet['net']['srcIp']) elif packet['net']['dstIp'] == d.BROADCAST_ADDRESS: # broadcast DIS self.trickle_timer.reset() else: # shouldn't happen assert False def _get_dis_mode(self): if 'dis_unicast' in self.settings.rpl_extensions: assert 'dis_broadcast' not in self.settings.rpl_extensions return 'dis_unicast' elif 'dis_broadcast' in self.settings.rpl_extensions: assert 'dis_unicast' not in self.settings.rpl_extensions return 'dis_broadcast' else: return 'disabled' def _start_dis_timer(self): self.engine.scheduleIn(delay=self.DEFAULT_DIS_INTERVAL_SECONDS, cb=self._send_DIS, uniqueTag=str(self.mote.id) + 'dis', intraSlotOrder=d.INTRASLOTORDER_STACKTASKS) def _stop_dis_timer(self): self.engine.removeFutureEvent(str(self.mote.id) + 'dis') def _send_DIS(self): if self.dis_mode == 'dis_unicast': dstIp = self.mote.tsch.join_proxy # possible parent elif self.dis_mode == 'dis_broadcast': dstIp = d.BROADCAST_ADDRESS elif self.dis_mode == 'disabled': return dis = { 'type': d.PKT_TYPE_DIS, 'net': { 'srcIp': self.mote.id, 'dstIp': dstIp, 'packet_length': d.PKT_LEN_DIS }, 'app': {} } self.mote.sixlowpan.sendPacket(dis, link_local=True) self._start_dis_timer() # === DIO def _send_DIO(self, dstIp=None): dio = self._create_DIO(dstIp) # log self.log(SimEngine.SimLog.LOG_RPL_DIO_TX, { "_mote_id": self.mote.id, "packet": dio, }) self.mote.sixlowpan.sendPacket(dio, link_local=True) def _create_DIO(self, dstIp=None): assert self.mote.dodagId is not None if dstIp is None: dstIp = d.BROADCAST_ADDRESS # create newDIO = { 'type': d.PKT_TYPE_DIO, 'app': { 'rank': self.of.get_rank(), 'dodagId': self.mote.dodagId, }, 'net': { 'srcIp': self.mote.id, # from mote 'dstIp': dstIp, # broadcast (in reality "all RPL routers") 'packet_length': d.PKT_LEN_DIO } } return newDIO def action_receiveDIO(self, packet): assert packet['type'] == d.PKT_TYPE_DIO # abort if I'm not sync'ed (I cannot decrypt the DIO) if not self.mote.tsch.getIsSync(): return # abort if I'm not join'ed (I cannot decrypt the DIO) if not self.mote.secjoin.getIsJoined(): return # abort if I'm the DAGroot (I don't need to parse a DIO) if self.mote.dagRoot: return # log self.log(SimEngine.SimLog.LOG_RPL_DIO_RX, { "_mote_id": self.mote.id, "packet": packet, }) # record dodagId if self.mote.dodagId is None: # join the RPL network self.mote.dodagId = packet['app']['dodagId'] self.trickle_timer.start() self.trickle_timer.reset() self._stop_dis_timer() # feed our OF with the received DIO self.of.update(packet) # === DAO def _schedule_sendDAO(self, firstDAO=False): """ Schedule to send a DAO sometimes in the future. """ assert self.mote.dagRoot is False # abort if DAO disabled if self.settings.rpl_daoPeriod == 0: return asnNow = self.engine.getAsn() if firstDAO: asnDiff = 1 else: asnDiff = int( math.ceil( random.uniform(0.8 * self.settings.rpl_daoPeriod, 1.2 * self.settings.rpl_daoPeriod) / self.settings.tsch_slotDuration)) # schedule sending a DAO self.engine.scheduleAtAsn( asn=asnNow + asnDiff, cb=self._action_sendDAO, uniqueTag=(self.mote.id, '_action_sendDAO'), intraSlotOrder=d.INTRASLOTORDER_STACKTASKS, ) def _action_sendDAO(self): """ Enqueue a DAO and schedule next one. """ # enqueue self._action_enqueueDAO() # the root now knows a source route to me # I can serve as join proxy: start sending DIOs and EBs # I can send data back-and-forth with an app self.mote.tsch.startSendingEBs() # mote self.mote.app.startSendingData() # mote # schedule next DAO self._schedule_sendDAO() def _action_enqueueDAO(self): """ enqueue a DAO into TSCH queue """ assert not self.mote.dagRoot assert self.mote.dodagId != None # abort if not ready yet if self.mote.clear_to_send_EBs_DATA() == False: return # create newDAO = { 'type': d.PKT_TYPE_DAO, 'app': { 'child_id': self.mote.id, 'parent_id': self.of.get_preferred_parent() }, 'net': { 'srcIp': self.mote.id, # from mote 'dstIp': self.mote.dodagId, # to DAGroot 'packet_length': d.PKT_LEN_DAO, }, } # log self.log(SimEngine.SimLog.LOG_RPL_DAO_TX, { "_mote_id": self.mote.id, "packet": newDAO, }) # remove other possible DAOs from the queue self.mote.tsch.remove_frame_from_tx_queue(type=d.PKT_TYPE_DAO) # send self.mote.sixlowpan.sendPacket(newDAO) def action_receiveDAO(self, packet): """ DAGroot receives DAO, store parent/child relationship for source route calculation. """ assert self.mote.dagRoot # log self.log(SimEngine.SimLog.LOG_RPL_DAO_RX, { "_mote_id": self.mote.id, "packet": packet, }) # store parent/child relationship for source route calculation self.addParentChildfromDAOs( parent_id=packet['app']['parent_id'], child_id=packet['app']['child_id'], ) # source route def computeSourceRoute(self, dest_id): """ Compute the source route to a given mote. :param destAddr: [in] The EUI64 address of the final destination. :returns: The source route, a list of EUI64 address, ordered from destination to source, or None """ assert type(dest_id) is int try: sourceRoute = [] cur_id = dest_id while cur_id != 0: sourceRoute += [cur_id] cur_id = self.parentChildfromDAOs[cur_id] except KeyError: returnVal = None else: # reverse (so goes from source to destination) sourceRoute.reverse() returnVal = sourceRoute return returnVal