1#!/usr/bin/env python3 2# 3# Copyright (c) 2021, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30import logging 31import unittest 32 33import pktverify 34from pktverify import packet_verifier, packet_filter, consts 35from pktverify.consts import MA1, MA2 36import config 37import thread_cert 38 39# Test description: 40# The purpose of this test case is to verify that a Thread End Device detects a 41# change of Primary BBR device and triggers a re-registration to its multicast 42# groups. 43# 44# Topology: 45# 46# 47# ----------------(eth)-------------------- 48# | 49# BR_1 (Leader) -----BR_2 50# | | 51# | | 52# | | 53# Router_1-------------+ 54# | 55# | 56# TD 57# 58 59BR_1 = 1 60BR_2 = 2 61ROUTER_1 = 3 62TD = 4 63 64REG_DELAY = 10 65 66NETWORK_ID_TIMEOUT = 120 67WAIT_TIME_ALLOWANCE = 60 68 69 70class MATN_15_ChangeOfPrimaryBBRTriggersRegistration(thread_cert.TestCase): 71 USE_MESSAGE_FACTORY = False 72 73 TOPOLOGY = { 74 BR_1: { 75 'name': 'BR_1', 76 'is_otbr': True, 77 'allowlist': [BR_2, ROUTER_1], 78 'version': '1.2', 79 }, 80 BR_2: { 81 'name': 'BR_2', 82 'is_otbr': True, 83 'allowlist': [BR_1, ROUTER_1], 84 'version': '1.2', 85 }, 86 ROUTER_1: { 87 'name': 'Router_1', 88 'allowlist': [BR_1, BR_2, TD], 89 'version': '1.2', 90 'partition_id': 1, 91 }, 92 TD: { 93 'name': 'TD', 94 'allowlist': [ROUTER_1], 95 'version': '1.2', 96 'partition_id': 1, 97 }, 98 } 99 100 def test(self): 101 br1 = self.nodes[BR_1] 102 br2 = self.nodes[BR_2] 103 router1 = self.nodes[ROUTER_1] 104 td = self.nodes[TD] 105 106 br1.set_backbone_router(reg_delay=REG_DELAY, mlr_timeout=consts.MLR_TIMEOUT_MIN) 107 br1.start() 108 self.simulator.go(5) 109 self.assertEqual('leader', br1.get_state()) 110 self.assertTrue(br1.is_primary_backbone_router) 111 112 br2.set_backbone_router(reg_delay=REG_DELAY, mlr_timeout=consts.MLR_TIMEOUT_MIN) 113 router1.start() 114 self.simulator.go(5) 115 self.assertEqual('router', router1.get_state()) 116 117 td.start() 118 self.simulator.go(5) 119 self.assertEqual('router', td.get_state()) 120 121 br2.start() 122 self.simulator.go(5) 123 self.assertEqual('router', br2.get_state()) 124 self.assertFalse(br2.is_primary_backbone_router) 125 126 # TD registers for a multicast address, MA1. 127 td.add_ipmaddr(MA1) 128 self.simulator.go(20) 129 130 # 1. Disable BR_1 such that BR_2 becomes the Primary BBR for the Thread 131 # Network. 132 br1.disable_backbone_router() 133 br1.thread_stop() 134 br1.interface_down() 135 self.simulator.go(NETWORK_ID_TIMEOUT + WAIT_TIME_ALLOWANCE) 136 137 # Make sure that BR_2 becomes the primary BBR 138 self.assertEqual('disabled', br1.get_state()) 139 self.assertFalse(br1.is_primary_backbone_router) 140 self.assertTrue(br2.is_primary_backbone_router) 141 142 self.collect_ipaddrs() 143 self.collect_rloc16s() 144 self.collect_rlocs() 145 self.collect_extra_vars() 146 147 def verify(self, pv: pktverify.packet_verifier.PacketVerifier): 148 pkts = pv.pkts 149 vars = pv.vars 150 pv.summary.show() 151 152 # Ensure the topology is formed correctly 153 pv.verify_attached('Router_1', 'BR_1') 154 155 # 2. TD automatically detects the Primary BBR change and registers for 156 # multicast address, MA1, at BR_2. 157 # TD unicasts an MLR.req CoAP request as follows: 158 # coap://[<BR_2 RLOC or PBBR ALOC>]:MM/n/mr 159 # Where the payload contains: 160 # IPv6 Addresses TLV: MA1 161 _pkt = pkts.filter_wpan_src64(vars['TD']) \ 162 .filter_ipv6_2dsts(vars['BR_2_RLOC'], consts.PBBR_ALOC) \ 163 .filter_coap_request('/n/mr') \ 164 .filter(lambda p: p.thread_meshcop.tlv.ipv6_addr == [MA1]) \ 165 .must_next() 166 167 # 3. Router_1 forwards the registration request to BR_2. 168 pkts.filter_wpan_src64(vars['Router_1']) \ 169 .filter_ipv6_2dsts(vars['BR_2_RLOC'], consts.PBBR_ALOC) \ 170 .filter_coap_request('/n/mr') \ 171 .filter(lambda 172 p: p.thread_meshcop.tlv.ipv6_addr == [MA1] and 173 p.coap.mid == _pkt.coap.mid) \ 174 .must_next() 175 176 # 4. BR_2 unicasts an MLR.rsp CoAP response to TD as: 2.04 changed. 177 # Where the payload contains: 178 # Status TLV: ST_MLR_SUCCESS 179 pkts.filter_wpan_src64(vars['BR_2']) \ 180 .filter_ipv6_dst(_pkt.ipv6.src) \ 181 .filter_coap_ack('/n/mr') \ 182 .filter(lambda 183 p: p.coap.mid == _pkt.coap.mid and 184 p.thread_nm.tlv.status == 0) \ 185 .must_next() 186 187 # 5. Router_1 forwards the response to TD. 188 pkts.filter_wpan_src64(vars['Router_1']) \ 189 .filter_ipv6_dst(_pkt.ipv6.src) \ 190 .filter_coap_ack('/n/mr') \ 191 .filter(lambda 192 p: p.coap.mid == _pkt.coap.mid and 193 p.thread_nm.tlv.status == 0) \ 194 .must_next() 195 196 197if __name__ == '__main__': 198 unittest.main() 199