[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:
Jiacheng Guo
2020-03-30 14:14:55 -07:00
committed by GitHub
parent 8328aff1d9
commit e2be5c475c
3 changed files with 267 additions and 41 deletions
+75
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}