mirror of
https://github.com/espressif/openthread.git
synced 2026-08-09 04:07:47 +00:00
[posix] add support for multicast group join (#4687)
Listen for mld reports sent by kernel to capture user multicast group join and forward the addresses to OpenThread interface. See openthread/wpantund#444 for more background.
This commit is contained in:
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2020, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ipaddress import ip_address
|
||||
|
||||
|
||||
def get_maddrs():
|
||||
lines = subprocess.run(
|
||||
['ot-ctl', 'ipmaddr'], stdout=subprocess.PIPE).stdout.decode().split()
|
||||
return [ip_address(l) for l in lines if l.startswith('ff')]
|
||||
|
||||
|
||||
def main():
|
||||
group = 'ff02::158'
|
||||
if_index = int(sys.argv[1])
|
||||
|
||||
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s:
|
||||
s.setsockopt(socket.IPPROTO_IPV6,
|
||||
socket.IPV6_MULTICAST_IF,
|
||||
if_index)
|
||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP,
|
||||
struct.pack('16si', socket.inet_pton(socket.AF_INET6, group),
|
||||
if_index))
|
||||
time.sleep(2)
|
||||
maddrs = get_maddrs()
|
||||
print(maddrs)
|
||||
if not any(addr == ip_address(group) for addr in maddrs):
|
||||
return -1
|
||||
|
||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_LEAVE_GROUP,
|
||||
struct.pack('16si', socket.inet_pton(socket.AF_INET6, group),
|
||||
if_index))
|
||||
|
||||
time.sleep(2)
|
||||
maddrs = get_maddrs()
|
||||
print(maddrs)
|
||||
if any(addr == ip_address(group) for addr in maddrs):
|
||||
return -1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
+13
-3
@@ -72,16 +72,17 @@ check() {
|
||||
RADIO_NCP_PATH="$(pwd)/$(ls output/*linux*/bin/ot-rcp)"
|
||||
$RADIO_NCP_PATH 1 > $RADIO_PTY < $RADIO_PTY &
|
||||
|
||||
# Cover setting a valid network interface name.
|
||||
readonly VALID_NETIF_NAME="wan$(date +%H%M%S)"
|
||||
|
||||
if [[ "${DAEMON}" = 1 ]]; then
|
||||
sudo "$(pwd)/$(ls output/posix/*linux*/bin/ot-daemon)" ${CORE_PTY} &
|
||||
sudo "$(pwd)/$(ls output/posix/*linux*/bin/ot-daemon)" -I "${VALID_NETIF_NAME}" ${CORE_PTY} &
|
||||
sleep 1
|
||||
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-ctl)"
|
||||
else
|
||||
OT_CLI_CMD="$(pwd)/$(ls output/posix/*linux*/bin/ot-cli) ${CORE_PTY}"
|
||||
fi
|
||||
|
||||
# Cover setting a valid network interface name.
|
||||
readonly VALID_NETIF_NAME="wan$(date +%H%M%S)"
|
||||
sudo ${OT_CLI_CMD} -I "${VALID_NETIF_NAME}" -n
|
||||
|
||||
# Cover setting a too long(max is 15 characters) network interface name.
|
||||
@@ -129,6 +130,15 @@ EOF
|
||||
extaddr=$(awk '/extaddr/{getline; print}' $OT_OUTPUT | tr -d '\r\n')
|
||||
echo "Extended address is: ${extaddr}"
|
||||
|
||||
if [[ "${DAEMON}" = 1 ]]; then
|
||||
sudo killall -9 expect || true
|
||||
sudo killall -9 ot-ctl || true
|
||||
NETIF_INDEX=$(ip link show "${VALID_NETIF_NAME}" | cut -f 1 -d ":" | head -n 1)
|
||||
sudo PATH="$(dirname ${OT_CLI_CMD}):${PATH}" \
|
||||
python3 "$(pwd)/.travis/test_multicast_join.py" "${NETIF_INDEX}" \
|
||||
|| die 'multicast group join failed'
|
||||
fi
|
||||
|
||||
LEADER_ALOC=fdde:ad00:beef::ff:fe00:fc00
|
||||
# Retrievie extended address through network diagnostic get
|
||||
coap_response=$(echo -n '120100' | xxd -r -p | coap-client -m POST coap://[${LEADER_ALOC}]:61631/d/dg -f- | xxd -p | grep 0008)
|
||||
|
||||
+179
-38
@@ -79,12 +79,43 @@ struct in6_ifreq
|
||||
};
|
||||
#endif
|
||||
|
||||
static otInstance * sInstance = NULL;
|
||||
static int sTunFd = -1; ///< Used to exchange IPv6 packets.
|
||||
static int sIpFd = -1; ///< Used to manage IPv6 stack on Thread interface.
|
||||
static int sNetlinkFd = -1; ///< Used to receive netlink events.
|
||||
static unsigned int sTunIndex = 0;
|
||||
static otInstance * sInstance = NULL;
|
||||
static int sTunFd = -1; ///< Used to exchange IPv6 packets.
|
||||
static int sIpFd = -1; ///< Used to manage IPv6 stack on Thread interface.
|
||||
static int sNetlinkFd = -1; ///< Used to receive netlink events.
|
||||
static int sMLDMonitorFd = -1; ///< Used to receive MLD events.
|
||||
static unsigned int sTunIndex = 0;
|
||||
static char sTunName[IFNAMSIZ];
|
||||
// ff02::16
|
||||
static const otIp6Address kMLDv2MulticastAddress = {
|
||||
{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16}}};
|
||||
|
||||
OT_TOOL_PACKED_BEGIN
|
||||
struct MLDv2Header
|
||||
{
|
||||
uint8_t mType;
|
||||
uint8_t _rsv0;
|
||||
uint16_t mChecksum;
|
||||
uint16_t _rsv1;
|
||||
uint16_t mNumRecords;
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
OT_TOOL_PACKED_BEGIN
|
||||
struct MLDv2Record
|
||||
{
|
||||
uint8_t mRecordType;
|
||||
uint8_t mAuxDataLen;
|
||||
uint16_t mNumSources;
|
||||
struct in6_addr mMulticastAddress;
|
||||
struct in6_addr mSourceAddresses[];
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
enum
|
||||
{
|
||||
kICMPv6MLDv2Type = 143,
|
||||
kICMPv6MLDv2RecordChangeToExcludeType = 3,
|
||||
kICMPv6MLDv2RecordChangeToIncludeType = 4,
|
||||
};
|
||||
|
||||
static const size_t kMaxIp6Size = 1536;
|
||||
|
||||
@@ -404,18 +435,136 @@ void platformNetifDeinit(void)
|
||||
sNetlinkFd = -1;
|
||||
}
|
||||
|
||||
if (sMLDMonitorFd != -1)
|
||||
{
|
||||
close(sMLDMonitorFd);
|
||||
sMLDMonitorFd = -1;
|
||||
}
|
||||
|
||||
sTunIndex = 0;
|
||||
}
|
||||
|
||||
static void mldListenerInit(void)
|
||||
{
|
||||
struct ipv6_mreq mreq6;
|
||||
|
||||
sMLDMonitorFd = SocketWithCloseExec(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6);
|
||||
mreq6.ipv6mr_interface = sTunIndex;
|
||||
memcpy(&mreq6.ipv6mr_multiaddr, kMLDv2MulticastAddress.mFields.m8, sizeof(kMLDv2MulticastAddress.mFields.m8));
|
||||
|
||||
VerifyOrDie(setsockopt(sMLDMonitorFd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)) == 0, OT_EXIT_FAILURE);
|
||||
VerifyOrDie(setsockopt(sMLDMonitorFd, SOL_SOCKET, SO_BINDTODEVICE, sTunName,
|
||||
static_cast<socklen_t>(strnlen(sTunName, IFNAMSIZ))) == 0,
|
||||
OT_EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void processMLDEvent(otInstance *aInstance)
|
||||
{
|
||||
const size_t kMaxMLDEvent = 8192;
|
||||
uint8_t buffer[kMaxMLDEvent];
|
||||
ssize_t bufferLen = -1;
|
||||
struct sockaddr_in6 srcAddr;
|
||||
socklen_t addrLen;
|
||||
bool fromSelf = false;
|
||||
MLDv2Header * hdr = reinterpret_cast<MLDv2Header *>(buffer);
|
||||
size_t offset;
|
||||
uint8_t type;
|
||||
struct ifaddrs * ifAddrs = NULL;
|
||||
|
||||
bufferLen = recvfrom(sMLDMonitorFd, buffer, sizeof(buffer), 0, reinterpret_cast<sockaddr *>(&srcAddr), &addrLen);
|
||||
VerifyOrExit(bufferLen > 0);
|
||||
|
||||
type = buffer[0];
|
||||
VerifyOrExit(type == kICMPv6MLDv2Type && bufferLen >= static_cast<ssize_t>(sizeof(MLDv2Header)));
|
||||
|
||||
// Check whether it is sent by self
|
||||
VerifyOrExit(getifaddrs(&ifAddrs) == 0);
|
||||
for (struct ifaddrs *ifAddr = ifAddrs; ifAddr != NULL; ifAddr = ifAddr->ifa_next)
|
||||
{
|
||||
if (ifAddr->ifa_addr != NULL && ifAddr->ifa_addr->sa_family == AF_INET6 &&
|
||||
strncmp(sTunName, ifAddr->ifa_name, IFNAMSIZ) == 0)
|
||||
{
|
||||
struct sockaddr_in6 *addr6 = reinterpret_cast<struct sockaddr_in6 *>(ifAddr->ifa_addr);
|
||||
|
||||
if (memcmp(&addr6->sin6_addr, &srcAddr.sin6_addr, sizeof(in6_addr)) == 0)
|
||||
{
|
||||
fromSelf = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VerifyOrExit(fromSelf);
|
||||
|
||||
hdr = reinterpret_cast<MLDv2Header *>(buffer);
|
||||
offset = sizeof(MLDv2Header);
|
||||
|
||||
for (size_t i = 0; i < ntohs(hdr->mNumRecords) && offset < static_cast<size_t>(bufferLen); i++)
|
||||
{
|
||||
if (static_cast<size_t>(bufferLen) >= (sizeof(MLDv2Record) + offset))
|
||||
{
|
||||
MLDv2Record *record = reinterpret_cast<MLDv2Record *>(&buffer[offset]);
|
||||
|
||||
otError err;
|
||||
otIp6Address address;
|
||||
char addressString[INET6_ADDRSTRLEN + 1];
|
||||
|
||||
memcpy(&address.mFields.m8, &record->mMulticastAddress, sizeof(address.mFields.m8));
|
||||
inet_ntop(AF_INET6, &record->mMulticastAddress, addressString, sizeof(addressString));
|
||||
if (record->mRecordType == kICMPv6MLDv2RecordChangeToIncludeType)
|
||||
{
|
||||
err = otIp6SubscribeMulticastAddress(aInstance, &address);
|
||||
if (err == OT_ERROR_ALREADY)
|
||||
{
|
||||
otLogNotePlat(
|
||||
"Will not subscribe duplicate multicast address %s",
|
||||
inet_ntop(AF_INET6, &record->mMulticastAddress, addressString, sizeof(addressString)));
|
||||
}
|
||||
else if (err != OT_ERROR_NONE)
|
||||
{
|
||||
otLogWarnPlat("Failed to subscribe multicast address %s: %s", addressString,
|
||||
otThreadErrorToString(err));
|
||||
}
|
||||
else
|
||||
{
|
||||
otLogDebgPlat("Subscribed multicast address %s", addressString);
|
||||
}
|
||||
}
|
||||
else if (record->mRecordType == kICMPv6MLDv2RecordChangeToExcludeType)
|
||||
{
|
||||
err = otIp6UnsubscribeMulticastAddress(aInstance, &address);
|
||||
if (err != OT_ERROR_NONE)
|
||||
{
|
||||
otLogWarnPlat("Failed to unsubscribe multicast address %s: %s", addressString,
|
||||
otThreadErrorToString(err));
|
||||
}
|
||||
else
|
||||
{
|
||||
otLogDebgPlat("Unsubscribed multicast address %s", addressString);
|
||||
}
|
||||
}
|
||||
|
||||
offset += sizeof(MLDv2Record) + sizeof(in6_addr) * ntohs(record->mNumSources);
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (ifAddrs)
|
||||
{
|
||||
freeifaddrs(ifAddrs);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void platformNetifInit(otInstance *aInstance, const char *aInterfaceName)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
|
||||
sIpFd = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
||||
VerifyOrExit(sIpFd >= 0);
|
||||
VerifyOrDie(sIpFd >= 0, OT_EXIT_ERROR_ERRNO);
|
||||
|
||||
sNetlinkFd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||
VerifyOrExit(sNetlinkFd > 0);
|
||||
VerifyOrDie(sNetlinkFd > 0, OT_EXIT_ERROR_ERRNO);
|
||||
|
||||
otIcmp6SetEchoMode(aInstance, OT_ICMP6_ECHO_HANDLER_DISABLED);
|
||||
|
||||
@@ -425,11 +574,11 @@ void platformNetifInit(otInstance *aInstance, const char *aInterfaceName)
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.nl_family = AF_NETLINK;
|
||||
sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV6_IFADDR;
|
||||
VerifyOrExit(bind(sNetlinkFd, reinterpret_cast<struct sockaddr *>(&sa), sizeof(sa)) == 0);
|
||||
VerifyOrDie(bind(sNetlinkFd, reinterpret_cast<struct sockaddr *>(&sa), sizeof(sa)) == 0, OT_EXIT_ERROR_ERRNO);
|
||||
}
|
||||
|
||||
sTunFd = open(OPENTHREAD_POSIX_TUN_DEVICE, O_RDWR | O_CLOEXEC);
|
||||
VerifyOrExit(sTunFd > 0, otLogCritPlat("Unable to open tun device %s", OPENTHREAD_POSIX_TUN_DEVICE));
|
||||
VerifyOrDie(sTunFd > 0, OT_EXIT_ERROR_ERRNO);
|
||||
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
||||
@@ -445,47 +594,22 @@ void platformNetifInit(otInstance *aInstance, const char *aInterfaceName)
|
||||
strncpy(ifr.ifr_name, "wpan%d", IFNAMSIZ);
|
||||
}
|
||||
|
||||
VerifyOrExit(ioctl(sTunFd, TUNSETIFF, static_cast<void *>(&ifr)) == 0,
|
||||
otLogCritPlat("Unable to configure tun device %s", OPENTHREAD_POSIX_TUN_DEVICE));
|
||||
VerifyOrExit(ioctl(sTunFd, TUNSETLINK, ARPHRD_VOID) == 0,
|
||||
otLogCritPlat("Unable to set link type of tun device %s", OPENTHREAD_POSIX_TUN_DEVICE));
|
||||
VerifyOrDie(ioctl(sTunFd, TUNSETIFF, static_cast<void *>(&ifr)) == 0, OT_EXIT_ERROR_ERRNO);
|
||||
VerifyOrDie(ioctl(sTunFd, TUNSETLINK, ARPHRD_VOID) == 0, OT_EXIT_ERROR_ERRNO);
|
||||
|
||||
sTunIndex = if_nametoindex(ifr.ifr_name);
|
||||
VerifyOrExit(sTunIndex > 0);
|
||||
VerifyOrDie(sTunIndex > 0, OT_EXIT_FAILURE);
|
||||
|
||||
strncpy(sTunName, ifr.ifr_name, sizeof(sTunName));
|
||||
#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
|
||||
platformUdpInit(sTunName);
|
||||
#endif
|
||||
mldListenerInit();
|
||||
|
||||
otIp6SetReceiveCallback(aInstance, processReceive, aInstance);
|
||||
otIp6SetAddressCallback(aInstance, processAddressChange, aInstance);
|
||||
otSetStateChangedCallback(aInstance, processStateChange, aInstance);
|
||||
sInstance = aInstance;
|
||||
|
||||
exit:
|
||||
if (sTunIndex == 0)
|
||||
{
|
||||
if (sTunFd != -1)
|
||||
{
|
||||
close(sTunFd);
|
||||
sTunFd = -1;
|
||||
}
|
||||
|
||||
if (sIpFd != -1)
|
||||
{
|
||||
close(sIpFd);
|
||||
sIpFd = -1;
|
||||
}
|
||||
|
||||
if (sNetlinkFd != -1)
|
||||
{
|
||||
close(sNetlinkFd);
|
||||
sNetlinkFd = -1;
|
||||
}
|
||||
|
||||
DieNow(OT_EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void platformNetifUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int *aMaxFd)
|
||||
@@ -502,6 +626,8 @@ void platformNetifUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *a
|
||||
FD_SET(sTunFd, aErrorFdSet);
|
||||
FD_SET(sNetlinkFd, aReadFdSet);
|
||||
FD_SET(sNetlinkFd, aErrorFdSet);
|
||||
FD_SET(sMLDMonitorFd, aReadFdSet);
|
||||
FD_SET(sMLDMonitorFd, aErrorFdSet);
|
||||
|
||||
if (sTunFd > *aMaxFd)
|
||||
{
|
||||
@@ -513,6 +639,10 @@ void platformNetifUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *a
|
||||
*aMaxFd = sNetlinkFd;
|
||||
}
|
||||
|
||||
if (sMLDMonitorFd > *aMaxFd)
|
||||
{
|
||||
*aMaxFd = sMLDMonitorFd;
|
||||
}
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
@@ -534,6 +664,12 @@ void platformNetifProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, c
|
||||
DieNow(OT_EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (FD_ISSET(sMLDMonitorFd, aErrorFdSet))
|
||||
{
|
||||
close(sNetlinkFd);
|
||||
DieNow(OT_EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (FD_ISSET(sTunFd, aReadFdSet))
|
||||
{
|
||||
processTransmit(sInstance);
|
||||
@@ -544,6 +680,11 @@ void platformNetifProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, c
|
||||
processNetifEvent(sInstance);
|
||||
}
|
||||
|
||||
if (FD_ISSET(sMLDMonitorFd, aReadFdSet))
|
||||
{
|
||||
processMLDEvent(sInstance);
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user