xref: /aosp_15_r20/external/openthread/tests/toranj/cli/test-008-multicast-traffic.py (revision cfb92d1480a9e65faed56933e9c12405f45898b4)
1#!/usr/bin/env python3
2#
3#  Copyright (c) 2022, 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
29from cli import verify
30from cli import verify_within
31import cli
32import time
33
34# -----------------------------------------------------------------------------------------------------------------------
35# Test description: Multicast traffic
36#
37# Network topology
38#
39#     r1 ---- r2 ---- r3 ---- r4
40#             |               |
41#             |               |
42#            fed             sed
43#
44# Test covers the following multicast traffic:
45#
46# - r2  =>> link-local all-nodes.   Expected response from [r1, r3, fed].
47# - r3  =>> mesh-local all-nodes.   Expected response from [r1, r2, r4, fed].
48# - r3  =>> link-local all-routers. Expected response from [r2, r4].
49# - r3  =>> mesh-local all-routers. Expected response from all routers.
50# - r1  =>> link-local all-thread.  Expected response from [r1, r2].
51# - fed =>> mesh-local all-thread.  Expected response from all nodes.
52# - r1  =>> mesh-local all-thread (one hop). Expected response from [r2].
53# - r1  =>> mesh-local all-thread (two hops). Expected response from [r2, r3, fed].
54# - r1  =>> mesh-local all-thread (three hops). Expected response from [r2, r3, r4, fed].
55# - r1  =>> mesh-local all-thread (four hops). Expected response from [r2, r3, r4, fed, sed].
56#
57# - r1  =>> realm-local scope mcast addr (on r2 and sed). Expected to receive on [r2, sed].
58# - r2  =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [sed].
59# - r2  =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed].
60# - sed =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [r2].
61# - sed =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed].
62#
63# - r3  =>> site-local mcast addr (on r1, r2, sed). Expected to receive from [r1, r2, sed].
64# - r1  =>> site-local mcast addr (on r1, r2, sed) with `multicast-loop`. Expected to receive from [r1, r2, sed].
65# - r1  =>> site-local mcast addr (on r1, r2, sed) without `multicast-loop`. Expected to receive from [r2, sed].
66# - sed =>> site-local mcast addr (on r1, r2, sed). Expected to receive from [r1, r2].
67
68test_name = __file__[:-3] if __file__.endswith('.py') else __file__
69print('-' * 120)
70print('Starting \'{}\''.format(test_name))
71
72# -----------------------------------------------------------------------------------------------------------------------
73# Creating `cli.Nodes` instances
74
75speedup = 10
76cli.Node.set_time_speedup_factor(speedup)
77
78r1 = cli.Node()
79r2 = cli.Node()
80r3 = cli.Node()
81r4 = cli.Node()
82fed = cli.Node()
83sed = cli.Node()
84
85# -----------------------------------------------------------------------------------------------------------------------
86# Form topology
87
88r1.allowlist_node(r2)
89
90r2.allowlist_node(r1)
91r2.allowlist_node(fed)
92r2.allowlist_node(r3)
93
94fed.allowlist_node(r2)
95
96r3.allowlist_node(r2)
97r3.allowlist_node(r4)
98
99r4.allowlist_node(r3)
100r4.allowlist_node(sed)
101
102sed.allowlist_node(r4)
103
104r1.form("multicast")
105r2.join(r1)
106r3.join(r2)
107r4.join(r3)
108fed.join(r1, cli.JOIN_TYPE_REED)
109
110sed.join(r3, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
111
112sed.set_pollperiod(600)
113
114verify(r1.get_state() == 'leader')
115verify(r2.get_state() == 'router')
116verify(r3.get_state() == 'router')
117verify(r4.get_state() == 'router')
118verify(fed.get_state() == 'child')
119verify(sed.get_state() == 'child')
120
121# -----------------------------------------------------------------------------------------------------------------------
122# Test Implementation
123
124# Wait till first router has either established a link or
125# has a valid "next hop" towards all other routers.
126
127r1_rloc16 = int(r1.get_rloc16(), 16)
128
129
130def check_r1_router_table():
131    table = r1.get_router_table()
132    verify(len(table) == 4)
133    for entry in table:
134        verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
135
136
137verify_within(check_r1_router_table, 160)
138
139# r2  =>> link-local all-nodes. Expected response from [r1, r3, fed].
140
141outputs = r2.cli('ping ff02::1')
142verify(len(outputs) == 4)
143for node in [r1, r3, fed]:
144    ll_addr = node.get_linklocal_ip_addr()
145    verify(any(ll_addr in line for line in outputs))
146
147# r3  =>> mesh-local all-nodes. Expected response from [r1, r2, r4, fed].
148
149outputs = r3.cli('ping ff03::1')
150verify(len(outputs) == 5)
151for node in [r1, r2, r4, fed]:
152    ml_addr = node.get_mleid_ip_addr()
153    verify(any(ml_addr in line for line in outputs))
154
155# r3  =>> link-local all-routers. Expected response from [r2, r4].
156
157outputs = r3.cli('ping ff02::2')
158verify(len(outputs) == 3)
159for node in [r2, r4]:
160    ll_addr = node.get_linklocal_ip_addr()
161    verify(any(ll_addr in line for line in outputs))
162
163# r3  =>> mesh-local all-routers. Expected response from all routers.
164
165outputs = r3.cli('ping ff03::2')
166verify(len(outputs) == 5)
167for node in [r1, r2, r4, fed]:
168    ml_addr = node.get_mleid_ip_addr()
169    verify(any(ml_addr in line for line in outputs))
170
171# r1  =>> link-local all-thread.  Expected response from [r2].
172
173ml_prefix = r1.get_mesh_local_prefix().strip().split('/')[0]
174ll_all_thread_nodes_addr = 'ff32:40:' + ml_prefix + '1'
175outputs = r1.cli('ping', ll_all_thread_nodes_addr)
176verify(len(outputs) == 2)
177for node in [r2]:
178    ll_addr = node.get_linklocal_ip_addr()
179    verify(any(ll_addr in line for line in outputs))
180
181# fed =>> mesh-local all-thread.  Expected response from all nodes.
182
183ml_all_thread_nodes_addr = 'ff33:40:' + ml_prefix + '1'
184outputs = fed.cli('ping', ml_all_thread_nodes_addr)
185verify(len(outputs) == 6)
186for node in [r1, r2, r3, r4, sed]:
187    ml_addr = node.get_mleid_ip_addr()
188    verify(any(ml_addr in line for line in outputs))
189
190# Repeat the same with larger ping msg requiring fragmentation
191
192outputs = fed.cli('ping', ml_all_thread_nodes_addr, 200)
193verify(len(outputs) == 6)
194for node in [r1, r2, r3, r4, sed]:
195    ml_addr = node.get_mleid_ip_addr()
196    verify(any(ml_addr in line for line in outputs))
197
198# r1  =>> mesh-local all-thread (one hop). Expected response from [r2].
199
200hop_limit = 1
201outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
202verify(len(outputs) == 2)
203for node in [r2]:
204    ml_addr = node.get_mleid_ip_addr()
205    verify(any(ml_addr in line for line in outputs))
206
207# r1  =>> mesh-local all-thread (two hops). Expected response from [r2, r3, fed].
208
209hop_limit = 2
210outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
211verify(len(outputs) == 4)
212for node in [r2, r3, fed]:
213    ml_addr = node.get_mleid_ip_addr()
214    verify(any(ml_addr in line for line in outputs))
215
216# r1  =>> mesh-local all-thread (three hops). Expected response from [r2, r3, r4, fed].
217
218hop_limit = 3
219outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
220verify(len(outputs) == 5)
221for node in [r2, r3, r4, fed]:
222    ml_addr = node.get_mleid_ip_addr()
223    verify(any(ml_addr in line for line in outputs))
224
225# r1  =>> mesh-local all-thread (four hops). Expected response from [r2, r3, r4, fed, sed].
226
227hop_limit = 4
228outputs = r1.cli('ping', ml_all_thread_nodes_addr, 10, 1, 0, hop_limit)
229verify(len(outputs) == 6)
230for node in [r2, r3, r4, fed, sed]:
231    ml_addr = node.get_mleid_ip_addr()
232    verify(any(ml_addr in line for line in outputs))
233
234# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
235# Subscribe to a realm-local scope multicast address on r2 and sed
236
237mcast_addr = 'ff03:0:0:0:0:0:0:114'
238r2.add_ip_maddr(mcast_addr)
239sed.add_ip_maddr(mcast_addr)
240time.sleep(1)
241maddrs = r2.get_ip_maddrs()
242verify(any(mcast_addr in maddr for maddr in maddrs))
243maddrs = sed.get_ip_maddrs()
244verify(any(mcast_addr in maddr for maddr in maddrs))
245
246# r1  =>> realm-local scope mcast addr (on r2 and sed). Expected to receive on [r2, sed].
247
248outputs = r1.cli('ping', mcast_addr)
249verify(len(outputs) == 3)
250for node in [r2, sed]:
251    ml_addr = node.get_mleid_ip_addr()
252    verify(any(ml_addr in line for line in outputs))
253
254# r2  =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [sed].
255
256outputs = r2.cli('ping', mcast_addr)
257verify(len(outputs) == 2)
258for node in [sed]:
259    ml_addr = node.get_mleid_ip_addr()
260    verify(any(ml_addr in line for line in outputs))
261
262# r2  =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed].
263
264outputs = r2.cli('ping', '-m', mcast_addr)
265verify(len(outputs) == 3)
266for node in [r2, sed]:
267    ml_addr = node.get_mleid_ip_addr()
268    verify(any(ml_addr in line for line in outputs))
269
270# sed  =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [r2].
271
272outputs = sed.cli('ping', mcast_addr)
273verify(len(outputs) == 2)
274for node in [r2]:
275    ml_addr = node.get_mleid_ip_addr()
276    verify(any(ml_addr in line for line in outputs))
277
278# sed  =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed].
279
280outputs = sed.cli('ping', '-m', mcast_addr)
281verify(len(outputs) == 3)
282for node in [r2, sed]:
283    ml_addr = node.get_mleid_ip_addr()
284    verify(any(ml_addr in line for line in outputs))
285
286# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
287# Subscribe to a larger than realm-local scope (site-local) multicast address on r1, r2, and sed
288
289mcast_addr = 'ff05:0:0:0:0:0:0:abcd'
290r1.add_ip_maddr(mcast_addr)
291r2.add_ip_maddr(mcast_addr)
292sed.add_ip_maddr(mcast_addr)
293time.sleep(1)
294maddrs = r1.get_ip_maddrs()
295verify(any(mcast_addr in maddr for maddr in maddrs))
296maddrs = r2.get_ip_maddrs()
297verify(any(mcast_addr in maddr for maddr in maddrs))
298maddrs = sed.get_ip_maddrs()
299verify(any(mcast_addr in maddr for maddr in maddrs))
300
301# r3  =>> site-local mcast addr (on r1, r2, sed) with `multicast-loop`. Expected to receive from [r1, r2, sed].
302
303outputs = r3.cli('ping', '-m', mcast_addr)
304verify(len(outputs) == 4)
305for node in [r1, r2, sed]:
306    ml_addr = node.get_mleid_ip_addr()
307    verify(any(ml_addr in line for line in outputs))
308
309# r1  =>> site-local mcast addr (on r1, r2, sed) with `multicast-loop`. Expected to receive from [r1, r2, sed].
310
311outputs = r1.cli('ping', '-m', mcast_addr)
312verify(len(outputs) == 4)
313for node in [r1, r2, sed]:
314    ml_addr = node.get_mleid_ip_addr()
315    verify(any(ml_addr in line for line in outputs))
316
317# r1  =>> site-local mcast addr (on r1, r2, sed) without `multicast-loop`. Expected to receive from [r2, sed].
318
319outputs = r1.cli('ping', mcast_addr)
320verify(len(outputs) == 3)
321for node in [r2, sed]:
322    ml_addr = node.get_mleid_ip_addr()
323    verify(any(ml_addr in line for line in outputs))
324
325# sed  =>> site-local mcast addr (on r1, r2, sed) without `multicast-loop`. Expected to receive from [r1, r2].
326
327outputs = sed.cli('ping', mcast_addr)
328verify(len(outputs) >= 3)
329for node in [r1, r2]:
330    ml_addr = node.get_mleid_ip_addr()
331    verify(any(ml_addr in line for line in outputs))
332
333# -----------------------------------------------------------------------------------------------------------------------
334# Test finished
335
336cli.Node.finalize_all_nodes()
337
338print('\'{}\' passed.'.format(test_name))
339