|
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 "use strict"; |
|
6 |
|
7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
|
8 |
|
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
10 Cu.import("resource://gre/modules/Services.jsm"); |
|
11 Cu.import("resource://gre/modules/NetUtil.jsm"); |
|
12 Cu.import("resource://gre/modules/FileUtils.jsm"); |
|
13 |
|
14 const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1"; |
|
15 const NETWORKSERVICE_CID = Components.ID("{baec696c-c78d-42db-8b44-603f8fbfafb4}"); |
|
16 |
|
17 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker", |
|
18 "@mozilla.org/network/worker;1", |
|
19 "nsINetworkWorker"); |
|
20 |
|
21 // 1xx - Requested action is proceeding |
|
22 const NETD_COMMAND_PROCEEDING = 100; |
|
23 // 2xx - Requested action has been successfully completed |
|
24 const NETD_COMMAND_OKAY = 200; |
|
25 // 4xx - The command is accepted but the requested action didn't |
|
26 // take place. |
|
27 const NETD_COMMAND_FAIL = 400; |
|
28 // 5xx - The command syntax or parameters error |
|
29 const NETD_COMMAND_ERROR = 500; |
|
30 // 6xx - Unsolicited broadcasts |
|
31 const NETD_COMMAND_UNSOLICITED = 600; |
|
32 |
|
33 const WIFI_CTRL_INTERFACE = "wl0.1"; |
|
34 |
|
35 const MANUAL_PROXY_CONFIGURATION = 1; |
|
36 |
|
37 let DEBUG = false; |
|
38 |
|
39 // Read debug setting from pref. |
|
40 try { |
|
41 let debugPref = Services.prefs.getBoolPref("network.debugging.enabled"); |
|
42 DEBUG = DEBUG || debugPref; |
|
43 } catch (e) {} |
|
44 |
|
45 function netdResponseType(code) { |
|
46 return Math.floor(code / 100) * 100; |
|
47 } |
|
48 |
|
49 function isError(code) { |
|
50 let type = netdResponseType(code); |
|
51 return (type !== NETD_COMMAND_PROCEEDING && type !== NETD_COMMAND_OKAY); |
|
52 } |
|
53 |
|
54 function debug(msg) { |
|
55 dump("-*- NetworkService: " + msg + "\n"); |
|
56 } |
|
57 |
|
58 /** |
|
59 * This component watches for network interfaces changing state and then |
|
60 * adjusts routes etc. accordingly. |
|
61 */ |
|
62 function NetworkService() { |
|
63 if(DEBUG) debug("Starting net_worker."); |
|
64 |
|
65 let self = this; |
|
66 |
|
67 if (gNetworkWorker) { |
|
68 let networkListener = { |
|
69 onEvent: function(event) { |
|
70 self.handleWorkerMessage(event); |
|
71 } |
|
72 }; |
|
73 gNetworkWorker.start(networkListener); |
|
74 } |
|
75 // Callbacks to invoke when a reply arrives from the net_worker. |
|
76 this.controlCallbacks = Object.create(null); |
|
77 |
|
78 this.shutdown = false; |
|
79 Services.obs.addObserver(this, "xpcom-shutdown", false); |
|
80 } |
|
81 |
|
82 NetworkService.prototype = { |
|
83 classID: NETWORKSERVICE_CID, |
|
84 classInfo: XPCOMUtils.generateCI({classID: NETWORKSERVICE_CID, |
|
85 contractID: NETWORKSERVICE_CONTRACTID, |
|
86 classDescription: "Network Service", |
|
87 interfaces: [Ci.nsINetworkService]}), |
|
88 QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkService]), |
|
89 |
|
90 // Helpers |
|
91 |
|
92 idgen: 0, |
|
93 controlMessage: function(params, callback) { |
|
94 if (this.shutdown) { |
|
95 return; |
|
96 } |
|
97 |
|
98 if (callback) { |
|
99 let id = this.idgen++; |
|
100 params.id = id; |
|
101 this.controlCallbacks[id] = callback; |
|
102 } |
|
103 if (gNetworkWorker) { |
|
104 gNetworkWorker.postMessage(params); |
|
105 } |
|
106 }, |
|
107 |
|
108 handleWorkerMessage: function(response) { |
|
109 if(DEBUG) debug("NetworkManager received message from worker: " + JSON.stringify(response)); |
|
110 let id = response.id; |
|
111 if (response.broadcast === true) { |
|
112 Services.obs.notifyObservers(null, response.topic, response.reason); |
|
113 return; |
|
114 } |
|
115 let callback = this.controlCallbacks[id]; |
|
116 if (callback) { |
|
117 callback.call(this, response); |
|
118 delete this.controlCallbacks[id]; |
|
119 } |
|
120 }, |
|
121 |
|
122 // nsINetworkService |
|
123 |
|
124 getNetworkInterfaceStats: function(networkName, callback) { |
|
125 if(DEBUG) debug("getNetworkInterfaceStats for " + networkName); |
|
126 |
|
127 if (this.shutdown) { |
|
128 return; |
|
129 } |
|
130 |
|
131 let file = new FileUtils.File("/proc/net/dev"); |
|
132 if (!file) { |
|
133 callback.networkStatsAvailable(false, -1, -1, new Date()); |
|
134 return; |
|
135 } |
|
136 |
|
137 NetUtil.asyncFetch(file, function(inputStream, status) { |
|
138 let result = { |
|
139 success: true, // netd always return success even interface doesn't exist. |
|
140 rxBytes: 0, |
|
141 txBytes: 0 |
|
142 }; |
|
143 result.date = new Date(); |
|
144 |
|
145 if (Components.isSuccessCode(status)) { |
|
146 // Find record for corresponding interface. |
|
147 let statExpr = /(\S+): +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+/; |
|
148 let data = NetUtil.readInputStreamToString(inputStream, |
|
149 inputStream.available()).split("\n"); |
|
150 for (let i = 2; i < data.length; i++) { |
|
151 let parseResult = statExpr.exec(data[i]); |
|
152 if (parseResult && parseResult[1] === networkName) { |
|
153 result.rxBytes = parseInt(parseResult[2], 10); |
|
154 result.txBytes = parseInt(parseResult[3], 10); |
|
155 break; |
|
156 } |
|
157 } |
|
158 } |
|
159 |
|
160 callback.networkStatsAvailable(result.success, result.rxBytes, |
|
161 result.txBytes, result.date); |
|
162 }); |
|
163 }, |
|
164 |
|
165 setNetworkInterfaceAlarm: function(networkName, threshold, callback) { |
|
166 if (!networkName) { |
|
167 callback.networkUsageAlarmResult(-1); |
|
168 return; |
|
169 } |
|
170 |
|
171 let self = this; |
|
172 this._disableNetworkInterfaceAlarm(networkName, function(result) { |
|
173 if (threshold < 0) { |
|
174 if (!isError(result.resultCode)) { |
|
175 callback.networkUsageAlarmResult(null); |
|
176 return; |
|
177 } |
|
178 callback.networkUsageAlarmResult(result.reason); |
|
179 return |
|
180 } |
|
181 |
|
182 self._setNetworkInterfaceAlarm(networkName, threshold, callback); |
|
183 }); |
|
184 }, |
|
185 |
|
186 _setNetworkInterfaceAlarm: function(networkName, threshold, callback) { |
|
187 if(DEBUG) debug("setNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes"); |
|
188 |
|
189 let params = { |
|
190 cmd: "setNetworkInterfaceAlarm", |
|
191 ifname: networkName, |
|
192 threshold: threshold |
|
193 }; |
|
194 |
|
195 params.report = true; |
|
196 params.isAsync = true; |
|
197 |
|
198 this.controlMessage(params, function(result) { |
|
199 if (!isError(result.resultCode)) { |
|
200 callback.networkUsageAlarmResult(null); |
|
201 return; |
|
202 } |
|
203 |
|
204 this._enableNetworkInterfaceAlarm(networkName, threshold, callback); |
|
205 }); |
|
206 }, |
|
207 |
|
208 _enableNetworkInterfaceAlarm: function(networkName, threshold, callback) { |
|
209 if(DEBUG) debug("enableNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes"); |
|
210 |
|
211 let params = { |
|
212 cmd: "enableNetworkInterfaceAlarm", |
|
213 ifname: networkName, |
|
214 threshold: threshold |
|
215 }; |
|
216 |
|
217 params.report = true; |
|
218 params.isAsync = true; |
|
219 |
|
220 this.controlMessage(params, function(result) { |
|
221 if (!isError(result.resultCode)) { |
|
222 callback.networkUsageAlarmResult(null); |
|
223 return; |
|
224 } |
|
225 callback.networkUsageAlarmResult(result.reason); |
|
226 }); |
|
227 }, |
|
228 |
|
229 _disableNetworkInterfaceAlarm: function(networkName, callback) { |
|
230 if(DEBUG) debug("disableNetworkInterfaceAlarm for " + networkName); |
|
231 |
|
232 let params = { |
|
233 cmd: "disableNetworkInterfaceAlarm", |
|
234 ifname: networkName, |
|
235 }; |
|
236 |
|
237 params.report = true; |
|
238 params.isAsync = true; |
|
239 |
|
240 this.controlMessage(params, function(result) { |
|
241 callback(result); |
|
242 }); |
|
243 }, |
|
244 |
|
245 setWifiOperationMode: function(interfaceName, mode, callback) { |
|
246 if(DEBUG) debug("setWifiOperationMode on " + interfaceName + " to " + mode); |
|
247 |
|
248 let params = { |
|
249 cmd: "setWifiOperationMode", |
|
250 ifname: interfaceName, |
|
251 mode: mode |
|
252 }; |
|
253 |
|
254 params.report = true; |
|
255 params.isAsync = true; |
|
256 |
|
257 this.controlMessage(params, function(result) { |
|
258 if (isError(result.resultCode)) { |
|
259 callback.wifiOperationModeResult("netd command error"); |
|
260 } else { |
|
261 callback.wifiOperationModeResult(null); |
|
262 } |
|
263 }); |
|
264 }, |
|
265 |
|
266 resetRoutingTable: function(network) { |
|
267 let ips = {}; |
|
268 let prefixLengths = {}; |
|
269 let length = network.getAddresses(ips, prefixLengths); |
|
270 |
|
271 for (let i = 0; i < length; i++) { |
|
272 let ip = ips.value[i]; |
|
273 let prefixLength = prefixLengths.value[i]; |
|
274 |
|
275 let options = { |
|
276 cmd: "removeNetworkRoute", |
|
277 ifname: network.name, |
|
278 ip: ip, |
|
279 prefixLength: prefixLength |
|
280 }; |
|
281 this.controlMessage(options); |
|
282 } |
|
283 }, |
|
284 |
|
285 setDNS: function(networkInterface) { |
|
286 if(DEBUG) debug("Going DNS to " + networkInterface.name); |
|
287 let dnses = networkInterface.getDnses(); |
|
288 let options = { |
|
289 cmd: "setDNS", |
|
290 ifname: networkInterface.name, |
|
291 domain: "mozilla." + networkInterface.name + ".doman", |
|
292 dnses: dnses |
|
293 }; |
|
294 this.controlMessage(options); |
|
295 }, |
|
296 |
|
297 setDefaultRouteAndDNS: function(network, oldInterface) { |
|
298 if(DEBUG) debug("Going to change route and DNS to " + network.name); |
|
299 let gateways = network.getGateways(); |
|
300 let dnses = network.getDnses(); |
|
301 let options = { |
|
302 cmd: "setDefaultRouteAndDNS", |
|
303 ifname: network.name, |
|
304 oldIfname: (oldInterface && oldInterface !== network) ? oldInterface.name : null, |
|
305 gateways: gateways, |
|
306 domain: "mozilla." + network.name + ".doman", |
|
307 dnses: dnses |
|
308 }; |
|
309 this.controlMessage(options); |
|
310 this.setNetworkProxy(network); |
|
311 }, |
|
312 |
|
313 removeDefaultRoute: function(network) { |
|
314 if(DEBUG) debug("Remove default route for " + network.name); |
|
315 let gateways = network.getGateways(); |
|
316 let options = { |
|
317 cmd: "removeDefaultRoute", |
|
318 ifname: network.name, |
|
319 gateways: gateways |
|
320 }; |
|
321 this.controlMessage(options); |
|
322 }, |
|
323 |
|
324 addHostRoute: function(network) { |
|
325 if(DEBUG) debug("Going to add host route on " + network.name); |
|
326 let gateways = network.getGateways(); |
|
327 let dnses = network.getDnses(); |
|
328 let options = { |
|
329 cmd: "addHostRoute", |
|
330 ifname: network.name, |
|
331 gateways: gateways, |
|
332 hostnames: dnses.concat(network.httpProxyHost) |
|
333 }; |
|
334 this.controlMessage(options); |
|
335 }, |
|
336 |
|
337 removeHostRoute: function(network) { |
|
338 if(DEBUG) debug("Going to remove host route on " + network.name); |
|
339 let gateways = network.getGateways(); |
|
340 let dnses = network.getDnses(); |
|
341 let options = { |
|
342 cmd: "removeHostRoute", |
|
343 ifname: network.name, |
|
344 gateways: gateways, |
|
345 hostnames: dnses.concat(network.httpProxyHost) |
|
346 }; |
|
347 this.controlMessage(options); |
|
348 }, |
|
349 |
|
350 removeHostRoutes: function(ifname) { |
|
351 if(DEBUG) debug("Going to remove all host routes on " + ifname); |
|
352 let options = { |
|
353 cmd: "removeHostRoutes", |
|
354 ifname: ifname, |
|
355 }; |
|
356 this.controlMessage(options); |
|
357 }, |
|
358 |
|
359 addHostRouteWithResolve: function(network, hosts) { |
|
360 if(DEBUG) debug("Going to add host route after dns resolution on " + network.name); |
|
361 let gateways = network.getGateways(); |
|
362 let options = { |
|
363 cmd: "addHostRoute", |
|
364 ifname: network.name, |
|
365 gateways: gateways, |
|
366 hostnames: hosts |
|
367 }; |
|
368 this.controlMessage(options); |
|
369 }, |
|
370 |
|
371 removeHostRouteWithResolve: function(network, hosts) { |
|
372 if(DEBUG) debug("Going to remove host route after dns resolution on " + network.name); |
|
373 let gateways = network.getGateways(); |
|
374 let options = { |
|
375 cmd: "removeHostRoute", |
|
376 ifname: network.name, |
|
377 gateways: gateways, |
|
378 hostnames: hosts |
|
379 }; |
|
380 this.controlMessage(options); |
|
381 }, |
|
382 |
|
383 addSecondaryRoute: function(ifname, route) { |
|
384 if(DEBUG) debug("Going to add route to secondary table on " + ifname); |
|
385 let options = { |
|
386 cmd: "addSecondaryRoute", |
|
387 ifname: ifname, |
|
388 ip: route.ip, |
|
389 prefix: route.prefix, |
|
390 gateway: route.gateway |
|
391 }; |
|
392 this.controlMessage(options); |
|
393 }, |
|
394 |
|
395 removeSecondaryRoute: function(ifname, route) { |
|
396 if(DEBUG) debug("Going to remove route from secondary table on " + ifname); |
|
397 let options = { |
|
398 cmd: "removeSecondaryRoute", |
|
399 ifname: ifname, |
|
400 ip: route.ip, |
|
401 prefix: route.prefix, |
|
402 gateway: route.gateway |
|
403 }; |
|
404 this.controlMessage(options); |
|
405 }, |
|
406 |
|
407 setNetworkProxy: function(network) { |
|
408 try { |
|
409 if (!network.httpProxyHost || network.httpProxyHost === "") { |
|
410 // Sets direct connection to internet. |
|
411 Services.prefs.clearUserPref("network.proxy.type"); |
|
412 Services.prefs.clearUserPref("network.proxy.share_proxy_settings"); |
|
413 Services.prefs.clearUserPref("network.proxy.http"); |
|
414 Services.prefs.clearUserPref("network.proxy.http_port"); |
|
415 Services.prefs.clearUserPref("network.proxy.ssl"); |
|
416 Services.prefs.clearUserPref("network.proxy.ssl_port"); |
|
417 if(DEBUG) debug("No proxy support for " + network.name + " network interface."); |
|
418 return; |
|
419 } |
|
420 |
|
421 if(DEBUG) debug("Going to set proxy settings for " + network.name + " network interface."); |
|
422 // Sets manual proxy configuration. |
|
423 Services.prefs.setIntPref("network.proxy.type", MANUAL_PROXY_CONFIGURATION); |
|
424 // Do not use this proxy server for all protocols. |
|
425 Services.prefs.setBoolPref("network.proxy.share_proxy_settings", false); |
|
426 Services.prefs.setCharPref("network.proxy.http", network.httpProxyHost); |
|
427 Services.prefs.setCharPref("network.proxy.ssl", network.httpProxyHost); |
|
428 let port = network.httpProxyPort === 0 ? 8080 : network.httpProxyPort; |
|
429 Services.prefs.setIntPref("network.proxy.http_port", port); |
|
430 Services.prefs.setIntPref("network.proxy.ssl_port", port); |
|
431 } catch(ex) { |
|
432 if(DEBUG) debug("Exception " + ex + ". Unable to set proxy setting for " + |
|
433 network.name + " network interface."); |
|
434 } |
|
435 }, |
|
436 |
|
437 // Enable/Disable DHCP server. |
|
438 setDhcpServer: function(enabled, config, callback) { |
|
439 if (null === config) { |
|
440 config = {}; |
|
441 } |
|
442 |
|
443 config.cmd = "setDhcpServer"; |
|
444 config.isAsync = true; |
|
445 config.enabled = enabled; |
|
446 |
|
447 this.controlMessage(config, function setDhcpServerResult(response) { |
|
448 if (!response.success) { |
|
449 callback.dhcpServerResult('Set DHCP server error'); |
|
450 return; |
|
451 } |
|
452 callback.dhcpServerResult(null); |
|
453 }); |
|
454 }, |
|
455 |
|
456 // Enable/disable WiFi tethering by sending commands to netd. |
|
457 setWifiTethering: function(enable, config, callback) { |
|
458 // config should've already contained: |
|
459 // .ifname |
|
460 // .internalIfname |
|
461 // .externalIfname |
|
462 config.wifictrlinterfacename = WIFI_CTRL_INTERFACE; |
|
463 config.cmd = "setWifiTethering"; |
|
464 |
|
465 // The callback function in controlMessage may not be fired immediately. |
|
466 config.isAsync = true; |
|
467 this.controlMessage(config, function setWifiTetheringResult(data) { |
|
468 let code = data.resultCode; |
|
469 let reason = data.resultReason; |
|
470 let enable = data.enable; |
|
471 let enableString = enable ? "Enable" : "Disable"; |
|
472 |
|
473 if(DEBUG) debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason); |
|
474 |
|
475 if (isError(code)) { |
|
476 callback.wifiTetheringEnabledChange("netd command error"); |
|
477 } else { |
|
478 callback.wifiTetheringEnabledChange(null); |
|
479 } |
|
480 }); |
|
481 }, |
|
482 |
|
483 // Enable/disable USB tethering by sending commands to netd. |
|
484 setUSBTethering: function(enable, config, callback) { |
|
485 config.cmd = "setUSBTethering"; |
|
486 // The callback function in controlMessage may not be fired immediately. |
|
487 config.isAsync = true; |
|
488 this.controlMessage(config, function setUsbTetheringResult(data) { |
|
489 let code = data.resultCode; |
|
490 let reason = data.resultReason; |
|
491 let enable = data.enable; |
|
492 let enableString = enable ? "Enable" : "Disable"; |
|
493 |
|
494 if(DEBUG) debug(enableString + " USB tethering result: Code " + code + " reason " + reason); |
|
495 |
|
496 if (isError(code)) { |
|
497 callback.usbTetheringEnabledChange("netd command error"); |
|
498 } else { |
|
499 callback.usbTetheringEnabledChange(null); |
|
500 } |
|
501 }); |
|
502 }, |
|
503 |
|
504 // Switch usb function by modifying property of persist.sys.usb.config. |
|
505 enableUsbRndis: function(enable, callback) { |
|
506 if(DEBUG) debug("enableUsbRndis: " + enable); |
|
507 |
|
508 let params = { |
|
509 cmd: "enableUsbRndis", |
|
510 enable: enable |
|
511 }; |
|
512 // Ask net work to report the result when this value is set to true. |
|
513 if (callback) { |
|
514 params.report = true; |
|
515 } else { |
|
516 params.report = false; |
|
517 } |
|
518 |
|
519 // The callback function in controlMessage may not be fired immediately. |
|
520 params.isAsync = true; |
|
521 //this._usbTetheringAction = TETHERING_STATE_ONGOING; |
|
522 this.controlMessage(params, function(data) { |
|
523 callback.enableUsbRndisResult(data.result, data.enable); |
|
524 }); |
|
525 }, |
|
526 |
|
527 updateUpStream: function(previous, current, callback) { |
|
528 let params = { |
|
529 cmd: "updateUpStream", |
|
530 isAsync: true, |
|
531 preInternalIfname: previous.internalIfname, |
|
532 preExternalIfname: previous.externalIfname, |
|
533 curInternalIfname: current.internalIfname, |
|
534 curExternalIfname: current.externalIfname |
|
535 }; |
|
536 |
|
537 this.controlMessage(params, function(data) { |
|
538 let code = data.resultCode; |
|
539 let reason = data.resultReason; |
|
540 if(DEBUG) debug("updateUpStream result: Code " + code + " reason " + reason); |
|
541 callback.updateUpStreamResult(!isError(code), data.curExternalIfname); |
|
542 }); |
|
543 }, |
|
544 |
|
545 shutdown: false, |
|
546 |
|
547 observe: function observe(aSubject, aTopic, aData) { |
|
548 switch (aTopic) { |
|
549 case "xpcom-shutdown": |
|
550 debug("NetworkService shutdown"); |
|
551 this.shutdown = true; |
|
552 Services.obs.removeObserver(this, "xpcom-shutdown"); |
|
553 if (gNetworkWorker) { |
|
554 gNetworkWorker.shutdown(); |
|
555 gNetworkWorker = null; |
|
556 } |
|
557 break; |
|
558 } |
|
559 }, |
|
560 }; |
|
561 |
|
562 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]); |