mirror of
https://github.com/espressif/openthread.git
synced 2026-06-06 05:24:51 +00:00
[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.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
@@ -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] <br/> or <br/>[DUT and Commissioner] <br/>in an RF enclosure ", "Shield Devices"):
|
||||
test_9_2_9_leader.placeRFEnclosure,
|
||||
("Remove [Router1, Router2 and Sniffer] <br/> or <br/>[DUT and Commissioner] <br/>from RF enclosure ", "Unshield Devices"):
|
||||
test_9_2_9_leader.removeRFEnclosure,
|
||||
("Place [DUT,Router2 and sniffer] <br/> or <br/>[Leader and Commissioner] <br/>in an RF enclosure ", "Shield Devices"):
|
||||
test_9_2_9_router.placeRFEnclosure,
|
||||
("Remove [DUT, Router2 and sniffer] <br/> or <br/>[Leader and Commissioner] <br/>from RF enclosure ", "Unshield Devices"):
|
||||
test_9_2_9_router.removeRFEnclosure,
|
||||
("Place the <br/> [Leader and Commissioner] devices <br/> in an RF enclosure ", "Shield Devices"):
|
||||
test_9_2_10_router.placeRFEnclosure,
|
||||
("Remove <br/>[Leader and Commissioner] <br/>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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"ot_path": "/home/pi/work/src/openthread-pr",
|
||||
"ot_path": "/home/pi/repo/openthread",
|
||||
"ot_build": {
|
||||
"max_number": 64,
|
||||
"ot": [
|
||||
|
||||
@@ -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__':
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user