/* * Copyright (c) 2020, 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 "WEB" #include "web/web-service/ot_client.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "common/code_utils.hpp" #include "common/logging.hpp" // Temporary solution before posix platform header files are cleaned up. #ifndef OPENTHREAD_POSIX_DAEMON_SOCKET_NAME #ifdef __linux__ #define OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME "/run/openthread-%s" #else #define OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME "/tmp/openthread-%s" #endif #define OPENTHREAD_POSIX_DAEMON_SOCKET_NAME OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".sock" #endif namespace otbr { namespace Web { OpenThreadClient::OpenThreadClient(const char *aNetifName) : mNetifName(aNetifName) , mTimeout(kDefaultTimeout) , mSocket(-1) { } OpenThreadClient::~OpenThreadClient(void) { Disconnect(); } void OpenThreadClient::Disconnect(void) { if (mSocket != -1) { close(mSocket); mSocket = -1; } } bool OpenThreadClient::Connect(void) { struct sockaddr_un sockname; int ret; mSocket = socket(AF_UNIX, SOCK_STREAM, 0); VerifyOrExit(mSocket != -1, perror("socket"); ret = EXIT_FAILURE); memset(&sockname, 0, sizeof(struct sockaddr_un)); sockname.sun_family = AF_UNIX; ret = snprintf(sockname.sun_path, sizeof(sockname.sun_path), OPENTHREAD_POSIX_DAEMON_SOCKET_NAME, mNetifName); VerifyOrExit(ret >= 0 && static_cast(ret) < sizeof(sockname.sun_path), { errno = EINVAL; ret = -1; }); ret = connect(mSocket, reinterpret_cast(&sockname), sizeof(struct sockaddr_un)); if (ret == -1) { otbrLogErr("OpenThread daemon is not running."); } exit: return ret == 0; } void OpenThreadClient::DiscardRead(void) { fd_set readFdSet; timeval timeout = {0, 0}; ssize_t count; int ret; for (;;) { FD_ZERO(&readFdSet); FD_SET(mSocket, &readFdSet); ret = select(mSocket + 1, &readFdSet, nullptr, nullptr, &timeout); if (ret <= 0) { break; } count = read(mSocket, mBuffer, sizeof(mBuffer)); if (count <= 0) { break; } } } char *OpenThreadClient::Execute(const char *aFormat, ...) { va_list args; int ret; char *rval = nullptr; ssize_t count; size_t rxLength = 0; DiscardRead(); va_start(args, aFormat); ret = vsnprintf(&mBuffer[1], sizeof(mBuffer) - 2, aFormat, args); va_end(args); if (ret < 0) { otbrLogErr("Failed to generate command: %s", strerror(errno)); ExitNow(); } if (static_cast(ret) >= sizeof(mBuffer) - 2) { otbrLogErr("Command exceeds maximum limit: %d", kBufferSize); ExitNow(); } mBuffer[0] = '\n'; mBuffer[ret + 1] = '\n'; ret += 2; count = write(mSocket, mBuffer, ret); if (count != ret) { mBuffer[ret] = '\0'; otbrLogErr("Failed to send command: %s", mBuffer); ExitNow(); } for (int i = 0; i < mTimeout; ++i) { fd_set readFdSet; timeval timeout = {0, 1000}; char *done; FD_ZERO(&readFdSet); FD_SET(mSocket, &readFdSet); ret = select(mSocket + 1, &readFdSet, nullptr, nullptr, &timeout); VerifyOrExit(ret != -1 || errno == EINTR); if (ret <= 0) { continue; } count = read(mSocket, &mBuffer[rxLength], sizeof(mBuffer) - rxLength); VerifyOrExit(count > 0); rxLength += count; mBuffer[rxLength] = '\0'; done = strstr(mBuffer, "Done\r\n> "); if (done != nullptr) { // remove trailing \r\n if (done - mBuffer > 2) { done[-2] = '\0'; } rval = mBuffer; break; } } exit: return rval; } char *OpenThreadClient::Read(const char *aResponse, int aTimeout) { ssize_t count = 0; size_t rxLength = 0; char *found; char *rval = nullptr; for (int i = 0; i < aTimeout; ++i) { count = read(mSocket, &mBuffer[rxLength], sizeof(mBuffer) - rxLength); VerifyOrExit(count > 0); rxLength += count; mBuffer[rxLength] = '\0'; found = strstr(mBuffer, aResponse); if (found != nullptr) { rval = mBuffer; break; } } exit: return rval; } int OpenThreadClient::Scan(WpanNetworkInfo *aNetworks, int aLength) { char *result; int rval = 0; mTimeout = 5000; result = Execute("scan"); VerifyOrExit(result != nullptr); for (result = strtok(result, "\r\n"); result != nullptr && rval < aLength; result = strtok(nullptr, "\r\n")) { static const char kCliPrompt[] = "> "; char *cliPrompt; int matched; int lqi; // remove prompt if ((cliPrompt = strstr(result, kCliPrompt)) != nullptr) { if (cliPrompt == result) { result += sizeof(kCliPrompt) - 1; } else { memmove(cliPrompt, cliPrompt + sizeof(kCliPrompt) - 1, strlen(cliPrompt) - sizeof(kCliPrompt) - 1); } } matched = sscanf(result, "| %hx | %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx | %hu | %hhd | %d |", &aNetworks[rval].mPanId, &aNetworks[rval].mHardwareAddress[0], &aNetworks[rval].mHardwareAddress[1], &aNetworks[rval].mHardwareAddress[2], &aNetworks[rval].mHardwareAddress[3], &aNetworks[rval].mHardwareAddress[4], &aNetworks[rval].mHardwareAddress[5], &aNetworks[rval].mHardwareAddress[6], &aNetworks[rval].mHardwareAddress[7], &aNetworks[rval].mChannel, &aNetworks[rval].mRssi, &lqi); // 15 is the number of output arguments of the last sscanf() if (matched != 12) { continue; } ++rval; } mTimeout = kDefaultTimeout; exit: return rval; } bool OpenThreadClient::FactoryReset(void) { const char *result; bool rval = false; #if __APPLE__ typedef sig_t sighandler_t; #endif sighandler_t handler; // Ignore the expected SIGPIPE signal during daemon reset. handler = signal(SIGPIPE, SIG_IGN); Execute("factoryreset"); signal(SIGPIPE, handler); Disconnect(); sleep(4); VerifyOrExit(rval = Connect()); result = Execute("version"); VerifyOrExit(result != nullptr); rval = strstr(result, "OPENTHREAD") != nullptr; exit: return rval; } } // namespace Web } // namespace otbr