mirror of
https://github.com/espressif/openthread.git
synced 2026-08-22 10:29:52 +00:00
[ping-sender] let ping return statistics in OTCI (#6370)
- Ping command in CLI will print Done in the end, after printing replies and statistics. The old behavior is, ping command prints Done before any replies. - In OTCI, let ping function return statistics. - Support timeout parameter in ping command. - Fix a bug that the arguments are not properly passed to ping in node.py. - Adjust timeouts in tests.
This commit is contained in:
@@ -53,7 +53,7 @@ extern "C" {
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (94)
|
||||
#define OPENTHREAD_API_VERSION (95)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -115,7 +115,7 @@ typedef struct otPingSenderConfig
|
||||
uint16_t mSize; ///< Data size (# of bytes) excludes IPv6/ICMPv6 header. Zero for default.
|
||||
uint16_t mCount; ///< Number of ping messages to send. Zero to use default.
|
||||
uint32_t mInterval; ///< Ping tx interval in milliseconds. Zero to use default.
|
||||
uint16_t mTimeout; ///< Time in milliseconds to wait for a reply after sending out the request.
|
||||
uint16_t mTimeout; ///< Time in milliseconds to wait for final reply after sending final request.
|
||||
///< Zero to use default.
|
||||
uint8_t mHopLimit; ///< Hop limit (used if `mAllowZeroHopLimit` is false). Zero for default.
|
||||
bool mAllowZeroHopLimit; ///< Indicates whether hop limit is zero.
|
||||
|
||||
+2
-1
@@ -1882,7 +1882,7 @@ Set the preferred Thread Leader Partition ID.
|
||||
Done
|
||||
```
|
||||
|
||||
### ping \<ipaddr\> [size][count] [interval][hoplimit]
|
||||
### ping \<ipaddr\> \[size\] \[count\] \[interval\] \[hoplimit\] \[timeout\]
|
||||
|
||||
Send an ICMPv6 Echo Request.
|
||||
|
||||
@@ -1890,6 +1890,7 @@ Send an ICMPv6 Echo Request.
|
||||
- count: The number of ICMPv6 Echo Requests to be sent.
|
||||
- interval: The interval between two consecutive ICMPv6 Echo Requests in seconds. The value may have fractional form, for example `0.5`.
|
||||
- hoplimit: The hoplimit of ICMPv6 Echo Request to be sent.
|
||||
- timeout: Time in seconds to wait for the final ICMPv6 Echo Reply after sending out the request. The value may have fractional form, for example `3.5`.
|
||||
|
||||
```bash
|
||||
> ping fdde:ad00:beef:0:558:f56b:d688:799
|
||||
|
||||
+12
-2
@@ -3202,7 +3202,8 @@ void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics
|
||||
{
|
||||
OutputFormat("%u packets transmitted, %u packets received.", aStatistics->mSentCount, aStatistics->mReceivedCount);
|
||||
|
||||
if ((aStatistics->mSentCount != 0) && !aStatistics->mIsMulticast)
|
||||
if ((aStatistics->mSentCount != 0) && !aStatistics->mIsMulticast &&
|
||||
aStatistics->mReceivedCount <= aStatistics->mSentCount)
|
||||
{
|
||||
uint32_t packetLossRate =
|
||||
1000 * (aStatistics->mSentCount - aStatistics->mReceivedCount) / aStatistics->mSentCount;
|
||||
@@ -3217,6 +3218,7 @@ void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics
|
||||
}
|
||||
|
||||
OutputLine("");
|
||||
OutputResult(OT_ERROR_NONE);
|
||||
}
|
||||
|
||||
otError Interpreter::ProcessPing(uint8_t aArgsLength, char *aArgs[])
|
||||
@@ -3257,7 +3259,15 @@ otError Interpreter::ProcessPing(uint8_t aArgsLength, char *aArgs[])
|
||||
config.mAllowZeroHopLimit = (config.mHopLimit == 0);
|
||||
}
|
||||
|
||||
VerifyOrExit(aArgsLength <= 5, error = OT_ERROR_INVALID_ARGS);
|
||||
if (aArgsLength > 5)
|
||||
{
|
||||
uint32_t timeout;
|
||||
SuccessOrExit(error = ParsePingInterval(aArgs[5], timeout));
|
||||
VerifyOrExit(timeout <= NumericLimits<uint16_t>::Max(), error = OT_ERROR_INVALID_ARGS);
|
||||
config.mTimeout = static_cast<uint16_t>(timeout);
|
||||
}
|
||||
|
||||
VerifyOrExit(aArgsLength <= 6, error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
config.mReplyCallback = Interpreter::HandlePingReply;
|
||||
config.mStatisticsCallback = Interpreter::HandlePingStatistics;
|
||||
|
||||
@@ -97,7 +97,7 @@ PingSender::PingSender(Instance &aInstance)
|
||||
|
||||
Error PingSender::Ping(const Config &aConfig)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
Error error = kErrorPending;
|
||||
|
||||
VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
|
||||
|
||||
@@ -160,7 +160,7 @@ exit:
|
||||
{
|
||||
mTimer.Start(mConfig.mInterval);
|
||||
}
|
||||
else if (!mStatistics.mIsMulticast)
|
||||
else
|
||||
{
|
||||
mTimer.Start(mConfig.mTimeout);
|
||||
}
|
||||
@@ -200,6 +200,7 @@ void PingSender::HandleIcmpReceive(const Message & aMessage,
|
||||
Reply reply;
|
||||
uint32_t timestamp;
|
||||
|
||||
VerifyOrExit(mTimer.IsRunning());
|
||||
VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeEchoReply);
|
||||
VerifyOrExit(aIcmpHeader.GetId() == mIdentifier);
|
||||
|
||||
|
||||
@@ -120,23 +120,23 @@ class Cert_5_6_9_NetworkDataForwarding(thread_cert.TestCase):
|
||||
self.nodes[ROUTER2].register_netdata()
|
||||
self.simulator.go(15)
|
||||
|
||||
self.assertFalse(self.nodes[SED].ping('2001:2:0:2::1'))
|
||||
self.assertFalse(self.nodes[SED].ping('2001:2:0:2::1', timeout=10))
|
||||
|
||||
self.assertFalse(self.nodes[SED].ping('2007::1'))
|
||||
self.assertFalse(self.nodes[SED].ping('2007::1', timeout=10))
|
||||
|
||||
self.nodes[ROUTER2].remove_prefix('2001:2:0:1::/64')
|
||||
self.nodes[ROUTER2].add_prefix('2001:2:0:1::/64', 'paros', 'high')
|
||||
self.nodes[ROUTER2].register_netdata()
|
||||
self.simulator.go(15)
|
||||
|
||||
self.assertFalse(self.nodes[SED].ping('2007::1'))
|
||||
self.assertFalse(self.nodes[SED].ping('2007::1', timeout=10))
|
||||
|
||||
self.nodes[ROUTER2].remove_prefix('2001:2:0:1::/64')
|
||||
self.nodes[ROUTER2].add_prefix('2001:2:0:1::/64', 'paros', 'med')
|
||||
self.nodes[ROUTER2].register_netdata()
|
||||
self.simulator.go(15)
|
||||
|
||||
self.assertFalse(self.nodes[SED].ping('2007::1'))
|
||||
self.assertFalse(self.nodes[SED].ping('2007::1', timeout=10))
|
||||
|
||||
def verify(self, pv):
|
||||
pkts = pv.pkts
|
||||
|
||||
@@ -208,8 +208,8 @@ class Cert_9_2_09_PendingPartition(thread_cert.TestCase):
|
||||
|
||||
leader_addr = self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.ML_EID)
|
||||
router1_addr = self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.ML_EID)
|
||||
self.assertTrue(self.nodes[ROUTER2].ping(leader_addr))
|
||||
self.assertTrue(self.nodes[COMMISSIONER].ping(router1_addr))
|
||||
self.assertTrue(self.nodes[ROUTER2].ping(leader_addr, timeout=10))
|
||||
self.assertTrue(self.nodes[COMMISSIONER].ping(router1_addr, timeout=10))
|
||||
|
||||
def verify(self, pv):
|
||||
pkts = pv.pkts
|
||||
|
||||
@@ -1751,28 +1751,23 @@ class NodeImpl:
|
||||
return self._expect_results(
|
||||
r'\|\s(\S+)\s+\|\s(\S+)\s+\|\s([0-9a-fA-F]{4})\s\|\s([0-9a-fA-F]{16})\s\|\s(\d+)')
|
||||
|
||||
def ping(self, ipaddr, num_responses=1, size=None, timeout=5):
|
||||
cmd = 'ping %s' % ipaddr
|
||||
if size is not None:
|
||||
cmd += ' %d' % size
|
||||
def ping(self, ipaddr, num_responses=1, size=8, timeout=5, count=1, interval=1, hoplimit=64):
|
||||
cmd = f'ping {ipaddr} {size} {count} {interval} {hoplimit} {timeout}'
|
||||
|
||||
self.send_command(cmd)
|
||||
|
||||
end = self.simulator.now() + timeout
|
||||
wait_allowance = 3
|
||||
end = self.simulator.now() + timeout + wait_allowance
|
||||
|
||||
responders = {}
|
||||
|
||||
result = True
|
||||
# ncp-sim doesn't print Done
|
||||
done = (self.node_type == 'ncp-sim')
|
||||
|
||||
# ncp-sim doesn't print statistics
|
||||
received_statistics = (self.node_type == 'ncp-sim')
|
||||
is_multicast = ipaddress.IPv6Address(ipaddr).is_multicast
|
||||
while len(responders) < num_responses or not done or (not is_multicast and not received_statistics):
|
||||
while len(responders) < num_responses or not done:
|
||||
self.simulator.go(1)
|
||||
try:
|
||||
i = self._expect([r'from (\S+):', r'Done', r'packets transmitted'], timeout=0.1)
|
||||
i = self._expect([r'from (\S+):', r'Done'], timeout=0.1)
|
||||
except (pexpect.TIMEOUT, socket.timeout):
|
||||
if self.simulator.now() < end:
|
||||
continue
|
||||
@@ -1785,8 +1780,6 @@ class NodeImpl:
|
||||
responders[self.pexpect.match.groups()[0]] = 1
|
||||
elif i == 1:
|
||||
done = True
|
||||
elif i == 2:
|
||||
received_statistics = True
|
||||
return result
|
||||
|
||||
def reset(self):
|
||||
|
||||
@@ -76,9 +76,7 @@ class OtCliCommandRunner(OTCommandHandler):
|
||||
r')')
|
||||
"""regex used to filter logs"""
|
||||
|
||||
__ASYNC_COMMANDS = {
|
||||
'scan',
|
||||
}
|
||||
__ASYNC_COMMANDS = {'scan', 'ping'}
|
||||
|
||||
def __init__(self, otcli: OtCliHandler, is_spinel_cli=False):
|
||||
self.__otcli: OtCliHandler = otcli
|
||||
@@ -94,7 +92,7 @@ class OtCliCommandRunner(OTCommandHandler):
|
||||
def __repr__(self):
|
||||
return repr(self.__otcli)
|
||||
|
||||
def execute_command(self, cmd, timeout=10) -> None:
|
||||
def execute_command(self, cmd, timeout=10) -> List[str]:
|
||||
self.__otcli.writeline(cmd)
|
||||
|
||||
if cmd in {'reset', 'factoryreset'}:
|
||||
|
||||
+36
-19
@@ -174,31 +174,48 @@ class OTCI(object):
|
||||
#
|
||||
# Network Operations
|
||||
#
|
||||
_PING_STATISTICS_PATTERN = re.compile(
|
||||
r'^(?P<transmitted>\d+) packets transmitted, (?P<received>\d+) packets received.(?: Packet loss = (?P<loss>\d+\.\d+)%.)?(?: Round-trip min/avg/max = (?P<min>\d+)/(?P<avg>\d+\.\d+)/(?P<max>\d+) ms.)?$'
|
||||
)
|
||||
|
||||
def ping(self, ip: str, size: int = None, count: int = None, interval: int = None, hoplimit: int = None):
|
||||
"""Send an ICMPv6 Echo Request.
|
||||
def ping(self,
|
||||
ip: str,
|
||||
size: int = 8,
|
||||
count: int = 1,
|
||||
interval: float = 1,
|
||||
hoplimit: int = 64,
|
||||
timeout: float = 3) -> Dict:
|
||||
"""Send an ICMPv6 Echo Request.
|
||||
The default arguments are consistent with https://github.com/openthread/openthread/blob/main/src/core/utils/ping_sender.hpp.
|
||||
|
||||
:param ip: The target IPv6 address to ping.
|
||||
:param size: The number of data bytes in the payload.
|
||||
:param count: The number of ICMPv6 Echo Requests to be sent.
|
||||
:param interval: The interval between two consecutive ICMPv6 Echo Requests in seconds. The value may have fractional form, for example 0.5.
|
||||
:param hoplimit: The hoplimit of ICMPv6 Echo Request to be sent.
|
||||
:param size: The number of data bytes in the payload. Default is 8.
|
||||
:param count: The number of ICMPv6 Echo Requests to be sent. Default is 1.
|
||||
:param interval: The interval between two consecutive ICMPv6 Echo Requests in seconds. The value may have fractional form, for example 0.5. Default is 1.
|
||||
:param hoplimit: The hoplimit of ICMPv6 Echo Request to be sent. Default is 64. See OPENTHREAD_CONFIG_IP6_HOP_LIMIT_DEFAULT in src/core/config/ip6.h.
|
||||
:param timeout: The maximum duration in seconds for the ping command to wait after the final echo request is sent. Default is 3.
|
||||
"""
|
||||
cmd = f'ping {ip}'
|
||||
cmd = f'ping {ip} {size} {count} {interval} {hoplimit} {timeout}'
|
||||
|
||||
if size is not None:
|
||||
cmd += f' {size}'
|
||||
timeout_allowance = 3
|
||||
lines = self.execute_command(cmd, timeout=(count - 1) * interval + timeout + timeout_allowance)
|
||||
|
||||
if count is not None:
|
||||
cmd += f' {count}'
|
||||
|
||||
if interval is not None:
|
||||
cmd += f' {interval}'
|
||||
|
||||
if hoplimit is not None:
|
||||
cmd += f' {hoplimit}'
|
||||
|
||||
self.execute_command(cmd)
|
||||
statistics = {}
|
||||
for line in lines:
|
||||
m = OTCI._PING_STATISTICS_PATTERN.match(line)
|
||||
if m is not None:
|
||||
if m.group('transmitted') is not None:
|
||||
statistics['transmitted_packets'] = int(m.group('transmitted'))
|
||||
statistics['received_packets'] = int(m.group('received'))
|
||||
if m.group('loss') is not None:
|
||||
statistics['packet_loss'] = float(m.group('loss')) / 100
|
||||
if m.group('min') is not None:
|
||||
statistics['round_trip_time'] = {
|
||||
'min': int(m.group('min')),
|
||||
'avg': float(m.group('avg')),
|
||||
'max': int(m.group('max'))
|
||||
}
|
||||
return statistics
|
||||
|
||||
def ping_stop(self):
|
||||
"""Stop sending ICMPv6 Echo Requests."""
|
||||
|
||||
@@ -402,7 +402,12 @@ class TestOTCI(unittest.TestCase):
|
||||
self.assertEqual('router', commissioner.get_state())
|
||||
|
||||
for dst_ip in leader.get_ipaddrs():
|
||||
commissioner.ping(dst_ip, size=10, count=1, interval=2, hoplimit=3)
|
||||
statistics = commissioner.ping(dst_ip, size=10, count=10, interval=2, hoplimit=3)
|
||||
self.assertEqual(statistics['transmitted_packets'], 10)
|
||||
self.assertEqual(statistics['received_packets'], 10)
|
||||
self.assertAlmostEqual(statistics['packet_loss'], 0.0, delta=1e-9)
|
||||
rtt = statistics['round_trip_time']
|
||||
self.assertTrue(rtt['min'] - 1e-9 <= rtt['avg'] <= rtt['max'] + 1e-9)
|
||||
commissioner.wait(1)
|
||||
|
||||
self.assertEqual('disabled', commissioner.get_commissioiner_state())
|
||||
@@ -513,6 +518,12 @@ class TestOTCI(unittest.TestCase):
|
||||
|
||||
self.assertFalse(leader.is_singleton())
|
||||
|
||||
statistics = commissioner.ping("ff02::1", size=1, count=10, interval=1, hoplimit=255)
|
||||
self.assertEqual(statistics['transmitted_packets'], 10)
|
||||
self.assertEqual(statistics['received_packets'], 20)
|
||||
rtt = statistics['round_trip_time']
|
||||
self.assertTrue(rtt['min'] - 1e-9 <= rtt['avg'] <= rtt['max'] + 1e-9)
|
||||
|
||||
# Shutdown
|
||||
leader.thread_stop()
|
||||
logging.info("node state: %s", leader.get_state())
|
||||
|
||||
Reference in New Issue
Block a user