def verify_attached(self, name: str, pkts=None) -> VerifyResult: """ Verify that the device attaches to the Thread network. :param name: The device name. """ result = VerifyResult() assert self.is_wpan_device(name), name pkts = pkts or self.pkts extaddr = self.vars[name] src_pkts = pkts.filter_wpan_src64(extaddr) src_pkts.filter_mle_cmd(MLE_CHILD_ID_REQUEST).must_next() # Child Id Request result.record_last('child_id_request', pkts) dst_pkts = pkts.filter_wpan_dst64(extaddr) dst_pkts.filter_mle_cmd(MLE_CHILD_ID_RESPONSE).must_next() # Child Id Response result.record_last('child_id_response', pkts) with pkts.save_index(): src_pkts.filter_mle_cmd(MLE_ADVERTISEMENT).must_next() # MLE Advertisement result.record_last('mle_advertisement', pkts) logging.info(f"verify attached: d={name}, result={result}") return result
def verify_attached(self, child: str, parent: str = None, child_type: str = 'FTD', pkts=None) -> VerifyResult: """ Verify that the device attaches to the Thread network. :param child: The child device name. :param parent: The parent device name. :param child_type: The child device type (FTD, FTD-ED, MTD). """ result = VerifyResult() assert self.is_thread_device(child), child assert child_type in ('FTD', 'FTD-ED', 'MTD'), child_type pkts = pkts or self.pkts child_extaddr = self.vars[child] src_pkts = pkts.filter_wpan_src64(child_extaddr) if parent: assert self.is_thread_device(parent), parent src_pkts = pkts.filter_wpan_src64(child_extaddr).\ filter_wpan_dst64(self.vars[parent]) src_pkts.filter_mle_cmd( MLE_CHILD_ID_REQUEST).must_next() # Child Id Request result.record_last('child_id_request', pkts) dst_pkts = pkts.filter_wpan_dst64(child_extaddr) if parent: dst_pkts = pkts.filter_wpan_src64(self.vars[parent]).\ filter_wpan_dst64(child_extaddr) dst_pkts.filter_mle_cmd( MLE_CHILD_ID_RESPONSE).must_next() # Child Id Response result.record_last('child_id_response', pkts) with pkts.save_index(): if child_type == 'FTD': src_pkts = pkts.filter_wpan_src64(child_extaddr) src_pkts.filter_mle_cmd( MLE_ADVERTISEMENT).must_next() # MLE Advertisement result.record_last('mle_advertisement', pkts) logging.info(f"verify attached: d={child}, result={result}") return result
def verify_ping(self, src: str, dst: str, bbr: str = None, pkts: 'PacketVerifier' = None) -> VerifyResult: """ Verify the ping process. :param src: The source device name. :param dst: The destination device name. :param bbr: The Backbone Router name. If specified, this method also verifies that the ping request and reply be forwarded by the Backbone Router. :param pkts: The PacketFilter to search. :return: The verification result. """ if bbr: assert not (self.is_thread_device(src) and self.is_thread_device(dst)), \ f"both {src} and {dst} are WPAN devices" assert not (self.is_backbone_device(src) and self.is_backbone_device(dst)), \ f"both {src} and {dst} are ETH devices" if pkts is None: pkts = self.pkts src_dua = self.vars[src + '_DUA'] dst_dua = self.vars[dst + '_DUA'] if bbr: bbr_ext = self.vars[bbr] bbr_eth = self.vars[bbr + '_ETH'] result = VerifyResult() ping_req = pkts.filter_ping_request().filter_ipv6_dst(dst_dua) if self.is_backbone_device(src): p = ping_req.filter_eth_src(self.vars[src + '_ETH']).must_next() else: p = ping_req.filter_wpan_src64(self.vars[src]).must_next() # pkts.last().show() ping_id = p.icmpv6.echo.identifier logging.info("verify_ping: ping_id=%x", ping_id) result.record_last('ping_request', pkts) ping_req = ping_req.filter( lambda p: p.icmpv6.echo.identifier == ping_id) # BBR unicasts the ping packet to TD. if bbr: if self.is_backbone_device(src): ping_req.filter_wpan_src64(bbr_ext).must_next() else: ping_req.filter_eth_src(bbr_eth).must_next() ping_reply = pkts.filter_ping_reply().filter_ipv6_dst(src_dua).filter( lambda p: p.icmpv6.echo.identifier == ping_id) # TD receives ping packet and responds back to Host via SBBR. if self.is_thread_device(dst): ping_reply.filter_wpan_src64(self.vars[dst]).must_next() else: ping_reply.filter_eth_src(self.vars[dst + '_ETH']).must_next() result.record_last('ping_reply', pkts) if bbr: # SBBR forwards the ping response packet to Host. if self.is_thread_device(dst): ping_reply.filter_eth_src(bbr_eth).must_next() else: ping_reply.filter_wpan_src64(bbr_ext).must_next() return result
def verify_dua_registration(self, td: str, bbr: str, pkts=None, dua_deadline=None, DAD=False, sbbr=None) -> VerifyResult: """ Run the packet verification for the while DAD registration, including optional steps This is commonly used in many test cases. :param pkts: The packet filter to verify, or self.pkts if None :param td: TB's name. :param bbr: BBR's name. """ assert self.is_wpan_device(td) assert self.is_wpan_device(bbr) and self.is_eth_device(bbr), bbr if pkts is None: pkts = self.pkts logging.info("verifying DUA registration from %s to %s ...", td, bbr) result = VerifyResult() TD = self.vars[td] BBR = self.vars[bbr] BBR_ETH = self.vars[bbr + '_ETH'] if sbbr: SBBR_ETH = self.vars[sbbr + '_ETH'] # BBR_DUA = self.vars[bbr + '_DUA'] p = pkts.filter_wpan_src64(TD) \ .filter_coap_request("/n/dr", port=self.MM_PORT) \ .must_next() result.record_last("/n/dr", pkts) if dua_deadline is not None: p.must_verify(lambda p: p.sniff_timestamp <= dua_deadline) idx_after_n_dr = pkts.index p.must_verify(lambda p: p.coap.tlv.target_eid and p.coap.tlv.ml_eid) DUA = p.coap.tlv.target_eid MLEID = p.coap.tlv.ml_eid self.add_vars(**{td + "_DUA": DUA, td + "_MLEID": MLEID}) logging.info(f"DUA={DUA}, MLEID={MLEID}") if DAD: # DAD (DUA_DATA_REPEAT+1) TIMES before_dad_index = pkts.index after_dad_index = before_dad_index with pkts.save_index(): for i in range(consts.DUA_DAD_REPEATS + 1): # Step 3: PBBR - Performs DAD on the backbone link - Multicasts a BB.qry CoAP request filter = pkts.filter_eth_src(BBR_ETH) \ .filter_LLABMA() \ .filter_coap_request("/b/bq") \ .filter(lambda p: p.coap.tlv.target_eid == DUA) p = filter.must_next() after_dad_index = self.max_index(after_dad_index, pkts.index) with pkts.save_index(): # try to find the next multicast # start_index = pkts.index if filter.next(): pe = pkts.last() time_gap = pe.sniff_timestamp - p.sniff_timestamp # PBBR Waits for DUA_DAD_QUERY_TIMEOUT: Verify that DUA_DAD_QUERY_TIMEOUT time passes assert time_gap >= consts.DUA_DAD_QUERY_TIMEOUT - 0.01, time_gap # SBBR: Does not respond: SBBR does not respond to the BB.qry message. if sbbr is not None: dad_pkts_range = pkts.range(before_dad_index, after_dad_index, cascade=False) dad_pkts_range.filter_eth_src(SBBR_ETH) \ .filter_LLABMA() \ .filter_coap_ack("/b/bq") \ .must_not_next() # BBR updates the corresponding entry in its DUA device table. # No pass criteria # Step 7: BBR informs other BBRs on the network of the DUA registration. # FIXME: Test plan requires that last_transaction_time <= 3, # however real OT implementation can have last_transaction_time == 4 expected_last_transaction_time = 0 if not DAD else 4 pkts.filter_eth_src(BBR_ETH) \ .filter_LLABMA() \ .filter_coap_request("/b/ba") \ .filter("coap.tlv.target_eid == {DUA}", DUA=DUA) \ .must_next() \ .must_verify(""" coap.tlv.ml_eid == {MLEID} and coap.tlv.last_transaction_time <= {expected_last_transaction_time} and coap.tlv.net_name == {NET_NAME} """, MLEID=MLEID, NET_NAME=self.NET_NAME, expected_last_transaction_time=expected_last_transaction_time) idx1 = pkts.index # SBBR receives PRO_BB.ntf and optionally updates the corresponding entry # in its Backup DUA Devices Table. No pass criteria. # BBR announces itself as the new ND proxy for the roaming device pkts.seek_back(0.2, eth=True) \ .filter_eth_src(BBR_ETH) \ .filter_LLANMA() \ .filter_icmpv6_nd_na(DUA) \ .must_next() \ .must_verify(""" icmpv6.nd.na.flag.s == 0 and icmpv6.nd.na.flag.o == 1 and icmpv6.nd.na.flag.r == 1 and icmpv6.opt.target_linkaddr == {BBR_ETH} """, BBR_ETH=BBR_ETH) idx2 = pkts.index # BBR responds to the DUA registration pkts.index = idx_after_n_dr # reset index to just after /n/dr request pkts.filter_wpan_src64(BBR) \ .filter_coap_ack("/n/dr") \ .filter("coap.tlv.target_eid == {DUA}", DUA=DUA) \ .must_next() \ .must_verify(""" coap.tlv.target_eid == {DUA} and coap.tlv.status == 0 """, DUA=DUA) pkts.index = self.max_index(idx1, idx2, pkts.index) # BBR optionally repeats the unsolicited neighbor advertisement. # Optional 1 or 2 times with pkts.save_index(): filter = pkts.filter_eth_src(BBR_ETH) \ .filter_LLANMA() \ .filter_icmpv6_nd_na(DUA) for i in range(2): if not filter.next(): break filter.last().must_verify(""" icmpv6.nd.na.flag.s == 0 and icmpv6.nd.na.flag.o == 1 and icmpv6.nd.na.flag.r == 1 and icmpv6.opt.target_linkaddr == {BBR_ETH} """, BBR_ETH=BBR_ETH) # BBR Optionally repeats the DUA registration notification # Optional with pkts.save_index(): filter = pkts.filter_eth_src(BBR_ETH) \ .filter_LLABMA() \ .filter_coap_request("/b/ba") \ .filter("coap.tlv.target_eid == {DUA}", DUA=DUA) if filter.next(): p = filter.last() p.must_verify(""" coap.tlv.ml_eid == {MLEID} and coap.tlv.net_name == {NET_NAME} and 3 < coap.tlv.last_transaction_time < DUA_RECENT_TIME """, DUA_RECENT_TIME=DUA_RECENT_TIME, MLEID=MLEID, NET_NAME=self.NET_NAME) if sbbr is not None: # SBBR: Does not respond to the ND Neighbor Solicitation message. SBBR_ETH = self.vars[sbbr + '_ETH'] pkts_in_range = pkts.range(idx_after_n_dr, pkts.index, cascade=False) pkts_in_range.filter_eth_src(SBBR_ETH).filter_LLANMA().filter_icmpv6_nd_na(DUA).must_not_next() return result