From 8f6d6703a26b617cd7bbec624f74059c6706adef Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 11 Oct 2023 11:37:34 -0700 Subject: [PATCH] [ping-sender] allow setting multicast loop (#9494) This commit updates `PingSender` and its config to include a `mMulticastLoop` flag and allow the caller to set this flag. When set, multicast ping echo request messages are looped back and received by the device itself if it is subscribed to the same address. The `ping` CLI command is also updated to allow setting this flag. This commit also updates `test-008-multicast-traffic.py` to add new test cases to cover the multicast loop flag behavior (pinging with or without this flag). In particular, it covers when the ping destination is a realm-local multicast address and when it is a larger-than-realm-local multicast address (where the `ot::Ip6` module would use a tunnel header for forwarding the message). --- include/openthread/instance.h | 2 +- include/openthread/ping_sender.h | 1 + src/cli/README.md | 5 +- src/cli/cli.cpp | 11 ++- src/core/utils/ping_sender.cpp | 1 + .../toranj/cli/test-008-multicast-traffic.py | 94 ++++++++++++++++++- 6 files changed, 108 insertions(+), 6 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index d450a0218..782a0b353 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 (363) +#define OPENTHREAD_API_VERSION (364) /** * @addtogroup api-instance diff --git a/include/openthread/ping_sender.h b/include/openthread/ping_sender.h index 174cccc25..28b5f9a6c 100644 --- a/include/openthread/ping_sender.h +++ b/include/openthread/ping_sender.h @@ -120,6 +120,7 @@ typedef struct otPingSenderConfig ///< 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. + bool mMulticastLoop; ///< Allow looping back pings to multicast address that device is subscribed to. } otPingSenderConfig; /** diff --git a/src/cli/README.md b/src/cli/README.md index f10d25100..e13f03204 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -89,7 +89,7 @@ Done - [parent](#parent) - [parentpriority](#parentpriority) - [partitionid](#partitionid) -- [ping](#ping-async--i-source-ipaddr-size-count-interval-hoplimit-timeout) +- [ping](#ping-async--i-source--m-ipaddr-size-count-interval-hoplimit-timeout) - [platform](#platform) - [pollperiod](#pollperiod-pollperiod) - [preferrouterid](#preferrouterid-routerid) @@ -2728,12 +2728,13 @@ Set the preferred Thread Leader Partition ID. Done ``` -### ping \[async\] \[-I source\] \ \[size\] \[count\] \[interval\] \[hoplimit\] \[timeout\] +### ping \[async\] \[-I source\] \[-m] \ \[size\] \[count\] \[interval\] \[hoplimit\] \[timeout\] Send an ICMPv6 Echo Request. - async: Use the non-blocking mode. New commands are allowed before the ping process terminates. - source: The source IPv6 address of the echo request. +- -m: multicast loop, which allows looping back pings to multicast addresses that the device itself is subscribed to. - size: The number of data bytes to be sent. - 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`. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index a2be21902..381d92bf2 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -6019,7 +6019,7 @@ void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics * 1 packets transmitted, 1 packets received. Packet loss = 0.0%. Round-trip min/avg/max = 0/0.0/0 ms. * Done * @endcode - * @cparam ping [@ca{async}] [@ca{-I source}] @ca{ipaddrc} [@ca{size}] [@ca{count}] [@ca{interval}] [@ca{hoplimit}] [@ca{timeout}] * @par * Send an ICMPv6 Echo Request. @@ -6027,6 +6027,9 @@ void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics * The address can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix * from the network data. * @par + * The optional `-m` flag sets the multicast loop flag, which allows looping back pings to multicast addresses that the + * device itself is subscribed to. + * @par * Note: The command will return InvalidState when the preferred NAT64 prefix is unavailable. * @sa otPingSenderPing */ @@ -6085,6 +6088,12 @@ template <> otError Interpreter::Process(Arg aArgs[]) aArgs += 2; } + if (aArgs[0] == "-m") + { + config.mMulticastLoop = true; + aArgs++; + } + SuccessOrExit(error = ParseToIp6Address(GetInstancePtr(), aArgs[0], config.mDestination, nat64SynthesizedAddress)); if (nat64SynthesizedAddress) { diff --git a/src/core/utils/ping_sender.cpp b/src/core/utils/ping_sender.cpp index d1565ef78..9949f2a0e 100644 --- a/src/core/utils/ping_sender.cpp +++ b/src/core/utils/ping_sender.cpp @@ -134,6 +134,7 @@ void PingSender::SendPing(void) messageInfo.SetPeerAddr(mConfig.GetDestination()); messageInfo.mHopLimit = mConfig.mHopLimit; messageInfo.mAllowZeroHopLimit = mConfig.mAllowZeroHopLimit; + messageInfo.mMulticastLoop = mConfig.mMulticastLoop; message = Get().NewMessage(); VerifyOrExit(message != nullptr); diff --git a/tests/toranj/cli/test-008-multicast-traffic.py b/tests/toranj/cli/test-008-multicast-traffic.py index 7623a9c5a..d83bd1f06 100755 --- a/tests/toranj/cli/test-008-multicast-traffic.py +++ b/tests/toranj/cli/test-008-multicast-traffic.py @@ -53,8 +53,17 @@ import time # - r1 =>> mesh-local all-thread (two hops). Expected response from [r2, r3, fed]. # - r1 =>> mesh-local all-thread (three hops). Expected response from [r2, r3, r4, fed]. # - r1 =>> mesh-local all-thread (four hops). Expected response from [r2, r3, r4, fed, sed]. -# - r1 =>> specific address (on r2 and sed). Expected to receive on [r2, sed]. # +# - r1 =>> realm-local scope mcast addr (on r2 and sed). Expected to receive on [r2, sed]. +# - r2 =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [sed]. +# - r2 =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed]. +# - sed =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [r2]. +# - sed =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed]. +# +# - r3 =>> site-local mcast addr (on r1, r2, sed). Expected to receive from [r1, r2, sed]. +# - r1 =>> site-local mcast addr (on r1, r2, sed) with `multicast-loop`. Expected to receive from [r1, r2, sed]. +# - r1 =>> site-local mcast addr (on r1, r2, sed) without `multicast-loop`. Expected to receive from [r2, sed]. +# - sed =>> site-local mcast addr (on r1, r2, sed). Expected to receive from [r1, r2]. test_name = __file__[:-3] if __file__.endswith('.py') else __file__ print('-' * 120) @@ -223,7 +232,7 @@ for node in [r2, r3, r4, fed, sed]: verify(any(ml_addr in line for line in outputs)) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Subscribe to a specific multicast address on r2 and sed +# Subscribe to a realm-local scope multicast address on r2 and sed mcast_addr = 'ff03:0:0:0:0:0:0:114' r2.add_ip_maddr(mcast_addr) @@ -234,12 +243,93 @@ verify(any(mcast_addr in maddr for maddr in maddrs)) maddrs = sed.get_ip_maddrs() verify(any(mcast_addr in maddr for maddr in maddrs)) +# r1 =>> realm-local scope mcast addr (on r2 and sed). Expected to receive on [r2, sed]. + outputs = r1.cli('ping', mcast_addr) verify(len(outputs) == 3) for node in [r2, sed]: ml_addr = node.get_mleid_ip_addr() verify(any(ml_addr in line for line in outputs)) +# r2 =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [sed]. + +outputs = r2.cli('ping', mcast_addr) +verify(len(outputs) == 2) +for node in [sed]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# r2 =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed]. + +outputs = r2.cli('ping', '-m', mcast_addr) +verify(len(outputs) == 3) +for node in [r2, sed]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# sed =>> realm-local scope mcast addr (on r2 and sed) without `multicast-loop`. Expected to receive from [r2]. + +outputs = sed.cli('ping', mcast_addr) +verify(len(outputs) == 2) +for node in [r2]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# sed =>> realm-local scope mcast addr (on r2 and sed) with `multicast-loop`. Expected to receive from [r2, sed]. + +outputs = sed.cli('ping', '-m', mcast_addr) +verify(len(outputs) == 3) +for node in [r2, sed]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Subscribe to a larger than realm-local scope (site-local) multicast address on r1, r2, and sed + +mcast_addr = 'ff05:0:0:0:0:0:0:abcd' +r1.add_ip_maddr(mcast_addr) +r2.add_ip_maddr(mcast_addr) +sed.add_ip_maddr(mcast_addr) +time.sleep(1) +maddrs = r1.get_ip_maddrs() +verify(any(mcast_addr in maddr for maddr in maddrs)) +maddrs = r2.get_ip_maddrs() +verify(any(mcast_addr in maddr for maddr in maddrs)) +maddrs = sed.get_ip_maddrs() +verify(any(mcast_addr in maddr for maddr in maddrs)) + +# r3 =>> site-local mcast addr (on r1, r2, sed) with `multicast-loop`. Expected to receive from [r1, r2, sed]. + +outputs = r3.cli('ping', '-m', mcast_addr) +verify(len(outputs) == 4) +for node in [r1, r2, sed]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# r1 =>> site-local mcast addr (on r1, r2, sed) with `multicast-loop`. Expected to receive from [r1, r2, sed]. + +outputs = r1.cli('ping', '-m', mcast_addr) +verify(len(outputs) == 4) +for node in [r1, r2, sed]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# r1 =>> site-local mcast addr (on r1, r2, sed) without `multicast-loop`. Expected to receive from [r2, sed]. + +outputs = r1.cli('ping', mcast_addr) +verify(len(outputs) == 3) +for node in [r2, sed]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + +# sed =>> site-local mcast addr (on r1, r2, sed) without `multicast-loop`. Expected to receive from [r1, r2]. + +outputs = sed.cli('ping', mcast_addr) +verify(len(outputs) >= 3) +for node in [r1, r2]: + ml_addr = node.get_mleid_ip_addr() + verify(any(ml_addr in line for line in outputs)) + # ----------------------------------------------------------------------------------------------------------------------- # Test finished