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