blob: f3ec6cf59a8225efb950c6b9c801e58df5070b2e [file] [log] [blame]
/*
* 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 <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <openthread/logging.h>
#include <openthread/platform/trel.h>
#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