• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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