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 #if OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
32 #include <cutils/sockets.h>
33 #endif
34 #include <fcntl.h>
35 #include <signal.h>
36 #include <stdarg.h>
37 #include <string.h>
38 #include <sys/file.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/un.h>
43 #include <unistd.h>
44
45 #include <openthread/cli.h>
46
47 #include "cli/cli_config.h"
48 #include "common/code_utils.hpp"
49 #include "posix/platform/platform-posix.h"
50
51 #if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
52
53 #define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock"
54 static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockaddr_un::sun_path),
55 "OpenThread daemon socket name too long!");
56
57 namespace ot {
58 namespace Posix {
59
60 namespace {
61
62 typedef char(Filename)[sizeof(sockaddr_un::sun_path)];
63
GetFilename(Filename & aFilename,const char * aPattern)64 void GetFilename(Filename &aFilename, const char *aPattern)
65 {
66 int rval;
67 const char *netIfName = strlen(gNetifName) > 0 ? gNetifName : OPENTHREAD_POSIX_CONFIG_THREAD_NETIF_DEFAULT_NAME;
68
69 rval = snprintf(aFilename, sizeof(aFilename), aPattern, netIfName);
70 if (rval < 0 && static_cast<size_t>(rval) >= sizeof(aFilename))
71 {
72 DieNow(OT_EXIT_INVALID_ARGUMENTS);
73 }
74 }
75
76 } // namespace
77
78 const char Daemon::kLogModuleName[] = "Daemon";
79
OutputFormat(const char * aFormat,...)80 int Daemon::OutputFormat(const char *aFormat, ...)
81 {
82 int ret;
83 va_list ap;
84
85 va_start(ap, aFormat);
86 ret = OutputFormatV(aFormat, ap);
87 va_end(ap);
88
89 return ret;
90 }
91
OutputFormatV(const char * aFormat,va_list aArguments)92 int Daemon::OutputFormatV(const char *aFormat, va_list aArguments)
93 {
94 static constexpr char truncatedMsg[] = "(truncated ...)";
95 char buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
96 int rval;
97
98 static_assert(sizeof(truncatedMsg) < OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
99 "OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH is too short!");
100
101 rval = vsnprintf(buf, sizeof(buf), aFormat, aArguments);
102 VerifyOrExit(rval >= 0, LogWarn("Failed to format CLI output: %s", strerror(errno)));
103
104 if (rval >= static_cast<int>(sizeof(buf)))
105 {
106 rval = static_cast<int>(sizeof(buf) - 1);
107 memcpy(buf + sizeof(buf) - sizeof(truncatedMsg), truncatedMsg, sizeof(truncatedMsg));
108 }
109
110 VerifyOrExit(mSessionSocket != -1);
111
112 #ifdef __linux__
113 // Don't die on SIGPIPE
114 rval = send(mSessionSocket, buf, static_cast<size_t>(rval), MSG_NOSIGNAL);
115 #else
116 rval = static_cast<int>(write(mSessionSocket, buf, static_cast<size_t>(rval)));
117 #endif
118
119 if (rval < 0)
120 {
121 LogWarn("Failed to write CLI output: %s", strerror(errno));
122 close(mSessionSocket);
123 mSessionSocket = -1;
124 }
125
126 exit:
127 return rval;
128 }
129
InitializeSessionSocket(void)130 void Daemon::InitializeSessionSocket(void)
131 {
132 int newSessionSocket;
133 int rval;
134
135 VerifyOrExit((newSessionSocket = accept(mListenSocket, nullptr, nullptr)) != -1, rval = -1);
136
137 VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1);
138
139 rval |= FD_CLOEXEC;
140
141 VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1);
142
143 #ifndef __linux__
144 // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL
145 // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE
146 // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going
147 // to simply ignore it.
148 #if defined(SO_NOSIGPIPE)
149 rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval));
150 VerifyOrExit(rval != -1);
151 #else
152 #warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE"
153 #endif
154 #endif // __linux__
155
156 if (mSessionSocket != -1)
157 {
158 close(mSessionSocket);
159 }
160 mSessionSocket = newSessionSocket;
161
162 exit:
163 if (rval == -1)
164 {
165 LogWarn("Failed to initialize session socket: %s", strerror(errno));
166 if (newSessionSocket != -1)
167 {
168 close(newSessionSocket);
169 }
170 }
171 else
172 {
173 LogInfo("Session socket is ready");
174 }
175 }
176
177 #if OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
createListenSocketOrDie(void)178 void Daemon::createListenSocketOrDie(void)
179 {
180 Filename socketFile;
181
182 // Don't use OPENTHREAD_POSIX_DAEMON_SOCKET_NAME because android_get_control_socket
183 // below already assumes parent /dev/socket dir
184 GetFilename(socketFile, "ot-daemon/%s.sock");
185
186 // This returns the init-managed stream socket which is already bind to
187 // /dev/socket/ot-daemon/<interface-name>.sock
188 mListenSocket = android_get_control_socket(socketFile);
189
190 if (mListenSocket == -1)
191 {
192 DieNowWithMessage("android_get_control_socket", OT_EXIT_ERROR_ERRNO);
193 }
194 }
195 #else
createListenSocketOrDie(void)196 void Daemon::createListenSocketOrDie(void)
197 {
198 struct sockaddr_un sockname;
199 int ret;
200
201 class AllowAllGuard
202 {
203 public:
204 AllowAllGuard(void)
205 {
206 const char *allowAll = getenv("OT_DAEMON_ALLOW_ALL");
207 mAllowAll = (allowAll != nullptr && strcmp("1", allowAll) == 0);
208
209 if (mAllowAll)
210 {
211 mMode = umask(0);
212 }
213 }
214 ~AllowAllGuard(void)
215 {
216 if (mAllowAll)
217 {
218 umask(mMode);
219 }
220 }
221
222 private:
223 bool mAllowAll = false;
224 mode_t mMode = 0;
225 };
226
227 mListenSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock);
228
229 if (mListenSocket == -1)
230 {
231 DieNow(OT_EXIT_FAILURE);
232 }
233
234 {
235 static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK) == sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME),
236 "sock and lock file name pattern should have the same length!");
237 Filename lockfile;
238
239 GetFilename(lockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK);
240
241 mDaemonLock = open(lockfile, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);
242 }
243
244 if (mDaemonLock == -1)
245 {
246 DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO);
247 }
248
249 if (flock(mDaemonLock, LOCK_EX | LOCK_NB) == -1)
250 {
251 DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO);
252 }
253
254 memset(&sockname, 0, sizeof(struct sockaddr_un));
255
256 sockname.sun_family = AF_UNIX;
257 GetFilename(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
258 (void)unlink(sockname.sun_path);
259
260 {
261 AllowAllGuard allowAllGuard;
262
263 ret = bind(mListenSocket, reinterpret_cast<const struct sockaddr *>(&sockname), sizeof(struct sockaddr_un));
264 }
265
266 if (ret == -1)
267 {
268 DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO);
269 }
270 }
271 #endif // OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
272
SetUp(void)273 void Daemon::SetUp(void)
274 {
275 int ret;
276
277 // This allows implementing pseudo reset.
278 VerifyOrExit(mListenSocket == -1);
279 createListenSocketOrDie();
280
281 //
282 // only accept 1 connection.
283 //
284 ret = listen(mListenSocket, 1);
285 if (ret == -1)
286 {
287 DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO);
288 }
289
290 exit:
291 #if OPENTHREAD_POSIX_CONFIG_DAEMON_CLI_ENABLE
292 otSysCliInitUsingDaemon(gInstance);
293 #endif
294
295 Mainloop::Manager::Get().Add(*this);
296
297 return;
298 }
299
TearDown(void)300 void Daemon::TearDown(void)
301 {
302 Mainloop::Manager::Get().Remove(*this);
303
304 if (mSessionSocket != -1)
305 {
306 close(mSessionSocket);
307 mSessionSocket = -1;
308 }
309
310 #if !OPENTHREAD_POSIX_CONFIG_ANDROID_ENABLE
311 // The `mListenSocket` is managed by `init` on Android
312 if (mListenSocket != -1)
313 {
314 close(mListenSocket);
315 mListenSocket = -1;
316 }
317
318 if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE)
319 {
320 Filename sockfile;
321
322 GetFilename(sockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
323 LogDebg("Removing daemon socket: %s", sockfile);
324 (void)unlink(sockfile);
325 }
326
327 if (mDaemonLock != -1)
328 {
329 (void)flock(mDaemonLock, LOCK_UN);
330 close(mDaemonLock);
331 mDaemonLock = -1;
332 }
333 #endif
334 }
335
Update(otSysMainloopContext & aContext)336 void Daemon::Update(otSysMainloopContext &aContext)
337 {
338 if (mListenSocket != -1)
339 {
340 FD_SET(mListenSocket, &aContext.mReadFdSet);
341 FD_SET(mListenSocket, &aContext.mErrorFdSet);
342
343 if (aContext.mMaxFd < mListenSocket)
344 {
345 aContext.mMaxFd = mListenSocket;
346 }
347 }
348
349 if (mSessionSocket != -1)
350 {
351 FD_SET(mSessionSocket, &aContext.mReadFdSet);
352 FD_SET(mSessionSocket, &aContext.mErrorFdSet);
353
354 if (aContext.mMaxFd < mSessionSocket)
355 {
356 aContext.mMaxFd = mSessionSocket;
357 }
358 }
359
360 return;
361 }
362
Process(const otSysMainloopContext & aContext)363 void Daemon::Process(const otSysMainloopContext &aContext)
364 {
365 ssize_t rval;
366
367 VerifyOrExit(mListenSocket != -1);
368
369 if (FD_ISSET(mListenSocket, &aContext.mErrorFdSet))
370 {
371 DieNowWithMessage("daemon socket error", OT_EXIT_FAILURE);
372 }
373 else if (FD_ISSET(mListenSocket, &aContext.mReadFdSet))
374 {
375 InitializeSessionSocket();
376 }
377
378 VerifyOrExit(mSessionSocket != -1);
379
380 if (FD_ISSET(mSessionSocket, &aContext.mErrorFdSet))
381 {
382 close(mSessionSocket);
383 mSessionSocket = -1;
384 }
385 else if (FD_ISSET(mSessionSocket, &aContext.mReadFdSet))
386 {
387 uint8_t buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
388
389 // leave 1 byte for the null terminator
390 rval = read(mSessionSocket, buffer, sizeof(buffer) - 1);
391
392 if (rval > 0)
393 {
394 buffer[rval] = '\0';
395 #if OPENTHREAD_POSIX_CONFIG_DAEMON_CLI_ENABLE
396 otCliInputLine(reinterpret_cast<char *>(buffer));
397 #else
398 OutputFormat("Error: CLI is disabled!\n");
399 #endif
400 }
401 else
402 {
403 if (rval < 0)
404 {
405 LogWarn("Daemon read: %s", strerror(errno));
406 }
407 close(mSessionSocket);
408 mSessionSocket = -1;
409 }
410 }
411
412 exit:
413 return;
414 }
415
Get(void)416 Daemon &Daemon::Get(void)
417 {
418 static Daemon sInstance;
419
420 return sInstance;
421 }
422
423 } // namespace Posix
424 } // namespace ot
425 #endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
426