Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
5 "use strict";
7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
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");
14 const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1";
15 const NETWORKSERVICE_CID = Components.ID("{baec696c-c78d-42db-8b44-603f8fbfafb4}");
17 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker",
18 "@mozilla.org/network/worker;1",
19 "nsINetworkWorker");
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;
33 const WIFI_CTRL_INTERFACE = "wl0.1";
35 const MANUAL_PROXY_CONFIGURATION = 1;
37 let DEBUG = false;
39 // Read debug setting from pref.
40 try {
41 let debugPref = Services.prefs.getBoolPref("network.debugging.enabled");
42 DEBUG = DEBUG || debugPref;
43 } catch (e) {}
45 function netdResponseType(code) {
46 return Math.floor(code / 100) * 100;
47 }
49 function isError(code) {
50 let type = netdResponseType(code);
51 return (type !== NETD_COMMAND_PROCEEDING && type !== NETD_COMMAND_OKAY);
52 }
54 function debug(msg) {
55 dump("-*- NetworkService: " + msg + "\n");
56 }
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.");
65 let self = this;
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);
78 this.shutdown = false;
79 Services.obs.addObserver(this, "xpcom-shutdown", false);
80 }
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]),
90 // Helpers
92 idgen: 0,
93 controlMessage: function(params, callback) {
94 if (this.shutdown) {
95 return;
96 }
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 },
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 },
122 // nsINetworkService
124 getNetworkInterfaceStats: function(networkName, callback) {
125 if(DEBUG) debug("getNetworkInterfaceStats for " + networkName);
127 if (this.shutdown) {
128 return;
129 }
131 let file = new FileUtils.File("/proc/net/dev");
132 if (!file) {
133 callback.networkStatsAvailable(false, -1, -1, new Date());
134 return;
135 }
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();
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 }
160 callback.networkStatsAvailable(result.success, result.rxBytes,
161 result.txBytes, result.date);
162 });
163 },
165 setNetworkInterfaceAlarm: function(networkName, threshold, callback) {
166 if (!networkName) {
167 callback.networkUsageAlarmResult(-1);
168 return;
169 }
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 }
182 self._setNetworkInterfaceAlarm(networkName, threshold, callback);
183 });
184 },
186 _setNetworkInterfaceAlarm: function(networkName, threshold, callback) {
187 if(DEBUG) debug("setNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes");
189 let params = {
190 cmd: "setNetworkInterfaceAlarm",
191 ifname: networkName,
192 threshold: threshold
193 };
195 params.report = true;
196 params.isAsync = true;
198 this.controlMessage(params, function(result) {
199 if (!isError(result.resultCode)) {
200 callback.networkUsageAlarmResult(null);
201 return;
202 }
204 this._enableNetworkInterfaceAlarm(networkName, threshold, callback);
205 });
206 },
208 _enableNetworkInterfaceAlarm: function(networkName, threshold, callback) {
209 if(DEBUG) debug("enableNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes");
211 let params = {
212 cmd: "enableNetworkInterfaceAlarm",
213 ifname: networkName,
214 threshold: threshold
215 };
217 params.report = true;
218 params.isAsync = true;
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 },
229 _disableNetworkInterfaceAlarm: function(networkName, callback) {
230 if(DEBUG) debug("disableNetworkInterfaceAlarm for " + networkName);
232 let params = {
233 cmd: "disableNetworkInterfaceAlarm",
234 ifname: networkName,
235 };
237 params.report = true;
238 params.isAsync = true;
240 this.controlMessage(params, function(result) {
241 callback(result);
242 });
243 },
245 setWifiOperationMode: function(interfaceName, mode, callback) {
246 if(DEBUG) debug("setWifiOperationMode on " + interfaceName + " to " + mode);
248 let params = {
249 cmd: "setWifiOperationMode",
250 ifname: interfaceName,
251 mode: mode
252 };
254 params.report = true;
255 params.isAsync = true;
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 },
266 resetRoutingTable: function(network) {
267 let ips = {};
268 let prefixLengths = {};
269 let length = network.getAddresses(ips, prefixLengths);
271 for (let i = 0; i < length; i++) {
272 let ip = ips.value[i];
273 let prefixLength = prefixLengths.value[i];
275 let options = {
276 cmd: "removeNetworkRoute",
277 ifname: network.name,
278 ip: ip,
279 prefixLength: prefixLength
280 };
281 this.controlMessage(options);
282 }
283 },
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 },
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 },
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 },
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 },
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 },
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 },
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 },
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 },
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 },
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 },
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 }
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 },
437 // Enable/Disable DHCP server.
438 setDhcpServer: function(enabled, config, callback) {
439 if (null === config) {
440 config = {};
441 }
443 config.cmd = "setDhcpServer";
444 config.isAsync = true;
445 config.enabled = enabled;
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 },
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";
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";
473 if(DEBUG) debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason);
475 if (isError(code)) {
476 callback.wifiTetheringEnabledChange("netd command error");
477 } else {
478 callback.wifiTetheringEnabledChange(null);
479 }
480 });
481 },
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";
494 if(DEBUG) debug(enableString + " USB tethering result: Code " + code + " reason " + reason);
496 if (isError(code)) {
497 callback.usbTetheringEnabledChange("netd command error");
498 } else {
499 callback.usbTetheringEnabledChange(null);
500 }
501 });
502 },
504 // Switch usb function by modifying property of persist.sys.usb.config.
505 enableUsbRndis: function(enable, callback) {
506 if(DEBUG) debug("enableUsbRndis: " + enable);
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 }
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 },
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 };
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 },
545 shutdown: false,
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 };
562 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]);