tree: 0ceb19db50c579a48c1694ebb011249e084e8104 [path history] [tgz]
  1. build.sh
  2. openthread-core-toranj-config-posix.h
  3. openthread-core-toranj-config-simulation.h
  4. openthread-core-toranj-config.h
  5. README.md
  6. start.sh
  7. test-001-get-set.py
  8. test-002-form.py
  9. test-003-join.py
  10. test-004-scan.py
  11. test-005-discover-scan.py
  12. test-006-traffic-router-end-device.py
  13. test-007-traffic-router-sleepy.py
  14. test-008-permit-join.py
  15. test-009-insecure-traffic-join.py
  16. test-010-on-mesh-prefix-config-gateway.py
  17. test-011-child-table.py
  18. test-012-multi-hop-traffic.py
  19. test-013-off-mesh-route-traffic.py
  20. test-014-ip6-address-add.py
  21. test-015-same-prefix-on-multiple-nodes.py
  22. test-016-neighbor-table.py
  23. test-017-parent-reset-child-recovery.py
  24. test-018-child-supervision.py
  25. test-019-inform-previous-parent.py
  26. test-020-router-table.py
  27. test-021-address-cache-table.py
  28. test-022-multicast-ip6-address.py
  29. test-023-multicast-traffic.py
  30. test-024-partition-merge.py
  31. test-025-network-data-timeout.py
  32. test-026-slaac-address-wpantund.py
  33. test-027-child-mode-change.py
  34. test-028-router-leader-reset-recovery.py
  35. test-029-data-poll-interval.py
  36. test-030-slaac-address-ncp.py
  37. test-031-meshcop-joiner-commissioner.py
  38. test-032-child-attach-with-multiple-ip-addresses.py
  39. test-033-mesh-local-prefix-change.py
  40. test-034-poor-link-parent-child-attach.py
  41. test-035-child-timeout-large-data-poll.py
  42. test-036-wpantund-host-route-management.py
  43. test-037-wpantund-auto-add-route-for-on-mesh-prefix.py
  44. test-038-clear-address-cache-for-sed.py
  45. test-039-address-cache-table-snoop.py
  46. test-040-network-data-stable-full.py
  47. test-041-lowpan-fragmentation.py
  48. test-042-meshcop-joiner-discerner.py
  49. test-100-mcu-power-state.py
  50. test-600-channel-manager-properties.py
  51. test-601-channel-manager-channel-change.py
  52. test-602-channel-manager-channel-select.py
  53. test-603-channel-manager-announce-recovery.py
  54. test-nnn-template.py
  55. wpan.py
tests/toranj/README.md

toranj test framework

toranj is a test framework for OpenThread and wpantund.

  • It enables testing of combined behavior of OpenThread (in NCP mode), spinel interface, and wpantund driver on linux.
  • It can be used to simulate multiple nodes forming complex network topologies.
  • It allows testing of network interactions between many nodes (IPv6 traffic exchanges).

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.

Setup

toranj requires wpantund to be installed.

  • Please follow wpantund installation guide. Note that toranj expects wpantund installed from latest master branch.
  • Alternative way to install wpantund is to use the same commands from git workflow Simulation for build target toranj-test-framework.

To run all tests, start script can be used. This script will build OpenThread with proper configuration options and starts running all test.

    cd tests/toranj/    # from OpenThread repo root
    ./start.sh

Each test-case has its own script following naming model test-nnn-name.py (e.g., test-001-get-set.py).

To run a specific test

    sudo python test-001-get-set.py

toranj Components

wpan python module defines the toranj test components.

wpan.Node() Class

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.

>>> import wpan
>>> node1 = wpan.Node()
>>> node1
Node (index=1, interface_name=wpan1)
>>> node2 = wpan.Node()
>>> node2
Node (index=2, interface_name=wpan2)

Note: You may need to run as sudo to allow wpantund to create tunnel interface (i.e., use sudo python).

wpan.Node methods providing wpanctl commands

wpan.Node() provides methods matching all wpanctl commands.

  • Get the value of a wpantund property, set the value, or add/remove value to/from a list based property:
    node.get(prop_name)
    node.set(prop_name, value, binary_data=False)
    node.add(prop_name, value, binary_data=False)
    node.remove(prop_name, value, binary_data=False)

Example:

>>> node.get(wpan.WPAN_NAME)
'"test-network"'
>>> node.set(wpan.WPAN_NAME, 'my-network')
>>> node.get(wpan.WPAN_NAME)
'"my-network"'
>>> node.set(wpan.WPAN_KEY, '65F2C35C7B543BAC1F3E26BB9F866C1D', binary_data=True)
>>> node.get(wpan.WPAN_KEY)
'[65F2C35C7B543BAC1F3E26BB9F866C1D]'
  • Common network operations:
    node.reset()            # Reset the NCP
    node.status()           # Get current status
    node.leave()            # Leave the current network, clear all persistent data

    # Form a network in given channel (if none given use a random one)
    node.form(name, channel=None)

    # Join a network with given info.
    # node_type can be JOIN_TYPE_ROUTER, JOIN_TYPE_END_DEVICE, JOIN_TYPE_SLEEPY_END_DEVICE
    node.join(name, channel=None, node_type=None, panid=None, xpanid=None)

Example:

>>> result = node.status()
>>> print result
wpan1 => [
    "NCP:State" => "offline"
    "Daemon:Enabled" => true
    "NCP:Version" => "OPENTHREAD/20170716-00460-ga438cef0c-dirty; NONE; Feb 12 2018 11:47:01"
    "Daemon:Version" => "0.08.00d (0.07.01-191-g63265f7; Feb  2 2018 18:05:47)"
    "Config:NCP:DriverName" => "spinel"
    "NCP:HardwareAddress" => [18B4300000000001]
]
>>>
>>> node.form("test-network", channel=12)
'Forming WPAN "test-network" as node type "router"\nSuccessfully formed!'
>>>
>>> print node.status()
wpan1 => [
    "NCP:State" => "associated"
    "Daemon:Enabled" => true
    "NCP:Version" => "OPENTHREAD/20170716-00460-ga438cef0c-dirty; NONE; Feb 12 2018 11:47:01"
    "Daemon:Version" => "0.08.00d (0.07.01-191-g63265f7; Feb  2 2018 18:05:47)"
    "Config:NCP:DriverName" => "spinel"
    "NCP:HardwareAddress" => [18B4300000000001]
    "NCP:Channel" => 12
    "Network:NodeType" => "leader"
    "Network:Name" => "test-network"
    "Network:XPANID" => 0xA438CF5973FD86B2
    "Network:PANID" => 0x9D81
    "IPv6:MeshLocalAddress" => "fda4:38cf:5973:0:b899:3436:15c6:941d"
    "IPv6:MeshLocalPrefix" => "fda4:38cf:5973::/64"
    "com.nestlabs.internal:Network:AllowingJoin" => false
]
  • Scan:
    node.active_scan(channel=None)
    node.energy_scan(channel=None)
    node.discover_scan(channel=None, joiner_only=False, enable_filtering=False, panid_filter=None)
    node.permit_join(duration_sec=None, port=None, udp=True, tcp=True)
  • On-mesh prefixes and off-mesh routes:
    node.config_gateway(prefix, default_route=False)
    node.add_route(route_prefix, prefix_len_in_bytes=None, priority=None)
    node.remove_route(route_prefix, prefix_len_in_bytes=None, priority=None)

A direct wpanctl command can be issued using node.wpanctl(command) with a given command string.

wpan module provides variables for different wpantund properties. Some commonly used are:

  • 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

  • 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

  • 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,

      WPAN_THREAD_CHILD_TABLE, WPAN_THREAD_CHILD_TABLE_ADDRESSES, WPAN_THREAD_NEIGHBOR_TABLE,
      WPAN_THREAD_ROUTER_TABLE
    

Method join_node() can be used by a node to join another node:

    # `node1` joining `node2`'s network as a router
    node1.join_node(node2, node_type=JOIN_TYPE_ROUTER)

Method whitelist_node() can be used to add a given node to the whitelist of the device and enables white-listing:

    # `node2` is added to the whitelist of `node1` and white-listing is enabled on `node1`
    node1.whitelist_node(node2)

Example (simple 3-node topology)

Script below shows how to create a 3-node network topology with node1 and node2 being routers, and node3 an end-device connected to node2:

>>> import wpan
>>> node1 = wpan.Node()
>>> node2 = wpan.Node()
>>> node3 = wpan.Node()

>>> wpan.Node.init_all_nodes()

>>> node1.form("test-PAN")
'Forming WPAN "test-PAN" as node type "router"\nSuccessfully formed!'

>>> node1.whitelist_node(node2)
>>> node2.whitelist_node(node1)

>>> node2.join_node(node1, wpan.JOIN_TYPE_ROUTER)
'Joining "test-PAN" C474513CB487778D as node type "router"\nSuccessfully Joined!'

>>> node3.whitelist_node(node2)
>>> node2.whitelist_node(node3)

>>> node3.join_node(node2, wpan.JOIN_TYPE_END_DEVICE)
'Joining "test-PAN" C474513CB487778D as node type "end-device"\nSuccessfully Joined!'

>>> print node2.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)
[
    "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"
    "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"
]
>>> print node1.get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)
[
    "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"
]

IPv6 Message Exchange

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.

wpan.Node method prepare_tx() prepares a UDP6 transmission from a node.

    node1.prepare_tx(src, dst, data, count)
  • src and dst can be

    • either a string containing an IPv6 address
    • or a tuple (ipv6 address as string, port). if no port is given, a random port number is used.
  • data can be

    • either a string containing the message to be sent,
    • or an int indicating size of the message (a random message with the given length will be generated).
  • count gives number of times the message will be sent (default is 1).

prepare_tx returns a wpan.AsyncSender object. The sender object can be used to check success/failure of tx operation.

wpan.Node method prepare_rx() prepares a node to listen for UDP messages from a sender.

    node2.prepare_rx(sender)
  • sender should be an wpan.AsyncSender object returned from previous prepare_tx.
  • prepare_rx() returns a wpan.AsyncReceiver object to help test to check success/failure of rx operation.

After all exchanges are prepared, static method perform_async_tx_rx() should be used to start all previously prepared rx and tx operations.

    wpan.Node.perform_async_tx_rx(timeout)
  • timeout gives amount of time (in seconds) to wait for all operations to finish. (default is 20 seconds)

After perform_async_tx_rx() is done, the AsyncSender and AsyncReceiver objects can check if operations were successful (using property was_successful)

Example

Sending 10 messages containing "Hello there!" from node1 to node2 using their mesh-local addresses:

# `node1` and `node2` are already joined and are part of the same Thread network.

# Get the mesh local addresses
>>> mladdr1 = node1.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]  # remove `"` from start/end of string
>>> mladdr2 = node2.get(wpan.WPAN_IP6_MESH_LOCAL_ADDRESS)[1:-1]

>>> print (mladdr1, mladdr2)
('fda4:38cf:5973:0:b899:3436:15c6:941d', 'fda4:38cf:5973:0:5836:fa55:7394:6d4b')

# prepare a `sender` and corresponding `recver`
>>> sender = node1.prepare_tx((mladdr1, 1234), (mladdr2, 2345), "Hello there!", 10)
>>> recver = node2.prepare_rx(sender)

# perform async message transfer
>>> wpan.Node.perform_async_tx_rx()

# check status of `sender` and `recver`
>>> sender.was_successful
True
>>> recver.was_successful
True

# `sender` or `recver` can provide info about the exchange

>>> sender.src_addr
'fda4:38cf:5973:0:b899:3436:15c6:941d'
>>> sender.src_port
1234
>>> sender.dst_addr
'fda4:38cf:5973:0:5836:fa55:7394:6d4b'
>>> sender.dst_port
2345
>>> sender.msg
'Hello there!'
>>> sender.count
10

# get all received msg by `recver` as list of tuples `(msg, (src_address, src_port))`
>>> recver.all_rx_msg
[('Hello there!', ('fda4:38cf:5973:0:b899:3436:15c6:941d', 1234)), ...  ]

Logs and Verbose mode

Every 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.

When 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.

A 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:

    node = wpan.Node(verbose=True)     # `node` instance will provide extra logs.

Alternatively, 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>.logfiles) on all nodes.

Here is example of small test script and its corresponding log output with verbose mode enabled:

node1 = wpan.Node(verbose=True)
node2 = wpan.Node(verbose=True)

wpan.Node.init_all_nodes()

node1.form("toranj-net")
node2.active_scan()

node2.join_node(node1)
verify(node2.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)

lladdr1 = node1.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]
lladdr2 = node2.get(wpan.WPAN_IP6_LINK_LOCAL_ADDRESS)[1:-1]

sender = node1.prepare_tx(lladdr1, lladdr2, 20)
recver = node2.prepare_rx(sender)

wpan.Node.perform_async_tx_rx()

$ 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"
$ 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"
$ Node1.wpanctl('leave') -> 'Leaving current WPAN. . .'
$ Node2.wpanctl('leave') -> 'Leaving current WPAN. . .'
$ Node1.wpanctl('form "toranj-net"'):
     Forming WPAN "toranj-net" as node type "router"
     Successfully formed!
$ Node2.wpanctl('scan'):
        | Joinable | NetworkName        | PAN ID | Ch | XPanID           | HWAddr           | RSSI
     ---+----------+--------------------+--------+----+------------------+------------------+------
      1 |       NO | "toranj-net"       | 0x9DEB | 16 | 8CC6CFC810F23E1B | BEECDAF3439DC931 |  -20
$ Node1.wpanctl('get -v NCP:State') -> '"associated"'
$ Node1.wpanctl('get -v Network:Name') -> '"toranj-net"'
$ Node1.wpanctl('get -v Network:PANID') -> '0x9DEB'
$ Node1.wpanctl('get -v Network:XPANID') -> '0x8CC6CFC810F23E1B'
$ Node1.wpanctl('get -v Network:Key') -> '[BA2733A5D81EAB8FFB3C9A7383CB6045]'
$ Node1.wpanctl('get -v NCP:Channel') -> '16'
$ Node2.wpanctl('set Network:Key -d -v BA2733A5D81EAB8FFB3C9A7383CB6045') -> ''
$ Node2.wpanctl('join "toranj-net" -c 16 -T r -p 0x9DEB -x 0x8CC6CFC810F23E1B'):
     Joining "toranj-net" 8CC6CFC810F23E1B as node type "router"
     Successfully Joined!
$ Node2.wpanctl('get -v NCP:State') -> '"associated"'
$ Node1.wpanctl('get -v IPv6:LinkLocalAddress') -> '"fe80::bcec:daf3:439d:c931"'
$ Node2.wpanctl('get -v IPv6:LinkLocalAddress') -> '"fe80::ec08:f348:646f:d37d"'
- Node1 sent 20 bytes (":YeQuNKjuOtd%H#ipM7P") to [fe80::ec08:f348:646f:d37d]:404 from [fe80::bcec:daf3:439d:c931]:12557
- Node2 received 20 bytes (":YeQuNKjuOtd%H#ipM7P") on port 404 from [fe80::bcec:daf3:439d:c931]:12557


What does "toranj" mean? it's the name of a common symmetric weaving pattern used in Persian carpets.