[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:
whd
2021-04-07 08:40:18 -07:00
committed by GitHub
parent 83cecbb8e3
commit a145d24352
11 changed files with 81 additions and 50 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+3 -2
View File
@@ -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
+6 -13
View File
@@ -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):
+2 -4
View File
@@ -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
View File
@@ -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."""
+12 -1
View File
@@ -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())