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# This test verifies that PBBR sets DUA routes correctly.
30#
31import ipaddress
32import unittest
33
34import config
35import thread_cert
36from pktverify.consts import ADDR_QRY_URI, BACKBONE_QUERY_URI, BACKBONE_ANSWER_URI, ADDR_NTF_URI
37from pktverify.packet_verifier import PacketVerifier
38
39# Use two channels
40CH1 = 11
41CH2 = 22
42
43PBBR = 1
44ROUTER1 = 2
45MED1 = 3
46HOST = 4
47PBBR2 = 5
48MED2 = 6
49
50REREG_DELAY = 5  # Seconds
51MLR_TIMEOUT = 300  # Seconds
52WAIT_REDUNDANCE = 3
53
54
55class TestDuaRoutingMed(thread_cert.TestCase):
56    USE_MESSAGE_FACTORY = False
57
58    # Topology:
59    #    ------(eth)----------------------
60    #       |        |       |
61    #      PBBR     HOST   PBBR2
62    #     / CH1\            | CH2
63    #   MED1  ROUTER1      MED2
64    #
65    # PBBR2 is in the secondary channel
66    #
67    TOPOLOGY = {
68        PBBR: {
69            'name': 'PBBR',
70            'allowlist': [MED1, ROUTER1],
71            'is_otbr': True,
72            'version': '1.2',
73            'channel': CH1,
74        },
75        ROUTER1: {
76            'name': 'ROUTER1',
77            'allowlist': [PBBR],
78            'version': '1.2',
79            'channel': CH1,
80        },
81        MED1: {
82            'name': 'MED1',
83            'allowlist': [PBBR],
84            'version': '1.2',
85            'channel': CH1,
86            'mode': 'rn',
87        },
88        HOST: {
89            'name': 'HOST',
90            'is_host': True
91        },
92        PBBR2: {
93            'name': 'PBBR2',
94            'is_otbr': True,
95            'version': '1.2',
96            'channel': CH2,
97        },
98        MED2: {
99            'name': 'MED2',
100            'version': '1.2',
101            'channel': CH2,
102            'mode': 'rn',
103        },
104    }
105
106    def _bootstrap(self):
107        # Bring up HOST
108        self.nodes[HOST].start()
109
110        # Bring up PBBR
111        self.nodes[PBBR].start()
112        self.simulator.go(config.LEADER_STARTUP_DELAY)
113        self.assertEqual('leader', self.nodes[PBBR].get_state())
114        self.wait_node_state(PBBR, 'leader', 10)
115
116        self.nodes[PBBR].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT)
117        self.nodes[PBBR].enable_backbone_router()
118        self.nodes[PBBR].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD')
119        self.simulator.go(5)
120        self.assertTrue(self.nodes[PBBR].is_primary_backbone_router)
121        self.assertIsNotNone(self.nodes[PBBR].get_ip6_address(config.ADDRESS_TYPE.DUA))
122
123        # Bring up ROUTER1
124        self.nodes[ROUTER1].start()
125        self.simulator.go(5)
126        self.assertEqual('router', self.nodes[ROUTER1].get_state())
127        self.assertIsNotNone(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA))
128
129        # Bring up MED1
130        self.nodes[MED1].start()
131        self.simulator.go(5)
132        self.assertEqual('child', self.nodes[MED1].get_state())
133        self.assertIsNotNone(self.nodes[MED1].get_ip6_address(config.ADDRESS_TYPE.DUA))
134
135        # Bring up PBBR2
136        self.nodes[PBBR2].start()
137        self.simulator.go(config.LEADER_STARTUP_DELAY)
138        self.assertEqual('leader', self.nodes[PBBR2].get_state())
139        self.wait_node_state(PBBR2, 'leader', 10)
140
141        self.nodes[PBBR2].set_backbone_router(reg_delay=REREG_DELAY, mlr_timeout=MLR_TIMEOUT)
142        self.nodes[PBBR2].enable_backbone_router()
143        self.nodes[PBBR2].set_domain_prefix(config.DOMAIN_PREFIX, 'prosD')
144        self.simulator.go(5)
145        self.assertTrue(self.nodes[PBBR2].is_primary_backbone_router)
146        self.assertIsNotNone(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA))
147
148        # Bring up MED2
149        self.nodes[MED2].start()
150        self.simulator.go(5)
151        self.assertEqual('child', self.nodes[MED2].get_state())
152        self.assertIsNotNone(self.nodes[MED2].get_ip6_address(config.ADDRESS_TYPE.DUA))
153
154    def test(self):
155        self._bootstrap()
156
157        self.collect_ipaddrs()
158        self.collect_rloc16s()
159
160        self.simulator.go(10)
161
162        # PBBR2 pings MED2
163        self.assertTrue(self.nodes[PBBR2].ping(self.nodes[MED2].get_ip6_address(config.ADDRESS_TYPE.DUA)))
164        self.simulator.go(WAIT_REDUNDANCE)
165
166        # MED2 pings PBBR2
167        self.assertTrue(self.nodes[MED2].ping(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA)))
168        self.simulator.go(WAIT_REDUNDANCE)
169
170        # PBBR pings PBBR2
171        self.nodes[PBBR].ping(self.nodes[PBBR2].get_ip6_address(config.ADDRESS_TYPE.DUA))
172        self.simulator.go(WAIT_REDUNDANCE)
173
174        # MED1 pings ROUTER1 should succeed
175        self.assertTrue(self.nodes[MED1].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.DUA)))
176        self.simulator.go(WAIT_REDUNDANCE)
177
178        # MED1 pings MED2 which should succeed
179        MED2_DUA = self.nodes[MED2].get_ip6_address(config.ADDRESS_TYPE.DUA)
180        self.assertTrue(self.nodes[MED1].ping(MED2_DUA))
181        self.simulator.go(WAIT_REDUNDANCE)
182
183    def _get_mliid(self, nodeid: int) -> str:
184        mleid = self.nodes[nodeid].get_ip6_address(config.ADDRESS_TYPE.ML_EID)
185        mliid = ipaddress.IPv6Address(mleid).packed[-8:]
186        return ''.join(['%02x' % b for b in mliid])
187
188    def verify(self, pv: PacketVerifier):
189        pkts = pv.pkts
190        pv.add_common_vars()
191        pv.summary.show()
192
193        PBBR = pv.vars['PBBR']
194        ROUTER1 = pv.vars['ROUTER1']
195        PBBR2 = pv.vars['PBBR2']
196        MED1 = pv.vars['MED1']
197        MED2 = pv.vars['MED2']
198        MED1_DUA = pv.vars['MED1_DUA']
199        ROUTER1_DUA = pv.vars['ROUTER1_DUA']
200        PBBR_DUA = pv.vars['PBBR_DUA']
201        PBBR2_DUA = pv.vars['PBBR2_DUA']
202        MED2_DUA = pv.vars['MED2_DUA']
203        PBBR_ETH = pv.vars['PBBR_ETH']
204        PBBR2_ETH = pv.vars['PBBR2_ETH']
205
206        # PBBR should never send Address Query for MED1 (Child)
207        pkts.filter_wpan_src64(PBBR).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \
208            .filter('thread_address.tlv.target_eid == {eid}', eid=MED1_DUA) \
209            .must_not_next()
210
211        # PBBR2 should never send Address Query for MED2 (Child)
212        pkts.filter_wpan_src64(PBBR2).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \
213            .filter('thread_address.tlv.target_eid == {eid}', eid=MED2_DUA) \
214            .must_not_next()
215
216        # MEDs should never send Address Query
217        pkts.filter_wpan_src64(MED1).filter_RLARMA().filter_coap_request(ADDR_QRY_URI).must_not_next()
218        pkts.filter_wpan_src64(MED2).filter_RLARMA().filter_coap_request(ADDR_QRY_URI).must_not_next()
219
220        # PBBR pings PBBR2 should succeed
221        ping_id = pkts.filter_ipv6_src_dst(
222            PBBR_DUA, PBBR2_DUA).filter_eth_src(PBBR_ETH).filter_ping_request().must_next().icmpv6.echo.identifier
223        pkts.filter_ipv6_src_dst(PBBR2_DUA,
224                                 PBBR_DUA).filter_eth_src(PBBR2_ETH).filter_ping_reply(identifier=ping_id).must_next()
225
226        # MED1 pings ROUTER1
227        ping_request_pkts = pkts.filter_ipv6_src_dst(MED1_DUA, ROUTER1_DUA)
228        # MED1 sends the Ping Request
229        ping_pkt = ping_request_pkts.filter_wpan_src64(MED1).filter_ping_request().must_next()
230        ping_id = ping_pkt.icmpv6.echo.identifier
231        # PBBR sends Address Query for ROUTER1_DUA
232        pkts.filter_wpan_src64(PBBR).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \
233            .filter('thread_address.tlv.target_eid == {eid}', eid=ROUTER1_DUA) \
234            .must_next()
235        # PBBR sends Backbone Query for ROUTER1_DUA
236        pkts.filter_eth_src(PBBR_ETH).filter_coap_request(BACKBONE_QUERY_URI) \
237            .filter('thread_bl.tlv.target_eid == {eid}', eid=ROUTER1_DUA) \
238            .must_next()
239        # ROUTER1 sends Address Answer for ROUTER1_DUA
240        pkts.filter_wpan_src64(ROUTER1).filter_coap_request(ADDR_NTF_URI, confirmable=True) \
241            .filter('thread_address.tlv.target_eid == {eid}', eid=ROUTER1_DUA) \
242            .must_next()
243        # PBBR forwards Ping Request to ROUTER1, decreasing TTL by 1
244        ping_request_pkts.filter_wpan_src64(PBBR).filter_ping_request(identifier=ping_id).must_next().must_verify(
245            'ipv6.hlim == {hlim}', hlim=ping_pkt.ipv6.hlim - 1)
246
247        # MED1 pings MED2
248        # Verify Ping Request: MED1 -> PBBR -> PBBR2 -> MED2
249        ping_request_pkts = pkts.filter_ipv6_src_dst(MED1_DUA, MED2_DUA)
250        # MED1 sends the Ping Request
251        ping_pkt = ping_request_pkts.filter_wpan_src64(MED1).filter_ping_request().must_next()
252        ping_id = ping_pkt.icmpv6.echo.identifier
253        # PBBR sends Address Query for MED2_DUA
254        pkts.filter_wpan_src64(PBBR).filter_RLARMA().filter_coap_request(ADDR_QRY_URI) \
255            .filter('thread_address.tlv.target_eid == {eid}', eid=MED2_DUA) \
256            .must_next()
257        # PBBR sends Backbone Query for MED2_DUA
258        pkts.filter_eth_src(PBBR_ETH).filter_coap_request(BACKBONE_QUERY_URI) \
259            .filter('thread_bl.tlv.target_eid == {eid}', eid=MED2_DUA) \
260            .must_next()
261        # PBBR2 sends Backbone Answer for MED2_DUA
262        pkts.filter_eth_src(PBBR2_ETH).filter_coap_request(BACKBONE_ANSWER_URI, confirmable=True) \
263            .filter('thread_bl.tlv.target_eid == {eid}', eid=MED2_DUA) \
264            .must_next()
265        # PBBR forwards Ping Request to Backbone link, decreasing TTL by 1
266        ping_request_pkts.filter_eth_src(PBBR_ETH).filter_ping_request(identifier=ping_id).must_next().must_verify(
267            'ipv6.hlim == {hlim}', hlim=ping_pkt.ipv6.hlim - 1)
268        ping_request_pkts.filter_wpan_src64(PBBR2).filter_ping_request(identifier=ping_id).must_next()
269        # Verify Ping Reply: MED2 -> PBBR2 -> PBBR -> MED1
270        ping_reply_pkts = pkts.filter_ipv6_src_dst(MED2_DUA, MED1_DUA).filter_ping_reply(identifier=ping_id)
271        ping_reply_pkts.filter_eth_src(PBBR2_ETH).must_next()
272
273
274if __name__ == '__main__':
275    unittest.main()
276