| /* |
| * Copyright (c) 2025, 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. |
| */ |
| |
| #define OTBR_LOG_TAG "AGENT" |
| |
| #include <openthread-br/config.h> |
| |
| #include <algorithm> |
| #include <new> |
| #include <vector> |
| |
| #include <assert.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <openthread/logging.h> |
| #include <openthread/platform/radio.h> |
| |
| #if OTBR_ENABLE_PLATFORM_ANDROID |
| #include <cutils/properties.h> |
| #endif |
| |
| #include "agent/application.hpp" |
| #include "agent/realmain.hpp" |
| #include "common/code_utils.hpp" |
| #include "common/logging.hpp" |
| #include "common/mainloop.hpp" |
| #include "common/types.hpp" |
| #include "host/thread_host.hpp" |
| |
| #ifdef OTBR_ENABLE_PLATFORM_ANDROID |
| #include <log/log.h> |
| #ifndef __ANDROID__ |
| #error "OTBR_ENABLE_PLATFORM_ANDROID can be enabled for only Android devices" |
| #endif |
| #define OTBR_ENABLE_PLATFORM_RESET_EXIT |
| #endif |
| |
| #define DEFAULT_INTERFACE_NAME "wpan0" |
| static const char kDefaultInterfaceName[] = DEFAULT_INTERFACE_NAME; |
| |
| // Port number used by Rest server. |
| static const uint32_t kPortNumber = 8081; |
| #define HELP_DEFAULT_REST_PORT_NUMBER "8081" |
| |
| enum |
| { |
| OTBR_OPT_BACKBONE_INTERFACE_NAME = 'B', |
| OTBR_OPT_DEBUG_LEVEL = 'd', |
| OTBR_OPT_HELP = 'h', |
| OTBR_OPT_INTERFACE_NAME = 'I', |
| OTBR_OPT_VERBOSE = 'v', |
| OTBR_OPT_SYSLOG_DISABLE = 's', |
| OTBR_OPT_VERSION = 'V', |
| OTBR_OPT_SHORTMAX = 128, |
| OTBR_OPT_DATA_PATH, |
| OTBR_OPT_RADIO_VERSION, |
| OTBR_OPT_AUTO_ATTACH, |
| OTBR_OPT_REST_LISTEN_ADDR, |
| OTBR_OPT_REST_LISTEN_PORT, |
| #ifndef OTBR_VENDOR_NAME |
| OTBR_OPT_VENDOR_NAME, |
| #endif |
| #ifndef OTBR_PRODUCT_NAME |
| OTBR_OPT_MODEL_NAME, |
| #endif |
| }; |
| |
| #ifndef OTBR_ENABLE_PLATFORM_RESET_EXIT |
| static jmp_buf sResetJump; |
| #endif |
| static otbr::Application *gApp = nullptr; |
| |
| void __gcov_flush(); |
| static const struct option kOptions[] = { |
| {"backbone-ifname", required_argument, nullptr, OTBR_OPT_BACKBONE_INTERFACE_NAME}, |
| {"data-path", required_argument, nullptr, OTBR_OPT_DATA_PATH}, |
| {"debug-level", required_argument, nullptr, OTBR_OPT_DEBUG_LEVEL}, |
| {"help", no_argument, nullptr, OTBR_OPT_HELP}, |
| {"thread-ifname", required_argument, nullptr, OTBR_OPT_INTERFACE_NAME}, |
| {"verbose", no_argument, nullptr, OTBR_OPT_VERBOSE}, |
| {"syslog-disable", no_argument, nullptr, OTBR_OPT_SYSLOG_DISABLE}, |
| {"version", no_argument, nullptr, OTBR_OPT_VERSION}, |
| {"radio-version", no_argument, nullptr, OTBR_OPT_RADIO_VERSION}, |
| {"auto-attach", optional_argument, nullptr, OTBR_OPT_AUTO_ATTACH}, |
| {"rest-listen-address", required_argument, nullptr, OTBR_OPT_REST_LISTEN_ADDR}, |
| {"rest-listen-port", required_argument, nullptr, OTBR_OPT_REST_LISTEN_PORT}, |
| #ifndef OTBR_VENDOR_NAME |
| {"vendor-name", required_argument, nullptr, OTBR_OPT_VENDOR_NAME}, |
| #endif |
| #ifndef OTBR_PRODUCT_NAME |
| {"model-name", required_argument, nullptr, OTBR_OPT_MODEL_NAME}, |
| #endif |
| {0, 0, 0, 0}}; |
| |
| static bool ParseInteger(const char *aStr, long &aOutResult) |
| { |
| bool successful = true; |
| char *strEnd; |
| long result; |
| |
| VerifyOrExit(aStr != nullptr, successful = false); |
| errno = 0; |
| result = strtol(aStr, &strEnd, 0); |
| VerifyOrExit(errno != ERANGE, successful = false); |
| VerifyOrExit(aStr != strEnd, successful = false); |
| |
| aOutResult = result; |
| |
| exit: |
| return successful; |
| } |
| |
| #ifndef OTBR_ENABLE_PLATFORM_RESET_EXIT |
| static constexpr char kAutoAttachDisableArg[] = "--auto-attach=0"; |
| static char sAutoAttachDisableArgStorage[sizeof(kAutoAttachDisableArg)]; |
| |
| static std::vector<char *> AppendAutoAttachDisableArg(int argc, char *argv[]) |
| { |
| std::vector<char *> args(argv, argv + argc); |
| |
| args.erase(std::remove_if( |
| args.begin(), args.end(), |
| [](const char *arg) { return arg != nullptr && std::string(arg).rfind("--auto-attach", 0) == 0; }), |
| args.end()); |
| strcpy(sAutoAttachDisableArgStorage, kAutoAttachDisableArg); |
| args.push_back(sAutoAttachDisableArgStorage); |
| args.push_back(nullptr); |
| |
| return args; |
| } |
| #endif |
| |
| static void PrintHelp(const char *aProgramName) |
| { |
| fprintf(stderr, |
| "Usage: %s [-I interfaceName] [-B backboneIfName] [-d DEBUG_LEVEL] [-v] [-s] [--auto-attach[=0/1]] " |
| "RADIO_URL [RADIO_URL]\n" |
| " --data-path Path of directory to store data.\n" |
| " -I, --thread-ifname Name of the Thread network interface (default: " DEFAULT_INTERFACE_NAME ").\n" |
| " -B, --backbone-ifname Name of the backbone network interfaces (can be specified multiple times).\n" |
| " -d, --debug-level The log level (EMERG=0, ALERT=1, CRIT=2, ERR=3, WARNING=4, NOTICE=5, INFO=6, " |
| "DEBUG=7).\n" |
| " -v, --verbose Enable verbose logging.\n" |
| " -s, --syslog-disable Disable syslog and print to standard error.\n" |
| " -h, --help Show this help text.\n" |
| " -V, --version Print the application's version and exit.\n" |
| " --radio-version Print the radio coprocessor version and exit.\n" |
| " --auto-attach Whether or not to automatically attach to the saved network (default: 1).\n" |
| " --rest-listen-address Network address to listen on for the REST API (default: 127.0.0.1).\n" |
| " --rest-listen-port Network port to listen on for the REST API " |
| "(default: " HELP_DEFAULT_REST_PORT_NUMBER ").\n", |
| aProgramName); |
| #ifndef OTBR_VENDOR_NAME |
| fprintf(stderr, " --vendor-name Vendor Name.\n"); |
| #endif |
| #ifndef OTBR_PRODUCT_NAME |
| fprintf(stderr, " --model-name Model Name.\n"); |
| #endif |
| fprintf(stderr, "\n"); |
| fprintf(stderr, "%s", otSysGetRadioUrlHelpString()); |
| } |
| |
| static void PrintVersion(void) |
| { |
| printf("%s\n", OTBR_PACKAGE_VERSION); |
| } |
| |
| [[noreturn]] static void OnAllocateFailed(void) |
| { |
| otbrLogCrit("Allocate failure, exiting..."); |
| exit(1); |
| } |
| |
| static otbrLogLevel GetDefaultLogLevel(void) |
| { |
| #if OTBR_ENABLE_PLATFORM_ANDROID |
| // The log level is set to DEBUG by default, the final output log will be filtered by Android log system. |
| otbrLogLevel level = OTBR_LOG_DEBUG; |
| char value[PROPERTY_VALUE_MAX]; |
| |
| // Set the Android log level to INFO by default. |
| __android_log_set_minimum_priority(ANDROID_LOG_INFO); |
| |
| property_get("ro.build.type", value, "user"); |
| if (!strcmp(value, "user")) |
| { |
| level = OTBR_LOG_NOTICE; |
| } |
| #else |
| otbrLogLevel level = OTBR_LOG_INFO; |
| #endif |
| |
| return level; |
| } |
| |
| [[noreturn]] static void PrintRadioVersionAndExit(const std::vector<const char *> &aRadioUrls) |
| { |
| auto host = std::unique_ptr<otbr::Host::ThreadHost>( |
| otbr::Host::ThreadHost::Create(/* aInterfaceName */ "", aRadioUrls, |
| /* aBackboneInterfaceName */ "", |
| /* aDryRun */ true, /* aEnableAutoAttach */ false)); |
| const char *coprocessorVersion; |
| |
| host->Init(); |
| |
| coprocessorVersion = host->GetCoprocessorVersion(); |
| printf("%s\n", coprocessorVersion); |
| |
| host->Deinit(); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| static int realmain(int argc, char *argv[]) |
| { |
| otbrLogLevel logLevel = GetDefaultLogLevel(); |
| int opt; |
| int ret = EXIT_SUCCESS; |
| const char *interfaceName = kDefaultInterfaceName; |
| bool verbose = false; |
| bool syslogDisable = false; |
| bool printRadioVersion = false; |
| bool enableAutoAttach = true; |
| const char *restListenAddress = "127.0.0.1"; |
| int restListenPort = kPortNumber; |
| const char *dataPath = ""; |
| std::vector<const char *> radioUrls; |
| std::vector<const char *> backboneInterfaceNames; |
| long parseResult; |
| |
| #ifdef OTBR_VENDOR_NAME |
| const char *vendorName = OTBR_VENDOR_NAME; |
| #else |
| const char *vendorName = nullptr; |
| #endif |
| #ifdef OTBR_PRODUCT_NAME |
| const char *productName = OTBR_PRODUCT_NAME; |
| #else |
| const char *productName = nullptr; |
| #endif |
| |
| std::set_new_handler(OnAllocateFailed); |
| |
| optind = 0; |
| while ((opt = getopt_long(argc, argv, "B:d:hI:Vvs", kOptions, nullptr)) != -1) |
| { |
| switch (opt) |
| { |
| case OTBR_OPT_BACKBONE_INTERFACE_NAME: |
| backboneInterfaceNames.push_back(optarg); |
| otbrLogNotice("Backbone interface: %s", optarg); |
| break; |
| |
| case OTBR_OPT_DEBUG_LEVEL: |
| VerifyOrExit(ParseInteger(optarg, parseResult), ret = EXIT_FAILURE); |
| VerifyOrExit(OTBR_LOG_EMERG <= parseResult && parseResult <= OTBR_LOG_DEBUG, ret = EXIT_FAILURE); |
| logLevel = static_cast<otbrLogLevel>(parseResult); |
| break; |
| |
| case OTBR_OPT_INTERFACE_NAME: |
| interfaceName = optarg; |
| break; |
| |
| case OTBR_OPT_VERBOSE: |
| verbose = true; |
| break; |
| |
| case OTBR_OPT_SYSLOG_DISABLE: |
| syslogDisable = true; |
| break; |
| |
| case OTBR_OPT_VERSION: |
| PrintVersion(); |
| ExitNow(); |
| break; |
| |
| case OTBR_OPT_HELP: |
| PrintHelp(argv[0]); |
| ExitNow(ret = EXIT_SUCCESS); |
| break; |
| |
| case OTBR_OPT_RADIO_VERSION: |
| printRadioVersion = true; |
| break; |
| |
| case OTBR_OPT_AUTO_ATTACH: |
| if (optarg == nullptr) |
| { |
| enableAutoAttach = true; |
| } |
| else |
| { |
| VerifyOrExit(ParseInteger(optarg, parseResult), ret = EXIT_FAILURE); |
| enableAutoAttach = parseResult; |
| } |
| break; |
| case OTBR_OPT_REST_LISTEN_ADDR: |
| restListenAddress = optarg; |
| break; |
| |
| case OTBR_OPT_REST_LISTEN_PORT: |
| VerifyOrExit(ParseInteger(optarg, parseResult), ret = EXIT_FAILURE); |
| restListenPort = parseResult; |
| break; |
| #ifndef OTBR_VENDOR_NAME |
| case OTBR_OPT_VENDOR_NAME: |
| vendorName = optarg; |
| break; |
| #endif |
| #ifndef OTBR_PRODUCT_NAME |
| case OTBR_OPT_MODEL_NAME: |
| productName = optarg; |
| break; |
| #endif |
| case OTBR_OPT_DATA_PATH: |
| dataPath = optarg; |
| break; |
| |
| default: |
| PrintHelp(argv[0]); |
| ExitNow(ret = EXIT_FAILURE); |
| break; |
| } |
| } |
| |
| #ifndef OTBR_VENDOR_NAME |
| if (vendorName == nullptr) |
| { |
| fprintf(stderr, "Vendor name must be set.\n"); |
| ExitNow(ret = EXIT_FAILURE); |
| } |
| #endif |
| #ifndef OTBR_PRODUCT_NAME |
| if (productName == nullptr) |
| { |
| fprintf(stderr, "Model name must be set.\n"); |
| ExitNow(ret = EXIT_FAILURE); |
| } |
| #endif |
| |
| otbrLogInit(argv[0], logLevel, verbose, syslogDisable); |
| otbrLogNotice("Running %s", OTBR_PACKAGE_VERSION); |
| otbrLogNotice("Thread version: %s", otbr::Host::RcpHost::GetThreadVersion()); |
| otbrLogNotice("Thread interface: %s", interfaceName); |
| |
| if (backboneInterfaceNames.empty()) |
| { |
| otbrLogNotice("Backbone interface is not specified"); |
| } |
| |
| for (int i = optind; i < argc; i++) |
| { |
| otbrLogNotice("Radio URL: %s", argv[i]); |
| radioUrls.push_back(argv[i]); |
| } |
| |
| if (printRadioVersion) |
| { |
| PrintRadioVersionAndExit(radioUrls); |
| assert(false); |
| } |
| |
| { |
| #if __linux__ |
| otbr::Utils::InfraLinkSelector infraLinkSelector(backboneInterfaceNames); |
| const std::string backboneInterfaceName = infraLinkSelector.Select(); |
| otbr::Application::ErrorCondition errorCondition = [&backboneInterfaceName, &infraLinkSelector](void) { |
| return std::string(infraLinkSelector.Select()) == backboneInterfaceName ? OTBR_ERROR_NONE |
| : OTBR_ERROR_INFRA_LINK_CHANGED; |
| }; |
| #else |
| const std::string backboneInterfaceName = backboneInterfaceNames.empty() ? "" : backboneInterfaceNames.front(); |
| #endif |
| std::unique_ptr<otbr::Host::ThreadHost> host = otbr::Host::ThreadHost::Create( |
| interfaceName, radioUrls, backboneInterfaceName.c_str(), /* aDryRun */ false, enableAutoAttach, dataPath); |
| |
| otbr::Application app(*host, interfaceName, backboneInterfaceName); |
| |
| gApp = &app; |
| |
| #if OTBR_ENABLE_BORDER_AGENT |
| #if !defined(OTBR_VENDOR_NAME) || !defined(OTBR_PRODUCT_NAME) |
| #ifdef OTBR_MESHCOP_SERVICE_INSTANCE_NAME |
| SuccessOrExit(app.GetBorderAgent().SetMeshCoPServiceValues(OTBR_MESHCOP_SERVICE_INSTANCE_NAME, productName, |
| vendorName, {}, {}), |
| ret = EXIT_FAILURE); |
| #else |
| char instanceName[otbr::kMaxVendorNameLength + 1 + otbr::kMaxProductNameLength + 1]; |
| snprintf(instanceName, sizeof(instanceName), "%s %s", vendorName, productName); |
| SuccessOrExit(app.GetBorderAgent().SetMeshCoPServiceValues(instanceName, productName, vendorName, {}, {}), |
| ret = EXIT_FAILURE); |
| #endif |
| #endif |
| #endif |
| |
| app.Init(restListenAddress, restListenPort); |
| |
| #ifndef OTBR_VENDOR_NAME |
| if (app.GetHost().GetCoprocessorType() == OT_COPROCESSOR_RCP) |
| { |
| SuccessOrExit(app.GetHost().SetVendorName(vendorName), ret = EXIT_FAILURE); |
| } |
| #else |
| OT_UNUSED_VARIABLE(vendorName); |
| #endif |
| #ifndef OTBR_PRODUCT_NAME |
| if (app.GetHost().GetCoprocessorType() == OT_COPROCESSOR_RCP) |
| { |
| SuccessOrExit(app.GetHost().SetVendorModel(productName), ret = EXIT_FAILURE); |
| } |
| #else |
| OT_UNUSED_VARIABLE(productName); |
| #endif |
| |
| #if __linux__ |
| app.SetErrorCondition(errorCondition); |
| #endif |
| |
| ret = app.Run(); |
| |
| app.Deinit(); |
| } |
| |
| otbrLogDeinit(); |
| |
| exit: |
| return ret; |
| } |
| |
| void otPlatReset(otInstance *aInstance) |
| { |
| OT_UNUSED_VARIABLE(aInstance); |
| |
| gPlatResetReason = OT_PLAT_RESET_REASON_SOFTWARE; |
| |
| VerifyOrDie(gApp != nullptr, "gApp is null"); |
| gApp->Deinit(); |
| gApp = nullptr; |
| |
| #ifndef OTBR_ENABLE_PLATFORM_RESET_EXIT |
| longjmp(sResetJump, 1); |
| assert(false); |
| #else |
| otbrLogNotice("Exit for platform reset!"); |
| |
| // Exits immediately. The system server will receive the |
| // signal and decide whether (and how) to restart the ot-daemon |
| exit(0); |
| #endif |
| } |
| |
| int otbr::RealMain(int argc, char *argv[]) |
| { |
| int ret; |
| |
| #ifndef OTBR_ENABLE_PLATFORM_RESET_EXIT |
| if (setjmp(sResetJump)) |
| { |
| std::vector<char *> args = AppendAutoAttachDisableArg(argc, argv); |
| |
| alarm(0); |
| #if OPENTHREAD_ENABLE_COVERAGE |
| __gcov_flush(); |
| #endif |
| |
| execvp(args[0], args.data()); |
| } |
| #endif |
| |
| while ((ret = realmain(argc, argv)) == 0) |
| { |
| VerifyOrExit(Application::IsPseudoReset()); |
| } |
| |
| exit: |
| return ret; |
| } |