From 0829514edc17a0d91a83479922b8b61560c5967f Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 27 Dec 2019 13:17:30 -0800 Subject: [PATCH] [posix-app] adding support for TREL over IPv6/UDP (#4440) This commit implements TREL IPv6/UDP platform under POSIX App. The implementation uses datagram sockets to exchange packets over a given network interface (netif). The interface name can hard-coded in the build using `OPENTHREAD_CONFIG_POSIX_APP_TREL_INTERFACE_NAME` config or be specified as part of the input program arguments (using `--trel-interface` or `-t` option). --- src/posix/main.c | 8 +- src/posix/platform/CMakeLists.txt | 1 + src/posix/platform/Makefile.am | 1 + .../include/openthread/openthread-system.h | 1 + src/posix/platform/openthread-posix-config.h | 10 + src/posix/platform/platform-posix.h | 35 ++ src/posix/platform/system.cpp | 12 + src/posix/platform/trel_udp6.cpp | 587 ++++++++++++++++++ 8 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 src/posix/platform/trel_udp6.cpp diff --git a/src/posix/main.c b/src/posix/main.c index 625d982f8..dc6148593 100644 --- a/src/posix/main.c +++ b/src/posix/main.c @@ -107,6 +107,7 @@ enum OT_POSIX_OPT_HELP = 'h', OT_POSIX_OPT_INTERFACE_NAME = 'I', OT_POSIX_OPT_TIME_SPEED = 's', + OT_POSIX_OPT_TREL_INTERFACE = 't', OT_POSIX_OPT_VERBOSE = 'v', OT_POSIX_OPT_SHORT_MAX = 128, @@ -124,6 +125,7 @@ static const struct option kOptions[] = { {"radio-version", no_argument, NULL, OT_POSIX_OPT_RADIO_VERSION}, {"real-time-signal", required_argument, NULL, OT_POSIX_OPT_REAL_TIME_SIGNAL}, {"time-speed", required_argument, NULL, OT_POSIX_OPT_TIME_SPEED}, + {"trel-interface", required_argument, NULL, OT_POSIX_OPT_TREL_INTERFACE}, {"verbose", no_argument, NULL, OT_POSIX_OPT_VERBOSE}, {0, 0, 0, 0}}; @@ -140,6 +142,7 @@ static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode) " -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n" " --radio-version Print radio firmware version.\n" " -s --time-speed factor Time speed up factor.\n" + " -t --trel-interface name Interface name for TREL platform (e.g., wlan0 netif).\n" " -v --verbose Also log to stderr.\n", aProgramName); #ifdef __linux__ @@ -167,7 +170,7 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) while (true) { int index = 0; - int option = getopt_long(aArgCount, aArgVector, "B:d:hI:ns:v", kOptions, &index); + int option = getopt_long(aArgCount, aArgVector, "B:d:hI:t:ns:v", kOptions, &index); if (option == -1) { @@ -188,6 +191,9 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) case OT_POSIX_OPT_BACKBONE_INTERFACE_NAME: aConfig->mPlatformConfig.mBackboneInterfaceName = optarg; break; + case OT_POSIX_OPT_TREL_INTERFACE: + aConfig->mPlatformConfig.mTrelInterface = optarg; + break; case OT_POSIX_OPT_DRY_RUN: aConfig->mIsDryRun = true; break; diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 04e097e5e..c90f340dc 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -73,6 +73,7 @@ add_library(openthread-posix settings.cpp spi_interface.cpp system.cpp + trel_udp6.cpp uart.cpp udp.cpp virtual_time.cpp diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index 04cfb3ae2..928528497 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -57,6 +57,7 @@ libopenthread_posix_a_SOURCES = \ settings.cpp \ spi_interface.cpp \ system.cpp \ + trel_udp6.cpp \ uart.cpp \ udp.cpp \ virtual_time.cpp \ diff --git a/src/posix/platform/include/openthread/openthread-system.h b/src/posix/platform/include/openthread/openthread-system.h index 6e45fa540..5ee20b775 100644 --- a/src/posix/platform/include/openthread/openthread-system.h +++ b/src/posix/platform/include/openthread/openthread-system.h @@ -76,6 +76,7 @@ typedef struct otPlatformConfig const char *mRadioUrl; ///< Radio url. int mRealTimeSignal; ///< The real-time signal for microsecond timer. uint32_t mSpeedUpFactor; ///< Speed up factor. + const char *mTrelInterface; ///< Interface name used by TREL radio link (can be NULL to use default). } otPlatformConfig; /** diff --git a/src/posix/platform/openthread-posix-config.h b/src/posix/platform/openthread-posix-config.h index c851d8392..e122b8b05 100644 --- a/src/posix/platform/openthread-posix-config.h +++ b/src/posix/platform/openthread-posix-config.h @@ -47,6 +47,16 @@ #define OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_POSIX_APP_TREL_INTERFACE_NAME + * + * Defines the default interface name used for TREL UDP6 platform. + * + */ +#ifndef OPENTHREAD_CONFIG_POSIX_APP_TREL_INTERFACE_NAME +#define OPENTHREAD_CONFIG_POSIX_APP_TREL_INTERFACE_NAME "trel" +#endif + /** * @def OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME * diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 1c9d274bc..ff019479e 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -389,6 +389,41 @@ enum SocketBlockOption kSocketNonBlock, }; +/** + * This function initializes platform TREL UDP6 driver. + * + * @param[in] aInterfaceName The name of network interface. + * + */ +void platformTrelInit(const char *aInterfaceName); + +/** + * This function shuts down the platform TREL UDP6 platform driver. + * + */ +void platformTrelDeinit(void); + +/** + * This function updates the file descriptor sets with file descriptors used by the TREL driver. + * + * @param[inout] aReadFdSet A pointer to the read file descriptors. + * @param[inout] aWriteFdSet A pointer to the write file descriptors. + * @param[inout] aMaxFd A pointer to the max file descriptor. + * @param[inout] aTimeout A pointer to the timeout. + * + */ +void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout); + +/** + * This function performs TREL driver processing. + * + * @param[in] aInstance A pointer to the OpenThread instance. + * @param[in] aReadFdSet A pointer to the read file descriptors. + * @param[in] aWriteFdSet A pointer to the write file descriptors. + * + */ +void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet); + /** * This function creates a socket with SOCK_CLOEXEC flag set. * diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index fcd74653c..1824fe1b4 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -85,6 +85,9 @@ otInstance *otSysInit(otPlatformConfig *aPlatformConfig) VerifyOrDie(radioUrl.GetPath() != nullptr, OT_EXIT_INVALID_ARGUMENTS); platformAlarmInit(aPlatformConfig->mSpeedUpFactor, aPlatformConfig->mRealTimeSignal); platformRadioInit(&radioUrl); +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + platformTrelInit(aPlatformConfig->mTrelInterface); +#endif platformRandomInit(); instance = otInstanceInitSingle(); @@ -115,6 +118,9 @@ void otSysDeinit(void) platformRadioDeinit(); #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE platformNetifDeinit(); +#endif +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + platformTrelDeinit(); #endif IgnoreError(otPlatUartDisable()); } @@ -173,6 +179,9 @@ void otSysMainloopUpdate(otInstance *aInstance, otSysMainloopContext *aMainloop) #else platformRadioUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout); #endif +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + platformTrelUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout); +#endif if (otTaskletsArePending(aInstance)) { @@ -230,6 +239,9 @@ void otSysMainloopProcess(otInstance *aInstance, const otSysMainloopContext *aMa virtualTimeProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet); #else platformRadioProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet); +#endif +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + platformTrelProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet); #endif platformUartProcess(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet); platformAlarmProcess(aInstance); diff --git a/src/posix/platform/trel_udp6.cpp b/src/posix/platform/trel_udp6.cpp new file mode 100644 index 000000000..da4f70b93 --- /dev/null +++ b/src/posix/platform/trel_udp6.cpp @@ -0,0 +1,587 @@ +/* + * Copyright (c) 2019, 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. + */ + +/** + * @file + * This file implements platform for TREL using IPv6/UDP socket under POSIX. + */ + +#include "openthread-posix-config.h" + +#include "platform-posix.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/code_utils.hpp" +#include "common/logging.hpp" + +#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE + +#define TREL_MAX_PACKET_SIZE 1400 +#define TREL_PACKET_POOL_SIZE 5 + +#define USEC_PER_MSEC 1000u +#define TREL_SOCKET_BIND_MAX_WAIT_TIME_MSEC 4000u + +typedef struct TxPacket +{ + struct TxPacket *mNext; + uint8_t mBuffer[TREL_MAX_PACKET_SIZE]; + uint16_t mLength; + otIp6Address mDestAddress; +} TxPacket; + +static uint8_t sRxPacketBuffer[TREL_MAX_PACKET_SIZE]; +static uint16_t sRxPacketLength; +static TxPacket sTxPacketPool[TREL_PACKET_POOL_SIZE]; +static TxPacket * sFreeTxPacketHead; // A singly linked list of free/available `TxPacket` from pool. +static TxPacket * sTxPacketQueueTail; // A circular linked list for queued tx packets. +static char sInterfaceName[IFNAMSIZ + 1]; +static int sInterfaceIndex = -1; +static int sMulticastSocket = -1; +static int sSocket = -1; +static uint16_t sUdpPort = 0; +static otIp6Address sInterfaceAddress; + +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG) && OPENTHREAD_CONFIG_LOG_PLATFORM +static const char *Ip6AddrToString(const void *aAddress) +{ + static char string[INET6_ADDRSTRLEN]; + return inet_ntop(AF_INET6, aAddress, string, sizeof(string)); +} + +static const char *BufferToString(const uint8_t *aBuffer, uint16_t aLength) +{ + const uint16_t kMaxWrite = 16; + static char string[1600]; + + uint16_t num = 0; + char * cur = &string[0]; + char * end = &string[sizeof(string) - 1]; + + cur += snprintf(cur, end - cur, "[(len:%d) ", aLength); + VerifyOrExit(cur < end); + + while (aLength-- && (num < kMaxWrite)) + { + cur += snprintf(cur, end - cur, "%02x ", *aBuffer++); + VerifyOrExit(cur < end); + + num++; + } + + if (aLength != 0) + { + cur += snprintf(cur, end - cur, "... "); + VerifyOrExit(cur < end); + } + + *cur++ = ']'; + VerifyOrExit(cur < end); + + *cur++ = '\0'; + +exit: + *end = '\0'; + return string; +} +#endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG) && OPENTHREAD_CONFIG_LOG_PLATFORM + +static void AddUnicastAddress(const otIp6Address *aUnicastAddress) +{ + int mgmtFd; + int ret; + struct in6_ifreq + { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + int ifr6_ifindex; + } ifr6; + + otLogDebgPlat("[trel-plat] AddUnicastAddress(%s)", Ip6AddrToString(aUnicastAddress)); + + mgmtFd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP); + VerifyOrDie(mgmtFd >= 0, OT_EXIT_ERROR_ERRNO); + + memcpy(&ifr6.ifr6_addr, aUnicastAddress, sizeof(otIp6Address)); + ifr6.ifr6_prefixlen = 64; + ifr6.ifr6_ifindex = sInterfaceIndex; + + ret = ioctl(mgmtFd, SIOCSIFADDR, &ifr6); + + VerifyOrDie((ret == 0) || (errno == EALREADY) || (errno == EEXIST), OT_EXIT_ERROR_ERRNO); + + close(mgmtFd); +} + +static void RemoveUnicastAddress(const otIp6Address *aUnicastAddress) +{ + int mgmtFd; + int ret; + struct in6_ifreq + { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + int ifr6_ifindex; + } ifr6; + + otLogDebgPlat("[trel-plat] RemoveUnicastAddress(%s)", Ip6AddrToString(aUnicastAddress)); + + mgmtFd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP); + VerifyOrDie(mgmtFd >= 0, OT_EXIT_ERROR_ERRNO); + + memcpy(&ifr6.ifr6_addr, aUnicastAddress, sizeof(otIp6Address)); + ifr6.ifr6_prefixlen = 64; + ifr6.ifr6_ifindex = sInterfaceIndex; + + ret = ioctl(mgmtFd, SIOCDIFADDR, &ifr6); + + VerifyOrDie(ret == 0, OT_EXIT_ERROR_ERRNO); + + close(mgmtFd); +} + +static void PrepareSocket(void) +{ + int val; + struct sockaddr_in6 sockAddr; + uint64_t startTime; + bool isSocketBound = false; + + otLogDebgPlat("[trel-plat] PrepareSocket()"); + + sSocket = socket(AF_INET6, SOCK_DGRAM, 0); + VerifyOrDie(sSocket >= 0, OT_EXIT_ERROR_ERRNO); + + // Set the multicast interface index (for tx), disable loop back + // of multicast tx and set the multicast hop limit to 1 to reach + // a single sub-net. + + val = sInterfaceIndex; + VerifyOrDie(setsockopt(sSocket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + + val = 0; + VerifyOrDie(setsockopt(sSocket, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + + val = 1; + VerifyOrDie(setsockopt(sSocket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + VerifyOrDie(setsockopt(sSocket, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + + val = 1; + VerifyOrDie(setsockopt(sSocket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + + // Make the socket non-blocking to allow immediate tx attempt. + val = fcntl(sSocket, F_GETFL, 0); + VerifyOrDie(val != -1, OT_EXIT_ERROR_ERRNO); + val = val | O_NONBLOCK; + VerifyOrDie(fcntl(sSocket, F_SETFL, val) == 0, OT_EXIT_ERROR_ERRNO); + + // Bind the socket. The address to which we want to bind the + // socket, is itself added earlier above. The address therefore + // may not be immediately available/ready on the interface and the + // socket `bind()` call may fail with `EADDRNOTAVAIL` error. In + // such a case, we keep trying up to a maximum wait time. + + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin6_family = AF_INET6; + sockAddr.sin6_port = htons(sUdpPort); + memcpy(&sockAddr.sin6_addr, &sInterfaceAddress, sizeof(otIp6Address)); + sockAddr.sin6_scope_id = (uint32_t)sInterfaceIndex; + + startTime = otPlatTimeGet(); + + while (otPlatTimeGet() - startTime < TREL_SOCKET_BIND_MAX_WAIT_TIME_MSEC * USEC_PER_MSEC) + { + if (bind(sSocket, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) == -1) + { + VerifyOrDie(errno == EADDRNOTAVAIL, OT_EXIT_ERROR_ERRNO); + } + else + { + isSocketBound = true; + break; + } + } + + VerifyOrDie(isSocketBound, OT_EXIT_ERROR_ERRNO); +} + +static otError SendPacket(const uint8_t *aBuffer, uint16_t aLength, const otIp6Address *aDestAddress) +{ + otError error = OT_ERROR_NONE; + struct sockaddr_in6 sockAddr; + ssize_t ret; + + VerifyOrExit(sSocket >= 0, error = OT_ERROR_INVALID_STATE); + + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin6_family = AF_INET6; + sockAddr.sin6_port = htons(sUdpPort); + memcpy(&sockAddr.sin6_addr, aDestAddress, sizeof(otIp6Address)); + + ret = sendto(sSocket, aBuffer, aLength, 0, (struct sockaddr *)&sockAddr, sizeof(sockAddr)); + + if (ret != aLength) + { + otLogDebgPlat("[trel-plat] SendPacket() -- sendto() failed errno %d", errno); + + switch (errno) + { + case ENETUNREACH: + case ENETDOWN: + case EHOSTUNREACH: + error = OT_ERROR_ABORT; + break; + + default: + error = OT_ERROR_INVALID_STATE; + } + } + +exit: + otLogDebgPlat("[trel-plat] SendPacket(%s) err:%s pkt:%s", Ip6AddrToString(aDestAddress), + otThreadErrorToString(error), BufferToString(aBuffer, aLength)); + + return error; +} + +static void ReceivePacket(int aSocket, otInstance *aInstance) +{ + struct sockaddr_in6 sockAddr; + socklen_t sockAddrLen = sizeof(sockAddr); + ssize_t ret; + + memset(&sockAddr, 0, sizeof(sockAddr)); + + ret = recvfrom(aSocket, (char *)sRxPacketBuffer, sizeof(sRxPacketBuffer), 0, (struct sockaddr *)&sockAddr, + &sockAddrLen); + VerifyOrDie(ret >= 0, OT_EXIT_ERROR_ERRNO); + + sRxPacketLength = (uint16_t)(ret); + + if (sRxPacketLength > sizeof(sRxPacketBuffer)) + { + sRxPacketLength = sizeof(sRxPacketLength); + } + + otLogDebgPlat("[trel-plat] ReceivePacket() - received from %s port:%d, id:%d, pkt:%s", + Ip6AddrToString(&sockAddr.sin6_addr), ntohs(sockAddr.sin6_port), sockAddr.sin6_scope_id, + BufferToString(sRxPacketBuffer, sRxPacketLength)); + + otPlatTrelUdp6HandleReceived(aInstance, sRxPacketBuffer, sRxPacketLength); +} + +static void InitPacketQueue(void) +{ + sTxPacketQueueTail = NULL; + + // Chain all the packets in pool in the free linked list. + sFreeTxPacketHead = NULL; + + for (uint16_t index = 0; index < OT_ARRAY_LENGTH(sTxPacketPool); index++) + { + TxPacket *packet = &sTxPacketPool[index]; + + packet->mNext = sFreeTxPacketHead; + sFreeTxPacketHead = packet; + } +} + +static void SendQueuedPackets(void) +{ + while (sTxPacketQueueTail != NULL) + { + TxPacket *packet = sTxPacketQueueTail->mNext; // tail->mNext is the head of the list. + + if (SendPacket(packet->mBuffer, packet->mLength, &packet->mDestAddress) == OT_ERROR_INVALID_STATE) + { + otLogDebgPlat("[trel-plat] SendQueuedPackets() - SendPacket() would block"); + break; + } + + // Remove the `packet` from the packet queue (circular + // linked list). + + if (packet == sTxPacketQueueTail) + { + sTxPacketQueueTail = NULL; + } + else + { + sTxPacketQueueTail->mNext = packet->mNext; + } + + // Add the `packet` to the free packet singly linked list. + + packet->mNext = sFreeTxPacketHead; + sFreeTxPacketHead = packet; + } +} + +static otError EnqueuePacket(const uint8_t *aBuffer, uint16_t aLength, const otIp6Address *aDestAddress) +{ + otError error = OT_ERROR_NONE; + TxPacket *packet; + + // Allocate an available packet entry (from the free packet list) + // and copy the packet content into it. + + VerifyOrExit(sFreeTxPacketHead != NULL, error = OT_ERROR_NO_BUFS); + packet = sFreeTxPacketHead; + sFreeTxPacketHead = sFreeTxPacketHead->mNext; + + memcpy(packet->mBuffer, aBuffer, aLength); + packet->mLength = aLength; + packet->mDestAddress = *aDestAddress; + + // Add packet to the tail of TxPacketQueue circular linked-list. + + if (sTxPacketQueueTail == NULL) + { + packet->mNext = packet; + sTxPacketQueueTail = packet; + } + else + { + packet->mNext = sTxPacketQueueTail->mNext; + sTxPacketQueueTail->mNext = packet; + sTxPacketQueueTail = packet; + } + + otLogDebgPlat("[trel-plat] EnqueuePacket(%s) - %s", Ip6AddrToString(aDestAddress), + BufferToString(aBuffer, aLength)); + +exit: + return error; +} + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatTrelUdp6 + +void otPlatTrelUdp6Init(otInstance *aInstance, const otIp6Address *aUnicastAddress, uint16_t aUdpPort) +{ + int val; + struct sockaddr_in6 sockAddr; + + otLogDebgPlat("[trel-plat] otPlatTrelUdp6Init(%s, port:%d)", Ip6AddrToString(aUnicastAddress), aUdpPort); + + sUdpPort = aUdpPort; + sInterfaceAddress = *aUnicastAddress; + sInterfaceIndex = (int)if_nametoindex(sInterfaceName); + VerifyOrDie(sInterfaceIndex > 0, OT_EXIT_ERROR_ERRNO); + + AddUnicastAddress(aUnicastAddress); + + sMulticastSocket = socket(AF_INET6, SOCK_DGRAM, 0); + VerifyOrDie(sMulticastSocket >= 0, OT_EXIT_ERROR_ERRNO); + + val = 1; + VerifyOrDie(setsockopt(sMulticastSocket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + VerifyOrDie(setsockopt(sMulticastSocket, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) == 0, OT_EXIT_ERROR_ERRNO); + + // To receive from multicast addresses, the socket need to be + // bound to `in6addr_any` address. + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin6_family = AF_INET6; + sockAddr.sin6_port = htons(sUdpPort); + sockAddr.sin6_addr = in6addr_any; + sockAddr.sin6_scope_id = (uint32_t)sInterfaceIndex; + VerifyOrDie(bind(sMulticastSocket, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) != -1, OT_EXIT_ERROR_ERRNO); + + PrepareSocket(); + + OT_UNUSED_VARIABLE(aInstance); +} + +void otPlatTrelUdp6UpdateAddress(otInstance *aInstance, const otIp6Address *aUnicastAddress) +{ + assert(sSocket >= 0); + + otLogDebgPlat("[trel-plat] otPlatTrelUdp6UpdateAddress(%s)", Ip6AddrToString(aUnicastAddress)); + + VerifyOrExit(memcmp(aUnicastAddress, &sInterfaceAddress, sizeof(otIp6Address)) != 0); + + close(sSocket); + RemoveUnicastAddress(&sInterfaceAddress); + + sInterfaceAddress = *aUnicastAddress; + AddUnicastAddress(aUnicastAddress); + + PrepareSocket(); + +exit: + OT_UNUSED_VARIABLE(aInstance); +} + +void otPlatTrelUdp6SubscribeMulticastAddress(otInstance *aInstance, const otIp6Address *aMulticastAddress) +{ + struct ipv6_mreq mr; + + OT_UNUSED_VARIABLE(aInstance); + + assert(sMulticastSocket != -1); + + memcpy(&mr.ipv6mr_multiaddr, aMulticastAddress, sizeof(otIp6Address)); + mr.ipv6mr_interface = (unsigned int)sInterfaceIndex; + VerifyOrDie(setsockopt(sMulticastSocket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr, sizeof(mr)) == 0, OT_EXIT_ERROR_ERRNO); + + otLogDebgPlat("[trel-plat] otPlatTrelUdp6SubscribeMulticastAddress(%s)", Ip6AddrToString(aMulticastAddress)); +} + +otError otPlatTrelUdp6SendTo(otInstance * aInstance, + const uint8_t * aBuffer, + uint16_t aLength, + const otIp6Address *aDestAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + + otError error = OT_ERROR_NONE; + + assert(aLength <= TREL_MAX_PACKET_SIZE); + + otLogDebgPlat("[trel-plat] otPlatTrelUdp6SendTo(%s) %s", Ip6AddrToString(aDestAddress), + BufferToString(aBuffer, aLength)); + + // We try to send the packet immediately. If it fails (e.g., + // network is down) `SendPacket()` returns `OT_ERROR_ABORT`. If + // the send operation would block (e.g., socket is not yet ready + // or is out of buffer) we get `OT_ERROR_INVALID_STATE`. In that + // case we enqueue the packet to send it later when socket becomes + // ready. + + error = SendPacket(aBuffer, aLength, aDestAddress); + + if (error == OT_ERROR_INVALID_STATE) + { + error = EnqueuePacket(aBuffer, aLength, aDestAddress); + + if (error != OT_ERROR_NONE) + { + error = OT_ERROR_ABORT; + } + } + + return error; +} + +//--------------------------------------------------------------------------------------------------------------------- +// platformTrel system + +void platformTrelInit(const char *aInterfaceName) +{ + if (aInterfaceName != NULL) + { + strncpy(sInterfaceName, aInterfaceName, sizeof(sInterfaceName)); + } + else + { + strncpy(sInterfaceName, OPENTHREAD_CONFIG_POSIX_APP_TREL_INTERFACE_NAME, sizeof(sInterfaceName)); + } + + sInterfaceName[sizeof(sInterfaceName) - 1] = 0; + otLogDebgPlat("[trel-plat] platformTrelInit(InterfaceName:\"%s\")", sInterfaceName); + + InitPacketQueue(); +} + +void platformTrelDeinit(void) +{ + if (sSocket != -1) + { + close(sSocket); + } + + if (sMulticastSocket != -1) + { + close(sMulticastSocket); + } + + otLogDebgPlat("[trel-plat] platformTrelDeinit()"); +} + +void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout) +{ + assert((aReadFdSet != NULL) && (aWriteFdSet != NULL) && (aMaxFd != NULL) && (aTimeout != NULL)); + VerifyOrExit((sSocket >= 0) && (sMulticastSocket >= 0)); + + FD_SET(sMulticastSocket, aReadFdSet); + FD_SET(sSocket, aReadFdSet); + + if (sTxPacketQueueTail != NULL) + { + FD_SET(sSocket, aWriteFdSet); + } + + if (*aMaxFd < sMulticastSocket) + { + *aMaxFd = sMulticastSocket; + } + + if (*aMaxFd < sSocket) + { + *aMaxFd = sSocket; + } + +exit: + OT_UNUSED_VARIABLE(aTimeout); + return; +} + +void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet) +{ + VerifyOrExit((sSocket >= 0) && (sMulticastSocket >= 0)); + + if (FD_ISSET(sSocket, aWriteFdSet)) + { + SendQueuedPackets(); + } + + if (FD_ISSET(sSocket, aReadFdSet)) + { + ReceivePacket(sSocket, aInstance); + } + + if (FD_ISSET(sMulticastSocket, aReadFdSet)) + { + ReceivePacket(sMulticastSocket, aInstance); + } + +exit: + return; +} + +#endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE