/* * Copyright (c) 2019-2021, 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 "radio_url.hpp" #include "system.hpp" #include "common/code_utils.hpp" #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE #define TREL_MAX_PACKET_SIZE 1400 #define TREL_PACKET_POOL_SIZE 5 typedef struct TxPacket { struct TxPacket *mNext; uint8_t mBuffer[TREL_MAX_PACKET_SIZE]; uint16_t mLength; otSockAddr mDestSockAddr; } 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 bool sInitialized = false; static bool sEnabled = false; static int sSocket = -1; 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, (uint16_t)(end - cur), "[(len:%d) ", aLength); VerifyOrExit(cur < end); while (aLength-- && (num < kMaxWrite)) { cur += snprintf(cur, (uint16_t)(end - cur), "%02x ", *aBuffer++); VerifyOrExit(cur < end); num++; } if (aLength != 0) { cur += snprintf(cur, (uint16_t)(end - cur), "... "); VerifyOrExit(cur < end); } *cur++ = ']'; VerifyOrExit(cur < end); *cur = '\0'; exit: *end = '\0'; return string; } static void PrepareSocket(uint16_t &aUdpPort) { int val; struct sockaddr_in6 sockAddr; socklen_t sockLen; otLogDebgPlat("[trel] PrepareSocket()"); sSocket = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, 0, kSocketNonBlock); VerifyOrDie(sSocket >= 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. memset(&sockAddr, 0, sizeof(sockAddr)); sockAddr.sin6_family = AF_INET6; sockAddr.sin6_addr = in6addr_any; sockAddr.sin6_port = OPENTHREAD_POSIX_CONFIG_TREL_UDP_PORT; if (bind(sSocket, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) == -1) { otLogCritPlat("[trel] Failed to bind socket"); DieNow(OT_EXIT_ERROR_ERRNO); } sockLen = sizeof(sockAddr); if (getsockname(sSocket, (struct sockaddr *)&sockAddr, &sockLen) == -1) { otLogCritPlat("[trel] Failed to get the socket name"); DieNow(OT_EXIT_ERROR_ERRNO); } aUdpPort = ntohs(sockAddr.sin6_port); } static otError SendPacket(const uint8_t *aBuffer, uint16_t aLength, const otSockAddr *aDestSockAddr) { 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(aDestSockAddr->mPort); memcpy(&sockAddr.sin6_addr, &aDestSockAddr->mAddress, sizeof(otIp6Address)); ret = sendto(sSocket, aBuffer, aLength, 0, (struct sockaddr *)&sockAddr, sizeof(sockAddr)); if (ret != aLength) { otLogDebgPlat("[trel] 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] SendPacket([%s]:%u) err:%s pkt:%s", Ip6AddrToString(&aDestSockAddr->mAddress), aDestSockAddr->mPort, 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] ReceivePacket() - received from [%s]:%d, id:%d, pkt:%s", Ip6AddrToString(&sockAddr.sin6_addr), ntohs(sockAddr.sin6_port), sockAddr.sin6_scope_id, BufferToString(sRxPacketBuffer, sRxPacketLength)); if (sEnabled) { otPlatTrelHandleReceived(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->mDestSockAddr) == OT_ERROR_INVALID_STATE) { otLogDebgPlat("[trel] 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 void EnqueuePacket(const uint8_t *aBuffer, uint16_t aLength, const otSockAddr *aDestSockAddr) { TxPacket *packet; // Allocate an available packet entry (from the free packet list) // and copy the packet content into it. VerifyOrExit(sFreeTxPacketHead != NULL, otLogWarnPlat("[trel] EnqueuePacket failed, queue is full")); packet = sFreeTxPacketHead; sFreeTxPacketHead = sFreeTxPacketHead->mNext; memcpy(packet->mBuffer, aBuffer, aLength); packet->mLength = aLength; packet->mDestSockAddr = *aDestSockAddr; // 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] EnqueuePacket([%s]:%u) - %s", Ip6AddrToString(&aDestSockAddr->mAddress), aDestSockAddr->mPort, BufferToString(aBuffer, aLength)); exit: return; } //--------------------------------------------------------------------------------------------------------------------- // trelDnssd // // The functions below are tied to mDNS or DNS-SD library being used on // a device and need to be implemented per project/platform. A weak empty // implementation is provided here which describes the expected // behavior. They need to be overridden during project/platform // integration. OT_TOOL_WEAK void trelDnssdInitialize(const char *aTrelNetif) { // This function initialize the TREL DNS-SD module on the given // TREL Network Interface. OT_UNUSED_VARIABLE(aTrelNetif); } OT_TOOL_WEAK void trelDnssdStartBrowse(void) { // This function initiates an ongoing DNS-SD browse on the service // name "_trel._udp" within the local browsing domain to discover // other devices supporting TREL. The ongoing browse will produce // two different types of events: `add` events and `remove` events. // When the browse is started, it should produce an `add` event for // every TREL peer currently present on the network. Whenever a // TREL peer goes offline, a "remove" event should be produced. // `Remove` events are not guaranteed, however. When a TREL service // instance is discovered, a new ongoing DNS-SD query for an AAAA // record MUST be started on the hostname indicated in the SRV // record of the discovered instance. If multiple host IPv6 // addressees are discovered for a peer, one with highest scope // among all addresses MUST be reported (if there are multiple // address at same scope, one must be selected randomly). // // The platform MUST signal back the discovered peer info using // `otPlatTrelHandleDiscoveredPeerInfo()` callback. This callback // MUST be invoked when a new peer is discovered, or when there is // a change in an existing entry (e.g., new TXT record or new port // number or new IPv6 address), or when the peer is removed. } OT_TOOL_WEAK void trelDnssdStopBrowse(void) { // This function stops the ongoing DNS-SD browse started from an // earlier call to `trelDnssdStartBrowse()`. } OT_TOOL_WEAK void trelDnssdRegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength) { // This function registers a new service to be advertised using // DNS-SD. // // The service name is "_trel._udp". The platform should use its own // hostname, which when combined with the service name and the // local DNS-SD domain name will produce the full service instance // name, for example "example-host._trel._udp.local.". // // The domain under which the service instance name appears will // be 'local' for mDNS, and will be whatever domain is used for // service registration in the case of a non-mDNS local DNS-SD // service. // // A subsequent call to this function updates the previous service. // It is used to update the TXT record data and/or the port // number. // // The `aTxtData` buffer is not persisted after the return from this // function. The platform layer MUST not keep the pointer and // instead copy the content if needed. OT_UNUSED_VARIABLE(aPort); OT_UNUSED_VARIABLE(aTxtData); OT_UNUSED_VARIABLE(aTxtLength); } OT_TOOL_WEAK void trelDnssdRemoveService(void) { // This function removes any previously registered "_trel._udp" // service using `platTrelRegisterService()`. Device must stop // advertising TREL service after this call. } OT_TOOL_WEAK void trelDnssdUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout) { // This function can be used to update the file descriptor sets // by DNS-SD layer (if needed). OT_UNUSED_VARIABLE(aReadFdSet); OT_UNUSED_VARIABLE(aWriteFdSet); OT_UNUSED_VARIABLE(aMaxFd); OT_UNUSED_VARIABLE(aTimeout); } OT_TOOL_WEAK void trelDnssdProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet) { // This function performs processing by DNS-SD (if needed). OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aReadFdSet); OT_UNUSED_VARIABLE(aWriteFdSet); } //--------------------------------------------------------------------------------------------------------------------- // otPlatTrel void otPlatTrelEnable(otInstance *aInstance, uint16_t *aUdpPort) { OT_UNUSED_VARIABLE(aInstance); VerifyOrExit(!IsSystemDryRun()); assert(sInitialized); VerifyOrExit(!sEnabled); PrepareSocket(*aUdpPort); trelDnssdStartBrowse(); sEnabled = true; exit: return; } void otPlatTrelDisable(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); VerifyOrExit(!IsSystemDryRun()); assert(sInitialized); VerifyOrExit(sEnabled); close(sSocket); sSocket = -1; trelDnssdStopBrowse(); trelDnssdRemoveService(); sEnabled = false; exit: return; } void otPlatTrelSend(otInstance * aInstance, const uint8_t * aUdpPayload, uint16_t aUdpPayloadLen, const otSockAddr *aDestSockAddr) { OT_UNUSED_VARIABLE(aInstance); VerifyOrExit(!IsSystemDryRun()); VerifyOrExit(sEnabled); assert(aUdpPayloadLen <= TREL_MAX_PACKET_SIZE); // 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. if ((sTxPacketQueueTail != NULL) || (SendPacket(aUdpPayload, aUdpPayloadLen, aDestSockAddr) == OT_ERROR_INVALID_STATE)) { EnqueuePacket(aUdpPayload, aUdpPayloadLen, aDestSockAddr); } exit: return; } void otPlatTrelRegisterService(otInstance *aInstance, uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength) { OT_UNUSED_VARIABLE(aInstance); VerifyOrExit(!IsSystemDryRun()); trelDnssdRegisterService(aPort, aTxtData, aTxtLength); exit: return; } //--------------------------------------------------------------------------------------------------------------------- // platformTrel system void platformTrelInit(const char *aTrelUrl) { otLogDebgPlat("[trel] platformTrelInit(aTrelUrl:\"%s\")", aTrelUrl != nullptr ? aTrelUrl : ""); assert(!sInitialized); if (aTrelUrl != nullptr) { ot::Posix::RadioUrl url(aTrelUrl); strncpy(sInterfaceName, url.GetPath(), sizeof(sInterfaceName) - 1); sInterfaceName[sizeof(sInterfaceName) - 1] = '\0'; } trelDnssdInitialize(sInterfaceName); InitPacketQueue(); sInitialized = true; } void platformTrelDeinit(void) { VerifyOrExit(sInitialized); otPlatTrelDisable(nullptr); sInterfaceName[0] = '\0'; sInitialized = false; otLogDebgPlat("[trel] platformTrelDeinit()"); exit: return; } void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout) { assert((aReadFdSet != NULL) && (aWriteFdSet != NULL) && (aMaxFd != NULL) && (aTimeout != NULL)); VerifyOrExit(sEnabled); FD_SET(sSocket, aReadFdSet); if (sTxPacketQueueTail != NULL) { FD_SET(sSocket, aWriteFdSet); } if (*aMaxFd < sSocket) { *aMaxFd = sSocket; } trelDnssdUpdateFdSet(aReadFdSet, aWriteFdSet, aMaxFd, aTimeout); exit: return; } void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet) { VerifyOrExit(sEnabled); if (FD_ISSET(sSocket, aWriteFdSet)) { SendQueuedPackets(); } if (FD_ISSET(sSocket, aReadFdSet)) { ReceivePacket(sSocket, aInstance); } trelDnssdProcess(aInstance, aReadFdSet, aWriteFdSet); exit: return; } #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE