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