/* * 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. */ #include "posix/platform/daemon.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "cli/cli_config.h" #include "common/code_utils.hpp" #include "posix/platform/platform-posix.h" #if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE #define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock" static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockaddr_un::sun_path), "OpenThread daemon socket name too long!"); namespace ot { namespace Posix { namespace { typedef char(Filename)[sizeof(sockaddr_un::sun_path)]; void GetFilename(Filename &aFilename, const char *aPattern) { int rval; rval = snprintf(aFilename, sizeof(aFilename), aPattern, gNetifName); if (rval < 0 && static_cast(rval) >= sizeof(aFilename)) { DieNow(OT_EXIT_INVALID_ARGUMENTS); } } } // namespace int Daemon::OutputFormatV(const char *aFormat, va_list aArguments) { char buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH + 1]; int rval; buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH] = '\0'; rval = vsnprintf(buf, sizeof(buf) - 1, aFormat, aArguments); VerifyOrExit(rval >= 0, otLogWarnPlat("Failed to format CLI output: %s", strerror(errno))); VerifyOrExit(mSessionSocket != -1); #if defined(__linux__) // Don't die on SIGPIPE rval = send(mSessionSocket, buf, static_cast(rval), MSG_NOSIGNAL); #else rval = static_cast(write(mSessionSocket, buf, static_cast(rval))); #endif if (rval < 0) { otLogWarnPlat("Failed to write CLI output: %s", strerror(errno)); close(mSessionSocket); mSessionSocket = -1; } exit: return rval; } void Daemon::InitializeSessionSocket(void) { int newSessionSocket; int rval; VerifyOrExit((newSessionSocket = accept(mListenSocket, nullptr, nullptr)) != -1, rval = -1); VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1); rval |= FD_CLOEXEC; VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1); #ifndef __linux__ // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going // to simply ignore it. #if defined(SO_NOSIGPIPE) rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval)); VerifyOrExit(rval != -1); #else #warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE" #endif #endif // __linux__ if (mSessionSocket != -1) { close(mSessionSocket); } mSessionSocket = newSessionSocket; exit: if (rval == -1) { otLogWarnPlat("Failed to initialize session socket: %s", strerror(errno)); if (newSessionSocket != -1) { close(newSessionSocket); } } else { otLogInfoPlat("Session socket is ready", strerror(errno)); } } void Daemon::SetUp(void) { struct sockaddr_un sockname; int ret; class AllowAllGuard { public: AllowAllGuard(void) { const char *allowAll = getenv("OT_DAEMON_ALLOW_ALL"); mAllowAll = (allowAll != nullptr && strcmp("1", allowAll) == 0); if (mAllowAll) { mMode = umask(0); } } ~AllowAllGuard(void) { if (mAllowAll) { umask(mMode); } } private: bool mAllowAll = false; mode_t mMode = 0; }; // This allows implementing pseudo reset. VerifyOrExit(mListenSocket == -1); mListenSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock); if (mListenSocket == -1) { DieNow(OT_EXIT_FAILURE); } { static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK) == sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME), "sock and lock file name pattern should have the same length!"); Filename lockfile; GetFilename(lockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK); mDaemonLock = open(lockfile, O_CREAT | O_RDONLY | O_CLOEXEC, 0600); } if (mDaemonLock == -1) { DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO); } if (flock(mDaemonLock, LOCK_EX | LOCK_NB) == -1) { DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO); } memset(&sockname, 0, sizeof(struct sockaddr_un)); sockname.sun_family = AF_UNIX; GetFilename(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME); (void)unlink(sockname.sun_path); { AllowAllGuard allowAllGuard; ret = bind(mListenSocket, reinterpret_cast(&sockname), sizeof(struct sockaddr_un)); } if (ret == -1) { DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO); } // // only accept 1 connection. // ret = listen(mListenSocket, 1); if (ret == -1) { DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO); } otCliInit( gInstance, [](void *aContext, const char *aFormat, va_list aArguments) -> int { return static_cast(aContext)->OutputFormatV(aFormat, aArguments); }, this); Mainloop::Manager::Get().Add(*this); exit: return; } void Daemon::TearDown(void) { Mainloop::Manager::Get().Remove(*this); if (mSessionSocket != -1) { close(mSessionSocket); mSessionSocket = -1; } if (mListenSocket != -1) { close(mListenSocket); mListenSocket = -1; } if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE) { Filename sockfile; GetFilename(sockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME); otLogDebgPlat("Removing daemon socket: %s", sockfile); (void)unlink(sockfile); } if (mDaemonLock != -1) { (void)flock(mDaemonLock, LOCK_UN); close(mDaemonLock); mDaemonLock = -1; } } void Daemon::Update(otSysMainloopContext &aContext) { if (mListenSocket != -1) { FD_SET(mListenSocket, &aContext.mReadFdSet); FD_SET(mListenSocket, &aContext.mErrorFdSet); if (aContext.mMaxFd < mListenSocket) { aContext.mMaxFd = mListenSocket; } } if (mSessionSocket != -1) { FD_SET(mSessionSocket, &aContext.mReadFdSet); FD_SET(mSessionSocket, &aContext.mErrorFdSet); if (aContext.mMaxFd < mSessionSocket) { aContext.mMaxFd = mSessionSocket; } } return; } void Daemon::Process(const otSysMainloopContext &aContext) { ssize_t rval; VerifyOrExit(mListenSocket != -1); if (FD_ISSET(mListenSocket, &aContext.mErrorFdSet)) { DieNowWithMessage("daemon socket error", OT_EXIT_FAILURE); } else if (FD_ISSET(mListenSocket, &aContext.mReadFdSet)) { InitializeSessionSocket(); } VerifyOrExit(mSessionSocket != -1); if (FD_ISSET(mSessionSocket, &aContext.mErrorFdSet)) { close(mSessionSocket); mSessionSocket = -1; } else if (FD_ISSET(mSessionSocket, &aContext.mReadFdSet)) { uint8_t buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH]; // leave 1 byte for the null terminator rval = read(mSessionSocket, buffer, sizeof(buffer) - 1); if (rval > 0) { buffer[rval] = '\0'; otLogInfoPlat("> %s", reinterpret_cast(buffer)); otCliInputLine(reinterpret_cast(buffer)); } else { if (rval < 0) { otLogWarnPlat("Daemon read: %s", strerror(errno)); } close(mSessionSocket); mSessionSocket = -1; } } exit: return; } Daemon &Daemon::Get(void) { static Daemon sInstance; return sInstance; } } // namespace Posix } // namespace ot #endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE