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, PBBR_ALOC 36import config 37import thread_cert 38 39# Test description: 40# The purpose of this test case is to verify that a Primary BBR (DUT) can manage 41# a re-registration of a device on its network to remain receiving multicasts. 42# The test also verifies the usage of UDP multicast packets across backbone and 43# internal Thread network. 44# 45# Topology: 46# ----------------(eth)------------------ 47# | | | 48# BR_1 (Leader) ---- BR_2 HOST 49# | | 50# | | 51# Router_1 -----------+ 52# 53 54BR_1 = 1 55BR_2 = 2 56ROUTER_1 = 3 57HOST = 4 58 59REG_DELAY = 10 60UDP_HEADER_LENGTH = 8 61 62 63class MATN_05_ReregistrationToSameMulticastGroup(thread_cert.TestCase): 64 USE_MESSAGE_FACTORY = False 65 66 TOPOLOGY = { 67 BR_1: { 68 'name': 'BR_1', 69 'is_otbr': True, 70 'allowlist': [BR_2, ROUTER_1], 71 'version': '1.2', 72 }, 73 BR_2: { 74 'name': 'BR_2', 75 'allowlist': [BR_1, ROUTER_1], 76 'is_otbr': True, 77 'version': '1.2', 78 }, 79 ROUTER_1: { 80 'name': 'Router_1', 81 'allowlist': [BR_1, BR_2], 82 'version': '1.2', 83 }, 84 HOST: { 85 'name': 'Host', 86 'is_host': True 87 }, 88 } 89 90 def test(self): 91 br1 = self.nodes[BR_1] 92 br2 = self.nodes[BR_2] 93 router1 = self.nodes[ROUTER_1] 94 host = self.nodes[HOST] 95 96 br1.set_backbone_router(reg_delay=REG_DELAY, mlr_timeout=consts.MLR_TIMEOUT_MIN) 97 br1.start() 98 self.simulator.go(config.LEADER_STARTUP_DELAY) 99 self.assertEqual('leader', br1.get_state()) 100 self.assertTrue(br1.is_primary_backbone_router) 101 102 router1.start() 103 self.simulator.go(10) 104 self.assertEqual('router', router1.get_state()) 105 106 br2.start() 107 self.simulator.go(10) 108 self.assertEqual('router', br2.get_state()) 109 self.assertFalse(br2.is_primary_backbone_router) 110 111 host.start(start_radvd=False) 112 self.simulator.go(10) 113 114 # Router_1 registers for multicast address, MA1, at BR_1. 115 router1.add_ipmaddr(MA1) 116 self.simulator.go(5) 117 118 # 1. Host sends a ping packet to the multicast address, MA1. 119 self.assertTrue( 120 host.ping(MA1, backbone=True, ttl=10, interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) 121 122 # Ensure Router_1 renews its multicast registration 123 self.simulator.go(consts.MLR_TIMEOUT_MIN - 10) 124 125 # 4. Within MLR_TIMEOUT_MIN seconds, Host sends a ping packet to the 126 # multicast address, MA1. The destination port 5683 is used for the UDP 127 # Multicast packet transmission. 128 host.udp_send_host(data='PING', ipaddr=MA1, port=5683) 129 self.simulator.go(5) 130 131 # 6a. By internal means, Router_1 stops listening to the multicast 132 # address, MA1. 133 router1.del_ipmaddr(MA1) 134 135 # 7. After (MLR_TIMEOUT_MIN+2) seconds, Host multicasts a ping packet to 136 # multicast address, MA1, on the backbone link. 137 self.simulator.go(consts.MLR_TIMEOUT_MIN + 2) 138 self.assertFalse( 139 host.ping(MA1, backbone=True, ttl=10, interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) 140 141 self.collect_ipaddrs() 142 self.collect_rloc16s() 143 self.collect_rlocs() 144 self.collect_leader_aloc(BR_1) 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 logging.info(f'vars = {vars}') 152 153 # Ensure the topology is formed correctly 154 pv.verify_attached('Router_1', 'BR_1') 155 pv.verify_attached('BR_2') 156 157 # Initial registration 158 # Router_1 registers for multicast address, MA1, at BR_1. 159 # Router_1 unicasts an MLR.req CoAP request to BR_1 as 160 # "coap://[<BR_1 RLOC or PBBR ALOC>]:MM/n/mr". 161 # The payload contains "IPv6Address TLV: MA1". 162 initial_registration_pkt = pkts.filter_wpan_src64(vars['Router_1']) \ 163 .filter_ipv6_2dsts(vars['BR_1_RLOC'], PBBR_ALOC) \ 164 .filter_coap_request('/n/mr') \ 165 .filter(lambda p: p.thread_meshcop.tlv.ipv6_addr == [MA1]) \ 166 .must_next() 167 168 # 1. Host sends a ping packet to the multicast address, MA1. 169 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 170 .filter_ipv6_dst(MA1) \ 171 .filter_ping_request() \ 172 .must_next() 173 174 # 2. BR_1 forwards the ping packet with multicast address, MA1, to its 175 # Thread Network encapsulated in an MPL packet. 176 pkts.filter_wpan_src64(vars['BR_1']) \ 177 .filter_AMPLFMA(mpl_seed_id=vars['BR_1_RLOC']) \ 178 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 179 .must_next() 180 181 # 3. Router_1 receives the MPL packet containing an encapsulated ping 182 # packet to MA1, sent by Host, and unicasts a ping response packet back 183 # to Host. 184 pkts.filter_wpan_src64(vars['Router_1']) \ 185 .filter_ipv6_dst(_pkt.ipv6.src) \ 186 .filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier) \ 187 .must_next() 188 189 # 3a. Within MLR_TIMEOUT_MIN seconds of initial registration, Router_1 190 # re-registers for multicast address, MA1, at BR_1. 191 # Router_1 unicasts an MLR.req CoAP request to BR_1 as 192 # "coap://[<BR_1 RLOC or PBBR ALOC>]:MM/n/mr". 193 # The payload contains "IPv6Address TLV: MA1". 194 pkts.copy() \ 195 .filter_wpan_src64(vars['Router_1']) \ 196 .filter_ipv6_2dsts(vars['BR_1_RLOC'], PBBR_ALOC) \ 197 .filter_coap_request('/n/mr') \ 198 .filter(lambda p: p.thread_meshcop.tlv.ipv6_addr == [MA1] and 199 p.sniff_timestamp <= initial_registration_pkt.sniff_timestamp + consts.MLR_TIMEOUT_MIN) \ 200 .must_next() 201 202 # 4. Within MLR_TIMEOUT_MIN seconds, Host sends a ping packet to the 203 # multicast address, MA1. The destination port 5683 is used for the UDP 204 # Multicast packet transmission. 205 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 206 .filter_ipv6_dst(MA1) \ 207 .filter(lambda p: p.udp.length == UDP_HEADER_LENGTH + len('PING') 208 and p.udp.dstport == 5683) \ 209 .must_next() 210 211 # 5. BR_1 forwards the UDP ping packet with multicast address, MA1, to 212 # its Thread Network encapsulated in an MPL packet. 213 pkts.filter_wpan_src64(vars['BR_1']) \ 214 .filter_AMPLFMA(mpl_seed_id=vars['BR_1_RLOC']) \ 215 .filter(lambda p: p.udp.length == _pkt.udp.length) \ 216 .must_next() 217 218 # 6. Router_1 receives the ping packet. 219 # Use the port 5683 (CoAP port) to verify that the 220 # UDP Multicast packet is received. 221 pkts.filter_wpan_src64(vars['Router_1']) \ 222 .filter( 223 lambda p: p.udp.length == _pkt.udp.length and p.udp.dstport == 5683) \ 224 .must_next() 225 226 # 7. After (MLR_TIMEOUT_MIN+2) seconds, Host multicasts a ping packet to 227 # multicast address, MA1, on the backbone link. 228 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 229 .filter_ipv6_dst(MA1) \ 230 .filter_ping_request() \ 231 .must_next() 232 233 # 8. BR_1 does not forward the ping packet with multicast address, MA1, 234 # to its Thread Network. 235 pkts.filter_wpan_src64(vars['BR_1']) \ 236 .filter_AMPLFMA(mpl_seed_id=vars['BR_1_RLOC']) \ 237 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 238 .must_not_next() 239 240 241if __name__ == '__main__': 242 unittest.main() 243