From a0c4ede86db98120dbb48efd8a9f44d848ca07db Mon Sep 17 00:00:00 2001 From: Jiachen Dong Date: Thu, 15 Sep 2022 05:31:06 +0800 Subject: [PATCH] [harness-simulation] add support for RF enclosure simulation (#8092) This commit adds support for RF enclosure simulation. It can pass Leader 9.2.9, Router 9.2.9 and Router 9.2.10 in Thread Test Harness v56.0 now. --- examples/apps/cli/main.c | 14 +- .../simulation/platform-simulation.h | 12 ++ examples/platforms/simulation/radio.c | 122 +++++++++++++---- .../simulation/virtual_time/platform-sim.c | 24 +--- include/openthread/cli.h | 6 +- include/openthread/instance.h | 2 +- src/cli/cli.cpp | 3 +- src/posix/main.c | 6 +- .../Thread_Harness/Sniffer/SimSniffer.py | 123 ++++++++++++++++++ .../Thread_Harness/THCI/OpenThread_BR_Sim.py | 1 + .../Thread_Harness/THCI/OpenThread_Sim.py | 3 +- .../posix/launch_testbed.py | 10 +- .../harness-simulation/posix/simulation.conf | 2 +- .../posix/sniffer_sim/sniffer.py | 34 ++--- tools/harness-thci/OpenThread.py | 10 ++ 15 files changed, 302 insertions(+), 70 deletions(-) diff --git a/examples/apps/cli/main.c b/examples/apps/cli/main.c index 165f32f93..83de78e81 100644 --- a/examples/apps/cli/main.c +++ b/examples/apps/cli/main.c @@ -67,7 +67,7 @@ void otTaskletsSignalPending(otInstance *aInstance) } #if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) -static void ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[]) +static otError ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aContext); OT_UNUSED_VARIABLE(aArgsLength); @@ -75,9 +75,19 @@ static void ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[]) exit(EXIT_SUCCESS); } -static const otCliCommand kCommands[] = {{"exit", ProcessExit}}; + +#if OPENTHREAD_EXAMPLES_SIMULATION +extern otError ProcessNodeIdFilter(void *aContext, uint8_t aArgsLength, char *aArgs[]); #endif +static const otCliCommand kCommands[] = { + {"exit", ProcessExit}, +#if OPENTHREAD_EXAMPLES_SIMULATION + {"nodeidfilter", ProcessNodeIdFilter}, +#endif +}; +#endif // OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + int main(int argc, char *argv[]) { otInstance *instance; diff --git a/examples/platforms/simulation/platform-simulation.h b/examples/platforms/simulation/platform-simulation.h index 9379421d4..ecd40b9ee 100644 --- a/examples/platforms/simulation/platform-simulation.h +++ b/examples/platforms/simulation/platform-simulation.h @@ -232,6 +232,18 @@ void otSimSendUartWriteEvent(const uint8_t *aData, uint16_t aLength); */ bool platformRadioIsTransmitPending(void); +/** + * This function parses an environment variable as an unsigned 16-bit integer. + * + * If the environment variable does not exist, this function does nothing. + * If it is not a valid integer, this function will terminate the process with an error message. + * + * @param[in] aEnvName The name of the environment variable. + * @param[out] aValue A pointer to the unsigned 16-bit integer. + * + */ +void parseFromEnvAsUint16(const char *aEnvName, uint16_t *aValue); + #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE /** diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c index 2b627c2f2..4d6ab8810 100644 --- a/examples/platforms/simulation/radio.c +++ b/examples/platforms/simulation/radio.c @@ -71,10 +71,12 @@ enum #if OPENTHREAD_SIMULATION_VIRTUAL_TIME extern int sSockFd; +extern uint16_t sPortBase; extern uint16_t sPortOffset; #else static int sTxFd = -1; static int sRxFd = -1; +static uint16_t sPortBase = 9000; static uint16_t sPortOffset = 0; static uint16_t sPort = 0; #endif @@ -166,6 +168,75 @@ static otRadioKeyType sKeyType; static int8_t GetRssi(uint16_t aChannel); +#if OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 +static uint8_t sDeniedNodeIdsBitVector[(MAX_NETWORK_SIZE + 7) / 8]; + +static bool NodeIdFilterIsConnectable(uint16_t aNodeId) +{ + uint16_t index = aNodeId - 1; + + return (sDeniedNodeIdsBitVector[index / 8] & (0x80 >> (index % 8))) == 0; +} + +static void NodeIdFilterDeny(uint16_t aNodeId) +{ + uint16_t index = aNodeId - 1; + + sDeniedNodeIdsBitVector[index / 8] |= 0x80 >> (index % 8); +} + +static void NodeIdFilterClear(void) +{ + memset(sDeniedNodeIdsBitVector, 0, sizeof(sDeniedNodeIdsBitVector)); +} + +otError ProcessNodeIdFilter(void *aContext, uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aContext); + + otError error = OT_ERROR_NONE; + + otEXPECT_ACTION(aArgsLength > 0, error = OT_ERROR_INVALID_COMMAND); + + if (!strcmp(aArgs[0], "clear")) + { + otEXPECT_ACTION(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS); + + NodeIdFilterClear(); + } + else if (!strcmp(aArgs[0], "deny")) + { + uint16_t nodeId; + char * endptr; + + otEXPECT_ACTION(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS); + + nodeId = (uint16_t)strtol(aArgs[1], &endptr, 0); + + otEXPECT_ACTION(*endptr == '\0', error = OT_ERROR_INVALID_ARGS); + otEXPECT_ACTION(1 <= nodeId && nodeId <= MAX_NETWORK_SIZE, error = OT_ERROR_INVALID_ARGS); + + NodeIdFilterDeny(nodeId); + } + else + { + error = OT_ERROR_INVALID_COMMAND; + } + +exit: + return error; +} +#else +otError ProcessNodeIdFilter(void *aContext, uint8_t aArgsLength, char *aArgs[]) +{ + OT_UNUSED_VARIABLE(aContext); + OT_UNUSED_VARIABLE(aArgsLength); + OT_UNUSED_VARIABLE(aArgs); + + return OT_ERROR_NOT_IMPLEMENTED; +} +#endif // OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 + static bool IsTimeAfterOrEqual(uint32_t aTimeA, uint32_t aTimeB) { return (aTimeA - aTimeB) < (1U << 31); @@ -298,7 +369,7 @@ static void initFds(void) otEXPECT_ACTION((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1, perror("socket(sTxFd)")); - sPort = (uint16_t)(9000 + sPortOffset + gNodeId); + sPort = (uint16_t)(sPortBase + sPortOffset + gNodeId); sockaddr.sin_family = AF_INET; sockaddr.sin_port = htons(sPort); sockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); @@ -337,7 +408,7 @@ static void initFds(void) } sockaddr.sin_family = AF_INET; - sockaddr.sin_port = htons((uint16_t)(9000 + sPortOffset)); + sockaddr.sin_port = htons((uint16_t)(sPortBase + sPortOffset)); sockaddr.sin_addr.s_addr = inet_addr(OT_RADIO_GROUP); otEXPECT_ACTION(bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != -1, perror("bind(sRxFd)")); @@ -356,24 +427,10 @@ exit: void platformRadioInit(void) { #if OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 - char *offset; + parseFromEnvAsUint16("PORT_BASE", &sPortBase); - offset = getenv("PORT_OFFSET"); - - if (offset) - { - char *endptr; - - sPortOffset = (uint16_t)strtol(offset, &endptr, 0); - - if (*endptr != '\0') - { - fprintf(stderr, "Invalid PORT_OFFSET: %s\n", offset); - exit(EXIT_FAILURE); - } - - sPortOffset *= (MAX_NETWORK_SIZE + 1); - } + parseFromEnvAsUint16("PORT_OFFSET", &sPortOffset); + sPortOffset *= (MAX_NETWORK_SIZE + 1); initFds(); #endif // OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 @@ -829,7 +886,10 @@ void platformRadioProcess(otInstance *aInstance, const fd_set *aReadFdSet, const if (rval > 0) { - if (sockaddr.sin_port != htons(sPort)) + uint16_t srcPort = ntohs(sockaddr.sin_port); + uint16_t srcNodeId = srcPort - sPortOffset - sPortBase; + + if (NodeIdFilterIsConnectable(srcNodeId) && srcPort != sPort) { sReceiveFrame.mLength = (uint16_t)(rval - 1); @@ -847,7 +907,7 @@ void platformRadioProcess(otInstance *aInstance, const fd_set *aReadFdSet, const exit(EXIT_FAILURE); } } -#endif +#endif // OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 if (platformRadioIsTransmitPending()) { radioSendMessage(aInstance); @@ -870,7 +930,7 @@ void radioTransmit(struct RadioMessage *aMessage, const struct otRadioFrame *aFr sockaddr.sin_family = AF_INET; inet_pton(AF_INET, OT_RADIO_GROUP, &sockaddr.sin_addr); - sockaddr.sin_port = htons((uint16_t)(9000 + sPortOffset)); + sockaddr.sin_port = htons((uint16_t)(sPortBase + sPortOffset)); rval = sendto(sTxFd, (const char *)aMessage, 1 + aFrame->mLength, 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); @@ -1321,3 +1381,21 @@ otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode) exit: return error; } + +void parseFromEnvAsUint16(const char *aEnvName, uint16_t *aValue) +{ + char *env = getenv(aEnvName); + + if (env) + { + char *endptr; + + *aValue = (uint16_t)strtol(env, &endptr, 0); + + if (*endptr != '\0') + { + fprintf(stderr, "Invalid %s: %s\n", aEnvName, env); + exit(EXIT_FAILURE); + } + } +} diff --git a/examples/platforms/simulation/virtual_time/platform-sim.c b/examples/platforms/simulation/virtual_time/platform-sim.c index fa0dd9055..20eb94717 100644 --- a/examples/platforms/simulation/virtual_time/platform-sim.c +++ b/examples/platforms/simulation/virtual_time/platform-sim.c @@ -61,6 +61,7 @@ char **gArguments = NULL; uint64_t sNow = 0; // microseconds int sSockFd; +uint16_t sPortBase = 9000; uint16_t sPortOffset; static void handleSignal(int aSignal) @@ -78,7 +79,7 @@ void otSimSendEvent(const struct Event *aEvent) memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &sockaddr.sin_addr); - sockaddr.sin_port = htons(9000 + sPortOffset); + sockaddr.sin_port = htons(sPortBase + sPortOffset); rval = sendto(sSockFd, aEvent, offsetof(struct Event, mData) + aEvent->mDataLength, 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); @@ -176,28 +177,15 @@ otError otPlatUartFlush(void) static void socket_init(void) { struct sockaddr_in sockaddr; - char * offset; memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.sin_family = AF_INET; - offset = getenv("PORT_OFFSET"); + parseFromEnvAsUint16("PORT_BASE", &sPortBase); - if (offset) - { - char *endptr; + parseFromEnvAsUint16("PORT_OFFSET", &sPortOffset); + sPortOffset *= (MAX_NETWORK_SIZE + 1); - sPortOffset = (uint16_t)strtol(offset, &endptr, 0); - - if (*endptr != '\0') - { - fprintf(stderr, "Invalid PORT_OFFSET: %s\n", offset); - exit(EXIT_FAILURE); - } - - sPortOffset *= (MAX_NETWORK_SIZE + 1); - } - - sockaddr.sin_port = htons((uint16_t)(9000 + sPortOffset + gNodeId)); + sockaddr.sin_port = htons((uint16_t)(sPortBase + sPortOffset + gNodeId)); sockaddr.sin_addr.s_addr = INADDR_ANY; sSockFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); diff --git a/include/openthread/cli.h b/include/openthread/cli.h index 3fa5c5653..dcc33411a 100644 --- a/include/openthread/cli.h +++ b/include/openthread/cli.h @@ -52,9 +52,9 @@ extern "C" { typedef struct otCliCommand { const char *mName; ///< A pointer to the command string. - void (*mCommand)(void * aContext, - uint8_t aArgsLength, - char * aArgs[]); ///< A function pointer to process the command. + otError (*mCommand)(void * aContext, + uint8_t aArgsLength, + char * aArgs[]); ///< A function pointer to process the command. } otCliCommand; /** diff --git a/include/openthread/instance.h b/include/openthread/instance.h index d026a75d4..36c567037 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (242) +#define OPENTHREAD_API_VERSION (243) /** * @addtogroup api-instance diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 4ef9b60e1..25acbef6a 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -299,8 +299,7 @@ otError Interpreter::ProcessUserCommands(Arg aArgs[]) char *args[kMaxArgs]; Arg::CopyArgsToStringArray(aArgs, args); - mUserCommands[i].mCommand(mUserCommandsContext, Arg::GetArgsLength(aArgs) - 1, args + 1); - error = OT_ERROR_NONE; + error = mUserCommands[i].mCommand(mUserCommandsContext, Arg::GetArgsLength(aArgs) - 1, args + 1); break; } } diff --git a/src/posix/main.c b/src/posix/main.c index df014ea5f..d6de188a4 100644 --- a/src/posix/main.c +++ b/src/posix/main.c @@ -325,17 +325,19 @@ void otPlatReset(otInstance *aInstance) assert(false); } -static void ProcessNetif(void *aContext, uint8_t aArgsLength, char *aArgs[]) +static otError ProcessNetif(void *aContext, uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aContext); OT_UNUSED_VARIABLE(aArgsLength); OT_UNUSED_VARIABLE(aArgs); otCliOutputFormat("%s:%u\r\n", otSysGetThreadNetifName(), otSysGetThreadNetifIndex()); + + return OT_ERROR_NONE; } #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE -static void ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[]) +static otError ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aContext); OT_UNUSED_VARIABLE(aArgsLength); diff --git a/tools/harness-simulation/harness/Thread_Harness/Sniffer/SimSniffer.py b/tools/harness-simulation/harness/Thread_Harness/Sniffer/SimSniffer.py index 194b6ad03..90bc43f3d 100644 --- a/tools/harness-simulation/harness/Thread_Harness/Sniffer/SimSniffer.py +++ b/tools/harness-simulation/harness/Thread_Harness/Sniffer/SimSniffer.py @@ -29,6 +29,7 @@ import grpc import ipaddress +import itertools import json import netifaces import select @@ -38,6 +39,9 @@ import time import win32api import winreg as wr +from GRLLibs.UtilityModules.ModuleHelper import ModuleHelper +from GRLLibs.UtilityModules.SnifferManager import SnifferManager +from GRLLibs.UtilityModules.TopologyManager import TopologyManager from Sniffer.ISniffer import ISniffer from THCI.OpenThread import watched from simulation.Sniffer.proto import sniffer_pb2 @@ -56,7 +60,79 @@ IPPROTO_IPV6 = 41 WINREG_KEY = r'SYSTEM\CurrentControlSet\Control\Network\{4d36e972-e325-11ce-bfc1-08002be10318}' +# When Harness requires an RF shield box, it will pop up a message box via `ModuleHelper.UIMsgBox` +# Replace the function to add and remove an RF enclosure simulation automatically without popping up a window +def UIMsgBoxDecorator(UIMsgBox, replaceFuncs): + + @staticmethod + def UIMsgBoxWrapper(msg='Confirm ??', + title='User Input Required', + inputRequired=False, + default='', + choices=None, + timeout=None, + isPrompt=False): + func = replaceFuncs.get((msg, title)) + if func is None: + return UIMsgBox(msg, title, inputRequired, default, choices, timeout, isPrompt) + else: + return func() + + return UIMsgBoxWrapper + + +class DeviceManager: + + def __init__(self): + deviceManager = TopologyManager.m_DeviceManager + self._devices = {'DUT': deviceManager.AutoDUTDeviceObject.THCI_Object} + for device_obj in deviceManager.DeviceList.values(): + if device_obj.IsDeviceUsed: + self._devices[device_obj.DeviceTopologyInfo] = device_obj.THCI_Object + + def __getitem__(self, deviceName): + device = self._devices.get(deviceName) + if device is None: + raise KeyError('Device Name "%s" not found' % deviceName) + return device + + +class RFEnclosureManager: + + def __init__(self, deviceNames1, deviceNames2, snifferPartitionId): + self.deviceNames1 = deviceNames1 + self.deviceNames2 = deviceNames2 + self.snifferPartitionId = snifferPartitionId + + def placeRFEnclosure(self): + devices = DeviceManager() + self._partition1 = [devices[role] for role in self.deviceNames1] + self._partition2 = [devices[role] for role in self.deviceNames2] + sniffer_denied_partition = self._partition1 if self.snifferPartitionId == 2 else self._partition2 + + for device1 in self._partition1: + for device2 in self._partition2: + device1.addBlockedNodeId(device2.node_id) + device2.addBlockedNodeId(device1.node_id) + + for sniffer in SnifferManager.SnifferObjects.values(): + if sniffer.isSnifferCapturing(): + sniffer.filterNodes(device.node_id for device in sniffer_denied_partition) + + def removeRFEnclosure(self): + for device in itertools.chain(self._partition1, self._partition2): + device.clearBlockedNodeIds() + + for sniffer in SnifferManager.SnifferObjects.values(): + if sniffer.isSnifferCapturing(): + sniffer.filterNodes(()) + + self._partition1 = None + self._partition2 = None + + class SimSniffer(ISniffer): + replaced_msgbox = False @watched def __init__(self, **kwargs): @@ -66,12 +142,47 @@ class SimSniffer(ISniffer): self._local_pcapng_location = None if self.addr_port is not None: + # Replace `ModuleHelper.UIMsgBox` only when simulation devices exist + self._replaceMsgBox() + self._sniffer = grpc.insecure_channel(self.addr_port) self._stub = sniffer_pb2_grpc.SnifferStub(self._sniffer) # Close the sniffer only when Harness exits win32api.SetConsoleCtrlHandler(self.__disconnect, True) + @watched + def _replaceMsgBox(self): + # Replace the function only once + if SimSniffer.replaced_msgbox: + return + SimSniffer.replaced_msgbox = True + + test_9_2_9_leader = RFEnclosureManager(['Router_1', 'Router_2'], ['DUT', 'Commissioner'], 1) + test_9_2_9_router = RFEnclosureManager(['DUT', 'Router_2'], ['Leader', 'Commissioner'], 1) + test_9_2_10_router = RFEnclosureManager(['Leader', 'Commissioner'], ['DUT', 'MED_1', 'SED_1'], 2) + + # Alter the behavior of `ModuleHelper.UIMsgBox` only when it comes to the following test cases: + # - Leader 9.2.9 + # - Router 9.2.9 + # - Router 9.2.10 + ModuleHelper.UIMsgBox = UIMsgBoxDecorator( + ModuleHelper.UIMsgBox, + replaceFuncs={ + ("Place [Router1, Router2 and Sniffer]
or
[DUT and Commissioner]
in an RF enclosure ", "Shield Devices"): + test_9_2_9_leader.placeRFEnclosure, + ("Remove [Router1, Router2 and Sniffer]
or
[DUT and Commissioner]
from RF enclosure ", "Unshield Devices"): + test_9_2_9_leader.removeRFEnclosure, + ("Place [DUT,Router2 and sniffer]
or
[Leader and Commissioner]
in an RF enclosure ", "Shield Devices"): + test_9_2_9_router.placeRFEnclosure, + ("Remove [DUT, Router2 and sniffer]
or
[Leader and Commissioner]
from RF enclosure ", "Unshield Devices"): + test_9_2_9_router.removeRFEnclosure, + ("Place the
[Leader and Commissioner] devices
in an RF enclosure ", "Shield Devices"): + test_9_2_10_router.placeRFEnclosure, + ("Remove
[Leader and Commissioner]
from RF enclosure ", "Unshield Devices"): + test_9_2_10_router.removeRFEnclosure, + }) + def __repr__(self): return '%r' % self.__dict__ @@ -164,6 +275,18 @@ class SimSniffer(ISniffer): self.is_active = False + @watched + def filterNodes(self, nodeids): + if not self.is_active: + return + + request = sniffer_pb2.FilterNodesRequest() + request.nodeids.extend(nodeids) + + response = self._stub.FilterNodes(request) + if response.status != sniffer_pb2.OK: + raise RuntimeError('filterNodes error: %s' % sniffer_pb2.Status.Name(response.status)) + def __disconnect(self, dwCtrlType): if self._sniffer is not None: self._sniffer.close() diff --git a/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_BR_Sim.py b/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_BR_Sim.py index 0f68e6761..17c8698b7 100644 --- a/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_BR_Sim.py +++ b/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_BR_Sim.py @@ -123,6 +123,7 @@ class OpenThread_BR_Sim(OpenThread_BR): self.docker_name, self.ssh_ip = discovery_add.split('@') self.tag, self.node_id = self.docker_name.split('_') + self.node_id = int(self.node_id) # Let it crash if it is an invalid IP address ipaddress.ip_address(self.ssh_ip) diff --git a/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_Sim.py b/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_Sim.py index 59eebcbc7..18c622ec0 100644 --- a/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_Sim.py +++ b/tools/harness-simulation/harness/Thread_Harness/THCI/OpenThread_Sim.py @@ -155,16 +155,17 @@ class OpenThread_Sim(OpenThreadTHCI, IThci): prefix, self.ssh_ip = discovery_add.split('@') self.tag, self.node_id = prefix.split('_') + self.node_id = int(self.node_id) # Let it crash if it is an invalid IP address ipaddress.ip_address(self.ssh_ip) # Do not use `os.path.join` as it uses backslash as the separator on Windows + global config 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 - global config ssh = config['ssh'] self.telnetPort = ssh['port'] self.telnetUsername = ssh['username'] diff --git a/tools/harness-simulation/posix/launch_testbed.py b/tools/harness-simulation/posix/launch_testbed.py index c7363362c..dd49fa3bd 100755 --- a/tools/harness-simulation/posix/launch_testbed.py +++ b/tools/harness-simulation/posix/launch_testbed.py @@ -109,7 +109,7 @@ def advertise_sniffers(s: socket.socket, dst, add: str, ports: Iterable[int]): _advertise(s, dst, info) -def start_sniffer(addr: str, port: int, ot_path: str) -> subprocess.Popen: +def start_sniffer(addr: str, port: int, ot_path: str, max_nodes_num: int) -> subprocess.Popen: if isinstance(ipaddress.ip_address(addr), ipaddress.IPv6Address): server = f'[{addr}]:{port}' else: @@ -117,7 +117,11 @@ def start_sniffer(addr: str, port: int, ot_path: str) -> subprocess.Popen: cmd = [ 'python3', - os.path.join(ot_path, 'tools/harness-simulation/posix/sniffer_sim/sniffer.py'), '--grpc-server', server + os.path.join(ot_path, 'tools/harness-simulation/posix/sniffer_sim/sniffer.py'), + '--grpc-server', + server, + '--max-nodes-num', + str(max_nodes_num), ] logging.info('Executing command: %s', ' '.join(cmd)) return subprocess.Popen(cmd) @@ -172,7 +176,7 @@ def main(): sniffer_server_port_base = config['sniffer']['server_port_base'] sniffer_procs = [] for i in range(sniffer_num): - sniffer_procs.append(start_sniffer(addr, i + sniffer_server_port_base, ot_path)) + sniffer_procs.append(start_sniffer(addr, i + sniffer_server_port_base, ot_path, max_nodes_num)) # OTBR firewall scripts create rules inside the Docker container # Run modprobe to load the kernel modules for iptables diff --git a/tools/harness-simulation/posix/simulation.conf b/tools/harness-simulation/posix/simulation.conf index c506f92c9..7c7ab8859 100644 --- a/tools/harness-simulation/posix/simulation.conf +++ b/tools/harness-simulation/posix/simulation.conf @@ -1,5 +1,5 @@ { - "ot_path": "/home/pi/work/src/openthread-pr", + "ot_path": "/home/pi/repo/openthread", "ot_build": { "max_number": 64, "ot": [ diff --git a/tools/harness-simulation/posix/sniffer_sim/sniffer.py b/tools/harness-simulation/posix/sniffer_sim/sniffer.py index 2e4bc8e89..b059cf3da 100644 --- a/tools/harness-simulation/posix/sniffer_sim/sniffer.py +++ b/tools/harness-simulation/posix/sniffer_sim/sniffer.py @@ -58,21 +58,21 @@ class SnifferServicer(sniffer_pb2_grpc.Sniffer): RECV_BUFFER_SIZE = 4096 TIMEOUT = 0.1 - MAX_NODES_NUM = 64 def _reset(self): self._state = CaptureState.NONE self._pcap = None - self._allowed_nodeids = None + self._denied_nodeids = None self._transport = None self._thread = None self._thread_alive.clear() self._pcapng_filename = None self._tshark_proc = None - def __init__(self): + def __init__(self, max_nodes_num): + self._max_nodes_num = max_nodes_num self._thread_alive = threading.Event() - self._mutex = threading.Lock() # for self._allowed_nodeids + self._mutex = threading.Lock() # for self._denied_nodeids self._reset() def Start(self, request, context): @@ -104,8 +104,7 @@ class SnifferServicer(sniffer_pb2_grpc.Sniffer): self._pcap = pcap_codec.PcapCodec(request.channel, fifo_name) # Sniffer all nodes in default, i.e. there is no RF enclosure - # In this case, self._allowed_nodeids is set to None - self._allowed_nodeids = None + self._denied_nodeids = set() # Create transport transport_factory = sniffer_transport.SnifferTransportFactory() @@ -130,10 +129,10 @@ class SnifferServicer(sniffer_pb2_grpc.Sniffer): continue with self._mutex: - allowed_nodeids = self._allowed_nodeids + denied_nodeids = self._denied_nodeids # Equivalent to RF enclosure - if allowed_nodeids is None or nodeid in allowed_nodeids: + if nodeid not in denied_nodeids: self._pcap.append(data) def FilterNodes(self, request, context): @@ -145,14 +144,14 @@ class SnifferServicer(sniffer_pb2_grpc.Sniffer): if not (self._state & CaptureState.THREAD): return sniffer_pb2.FilterNodesResponse(status=sniffer_pb2.OPERATION_ERROR) - allowed_nodeids = set(request.nodeids) + denied_nodeids = set(request.nodeids) # Validate the node IDs - for nodeid in allowed_nodeids: - if not 1 <= nodeid <= self.MAX_NODES_NUM: + for nodeid in denied_nodeids: + if not 1 <= nodeid <= self._max_nodes_num: return sniffer_pb2.FilterNodesResponse(status=sniffer_pb2.VALUE_ERROR) with self._mutex: - self._allowed_nodeids = allowed_nodeids + self._denied_nodeids = denied_nodeids return sniffer_pb2.FilterNodesResponse(status=sniffer_pb2.OK) @@ -182,9 +181,9 @@ class SnifferServicer(sniffer_pb2_grpc.Sniffer): return sniffer_pb2.StopResponse(status=sniffer_pb2.OK, pcap_content=pcap_content) -def serve(address_port): +def serve(address_port, max_nodes_num): server = grpc.server(futures.ThreadPoolExecutor(max_workers=1)) - sniffer_pb2_grpc.add_SnifferServicer_to_server(SnifferServicer(), server) + sniffer_pb2_grpc.add_SnifferServicer_to_server(SnifferServicer(max_nodes_num), server) # add_secure_port requires a web domain server.add_insecure_port(address_port) logging.info('server starts on %s', address_port) @@ -208,9 +207,14 @@ def run_sniffer(): type=str, required=True, help='the address of the sniffer server') + parser.add_argument('--max-nodes-num', + dest='max_nodes_num', + type=int, + required=True, + help='the maximum number of nodes') args = parser.parse_args() - serve(args.grpc_server) + serve(args.grpc_server, args.max_nodes_num) if __name__ == '__main__': diff --git a/tools/harness-thci/OpenThread.py b/tools/harness-thci/OpenThread.py index 8d65cc464..297272f22 100644 --- a/tools/harness-thci/OpenThread.py +++ b/tools/harness-thci/OpenThread.py @@ -3259,6 +3259,16 @@ class OpenThreadTHCI(object): def setVrCheckSkip(self): self.__executeCommand("tvcheck disable") + @API + def addBlockedNodeId(self, node_id): + cmd = 'nodeidfilter deny %d' % node_id + self.__executeCommand(cmd) + + @API + def clearBlockedNodeIds(self): + cmd = 'nodeidfilter clear' + self.__executeCommand(cmd) + class OpenThread(OpenThreadTHCI, IThci):