• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2021, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "posix/platform/daemon.hpp"
30 
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <stdarg.h>
34 #include <string.h>
35 #include <sys/file.h>
36 #include <sys/socket.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <sys/un.h>
40 #include <unistd.h>
41 
42 #include <openthread/cli.h>
43 
44 #include "cli/cli_config.h"
45 #include "common/code_utils.hpp"
46 #include "posix/platform/platform-posix.h"
47 
48 #if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
49 
50 #define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock"
51 static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockaddr_un::sun_path),
52               "OpenThread daemon socket name too long!");
53 
54 namespace ot {
55 namespace Posix {
56 
57 namespace {
58 
59 typedef char(Filename)[sizeof(sockaddr_un::sun_path)];
60 
GetFilename(Filename & aFilename,const char * aPattern)61 void GetFilename(Filename &aFilename, const char *aPattern)
62 {
63     int rval;
64 
65     rval = snprintf(aFilename, sizeof(aFilename), aPattern, gNetifName);
66     if (rval < 0 && static_cast<size_t>(rval) >= sizeof(aFilename))
67     {
68         DieNow(OT_EXIT_INVALID_ARGUMENTS);
69     }
70 }
71 
72 } // namespace
73 
OutputFormatV(const char * aFormat,va_list aArguments)74 int Daemon::OutputFormatV(const char *aFormat, va_list aArguments)
75 {
76     char buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH + 1];
77     int  rval;
78 
79     buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH] = '\0';
80 
81     rval = vsnprintf(buf, sizeof(buf) - 1, aFormat, aArguments);
82 
83     VerifyOrExit(rval >= 0, otLogWarnPlat("Failed to format CLI output: %s", strerror(errno)));
84 
85     VerifyOrExit(mSessionSocket != -1);
86 
87 #if defined(__linux__)
88     // Don't die on SIGPIPE
89     rval = send(mSessionSocket, buf, static_cast<size_t>(rval), MSG_NOSIGNAL);
90 #else
91     rval = static_cast<int>(write(mSessionSocket, buf, static_cast<size_t>(rval)));
92 #endif
93 
94     if (rval < 0)
95     {
96         otLogWarnPlat("Failed to write CLI output: %s", strerror(errno));
97         close(mSessionSocket);
98         mSessionSocket = -1;
99     }
100 
101 exit:
102     return rval;
103 }
104 
InitializeSessionSocket(void)105 void Daemon::InitializeSessionSocket(void)
106 {
107     int newSessionSocket;
108     int rval;
109 
110     VerifyOrExit((newSessionSocket = accept(mListenSocket, nullptr, nullptr)) != -1, rval = -1);
111 
112     VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1);
113 
114     rval |= FD_CLOEXEC;
115 
116     VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1);
117 
118 #ifndef __linux__
119     // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL
120     // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE
121     // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going
122     // to simply ignore it.
123 #if defined(SO_NOSIGPIPE)
124     rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval));
125     VerifyOrExit(rval != -1);
126 #else
127 #warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE"
128 #endif
129 #endif // __linux__
130 
131     if (mSessionSocket != -1)
132     {
133         close(mSessionSocket);
134     }
135     mSessionSocket = newSessionSocket;
136 
137 exit:
138     if (rval == -1)
139     {
140         otLogWarnPlat("Failed to initialize session socket: %s", strerror(errno));
141         if (newSessionSocket != -1)
142         {
143             close(newSessionSocket);
144         }
145     }
146     else
147     {
148         otLogInfoPlat("Session socket is ready", strerror(errno));
149     }
150 }
151 
SetUp(void)152 void Daemon::SetUp(void)
153 {
154     struct sockaddr_un sockname;
155     int                ret;
156 
157     class AllowAllGuard
158     {
159     public:
160         AllowAllGuard(void)
161         {
162             const char *allowAll = getenv("OT_DAEMON_ALLOW_ALL");
163             mAllowAll            = (allowAll != nullptr && strcmp("1", allowAll) == 0);
164 
165             if (mAllowAll)
166             {
167                 mMode = umask(0);
168             }
169         }
170         ~AllowAllGuard(void)
171         {
172             if (mAllowAll)
173             {
174                 umask(mMode);
175             }
176         }
177 
178     private:
179         bool   mAllowAll = false;
180         mode_t mMode     = 0;
181     };
182 
183     // This allows implementing pseudo reset.
184     VerifyOrExit(mListenSocket == -1);
185 
186     mListenSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock);
187 
188     if (mListenSocket == -1)
189     {
190         DieNow(OT_EXIT_FAILURE);
191     }
192 
193     {
194         static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK) == sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME),
195                       "sock and lock file name pattern should have the same length!");
196         Filename lockfile;
197 
198         GetFilename(lockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK);
199 
200         mDaemonLock = open(lockfile, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);
201     }
202 
203     if (mDaemonLock == -1)
204     {
205         DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO);
206     }
207 
208     if (flock(mDaemonLock, LOCK_EX | LOCK_NB) == -1)
209     {
210         DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO);
211     }
212 
213     memset(&sockname, 0, sizeof(struct sockaddr_un));
214 
215     sockname.sun_family = AF_UNIX;
216     GetFilename(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
217     (void)unlink(sockname.sun_path);
218 
219     {
220         AllowAllGuard allowAllGuard;
221 
222         ret = bind(mListenSocket, reinterpret_cast<const struct sockaddr *>(&sockname), sizeof(struct sockaddr_un));
223     }
224 
225     if (ret == -1)
226     {
227         DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO);
228     }
229 
230     //
231     // only accept 1 connection.
232     //
233     ret = listen(mListenSocket, 1);
234     if (ret == -1)
235     {
236         DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO);
237     }
238 
239     otCliInit(
240         gInstance,
241         [](void *aContext, const char *aFormat, va_list aArguments) -> int {
242             return static_cast<Daemon *>(aContext)->OutputFormatV(aFormat, aArguments);
243         },
244         this);
245 
246     Mainloop::Manager::Get().Add(*this);
247 
248 exit:
249     return;
250 }
251 
TearDown(void)252 void Daemon::TearDown(void)
253 {
254     Mainloop::Manager::Get().Remove(*this);
255 
256     if (mSessionSocket != -1)
257     {
258         close(mSessionSocket);
259         mSessionSocket = -1;
260     }
261 
262     if (mListenSocket != -1)
263     {
264         close(mListenSocket);
265         mListenSocket = -1;
266     }
267 
268     if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE)
269     {
270         Filename sockfile;
271 
272         GetFilename(sockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
273         otLogDebgPlat("Removing daemon socket: %s", sockfile);
274         (void)unlink(sockfile);
275     }
276 
277     if (mDaemonLock != -1)
278     {
279         (void)flock(mDaemonLock, LOCK_UN);
280         close(mDaemonLock);
281         mDaemonLock = -1;
282     }
283 }
284 
Update(otSysMainloopContext & aContext)285 void Daemon::Update(otSysMainloopContext &aContext)
286 {
287     if (mListenSocket != -1)
288     {
289         FD_SET(mListenSocket, &aContext.mReadFdSet);
290         FD_SET(mListenSocket, &aContext.mErrorFdSet);
291 
292         if (aContext.mMaxFd < mListenSocket)
293         {
294             aContext.mMaxFd = mListenSocket;
295         }
296     }
297 
298     if (mSessionSocket != -1)
299     {
300         FD_SET(mSessionSocket, &aContext.mReadFdSet);
301         FD_SET(mSessionSocket, &aContext.mErrorFdSet);
302 
303         if (aContext.mMaxFd < mSessionSocket)
304         {
305             aContext.mMaxFd = mSessionSocket;
306         }
307     }
308 
309     return;
310 }
311 
Process(const otSysMainloopContext & aContext)312 void Daemon::Process(const otSysMainloopContext &aContext)
313 {
314     ssize_t rval;
315 
316     VerifyOrExit(mListenSocket != -1);
317 
318     if (FD_ISSET(mListenSocket, &aContext.mErrorFdSet))
319     {
320         DieNowWithMessage("daemon socket error", OT_EXIT_FAILURE);
321     }
322     else if (FD_ISSET(mListenSocket, &aContext.mReadFdSet))
323     {
324         InitializeSessionSocket();
325     }
326 
327     VerifyOrExit(mSessionSocket != -1);
328 
329     if (FD_ISSET(mSessionSocket, &aContext.mErrorFdSet))
330     {
331         close(mSessionSocket);
332         mSessionSocket = -1;
333     }
334     else if (FD_ISSET(mSessionSocket, &aContext.mReadFdSet))
335     {
336         uint8_t buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
337 
338         // leave 1 byte for the null terminator
339         rval = read(mSessionSocket, buffer, sizeof(buffer) - 1);
340 
341         if (rval > 0)
342         {
343             buffer[rval] = '\0';
344             otLogInfoPlat("> %s", reinterpret_cast<const char *>(buffer));
345             otCliInputLine(reinterpret_cast<char *>(buffer));
346         }
347         else
348         {
349             if (rval < 0)
350             {
351                 otLogWarnPlat("Daemon read: %s", strerror(errno));
352             }
353             close(mSessionSocket);
354             mSessionSocket = -1;
355         }
356     }
357 
358 exit:
359     return;
360 }
361 
Get(void)362 Daemon &Daemon::Get(void)
363 {
364     static Daemon sInstance;
365 
366     return sInstance;
367 }
368 
369 } // namespace Posix
370 } // namespace ot
371 #endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
372