michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: michael@0: const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1"; michael@0: const NETWORKSERVICE_CID = Components.ID("{baec696c-c78d-42db-8b44-603f8fbfafb4}"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker", michael@0: "@mozilla.org/network/worker;1", michael@0: "nsINetworkWorker"); michael@0: michael@0: // 1xx - Requested action is proceeding michael@0: const NETD_COMMAND_PROCEEDING = 100; michael@0: // 2xx - Requested action has been successfully completed michael@0: const NETD_COMMAND_OKAY = 200; michael@0: // 4xx - The command is accepted but the requested action didn't michael@0: // take place. michael@0: const NETD_COMMAND_FAIL = 400; michael@0: // 5xx - The command syntax or parameters error michael@0: const NETD_COMMAND_ERROR = 500; michael@0: // 6xx - Unsolicited broadcasts michael@0: const NETD_COMMAND_UNSOLICITED = 600; michael@0: michael@0: const WIFI_CTRL_INTERFACE = "wl0.1"; michael@0: michael@0: const MANUAL_PROXY_CONFIGURATION = 1; michael@0: michael@0: let DEBUG = false; michael@0: michael@0: // Read debug setting from pref. michael@0: try { michael@0: let debugPref = Services.prefs.getBoolPref("network.debugging.enabled"); michael@0: DEBUG = DEBUG || debugPref; michael@0: } catch (e) {} michael@0: michael@0: function netdResponseType(code) { michael@0: return Math.floor(code / 100) * 100; michael@0: } michael@0: michael@0: function isError(code) { michael@0: let type = netdResponseType(code); michael@0: return (type !== NETD_COMMAND_PROCEEDING && type !== NETD_COMMAND_OKAY); michael@0: } michael@0: michael@0: function debug(msg) { michael@0: dump("-*- NetworkService: " + msg + "\n"); michael@0: } michael@0: michael@0: /** michael@0: * This component watches for network interfaces changing state and then michael@0: * adjusts routes etc. accordingly. michael@0: */ michael@0: function NetworkService() { michael@0: if(DEBUG) debug("Starting net_worker."); michael@0: michael@0: let self = this; michael@0: michael@0: if (gNetworkWorker) { michael@0: let networkListener = { michael@0: onEvent: function(event) { michael@0: self.handleWorkerMessage(event); michael@0: } michael@0: }; michael@0: gNetworkWorker.start(networkListener); michael@0: } michael@0: // Callbacks to invoke when a reply arrives from the net_worker. michael@0: this.controlCallbacks = Object.create(null); michael@0: michael@0: this.shutdown = false; michael@0: Services.obs.addObserver(this, "xpcom-shutdown", false); michael@0: } michael@0: michael@0: NetworkService.prototype = { michael@0: classID: NETWORKSERVICE_CID, michael@0: classInfo: XPCOMUtils.generateCI({classID: NETWORKSERVICE_CID, michael@0: contractID: NETWORKSERVICE_CONTRACTID, michael@0: classDescription: "Network Service", michael@0: interfaces: [Ci.nsINetworkService]}), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkService]), michael@0: michael@0: // Helpers michael@0: michael@0: idgen: 0, michael@0: controlMessage: function(params, callback) { michael@0: if (this.shutdown) { michael@0: return; michael@0: } michael@0: michael@0: if (callback) { michael@0: let id = this.idgen++; michael@0: params.id = id; michael@0: this.controlCallbacks[id] = callback; michael@0: } michael@0: if (gNetworkWorker) { michael@0: gNetworkWorker.postMessage(params); michael@0: } michael@0: }, michael@0: michael@0: handleWorkerMessage: function(response) { michael@0: if(DEBUG) debug("NetworkManager received message from worker: " + JSON.stringify(response)); michael@0: let id = response.id; michael@0: if (response.broadcast === true) { michael@0: Services.obs.notifyObservers(null, response.topic, response.reason); michael@0: return; michael@0: } michael@0: let callback = this.controlCallbacks[id]; michael@0: if (callback) { michael@0: callback.call(this, response); michael@0: delete this.controlCallbacks[id]; michael@0: } michael@0: }, michael@0: michael@0: // nsINetworkService michael@0: michael@0: getNetworkInterfaceStats: function(networkName, callback) { michael@0: if(DEBUG) debug("getNetworkInterfaceStats for " + networkName); michael@0: michael@0: if (this.shutdown) { michael@0: return; michael@0: } michael@0: michael@0: let file = new FileUtils.File("/proc/net/dev"); michael@0: if (!file) { michael@0: callback.networkStatsAvailable(false, -1, -1, new Date()); michael@0: return; michael@0: } michael@0: michael@0: NetUtil.asyncFetch(file, function(inputStream, status) { michael@0: let result = { michael@0: success: true, // netd always return success even interface doesn't exist. michael@0: rxBytes: 0, michael@0: txBytes: 0 michael@0: }; michael@0: result.date = new Date(); michael@0: michael@0: if (Components.isSuccessCode(status)) { michael@0: // Find record for corresponding interface. michael@0: let statExpr = /(\S+): +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+/; michael@0: let data = NetUtil.readInputStreamToString(inputStream, michael@0: inputStream.available()).split("\n"); michael@0: for (let i = 2; i < data.length; i++) { michael@0: let parseResult = statExpr.exec(data[i]); michael@0: if (parseResult && parseResult[1] === networkName) { michael@0: result.rxBytes = parseInt(parseResult[2], 10); michael@0: result.txBytes = parseInt(parseResult[3], 10); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: callback.networkStatsAvailable(result.success, result.rxBytes, michael@0: result.txBytes, result.date); michael@0: }); michael@0: }, michael@0: michael@0: setNetworkInterfaceAlarm: function(networkName, threshold, callback) { michael@0: if (!networkName) { michael@0: callback.networkUsageAlarmResult(-1); michael@0: return; michael@0: } michael@0: michael@0: let self = this; michael@0: this._disableNetworkInterfaceAlarm(networkName, function(result) { michael@0: if (threshold < 0) { michael@0: if (!isError(result.resultCode)) { michael@0: callback.networkUsageAlarmResult(null); michael@0: return; michael@0: } michael@0: callback.networkUsageAlarmResult(result.reason); michael@0: return michael@0: } michael@0: michael@0: self._setNetworkInterfaceAlarm(networkName, threshold, callback); michael@0: }); michael@0: }, michael@0: michael@0: _setNetworkInterfaceAlarm: function(networkName, threshold, callback) { michael@0: if(DEBUG) debug("setNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes"); michael@0: michael@0: let params = { michael@0: cmd: "setNetworkInterfaceAlarm", michael@0: ifname: networkName, michael@0: threshold: threshold michael@0: }; michael@0: michael@0: params.report = true; michael@0: params.isAsync = true; michael@0: michael@0: this.controlMessage(params, function(result) { michael@0: if (!isError(result.resultCode)) { michael@0: callback.networkUsageAlarmResult(null); michael@0: return; michael@0: } michael@0: michael@0: this._enableNetworkInterfaceAlarm(networkName, threshold, callback); michael@0: }); michael@0: }, michael@0: michael@0: _enableNetworkInterfaceAlarm: function(networkName, threshold, callback) { michael@0: if(DEBUG) debug("enableNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes"); michael@0: michael@0: let params = { michael@0: cmd: "enableNetworkInterfaceAlarm", michael@0: ifname: networkName, michael@0: threshold: threshold michael@0: }; michael@0: michael@0: params.report = true; michael@0: params.isAsync = true; michael@0: michael@0: this.controlMessage(params, function(result) { michael@0: if (!isError(result.resultCode)) { michael@0: callback.networkUsageAlarmResult(null); michael@0: return; michael@0: } michael@0: callback.networkUsageAlarmResult(result.reason); michael@0: }); michael@0: }, michael@0: michael@0: _disableNetworkInterfaceAlarm: function(networkName, callback) { michael@0: if(DEBUG) debug("disableNetworkInterfaceAlarm for " + networkName); michael@0: michael@0: let params = { michael@0: cmd: "disableNetworkInterfaceAlarm", michael@0: ifname: networkName, michael@0: }; michael@0: michael@0: params.report = true; michael@0: params.isAsync = true; michael@0: michael@0: this.controlMessage(params, function(result) { michael@0: callback(result); michael@0: }); michael@0: }, michael@0: michael@0: setWifiOperationMode: function(interfaceName, mode, callback) { michael@0: if(DEBUG) debug("setWifiOperationMode on " + interfaceName + " to " + mode); michael@0: michael@0: let params = { michael@0: cmd: "setWifiOperationMode", michael@0: ifname: interfaceName, michael@0: mode: mode michael@0: }; michael@0: michael@0: params.report = true; michael@0: params.isAsync = true; michael@0: michael@0: this.controlMessage(params, function(result) { michael@0: if (isError(result.resultCode)) { michael@0: callback.wifiOperationModeResult("netd command error"); michael@0: } else { michael@0: callback.wifiOperationModeResult(null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: resetRoutingTable: function(network) { michael@0: let ips = {}; michael@0: let prefixLengths = {}; michael@0: let length = network.getAddresses(ips, prefixLengths); michael@0: michael@0: for (let i = 0; i < length; i++) { michael@0: let ip = ips.value[i]; michael@0: let prefixLength = prefixLengths.value[i]; michael@0: michael@0: let options = { michael@0: cmd: "removeNetworkRoute", michael@0: ifname: network.name, michael@0: ip: ip, michael@0: prefixLength: prefixLength michael@0: }; michael@0: this.controlMessage(options); michael@0: } michael@0: }, michael@0: michael@0: setDNS: function(networkInterface) { michael@0: if(DEBUG) debug("Going DNS to " + networkInterface.name); michael@0: let dnses = networkInterface.getDnses(); michael@0: let options = { michael@0: cmd: "setDNS", michael@0: ifname: networkInterface.name, michael@0: domain: "mozilla." + networkInterface.name + ".doman", michael@0: dnses: dnses michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: setDefaultRouteAndDNS: function(network, oldInterface) { michael@0: if(DEBUG) debug("Going to change route and DNS to " + network.name); michael@0: let gateways = network.getGateways(); michael@0: let dnses = network.getDnses(); michael@0: let options = { michael@0: cmd: "setDefaultRouteAndDNS", michael@0: ifname: network.name, michael@0: oldIfname: (oldInterface && oldInterface !== network) ? oldInterface.name : null, michael@0: gateways: gateways, michael@0: domain: "mozilla." + network.name + ".doman", michael@0: dnses: dnses michael@0: }; michael@0: this.controlMessage(options); michael@0: this.setNetworkProxy(network); michael@0: }, michael@0: michael@0: removeDefaultRoute: function(network) { michael@0: if(DEBUG) debug("Remove default route for " + network.name); michael@0: let gateways = network.getGateways(); michael@0: let options = { michael@0: cmd: "removeDefaultRoute", michael@0: ifname: network.name, michael@0: gateways: gateways michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: addHostRoute: function(network) { michael@0: if(DEBUG) debug("Going to add host route on " + network.name); michael@0: let gateways = network.getGateways(); michael@0: let dnses = network.getDnses(); michael@0: let options = { michael@0: cmd: "addHostRoute", michael@0: ifname: network.name, michael@0: gateways: gateways, michael@0: hostnames: dnses.concat(network.httpProxyHost) michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: removeHostRoute: function(network) { michael@0: if(DEBUG) debug("Going to remove host route on " + network.name); michael@0: let gateways = network.getGateways(); michael@0: let dnses = network.getDnses(); michael@0: let options = { michael@0: cmd: "removeHostRoute", michael@0: ifname: network.name, michael@0: gateways: gateways, michael@0: hostnames: dnses.concat(network.httpProxyHost) michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: removeHostRoutes: function(ifname) { michael@0: if(DEBUG) debug("Going to remove all host routes on " + ifname); michael@0: let options = { michael@0: cmd: "removeHostRoutes", michael@0: ifname: ifname, michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: addHostRouteWithResolve: function(network, hosts) { michael@0: if(DEBUG) debug("Going to add host route after dns resolution on " + network.name); michael@0: let gateways = network.getGateways(); michael@0: let options = { michael@0: cmd: "addHostRoute", michael@0: ifname: network.name, michael@0: gateways: gateways, michael@0: hostnames: hosts michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: removeHostRouteWithResolve: function(network, hosts) { michael@0: if(DEBUG) debug("Going to remove host route after dns resolution on " + network.name); michael@0: let gateways = network.getGateways(); michael@0: let options = { michael@0: cmd: "removeHostRoute", michael@0: ifname: network.name, michael@0: gateways: gateways, michael@0: hostnames: hosts michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: addSecondaryRoute: function(ifname, route) { michael@0: if(DEBUG) debug("Going to add route to secondary table on " + ifname); michael@0: let options = { michael@0: cmd: "addSecondaryRoute", michael@0: ifname: ifname, michael@0: ip: route.ip, michael@0: prefix: route.prefix, michael@0: gateway: route.gateway michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: removeSecondaryRoute: function(ifname, route) { michael@0: if(DEBUG) debug("Going to remove route from secondary table on " + ifname); michael@0: let options = { michael@0: cmd: "removeSecondaryRoute", michael@0: ifname: ifname, michael@0: ip: route.ip, michael@0: prefix: route.prefix, michael@0: gateway: route.gateway michael@0: }; michael@0: this.controlMessage(options); michael@0: }, michael@0: michael@0: setNetworkProxy: function(network) { michael@0: try { michael@0: if (!network.httpProxyHost || network.httpProxyHost === "") { michael@0: // Sets direct connection to internet. michael@0: Services.prefs.clearUserPref("network.proxy.type"); michael@0: Services.prefs.clearUserPref("network.proxy.share_proxy_settings"); michael@0: Services.prefs.clearUserPref("network.proxy.http"); michael@0: Services.prefs.clearUserPref("network.proxy.http_port"); michael@0: Services.prefs.clearUserPref("network.proxy.ssl"); michael@0: Services.prefs.clearUserPref("network.proxy.ssl_port"); michael@0: if(DEBUG) debug("No proxy support for " + network.name + " network interface."); michael@0: return; michael@0: } michael@0: michael@0: if(DEBUG) debug("Going to set proxy settings for " + network.name + " network interface."); michael@0: // Sets manual proxy configuration. michael@0: Services.prefs.setIntPref("network.proxy.type", MANUAL_PROXY_CONFIGURATION); michael@0: // Do not use this proxy server for all protocols. michael@0: Services.prefs.setBoolPref("network.proxy.share_proxy_settings", false); michael@0: Services.prefs.setCharPref("network.proxy.http", network.httpProxyHost); michael@0: Services.prefs.setCharPref("network.proxy.ssl", network.httpProxyHost); michael@0: let port = network.httpProxyPort === 0 ? 8080 : network.httpProxyPort; michael@0: Services.prefs.setIntPref("network.proxy.http_port", port); michael@0: Services.prefs.setIntPref("network.proxy.ssl_port", port); michael@0: } catch(ex) { michael@0: if(DEBUG) debug("Exception " + ex + ". Unable to set proxy setting for " + michael@0: network.name + " network interface."); michael@0: } michael@0: }, michael@0: michael@0: // Enable/Disable DHCP server. michael@0: setDhcpServer: function(enabled, config, callback) { michael@0: if (null === config) { michael@0: config = {}; michael@0: } michael@0: michael@0: config.cmd = "setDhcpServer"; michael@0: config.isAsync = true; michael@0: config.enabled = enabled; michael@0: michael@0: this.controlMessage(config, function setDhcpServerResult(response) { michael@0: if (!response.success) { michael@0: callback.dhcpServerResult('Set DHCP server error'); michael@0: return; michael@0: } michael@0: callback.dhcpServerResult(null); michael@0: }); michael@0: }, michael@0: michael@0: // Enable/disable WiFi tethering by sending commands to netd. michael@0: setWifiTethering: function(enable, config, callback) { michael@0: // config should've already contained: michael@0: // .ifname michael@0: // .internalIfname michael@0: // .externalIfname michael@0: config.wifictrlinterfacename = WIFI_CTRL_INTERFACE; michael@0: config.cmd = "setWifiTethering"; michael@0: michael@0: // The callback function in controlMessage may not be fired immediately. michael@0: config.isAsync = true; michael@0: this.controlMessage(config, function setWifiTetheringResult(data) { michael@0: let code = data.resultCode; michael@0: let reason = data.resultReason; michael@0: let enable = data.enable; michael@0: let enableString = enable ? "Enable" : "Disable"; michael@0: michael@0: if(DEBUG) debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason); michael@0: michael@0: if (isError(code)) { michael@0: callback.wifiTetheringEnabledChange("netd command error"); michael@0: } else { michael@0: callback.wifiTetheringEnabledChange(null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: // Enable/disable USB tethering by sending commands to netd. michael@0: setUSBTethering: function(enable, config, callback) { michael@0: config.cmd = "setUSBTethering"; michael@0: // The callback function in controlMessage may not be fired immediately. michael@0: config.isAsync = true; michael@0: this.controlMessage(config, function setUsbTetheringResult(data) { michael@0: let code = data.resultCode; michael@0: let reason = data.resultReason; michael@0: let enable = data.enable; michael@0: let enableString = enable ? "Enable" : "Disable"; michael@0: michael@0: if(DEBUG) debug(enableString + " USB tethering result: Code " + code + " reason " + reason); michael@0: michael@0: if (isError(code)) { michael@0: callback.usbTetheringEnabledChange("netd command error"); michael@0: } else { michael@0: callback.usbTetheringEnabledChange(null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: // Switch usb function by modifying property of persist.sys.usb.config. michael@0: enableUsbRndis: function(enable, callback) { michael@0: if(DEBUG) debug("enableUsbRndis: " + enable); michael@0: michael@0: let params = { michael@0: cmd: "enableUsbRndis", michael@0: enable: enable michael@0: }; michael@0: // Ask net work to report the result when this value is set to true. michael@0: if (callback) { michael@0: params.report = true; michael@0: } else { michael@0: params.report = false; michael@0: } michael@0: michael@0: // The callback function in controlMessage may not be fired immediately. michael@0: params.isAsync = true; michael@0: //this._usbTetheringAction = TETHERING_STATE_ONGOING; michael@0: this.controlMessage(params, function(data) { michael@0: callback.enableUsbRndisResult(data.result, data.enable); michael@0: }); michael@0: }, michael@0: michael@0: updateUpStream: function(previous, current, callback) { michael@0: let params = { michael@0: cmd: "updateUpStream", michael@0: isAsync: true, michael@0: preInternalIfname: previous.internalIfname, michael@0: preExternalIfname: previous.externalIfname, michael@0: curInternalIfname: current.internalIfname, michael@0: curExternalIfname: current.externalIfname michael@0: }; michael@0: michael@0: this.controlMessage(params, function(data) { michael@0: let code = data.resultCode; michael@0: let reason = data.resultReason; michael@0: if(DEBUG) debug("updateUpStream result: Code " + code + " reason " + reason); michael@0: callback.updateUpStreamResult(!isError(code), data.curExternalIfname); michael@0: }); michael@0: }, michael@0: michael@0: shutdown: false, michael@0: michael@0: observe: function observe(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "xpcom-shutdown": michael@0: debug("NetworkService shutdown"); michael@0: this.shutdown = true; michael@0: Services.obs.removeObserver(this, "xpcom-shutdown"); michael@0: if (gNetworkWorker) { michael@0: gNetworkWorker.shutdown(); michael@0: gNetworkWorker = null; michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]);