|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "Netd.h" |
|
6 #include <android/log.h> |
|
7 #include <cutils/sockets.h> |
|
8 #include <fcntl.h> |
|
9 #include <sys/socket.h> |
|
10 |
|
11 #include "cutils/properties.h" |
|
12 #include "android/log.h" |
|
13 |
|
14 #include "nsWhitespaceTokenizer.h" |
|
15 #include "nsXULAppAPI.h" |
|
16 #include "nsAutoPtr.h" |
|
17 #include "nsString.h" |
|
18 #include "nsThreadUtils.h" |
|
19 #include "mozilla/RefPtr.h" |
|
20 |
|
21 |
|
22 #define CHROMIUM_LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk", args) |
|
23 #define ICS_SYS_USB_RNDIS_MAC "/sys/class/android_usb/android0/f_rndis/ethaddr" |
|
24 #define INVALID_SOCKET -1 |
|
25 #define MAX_RECONNECT_TIMES 10 |
|
26 |
|
27 namespace { |
|
28 |
|
29 mozilla::RefPtr<mozilla::ipc::NetdClient> gNetdClient; |
|
30 mozilla::RefPtr<mozilla::ipc::NetdConsumer> gNetdConsumer; |
|
31 |
|
32 class StopNetdConsumer : public nsRunnable { |
|
33 public: |
|
34 NS_IMETHOD Run() |
|
35 { |
|
36 MOZ_ASSERT(NS_IsMainThread()); |
|
37 |
|
38 gNetdConsumer = nullptr; |
|
39 return NS_OK; |
|
40 } |
|
41 }; |
|
42 |
|
43 bool |
|
44 InitRndisAddress() |
|
45 { |
|
46 char mac[20]; |
|
47 char serialno[] = "1234567890ABCDEF"; |
|
48 static const int kEthernetAddressLength = 6; |
|
49 char address[kEthernetAddressLength]; |
|
50 int i = 0; |
|
51 int ret = 0; |
|
52 int length = 0; |
|
53 mozilla::ScopedClose fd; |
|
54 |
|
55 fd.rwget() = open(ICS_SYS_USB_RNDIS_MAC, O_WRONLY); |
|
56 if (fd.rwget() == -1) { |
|
57 CHROMIUM_LOG("Unable to open file %s.", ICS_SYS_USB_RNDIS_MAC); |
|
58 return false; |
|
59 } |
|
60 |
|
61 property_get("ro.serialno", serialno, "1234567890ABCDEF"); |
|
62 |
|
63 memset(address, 0, sizeof(address)); |
|
64 // First byte is 0x02 to signify a locally administered address. |
|
65 address[0] = 0x02; |
|
66 length = strlen(serialno); |
|
67 for (i = 0; i < length; i++) { |
|
68 address[i % (kEthernetAddressLength - 1) + 1] ^= serialno[i]; |
|
69 } |
|
70 |
|
71 sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", |
|
72 address[0], address[1], address[2], |
|
73 address[3], address[4], address[5]); |
|
74 length = strlen(mac); |
|
75 ret = write(fd.get(), mac, length); |
|
76 if (ret != length) { |
|
77 CHROMIUM_LOG("Fail to write file %s.", ICS_SYS_USB_RNDIS_MAC); |
|
78 return false; |
|
79 } |
|
80 return true; |
|
81 } |
|
82 |
|
83 } // anonymous namespace |
|
84 |
|
85 namespace mozilla { |
|
86 namespace ipc { |
|
87 |
|
88 NetdClient::NetdClient() |
|
89 : LineWatcher('\0', MAX_COMMAND_SIZE) |
|
90 , mIOLoop(MessageLoopForIO::current()) |
|
91 , mSocket(INVALID_SOCKET) |
|
92 , mCurrentWriteOffset(0) |
|
93 , mReConnectTimes(0) |
|
94 { |
|
95 MOZ_COUNT_CTOR(NetdClient); |
|
96 } |
|
97 |
|
98 NetdClient::~NetdClient() |
|
99 { |
|
100 MOZ_COUNT_DTOR(NetdClient); |
|
101 } |
|
102 |
|
103 bool |
|
104 NetdClient::OpenSocket() |
|
105 { |
|
106 mSocket.rwget() = socket_local_client("netd", |
|
107 ANDROID_SOCKET_NAMESPACE_RESERVED, |
|
108 SOCK_STREAM); |
|
109 if (mSocket.rwget() < 0) { |
|
110 CHROMIUM_LOG("Error connecting to : netd (%s) - will retry", strerror(errno)); |
|
111 return false; |
|
112 } |
|
113 // Add FD_CLOEXEC flag. |
|
114 int flags = fcntl(mSocket.get(), F_GETFD); |
|
115 if (flags == -1) { |
|
116 CHROMIUM_LOG("Error doing fcntl with F_GETFD command(%s)", strerror(errno)); |
|
117 return false; |
|
118 } |
|
119 flags |= FD_CLOEXEC; |
|
120 if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { |
|
121 CHROMIUM_LOG("Error doing fcntl with F_SETFD command(%s)", strerror(errno)); |
|
122 return false; |
|
123 } |
|
124 // Set non-blocking. |
|
125 if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { |
|
126 CHROMIUM_LOG("Error set non-blocking socket(%s)", strerror(errno)); |
|
127 return false; |
|
128 } |
|
129 if (!MessageLoopForIO::current()-> |
|
130 WatchFileDescriptor(mSocket.get(), |
|
131 true, |
|
132 MessageLoopForIO::WATCH_READ, |
|
133 &mReadWatcher, |
|
134 this)) { |
|
135 CHROMIUM_LOG("Error set socket read watcher(%s)", strerror(errno)); |
|
136 return false; |
|
137 } |
|
138 |
|
139 if (!mOutgoingQ.empty()) { |
|
140 MessageLoopForIO::current()-> |
|
141 WatchFileDescriptor(mSocket.get(), |
|
142 false, |
|
143 MessageLoopForIO::WATCH_WRITE, |
|
144 &mWriteWatcher, |
|
145 this); |
|
146 } |
|
147 |
|
148 CHROMIUM_LOG("Connected to netd"); |
|
149 return true; |
|
150 } |
|
151 |
|
152 void NetdClient::OnLineRead(int aFd, nsDependentCSubstring& aMessage) |
|
153 { |
|
154 // Set errno to 0 first. For preventing to use the stale version of errno. |
|
155 errno = 0; |
|
156 // We found a line terminator. Each line is formatted as an |
|
157 // integer response code followed by the rest of the line. |
|
158 // Fish out the response code. |
|
159 int responseCode = strtol(aMessage.Data(), nullptr, 10); |
|
160 if (!errno) { |
|
161 NetdCommand* response = new NetdCommand(); |
|
162 // Passing all the response message, including the line terminator. |
|
163 response->mSize = aMessage.Length(); |
|
164 memcpy(response->mData, aMessage.Data(), aMessage.Length()); |
|
165 gNetdConsumer->MessageReceived(response); |
|
166 } |
|
167 |
|
168 if (!responseCode) { |
|
169 CHROMIUM_LOG("Can't parse netd's response"); |
|
170 } |
|
171 } |
|
172 |
|
173 void |
|
174 NetdClient::OnFileCanWriteWithoutBlocking(int aFd) |
|
175 { |
|
176 MOZ_ASSERT(aFd == mSocket.get()); |
|
177 WriteNetdCommand(); |
|
178 } |
|
179 |
|
180 void |
|
181 NetdClient::OnError() |
|
182 { |
|
183 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
184 |
|
185 mReadWatcher.StopWatchingFileDescriptor(); |
|
186 mWriteWatcher.StopWatchingFileDescriptor(); |
|
187 |
|
188 mSocket.dispose(); |
|
189 mCurrentWriteOffset = 0; |
|
190 mCurrentNetdCommand = nullptr; |
|
191 while (!mOutgoingQ.empty()) { |
|
192 delete mOutgoingQ.front(); |
|
193 mOutgoingQ.pop(); |
|
194 } |
|
195 Start(); |
|
196 } |
|
197 |
|
198 // static |
|
199 void |
|
200 NetdClient::Start() |
|
201 { |
|
202 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
203 |
|
204 if (!gNetdClient) { |
|
205 CHROMIUM_LOG("Netd Client is not initialized"); |
|
206 return; |
|
207 } |
|
208 |
|
209 if (!gNetdClient->OpenSocket()) { |
|
210 // Socket open failed, try again in a second. |
|
211 CHROMIUM_LOG("Fail to connect to Netd"); |
|
212 if (++gNetdClient->mReConnectTimes > MAX_RECONNECT_TIMES) { |
|
213 CHROMIUM_LOG("Fail to connect to Netd after retry %d times", MAX_RECONNECT_TIMES); |
|
214 return; |
|
215 } |
|
216 |
|
217 MessageLoopForIO::current()-> |
|
218 PostDelayedTask(FROM_HERE, |
|
219 NewRunnableFunction(NetdClient::Start), |
|
220 1000); |
|
221 return; |
|
222 } |
|
223 gNetdClient->mReConnectTimes = 0; |
|
224 } |
|
225 |
|
226 // static |
|
227 void |
|
228 NetdClient::SendNetdCommandIOThread(NetdCommand* aMessage) |
|
229 { |
|
230 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
231 MOZ_ASSERT(aMessage); |
|
232 |
|
233 if (!gNetdClient) { |
|
234 CHROMIUM_LOG("Netd Client is not initialized"); |
|
235 return; |
|
236 } |
|
237 |
|
238 gNetdClient->mOutgoingQ.push(aMessage); |
|
239 |
|
240 if (gNetdClient->mSocket.get() == INVALID_SOCKET) { |
|
241 CHROMIUM_LOG("Netd connection is not established, push the message to queue"); |
|
242 return; |
|
243 } |
|
244 |
|
245 gNetdClient->WriteNetdCommand(); |
|
246 } |
|
247 |
|
248 void |
|
249 NetdClient::WriteNetdCommand() |
|
250 { |
|
251 if (!mCurrentNetdCommand) { |
|
252 mCurrentWriteOffset = 0; |
|
253 mCurrentNetdCommand = mOutgoingQ.front(); |
|
254 mOutgoingQ.pop(); |
|
255 } |
|
256 |
|
257 while (mCurrentWriteOffset < mCurrentNetdCommand->mSize) { |
|
258 ssize_t write_amount = mCurrentNetdCommand->mSize - mCurrentWriteOffset; |
|
259 ssize_t written = write(mSocket.get(), |
|
260 mCurrentNetdCommand->mData + mCurrentWriteOffset, |
|
261 write_amount); |
|
262 if (written < 0) { |
|
263 CHROMIUM_LOG("Cannot write to network, error %d\n", (int) written); |
|
264 OnError(); |
|
265 return; |
|
266 } |
|
267 |
|
268 if (written > 0) { |
|
269 mCurrentWriteOffset += written; |
|
270 } |
|
271 |
|
272 if (written != write_amount) { |
|
273 CHROMIUM_LOG("WriteNetdCommand fail !!! Write is not completed"); |
|
274 break; |
|
275 } |
|
276 } |
|
277 |
|
278 if (mCurrentWriteOffset != mCurrentNetdCommand->mSize) { |
|
279 MessageLoopForIO::current()-> |
|
280 WatchFileDescriptor(mSocket.get(), |
|
281 false, |
|
282 MessageLoopForIO::WATCH_WRITE, |
|
283 &mWriteWatcher, |
|
284 this); |
|
285 return; |
|
286 } |
|
287 |
|
288 mCurrentNetdCommand = nullptr; |
|
289 } |
|
290 |
|
291 static void |
|
292 InitNetdIOThread() |
|
293 { |
|
294 bool result; |
|
295 char propValue[PROPERTY_VALUE_MAX]; |
|
296 |
|
297 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
298 MOZ_ASSERT(!gNetdClient); |
|
299 |
|
300 property_get("ro.build.version.sdk", propValue, "0"); |
|
301 // Assign rndis address for usb tethering in ICS. |
|
302 if (atoi(propValue) >= 15) { |
|
303 result = InitRndisAddress(); |
|
304 // We don't return here because InitRnsisAddress() function is related to |
|
305 // usb tethering only. Others service such as wifi tethering still need |
|
306 // to use ipc to communicate with netd. |
|
307 if (!result) { |
|
308 CHROMIUM_LOG("fail to give rndis interface an address"); |
|
309 } |
|
310 } |
|
311 gNetdClient = new NetdClient(); |
|
312 gNetdClient->Start(); |
|
313 } |
|
314 |
|
315 static void |
|
316 ShutdownNetdIOThread() |
|
317 { |
|
318 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
319 nsCOMPtr<nsIRunnable> shutdownEvent = new StopNetdConsumer(); |
|
320 |
|
321 gNetdClient = nullptr; |
|
322 |
|
323 NS_DispatchToMainThread(shutdownEvent); |
|
324 } |
|
325 |
|
326 void |
|
327 StartNetd(NetdConsumer* aNetdConsumer) |
|
328 { |
|
329 MOZ_ASSERT(NS_IsMainThread()); |
|
330 MOZ_ASSERT(aNetdConsumer); |
|
331 MOZ_ASSERT(gNetdConsumer == nullptr); |
|
332 |
|
333 gNetdConsumer = aNetdConsumer; |
|
334 XRE_GetIOMessageLoop()->PostTask( |
|
335 FROM_HERE, |
|
336 NewRunnableFunction(InitNetdIOThread)); |
|
337 } |
|
338 |
|
339 void |
|
340 StopNetd() |
|
341 { |
|
342 MOZ_ASSERT(NS_IsMainThread()); |
|
343 |
|
344 nsIThread* currentThread = NS_GetCurrentThread(); |
|
345 NS_ASSERTION(currentThread, "This should never be null!"); |
|
346 |
|
347 XRE_GetIOMessageLoop()->PostTask( |
|
348 FROM_HERE, |
|
349 NewRunnableFunction(ShutdownNetdIOThread)); |
|
350 |
|
351 while (gNetdConsumer) { |
|
352 if (!NS_ProcessNextEvent(currentThread)) { |
|
353 NS_WARNING("Something bad happened!"); |
|
354 break; |
|
355 } |
|
356 } |
|
357 } |
|
358 |
|
359 /************************************************************************** |
|
360 * |
|
361 * This function runs in net worker Thread context. The net worker thread |
|
362 * is created in dom/system/gonk/NetworkManager.js |
|
363 * |
|
364 **************************************************************************/ |
|
365 void |
|
366 SendNetdCommand(NetdCommand* aMessage) |
|
367 { |
|
368 MOZ_ASSERT(aMessage); |
|
369 |
|
370 XRE_GetIOMessageLoop()->PostTask( |
|
371 FROM_HERE, |
|
372 NewRunnableFunction(NetdClient::SendNetdCommandIOThread, aMessage)); |
|
373 } |
|
374 |
|
375 } // namespace ipc |
|
376 } // namespace mozilla |