1 /*
2 * Copyright (c) 2020, 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 #define OTBR_LOG_TAG "WEB"
30
31 #include "web/web-service/ot_client.hpp"
32
33 #include <openthread/platform/toolchain.h>
34
35 #include <errno.h>
36 #include <inttypes.h>
37 #include <signal.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/select.h>
43 #include <sys/socket.h>
44 #include <sys/un.h>
45 #include <unistd.h>
46
47 #include "common/code_utils.hpp"
48 #include "common/logging.hpp"
49
50 // Temporary solution before posix platform header files are cleaned up.
51 #ifndef OPENTHREAD_POSIX_DAEMON_SOCKET_NAME
52 #ifdef __linux__
53 #define OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME "/run/openthread-%s"
54 #else
55 #define OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME "/tmp/openthread-%s"
56 #endif
57 #define OPENTHREAD_POSIX_DAEMON_SOCKET_NAME OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".sock"
58 #endif
59
60 namespace otbr {
61 namespace Web {
62
OpenThreadClient(const char * aNetifName)63 OpenThreadClient::OpenThreadClient(const char *aNetifName)
64
65 : mNetifName(aNetifName)
66 , mTimeout(kDefaultTimeout)
67 , mSocket(-1)
68 {
69 }
70
~OpenThreadClient(void)71 OpenThreadClient::~OpenThreadClient(void)
72 {
73 Disconnect();
74 }
75
Disconnect(void)76 void OpenThreadClient::Disconnect(void)
77 {
78 if (mSocket != -1)
79 {
80 close(mSocket);
81 mSocket = -1;
82 }
83 }
84
Connect(void)85 bool OpenThreadClient::Connect(void)
86 {
87 struct sockaddr_un sockname;
88 int ret;
89
90 mSocket = socket(AF_UNIX, SOCK_STREAM, 0);
91 VerifyOrExit(mSocket != -1, perror("socket"); ret = EXIT_FAILURE);
92
93 memset(&sockname, 0, sizeof(struct sockaddr_un));
94 sockname.sun_family = AF_UNIX;
95 ret = snprintf(sockname.sun_path, sizeof(sockname.sun_path), OPENTHREAD_POSIX_DAEMON_SOCKET_NAME, mNetifName);
96
97 VerifyOrExit(ret >= 0 && static_cast<size_t>(ret) < sizeof(sockname.sun_path), {
98 errno = EINVAL;
99 ret = -1;
100 });
101
102 ret = connect(mSocket, reinterpret_cast<const struct sockaddr *>(&sockname), sizeof(struct sockaddr_un));
103
104 if (ret == -1)
105 {
106 otbrLogErr("OpenThread daemon is not running.");
107 }
108
109 exit:
110 return ret == 0;
111 }
112
DiscardRead(void)113 void OpenThreadClient::DiscardRead(void)
114 {
115 fd_set readFdSet;
116 timeval timeout = {0, 0};
117 ssize_t count;
118 int ret;
119
120 for (;;)
121 {
122 FD_ZERO(&readFdSet);
123 FD_SET(mSocket, &readFdSet);
124
125 ret = select(mSocket + 1, &readFdSet, nullptr, nullptr, &timeout);
126 if (ret <= 0)
127 {
128 break;
129 }
130
131 count = read(mSocket, mBuffer, sizeof(mBuffer));
132 if (count <= 0)
133 {
134 break;
135 }
136 }
137 }
138
Execute(const char * aFormat,...)139 char *OpenThreadClient::Execute(const char *aFormat, ...)
140 {
141 va_list args;
142 int ret;
143 char *rval = nullptr;
144 ssize_t count;
145 size_t rxLength = 0;
146
147 DiscardRead();
148
149 va_start(args, aFormat);
150 ret = vsnprintf(&mBuffer[1], sizeof(mBuffer) - 2, aFormat, args);
151 va_end(args);
152
153 if (ret < 0)
154 {
155 otbrLogErr("Failed to generate command: %s", strerror(errno));
156 ExitNow();
157 }
158 if (static_cast<size_t>(ret) >= sizeof(mBuffer) - 2)
159 {
160 otbrLogErr("Command exceeds maximum limit: %d", kBufferSize);
161 ExitNow();
162 }
163
164 mBuffer[0] = '\n';
165 mBuffer[ret + 1] = '\n';
166 ret += 2;
167
168 count = write(mSocket, mBuffer, ret);
169
170 if (count != ret)
171 {
172 mBuffer[ret] = '\0';
173 otbrLogErr("Failed to send command: %s", mBuffer);
174 ExitNow();
175 }
176
177 for (int i = 0; i < mTimeout; ++i)
178 {
179 fd_set readFdSet;
180 timeval timeout = {0, 1000};
181 char *done;
182
183 FD_ZERO(&readFdSet);
184 FD_SET(mSocket, &readFdSet);
185
186 ret = select(mSocket + 1, &readFdSet, nullptr, nullptr, &timeout);
187 VerifyOrExit(ret != -1 || errno == EINTR);
188 if (ret <= 0)
189 {
190 continue;
191 }
192
193 count = read(mSocket, &mBuffer[rxLength], sizeof(mBuffer) - rxLength);
194 VerifyOrExit(count > 0);
195 rxLength += count;
196
197 mBuffer[rxLength] = '\0';
198 done = strstr(mBuffer, "Done\r\n> ");
199
200 if (done != nullptr)
201 {
202 // remove trailing \r\n
203 if (done - mBuffer > 2)
204 {
205 done[-2] = '\0';
206 }
207
208 rval = mBuffer;
209 break;
210 }
211 }
212
213 exit:
214 return rval;
215 }
216
Read(const char * aResponse,int aTimeout)217 char *OpenThreadClient::Read(const char *aResponse, int aTimeout)
218 {
219 ssize_t count = 0;
220 size_t rxLength = 0;
221 char *found;
222 char *rval = nullptr;
223
224 for (int i = 0; i < aTimeout; ++i)
225 {
226 count = read(mSocket, &mBuffer[rxLength], sizeof(mBuffer) - rxLength);
227 VerifyOrExit(count > 0);
228 rxLength += count;
229
230 mBuffer[rxLength] = '\0';
231 found = strstr(mBuffer, aResponse);
232
233 if (found != nullptr)
234 {
235 rval = mBuffer;
236 break;
237 }
238 }
239
240 exit:
241 return rval;
242 }
243
Scan(WpanNetworkInfo * aNetworks,int aLength)244 int OpenThreadClient::Scan(WpanNetworkInfo *aNetworks, int aLength)
245 {
246 char *result;
247 int rval = 0;
248
249 mTimeout = 5000;
250 result = Execute("scan");
251 VerifyOrExit(result != nullptr);
252
253 for (result = strtok(result, "\r\n"); result != nullptr && rval < aLength; result = strtok(nullptr, "\r\n"))
254 {
255 static const char kCliPrompt[] = "> ";
256 char *cliPrompt;
257 int matched;
258 int lqi;
259
260 // remove prompt
261 if ((cliPrompt = strstr(result, kCliPrompt)) != nullptr)
262 {
263 if (cliPrompt == result)
264 {
265 result += sizeof(kCliPrompt) - 1;
266 }
267 else
268 {
269 memmove(cliPrompt, cliPrompt + sizeof(kCliPrompt) - 1, strlen(cliPrompt) - sizeof(kCliPrompt) - 1);
270 }
271 }
272
273 matched = sscanf(result, "| %hx | %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx | %hu | %hhd | %d |",
274 &aNetworks[rval].mPanId, &aNetworks[rval].mHardwareAddress[0],
275 &aNetworks[rval].mHardwareAddress[1], &aNetworks[rval].mHardwareAddress[2],
276 &aNetworks[rval].mHardwareAddress[3], &aNetworks[rval].mHardwareAddress[4],
277 &aNetworks[rval].mHardwareAddress[5], &aNetworks[rval].mHardwareAddress[6],
278 &aNetworks[rval].mHardwareAddress[7], &aNetworks[rval].mChannel, &aNetworks[rval].mRssi, &lqi);
279
280 // 15 is the number of output arguments of the last sscanf()
281 if (matched != 12)
282 {
283 continue;
284 }
285
286 ++rval;
287 }
288
289 mTimeout = kDefaultTimeout;
290
291 exit:
292 return rval;
293 }
294
FactoryReset(void)295 bool OpenThreadClient::FactoryReset(void)
296 {
297 const char *result;
298 bool rval = false;
299 #if __APPLE__
300 typedef sig_t sighandler_t;
301 #endif
302 sighandler_t handler;
303
304 // Ignore the expected SIGPIPE signal during daemon reset.
305 handler = signal(SIGPIPE, SIG_IGN);
306 Execute("factoryreset");
307 signal(SIGPIPE, handler);
308 Disconnect();
309 sleep(4);
310 VerifyOrExit(rval = Connect());
311
312 result = Execute("version");
313 VerifyOrExit(result != nullptr);
314
315 rval = strstr(result, "OPENTHREAD") != nullptr;
316
317 exit:
318 return rval;
319 }
320
321 } // namespace Web
322 } // namespace otbr
323