• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..--

README.mdD11-Mar-202414.7 KiB374272

build.shD11-Mar-202413.4 KiB295243

openthread-core-toranj-config-posix.hD11-Mar-20243.4 KiB9815

openthread-core-toranj-config-simulation.hD11-Mar-20242.7 KiB7111

openthread-core-toranj-config.hD11-Mar-202414.9 KiB53864

start.shD11-Mar-20246 KiB187126

test-001-get-set.pyD11-Mar-20247.4 KiB194122

test-002-form.pyD11-Mar-20245.3 KiB14268

test-003-join.pyD11-Mar-20244.7 KiB10744

test-004-scan.pyD11-Mar-20243.7 KiB10332

test-005-discover-scan.pyD11-Mar-20244.7 KiB13049

test-006-traffic-router-end-device.pyD11-Mar-20244.3 KiB10937

test-007-traffic-router-sleepy.pyD11-Mar-20244.6 KiB11440

test-008-permit-join.pyD11-Mar-20243.3 KiB8424

test-009-insecure-traffic-join.pyD11-Mar-20244.9 KiB12748

test-010-on-mesh-prefix-config-gateway.pyD11-Mar-20248.1 KiB282160

test-011-child-table.pyD11-Mar-20243.8 KiB9834

test-012-multi-hop-traffic.pyD11-Mar-20246.9 KiB19174

test-013-off-mesh-route-traffic.pyD11-Mar-20246.6 KiB18869

test-014-ip6-address-add.pyD11-Mar-20247.2 KiB18870

test-015-same-prefix-on-multiple-nodes.pyD11-Mar-20245.4 KiB14854

test-016-neighbor-table.pyD11-Mar-20245.6 KiB15066

test-017-parent-reset-child-recovery.pyD11-Mar-20246.1 KiB16841

test-018-child-supervision.pyD11-Mar-20246.8 KiB17954

test-019-inform-previous-parent.pyD11-Mar-20246.2 KiB16642

test-020-router-table.pyD11-Mar-20247.3 KiB204103

test-021-address-cache-table.pyD11-Mar-202413.3 KiB398173

test-022-multicast-ip6-address.pyD11-Mar-20245.1 KiB13946

test-023-multicast-traffic.pyD11-Mar-20248.6 KiB262116

test-024-partition-merge.pyD11-Mar-20246.5 KiB20688

test-025-network-data-timeout.pyD11-Mar-20248.2 KiB256145

test-026-slaac-address-wpantund.pyD11-Mar-20249.3 KiB264126

test-027-child-mode-change.pyD11-Mar-20246.5 KiB16468

test-028-router-leader-reset-recovery.pyD11-Mar-20244.9 KiB14155

test-029-data-poll-interval.pyD11-Mar-20245.2 KiB13044

test-030-slaac-address-ncp.pyD11-Mar-202413.6 KiB337151

test-031-meshcop-joiner-commissioner.pyD11-Mar-20243.4 KiB9524

test-032-child-attach-with-multiple-ip-addresses.pyD11-Mar-20246.9 KiB20776

test-033-mesh-local-prefix-change.pyD11-Mar-20243.8 KiB10638

test-034-poor-link-parent-child-attach.pyD11-Mar-20243.2 KiB7717

test-035-child-timeout-large-data-poll.pyD11-Mar-20243.4 KiB8421

test-036-wpantund-host-route-management.pyD11-Mar-20249.6 KiB270101

test-037-wpantund-auto-add-route-for-on-mesh-prefix.pyD11-Mar-20247.8 KiB21791

test-038-clear-address-cache-for-sed.pyD11-Mar-20247 KiB20879

test-039-address-cache-table-snoop.pyD11-Mar-202414.8 KiB384191

test-040-network-data-stable-full.pyD11-Mar-202410.3 KiB306161

test-041-lowpan-fragmentation.pyD11-Mar-20243.5 KiB8825

test-042-meshcop-joiner-discerner.pyD11-Mar-20246.1 KiB16965

test-043-meshcop-joiner-router.pyD11-Mar-20243.8 KiB11030

test-100-mcu-power-state.pyD11-Mar-20249.7 KiB23999

test-600-channel-manager-properties.pyD11-Mar-20245.4 KiB12449

test-601-channel-manager-channel-change.pyD11-Mar-20245.6 KiB15070

test-602-channel-manager-channel-select.pyD11-Mar-20247.2 KiB16964

test-603-channel-manager-announce-recovery.pyD11-Mar-20244.5 KiB12043

test-700-multi-radio-join.pyD11-Mar-20245.2 KiB13247

test-701-multi-radio-probe.pyD11-Mar-20248.4 KiB20695

test-702-multi-radio-discovery-by-rx.pyD11-Mar-20248.5 KiB20795

test-703-multi-radio-mesh-header-msg.pyD11-Mar-20245.5 KiB14350

test-704-multi-radio-scan.pyD11-Mar-20244.7 KiB11643

test-705-multi-radio-discover-scan.pyD11-Mar-20244.7 KiB11643

test-nnn-template.pyD11-Mar-20242.7 KiB647

wpan.pyD11-Mar-202460.9 KiB1,5941,066

README.md

1# `toranj` test framework
2
3`toranj` is a test framework for OpenThread and `wpantund`.
4
5- It enables testing of combined behavior of OpenThread (in NCP mode), spinel interface, and `wpantund` driver on linux.
6- It can be used to simulate multiple nodes forming complex network topologies.
7- It allows testing of network interactions between many nodes (IPv6 traffic exchanges).
8
9`toranj` is developed in Python. `toranj` runs wpantund natively with OpenThread in NCP mode on POSIX simulation platform. `toranj` tests will run as part of GitHub Actions pull request validation in OpenThread and/or `wpantund` GitHub projects.
10
11## Setup
12
13`toranj` requires `wpantund` to be installed.
14
15- Please follow [`wpantund` installation guide](https://github.com/openthread/wpantund/blob/master/INSTALL.md#wpantund-installation-guide). Note that `toranj` expects `wpantund` installed from latest master branch.
16- Alternative way to install `wpantund` is to use the same commands from git workflow [Simulation](https://github.com/openthread/openthread/blob/4b55284bd20f99a88e8e2c617ba358a0a5547f5d/.github/workflows/simulation.yml#L336-L341) for build target `toranj-test-framework`.
17
18To run all tests, `start` script can be used. This script will build OpenThread with proper configuration options and starts running all test.
19
20```bash
21    cd tests/toranj/    # from OpenThread repo root
22    ./start.sh
23```
24
25Each test-case has its own script following naming model `test-nnn-name.py` (e.g., `test-001-get-set.py`).
26
27To run a specific test
28
29```bash
30    sudo python test-001-get-set.py
31```
32
33## `toranj` Components
34
35`wpan` python module defines the `toranj` test components.
36
37### `wpan.Node()` Class
38
39`wpan.Node()` class creates a Thread node instance. It creates a sub-process to run `wpantund` and OpenThread, and provides methods to control the node.
40
41```python
42>>> import wpan
43>>> node1 = wpan.Node()
44>>> node1
45Node (index=1, interface_name=wpan1)
46>>> node2 = wpan.Node()
47>>> node2
48Node (index=2, interface_name=wpan2)
49```
50
51Note: You may need to run as `sudo` to allow `wpantund` to create tunnel interface (i.e., use `sudo python`).
52
53### `wpan.Node` methods providing `wpanctl` commands
54
55`wpan.Node()` provides methods matching all `wpanctl` commands.
56
57- Get the value of a `wpantund` property, set the value, or add/remove value to/from a list based property:
58
59```python
60    node.get(prop_name)
61    node.set(prop_name, value, binary_data=False)
62    node.add(prop_name, value, binary_data=False)
63    node.remove(prop_name, value, binary_data=False)
64```
65
66Example:
67
68```python
69>>> node.get(wpan.WPAN_NAME)
70'"test-network"'
71>>> node.set(wpan.WPAN_NAME, 'my-network')
72>>> node.get(wpan.WPAN_NAME)
73'"my-network"'
74>>> node.set(wpan.WPAN_KEY, '65F2C35C7B543BAC1F3E26BB9F866C1D', binary_data=True)
75>>> node.get(wpan.WPAN_KEY)
76'[65F2C35C7B543BAC1F3E26BB9F866C1D]'
77```
78
79- Common network operations:
80
81```python
82    node.reset()            # Reset the NCP
83    node.status()           # Get current status
84    node.leave()            # Leave the current network, clear all persistent data
85
86    # Form a network in given channel (if none given use a random one)
87    node.form(name, channel=None)
88
89    # Join a network with given info.
90    # node_type can be JOIN_TYPE_ROUTER, JOIN_TYPE_END_DEVICE, JOIN_TYPE_SLEEPY_END_DEVICE
91    node.join(name, channel=None, node_type=None, panid=None, xpanid=None)
92```
93
94Example:
95
96```python
97>>> result = node.status()
98>>> print result
99wpan1 => [
100    "NCP:State" => "offline"
101    "Daemon:Enabled" => true
102    "NCP:Version" => "OPENTHREAD/20170716-00460-ga438cef0c-dirty; NONE; Feb 12 2018 11:47:01"
103    "Daemon:Version" => "0.08.00d (0.07.01-191-g63265f7; Feb  2 2018 18:05:47)"
104    "Config:NCP:DriverName" => "spinel"
105    "NCP:HardwareAddress" => [18B4300000000001]
106]
107>>>
108>>> node.form("test-network", channel=12)
109'Forming WPAN "test-network" as node type "router"\nSuccessfully formed!'
110>>>
111>>> print node.status()
112wpan1 => [
113    "NCP:State" => "associated"
114    "Daemon:Enabled" => true
115    "NCP:Version" => "OPENTHREAD/20170716-00460-ga438cef0c-dirty; NONE; Feb 12 2018 11:47:01"
116    "Daemon:Version" => "0.08.00d (0.07.01-191-g63265f7; Feb  2 2018 18:05:47)"
117    "Config:NCP:DriverName" => "spinel"
118    "NCP:HardwareAddress" => [18B4300000000001]
119    "NCP:Channel" => 12
120    "Network:NodeType" => "leader"
121    "Network:Name" => "test-network"
122    "Network:XPANID" => 0xA438CF5973FD86B2
123    "Network:PANID" => 0x9D81
124    "IPv6:MeshLocalAddress" => "fda4:38cf:5973:0:b899:3436:15c6:941d"
125    "IPv6:MeshLocalPrefix" => "fda4:38cf:5973::/64"
126    "com.nestlabs.internal:Network:AllowingJoin" => false
127]
128```
129
130- Scan:
131
132```python
133    node.active_scan(channel=None)
134    node.energy_scan(channel=None)
135    node.discover_scan(channel=None, joiner_only=False, enable_filtering=False, panid_filter=None)
136    node.permit_join(duration_sec=None, port=None, udp=True, tcp=True)
137```
138
139- On-mesh prefixes and off-mesh routes:
140
141```python
142    node.config_gateway(prefix, default_route=False)
143    node.add_route(route_prefix, prefix_len_in_bytes=None, priority=None)
144    node.remove_route(route_prefix, prefix_len_in_bytes=None, priority=None)
145```
146
147A direct `wpanctl` command can be issued using `node.wpanctl(command)` with a given `command` string.
148
149`wpan` module provides variables for different `wpantund` properties. Some commonly used are:
150
151- Network/NCP properties: WPAN_STATE, WPAN_NAME, WPAN_PANID, WPAN_XPANID, WPAN_KEY, WPAN_CHANNEL, WPAN_HW_ADDRESS, WPAN_EXT_ADDRESS, WPAN_POLL_INTERVAL, WPAN_NODE_TYPE, WPAN_ROLE, WPAN_PARTITION_ID
152
153- IPv6 Addresses: WPAN_IP6_LINK_LOCAL_ADDRESS, WPAN_IP6_MESH_LOCAL_ADDRESS, WPAN_IP6_MESH_LOCAL_PREFIX, WPAN_IP6_ALL_ADDRESSES, WPAN_IP6_MULTICAST_ADDRESSES
154
155- Thread Properties: WPAN_THREAD_RLOC16, WPAN_THREAD_ROUTER_ID, WPAN_THREAD_LEADER_ADDRESS, WPAN_THREAD_LEADER_ROUTER_ID, WPAN_THREAD_LEADER_WEIGHT, WPAN_THREAD_LEADER_NETWORK_DATA,
156
157        WPAN_THREAD_CHILD_TABLE, WPAN_THREAD_CHILD_TABLE_ADDRESSES, WPAN_THREAD_NEIGHBOR_TABLE,
158        WPAN_THREAD_ROUTER_TABLE
159
160Method `join_node()` can be used by a node to join another node:
161
162```python
163    # `node1` joining `node2`'s network as a router
164    node1.join_node(node2, node_type=JOIN_TYPE_ROUTER)
165```
166
167Method `allowlist_node()` can be used to add a given node to the allowlist of the device and enables allowlisting:
168
169```python
170    # `node2` is added to the allowlist of `node1` and allowlisting is enabled on `node1`
171    node1.allowlist_node(node2)
172```
173
174#### Example (simple 3-node topology)
175
176Script below shows how to create a 3-node network topology with `node1` and `node2` being routers, and `node3` an end-device connected to `node2`:
177
178```python
179>>> import wpan
180>>> node1 = wpan.Node()
181>>> node2 = wpan.Node()
182>>> node3 = wpan.Node()
183
184>>> wpan.Node.init_all_nodes()
185
186>>> node1.form("test-PAN")
187'Forming WPAN "test-PAN" as node type "router"\nSuccessfully formed!'
188
189>>> node1.allowlist_node(node2)
190>>> node2.allowlist_node(node1)
191
192>>> node2.join_node(node1, wpan.JOIN_TYPE_ROUTER)
193'Joining "test-PAN" C474513CB487778D as node type "router"\nSuccessfully Joined!'
194
195>>> node3.allowlist_node(node2)
196>>> node2.allowlist_node(node3)
197
198>>> node3.join_node(node2, wpan.JOIN_TYPE_END_DEVICE)
199'Joining "test-PAN" C474513CB487778D as node type "end-device"\nSuccessfully Joined!'
200
201>>> print node2.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)
202[
203    "EAC1672C3EAB30A4, RLOC16:9401, LQIn:3, AveRssi:-20, LastRssi:-20, Age:30, LinkFC:6, MleFC:0, IsChild:yes, RxOnIdle:yes, FTD:yes, SecDataReq:yes, FullNetData:yes"
204    "A2042C8762576FD5, RLOC16:dc00, LQIn:3, AveRssi:-20, LastRssi:-20, Age:5, LinkFC:21, MleFC:18, IsChild:no, RxOnIdle:yes, FTD:yes, SecDataReq:no, FullNetData:yes"
205]
206>>> print node1.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)
207[
208    "960947C53415DAA1, RLOC16:9400, LQIn:3, AveRssi:-20, LastRssi:-20, Age:18, LinkFC:15, MleFC:11, IsChild:no, RxOnIdle:yes, FTD:yes, SecDataReq:no, FullNetData:yes"
209]
210
211```
212
213### IPv6 Message Exchange
214
215`toranj` allows a test-case to define traffic patterns (IPv6 message exchange) between different nodes. Message exchanges (tx/rx) are prepared and then an async rx/tx operation starts. The success and failure of tx/rx operations can then be verified by the test case.
216
217`wpan.Node` method `prepare_tx()` prepares a UDP6 transmission from a node.
218
219```python
220    node1.prepare_tx(src, dst, data, count)
221```
222
223- `src` and `dst` can be
224
225  - either a string containing an IPv6 address
226  - or a tuple (ipv6 address as string, port). if no port is given, a random port number is used.
227
228- `data` can be
229
230  - either a string containing the message to be sent,
231  - or an int indicating size of the message (a random message with the given length will be generated).
232
233- `count` gives number of times the message will be sent (default is 1).
234
235`prepare_tx` returns a `wpan.AsyncSender` object. The sender object can be used to check success/failure of tx operation.
236
237`wpan.Node` method `prepare_rx()` prepares a node to listen for UDP messages from a sender.
238
239```python
240    node2.prepare_rx(sender)
241```
242
243- `sender` should be an `wpan.AsyncSender` object returned from previous `prepare_tx`.
244- `prepare_rx()` returns a `wpan.AsyncReceiver` object to help test to check success/failure of rx operation.
245
246After all exchanges are prepared, static method `perform_async_tx_rx()` should be used to start all previously prepared rx and tx operations.
247
248```python
249    wpan.Node.perform_async_tx_rx(timeout)
250```
251
252- `timeout` gives amount of time (in seconds) to wait for all operations to finish. (default is 20 seconds)
253
254After `perform_async_tx_rx()` is done, the `AsyncSender` and `AsyncReceiver` objects can check if operations were successful (using property `was_successful`)
255
256#### Example
257
258Sending 10 messages containing `"Hello there!"` from `node1` to `node2` using their mesh-local addresses:
259
260```python
261# `node1` and `node2` are already joined and are part of the same Thread network.
262
263# Get the mesh local addresses
264>>> mladdr1 = node1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]  # remove `"` from start/end of string
265>>> mladdr2 = node2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]
266
267>>> print (mladdr1, mladdr2)
268('fda4:38cf:5973:0:b899:3436:15c6:941d', 'fda4:38cf:5973:0:5836:fa55:7394:6d4b')
269
270# prepare a `sender` and corresponding `recver`
271>>> sender = node1.prepare_tx((mladdr1, 1234), (mladdr2, 2345), "Hello there!", 10)
272>>> recver = node2.prepare_rx(sender)
273
274# perform async message transfer
275>>> wpan.Node.perform_async_tx_rx()
276
277# check status of `sender` and `recver`
278>>> sender.was_successful
279True
280>>> recver.was_successful
281True
282
283# `sender` or `recver` can provide info about the exchange
284
285>>> sender.src_addr
286'fda4:38cf:5973:0:b899:3436:15c6:941d'
287>>> sender.src_port
2881234
289>>> sender.dst_addr
290'fda4:38cf:5973:0:5836:fa55:7394:6d4b'
291>>> sender.dst_port
2922345
293>>> sender.msg
294'Hello there!'
295>>> sender.count
29610
297
298# get all received msg by `recver` as list of tuples `(msg, (src_address, src_port))`
299>>> recver.all_rx_msg
300[('Hello there!', ('fda4:38cf:5973:0:b899:3436:15c6:941d', 1234)), ...  ]
301```
302
303### Logs and Verbose mode
304
305Every `wpan.Node()` instance will save its corresponding `wpantund` logs. By default the logs are saved in a file `wpantun-log<node_index>.log`. By setting `wpan.Node__TUND_LOG_TO_FILE` to `False` the logs are written to `stdout` as the test-cases are executed.
306
307When `start.sh` script is used to run all test-cases, if any test fails, to help with debugging of the issue, the last 30 lines of `wpantund` logs of every node involved in the test-case is dumped to `stdout`.
308
309A `wpan.Node()` instance can also provide additional logs and info as the test-cases are run (verbose mode). It can be enabled for a node instance when it is created:
310
311```python
312    node = wpan.Node(verbose=True)     # `node` instance will provide extra logs.
313```
314
315Alternatively, `wpan.Node._VERBOSE` settings can be changed to enable verbose logging for all nodes. The default value of `wpan.Node._VERBOSE` is determined from environment variable `TORANJ_VERBOSE` (verbose mode is enabled when env variable is set to any of `1`, `True`, `Yes`, `Y`, `On` (case-insensitive)), otherwise it is disabled. When `TORANJ_VERBOSE` is enabled, the OpenThread logging is also enabled (and collected in `wpantund-log<node_index>.log`files) on all nodes.
316
317Here is example of small test script and its corresponding log output with `verbose` mode enabled:
318
319```python
320node1 = wpan.Node(verbose=True)
321node2 = wpan.Node(verbose=True)
322
323wpan.Node.init_all_nodes()
324
325node1.form("toranj-net")
326node2.active_scan()
327
328node2.join_node(node1)
329verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
330
331lladdr1 = node1.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
332lladdr2 = node2.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
333
334sender = node1.prepare_tx(lladdr1, lladdr2, 20)
335recver = node2.prepare_rx(sender)
336
337wpan.Node.perform_async_tx_rx()
338
339```
340
341```
342$ Node1.__init__() cmd: /usr/local/sbin/wpantund -o Config:NCP:SocketPath "system:../../examples/apps/ncp/ot-ncp-ftd 1" -o Config:TUN:InterfaceName wpan1 -o Config:NCP:DriverName spinel -o Daemon:SyslogMask "all -debug"
343$ Node2.__init__() cmd: /usr/local/sbin/wpantund -o Config:NCP:SocketPath "system:../../examples/apps/ncp/ot-ncp-ftd 2" -o Config:TUN:InterfaceName wpan2 -o Config:NCP:DriverName spinel -o Daemon:SyslogMask "all -debug"
344$ Node1.wpanctl('leave') -> 'Leaving current WPAN. . .'
345$ Node2.wpanctl('leave') -> 'Leaving current WPAN. . .'
346$ Node1.wpanctl('form "toranj-net"'):
347     Forming WPAN "toranj-net" as node type "router"
348     Successfully formed!
349$ Node2.wpanctl('scan'):
350        | Joinable | NetworkName        | PAN ID | Ch | XPanID           | HWAddr           | RSSI
351     ---+----------+--------------------+--------+----+------------------+------------------+------
352      1 |       NO | "toranj-net"       | 0x9DEB | 16 | 8CC6CFC810F23E1B | BEECDAF3439DC931 |  -20
353$ Node1.wpanctl('get -v NCP:State') -> '"associated"'
354$ Node1.wpanctl('get -v Network:Name') -> '"toranj-net"'
355$ Node1.wpanctl('get -v Network:PANID') -> '0x9DEB'
356$ Node1.wpanctl('get -v Network:XPANID') -> '0x8CC6CFC810F23E1B'
357$ Node1.wpanctl('get -v Network:Key') -> '[BA2733A5D81EAB8FFB3C9A7383CB6045]'
358$ Node1.wpanctl('get -v NCP:Channel') -> '16'
359$ Node2.wpanctl('set Network:Key -d -v BA2733A5D81EAB8FFB3C9A7383CB6045') -> ''
360$ Node2.wpanctl('join "toranj-net" -c 16 -T r -p 0x9DEB -x 0x8CC6CFC810F23E1B'):
361     Joining "toranj-net" 8CC6CFC810F23E1B as node type "router"
362     Successfully Joined!
363$ Node2.wpanctl('get -v NCP:State') -> '"associated"'
364$ Node1.wpanctl('get -v IPv6:LinkLocalAddress') -> '"fe80::bcec:daf3:439d:c931"'
365$ Node2.wpanctl('get -v IPv6:LinkLocalAddress') -> '"fe80::ec08:f348:646f:d37d"'
366- Node1 sent 20 bytes (":YeQuNKjuOtd%H#ipM7P") to [fe80::ec08:f348:646f:d37d]:404 from [fe80::bcec:daf3:439d:c931]:12557
367- Node2 received 20 bytes (":YeQuNKjuOtd%H#ipM7P") on port 404 from [fe80::bcec:daf3:439d:c931]:12557
368
369```
370
371---
372
373What does `"toranj"` mean? it's the name of a common symmetric weaving [pattern](https://en.wikipedia.org/wiki/Persian_carpet#/media/File:Toranj_-_special_circular_design_of_Iranian_carpets.JPG) used in Persian carpets.
374