| /* |
| * Copyright (c) 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 |
| * The file implements the OTBR Agent. |
| */ |
| |
| #define OTBR_LOG_TAG "APP" |
| |
| #ifdef HAVE_LIBSYSTEMD |
| #include <systemd/sd-daemon.h> |
| #endif |
| |
| #include "agent/application.hpp" |
| #include "common/code_utils.hpp" |
| #include "common/mainloop_manager.hpp" |
| #include "host/posix/dnssd.hpp" |
| #include "utils/infra_link_selector.hpp" |
| |
| namespace otbr { |
| |
| #ifndef OTBR_MAINLOOP_POLL_TIMEOUT_SEC |
| #define OTBR_MAINLOOP_POLL_TIMEOUT_SEC 10 |
| #endif |
| |
| std::atomic_bool Application::sShouldTerminate(false); |
| std::atomic_bool Application::sIsPseudoReset(false); |
| const struct timeval Application::kPollTimeout = {OTBR_MAINLOOP_POLL_TIMEOUT_SEC, 0}; |
| |
| Application::Application(Host::ThreadHost &aHost, |
| const std::string &aInterfaceName, |
| const std::string &aBackboneInterfaceName) |
| : mInterfaceName(aInterfaceName) |
| , mBackboneInterfaceName(aBackboneInterfaceName) |
| , mHost(aHost) |
| #if OTBR_ENABLE_MDNS |
| , mPublisher( |
| Mdns::Publisher::Create([this](Mdns::Publisher::State aState) { mMdnsStateSubject.UpdateState(aState); })) |
| #endif |
| #if OTBR_ENABLE_DNSSD_PLAT |
| , mDnssdPlatform(*mPublisher) |
| #endif |
| #if OTBR_ENABLE_BORDER_AGENT |
| #if OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| , mBorderAgent(*mPublisher) |
| #endif |
| , mBorderAgentUdpProxy(mHost) |
| #if OTBR_ENABLE_EPSKC |
| , mEphemeralKeyUdpProxy(mHost) |
| #endif |
| #endif |
| #if OTBR_ENABLE_DBUS_SERVER |
| , mDBusAgent(MakeDBusDependentComponents()) |
| #endif |
| { |
| sShouldTerminate = false; |
| sIsPseudoReset = false; |
| |
| if (mHost.GetCoprocessorType() == OT_COPROCESSOR_RCP) |
| { |
| CreateRcpMode(); |
| } |
| else if (mHost.GetCoprocessorType() == OT_COPROCESSOR_NCP) |
| { |
| CreateNcpMode(); |
| } |
| else |
| { |
| DieNow("Unknown Co-processor type!"); |
| } |
| } |
| |
| void Application::Init(const std::string &aRestListenAddress, int aRestListenPort) |
| { |
| CoprocessorType type; |
| |
| mHost.Init(); |
| |
| type = mHost.GetCoprocessorType(); |
| |
| switch (type) |
| { |
| case OT_COPROCESSOR_RCP: |
| InitRcpMode(aRestListenAddress, aRestListenPort); |
| break; |
| case OT_COPROCESSOR_NCP: |
| InitNcpMode(); |
| break; |
| default: |
| DieNow("Unknown coprocessor type!"); |
| break; |
| } |
| |
| #if OTBR_ENABLE_DBUS_SERVER |
| mDBusAgent.Init(); |
| #endif |
| |
| otbrLogInfo("%s Co-processor version: %s", type == OT_COPROCESSOR_RCP ? "Radio" : "Network", |
| mHost.GetCoprocessorVersion()); |
| } |
| |
| void Application::Deinit(void) |
| { |
| switch (mHost.GetCoprocessorType()) |
| { |
| case OT_COPROCESSOR_RCP: |
| DeinitRcpMode(); |
| break; |
| case OT_COPROCESSOR_NCP: |
| DeinitNcpMode(); |
| break; |
| default: |
| DieNow("Unknown coprocessor type!"); |
| break; |
| } |
| |
| mHost.Deinit(); |
| |
| #if OTBR_ENABLE_DBUS_SERVER |
| mDBusAgent.Deinit(); |
| #endif |
| } |
| |
| otbrError Application::Run(void) |
| { |
| otbrError error = OTBR_ERROR_NONE; |
| |
| #ifdef HAVE_LIBSYSTEMD |
| if (getenv("SYSTEMD_EXEC_PID") != nullptr) |
| { |
| otbrLogInfo("Notify systemd the service is ready."); |
| |
| // Ignored return value as systemd recommends. |
| // See https://www.freedesktop.org/software/systemd/man/sd_notify.html |
| sd_notify(0, "READY=1"); |
| } |
| #endif |
| |
| #if OTBR_ENABLE_NOTIFY_UPSTART |
| if (getenv("UPSTART_JOB") != nullptr) |
| { |
| otbrLogInfo("Notify Upstart the service is ready."); |
| if (raise(SIGSTOP)) |
| { |
| otbrLogWarning("Failed to notify Upstart."); |
| } |
| } |
| #endif |
| |
| // allow quitting elegantly |
| signal(SIGTERM, HandleSignal); |
| |
| // avoid exiting on SIGPIPE |
| signal(SIGPIPE, SIG_IGN); |
| |
| while (!sShouldTerminate) |
| { |
| otbr::MainloopContext mainloop; |
| int rval; |
| |
| mainloop.mMaxFd = -1; |
| mainloop.mTimeout = kPollTimeout; |
| |
| FD_ZERO(&mainloop.mReadFdSet); |
| FD_ZERO(&mainloop.mWriteFdSet); |
| FD_ZERO(&mainloop.mErrorFdSet); |
| |
| MainloopManager::GetInstance().Update(mainloop); |
| |
| rval = select(mainloop.mMaxFd + 1, &mainloop.mReadFdSet, &mainloop.mWriteFdSet, &mainloop.mErrorFdSet, |
| &mainloop.mTimeout); |
| |
| if (rval >= 0) |
| { |
| MainloopManager::GetInstance().Process(mainloop); |
| |
| if (mErrorCondition) |
| { |
| error = mErrorCondition(); |
| if (error != OTBR_ERROR_NONE) |
| { |
| break; |
| } |
| } |
| } |
| else if (errno != EINTR) |
| { |
| error = OTBR_ERROR_ERRNO; |
| otbrLogErr("select() failed: %s", strerror(errno)); |
| break; |
| } |
| } |
| |
| return error; |
| } |
| |
| void Application::HandleSignal(int aSignal) |
| { |
| sShouldTerminate = true; |
| signal(aSignal, SIG_DFL); |
| } |
| |
| void Application::CreateRcpMode(void) |
| { |
| otbr::Host::RcpHost &rcpHost = static_cast<otbr::Host::RcpHost &>(mHost); |
| #if OTBR_ENABLE_BACKBONE_ROUTER |
| mBackboneAgent = MakeUnique<BackboneRouter::BackboneAgent>(rcpHost); |
| #endif |
| #if OTBR_ENABLE_SRP_ADVERTISING_PROXY |
| mAdvertisingProxy = MakeUnique<AdvertisingProxy>(rcpHost, *mPublisher); |
| #endif |
| #if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY |
| mDiscoveryProxy = MakeUnique<Dnssd::DiscoveryProxy>(rcpHost, *mPublisher); |
| #endif |
| #if OTBR_ENABLE_TREL_DNSSD |
| mTrelDnssd = MakeUnique<TrelDnssd::TrelDnssd>(rcpHost, *mPublisher); |
| #endif |
| #if OTBR_ENABLE_OPENWRT |
| mUbusAgent = MakeUnique<ubus::UBusAgent>(rcpHost); |
| #endif |
| #if OTBR_ENABLE_REST_SERVER |
| mRestWebServer = std::make_shared<rest::RestWebServer>(rcpHost); |
| #endif |
| #if OTBR_ENABLE_VENDOR_SERVER |
| mVendorServer = vendor::VendorServer::newInstance(*this); |
| #endif |
| |
| OTBR_UNUSED_VARIABLE(rcpHost); |
| } |
| |
| void Application::InitRcpMode(const std::string &aRestListenAddress, int aRestListenPort) |
| { |
| Host::RcpHost &rcpHost = static_cast<otbr::Host::RcpHost &>(mHost); |
| OTBR_UNUSED_VARIABLE(rcpHost); |
| OTBR_UNUSED_VARIABLE(aRestListenAddress); |
| OTBR_UNUSED_VARIABLE(aRestListenPort); |
| |
| #if OTBR_ENABLE_BORDER_AGENT && OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| mMdnsStateSubject.AddObserver(mBorderAgent); |
| #endif |
| #if OTBR_ENABLE_SRP_ADVERTISING_PROXY |
| mMdnsStateSubject.AddObserver(*mAdvertisingProxy); |
| #endif |
| #if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY |
| mMdnsStateSubject.AddObserver(*mDiscoveryProxy); |
| #endif |
| #if OTBR_ENABLE_TREL_DNSSD |
| mMdnsStateSubject.AddObserver(*mTrelDnssd); |
| #endif |
| #if OTBR_ENABLE_DNSSD_PLAT |
| mMdnsStateSubject.AddObserver(mDnssdPlatform); |
| mDnssdPlatform.SetDnssdStateChangedCallback(([&rcpHost](otPlatDnssdState aState) { |
| OTBR_UNUSED_VARIABLE(aState); |
| otPlatDnssdStateHandleStateChange(rcpHost.GetInstance()); |
| })); |
| #endif |
| |
| #if OTBR_ENABLE_MDNS |
| mPublisher->Start(); |
| #endif |
| #if OTBR_ENABLE_BORDER_AGENT |
| rcpHost.AddThreadRoleChangedCallback([this, &rcpHost](otDeviceRole aRole) { |
| OT_UNUSED_VARIABLE(aRole); |
| // This ensures the Border Agent is started only when the Thread device |
| // successfully attaches to a network. This aligns with the OpenThread |
| // core behavior and is particularly useful when the agent is initially |
| // disabled (e.g., via OTBR_STOP_BORDER_AGENT_ON_INIT). |
| if (rcpHost.IsAttached()) |
| { |
| mBorderAgent.SetEnabled(true); |
| } |
| }); |
| #if OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| mHost.SetBorderAgentMeshCoPServiceChangedCallback( |
| [this](bool aIsActive, uint16_t aPort, const uint8_t *aTxtData, uint16_t aLength) { |
| mBorderAgent.HandleBorderAgentMeshCoPServiceChanged(aIsActive, aPort, |
| std::vector<uint8_t>(aTxtData, aTxtData + aLength)); |
| }); |
| mHost.AddEphemeralKeyStateChangedCallback([this](otBorderAgentEphemeralKeyState aEpskcState, uint16_t aPort) { |
| mBorderAgent.HandleEpskcStateChanged(aEpskcState, aPort); |
| }); |
| SetBorderAgentOnInitState(); |
| #else |
| mBorderAgent.SetVendorTxtDataChangedCallback([this](const BorderAgent::TxtData &aVendorTxtData) { |
| otError error = mHost.SetBorderAgentMeshCoPServiceBaseName(mBorderAgent.GetBaseServiceInstanceName()); |
| if (error != OT_ERROR_NONE) |
| { |
| otbrLogWarning("Failed to set Border Agent MeshCoP service base name: %s", otThreadErrorToString(error)); |
| } |
| |
| mHost.SetBorderAgentVendorTxtData(aVendorTxtData); |
| }); |
| #endif |
| #endif // OTBR_ENABLE_BORDER_AGENT |
| #if OTBR_ENABLE_BACKBONE_ROUTER |
| mBackboneAgent->Init(); |
| #endif |
| #if OTBR_ENABLE_SRP_ADVERTISING_PROXY |
| mAdvertisingProxy->SetEnabled(true); |
| #endif |
| #if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY |
| mDiscoveryProxy->SetEnabled(true); |
| #endif |
| #if OTBR_ENABLE_OPENWRT |
| mUbusAgent->Init(); |
| #endif |
| #if OTBR_ENABLE_REST_SERVER |
| mRestWebServer->Init(aRestListenAddress, aRestListenPort); |
| #endif |
| #if OTBR_ENABLE_VENDOR_SERVER |
| mVendorServer->Init(); |
| #endif |
| #if OTBR_ENABLE_DNSSD_PLAT |
| mDnssdPlatform.Start(); |
| #endif |
| } |
| |
| void Application::DeinitRcpMode(void) |
| { |
| #if OTBR_ENABLE_DNSSD_PLAT |
| mDnssdPlatform.Stop(); |
| #endif |
| #if OTBR_ENABLE_SRP_ADVERTISING_PROXY |
| mAdvertisingProxy->SetEnabled(false); |
| #endif |
| #if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY |
| mDiscoveryProxy->SetEnabled(false); |
| #endif |
| #if OTBR_ENABLE_BORDER_AGENT |
| mBorderAgent.SetEnabled(false); |
| mBorderAgent.Deinit(); |
| #endif |
| #if OTBR_ENABLE_MDNS |
| mMdnsStateSubject.Clear(); |
| mPublisher->Stop(); |
| #endif |
| } |
| |
| void Application::CreateNcpMode(void) |
| { |
| otbr::Host::NcpHost &ncpHost = static_cast<otbr::Host::NcpHost &>(mHost); |
| |
| mNetif = MakeUnique<Netif>(mInterfaceName, ncpHost); |
| mInfraIf = MakeUnique<InfraIf>(ncpHost); |
| #if OTBR_ENABLE_BACKBONE_ROUTER |
| mMulticastRoutingManager = MakeUnique<MulticastRoutingManager>(*mNetif, *mInfraIf, ncpHost); |
| #endif |
| } |
| |
| void Application::InitNcpMode(void) |
| { |
| otbr::Host::NcpHost &ncpHost = static_cast<otbr::Host::NcpHost &>(mHost); |
| |
| SuccessOrDie(mNetif->Init(), "Failed to initialize the Netif!"); |
| ncpHost.InitNetifCallbacks(*mNetif); |
| |
| mInfraIf->Init(); |
| if (!mBackboneInterfaceName.empty()) |
| { |
| mInfraIf->SetInfraIf(mBackboneInterfaceName); |
| } |
| ncpHost.InitInfraIfCallbacks(*mInfraIf); |
| |
| #if OTBR_ENABLE_SRP_ADVERTISING_PROXY |
| mMdnsStateSubject.AddObserver(ncpHost); |
| #endif |
| #if OTBR_ENABLE_BORDER_AGENT && OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| mMdnsStateSubject.AddObserver(mBorderAgent); |
| #endif |
| #if OTBR_ENABLE_DNSSD_PLAT |
| mMdnsStateSubject.AddObserver(mDnssdPlatform); |
| #endif |
| #if OTBR_ENABLE_MDNS |
| ncpHost.SetMdnsPublisher(mPublisher.get()); |
| mPublisher->Start(); |
| #endif |
| |
| #if OTBR_ENABLE_BORDER_AGENT |
| mHost.SetBorderAgentMeshCoPServiceChangedCallback( |
| [this](bool aIsActive, uint16_t aPort, const uint8_t *aTxtData, uint16_t aLength) { |
| if (!aIsActive) |
| { |
| mBorderAgentUdpProxy.Stop(); |
| } |
| else |
| { |
| mBorderAgentUdpProxy.Start(aPort); |
| } |
| #if OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| mBorderAgent.HandleBorderAgentMeshCoPServiceChanged(aIsActive, mBorderAgentUdpProxy.GetHostPort(), |
| std::vector<uint8_t>(aTxtData, aTxtData + aLength)); |
| #else |
| OTBR_UNUSED_VARIABLE(aTxtData); |
| OTBR_UNUSED_VARIABLE(aLength); |
| #endif |
| }); |
| mHost.SetUdpForwardToHostCallback([this](const uint8_t *aUdpPayload, uint16_t aLength, |
| const otIp6Address &aPeerAddr, uint16_t aPeerPort, uint16_t aLocalPort) { |
| if (aLocalPort == mBorderAgentUdpProxy.GetThreadPort()) |
| { |
| mBorderAgentUdpProxy.SendToPeer(aUdpPayload, aLength, aPeerAddr, aPeerPort); |
| } |
| #if OTBR_ENABLE_EPSKC |
| else if (aLocalPort == mEphemeralKeyUdpProxy.GetThreadPort()) |
| { |
| mEphemeralKeyUdpProxy.SendToPeer(aUdpPayload, aLength, aPeerAddr, aPeerPort); |
| } |
| #endif // OTBR_ENABLE_EPSKC |
| }); |
| #if OTBR_ENABLE_EPSKC |
| mHost.AddEphemeralKeyStateChangedCallback([this](otBorderAgentEphemeralKeyState aState, uint16_t aPort) { |
| if (aState == OT_BORDER_AGENT_STATE_STARTED) |
| { |
| otbrLogInfo("Border Agent Ephemeral Key State Changed: Active on port %d", aPort); |
| mEphemeralKeyUdpProxy.Start(aPort); |
| } |
| else if (aState == OT_BORDER_AGENT_STATE_STOPPED || aState == OT_BORDER_AGENT_STATE_DISABLED) |
| { |
| otbrLogInfo("Border Agent Ephemeral Key State Changed: Inactive"); |
| mEphemeralKeyUdpProxy.Stop(); |
| } |
| #if OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| mBorderAgent.HandleEpskcStateChanged(aState, mEphemeralKeyUdpProxy.GetHostPort()); |
| #endif // OTBR_ENABLE_BORDER_AGENT_MESHCOP_SERVICE |
| }); |
| #endif // OTBR_ENABLE_EPSKC |
| SetBorderAgentOnInitState(); |
| #endif |
| #if OTBR_ENABLE_BACKBONE_ROUTER |
| mHost.SetBackboneRouterStateChangedCallback( |
| [this](otBackboneRouterState aState) { mMulticastRoutingManager->HandleStateChange(aState); }); |
| mHost.SetBackboneRouterMulticastListenerCallback( |
| [this](otBackboneRouterMulticastListenerEvent aEvent, const Ip6Address &aAddress) { |
| mMulticastRoutingManager->HandleBackboneMulticastListenerEvent(aEvent, aAddress); |
| }); |
| #if OTBR_ENABLE_BACKBONE_ROUTER_ON_INIT |
| mHost.SetBackboneRouterEnabled(true); |
| #endif |
| #endif |
| |
| #if OTBR_ENABLE_DNSSD_PLAT |
| mDnssdPlatform.Start(); |
| #endif |
| } |
| |
| void Application::DeinitNcpMode(void) |
| { |
| #if OTBR_ENABLE_BORDER_AGENT |
| mBorderAgent.SetEnabled(false); |
| mBorderAgent.Deinit(); |
| mBorderAgentUdpProxy.Stop(); |
| #endif |
| #if OTBR_ENABLE_SRP_ADVERTISING_PROXY |
| mPublisher->Stop(); |
| #endif |
| mNetif->Deinit(); |
| mInfraIf->Deinit(); |
| } |
| |
| #if OTBR_ENABLE_BORDER_AGENT |
| void Application::SetBorderAgentOnInitState(void) |
| { |
| // This is for delaying publishing the MeshCoP service until the correct |
| // vendor name and OUI etc. are correctly set by BorderAgent::SetMeshCopServiceValues() |
| #if OTBR_STOP_BORDER_AGENT_ON_INIT |
| mBorderAgent.SetEnabled(false); |
| #else |
| mBorderAgent.SetEnabled(true); |
| #endif |
| } |
| #endif |
| |
| #if OTBR_ENABLE_DBUS_SERVER |
| DBus::DependentComponents Application::MakeDBusDependentComponents(void) |
| { |
| return DBus::DependentComponents{mHost, *mPublisher, |
| #if OTBR_ENABLE_BORDER_AGENT |
| mBorderAgent |
| #endif |
| }; |
| } |
| #endif |
| |
| } // namespace otbr |