1 /*
2 * Copyright (c) 2019-2021, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * This file implements platform for TREL using IPv6/UDP socket under POSIX.
32 */
33
34 #include "openthread-posix-config.h"
35
36 #include "platform-posix.h"
37
38 #include <arpa/inet.h>
39 #include <assert.h>
40 #include <fcntl.h>
41 #include <netinet/in.h>
42 #include <sys/socket.h>
43 #include <unistd.h>
44
45 #include <openthread/logging.h>
46 #include <openthread/platform/trel.h>
47
48 #include "radio_url.hpp"
49 #include "system.hpp"
50 #include "common/code_utils.hpp"
51
52 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
53
54 #define TREL_MAX_PACKET_SIZE 1400
55 #define TREL_PACKET_POOL_SIZE 5
56
57 typedef struct TxPacket
58 {
59 struct TxPacket *mNext;
60 uint8_t mBuffer[TREL_MAX_PACKET_SIZE];
61 uint16_t mLength;
62 otSockAddr mDestSockAddr;
63 } TxPacket;
64
65 static uint8_t sRxPacketBuffer[TREL_MAX_PACKET_SIZE];
66 static uint16_t sRxPacketLength;
67 static TxPacket sTxPacketPool[TREL_PACKET_POOL_SIZE];
68 static TxPacket *sFreeTxPacketHead; // A singly linked list of free/available `TxPacket` from pool.
69 static TxPacket *sTxPacketQueueTail; // A circular linked list for queued tx packets.
70
71 static char sInterfaceName[IFNAMSIZ + 1];
72 static bool sInitialized = false;
73 static bool sEnabled = false;
74 static int sSocket = -1;
75
Ip6AddrToString(const void * aAddress)76 static const char *Ip6AddrToString(const void *aAddress)
77 {
78 static char string[INET6_ADDRSTRLEN];
79 return inet_ntop(AF_INET6, aAddress, string, sizeof(string));
80 }
81
BufferToString(const uint8_t * aBuffer,uint16_t aLength)82 static const char *BufferToString(const uint8_t *aBuffer, uint16_t aLength)
83 {
84 const uint16_t kMaxWrite = 16;
85 static char string[1600];
86
87 uint16_t num = 0;
88 char * cur = &string[0];
89 char * end = &string[sizeof(string) - 1];
90
91 cur += snprintf(cur, (uint16_t)(end - cur), "[(len:%d) ", aLength);
92 VerifyOrExit(cur < end);
93
94 while (aLength-- && (num < kMaxWrite))
95 {
96 cur += snprintf(cur, (uint16_t)(end - cur), "%02x ", *aBuffer++);
97 VerifyOrExit(cur < end);
98
99 num++;
100 }
101
102 if (aLength != 0)
103 {
104 cur += snprintf(cur, (uint16_t)(end - cur), "... ");
105 VerifyOrExit(cur < end);
106 }
107
108 *cur++ = ']';
109 VerifyOrExit(cur < end);
110
111 *cur = '\0';
112
113 exit:
114 *end = '\0';
115 return string;
116 }
117
PrepareSocket(uint16_t & aUdpPort)118 static void PrepareSocket(uint16_t &aUdpPort)
119 {
120 int val;
121 struct sockaddr_in6 sockAddr;
122 socklen_t sockLen;
123
124 otLogDebgPlat("[trel] PrepareSocket()");
125
126 sSocket = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, 0, kSocketNonBlock);
127 VerifyOrDie(sSocket >= 0, OT_EXIT_ERROR_ERRNO);
128
129 // Make the socket non-blocking to allow immediate tx attempt.
130 val = fcntl(sSocket, F_GETFL, 0);
131 VerifyOrDie(val != -1, OT_EXIT_ERROR_ERRNO);
132 val = val | O_NONBLOCK;
133 VerifyOrDie(fcntl(sSocket, F_SETFL, val) == 0, OT_EXIT_ERROR_ERRNO);
134
135 // Bind the socket.
136
137 memset(&sockAddr, 0, sizeof(sockAddr));
138 sockAddr.sin6_family = AF_INET6;
139 sockAddr.sin6_addr = in6addr_any;
140 sockAddr.sin6_port = OPENTHREAD_POSIX_CONFIG_TREL_UDP_PORT;
141
142 if (bind(sSocket, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) == -1)
143 {
144 otLogCritPlat("[trel] Failed to bind socket");
145 DieNow(OT_EXIT_ERROR_ERRNO);
146 }
147
148 sockLen = sizeof(sockAddr);
149
150 if (getsockname(sSocket, (struct sockaddr *)&sockAddr, &sockLen) == -1)
151 {
152 otLogCritPlat("[trel] Failed to get the socket name");
153 DieNow(OT_EXIT_ERROR_ERRNO);
154 }
155
156 aUdpPort = ntohs(sockAddr.sin6_port);
157 }
158
SendPacket(const uint8_t * aBuffer,uint16_t aLength,const otSockAddr * aDestSockAddr)159 static otError SendPacket(const uint8_t *aBuffer, uint16_t aLength, const otSockAddr *aDestSockAddr)
160 {
161 otError error = OT_ERROR_NONE;
162 struct sockaddr_in6 sockAddr;
163 ssize_t ret;
164
165 VerifyOrExit(sSocket >= 0, error = OT_ERROR_INVALID_STATE);
166
167 memset(&sockAddr, 0, sizeof(sockAddr));
168 sockAddr.sin6_family = AF_INET6;
169 sockAddr.sin6_port = htons(aDestSockAddr->mPort);
170 memcpy(&sockAddr.sin6_addr, &aDestSockAddr->mAddress, sizeof(otIp6Address));
171
172 ret = sendto(sSocket, aBuffer, aLength, 0, (struct sockaddr *)&sockAddr, sizeof(sockAddr));
173
174 if (ret != aLength)
175 {
176 otLogDebgPlat("[trel] SendPacket() -- sendto() failed errno %d", errno);
177
178 switch (errno)
179 {
180 case ENETUNREACH:
181 case ENETDOWN:
182 case EHOSTUNREACH:
183 error = OT_ERROR_ABORT;
184 break;
185
186 default:
187 error = OT_ERROR_INVALID_STATE;
188 }
189 }
190
191 exit:
192 otLogDebgPlat("[trel] SendPacket([%s]:%u) err:%s pkt:%s", Ip6AddrToString(&aDestSockAddr->mAddress),
193 aDestSockAddr->mPort, otThreadErrorToString(error), BufferToString(aBuffer, aLength));
194
195 return error;
196 }
197
ReceivePacket(int aSocket,otInstance * aInstance)198 static void ReceivePacket(int aSocket, otInstance *aInstance)
199 {
200 struct sockaddr_in6 sockAddr;
201 socklen_t sockAddrLen = sizeof(sockAddr);
202 ssize_t ret;
203
204 memset(&sockAddr, 0, sizeof(sockAddr));
205
206 ret = recvfrom(aSocket, (char *)sRxPacketBuffer, sizeof(sRxPacketBuffer), 0, (struct sockaddr *)&sockAddr,
207 &sockAddrLen);
208 VerifyOrDie(ret >= 0, OT_EXIT_ERROR_ERRNO);
209
210 sRxPacketLength = (uint16_t)(ret);
211
212 if (sRxPacketLength > sizeof(sRxPacketBuffer))
213 {
214 sRxPacketLength = sizeof(sRxPacketLength);
215 }
216
217 otLogDebgPlat("[trel] ReceivePacket() - received from [%s]:%d, id:%d, pkt:%s", Ip6AddrToString(&sockAddr.sin6_addr),
218 ntohs(sockAddr.sin6_port), sockAddr.sin6_scope_id, BufferToString(sRxPacketBuffer, sRxPacketLength));
219
220 if (sEnabled)
221 {
222 otPlatTrelHandleReceived(aInstance, sRxPacketBuffer, sRxPacketLength);
223 }
224 }
225
InitPacketQueue(void)226 static void InitPacketQueue(void)
227 {
228 sTxPacketQueueTail = NULL;
229
230 // Chain all the packets in pool in the free linked list.
231 sFreeTxPacketHead = NULL;
232
233 for (uint16_t index = 0; index < OT_ARRAY_LENGTH(sTxPacketPool); index++)
234 {
235 TxPacket *packet = &sTxPacketPool[index];
236
237 packet->mNext = sFreeTxPacketHead;
238 sFreeTxPacketHead = packet;
239 }
240 }
241
SendQueuedPackets(void)242 static void SendQueuedPackets(void)
243 {
244 while (sTxPacketQueueTail != NULL)
245 {
246 TxPacket *packet = sTxPacketQueueTail->mNext; // tail->mNext is the head of the list.
247
248 if (SendPacket(packet->mBuffer, packet->mLength, &packet->mDestSockAddr) == OT_ERROR_INVALID_STATE)
249 {
250 otLogDebgPlat("[trel] SendQueuedPackets() - SendPacket() would block");
251 break;
252 }
253
254 // Remove the `packet` from the packet queue (circular
255 // linked list).
256
257 if (packet == sTxPacketQueueTail)
258 {
259 sTxPacketQueueTail = NULL;
260 }
261 else
262 {
263 sTxPacketQueueTail->mNext = packet->mNext;
264 }
265
266 // Add the `packet` to the free packet singly linked list.
267
268 packet->mNext = sFreeTxPacketHead;
269 sFreeTxPacketHead = packet;
270 }
271 }
272
EnqueuePacket(const uint8_t * aBuffer,uint16_t aLength,const otSockAddr * aDestSockAddr)273 static void EnqueuePacket(const uint8_t *aBuffer, uint16_t aLength, const otSockAddr *aDestSockAddr)
274 {
275 TxPacket *packet;
276
277 // Allocate an available packet entry (from the free packet list)
278 // and copy the packet content into it.
279
280 VerifyOrExit(sFreeTxPacketHead != NULL, otLogWarnPlat("[trel] EnqueuePacket failed, queue is full"));
281 packet = sFreeTxPacketHead;
282 sFreeTxPacketHead = sFreeTxPacketHead->mNext;
283
284 memcpy(packet->mBuffer, aBuffer, aLength);
285 packet->mLength = aLength;
286 packet->mDestSockAddr = *aDestSockAddr;
287
288 // Add packet to the tail of TxPacketQueue circular linked-list.
289
290 if (sTxPacketQueueTail == NULL)
291 {
292 packet->mNext = packet;
293 sTxPacketQueueTail = packet;
294 }
295 else
296 {
297 packet->mNext = sTxPacketQueueTail->mNext;
298 sTxPacketQueueTail->mNext = packet;
299 sTxPacketQueueTail = packet;
300 }
301
302 otLogDebgPlat("[trel] EnqueuePacket([%s]:%u) - %s", Ip6AddrToString(&aDestSockAddr->mAddress), aDestSockAddr->mPort,
303 BufferToString(aBuffer, aLength));
304
305 exit:
306 return;
307 }
308
309 //---------------------------------------------------------------------------------------------------------------------
310 // trelDnssd
311 //
312 // The functions below are tied to mDNS or DNS-SD library being used on
313 // a device and need to be implemented per project/platform. A weak empty
314 // implementation is provided here which describes the expected
315 // behavior. They need to be overridden during project/platform
316 // integration.
317
trelDnssdInitialize(const char * aTrelNetif)318 OT_TOOL_WEAK void trelDnssdInitialize(const char *aTrelNetif)
319 {
320 // This function initialize the TREL DNS-SD module on the given
321 // TREL Network Interface.
322
323 OT_UNUSED_VARIABLE(aTrelNetif);
324 }
325
trelDnssdStartBrowse(void)326 OT_TOOL_WEAK void trelDnssdStartBrowse(void)
327 {
328 // This function initiates an ongoing DNS-SD browse on the service
329 // name "_trel._udp" within the local browsing domain to discover
330 // other devices supporting TREL. The ongoing browse will produce
331 // two different types of events: `add` events and `remove` events.
332 // When the browse is started, it should produce an `add` event for
333 // every TREL peer currently present on the network. Whenever a
334 // TREL peer goes offline, a "remove" event should be produced.
335 // `Remove` events are not guaranteed, however. When a TREL service
336 // instance is discovered, a new ongoing DNS-SD query for an AAAA
337 // record MUST be started on the hostname indicated in the SRV
338 // record of the discovered instance. If multiple host IPv6
339 // addressees are discovered for a peer, one with highest scope
340 // among all addresses MUST be reported (if there are multiple
341 // address at same scope, one must be selected randomly).
342 //
343 // The platform MUST signal back the discovered peer info using
344 // `otPlatTrelHandleDiscoveredPeerInfo()` callback. This callback
345 // MUST be invoked when a new peer is discovered, or when there is
346 // a change in an existing entry (e.g., new TXT record or new port
347 // number or new IPv6 address), or when the peer is removed.
348 }
349
trelDnssdStopBrowse(void)350 OT_TOOL_WEAK void trelDnssdStopBrowse(void)
351 {
352 // This function stops the ongoing DNS-SD browse started from an
353 // earlier call to `trelDnssdStartBrowse()`.
354 }
355
trelDnssdRegisterService(uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)356 OT_TOOL_WEAK void trelDnssdRegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
357 {
358 // This function registers a new service to be advertised using
359 // DNS-SD.
360 //
361 // The service name is "_trel._udp". The platform should use its own
362 // hostname, which when combined with the service name and the
363 // local DNS-SD domain name will produce the full service instance
364 // name, for example "example-host._trel._udp.local.".
365 //
366 // The domain under which the service instance name appears will
367 // be 'local' for mDNS, and will be whatever domain is used for
368 // service registration in the case of a non-mDNS local DNS-SD
369 // service.
370 //
371 // A subsequent call to this function updates the previous service.
372 // It is used to update the TXT record data and/or the port
373 // number.
374 //
375 // The `aTxtData` buffer is not persisted after the return from this
376 // function. The platform layer MUST not keep the pointer and
377 // instead copy the content if needed.
378
379 OT_UNUSED_VARIABLE(aPort);
380 OT_UNUSED_VARIABLE(aTxtData);
381 OT_UNUSED_VARIABLE(aTxtLength);
382 }
383
trelDnssdRemoveService(void)384 OT_TOOL_WEAK void trelDnssdRemoveService(void)
385 {
386 // This function removes any previously registered "_trel._udp"
387 // service using `platTrelRegisterService()`. Device must stop
388 // advertising TREL service after this call.
389 }
390
trelDnssdUpdateFdSet(fd_set * aReadFdSet,fd_set * aWriteFdSet,int * aMaxFd,struct timeval * aTimeout)391 OT_TOOL_WEAK void trelDnssdUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout)
392 {
393 // This function can be used to update the file descriptor sets
394 // by DNS-SD layer (if needed).
395
396 OT_UNUSED_VARIABLE(aReadFdSet);
397 OT_UNUSED_VARIABLE(aWriteFdSet);
398 OT_UNUSED_VARIABLE(aMaxFd);
399 OT_UNUSED_VARIABLE(aTimeout);
400 }
401
trelDnssdProcess(otInstance * aInstance,const fd_set * aReadFdSet,const fd_set * aWriteFdSet)402 OT_TOOL_WEAK void trelDnssdProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
403 {
404 // This function performs processing by DNS-SD (if needed).
405
406 OT_UNUSED_VARIABLE(aInstance);
407 OT_UNUSED_VARIABLE(aReadFdSet);
408 OT_UNUSED_VARIABLE(aWriteFdSet);
409 }
410
411 //---------------------------------------------------------------------------------------------------------------------
412 // otPlatTrel
413
otPlatTrelEnable(otInstance * aInstance,uint16_t * aUdpPort)414 void otPlatTrelEnable(otInstance *aInstance, uint16_t *aUdpPort)
415 {
416 OT_UNUSED_VARIABLE(aInstance);
417
418 VerifyOrExit(!IsSystemDryRun());
419
420 assert(sInitialized);
421
422 VerifyOrExit(!sEnabled);
423
424 PrepareSocket(*aUdpPort);
425 trelDnssdStartBrowse();
426
427 sEnabled = true;
428
429 exit:
430 return;
431 }
432
otPlatTrelDisable(otInstance * aInstance)433 void otPlatTrelDisable(otInstance *aInstance)
434 {
435 OT_UNUSED_VARIABLE(aInstance);
436
437 VerifyOrExit(!IsSystemDryRun());
438
439 assert(sInitialized);
440 VerifyOrExit(sEnabled);
441
442 close(sSocket);
443 sSocket = -1;
444 trelDnssdStopBrowse();
445 trelDnssdRemoveService();
446 sEnabled = false;
447
448 exit:
449 return;
450 }
451
otPlatTrelSend(otInstance * aInstance,const uint8_t * aUdpPayload,uint16_t aUdpPayloadLen,const otSockAddr * aDestSockAddr)452 void otPlatTrelSend(otInstance * aInstance,
453 const uint8_t * aUdpPayload,
454 uint16_t aUdpPayloadLen,
455 const otSockAddr *aDestSockAddr)
456 {
457 OT_UNUSED_VARIABLE(aInstance);
458
459 VerifyOrExit(!IsSystemDryRun());
460
461 VerifyOrExit(sEnabled);
462
463 assert(aUdpPayloadLen <= TREL_MAX_PACKET_SIZE);
464
465 // We try to send the packet immediately. If it fails (e.g.,
466 // network is down) `SendPacket()` returns `OT_ERROR_ABORT`. If
467 // the send operation would block (e.g., socket is not yet ready
468 // or is out of buffer) we get `OT_ERROR_INVALID_STATE`. In that
469 // case we enqueue the packet to send it later when socket becomes
470 // ready.
471
472 if ((sTxPacketQueueTail != NULL) ||
473 (SendPacket(aUdpPayload, aUdpPayloadLen, aDestSockAddr) == OT_ERROR_INVALID_STATE))
474 {
475 EnqueuePacket(aUdpPayload, aUdpPayloadLen, aDestSockAddr);
476 }
477
478 exit:
479 return;
480 }
481
otPlatTrelRegisterService(otInstance * aInstance,uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)482 void otPlatTrelRegisterService(otInstance *aInstance, uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
483 {
484 OT_UNUSED_VARIABLE(aInstance);
485 VerifyOrExit(!IsSystemDryRun());
486
487 trelDnssdRegisterService(aPort, aTxtData, aTxtLength);
488
489 exit:
490 return;
491 }
492
493 //---------------------------------------------------------------------------------------------------------------------
494 // platformTrel system
495
platformTrelInit(const char * aTrelUrl)496 void platformTrelInit(const char *aTrelUrl)
497 {
498 otLogDebgPlat("[trel] platformTrelInit(aTrelUrl:\"%s\")", aTrelUrl != nullptr ? aTrelUrl : "");
499
500 assert(!sInitialized);
501
502 if (aTrelUrl != nullptr)
503 {
504 ot::Posix::RadioUrl url(aTrelUrl);
505 strncpy(sInterfaceName, url.GetPath(), sizeof(sInterfaceName) - 1);
506 sInterfaceName[sizeof(sInterfaceName) - 1] = '\0';
507 }
508
509 trelDnssdInitialize(sInterfaceName);
510
511 InitPacketQueue();
512 sInitialized = true;
513 }
514
platformTrelDeinit(void)515 void platformTrelDeinit(void)
516 {
517 VerifyOrExit(sInitialized);
518
519 otPlatTrelDisable(nullptr);
520 sInterfaceName[0] = '\0';
521 sInitialized = false;
522 otLogDebgPlat("[trel] platformTrelDeinit()");
523
524 exit:
525 return;
526 }
527
platformTrelUpdateFdSet(fd_set * aReadFdSet,fd_set * aWriteFdSet,int * aMaxFd,struct timeval * aTimeout)528 void platformTrelUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout)
529 {
530 assert((aReadFdSet != NULL) && (aWriteFdSet != NULL) && (aMaxFd != NULL) && (aTimeout != NULL));
531
532 VerifyOrExit(sEnabled);
533
534 FD_SET(sSocket, aReadFdSet);
535
536 if (sTxPacketQueueTail != NULL)
537 {
538 FD_SET(sSocket, aWriteFdSet);
539 }
540
541 if (*aMaxFd < sSocket)
542 {
543 *aMaxFd = sSocket;
544 }
545
546 trelDnssdUpdateFdSet(aReadFdSet, aWriteFdSet, aMaxFd, aTimeout);
547
548 exit:
549 return;
550 }
551
platformTrelProcess(otInstance * aInstance,const fd_set * aReadFdSet,const fd_set * aWriteFdSet)552 void platformTrelProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
553 {
554 VerifyOrExit(sEnabled);
555
556 if (FD_ISSET(sSocket, aWriteFdSet))
557 {
558 SendQueuedPackets();
559 }
560
561 if (FD_ISSET(sSocket, aReadFdSet))
562 {
563 ReceivePacket(sSocket, aInstance);
564 }
565
566 trelDnssdProcess(aInstance, aReadFdSet, aWriteFdSet);
567
568 exit:
569 return;
570 }
571
572 #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
573