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 35from pktverify.consts import MA1, MA1g, MA2, PBBR_ALOC 36import config 37import thread_cert 38 39# Test description: 40# The purpose of this test case is to verify that a Thread device is able to 41# register a multicast address with a Primary BBR and that the Primary BBR can 42# notify devices on the backbone link of the multicast address. The test also 43# verifies that an IPv6 multicast packet originating from the backbone is 44# correctly forwarded (in the Thread Network) to the device that registered the 45# multicast address. 46# 47# Topology: 48# ----------------(eth)------------------ 49# | | | 50# BR1 (Leader) ----- BR2 HOST 51# | | 52# | | 53# TD --------(when TD is router) 54# 55 56BR_1 = 1 57BR_2 = 2 58TD = 3 59HOST = 4 60 61CHANNEL1 = 18 62 63MLR_NOTIFICATION_TIMEOUT = 3600 64 65 66class MATN_02_MLRFirstUse(thread_cert.TestCase): 67 USE_MESSAGE_FACTORY = False 68 69 TOPOLOGY = { 70 BR_1: { 71 'name': 'BR_1', 72 'is_otbr': True, 73 'allowlist': [BR_2, TD], 74 'version': '1.2', 75 }, 76 BR_2: { 77 'name': 'BR_2', 78 'allowlist': [BR_1, TD], 79 'is_otbr': True, 80 'version': '1.2', 81 }, 82 TD: { 83 'name': 'TD', 84 'allowlist': [BR_1, BR_2], 85 'version': '1.2', 86 }, 87 HOST: { 88 'name': 'Host', 89 'is_host': True 90 }, 91 } 92 93 def test(self): 94 br1 = self.nodes[BR_1] 95 br2 = self.nodes[BR_2] 96 td = self.nodes[TD] 97 host = self.nodes[HOST] 98 99 br1.start() 100 self.simulator.go(config.LEADER_STARTUP_DELAY) 101 self.assertEqual('leader', br1.get_state()) 102 self.assertTrue(br1.is_primary_backbone_router) 103 104 td.start() 105 self.simulator.go(5) 106 self.assertEqual('router', td.get_state()) 107 108 br2.start() 109 self.simulator.go(5) 110 self.assertEqual('router', br2.get_state()) 111 112 host.start(start_radvd=False) 113 self.simulator.go(10) 114 115 # 1. TD registers for multicast address, MA1, at BR_1. 116 td.add_ipmaddr(MA1) 117 self.simulator.go(5) 118 119 # 7. Host sends a ping packet to the multicast address, MA1. TD should 120 # respond to the ping request. 121 self.assertTrue( 122 host.ping(MA1, backbone=True, ttl=10, interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) 123 self.simulator.go(5) 124 125 # 11. Host sends a ping packet to the multicast address, MA2. No one 126 # should respond. 127 self.assertFalse( 128 host.ping(MA2, backbone=True, ttl=10, interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) 129 self.simulator.go(5) 130 131 # 14. Host sends a ping packet to the multicast address, MA1g. No one 132 # should respond. 133 self.assertFalse( 134 host.ping(MA1g, backbone=True, ttl=10, interface=host.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) 135 self.simulator.go(5) 136 137 # 17. Host sends a ping packet to the global unicast address of BR2. BR2 138 # should respond. 139 self.assertTrue(host.ping(br2.get_ip6_address(config.ADDRESS_TYPE.BACKBONE_GUA), backbone=True, ttl=10)) 140 self.simulator.go(5) 141 142 self.collect_ipaddrs() 143 self.collect_rloc16s() 144 self.collect_rlocs() 145 self.collect_leader_aloc(BR_1) 146 self.collect_extra_vars() 147 148 def verify(self, pv: pktverify.packet_verifier.PacketVerifier): 149 pkts = pv.pkts 150 vars = pv.vars 151 pv.summary.show() 152 153 # Ensure the topology is formed correctly 154 pv.verify_attached('TD', 'BR_1') 155 pv.verify_attached('BR_2') 156 157 # 1. TD Registers for multicast address, MA1, at BR_1. 158 # TD unicasts an MLR.req CoAP request to BR_1 as 159 # "coap://[<BR_1 RLOC or PBBR ALOC>]:MM/n/mr". 160 # The payload contains "IPv6Address TLV: MA1". 161 pkts.filter_wpan_src64(vars['TD']) \ 162 .filter_ipv6_2dsts(vars['BR_1_RLOC'], 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. BR_1 responds to the multicast registration. 168 # BR_1 unicasts an MLR.rsp CoAP response to TD as "2.04 changed". 169 # The payload contains "Status TLV: ST_MLR_SUCCESS". 170 pkts.copy().filter_wpan_src64(vars['BR_1']) \ 171 .filter_ipv6_dst(vars['TD_RLOC']) \ 172 .filter_coap_ack('/n/mr') \ 173 .filter(lambda p: p.thread_nm.tlv.status == 0) \ 174 .must_next() 175 176 # 3a. BR_2 does not respond to the multicast registration. 177 pkts.filter_wpan_src64(vars['BR_2']) \ 178 .filter_ipv6_dst(vars['TD_RLOC']) \ 179 .filter_coap_ack('/n/mr') \ 180 .must_not_next() 181 182 # 4. BR_1 informs other BBRs on the network of multicast registration. 183 # BR_1 multicasts a BMLR.ntf CoAP request to the Backbone Link including 184 # to BR_2, as follows 185 pkts.filter_eth_src(vars['BR_1_ETH']) \ 186 .filter_ipv6_dst(config.ALL_NETWORK_BBRS_ADDRESS) \ 187 .filter_coap_request('/b/bmr') \ 188 .filter(lambda 189 p: p.thread_meshcop.tlv.ipv6_addr == [MA1] and 190 p.thread_bl.tlv.timeout == MLR_NOTIFICATION_TIMEOUT) \ 191 .must_next() 192 193 # 6. BR_1 multicasts an MLDv2 message to start listening to MA1. 194 # BR_1 multicasts an MLDv2 message of type “Version 2 Multicast Listener 195 # Report” (see [RFC 3810] Section 5.2). Where: 196 # Nr of Mcast Address at least 1 Records (M): Multicast Address Record 197 # The Multicast Address Record contains the following: 198 # Record Type: 4 (CHANGE_TO_EXCLUDE_MODE) 199 # Number of Sources (N): 0 200 # Multicast Address: MA1 201 # TODO: Implement this verification 202 203 # 7. Host sends a ping packet to the multicast address, MA1. 204 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 205 .filter_ipv6_dst(MA1) \ 206 .filter_ping_request() \ 207 .must_next() 208 209 # 8. BR_2 does not forward the ping packet with multicast address MA1 to 210 # its Thread Network. 211 pkts.filter_wpan_src64(vars['BR_2']) \ 212 .filter_AMPLFMA(mpl_seed_id=(vars['BR_2_RLOC'])) \ 213 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 214 .must_not_next() 215 216 # 9. BR_1 forwards the ping packet to its Thread Network. 217 # BR_1 forwards the ping packet with multicast address, MA1, to its 218 # Thread Network encapsulated in an MPL packet, where: 219 # MPL Seed ID: If Source outer IP header = BR_1 RLOC, SeedID length = 0 220 # Else, SeedID length = 1, and Seed ID = BR_1 RLOC16 221 pkts.filter_wpan_src64(vars['BR_1']) \ 222 .filter_AMPLFMA(mpl_seed_id=(vars['BR_1_RLOC'])) \ 223 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 224 .must_next() 225 226 # 10. TD receives the multicast ping packet and sends a ping response 227 # packet back to Host. 228 # TD receives the MPL packet containing an encapsulated ping packet to 229 # MA1, sent by Host, and unicasts a ping response packet back to Host. 230 pkts.filter_wpan_src64(vars['TD']) \ 231 .filter_ipv6_dst(_pkt.ipv6.src) \ 232 .filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier) \ 233 .must_next() 234 235 # 11. Host sends a ping packet to the multicast address, MA2. 236 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 237 .filter_ipv6_dst(MA2) \ 238 .filter_ping_request() \ 239 .must_next() 240 241 # 12. BR_2 does not forward the ping packet with multicast address, MA2, 242 # to the Thread Network in whatever way. 243 pkts.filter_wpan_src64(vars['BR_2']) \ 244 .filter_AMPLFMA(mpl_seed_id=(vars['BR_2_RLOC'])) \ 245 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 246 .must_not_next() 247 248 # 13. BR_1 does not forward the ping packet with multicast address, MA2, 249 # to the Thread Network in whatever way. 250 pkts.filter_wpan_src64(vars['BR_1']) \ 251 .filter_AMPLFMA(mpl_seed_id=(vars['BR_1_RLOC'])) \ 252 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 253 .must_not_next() 254 255 # 14. Host sends a ping packet to the multicast address, MA1g. 256 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 257 .filter_ipv6_dst(MA1g) \ 258 .filter_ping_request() \ 259 .must_next() 260 261 # 15. BR_2 does not forward the ping packet with multicast address MA1g, 262 # to the Thread Network in whatever way. 263 pkts.filter_wpan_src64(vars['BR_2']) \ 264 .filter_AMPLFMA(mpl_seed_id=(vars['BR_2_RLOC'])) \ 265 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 266 .must_not_next() 267 268 # 16. BR_1 does not forward the ping packet with multicast address MA1g, 269 # to its Thread Network in whatever way. 270 pkts.filter_wpan_src64(vars['BR_1']) \ 271 .filter_AMPLFMA(mpl_seed_id=(vars['BR_1_RLOC'])) \ 272 .filter_ping_request(identifier=_pkt.icmpv6.echo.identifier) \ 273 .must_not_next() 274 275 # 17. Host sends a ping packet to the BR_2's global unicast address, Gg. 276 _pkt = pkts.filter_eth_src(vars['Host_ETH']) \ 277 .filter_ipv6_dst(vars['BR_2_BGUA']) \ 278 .filter_ping_request() \ 279 .must_next() 280 281 # 18. BR_2 receives and provides the ping response. 282 # BR_2 Must send back the ping response to the Host. 283 pkts.filter_eth_src(vars['BR_2_ETH']) \ 284 .filter_ipv6_dst(vars['Host_BGUA']) \ 285 .filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier) \ 286 .must_next() 287 288 289if __name__ == '__main__': 290 unittest.main() 291