class NSM(object): """ Neighbor State Machine :param ism: ISM objects. :param rtid: RouterID. :param pkt: coming hello packet. """ def __init__(self, ism, rtid, pkt): # OSPF neighbor information self.state = NSM_STATE["NSM_Down"] # NSM status. self._inactive_timer = None self._dd_exstart_timer = None self.lsr_resend_timer = None self.dd_flags = 0 # DD bit flags. Slave or master. self.dd_seqnum = 0 # DD Sequence Number. # Last sent Database Description packet. self.last_send = None # just the packet, not tuple # Timestemp when last Database Description packet was sent self.last_send_ts = 0 # not used # Last received Database Description packet. self.last_recv = tuple() # pattern: (ddseq, init, more, ms) # LSA data. self.ls_rxmt = list() # Link state retransmission list self.db_sum = list() # Database summary list self.ls_req = list() # Link state request list self.ism = ism self.rtid = rtid # neighbor router id self.nsm = dict() # Neighbor Information from Hello. self.src = pkt["H"]["SRC"] self.options = pkt["V"]["V"]["OPTS"] self.priority = pkt["V"]["V"]["PRIO"] self.d_router = pkt["V"]["V"]["DESIG"] self.bd_router = pkt["V"]["V"]["BDESIG"] # inactive timer is equal to dead interval self.inactive_timer_interval = self.ism.dead_interval self.ep = ExchangeProtocol(self) self.fp = FloodProtocol(self) # register all nsm events for nsmEvent in NSM_EVENT.keys(): if nsmEvent == "NSM_PacketReceived": self.nsm[nsmEvent] = self._hello_received elif nsmEvent == "NSM_TwoWayReceived": self.nsm[nsmEvent] = self._two_way_or_exstart elif nsmEvent == "NSM_OneWayReceived": self.nsm[nsmEvent] = self._init elif nsmEvent == "NSM_NegotiationDone": self.nsm[nsmEvent] = self._exchange elif nsmEvent == "NSM_SeqNumberMismatch": self.nsm[nsmEvent] = self._seq_mismatch_or_bad_lsr elif nsmEvent == "NSM_ExchangeDone": self.nsm[nsmEvent] = self._loading elif nsmEvent == "NSM_BadLSReq": self.nsm[nsmEvent] = self._seq_mismatch_or_bad_lsr elif nsmEvent == "NSM_LoadingDone": self.nsm[nsmEvent] = self._full else: continue def fire(self, event): self.nsm[event]() def reset(self): self.change_nsm_state("NSM_Down") if self._dd_exstart_timer is not None: self._dd_exstart_timer.stop() del self._dd_exstart_timer if self.lsr_resend_timer is not None: self.lsr_resend_timer.stop() del self.lsr_resend_timer self._dd_exstart_timer = None self.lsr_resend_timer = None self.ls_req = list() self.ls_rxmt = list() self.db_sum = list() self.last_recv = tuple() self.last_send = None self.last_send_ts = 0 # Delete all LSA in LSDB self.ism.ai.oi.lsdb.empty_lsdb() def dead(self): neighborLock.acquire() self.reset() self.rtid = 0 self.src = 0 self.options = {"E": 0, "MC": 0, "L": 0, "NP": 0, "DC": 0, "O": 0, "DN": 0, "Q": 0} self.priority = 0 self.d_router = 0 self.bd_router = 0 if self._inactive_timer is not None: self._inactive_timer.stop() del self._inactive_timer self._inactive_timer = None LOG.info("[ISM] Event: ISM_NeighborChange") self.ism.fire("ISM_NeighborChange") neighborLock.release() def _hello_received(self): if self.state == NSM_STATE["NSM_Down"]: self.change_nsm_state("NSM_Init") # start inactive timer if self._inactive_timer is None or self._inactive_timer.is_stop(): self._inactive_timer = Timer(self.inactive_timer_interval, self.dead) self._inactive_timer.start() LOG.debug("[NSM] %s starts inactive timer." % util.int2ip(self.rtid)) else: self._inactive_timer.reset() else: self._inactive_timer.reset() def _attempt(self): """ Only for nbma network """ self.change_nsm_state("NSM_Attempt") def _init(self): if self.state == NSM_STATE["NSM_Init"]: return self.change_nsm_state("NSM_Init") self.last_recv = tuple() self.last_send = None self.last_send_ts = 0 self.ls_req = list() self.ls_rxmt = list() self.db_sum = list() if not self.lsr_resend_timer is None: self.lsr_resend_timer.stop() def _two_way_or_exstart(self): # whether become adjacent in broadcast, rfc 10.4 if self.state == NSM_STATE["NSM_Init"] or self.state == NSM_STATE["NSM_TwoWay"]: # need to make adjacency if self.ism.bdrip == self.src or self.ism.drip == self.src or self.ism.link_type == "Point-to-Point": self.change_nsm_state("NSM_ExStart") # Generate dd_sum from each lsa list, and exception for virtual link and stub area. # But as a probe, we need not to send dd_sum. # start dd sync procedure self.dd_seqnum = int(time.time()) self.dd_flags = 7 # set init bit, more bit, master bit self._send_dd() if self._dd_exstart_timer is None: pass elif self._dd_exstart_timer.is_stop(): del self._dd_exstart_timer else: self._dd_exstart_timer.stop() del self._dd_exstart_timer self._dd_exstart_timer = Timer(self.ism.rxmt_interval, self._send_dd) self._dd_exstart_timer.start() # do not need to make adjacency else: if self.state == NSM_STATE["NSM_Init"]: # change to 2-way state self.change_nsm_state("NSM_TwoWay") else: # stay in 2-way state pass def _exchange(self): self.change_nsm_state("NSM_Exchange") self._dd_exstart_timer.stop() self.fp.set_ospf_header(self.ism.version, self.ism.area_id, self.ism.rid, self.ism.options) self.ep.exchange() def _loading(self): self.change_nsm_state("NSM_Loading") # start to send first lsr if len(self.ls_req) != 0: self._send_lsr(self.ls_req) self.lsr_resend_timer = Timer(self.ism.rxmt_interval, self._send_lsr, self.ls_req) self.lsr_resend_timer.start() else: self._full() def _full(self): self.change_nsm_state("NSM_Full") def _seq_mismatch_or_bad_lsr(self): LOG.warn("[NSM] %s sequence mismatch or bad LSR." % util.int2ip(self.rtid)) # make sure that all these are clear self.ls_req = list() self.ls_rxmt = list() self.db_sum = list() self.last_recv = tuple() self.last_send = None self.last_send_ts = 0 self.change_nsm_state("NSM_ExStart") self.dd_flags = 7 # set init bit, more bit, master bit self.dd_seqnum = int(time.time()) self._send_dd() if self._dd_exstart_timer is None: self._dd_exstart_timer = Timer(self.ism.rxmt_interval, self._send_dd) self._dd_exstart_timer.start() elif self._dd_exstart_timer.is_stop(): del self._dd_exstart_timer self._dd_exstart_timer = Timer(self.ism.rxmt_interval, self._send_dd) self._dd_exstart_timer.start() else: self._dd_exstart_timer.reset() def _send_dd(self, lsa=None): self.ep.set_ospf_header(self.ism.version, self.ism.area_id, self.ism.rid, self.ism.options) self.ep.set_dd_options() self.ep.send_dd(self.ep.gen_dd(lsa)) def _send_lsr(self, lsr): self.ep.send_lsr(self.ep.gen_lsr(lsr)) def change_nsm_state(self, newstate): LOG.info("[NSM] %s change state to %s." % (util.int2ip(self.rtid), newstate)) self.state = NSM_STATE[newstate]
class ExchangeProtocol(OspfProtocol): def __init__(self, nsm): OspfProtocol.__init__(self) self.init = 0 self.more = 0 self.ms = 0 self._rls_last_dd_timer = None self.nsm = nsm self.dst = ALL_SPF_ROUTER # by default if self.nsm.ism.link_type == 'Broadcast': if self.nsm.ism.drip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.drip) if self.nsm.ism.bdrip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.bdrip) elif self.nsm.ism.link_type == 'Point-to-Point': self.dst = util.int2ip(self.nsm.src) else: #TODO: if link type is virtual or others, set dst specific. pass #ospf socket self._sock = OspfSock() self._sock.bind(self.nsm.ism.ip_intf_addr) def __del__(self): self._sock.close() def set_dd_options(self): dolist = list(util.int2bin(self.nsm.dd_flags)) self.init, self.more, self.ms = dolist[-3], dolist[-2], dolist[-1] def gen_dd(self, lsa=None): #In dd packet, the mtu is 1500 by default, but in virtual link, mtu is must set to 0 #TODO: set virtual link mtu dd = DBDesc( mtu=self.nsm.ism.mtu, ddoptions=self.nsm.dd_flags, options=self.options, ddseq=self.nsm.dd_seqnum, ) #TODO: unimplemented to send probe's dd if not lsa is None: pass ospf_packet = OSPF( v=self.version, type=2, # 2 for dd area=self.area, len=len(dd) + len(OSPF()), router=self.rid, data=dd) return str(ospf_packet) def check_dd(self, pkt): """ check received dd packet, return True means the packet is handled correctly, return False means the packet is not in right procedure """ seq = pkt['V']['V']['DDSEQ'] mss, i, m = pkt['V']['V']['MS'], pkt['V']['V']['INIT'], pkt['V']['V'][ 'MORE'] r, mtu = pkt['V']['RID'], pkt['V']['V']['MTU'] opt = pkt['V']['V']['OPTS'] last_ospf_opt = self.nsm.options # save current ospf options self.nsm.options = opt # update nsm ospf options tmp_last_recv = None # save last recv packet if self.nsm.last_recv != (seq, i, m, mss): tmp_last_recv = self.nsm.last_recv self.nsm.last_recv = (seq, i, m, mss) #check mtu, if neighbor's mtu greater than our interface, drop it if self.nsm.ism.mtu < mtu: LOG.warn('[Exchange] Deny for bigger MTU.') return False if self.nsm.state == NSM_STATE[ 'NSM_Down'] or self.nsm.state == NSM_STATE['NSM_Attempt']: LOG.warn('[Exchange] Deny for inappropriate state.') return False elif self.nsm.state == NSM_STATE['NSM_Init']: self.nsm.fire('NSM_TwoWayReceived') return False elif self.nsm.state == NSM_STATE['NSM_TwoWay']: LOG.warn('[Exchange] Ignore for inappropriate state.') return False elif self.nsm.state == NSM_STATE['NSM_ExStart']: #Negotiate DD exchange if mss == 1 and m == 1 and i == 1 and r > self.rid: self.init = 0 self.more = 1 self.ms = 0 # set myself slave, use master dd seq number self.nsm.dd_seqnum = pkt['V']['V']['DDSEQ'] self.nsm.dd_flags = 4 * self.init + 2 * self.more + self.ms LOG.info('[NSM] Event: NSM_NegotiationDone') LOG.info('[NSM] We are slave.') self.nsm.fire('NSM_NegotiationDone') return True elif mss == 0 and i == 0 and r < self.rid: self.init = 0 self.more = 1 self.ms = 1 # set myself master, use my dd seq number self.nsm.dd_seqnum += 1 self.nsm.dd_flags = 4 * self.init + 2 * self.more + self.ms LOG.info('[NSM] Event: NSM_NegotiationDone') LOG.info('[NSM] We are master.') self.nsm.fire('NSM_NegotiationDone') if m == 0: self.nsm.fire('NSM_ExchangeDone') return True else: LOG.warn('[Exchange] Ignore for inappropriate dd options.') return False if self.nsm.state == NSM_STATE['NSM_Exchange']: if (seq, i, m, mss) == tmp_last_recv: if self.ms == 1: LOG.warn('[Exchange] Duplicate DD packet, drop as master.') return False else: #retransmit the last send packet LOG.warn( '[Exchange] Duplicate DD packet, retransmit as slave.') self.send_dd(self.nsm.last_send) return False #check whether master/slave bit match or init bit is set unexpected if mss == self.ms or i == 1: LOG.warn('[Exchange] DD packet wrong options.') self.nsm.fire('NSM_SeqNumberMismatch') return False #check whether ospf option is as same as the last received ospf packet if not last_ospf_opt is None: if last_ospf_opt != pkt['V']['V']['OPTS']: LOG.warn( '[Exchange] DD packet OSPF options are not same as the last received packet.' ) self.nsm.fire('NSM_SeqNumberMismatch') return False #new recv dd packet is not last recv dd packet, get its lsa header if not self._get_lsa(pkt): #if there's some thing wrong in lsa header, fire SeqNumMismatch LOG.error('[Exchange] DD packet has wrong LSA header.') self.nsm.fire('NSM_SeqNumberMismatch') return False #when more bit is 0, exchange stop, goto loading state if m == 0: if self.ms == 0: # If we are slave, send dd as reply. self.nsm.dd_seqnum = seq #all pass checked, send my dd to neighbor self.exchange() self.nsm.fire('NSM_ExchangeDone') else: #if probe is master, ddseq + 1; if slave, set ddseq to master ddseq if self.ms == 1: self.nsm.dd_seqnum += 1 else: self.nsm.dd_seqnum = seq #all pass checked, send my dd to neighbor self.exchange() return True elif self.nsm.state == NSM_STATE[ 'NSM_Loading'] or self.nsm.state == NSM_STATE['NSM_Full']: #check whether ospf option is as same as the last received ospf packet if not last_ospf_opt is None: if last_ospf_opt != pkt['V']['V']['OPTS']: LOG.warn( '[Exchange] DD packet OSPF options are not same as the last received packet.' ) self.nsm.fire('NSM_SeqNumberMismatch') return False #Unexpected init bit if i == 1: LOG.error('[Exchange] Unexpected init bit in DD packet.') self.nsm.fire('NSM_SeqNumberMismatch') return False if self.ms == 1: LOG.error('[Exchange] Duplicate DD packet, drop as master.') return False else: #retransmit the last send packet if self.nsm.last_send is None: LOG.error('[Exchange] Cannot retransmit last DD packet.') self.nsm.fire('NSM_SeqNumberMismatch') return False LOG.warn( '[Exchange] Duplicate DD packet, retransmit as slave.') self.send_dd(self.nsm.last_send) return True else: pass def send_dd(self, pkt): if self.nsm.ism.link_type == 'Broadcast' and self.dst != self.nsm.ism.drip and self.dst != self.nsm.ism.bdrip: if self.nsm.ism.drip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.drip) if self.nsm.ism.bdrip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.bdrip) self._sock.conn(self.dst) LOG.info('[Exchange] Send DD to %s.' % self.dst) self._sock.sendp(pkt) self.nsm.ism.ai.oi.stat.send_dd_count += 1 self.nsm.ism.ai.oi.stat.total_send_packet_count += 1 if self.nsm.state == NSM_STATE[ 'NSM_Loading'] or self.nsm.state == NSM_STATE['NSM_Full']: self.nsm.last_send = pkt # save the last sent packet #start a timer to wait dead interval to release the last recv packet if self._rls_last_dd_timer is None or self._rls_last_dd_timer.is_stop( ): self._rls_last_dd_timer = Timer(self.nsm.ism.dead_interval, self._rls_last_dd, once=True) self._rls_last_dd_timer.start() else: self._rls_last_dd_timer.reset() def _rls_last_dd(self): LOG.debug('[Exchange] Release last send DD packet.') self.nsm.last_send = None def _get_lsa(self, pkt): aid = pkt['V']['AID'] lsa_hdr_list = pkt['V']['V']['LSAS'] for lsah in lsa_hdr_list.keys(): tp, lsid, adv, seq = lsa_hdr_list[lsah]['T'],\ lsa_hdr_list[lsah]['LSID'],\ lsa_hdr_list[lsah]['ADVRTR'],\ lsa_hdr_list[lsah]['LSSEQNO'],\ #generate lsa key according to lsa type. if tp == 5: #check if a type-5 lsa into a stub area, return false if self.nsm.ism.options['E'] == 0: return False lsakey = (tp, lsid, adv) else: lsakey = (tp, aid, lsid, adv) lsalist = self.nsm.ism.ai.oi.lsdb.lookup_lsa_list(tp) if not lsalist is None: lsa = self.lookup_lsa(lsakey, lsalist) if lsa is None: self.nsm.ls_req.append(lsakey) elif lsa['H']['LSSEQNO'] < seq: #the lsa in dd is newer than lsa in the database self.nsm.ls_req.append(lsakey) else: continue #if did not find the lsa list else: LOG.error('[Exchange] Wrong LSA type in DD.') return False return True def exchange(self): #TODO: The probe always set more bit to 0 when exchange. Need to modify if we want to send probe's DD summary. lsa = self.nsm.db_sum tosend = lsa self.more = 0 # set more bit 0 self.nsm.dd_flags = 4 * int(self.init) + 2 * int(self.more) + int( self.ms) LOG.debug('[Exchange] DD flag is %s.' % self.nsm.dd_flags) self.send_dd(self.gen_dd(tosend)) def gen_lsr(self, rq): pkts = [] maxlsa = 100 more = True #In each LSR packet, it contains 100 lsa headers at max while more: if len(rq) - maxlsa > 0: lsas, rq = rq[:maxlsa], rq[maxlsa:] else: lsas = rq more = False lsrdata = [] lsrlen = len(lsas) * len(LSR()) for r in lsas: if r[0] == 5: lsrdata.append(str(LSR(lstype=r[0], lsid=r[1], adv=r[2]))) else: lsrdata.append(str(LSR(lstype=r[0], lsid=r[2], adv=r[3]))) ospf_packet = OSPF( v=self.version, type=3, # 3 for lsr area=self.area, len=lsrlen + len(OSPF()), router=self.rid, data=''.join(lsrdata)) pkts.append(str(ospf_packet)) return pkts def send_lsr(self, pkts): if self.nsm.ism.link_type == 'Broadcast' and self.dst != self.nsm.ism.drip and self.dst != self.nsm.ism.bdrip: if self.nsm.ism.drip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.drip) if self.nsm.ism.bdrip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.bdrip) LOG.debug('[Exchange] Send LSR to %s.' % self.dst) self._sock.conn(self.dst) for p in pkts: self._sock.sendp(p) self.nsm.ism.ai.oi.stat.send_lsr_count += 1 self.nsm.ism.ai.oi.stat.total_send_packet_count += 1 def check_lsr(self, pkt): """ The probe doesn't need to receive LSR """ pass
class ExchangeProtocol(OspfProtocol): def __init__(self, nsm): OspfProtocol.__init__(self) self.init = 0 self.more = 0 self.ms = 0 self._rls_last_dd_timer = None self.nsm = nsm self.dst = ALL_SPF_ROUTER # by default if self.nsm.ism.link_type == 'Broadcast': if self.nsm.ism.drip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.drip) if self.nsm.ism.bdrip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.bdrip) elif self.nsm.ism.link_type == 'Point-to-Point': self.dst = util.int2ip(self.nsm.src) else: #TODO: if link type is virtual or others, set dst specific. pass #ospf socket self._sock = OspfSock() self._sock.bind(self.nsm.ism.ip_intf_addr) def __del__(self): self._sock.close() def set_dd_options(self): dolist = list(util.int2bin(self.nsm.dd_flags)) self.init, self.more, self.ms = dolist[-3], dolist[-2], dolist[-1] def gen_dd(self, lsa=None): #In dd packet, the mtu is 1500 by default, but in virtual link, mtu is must set to 0 #TODO: set virtual link mtu dd = DBDesc( mtu=self.nsm.ism.mtu, ddoptions=self.nsm.dd_flags, options=self.options, ddseq=self.nsm.dd_seqnum, ) #TODO: unimplemented to send probe's dd if not lsa is None: pass ospf_packet = OSPF( v=self.version, type=2, # 2 for dd area=self.area, len=len(dd)+len(OSPF()), router=self.rid, data=dd ) return str(ospf_packet) def check_dd(self, pkt): """ check received dd packet, return True means the packet is handled correctly, return False means the packet is not in right procedure """ seq = pkt['V']['V']['DDSEQ'] mss, i, m = pkt['V']['V']['MS'], pkt['V']['V']['INIT'], pkt['V']['V']['MORE'] r, mtu = pkt['V']['RID'], pkt['V']['V']['MTU'] opt = pkt['V']['V']['OPTS'] last_ospf_opt = self.nsm.options # save current ospf options self.nsm.options = opt # update nsm ospf options tmp_last_recv = None # save last recv packet if self.nsm.last_recv != (seq, i, m, mss): tmp_last_recv = self.nsm.last_recv self.nsm.last_recv = (seq, i, m, mss) #check mtu, if neighbor's mtu greater than our interface, drop it if self.nsm.ism.mtu < mtu: LOG.warn('[Exchange] Deny for bigger MTU.') return False if self.nsm.state == NSM_STATE['NSM_Down'] or self.nsm.state == NSM_STATE['NSM_Attempt']: LOG.warn('[Exchange] Deny for inappropriate state.') return False elif self.nsm.state == NSM_STATE['NSM_Init']: self.nsm.fire('NSM_TwoWayReceived') return False elif self.nsm.state == NSM_STATE['NSM_TwoWay']: LOG.warn('[Exchange] Ignore for inappropriate state.') return False elif self.nsm.state == NSM_STATE['NSM_ExStart']: #Negotiate DD exchange if mss == 1 and m == 1 and i == 1 and r > self.rid: self.init = 0 self.more = 1 self.ms = 0 # set myself slave, use master dd seq number self.nsm.dd_seqnum = pkt['V']['V']['DDSEQ'] self.nsm.dd_flags = 4 * self.init + 2 * self.more + self.ms LOG.info('[NSM] Event: NSM_NegotiationDone') LOG.info('[NSM] We are slave.') self.nsm.fire('NSM_NegotiationDone') return True elif mss == 0 and i == 0 and r < self.rid: self.init = 0 self.more = 1 self.ms = 1 # set myself master, use my dd seq number self.nsm.dd_seqnum += 1 self.nsm.dd_flags = 4 * self.init + 2 * self.more + self.ms LOG.info('[NSM] Event: NSM_NegotiationDone') LOG.info('[NSM] We are master.') self.nsm.fire('NSM_NegotiationDone') if m == 0: self.nsm.fire('NSM_ExchangeDone') return True else: LOG.warn('[Exchange] Ignore for inappropriate dd options.') return False if self.nsm.state == NSM_STATE['NSM_Exchange']: if (seq, i, m, mss) == tmp_last_recv: if self.ms == 1: LOG.warn('[Exchange] Duplicate DD packet, drop as master.') return False else: #retransmit the last send packet LOG.warn('[Exchange] Duplicate DD packet, retransmit as slave.') self.send_dd(self.nsm.last_send) return False #check whether master/slave bit match or init bit is set unexpected if mss == self.ms or i == 1: LOG.warn('[Exchange] DD packet wrong options.') self.nsm.fire('NSM_SeqNumberMismatch') return False #check whether ospf option is as same as the last received ospf packet if not last_ospf_opt is None: if last_ospf_opt != pkt['V']['V']['OPTS']: LOG.warn('[Exchange] DD packet OSPF options are not same as the last received packet.') self.nsm.fire('NSM_SeqNumberMismatch') return False #new recv dd packet is not last recv dd packet, get its lsa header if not self._get_lsa(pkt): #if there's some thing wrong in lsa header, fire SeqNumMismatch LOG.error('[Exchange] DD packet has wrong LSA header.') self.nsm.fire('NSM_SeqNumberMismatch') return False #when more bit is 0, exchange stop, goto loading state if m == 0: if self.ms == 0: # If we are slave, send dd as reply. self.nsm.dd_seqnum = seq #all pass checked, send my dd to neighbor self.exchange() self.nsm.fire('NSM_ExchangeDone') else: #if probe is master, ddseq + 1; if slave, set ddseq to master ddseq if self.ms == 1: self.nsm.dd_seqnum += 1 else: self.nsm.dd_seqnum = seq #all pass checked, send my dd to neighbor self.exchange() return True elif self.nsm.state == NSM_STATE['NSM_Loading'] or self.nsm.state == NSM_STATE['NSM_Full']: #check whether ospf option is as same as the last received ospf packet if not last_ospf_opt is None: if last_ospf_opt != pkt['V']['V']['OPTS']: LOG.warn('[Exchange] DD packet OSPF options are not same as the last received packet.') self.nsm.fire('NSM_SeqNumberMismatch') return False #Unexpected init bit if i == 1: LOG.error('[Exchange] Unexpected init bit in DD packet.') self.nsm.fire('NSM_SeqNumberMismatch') return False if self.ms == 1: LOG.error('[Exchange] Duplicate DD packet, drop as master.') return False else: #retransmit the last send packet if self.nsm.last_send is None: LOG.error('[Exchange] Cannot retransmit last DD packet.') self.nsm.fire('NSM_SeqNumberMismatch') return False LOG.warn('[Exchange] Duplicate DD packet, retransmit as slave.') self.send_dd(self.nsm.last_send) return True else: pass def send_dd(self, pkt): if self.nsm.ism.link_type == 'Broadcast' and self.dst != self.nsm.ism.drip and self.dst != self.nsm.ism.bdrip: if self.nsm.ism.drip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.drip) if self.nsm.ism.bdrip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.bdrip) self._sock.conn(self.dst) LOG.info('[Exchange] Send DD to %s.' % self.dst) self._sock.sendp(pkt) self.nsm.ism.ai.oi.stat.send_dd_count += 1 self.nsm.ism.ai.oi.stat.total_send_packet_count += 1 if self.nsm.state == NSM_STATE['NSM_Loading'] or self.nsm.state == NSM_STATE['NSM_Full']: self.nsm.last_send = pkt # save the last sent packet #start a timer to wait dead interval to release the last recv packet if self._rls_last_dd_timer is None or self._rls_last_dd_timer.is_stop(): self._rls_last_dd_timer = Timer(self.nsm.ism.dead_interval, self._rls_last_dd, once=True) self._rls_last_dd_timer.start() else: self._rls_last_dd_timer.reset() def _rls_last_dd(self): LOG.debug('[Exchange] Release last send DD packet.') self.nsm.last_send = None def _get_lsa(self, pkt): aid = pkt['V']['AID'] lsa_hdr_list = pkt['V']['V']['LSAS'] for lsah in lsa_hdr_list.keys(): tp, lsid, adv, seq = lsa_hdr_list[lsah]['T'],\ lsa_hdr_list[lsah]['LSID'],\ lsa_hdr_list[lsah]['ADVRTR'],\ lsa_hdr_list[lsah]['LSSEQNO'],\ #generate lsa key according to lsa type. if tp == 5: #check if a type-5 lsa into a stub area, return false if self.nsm.ism.options['E'] == 0: return False lsakey = (tp, lsid, adv) else: lsakey = (tp, aid, lsid, adv) lsalist = self.nsm.ism.ai.oi.lsdb.lookup_lsa_list(tp) if not lsalist is None: lsa = self.lookup_lsa(lsakey, lsalist) if lsa is None: self.nsm.ls_req.append(lsakey) elif lsa['H']['LSSEQNO'] < seq: #the lsa in dd is newer than lsa in the database self.nsm.ls_req.append(lsakey) else: continue #if did not find the lsa list else: LOG.error('[Exchange] Wrong LSA type in DD.') return False return True def exchange(self): #TODO: The probe always set more bit to 0 when exchange. Need to modify if we want to send probe's DD summary. lsa = self.nsm.db_sum tosend = lsa self.more = 0 # set more bit 0 self.nsm.dd_flags = 4 * int(self.init) + 2 * int(self.more) + int(self.ms) LOG.debug('[Exchange] DD flag is %s.' % self.nsm.dd_flags) self.send_dd(self.gen_dd(tosend)) def gen_lsr(self, rq): pkts = [] maxlsa = 100 more = True #In each LSR packet, it contains 100 lsa headers at max while more: if len(rq) - maxlsa > 0: lsas, rq = rq[:maxlsa], rq[maxlsa:] else: lsas = rq more = False lsrdata = [] lsrlen = len(lsas) * len(LSR()) for r in lsas: if r[0] == 5: lsrdata.append(str(LSR( lstype=r[0], lsid=r[1], adv=r[2] ))) else: lsrdata.append(str(LSR( lstype=r[0], lsid=r[2], adv=r[3] ))) ospf_packet = OSPF( v=self.version, type=3, # 3 for lsr area=self.area, len=lsrlen + len(OSPF()), router=self.rid, data=''.join(lsrdata) ) pkts.append(str(ospf_packet)) return pkts def send_lsr(self, pkts): if self.nsm.ism.link_type == 'Broadcast' and self.dst != self.nsm.ism.drip and self.dst != self.nsm.ism.bdrip: if self.nsm.ism.drip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.drip) if self.nsm.ism.bdrip == self.nsm.src: self.dst = util.int2ip(self.nsm.ism.bdrip) LOG.debug('[Exchange] Send LSR to %s.' % self.dst) self._sock.conn(self.dst) for p in pkts: self._sock.sendp(p) self.nsm.ism.ai.oi.stat.send_lsr_count += 1 self.nsm.ism.ai.oi.stat.total_send_packet_count += 1 def check_lsr(self, pkt): """ The probe doesn't need to receive LSR """ pass
class NSM(object): """ Neighbor State Machine :param ism: ISM objects. :param rtid: RouterID. :param pkt: coming hello packet. """ def __init__(self, ism, rtid, pkt): #OSPF neighbor information self.state = NSM_STATE['NSM_Down'] # NSM status. self._inactive_timer = None self._dd_exstart_timer = None self.lsr_resend_timer = None self.dd_flags = 0 # DD bit flags. Slave or master. self.dd_seqnum = 0 # DD Sequence Number. #Last sent Database Description packet. self.last_send = None # just the packet, not tuple #Timestemp when last Database Description packet was sent self.last_send_ts = 0 # not used #Last received Database Description packet. self.last_recv = tuple() # pattern: (ddseq, init, more, ms) #LSA data. self.ls_rxmt = list() # Link state retransmission list self.db_sum = list() # Database summary list self.ls_req = list() # Link state request list self.ism = ism self.rtid = rtid # neighbor router id self.nsm = dict() #Neighbor Information from Hello. self.src = pkt['H']['SRC'] self.options = pkt['V']['V']['OPTS'] self.priority = pkt['V']['V']['PRIO'] self.d_router = pkt['V']['V']['DESIG'] self.bd_router = pkt['V']['V']['BDESIG'] #inactive timer is equal to dead interval self.inactive_timer_interval = self.ism.dead_interval self.ep = ExchangeProtocol(self) self.fp = FloodProtocol(self) #register all nsm events for nsmEvent in NSM_EVENT.keys(): if nsmEvent == 'NSM_PacketReceived': self.nsm[nsmEvent] = self._hello_received elif nsmEvent == 'NSM_TwoWayReceived': self.nsm[nsmEvent] = self._two_way_or_exstart elif nsmEvent == 'NSM_OneWayReceived': self.nsm[nsmEvent] = self._init elif nsmEvent == 'NSM_NegotiationDone': self.nsm[nsmEvent] = self._exchange elif nsmEvent == 'NSM_SeqNumberMismatch': self.nsm[nsmEvent] = self._seq_mismatch_or_bad_lsr elif nsmEvent == 'NSM_ExchangeDone': self.nsm[nsmEvent] = self._loading elif nsmEvent == 'NSM_BadLSReq': self.nsm[nsmEvent] = self._seq_mismatch_or_bad_lsr elif nsmEvent == 'NSM_LoadingDone': self.nsm[nsmEvent] = self._full else: continue def fire(self, event): self.nsm[event]() def reset(self): self.change_nsm_state('NSM_Down') if self._dd_exstart_timer is not None: self._dd_exstart_timer.stop() del self._dd_exstart_timer if self.lsr_resend_timer is not None: self.lsr_resend_timer.stop() del self.lsr_resend_timer self._dd_exstart_timer = None self.lsr_resend_timer = None self.ls_req = list() self.ls_rxmt = list() self.db_sum = list() self.last_recv = tuple() self.last_send = None self.last_send_ts = 0 #Delete all LSA in LSDB self.ism.ai.oi.lsdb.empty_lsdb() def dead(self): neighborLock.acquire() self.reset() self.rtid = 0 self.src = 0 self.options = {'E': 0, 'MC': 0, 'L': 0, 'NP': 0, 'DC': 0, 'O': 0, 'DN': 0, 'Q': 0} self.priority = 0 self.d_router = 0 self.bd_router = 0 if self._inactive_timer is not None: self._inactive_timer.stop() del self._inactive_timer self._inactive_timer = None LOG.info('[ISM] Event: ISM_NeighborChange') self.ism.fire('ISM_NeighborChange') neighborLock.release() def _hello_received(self): if self.state == NSM_STATE['NSM_Down']: self.change_nsm_state('NSM_Init') #start inactive timer if self._inactive_timer is None or self._inactive_timer.is_stop(): self._inactive_timer = Timer(self.inactive_timer_interval, self.dead) self._inactive_timer.start() LOG.debug('[NSM] %s starts inactive timer.' % util.int2ip(self.rtid)) else: self._inactive_timer.reset() else: self._inactive_timer.reset() def _attempt(self): """ Only for nbma network """ self.change_nsm_state('NSM_Attempt') def _init(self): if self.state == NSM_STATE['NSM_Init']: return self.change_nsm_state('NSM_Init') self.last_recv = tuple() self.last_send = None self.last_send_ts = 0 self.ls_req = list() self.ls_rxmt = list() self.db_sum = list() if not self.lsr_resend_timer is None: self.lsr_resend_timer.stop() def _two_way_or_exstart(self): #whether become adjacent in broadcast, rfc 10.4 if self.state == NSM_STATE['NSM_Init'] or self.state == NSM_STATE['NSM_TwoWay']: #need to make adjacency if self.ism.bdrip == self.src or self.ism.drip == self.src or self.ism.link_type == 'Point-to-Point': self.change_nsm_state('NSM_ExStart') #Generate dd_sum from each lsa list, and exception for virtual link and stub area. #But as a probe, we need not to send dd_sum. #start dd sync procedure self.dd_seqnum = int(time.time()) self.dd_flags = 7 # set init bit, more bit, master bit self._send_dd() if self._dd_exstart_timer is None: pass elif self._dd_exstart_timer.is_stop(): del self._dd_exstart_timer else: self._dd_exstart_timer.stop() del self._dd_exstart_timer self._dd_exstart_timer = Timer(self.ism.rxmt_interval, self._send_dd) self._dd_exstart_timer.start() #do not need to make adjacency else: if self.state == NSM_STATE['NSM_Init']: #change to 2-way state self.change_nsm_state('NSM_TwoWay') else: #stay in 2-way state pass def _exchange(self): self.change_nsm_state('NSM_Exchange') self._dd_exstart_timer.stop() self.fp.set_ospf_header( self.ism.version, self.ism.area_id, self.ism.rid, self.ism.options, ) self.ep.exchange() def _loading(self): self.change_nsm_state('NSM_Loading') #start to send first lsr if len(self.ls_req) != 0: self._send_lsr(self.ls_req) self.lsr_resend_timer = Timer(self.ism.rxmt_interval, self._send_lsr, self.ls_req) self.lsr_resend_timer.start() else: self._full() def _full(self): self.change_nsm_state('NSM_Full') def _seq_mismatch_or_bad_lsr(self): LOG.warn('[NSM] %s sequence mismatch or bad LSR.' % util.int2ip(self.rtid)) #make sure that all these are clear self.ls_req = list() self.ls_rxmt = list() self.db_sum = list() self.last_recv = tuple() self.last_send = None self.last_send_ts = 0 self.change_nsm_state('NSM_ExStart') self.dd_flags = 7 # set init bit, more bit, master bit self.dd_seqnum = int(time.time()) self._send_dd() if self._dd_exstart_timer is None: self._dd_exstart_timer = Timer(self.ism.rxmt_interval, self._send_dd) self._dd_exstart_timer.start() elif self._dd_exstart_timer.is_stop(): del self._dd_exstart_timer self._dd_exstart_timer = Timer(self.ism.rxmt_interval, self._send_dd) self._dd_exstart_timer.start() else: self._dd_exstart_timer.reset() def _send_dd(self, lsa=None): self.ep.set_ospf_header( self.ism.version, self.ism.area_id, self.ism.rid, self.ism.options, ) self.ep.set_dd_options() self.ep.send_dd(self.ep.gen_dd(lsa)) def _send_lsr(self, lsr): self.ep.send_lsr(self.ep.gen_lsr(lsr)) def change_nsm_state(self, newstate): LOG.info('[NSM] %s change state to %s.' % (util.int2ip(self.rtid), newstate)) self.state = NSM_STATE[newstate]