diff --git a/configure.ac b/configure.ac index 3edc2b0a0..d33e4cd1f 100644 --- a/configure.ac +++ b/configure.ac @@ -470,6 +470,38 @@ AC_SUBST(OPENTHREAD_ENABLE_POSIX_APP) AM_CONDITIONAL([OPENTHREAD_ENABLE_POSIX_APP], [test "${enable_posix_app}" = "yes"]) AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_POSIX_APP], [${OPENTHREAD_ENABLE_POSIX_APP}], [Define to 1 to build posix application.]) +# +# POSIX Daemon +# + +AC_MSG_CHECKING([whether to build POSIX applicaton in daemon mode]) +AC_ARG_ENABLE(posix-app-daemon, + [AS_HELP_STRING([--enable-posix-app-daemon], [Build POSIX application in daemon mode@<:@default=no@:>@.])], + [ + case "${enableval}" in + + no|yes) + enable_posix_app_daemon=${enableval} + ;; + + *) + AC_MSG_ERROR([Invalid value ${enable_posix_app} for --enable-posix-app-daemon]) + ;; + esac + ], + [enable_posix_app_daemon=no]) + +if test "$enable_posix_app_daemon" = "yes"; then + OPENTHREAD_ENABLE_POSIX_APP_DAEMON=1 +else + OPENTHREAD_ENABLE_POSIX_APP_DAEMON=0 +fi + +AC_MSG_RESULT(${enable_posix_app_daemon}) +AC_SUBST(OPENTHREAD_ENABLE_POSIX_APP_DAEMON) +AM_CONDITIONAL([OPENTHREAD_ENABLE_POSIX_APP_DAEMON], [test "${enable_posix_app_daemon}" = "yes"]) +AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_POSIX_APP_DAEMON], [${OPENTHREAD_ENABLE_POSIX_APP_DAEMON}], [Define to 1 to build posix application in daemon mode.]) + # # FTD Library # diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix index bf439166f..06c7c114c 100644 --- a/src/posix/Makefile-posix +++ b/src/posix/Makefile-posix @@ -44,6 +44,7 @@ COMMISSIONER ?= 1 CHANNEL_MANAGER ?= 1 CHANNEL_MONITOR ?= 1 CHILD_SUPERVISION ?= 1 +DAEMON ?= 0 DHCP6_CLIENT ?= 1 DHCP6_SERVER ?= 1 DIAGNOSTIC ?= 1 @@ -76,6 +77,10 @@ configure_OPTIONS = \ # Platform specific switches +ifeq (${DAEMON},1) +configure_OPTIONS += --enable-posix-app-daemon +endif + ifneq ($(DEBUG),1) COMMONCFLAGS += \ -O2 \ diff --git a/src/posix/Makefile.am b/src/posix/Makefile.am index e67257a3b..944e2e564 100644 --- a/src/posix/Makefile.am +++ b/src/posix/Makefile.am @@ -77,12 +77,6 @@ bin_PROGRAMS = \ $(NULL) if OPENTHREAD_ENABLE_EXECUTABLE -if OPENTHREAD_ENABLE_NCP -bin_PROGRAMS += \ - ot-ncp \ - $(NULL) -endif - ot_ncp_CPPFLAGS = \ $(CPPFLAGS_COMMON) \ -DOPENTHREAD_POSIX_APP=1 \ @@ -108,11 +102,56 @@ ot_ncp_LIBTOOLFLAGS = \ $(LIBTOOLFLAGS_COMMON) \ $(NULL) +if OPENTHREAD_ENABLE_POSIX_APP_DAEMON +bin_PROGRAMS += \ + ot-ctl \ + ot-daemon \ + $(NULL) +else # OPENTHREAD_ENABLE_POSIX_APP_DAEMON if OPENTHREAD_ENABLE_CLI bin_PROGRAMS += \ ot-cli \ $(NULL) endif +if OPENTHREAD_ENABLE_NCP +bin_PROGRAMS += \ + ot-ncp \ + $(NULL) +endif +endif # OPENTHREAD_ENABLE_POSIX_APP_DAEMON + +ot_ctl_SOURCES = \ + client.c \ + $(NULL) + +ot_ctl_CPPFLAGS = \ + $(CPPFLAGS_COMMON) \ + $(NULL) + +ot_daemon_CPPFLAGS = \ + $(CPPFLAGS_COMMON) \ + -DOPENTHREAD_POSIX_APP=2 \ + $(NULL) + +ot_daemon_SOURCES = \ + main.c \ + $(NULL) + +ot_daemon_LDADD = \ + $(top_builddir)/src/cli/libopenthread-cli-ftd.a \ + $(top_builddir)/src/core/libopenthread-ftd.a \ + $(LDADD_COMMON) \ + $(top_builddir)/src/core/libopenthread-ftd.a \ + $(LDADD_COMMON) \ + $(NULL) + +ot_daemon_LDFLAGS = \ + $(LDFLAGS_COMMON) \ + $(NULL) + +ot_daemon_LIBTOOLFLAGS = \ + $(LIBTOOLFLAGS_COMMON) \ + $(NULL) ot_cli_CPPFLAGS = \ $(CPPFLAGS_COMMON) \ diff --git a/src/posix/README.md b/src/posix/README.md index 41a15045a..78453fc75 100644 --- a/src/posix/README.md +++ b/src/posix/README.md @@ -105,3 +105,20 @@ sudo wpantund -s 'system:./output/posix/x86_64-unknown-linux-gnu/bin/ot-ncp /dev # CC2538 sudo wpantund -s 'system:./output/posix/x86_64-unknown-linux-gnu/bin/ot-ncp /dev/ttyUSB0 115200' ``` + +Daemon Mode Support +------------------- + +OpenThread Posix Daemon mode uses a unix socket as input and output, so that OpenThread core can run as a service. And a client +can communicate with it by connecting to the socket. The protocol is OpenThread CLI. + +``` +# build daemon mode core stack for POSIX +make -f src/posix/Makefile-posix DAEMON=1 +# Daemon with simulation +./output/posix/x86_64-unknown-linux-gnu/bin/ot-daemon ./output/x86_64-unknown-linux-gnu/bin/ot-ncp-radio 1 +# Daemon with real device +./output/posix/x86_64-unknown-linux-gnu/bin/ot-daemon /dev/ttyACM0 115200 +# Built-in controller +./output/posix/x86_64-unknown-linux-gnu/bin/ot-ctl +``` diff --git a/src/posix/client.c b/src/posix/client.c new file mode 100644 index 000000000..538349dbe --- /dev/null +++ b/src/posix/client.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019, 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 "openthread-core-config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "code_utils.h" +#include "platform-posix.h" + +int main(int argc, char *argv[]) +{ + OT_UNUSED_VARIABLE(argc); + OT_UNUSED_VARIABLE(argv); + + int ret; + int session = -1; + + session = socket(AF_UNIX, SOCK_STREAM, 0); + otEXPECT_ACTION(session != -1, perror("socket"); ret = OT_EXIT_FAILURE); + + { + struct sockaddr_un sockname; + + memset(&sockname, 0, sizeof(struct sockaddr_un)); + sockname.sun_family = AF_UNIX; + strncpy(sockname.sun_path, OPENTHREAD_POSIX_APP_SOCKET_NAME, sizeof(sockname.sun_path) - 1); + + ret = connect(session, (const struct sockaddr *)&sockname, sizeof(struct sockaddr_un)); + + if (ret == -1) + { + fprintf(stderr, "OpenThread daemon is not running.\n"); + otEXIT_NOW(ret = OT_EXIT_FAILURE); + } + } + + while (1) + { + char buffer[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + fd_set readFdSet; + int maxFd = STDIN_FILENO; + + FD_ZERO(&readFdSet); + + FD_SET(STDIN_FILENO, &readFdSet); + FD_SET(session, &readFdSet); + + maxFd = session > maxFd ? session : maxFd; + + ret = select(maxFd + 1, &readFdSet, NULL, NULL, NULL); + + otEXPECT_ACTION(ret != -1, perror("select"); ret = OT_EXIT_FAILURE); + + if (ret == 0) + { + otEXIT_NOW(ret = OT_EXIT_SUCCESS); + } + + if (FD_ISSET(STDIN_FILENO, &readFdSet)) + { + otEXPECT_ACTION(fgets(buffer, sizeof(buffer), stdin) != NULL, ret = OT_EXIT_FAILURE); + + ret = (int)write(session, buffer, strlen(buffer)); + otEXPECT_ACTION(ret != -1, perror("write"); ret = OT_EXIT_FAILURE); + } + + if (FD_ISSET(session, &readFdSet)) + { + ret = (int)read(session, buffer, sizeof(buffer)); + otEXPECT_ACTION(ret != -1, perror("read"); ret = OT_EXIT_FAILURE); + + if (ret == 0) + { + // daemon closed session + otEXIT_NOW(ret = OT_EXIT_FAILURE); + } + else + { + buffer[ret] = 0; + printf("%s", buffer); + fflush(stdout); + } + } + } + +exit: + if (session != -1) + { + close(session); + } + + return ret; +} diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp index 9f0613355..fb3d63f75 100644 --- a/src/posix/platform/hdlc_interface.hpp +++ b/src/posix/platform/hdlc_interface.hpp @@ -34,7 +34,7 @@ #ifndef POSIX_APP_HDLC_INTERFACE_HPP_ #define POSIX_APP_HDLC_INTERFACE_HPP_ -#include "platform-posix.h" +#include "platform-config.h" #include "hdlc.hpp" diff --git a/src/posix/platform/platform-config.h b/src/posix/platform/platform-config.h new file mode 100644 index 000000000..e6f6739f4 --- /dev/null +++ b/src/posix/platform/platform-config.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019, 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 + * @brief + * This file includes the POSIX platform-specific configurations. + */ + +/** + * @def OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE + * + * Define as 1 to enable PTY device support in POSIX app. + * + */ +#ifndef OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE +#define OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE 1 +#endif + +/** + * @def OPENTHREAD_POSIX_APP_SOCKET_BASENAME + * + * Define socket basename used by POSIX app daemon. + * + */ +#ifndef OPENTHREAD_POSIX_APP_SOCKET_BASENAME +#define OPENTHREAD_POSIX_APP_SOCKET_BASENAME "/tmp/openthread" +#endif diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index 12c9f03e7..e321bd243 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -40,15 +40,13 @@ #include +#include "platform-config.h" + /** - * @def OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE - * - * Define as 1 to enable PTY device support in POSIX app. + * This is the socket name used by daemon mode. * */ -#ifndef OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE -#define OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE 1 -#endif +#define OPENTHREAD_POSIX_APP_SOCKET_NAME OPENTHREAD_POSIX_APP_SOCKET_BASENAME ".sock" #ifdef __cplusplus extern "C" { diff --git a/src/posix/platform/uart.c b/src/posix/platform/uart.c index e0d44fe35..994a6e364 100644 --- a/src/posix/platform/uart.c +++ b/src/posix/platform/uart.c @@ -29,6 +29,16 @@ #include "openthread-core-config.h" #include "platform-posix.h" +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON +#include +#include +#include +#include +#include +#include +#include +#endif + #include #include #include @@ -37,9 +47,16 @@ #include "code_utils.h" -static uint8_t sReceiveBuffer[128]; -static const uint8_t *sWriteBuffer; -static uint16_t sWriteLength; +#define OPENTHREAD_POSIX_APP_SOCKET_LOCK OPENTHREAD_POSIX_APP_SOCKET_BASENAME ".lock" + +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON +static int sUartSocket = -1; +static int sUartLock = -1; +static int sSessionSocket = -1; +#endif + +static const uint8_t *sWriteBuffer = NULL; +static uint16_t sWriteLength = 0; void platformUartRestore(void) { @@ -47,12 +64,86 @@ void platformUartRestore(void) otError otPlatUartEnable(void) { - return OT_ERROR_NONE; + otError error = OT_ERROR_NONE; +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + struct sockaddr_un sockname; + int ret; + + sUartSocket = socket(AF_UNIX, SOCK_STREAM, 0); + if (sUartSocket == -1) + { + perror("socket"); + exit(OT_EXIT_FAILURE); + } + + sUartLock = open(OPENTHREAD_POSIX_APP_SOCKET_LOCK, O_CREAT | O_RDONLY, 0600); + + if (sUartLock == -1) + { + perror("open"); + exit(OT_EXIT_FAILURE); + } + + if (flock(sUartLock, LOCK_EX | LOCK_NB) == -1) + { + perror("flock"); + exit(OT_EXIT_FAILURE); + } + + memset(&sockname, 0, sizeof(struct sockaddr_un)); + + (void)unlink(OPENTHREAD_POSIX_APP_SOCKET_NAME); + + sockname.sun_family = AF_UNIX; + assert(sizeof(OPENTHREAD_POSIX_APP_SOCKET_NAME) < sizeof(sockname.sun_path), "socket name too long"); + strncpy(sockname.sun_path, OPENTHREAD_POSIX_APP_SOCKET_NAME, sizeof(sockname.sun_path) - 1); + + ret = bind(sUartSocket, (const struct sockaddr *)&sockname, sizeof(struct sockaddr_un)); + + if (ret == -1) + { + perror("bind"); + exit(OT_EXIT_FAILURE); + } + + // + // only accept 1 connection. + // + ret = listen(sUartSocket, 1); + if (ret == -1) + { + perror("listen"); + exit(OT_EXIT_FAILURE); + } +#endif // OPENTHREAD_ENABLE_POSIX_APP_DAEMON + return error; } otError otPlatUartDisable(void) { - return OT_ERROR_NONE; + otError error = OT_ERROR_NONE; + +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + if (sSessionSocket != -1) + { + close(sSessionSocket); + sSessionSocket = -1; + } + + if (sUartSocket != -1) + { + close(sUartSocket); + sUartSocket = -1; + } + + if (sUartLock != -1) + { + close(sUartLock); + sUartLock = -1; + } +#endif // OPENTHREAD_ENABLE_POSIX_APP_DAEMON + + return error; } otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength) @@ -72,31 +163,42 @@ void platformUartUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aE { if (aReadFdSet != NULL) { - FD_SET(STDIN_FILENO, aReadFdSet); +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + int fd = (sSessionSocket == -1 ? sUartSocket : sSessionSocket); +#else + int fd = STDIN_FILENO; +#endif + + FD_SET(fd, aReadFdSet); if (aErrorFdSet != NULL) { - FD_SET(STDIN_FILENO, aErrorFdSet); + FD_SET(fd, aErrorFdSet); } - if (aMaxFd != NULL && *aMaxFd < STDIN_FILENO) + if (aMaxFd != NULL && *aMaxFd < fd) { - *aMaxFd = STDIN_FILENO; + *aMaxFd = fd; } } - if ((aWriteFdSet != NULL) && (sWriteLength > 0)) { - FD_SET(STDOUT_FILENO, aWriteFdSet); +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + int fd = (sSessionSocket == -1 ? sUartSocket : sSessionSocket); +#else + int fd = STDOUT_FILENO; +#endif + + FD_SET(fd, aWriteFdSet); if (aErrorFdSet != NULL) { - FD_SET(STDOUT_FILENO, aErrorFdSet); + FD_SET(fd, aErrorFdSet); } - if (aMaxFd != NULL && *aMaxFd < STDOUT_FILENO) + if (aMaxFd != NULL && *aMaxFd < fd) { - *aMaxFd = STDOUT_FILENO; + *aMaxFd = fd; } } } @@ -104,7 +206,31 @@ void platformUartUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aE void platformUartProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, const fd_set *aErrorFdSet) { ssize_t rval; + int fd; +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + if (FD_ISSET(sUartSocket, aErrorFdSet)) + { + perror("socket error"); + exit(OT_EXIT_FAILURE); + } + else if (FD_ISSET(sUartSocket, aReadFdSet)) + { + sSessionSocket = accept(sUartSocket, NULL, NULL); + } + + otEXPECT(sSessionSocket != -1); + + if (FD_ISSET(sSessionSocket, aErrorFdSet)) + { + close(sSessionSocket); + sSessionSocket = -1; + } + + otEXPECT(sSessionSocket != -1); + + fd = sSessionSocket; +#else // OPENTHREAD_ENABLE_POSIX_APP_DAEMON if (FD_ISSET(STDIN_FILENO, aErrorFdSet)) { perror("stdin"); @@ -117,36 +243,54 @@ void platformUartProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, co exit(OT_EXIT_FAILURE); } - if (FD_ISSET(STDIN_FILENO, aReadFdSet)) + fd = STDIN_FILENO; +#endif // OPENTHREAD_ENABLE_POSIX_APP_DAEMON + + if (FD_ISSET(fd, aReadFdSet)) { - rval = read(STDIN_FILENO, sReceiveBuffer, sizeof(sReceiveBuffer)); + uint8_t buffer[256]; + + rval = read(fd, buffer, sizeof(buffer)); if (rval > 0) { - otPlatUartReceived(sReceiveBuffer, (uint16_t)rval); + otPlatUartReceived(buffer, (uint16_t)rval); } - else if (rval < 0) + else if (rval <= 0) { perror("UART read"); +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + close(sSessionSocket); + sSessionSocket = -1; + otEXIT_NOW(); +#else exit(OT_EXIT_FAILURE); - } - else - { - fprintf(stderr, "UART ended\r\n"); - exit(OT_EXIT_SUCCESS); +#endif } } - if ((sWriteLength > 0) && (FD_ISSET(STDOUT_FILENO, aWriteFdSet))) - { - rval = write(STDOUT_FILENO, sWriteBuffer, sWriteLength); +#if !OPENTHREAD_ENABLE_POSIX_APP_DAEMON + fd = STDOUT_FILENO; +#endif - if (rval <= 0) + if ((sWriteLength > 0) && (FD_ISSET(fd, aWriteFdSet))) + { + rval = write(fd, sWriteBuffer, sWriteLength); + + if (rval < 0) { perror("UART write"); +#if OPENTHREAD_ENABLE_POSIX_APP_DAEMON + close(sSessionSocket); + sSessionSocket = -1; + otEXIT_NOW(); +#else exit(OT_EXIT_FAILURE); +#endif } + otEXPECT(rval > 0); + sWriteBuffer += (uint16_t)rval; sWriteLength -= (uint16_t)rval; @@ -155,4 +299,7 @@ void platformUartProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, co otPlatUartSendDone(); } } + +exit: + return; }