diff --git a/.travis/test_multicast_join.py b/.travis/test_multicast_join.py new file mode 100755 index 000000000..cb9d6cf71 --- /dev/null +++ b/.travis/test_multicast_join.py @@ -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()) diff --git a/script/check-posix-pty b/script/check-posix-pty index 596cd735f..0fc8ccffa 100755 --- a/script/check-posix-pty +++ b/script/check-posix-pty @@ -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) diff --git a/src/posix/platform/netif.cpp b/src/posix/platform/netif.cpp index 1cdbe6d1d..a19dd7d77 100644 --- a/src/posix/platform/netif.cpp +++ b/src/posix/platform/netif.cpp @@ -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(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(buffer); + size_t offset; + uint8_t type; + struct ifaddrs * ifAddrs = NULL; + + bufferLen = recvfrom(sMLDMonitorFd, buffer, sizeof(buffer), 0, reinterpret_cast(&srcAddr), &addrLen); + VerifyOrExit(bufferLen > 0); + + type = buffer[0]; + VerifyOrExit(type == kICMPv6MLDv2Type && bufferLen >= static_cast(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(ifAddr->ifa_addr); + + if (memcmp(&addr6->sin6_addr, &srcAddr.sin6_addr, sizeof(in6_addr)) == 0) + { + fromSelf = true; + break; + } + } + } + VerifyOrExit(fromSelf); + + hdr = reinterpret_cast(buffer); + offset = sizeof(MLDv2Header); + + for (size_t i = 0; i < ntohs(hdr->mNumRecords) && offset < static_cast(bufferLen); i++) + { + if (static_cast(bufferLen) >= (sizeof(MLDv2Record) + offset)) + { + MLDv2Record *record = reinterpret_cast(&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(&sa), sizeof(sa)) == 0); + VerifyOrDie(bind(sNetlinkFd, reinterpret_cast(&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(&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(&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; }