mirror of
https://github.com/espressif/openthread.git
synced 2026-08-17 07:59:51 +00:00
[harness-simulation] add multiple-version support (#8075)
This commit adds multiple-version support, and uses a unified configuration file to specify all the parameters.
This commit is contained in:
@@ -10,33 +10,19 @@ Platform developers should modify the THCI implementation and/or the SI implemen
|
||||
|
||||
## POSIX Environment Setup
|
||||
|
||||
1. Build OpenThread to generate standalone OpenThread simulation `ot-cli-ftd`. For example, to run OpenThread 1.2 test cases, run the following command in the top directory of OpenThread:
|
||||
1. Open the JSON format configuration file `tools/harness-simulation/posix/simulation.conf`:
|
||||
|
||||
```bash
|
||||
$ CFLAGS='-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8' \
|
||||
CXXFLAGS='-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8' \
|
||||
script/cmake-build simulation \
|
||||
-DOT_THREAD_VERSION=1.2 \
|
||||
-DOT_DUA=ON \
|
||||
-DOT_MLR=ON \
|
||||
-DOT_COMMISSIONER=ON \
|
||||
-DOT_CSL_RECEIVER=ON \
|
||||
-DOT_SIMULATION_MAX_NETWORK_SIZE=64
|
||||
```
|
||||
- Edit the value of `ot_path` to the absolute path where the top directory of the OpenThread repository is located. For example, change the value of `ot_path` to `/home/<username>/repo/openthread`.
|
||||
- For each entry in `ot_build.ot`, update the value of `number` to be the number of OT FTD simulations needed with the corresponding version.
|
||||
- For each entry in `ot_build.otbr`, update the value of `number` to be the number of OTBR simulations needed with the corresponding version.
|
||||
- The numbers above can be adjusted according to the requirement of test cases.
|
||||
- Edit the value of `ssh.username` to the username to be used for connecting to the remote POSIX environment.
|
||||
- Edit the value of `ssh.password` to the password corresponding to the username above.
|
||||
- Edit the value of `discovery_ifname` to the network interface that the Harness will connect to.
|
||||
|
||||
Then `ot-cli-ftd` is built in the directory `build/simulation/examples/apps/cli/`.
|
||||
Note that it may be time-consuming to build all versions of `ot-cli-ftd`s and OTBR Docker images especially on devices such as Raspberry Pis.
|
||||
|
||||
2. Open the configuration file `config.py`:
|
||||
|
||||
- Edit the value of `OT_PATH` to the absolute path where the top directory of the OpenThread repository is located. For example, change the value of `OT_PATH` to `/home/<username>/repo/openthread`.
|
||||
|
||||
3. Run the `build_docker_image.sh` with the environment variable `OT_PATH` set properly. `OT_PATH` should be the same as that in the previous step. For example run the following command:
|
||||
|
||||
```bash
|
||||
$ OT_PATH=~/repo/openthread ./build_docker_image.sh
|
||||
```
|
||||
|
||||
4. Run the installation script.
|
||||
2. Run the installation script.
|
||||
|
||||
```bash
|
||||
$ tools/harness-simulation/posix/install.sh
|
||||
@@ -44,33 +30,23 @@ Platform developers should modify the THCI implementation and/or the SI implemen
|
||||
|
||||
## Test Harness Environment Setup
|
||||
|
||||
1. Double click the file `harness\install.bat` on the machine which installed Harness.
|
||||
1. Copy the directory `tools/harness-simulation` from the POSIX machine to the Windows machine, and then switch to that directory.
|
||||
|
||||
2. Check the configuration file `C:\GRL\Thread1.2\Thread_Harness\simulation\config.py`
|
||||
|
||||
- Edit the value of `REMOTE_USERNAME` to the username expected to connect to on the remote POSIX environment.
|
||||
- Edit the value of `REMOTE_PASSWORD` to the password corresponding to the username above.
|
||||
- Edit the value of `REMOTE_OT_PATH` to the absolute path where the top directory of the OpenThread repository is located.
|
||||
2. Double click the file `harness\install.bat` on Windows.
|
||||
|
||||
3. Add the additional simulation device information in `harness\Web\data\deviceInputFields.xml` to `C:\GRL\Thread1.2\Web\data\deviceInputFields.xml`.
|
||||
|
||||
## Run Test Harness on Simulation
|
||||
|
||||
1. On POSIX machine, change directory to the top of OpenThread repository, and run the following commands.
|
||||
1. On the POSIX machine, change directory to the top of the OpenThread repository, and run the following commands.
|
||||
|
||||
```bash
|
||||
$ cd tools/harness-simulation/posix
|
||||
$ python3 launch_testbed.py \
|
||||
--interface=eth0 \
|
||||
--ot=6 \
|
||||
--otbr=4 \
|
||||
--sniffer=2
|
||||
$ ./launch_testbed.py -c simulation.conf
|
||||
```
|
||||
|
||||
This example starts 6 OT FTD simulations, 4 OTBR simulations, and 2 sniffer simulations and can be discovered on `eth0`.
|
||||
This example starts several OT FTD simulations, OTBR simulations, and sniffer simulations and can be discovered on `eth0`. The number of each type of simulation is specified in the configuration file `simulation.conf`.
|
||||
|
||||
The arguments can be adjusted according to the requirement of test cases.
|
||||
|
||||
2. Run Test Harness. The information field of the device is encoded as `<node_id>@<ip_addr>` for FTDs and `otbr_<node_id>@<ip_addr>` for BRs. Choose the proper device as the DUT accordingly.
|
||||
2. Run the Test Harness. The information field of the device is encoded as `<tag>_<node_id>@<ip_addr>`. Choose the desired device as the DUT.
|
||||
|
||||
3. Select one or more test cases to start the test.
|
||||
|
||||
@@ -39,12 +39,15 @@ import pipes
|
||||
import sys
|
||||
import time
|
||||
|
||||
from THCI.IThci import IThci
|
||||
from THCI.OpenThread import watched
|
||||
from THCI.OpenThread_BR import OpenThread_BR
|
||||
from simulation.config import (REMOTE_USERNAME, REMOTE_PASSWORD, REMOTE_PORT)
|
||||
from simulation.config import load_config
|
||||
|
||||
logging.getLogger('paramiko').setLevel(logging.WARNING)
|
||||
|
||||
config = load_config()
|
||||
|
||||
|
||||
class SSHHandle(object):
|
||||
# Unit: second
|
||||
@@ -109,11 +112,8 @@ class SSHHandle(object):
|
||||
class OpenThread_BR_Sim(OpenThread_BR):
|
||||
|
||||
def _getHandle(self):
|
||||
assert self.connectType == 'ip'
|
||||
assert '@' in self.telnetIp
|
||||
self.log('SSH connecting ...')
|
||||
docker_name, ssh_ip = self.telnetIp.split('@')
|
||||
return SSHHandle(ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, docker_name)
|
||||
return SSHHandle(self.ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, self.docker_name)
|
||||
|
||||
@watched
|
||||
def _parseConnectionParams(self, params):
|
||||
@@ -121,15 +121,19 @@ class OpenThread_BR_Sim(OpenThread_BR):
|
||||
if '@' not in discovery_add:
|
||||
raise ValueError('%r in the field `add` is invalid' % discovery_add)
|
||||
|
||||
docker_name, ssh_ip = discovery_add.split('@')
|
||||
self.docker_name, self.ssh_ip = discovery_add.split('@')
|
||||
self.tag, self.node_id = self.docker_name.split('_')
|
||||
# Let it crash if it is an invalid IP address
|
||||
ipaddress.ip_address(ssh_ip)
|
||||
ipaddress.ip_address(self.ssh_ip)
|
||||
|
||||
self.connectType = 'ip'
|
||||
self.telnetIp = self.port = discovery_add
|
||||
self.telnetPort = REMOTE_PORT
|
||||
self.telnetUsername = REMOTE_USERNAME
|
||||
self.telnetPassword = REMOTE_PASSWORD
|
||||
|
||||
global config
|
||||
ssh = config['ssh']
|
||||
self.telnetPort = ssh['port']
|
||||
self.telnetUsername = ssh['username']
|
||||
self.telnetPassword = ssh['password']
|
||||
|
||||
self.extraParams = {
|
||||
'cmd-start-otbr-agent': 'service otbr-agent start',
|
||||
@@ -137,3 +141,6 @@ class OpenThread_BR_Sim(OpenThread_BR):
|
||||
'cmd-restart-otbr-agent': 'service otbr-agent restart',
|
||||
'cmd-restart-radvd': 'service radvd stop; service radvd start',
|
||||
}
|
||||
|
||||
|
||||
assert issubclass(OpenThread_BR_Sim, IThci)
|
||||
|
||||
@@ -38,14 +38,12 @@ import socket
|
||||
import time
|
||||
import win32api
|
||||
|
||||
from simulation.config import load_config
|
||||
from THCI.IThci import IThci
|
||||
from THCI.OpenThread import OpenThreadTHCI, watched
|
||||
from simulation.config import (
|
||||
REMOTE_USERNAME,
|
||||
REMOTE_PASSWORD,
|
||||
REMOTE_PORT,
|
||||
REMOTE_OT_PATH,
|
||||
)
|
||||
|
||||
config = load_config()
|
||||
ot_subpath = {item['tag']: item['subpath'] for item in config['ot_build']['ot']}
|
||||
|
||||
|
||||
class SSHHandle(object):
|
||||
@@ -133,21 +131,15 @@ class SSHHandle(object):
|
||||
class OpenThread_Sim(OpenThreadTHCI, IThci):
|
||||
__handle = None
|
||||
|
||||
# Do not use `os.path.join` as it uses backslash as the separator on Windows
|
||||
device = REMOTE_OT_PATH + '/build/simulation/examples/apps/cli/ot-cli-ftd'
|
||||
|
||||
@watched
|
||||
def _connect(self):
|
||||
self.__lines = []
|
||||
|
||||
# Only actually connect once.
|
||||
if self.__handle is None:
|
||||
assert self.connectType == 'ip'
|
||||
assert '@' in self.telnetIp
|
||||
self.log('SSH connecting ...')
|
||||
node_id, ssh_ip = self.telnetIp.split('@')
|
||||
self.__handle = SSHHandle(ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, self.device,
|
||||
node_id)
|
||||
self.__handle = SSHHandle(self.ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword,
|
||||
self.device, self.node_id)
|
||||
|
||||
self.log('connected to %s successfully', self.telnetIp)
|
||||
|
||||
@@ -161,15 +153,22 @@ class OpenThread_Sim(OpenThreadTHCI, IThci):
|
||||
if '@' not in discovery_add:
|
||||
raise ValueError('%r in the field `add` is invalid' % discovery_add)
|
||||
|
||||
node_id, ssh_ip = discovery_add.split('@')
|
||||
prefix, self.ssh_ip = discovery_add.split('@')
|
||||
self.tag, self.node_id = prefix.split('_')
|
||||
# Let it crash if it is an invalid IP address
|
||||
ipaddress.ip_address(ssh_ip)
|
||||
ipaddress.ip_address(self.ssh_ip)
|
||||
|
||||
# Do not use `os.path.join` as it uses backslash as the separator on Windows
|
||||
self.device = '/'.join([config['ot_path'], ot_subpath[self.tag], 'examples/apps/cli/ot-cli-ftd'])
|
||||
|
||||
self.connectType = 'ip'
|
||||
self.telnetIp = self.port = discovery_add
|
||||
self.telnetPort = REMOTE_PORT
|
||||
self.telnetUsername = REMOTE_USERNAME
|
||||
self.telnetPassword = REMOTE_PASSWORD
|
||||
|
||||
global config
|
||||
ssh = config['ssh']
|
||||
self.telnetPort = ssh['port']
|
||||
self.telnetUsername = ssh['username']
|
||||
self.telnetPassword = ssh['password']
|
||||
|
||||
def _cliReadLine(self):
|
||||
if len(self.__lines) > 1:
|
||||
|
||||
@@ -27,8 +27,13 @@
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
REMOTE_USERNAME = 'pi'
|
||||
REMOTE_PASSWORD = 'raspberry'
|
||||
REMOTE_PORT = 22
|
||||
import json
|
||||
import os
|
||||
|
||||
REMOTE_OT_PATH = '/home/pi/repo/openthread'
|
||||
CONFIG_PATH = r'%s\GRL\Thread1.2\Thread_Harness\simulation\simulation.conf' % os.environ['systemdrive']
|
||||
|
||||
|
||||
def load_config():
|
||||
with open(CONFIG_PATH, 'rt') as f:
|
||||
config = json.load(f)
|
||||
return config
|
||||
|
||||
@@ -25,18 +25,19 @@
|
||||
:: POSSIBILITY OF SUCH DAMAGE.
|
||||
::
|
||||
|
||||
xcopy /E /Y Thread_Harness %systemdrive%\GRL\Thread1.2\Thread_Harness
|
||||
copy /Y ..\..\harness-thci\OpenThread.py %systemdrive%\GRL\Thread1.2\Thread_Harness\THCI
|
||||
copy /Y ..\..\harness-thci\OpenThread_BR.py %systemdrive%\GRL\Thread1.2\Thread_Harness\THCI
|
||||
copy /Y ..\..\harness-thci\OpenThread.png %systemdrive%\GRL\Thread1.2\Web\images
|
||||
copy /Y ..\..\harness-thci\OpenThread_BR.png %systemdrive%\GRL\Thread1.2\Web\images
|
||||
xcopy /E /Y ..\posix\sniffer_sim\proto %systemdrive%\GRL\Thread1.2\Thread_Harness\simulation\Sniffer\proto
|
||||
set THREADDIR=%systemdrive%\GRL\Thread1.2
|
||||
xcopy /E /Y Thread_Harness %THREADDIR%\Thread_Harness
|
||||
copy /Y ..\..\harness-thci\OpenThread.py %THREADDIR%\Thread_Harness\THCI
|
||||
copy /Y ..\..\harness-thci\OpenThread_BR.py %THREADDIR%\Thread_Harness\THCI
|
||||
copy /Y ..\..\harness-thci\OpenThread.png %THREADDIR%\Web\images
|
||||
copy /Y ..\..\harness-thci\OpenThread_BR.png %THREADDIR%\Web\images
|
||||
copy /Y ..\posix\simulation.conf %THREADDIR%\Thread_Harness\simulation
|
||||
xcopy /E /Y ..\posix\sniffer_sim\proto %THREADDIR%\Thread_Harness\simulation\Sniffer\proto
|
||||
|
||||
%systemdrive%\GRL\Thread1.2\Python27\python.exe -m pip install --upgrade pip
|
||||
%systemdrive%\GRL\Thread1.2\Python27\python.exe -m pip install -r requirements.txt
|
||||
|
||||
set BASEDIR=%systemdrive%\GRL\Thread1.2\Thread_Harness
|
||||
%THREADDIR%\Python27\python.exe -m pip install --upgrade pip
|
||||
%THREADDIR%\Python27\python.exe -m pip install -r requirements.txt
|
||||
|
||||
set BASEDIR=%THREADDIR%\Thread_Harness
|
||||
%systemdrive%\GRL\Thread1.2\Python27\python.exe -m grpc_tools.protoc -I%BASEDIR% --python_out=%BASEDIR% --grpc_python_out=%BASEDIR% simulation/Sniffer/proto/sniffer.proto
|
||||
|
||||
pause
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2022, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
# NOTE: This script has not been fully tested up to now
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
OT_PATH=${OT_PATH:-"/home/pi/repo/openthread"}
|
||||
OTBR_DOCKER_IMAGE=${OTBR_DOCKER_IMAGE:-"otbr-reference-device-1.2"}
|
||||
|
||||
DOCKER_BUILD_OTBR_OPTIONS=(
|
||||
"-DOTBR_DUA_ROUTING=ON"
|
||||
"-DOT_DUA=ON"
|
||||
"-DOT_MLR=ON"
|
||||
"-DOT_THREAD_VERSION=1.2"
|
||||
"-DOT_SIMULATION_MAX_NETWORK_SIZE=64"
|
||||
)
|
||||
|
||||
git clone https://github.com/openthread/ot-br-posix.git --recurse-submodules --shallow-submodules --depth=1
|
||||
ETC_PATH="${OT_PATH}/tools/harness-simulation/posix/etc"
|
||||
|
||||
(
|
||||
cd ot-br-posix
|
||||
# Use system V `service` command instead
|
||||
mkdir -p root/etc/init.d
|
||||
cp "${ETC_PATH}/commissionerd" root/etc/init.d/commissionerd
|
||||
sudo chown root:root root/etc/init.d/commissionerd
|
||||
sudo chmod +x root/etc/init.d/commissionerd
|
||||
|
||||
cp "${ETC_PATH}/server.patch" script/server.patch
|
||||
patch script/server script/server.patch
|
||||
mkdir -p root/tmp
|
||||
cp "${ETC_PATH}/requirements.txt" root/tmp/requirements.txt
|
||||
|
||||
docker build . \
|
||||
-t "${OTBR_DOCKER_IMAGE}" \
|
||||
-f "${ETC_PATH}/Dockerfile" \
|
||||
--build-arg REFERENCE_DEVICE=1 \
|
||||
--build-arg BORDER_ROUTING=0 \
|
||||
--build-arg BACKBONE_ROUTER=1 \
|
||||
--build-arg NAT64=0 \
|
||||
--build-arg WEB_GUI=0 \
|
||||
--build-arg REST_API=0 \
|
||||
--build-arg EXTERNAL_COMMISSIONER=1 \
|
||||
--build-arg OTBR_OPTIONS="${DOCKER_BUILD_OTBR_OPTIONS[*]}"
|
||||
)
|
||||
|
||||
rm -rf ot-br-posix
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2022, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
MAX_NODES_NUM = 64
|
||||
MAX_SNIFFER_NUM = 2
|
||||
SNIFFER_SERVER_PORT_BASE = 50051
|
||||
|
||||
OT_PATH = '/home/pi/work/src/openthread-pr'
|
||||
OTBR_DOCKER_IMAGE = 'otbr-reference-device-1.2'
|
||||
OTBR_DOCKER_NAME_PREFIX = 'otbr_'
|
||||
@@ -29,9 +29,123 @@
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
BASE_DIR=$(dirname "$0")
|
||||
SNIFFER_DIR="${BASE_DIR}/sniffer_sim"
|
||||
POSIX_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
OT_DIR="${POSIX_DIR}/../../.."
|
||||
ETC_DIR="${POSIX_DIR}/etc"
|
||||
SNIFFER_DIR="${POSIX_DIR}/sniffer_sim"
|
||||
|
||||
pip3 install -r "${BASE_DIR}/requirements.txt"
|
||||
CONFIG=${1:-"${POSIX_DIR}/simulation.conf"}
|
||||
# Use absolute path in case of changing current working directory
|
||||
if [[ ${CONFIG:0:1} != '/' ]]; then
|
||||
CONFIG="${POSIX_DIR}/${CONFIG}"
|
||||
fi
|
||||
|
||||
MAX_NETWORK_SIZE=$(jq -r '.ot_build.max_number' "$CONFIG")
|
||||
|
||||
PACKAGES=(
|
||||
"docker.io"
|
||||
"git"
|
||||
"jq"
|
||||
"socat"
|
||||
"tshark"
|
||||
)
|
||||
|
||||
sudo apt install -y "${PACKAGES[@]}"
|
||||
|
||||
pip3 install -r "${POSIX_DIR}/requirements.txt"
|
||||
python3 -m grpc_tools.protoc -I"${SNIFFER_DIR}" --python_out="${SNIFFER_DIR}" --grpc_python_out="${SNIFFER_DIR}" proto/sniffer.proto
|
||||
|
||||
build_ot()
|
||||
{
|
||||
# SC2155: Declare and assign separately to avoid masking return values
|
||||
local target build_dir cflags version options
|
||||
target="ot-cli-ftd"
|
||||
build_dir=$(jq -r '.subpath' <<<"$1")
|
||||
cflags=$(jq -r '.cflags | join(" ")' <<<"$1")
|
||||
version=$(jq -r '.version' <<<"$1")
|
||||
options=$(jq -r '.options | join(" ")' <<<"$1")
|
||||
# Intended splitting of options
|
||||
read -ra options <<<"$options"
|
||||
|
||||
(
|
||||
cd "$OT_DIR"
|
||||
|
||||
OT_CMAKE_NINJA_TARGET="$target" \
|
||||
OT_CMAKE_BUILD_DIR="$build_dir" \
|
||||
CFLAGS="$cflags" \
|
||||
CXXFLAGS="$cflags" \
|
||||
script/cmake-build \
|
||||
simulation \
|
||||
"${options[@]}" \
|
||||
-DOT_THREAD_VERSION="$version" \
|
||||
-DOT_SIMULATION_MAX_NETWORK_SIZE="$MAX_NETWORK_SIZE"
|
||||
)
|
||||
}
|
||||
|
||||
build_otbr()
|
||||
{
|
||||
# SC2155: Declare and assign separately to avoid masking return values
|
||||
local target build_dir version rcp_options
|
||||
target="ot-rcp"
|
||||
build_dir=$(jq -r '.rcp_subpath' <<<"$1")
|
||||
version=$(jq -r '.version' <<<"$1")
|
||||
rcp_options=$(jq -r '.rcp_options | join(" ")' <<<"$1")
|
||||
# Intended splitting of rcp_options
|
||||
read -ra rcp_options <<<"$rcp_options"
|
||||
|
||||
(
|
||||
cd "$OT_DIR"
|
||||
|
||||
OT_CMAKE_NINJA_TARGET="$target" \
|
||||
OT_CMAKE_BUILD_DIR="$build_dir" \
|
||||
script/cmake-build \
|
||||
simulation \
|
||||
"${rcp_options[@]}" \
|
||||
-DOT_THREAD_VERSION="$version" \
|
||||
-DOT_SIMULATION_MAX_NETWORK_SIZE="$MAX_NETWORK_SIZE"
|
||||
)
|
||||
|
||||
# SC2155: Declare and assign separately to avoid masking return values
|
||||
local otbr_docker_image build_args options
|
||||
otbr_docker_image=$(jq -r '.docker_image' <<<"$1")
|
||||
build_args=$(jq -r '.build_args | map("--build-arg " + .) | join(" ")' <<<"$1")
|
||||
# Intended splitting of build_args
|
||||
read -ra build_args <<<"$build_args"
|
||||
options=$(jq -r '.options | join(" ")' <<<"$1")
|
||||
|
||||
local otbr_options=(
|
||||
"$options"
|
||||
"-DOT_THREAD_VERSION=$version"
|
||||
"-DOT_SIMULATION_MAX_NETWORK_SIZE=$MAX_NETWORK_SIZE"
|
||||
)
|
||||
|
||||
docker build . \
|
||||
-t "${otbr_docker_image}" \
|
||||
-f "${ETC_DIR}/Dockerfile" \
|
||||
"${build_args[@]}" \
|
||||
--build-arg OTBR_OPTIONS="${otbr_options[*]}"
|
||||
}
|
||||
|
||||
for item in $(jq -c '.ot_build.ot | .[]' "$CONFIG"); do
|
||||
build_ot "$item"
|
||||
done
|
||||
|
||||
git clone https://github.com/openthread/ot-br-posix.git --recurse-submodules --shallow-submodules --depth=1
|
||||
(
|
||||
cd ot-br-posix
|
||||
# Use system V `service` command instead
|
||||
mkdir -p root/etc/init.d
|
||||
cp "${ETC_DIR}/commissionerd" root/etc/init.d/commissionerd
|
||||
sudo chown root:root root/etc/init.d/commissionerd
|
||||
sudo chmod +x root/etc/init.d/commissionerd
|
||||
|
||||
cp "${ETC_DIR}/server.patch" script/server.patch
|
||||
patch script/server script/server.patch
|
||||
mkdir -p root/tmp
|
||||
cp "${ETC_DIR}/requirements.txt" root/tmp/requirements.txt
|
||||
|
||||
for item in $(jq -c '.ot_build.otbr | .[]' "$CONFIG"); do
|
||||
build_otbr "$item"
|
||||
done
|
||||
)
|
||||
rm -rf ot-br-posix
|
||||
|
||||
Regular → Executable
+66
-72
@@ -41,12 +41,6 @@ import subprocess
|
||||
import sys
|
||||
from typing import Iterable
|
||||
|
||||
from config import (
|
||||
MAX_NODES_NUM,
|
||||
MAX_SNIFFER_NUM,
|
||||
SNIFFER_SERVER_PORT_BASE,
|
||||
OTBR_DOCKER_NAME_PREFIX,
|
||||
)
|
||||
from otbr_sim import otbr_docker
|
||||
|
||||
GROUP = 'ff02::114'
|
||||
@@ -94,34 +88,37 @@ def _advertise(s: socket.socket, dst, info):
|
||||
s.sendto(json.dumps(info).encode('utf-8'), dst)
|
||||
|
||||
|
||||
def advertise_devices(s: socket.socket, dst, ven: str, add: str, nodeids: Iterable[int], prefix: str = ''):
|
||||
def advertise_devices(s: socket.socket, dst, ven: str, add: str, nodeids: Iterable[int], tag: str):
|
||||
for nodeid in nodeids:
|
||||
info = {
|
||||
'ven': ven,
|
||||
'mod': 'OpenThread',
|
||||
'ver': '4',
|
||||
'add': f'{prefix}{nodeid}@{add}',
|
||||
'add': f'{tag}_{nodeid}@{add}',
|
||||
'por': 22,
|
||||
}
|
||||
_advertise(s, dst, info)
|
||||
|
||||
|
||||
def advertise_sniffers(s: socket.socket, dst, add: str, number: int):
|
||||
for i in range(number):
|
||||
def advertise_sniffers(s: socket.socket, dst, add: str, ports: Iterable[int]):
|
||||
for port in ports:
|
||||
info = {
|
||||
'add': add,
|
||||
'por': i + SNIFFER_SERVER_PORT_BASE,
|
||||
'por': port,
|
||||
}
|
||||
_advertise(s, dst, info)
|
||||
|
||||
|
||||
def start_sniffer(addr: str, port: int) -> subprocess.Popen:
|
||||
def start_sniffer(addr: str, port: int, ot_path: str) -> subprocess.Popen:
|
||||
if isinstance(ipaddress.ip_address(addr), ipaddress.IPv6Address):
|
||||
server = f'[{addr}]:{port}'
|
||||
else:
|
||||
server = f'{addr}:{port}'
|
||||
|
||||
cmd = ['python3', 'sniffer_sim/sniffer.py', '--grpc-server', server]
|
||||
cmd = [
|
||||
'python3',
|
||||
os.path.join(ot_path, 'tools/harness-simulation/posix/sniffer_sim/sniffer.py'), '--grpc-server', server
|
||||
]
|
||||
logging.info('Executing command: %s', ' '.join(cmd))
|
||||
return subprocess.Popen(cmd)
|
||||
|
||||
@@ -131,79 +128,74 @@ def main():
|
||||
|
||||
# Parse arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
# Determine the interface
|
||||
parser.add_argument('-i',
|
||||
'--interface',
|
||||
dest='ifname',
|
||||
parser.add_argument('-c',
|
||||
'--config',
|
||||
dest='config',
|
||||
type=str,
|
||||
required=True,
|
||||
help='the interface used for discovery')
|
||||
|
||||
# Determine the number of OpenThread FTD simulations to be "detected" and then started
|
||||
parser.add_argument('--ot',
|
||||
dest='ot_num',
|
||||
type=int,
|
||||
required=False,
|
||||
default=0,
|
||||
help=f'the number of OpenThread FTD simulations')
|
||||
|
||||
# Determine the number of OpenThread BR simulations to be initiated and then detected
|
||||
parser.add_argument('--otbr',
|
||||
dest='otbr_num',
|
||||
type=int,
|
||||
required=False,
|
||||
default=0,
|
||||
help=f'the number of OpenThread BR simulations')
|
||||
|
||||
# Determine the number of sniffer simulations to be started and then detected
|
||||
parser.add_argument('--sniffer',
|
||||
dest='sniffer_num',
|
||||
type=int,
|
||||
required=False,
|
||||
default=0,
|
||||
help=f'the number of sniffer simulations, no more than {MAX_SNIFFER_NUM}')
|
||||
|
||||
help='the path of the configuration JSON file')
|
||||
args = parser.parse_args()
|
||||
with open(args.config, 'rt') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Check validation of arguments
|
||||
if args.ot_num < 0:
|
||||
raise ValueError(f'The number of FTDs should be non-negative')
|
||||
ot_path = config['ot_path']
|
||||
ot_build = config['ot_build']
|
||||
max_nodes_num = ot_build['max_number']
|
||||
# No test case requires more than 2 sniffers
|
||||
MAX_SNIFFER_NUM = 2
|
||||
|
||||
if args.otbr_num < 0:
|
||||
raise ValueError(f'The number of OTBRs should be non-negative')
|
||||
ot_devices = [(item['tag'], item['number']) for item in ot_build['ot']]
|
||||
otbr_devices = [(item['tag'], item['number']) for item in ot_build['otbr']]
|
||||
ot_nodes_num = sum(x[1] for x in ot_devices)
|
||||
otbr_nodes_num = sum(x[1] for x in otbr_devices)
|
||||
nodes_num = ot_nodes_num + otbr_nodes_num
|
||||
sniffer_num = config['sniffer']['number']
|
||||
|
||||
if not 0 <= args.sniffer_num <= MAX_SNIFFER_NUM:
|
||||
raise ValueError(f'The number of sniffers should be between 0 and {MAX_SNIFFER_NUM}')
|
||||
# Check validation of numbers
|
||||
if not all(0 <= x[1] <= max_nodes_num for x in ot_devices):
|
||||
raise ValueError(f'The number of devices of each OT version should be between 0 and {max_nodes_num}')
|
||||
|
||||
if args.ot_num == args.otbr_num == 0:
|
||||
raise ValueError('At least one device is required')
|
||||
if not all(0 <= x[1] <= max_nodes_num for x in otbr_devices):
|
||||
raise ValueError(f'The number of devices of each OTBR version should be between 0 and {max_nodes_num}')
|
||||
|
||||
if args.sniffer_num == 0:
|
||||
raise ValueError('At least one sniffer is required')
|
||||
if not 1 <= nodes_num <= max_nodes_num:
|
||||
raise ValueError(f'The number of devices should be between 1 and {max_nodes_num}')
|
||||
|
||||
if args.ot_num + args.otbr_num > MAX_NODES_NUM:
|
||||
raise ValueError(f'The number of all devices should be no more than {MAX_NODES_NUM}')
|
||||
if not 1 <= sniffer_num <= MAX_SNIFFER_NUM:
|
||||
raise ValueError(f'The number of sniffers should be between 1 and {MAX_SNIFFER_NUM}')
|
||||
|
||||
# Get the local IP address on the specified interface
|
||||
addr = get_ipaddr(args.ifname)
|
||||
ifname = config['discovery_ifname']
|
||||
addr = get_ipaddr(ifname)
|
||||
|
||||
# Start the sniffer
|
||||
sniffer_server_port_base = config['sniffer']['server_port_base']
|
||||
sniffer_procs = []
|
||||
for i in range(args.sniffer_num):
|
||||
sniffer_procs.append(start_sniffer(addr, i + SNIFFER_SERVER_PORT_BASE))
|
||||
for i in range(sniffer_num):
|
||||
sniffer_procs.append(start_sniffer(addr, i + sniffer_server_port_base, ot_path))
|
||||
|
||||
# OTBR firewall scripts create rules inside the Docker container
|
||||
# Run modprobe to load the kernel modules for iptables
|
||||
subprocess.run(['sudo', 'modprobe', 'ip6table_filter'])
|
||||
# Start the BRs
|
||||
otbr_dockers = []
|
||||
for nodeid in range(args.ot_num + 1, args.ot_num + args.otbr_num + 1):
|
||||
otbr_dockers.append(otbr_docker.OtbrDocker(nodeid, OTBR_DOCKER_NAME_PREFIX + str(nodeid)))
|
||||
nodeid = ot_nodes_num
|
||||
for item in ot_build['otbr']:
|
||||
tag = item['tag']
|
||||
ot_rcp_path = os.path.join(ot_path, item['rcp_subpath'], 'examples/apps/ncp/ot-rcp')
|
||||
docker_image = item['docker_image']
|
||||
for _ in range(item['number']):
|
||||
nodeid += 1
|
||||
otbr_dockers.append(
|
||||
otbr_docker.OtbrDocker(nodeid=nodeid,
|
||||
ot_path=ot_path,
|
||||
ot_rcp_path=ot_rcp_path,
|
||||
docker_image=docker_image,
|
||||
docker_name=f'{tag}_{nodeid}'))
|
||||
|
||||
s = init_socket(args.ifname, GROUP, PORT)
|
||||
s = init_socket(ifname, GROUP, PORT)
|
||||
|
||||
logging.info('Advertising on interface %s group %s ...', args.ifname, GROUP)
|
||||
logging.info('Advertising on interface %s group %s ...', ifname, GROUP)
|
||||
|
||||
# Terminate all sniffer simulation server processes and then exit
|
||||
def exit_handler(signum, context):
|
||||
@@ -227,17 +219,19 @@ def main():
|
||||
|
||||
if data == b'BBR':
|
||||
logging.info('Received OpenThread simulation query, advertising')
|
||||
advertise_devices(s, src, ven='OpenThread_Sim', add=addr, nodeids=range(1, args.ot_num + 1))
|
||||
advertise_devices(s,
|
||||
src,
|
||||
ven='OpenThread_BR_Sim',
|
||||
add=addr,
|
||||
nodeids=range(args.ot_num + 1, args.ot_num + args.otbr_num + 1),
|
||||
prefix=OTBR_DOCKER_NAME_PREFIX)
|
||||
|
||||
nodeid = 1
|
||||
for ven, devices in [('OpenThread_Sim', ot_devices), ('OpenThread_BR_Sim', otbr_devices)]:
|
||||
for tag, number in devices:
|
||||
advertise_devices(s, src, ven=ven, add=addr, nodeids=range(nodeid, nodeid + number), tag=tag)
|
||||
nodeid += number
|
||||
|
||||
elif data == b'Sniffer':
|
||||
logging.info('Received sniffer simulation query, advertising')
|
||||
advertise_sniffers(s, src, add=addr, number=args.sniffer_num)
|
||||
advertise_sniffers(s,
|
||||
src,
|
||||
add=addr,
|
||||
ports=range(sniffer_server_port_base, sniffer_server_port_base + sniffer_num))
|
||||
|
||||
else:
|
||||
logging.warning('Received %r, but ignored', data)
|
||||
|
||||
@@ -33,14 +33,15 @@ import re
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from config import (OT_PATH, OTBR_DOCKER_IMAGE)
|
||||
|
||||
|
||||
class OtbrDocker:
|
||||
device_pattern = re.compile('(?<=PTY is )/dev/.+$')
|
||||
|
||||
def __init__(self, nodeid: int, docker_name: str):
|
||||
def __init__(self, nodeid: int, ot_path: str, ot_rcp_path: str, docker_image: str, docker_name: str):
|
||||
self.nodeid = nodeid
|
||||
self.ot_path = ot_path
|
||||
self.ot_rcp_path = ot_rcp_path
|
||||
self.docker_image = docker_image
|
||||
self.docker_name = docker_name
|
||||
|
||||
self.logger = logging.getLogger('otbr_docker.OtbrDocker')
|
||||
@@ -96,9 +97,8 @@ class OtbrDocker:
|
||||
self._rcp_device = None
|
||||
|
||||
def _launch_ot_rcp(self):
|
||||
ot_rcp_path = os.path.join(OT_PATH, 'build/simulation/examples/apps/ncp/ot-rcp')
|
||||
self._ot_rcp_proc = subprocess.Popen(
|
||||
f'{ot_rcp_path} {self.nodeid} > {self._rcp_device_pty} < {self._rcp_device_pty}',
|
||||
f'{self.ot_rcp_path} {self.nodeid} > {self._rcp_device_pty} < {self._rcp_device_pty}',
|
||||
shell=True,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
@@ -136,8 +136,8 @@ class OtbrDocker:
|
||||
'-v',
|
||||
f'{self._rcp_device}:/dev/ttyUSB0',
|
||||
'-v',
|
||||
f'{OT_PATH.rstrip("/")}:/home/pi/repo/openthread',
|
||||
OTBR_DOCKER_IMAGE,
|
||||
f'{self.ot_path.rstrip("/")}:/home/pi/repo/openthread',
|
||||
self.docker_image,
|
||||
]
|
||||
self.logger.info('Launching docker: %s', ' '.join(cmd))
|
||||
launch_proc = subprocess.Popen(cmd,
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"ot_path": "/home/pi/work/src/openthread-pr",
|
||||
"ot_build": {
|
||||
"max_number": 64,
|
||||
"ot": [
|
||||
{
|
||||
"tag": "OT11",
|
||||
"version": "1.1",
|
||||
"number": 33,
|
||||
"subpath": "build/ot11/simulation",
|
||||
"cflags": [
|
||||
"-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8"
|
||||
],
|
||||
"options": [
|
||||
"-DOT_REFERENCE_DEVICE=ON",
|
||||
"-DOT_COMMISSIONER=ON",
|
||||
"-DOT_JOINER=ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "OT12",
|
||||
"version": "1.2",
|
||||
"number": 10,
|
||||
"subpath": "build/ot12/simulation",
|
||||
"cflags": [
|
||||
"-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8"
|
||||
],
|
||||
"options": [
|
||||
"-DOT_REFERENCE_DEVICE=ON",
|
||||
"-DOT_DUA=ON",
|
||||
"-DOT_MLR=ON",
|
||||
"-DOT_COMMISSIONER=ON",
|
||||
"-DOT_JOINER=ON",
|
||||
"-DOT_CSL_RECEIVER=ON",
|
||||
"-DOT_LINK_METRICS_SUBJECT=ON",
|
||||
"-DOT_LINK_METRICS_INITIATOR=ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "OT13",
|
||||
"version": "1.3",
|
||||
"number": 10,
|
||||
"subpath": "build/ot13/simulation",
|
||||
"cflags": [
|
||||
"-DOPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS=8"
|
||||
],
|
||||
"options": [
|
||||
"-DOT_REFERENCE_DEVICE=ON",
|
||||
"-DOT_DUA=ON",
|
||||
"-DOT_MLR=ON",
|
||||
"-DOT_COMMISSIONER=ON",
|
||||
"-DOT_JOINER=ON",
|
||||
"-DOT_CSL_RECEIVER=ON",
|
||||
"-DOT_LINK_METRICS_SUBJECT=ON",
|
||||
"-DOT_LINK_METRICS_INITIATOR=ON"
|
||||
]
|
||||
}
|
||||
],
|
||||
"otbr": [
|
||||
{
|
||||
"tag": "OTBR12",
|
||||
"version": "1.2",
|
||||
"number": 4,
|
||||
"docker_image": "otbr-reference-device-1.2",
|
||||
"build_args": [
|
||||
"REFERENCE_DEVICE=1",
|
||||
"BORDER_ROUTING=0",
|
||||
"BACKBONE_ROUTER=1",
|
||||
"NAT64=0",
|
||||
"WEB_GUI=0",
|
||||
"REST_API=0",
|
||||
"OT_COMMISSIONER=1"
|
||||
],
|
||||
"options": [
|
||||
"-DOTBR_DUA_ROUTING=ON",
|
||||
"-DOT_DUA=ON",
|
||||
"-DOT_MLR=ON"
|
||||
],
|
||||
"rcp_subpath": "build/ot12/simulation",
|
||||
"rcp_options": [
|
||||
"-DOT_LINK_METRICS_SUBJECT=ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "OTBR13",
|
||||
"version": "1.3",
|
||||
"number": 4,
|
||||
"docker_image": "otbr-reference-device-1.3",
|
||||
"build_args": [
|
||||
"REFERENCE_DEVICE=1",
|
||||
"BORDER_ROUTING=1",
|
||||
"BACKBONE_ROUTER=1",
|
||||
"NAT64=0",
|
||||
"WEB_GUI=0",
|
||||
"REST_API=0",
|
||||
"EXTERNAL_COMMISSIONER=1"
|
||||
],
|
||||
"options": [
|
||||
"-DOTBR_DUA_ROUTING=ON",
|
||||
"-DOT_DUA=ON",
|
||||
"-DOT_MLR=ON"
|
||||
],
|
||||
"rcp_subpath": "build/ot13/simulation",
|
||||
"rcp_options": [
|
||||
"-DOT_LINK_METRICS_SUBJECT=ON"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ssh": {
|
||||
"username": "pi",
|
||||
"password": "raspberry",
|
||||
"port": 22
|
||||
},
|
||||
"sniffer": {
|
||||
"number": 2,
|
||||
"server_port_base": 50051
|
||||
},
|
||||
"discovery_ifname": "eth0"
|
||||
}
|
||||
Reference in New Issue
Block a user