1 /*
2 * Copyright (c) 2017, 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 * The file implements the Thread border agent.
32 */
33
34 #define OTBR_LOG_TAG "BA"
35
36 #include "border_agent/border_agent.hpp"
37
38 #include <arpa/inet.h>
39 #include <assert.h>
40 #include <errno.h>
41 #include <netinet/in.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #include <iomanip>
48 #include <random>
49 #include <sstream>
50
51 #include <openthread/border_agent.h>
52 #include <openthread/border_routing.h>
53 #include <openthread/random_noncrypto.h>
54 #include <openthread/thread_ftd.h>
55 #include <openthread/platform/settings.h>
56 #include <openthread/platform/toolchain.h>
57
58 #include "agent/uris.hpp"
59 #include "ncp/ncp_openthread.hpp"
60 #if OTBR_ENABLE_BACKBONE_ROUTER
61 #include "backbone_router/backbone_agent.hpp"
62 #endif
63 #include "common/byteswap.hpp"
64 #include "common/code_utils.hpp"
65 #include "common/logging.hpp"
66 #include "common/tlv.hpp"
67 #include "common/types.hpp"
68 #include "utils/hex.hpp"
69
70 namespace otbr {
71
72 static const char kBorderAgentServiceType[] = "_meshcop._udp"; ///< Border agent service type of mDNS
73 static constexpr int kBorderAgentServiceDummyPort = 49152;
74
75 /**
76 * Locators
77 *
78 */
79 enum
80 {
81 kAloc16Leader = 0xfc00, ///< leader anycast locator.
82 kInvalidLocator = 0xffff, ///< invalid locator.
83 };
84
85 enum : uint8_t
86 {
87 kConnectionModeDisabled = 0,
88 kConnectionModePskc = 1,
89 kConnectionModePskd = 2,
90 kConnectionModeVendor = 3,
91 kConnectionModeX509 = 4,
92 };
93
94 enum : uint8_t
95 {
96 kThreadIfStatusNotInitialized = 0,
97 kThreadIfStatusInitialized = 1,
98 kThreadIfStatusActive = 2,
99 };
100
101 enum : uint8_t
102 {
103 kAvailabilityInfrequent = 0,
104 kAvailabilityHigh = 1,
105 };
106
107 struct StateBitmap
108 {
109 uint32_t mConnectionMode : 3;
110 uint32_t mThreadIfStatus : 2;
111 uint32_t mAvailability : 2;
112 uint32_t mBbrIsActive : 1;
113 uint32_t mBbrIsPrimary : 1;
114
StateBitmapotbr::StateBitmap115 StateBitmap(void)
116 : mConnectionMode(0)
117 , mThreadIfStatus(0)
118 , mAvailability(0)
119 , mBbrIsActive(0)
120 , mBbrIsPrimary(0)
121 {
122 }
123
ToUint32otbr::StateBitmap124 uint32_t ToUint32(void) const
125 {
126 uint32_t bitmap = 0;
127
128 bitmap |= mConnectionMode << 0;
129 bitmap |= mThreadIfStatus << 3;
130 bitmap |= mAvailability << 5;
131 bitmap |= mBbrIsActive << 7;
132 bitmap |= mBbrIsPrimary << 8;
133
134 return bitmap;
135 }
136 };
137
BorderAgent(otbr::Ncp::ControllerOpenThread & aNcp,Mdns::Publisher & aPublisher)138 BorderAgent::BorderAgent(otbr::Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher)
139 : mNcp(aNcp)
140 , mPublisher(aPublisher)
141 , mIsEnabled(false)
142 , mVendorName(OTBR_VENDOR_NAME)
143 , mProductName(OTBR_PRODUCT_NAME)
144 , mBaseServiceInstanceName(OTBR_MESHCOP_SERVICE_INSTANCE_NAME)
145 {
146 mNcp.AddThreadStateChangedCallback([this](otChangedFlags aFlags) { HandleThreadStateChanged(aFlags); });
147 }
148
SetMeshCopServiceValues(const std::string & aServiceInstanceName,const std::string & aProductName,const std::string & aVendorName,const std::vector<uint8_t> & aVendorOui,const Mdns::Publisher::TxtList & aNonStandardTxtEntries)149 otbrError BorderAgent::SetMeshCopServiceValues(const std::string &aServiceInstanceName,
150 const std::string &aProductName,
151 const std::string &aVendorName,
152 const std::vector<uint8_t> &aVendorOui,
153 const Mdns::Publisher::TxtList &aNonStandardTxtEntries)
154 {
155 otbrError error = OTBR_ERROR_NONE;
156
157 VerifyOrExit(aProductName.size() <= kMaxProductNameLength, error = OTBR_ERROR_INVALID_ARGS);
158 VerifyOrExit(aVendorName.size() <= kMaxVendorNameLength, error = OTBR_ERROR_INVALID_ARGS);
159 VerifyOrExit(aVendorOui.empty() || aVendorOui.size() == kVendorOuiLength, error = OTBR_ERROR_INVALID_ARGS);
160 for (const auto &txtEntry : aNonStandardTxtEntries)
161 {
162 VerifyOrExit(!txtEntry.mKey.empty() && txtEntry.mKey.front() == 'v', error = OTBR_ERROR_INVALID_ARGS);
163 }
164
165 mProductName = aProductName;
166 mVendorName = aVendorName;
167 mVendorOui = aVendorOui;
168 mMeshCopTxtUpdate.clear();
169 for (const auto &txtEntry : aNonStandardTxtEntries)
170 {
171 mMeshCopTxtUpdate[txtEntry.mKey] = txtEntry.mValue;
172 }
173
174 mBaseServiceInstanceName = aServiceInstanceName;
175
176 exit:
177 return error;
178 }
179
SetEnabled(bool aIsEnabled)180 void BorderAgent::SetEnabled(bool aIsEnabled)
181 {
182 VerifyOrExit(IsEnabled() != aIsEnabled);
183 mIsEnabled = aIsEnabled;
184 if (mIsEnabled)
185 {
186 Start();
187 }
188 else
189 {
190 Stop();
191 }
192 exit:
193 return;
194 }
195
Start(void)196 void BorderAgent::Start(void)
197 {
198 otbrLogInfo("Start Thread Border Agent");
199
200 #if OTBR_ENABLE_DBUS_SERVER
201 mNcp.GetThreadHelper()->SetUpdateMeshCopTxtHandler([this](std::map<std::string, std::vector<uint8_t>> aUpdate) {
202 HandleUpdateVendorMeshCoPTxtEntries(std::move(aUpdate));
203 });
204 mNcp.RegisterResetHandler([this]() {
205 mNcp.GetThreadHelper()->SetUpdateMeshCopTxtHandler([this](std::map<std::string, std::vector<uint8_t>> aUpdate) {
206 HandleUpdateVendorMeshCoPTxtEntries(std::move(aUpdate));
207 });
208 });
209 #endif
210
211 mServiceInstanceName = GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName);
212 UpdateMeshCopService();
213 }
214
Stop(void)215 void BorderAgent::Stop(void)
216 {
217 otbrLogInfo("Stop Thread Border Agent");
218 UnpublishMeshCopService();
219 }
220
HandleMdnsState(Mdns::Publisher::State aState)221 void BorderAgent::HandleMdnsState(Mdns::Publisher::State aState)
222 {
223 VerifyOrExit(IsEnabled());
224
225 switch (aState)
226 {
227 case Mdns::Publisher::State::kReady:
228 UpdateMeshCopService();
229 break;
230 default:
231 otbrLogWarning("mDNS publisher not available!");
232 break;
233 }
234 exit:
235 return;
236 }
237
ConvertTimestampToUint64(const otTimestamp & aTimestamp)238 static uint64_t ConvertTimestampToUint64(const otTimestamp &aTimestamp)
239 {
240 // 64 bits Timestamp fields layout
241 //-----48 bits------//-----15 bits-----//-------1 bit-------//
242 // Seconds // Ticks // Authoritative //
243 return (aTimestamp.mSeconds << 16) | static_cast<uint64_t>(aTimestamp.mTicks << 1) |
244 static_cast<uint64_t>(aTimestamp.mAuthoritative);
245 }
246
247 #if OTBR_ENABLE_BORDER_ROUTING
AppendOmrTxtEntry(otInstance & aInstance,Mdns::Publisher::TxtList & aTxtList)248 void AppendOmrTxtEntry(otInstance &aInstance, Mdns::Publisher::TxtList &aTxtList)
249 {
250 otIp6Prefix omrPrefix;
251 otRoutePreference preference;
252
253 if (OT_ERROR_NONE == otBorderRoutingGetFavoredOmrPrefix(&aInstance, &omrPrefix, &preference))
254 {
255 std::vector<uint8_t> omrData;
256
257 omrData.reserve(1 + OT_IP6_PREFIX_SIZE);
258 omrData.push_back(omrPrefix.mLength);
259 std::copy(omrPrefix.mPrefix.mFields.m8, omrPrefix.mPrefix.mFields.m8 + (omrPrefix.mLength + 7) / 8,
260 std::back_inserter(omrData));
261 aTxtList.emplace_back("omr", omrData.data(), omrData.size());
262 }
263 }
264 #endif
265
GetStateBitmap(otInstance & aInstance)266 StateBitmap GetStateBitmap(otInstance &aInstance)
267 {
268 StateBitmap state;
269
270 state.mConnectionMode = kConnectionModePskc;
271 state.mAvailability = kAvailabilityHigh;
272
273 switch (otThreadGetDeviceRole(&aInstance))
274 {
275 case OT_DEVICE_ROLE_DISABLED:
276 state.mThreadIfStatus = kThreadIfStatusNotInitialized;
277 break;
278 case OT_DEVICE_ROLE_DETACHED:
279 state.mThreadIfStatus = kThreadIfStatusInitialized;
280 break;
281 default:
282 state.mThreadIfStatus = kThreadIfStatusActive;
283 }
284
285 #if OTBR_ENABLE_BACKBONE_ROUTER
286 state.mBbrIsActive = state.mThreadIfStatus == kThreadIfStatusActive &&
287 otBackboneRouterGetState(&aInstance) != OT_BACKBONE_ROUTER_STATE_DISABLED;
288 state.mBbrIsPrimary = state.mThreadIfStatus == kThreadIfStatusActive &&
289 otBackboneRouterGetState(&aInstance) == OT_BACKBONE_ROUTER_STATE_PRIMARY;
290 #endif
291
292 return state;
293 }
294
295 #if OTBR_ENABLE_BACKBONE_ROUTER
AppendBbrTxtEntries(otInstance & aInstance,StateBitmap aState,Mdns::Publisher::TxtList & aTxtList)296 void AppendBbrTxtEntries(otInstance &aInstance, StateBitmap aState, Mdns::Publisher::TxtList &aTxtList)
297 {
298 if (aState.mBbrIsActive)
299 {
300 otBackboneRouterConfig bbrConfig;
301 uint16_t bbrPort = htobe16(BackboneRouter::BackboneAgent::kBackboneUdpPort);
302
303 otBackboneRouterGetConfig(&aInstance, &bbrConfig);
304 aTxtList.emplace_back("sq", &bbrConfig.mSequenceNumber, sizeof(bbrConfig.mSequenceNumber));
305 aTxtList.emplace_back("bb", reinterpret_cast<const uint8_t *>(&bbrPort), sizeof(bbrPort));
306 }
307
308 aTxtList.emplace_back("dn", otThreadGetDomainName(&aInstance));
309 }
310 #endif
311
AppendActiveTimestampTxtEntry(otInstance & aInstance,Mdns::Publisher::TxtList & aTxtList)312 void AppendActiveTimestampTxtEntry(otInstance &aInstance, Mdns::Publisher::TxtList &aTxtList)
313 {
314 otError error;
315 otOperationalDataset activeDataset;
316
317 if ((error = otDatasetGetActive(&aInstance, &activeDataset)) != OT_ERROR_NONE)
318 {
319 otbrLogWarning("Failed to get active dataset: %s", otThreadErrorToString(error));
320 }
321 else
322 {
323 uint64_t activeTimestampValue = ConvertTimestampToUint64(activeDataset.mActiveTimestamp);
324
325 activeTimestampValue = htobe64(activeTimestampValue);
326 aTxtList.emplace_back("at", reinterpret_cast<uint8_t *>(&activeTimestampValue), sizeof(activeTimestampValue));
327 }
328 }
329
AppendVendorTxtEntries(const std::map<std::string,std::vector<uint8_t>> & aVendorEntries,Mdns::Publisher::TxtList & aTxtList)330 void AppendVendorTxtEntries(const std::map<std::string, std::vector<uint8_t>> &aVendorEntries,
331 Mdns::Publisher::TxtList &aTxtList)
332 {
333 for (const auto &entry : aVendorEntries)
334 {
335 const std::string &key = entry.first;
336 const std::vector<uint8_t> &value = entry.second;
337 bool found = false;
338
339 for (auto &addedEntry : aTxtList)
340 {
341 if (addedEntry.mKey == key)
342 {
343 addedEntry.mValue = value;
344 addedEntry.mIsBooleanAttribute = false;
345 found = true;
346 break;
347 }
348 }
349 if (!found)
350 {
351 aTxtList.emplace_back(key.c_str(), value.data(), value.size());
352 }
353 }
354 }
355
PublishMeshCopService(void)356 void BorderAgent::PublishMeshCopService(void)
357 {
358 StateBitmap state;
359 uint32_t stateUint32;
360 otInstance *instance = mNcp.GetInstance();
361 const otExtendedPanId *extPanId = otThreadGetExtendedPanId(instance);
362 const otExtAddress *extAddr = otLinkGetExtendedAddress(instance);
363 const char *networkName = otThreadGetNetworkName(instance);
364 Mdns::Publisher::TxtList txtList{{"rv", "1"}};
365 Mdns::Publisher::TxtData txtData;
366 int port;
367 otbrError error;
368
369 OTBR_UNUSED_VARIABLE(error);
370
371 otbrLogInfo("Publish meshcop service %s.%s.local.", mServiceInstanceName.c_str(), kBorderAgentServiceType);
372
373 #if OTBR_ENABLE_PUBLISH_MESHCOP_BA_ID
374 {
375 otError error;
376 otBorderAgentId id;
377
378 error = otBorderAgentGetId(instance, &id);
379 if (error == OT_ERROR_NONE)
380 {
381 txtList.emplace_back("id", id.mId, sizeof(id));
382 }
383 else
384 {
385 otbrLogWarning("Failed to retrieve Border Agent ID: %s", otThreadErrorToString(error));
386 }
387 }
388 #endif
389
390 if (!mVendorOui.empty())
391 {
392 txtList.emplace_back("vo", mVendorOui.data(), mVendorOui.size());
393 }
394 if (!mVendorName.empty())
395 {
396 txtList.emplace_back("vn", mVendorName.c_str());
397 }
398 if (!mProductName.empty())
399 {
400 txtList.emplace_back("mn", mProductName.c_str());
401 }
402 txtList.emplace_back("nn", networkName);
403 txtList.emplace_back("xp", extPanId->m8, sizeof(extPanId->m8));
404 txtList.emplace_back("tv", mNcp.GetThreadVersion());
405
406 // "xa" stands for Extended MAC Address (64-bit) of the Thread Interface of the Border Agent.
407 txtList.emplace_back("xa", extAddr->m8, sizeof(extAddr->m8));
408
409 state = GetStateBitmap(*instance);
410 stateUint32 = htobe32(state.ToUint32());
411 txtList.emplace_back("sb", reinterpret_cast<uint8_t *>(&stateUint32), sizeof(stateUint32));
412
413 if (state.mThreadIfStatus == kThreadIfStatusActive)
414 {
415 uint32_t partitionId;
416
417 AppendActiveTimestampTxtEntry(*instance, txtList);
418 partitionId = otThreadGetPartitionId(instance);
419 txtList.emplace_back("pt", reinterpret_cast<uint8_t *>(&partitionId), sizeof(partitionId));
420 }
421
422 #if OTBR_ENABLE_BACKBONE_ROUTER
423 AppendBbrTxtEntries(*instance, state, txtList);
424 #endif
425 #if OTBR_ENABLE_BORDER_ROUTING
426 AppendOmrTxtEntry(*instance, txtList);
427 #endif
428
429 AppendVendorTxtEntries(mMeshCopTxtUpdate, txtList);
430
431 if (otBorderAgentGetState(instance) != OT_BORDER_AGENT_STATE_STOPPED)
432 {
433 port = otBorderAgentGetUdpPort(instance);
434 }
435 else
436 {
437 // When thread interface is not active, the border agent is not started, thus it's not listening to any port and
438 // not handling requests. In such situation, we use a dummy port number for publishing the MeshCoP service to
439 // advertise the status of the border router. One can learn the thread interface status from `sb` entry so it
440 // doesn't have to send requests to the dummy port when border agent is not running.
441 port = kBorderAgentServiceDummyPort;
442 }
443
444 error = Mdns::Publisher::EncodeTxtData(txtList, txtData);
445 assert(error == OTBR_ERROR_NONE);
446
447 mPublisher.PublishService(/* aHostName */ "", mServiceInstanceName, kBorderAgentServiceType,
448 Mdns::Publisher::SubTypeList{}, port, txtData, [this](otbrError aError) {
449 if (aError == OTBR_ERROR_ABORTED)
450 {
451 // OTBR_ERROR_ABORTED is thrown when an ongoing service registration is
452 // cancelled. This can happen when the meshcop service is being updated
453 // frequently. To avoid false alarms, it should not be logged like a real error.
454 otbrLogInfo("Cancelled previous publishing meshcop service %s.%s.local",
455 mServiceInstanceName.c_str(), kBorderAgentServiceType);
456 }
457 else
458 {
459 otbrLogResult(aError, "Result of publish meshcop service %s.%s.local",
460 mServiceInstanceName.c_str(), kBorderAgentServiceType);
461 }
462 if (aError == OTBR_ERROR_DUPLICATED)
463 {
464 // Try to unpublish current service in case we are trying to register
465 // multiple new services simultaneously when the original service name
466 // is conflicted.
467 UnpublishMeshCopService();
468 mServiceInstanceName = GetAlternativeServiceInstanceName();
469 PublishMeshCopService();
470 }
471 });
472 }
473
UnpublishMeshCopService(void)474 void BorderAgent::UnpublishMeshCopService(void)
475 {
476 otbrLogInfo("Unpublish meshcop service %s.%s.local", mServiceInstanceName.c_str(), kBorderAgentServiceType);
477
478 mPublisher.UnpublishService(mServiceInstanceName, kBorderAgentServiceType, [this](otbrError aError) {
479 otbrLogResult(aError, "Result of unpublish meshcop service %s.%s.local", mServiceInstanceName.c_str(),
480 kBorderAgentServiceType);
481 });
482 }
483
UpdateMeshCopService(void)484 void BorderAgent::UpdateMeshCopService(void)
485 {
486 VerifyOrExit(IsEnabled());
487 VerifyOrExit(mPublisher.IsStarted());
488 PublishMeshCopService();
489
490 exit:
491 return;
492 }
493
494 #if OTBR_ENABLE_DBUS_SERVER
HandleUpdateVendorMeshCoPTxtEntries(std::map<std::string,std::vector<uint8_t>> aUpdate)495 void BorderAgent::HandleUpdateVendorMeshCoPTxtEntries(std::map<std::string, std::vector<uint8_t>> aUpdate)
496 {
497 mMeshCopTxtUpdate = std::move(aUpdate);
498 UpdateMeshCopService();
499 }
500 #endif
501
HandleThreadStateChanged(otChangedFlags aFlags)502 void BorderAgent::HandleThreadStateChanged(otChangedFlags aFlags)
503 {
504 VerifyOrExit(IsEnabled());
505
506 if (aFlags & OT_CHANGED_THREAD_ROLE)
507 {
508 otbrLogInfo("Thread is %s", (IsThreadStarted() ? "up" : "down"));
509 }
510
511 if (aFlags & (OT_CHANGED_THREAD_ROLE | OT_CHANGED_THREAD_EXT_PANID | OT_CHANGED_THREAD_NETWORK_NAME |
512 OT_CHANGED_THREAD_BACKBONE_ROUTER_STATE | OT_CHANGED_THREAD_NETDATA))
513 {
514 UpdateMeshCopService();
515 }
516
517 exit:
518 return;
519 }
520
IsThreadStarted(void) const521 bool BorderAgent::IsThreadStarted(void) const
522 {
523 otDeviceRole role = otThreadGetDeviceRole(mNcp.GetInstance());
524
525 return role == OT_DEVICE_ROLE_CHILD || role == OT_DEVICE_ROLE_ROUTER || role == OT_DEVICE_ROLE_LEADER;
526 }
527
GetServiceInstanceNameWithExtAddr(const std::string & aServiceInstanceName) const528 std::string BorderAgent::GetServiceInstanceNameWithExtAddr(const std::string &aServiceInstanceName) const
529 {
530 const otExtAddress *extAddress = otLinkGetExtendedAddress(mNcp.GetInstance());
531 std::stringstream ss;
532
533 ss << aServiceInstanceName << " #";
534 ss << std::uppercase << std::hex << std::setfill('0');
535 ss << std::setw(2) << static_cast<int>(extAddress->m8[6]);
536 ss << std::setw(2) << static_cast<int>(extAddress->m8[7]);
537 return ss.str();
538 }
539
GetAlternativeServiceInstanceName() const540 std::string BorderAgent::GetAlternativeServiceInstanceName() const
541 {
542 std::random_device r;
543 std::default_random_engine engine(r());
544 std::uniform_int_distribution<uint16_t> uniform_dist(1, 0xFFFF);
545 uint16_t rand = uniform_dist(engine);
546 std::stringstream ss;
547
548 ss << GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName) << " (" << rand << ")";
549 return ss.str();
550 }
551
552 } // namespace otbr
553