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/FileUtils.jsm"); michael@0: Cu.import("resource://gre/modules/systemlibs.js"); michael@0: michael@0: const NETWORKMANAGER_CONTRACTID = "@mozilla.org/network/manager;1"; michael@0: const NETWORKMANAGER_CID = michael@0: Components.ID("{33901e46-33b8-11e1-9869-f46d04d25bcc}"); michael@0: michael@0: const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", michael@0: "@mozilla.org/settingsService;1", michael@0: "nsISettingsService"); michael@0: XPCOMUtils.defineLazyGetter(this, "ppmm", function() { michael@0: return Cc["@mozilla.org/parentprocessmessagemanager;1"] michael@0: .getService(Ci.nsIMessageBroadcaster); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gDNSService", michael@0: "@mozilla.org/network/dns-service;1", michael@0: "nsIDNSService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService", michael@0: "@mozilla.org/network/service;1", michael@0: "nsINetworkService"); michael@0: michael@0: const TOPIC_INTERFACE_STATE_CHANGED = "network-interface-state-changed"; michael@0: const TOPIC_INTERFACE_REGISTERED = "network-interface-registered"; michael@0: const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered"; michael@0: const TOPIC_ACTIVE_CHANGED = "network-active-changed"; michael@0: const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed"; michael@0: const TOPIC_PREF_CHANGED = "nsPref:changed"; michael@0: const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown"; michael@0: const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed"; michael@0: const PREF_MANAGE_OFFLINE_STATUS = "network.gonk.manage-offline-status"; michael@0: michael@0: const POSSIBLE_USB_INTERFACE_NAME = "rndis0,usb0"; michael@0: const DEFAULT_USB_INTERFACE_NAME = "rndis0"; michael@0: const DEFAULT_3G_INTERFACE_NAME = "rmnet0"; michael@0: const DEFAULT_WIFI_INTERFACE_NAME = "wlan0"; michael@0: michael@0: // The kernel's proc entry for network lists. michael@0: const KERNEL_NETWORK_ENTRY = "/sys/class/net"; michael@0: michael@0: const TETHERING_TYPE_WIFI = "WiFi"; michael@0: const TETHERING_TYPE_USB = "USB"; michael@0: michael@0: const WIFI_FIRMWARE_AP = "AP"; michael@0: const WIFI_FIRMWARE_STATION = "STA"; michael@0: const WIFI_SECURITY_TYPE_NONE = "open"; michael@0: const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk"; michael@0: const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk"; michael@0: const WIFI_CTRL_INTERFACE = "wl0.1"; michael@0: michael@0: const NETWORK_INTERFACE_UP = "up"; michael@0: const NETWORK_INTERFACE_DOWN = "down"; michael@0: michael@0: const TETHERING_STATE_ONGOING = "ongoing"; michael@0: const TETHERING_STATE_IDLE = "idle"; michael@0: const TETHERING_STATE_ACTIVE = "active"; michael@0: michael@0: // Settings DB path for USB tethering. michael@0: const SETTINGS_USB_ENABLED = "tethering.usb.enabled"; michael@0: const SETTINGS_USB_IP = "tethering.usb.ip"; michael@0: const SETTINGS_USB_PREFIX = "tethering.usb.prefix"; michael@0: const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip"; michael@0: const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip"; michael@0: const SETTINGS_USB_DNS1 = "tethering.usb.dns1"; michael@0: const SETTINGS_USB_DNS2 = "tethering.usb.dns2"; michael@0: michael@0: // Settings DB path for WIFI tethering. michael@0: const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip"; michael@0: const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip"; michael@0: michael@0: // Settings DB patch for dun required setting. michael@0: const SETTINGS_DUN_REQUIRED = "tethering.dun.required"; michael@0: michael@0: // Default value for USB tethering. michael@0: const DEFAULT_USB_IP = "192.168.0.1"; michael@0: const DEFAULT_USB_PREFIX = "24"; michael@0: const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10"; michael@0: const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30"; michael@0: michael@0: const DEFAULT_DNS1 = "8.8.8.8"; michael@0: const DEFAULT_DNS2 = "8.8.4.4"; michael@0: michael@0: const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10"; michael@0: const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30"; michael@0: michael@0: const IPV4_ADDRESS_ANY = "0.0.0.0"; michael@0: const IPV6_ADDRESS_ANY = "::0"; michael@0: michael@0: const IPV4_MAX_PREFIX_LENGTH = 32; michael@0: const IPV6_MAX_PREFIX_LENGTH = 128; michael@0: michael@0: const PREF_DATA_DEFAULT_SERVICE_ID = "ril.data.defaultServiceId"; michael@0: const MOBILE_DUN_CONNECT_TIMEOUT = 30000; michael@0: const MOBILE_DUN_RETRY_INTERVAL = 5000; michael@0: const MOBILE_DUN_MAX_RETRIES = 5; michael@0: michael@0: // Connection Type for Network Information API michael@0: const CONNECTION_TYPE_CULLULAR = 0; michael@0: const CONNECTION_TYPE_BLUETOOTH = 1; michael@0: const CONNECTION_TYPE_ETHERNET = 2; michael@0: const CONNECTION_TYPE_WIFI = 3; michael@0: const CONNECTION_TYPE_OTHER = 4; michael@0: const CONNECTION_TYPE_NONE = 5; 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 defineLazyRegExp(obj, name, pattern) { michael@0: obj.__defineGetter__(name, function() { michael@0: delete obj[name]; michael@0: return obj[name] = new RegExp(pattern); michael@0: }); 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 NetworkManager() { michael@0: this.networkInterfaces = {}; michael@0: Services.obs.addObserver(this, TOPIC_INTERFACE_STATE_CHANGED, true); michael@0: #ifdef MOZ_B2G_RIL michael@0: Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, true); michael@0: Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, true); michael@0: #endif michael@0: Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false); michael@0: Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false); michael@0: michael@0: try { michael@0: this._manageOfflineStatus = michael@0: Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS); michael@0: } catch(ex) { michael@0: // Ignore. michael@0: } michael@0: Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false); michael@0: michael@0: // Possible usb tethering interfaces for different gonk platform. michael@0: this.possibleInterface = POSSIBLE_USB_INTERFACE_NAME.split(","); michael@0: michael@0: // Default values for internal and external interfaces. michael@0: this._tetheringInterface = Object.create(null); michael@0: this._tetheringInterface[TETHERING_TYPE_USB] = { michael@0: externalInterface: DEFAULT_3G_INTERFACE_NAME, michael@0: internalInterface: DEFAULT_USB_INTERFACE_NAME michael@0: }; michael@0: this._tetheringInterface[TETHERING_TYPE_WIFI] = { michael@0: externalInterface: DEFAULT_3G_INTERFACE_NAME, michael@0: internalInterface: DEFAULT_WIFI_INTERFACE_NAME michael@0: }; michael@0: michael@0: this.initTetheringSettings(); michael@0: michael@0: let settingsLock = gSettingsService.createLock(); michael@0: // Read usb tethering data from settings DB. michael@0: settingsLock.get(SETTINGS_USB_IP, this); michael@0: settingsLock.get(SETTINGS_USB_PREFIX, this); michael@0: settingsLock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this); michael@0: settingsLock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this); michael@0: settingsLock.get(SETTINGS_USB_DNS1, this); michael@0: settingsLock.get(SETTINGS_USB_DNS2, this); michael@0: settingsLock.get(SETTINGS_USB_ENABLED, this); michael@0: michael@0: // Read wifi tethering data from settings DB. michael@0: settingsLock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this); michael@0: settingsLock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this); michael@0: michael@0: this._usbTetheringSettingsToRead = [SETTINGS_USB_IP, michael@0: SETTINGS_USB_PREFIX, michael@0: SETTINGS_USB_DHCPSERVER_STARTIP, michael@0: SETTINGS_USB_DHCPSERVER_ENDIP, michael@0: SETTINGS_USB_DNS1, michael@0: SETTINGS_USB_DNS2, michael@0: SETTINGS_USB_ENABLED, michael@0: SETTINGS_WIFI_DHCPSERVER_STARTIP, michael@0: SETTINGS_WIFI_DHCPSERVER_ENDIP]; michael@0: michael@0: this.wantConnectionEvent = null; michael@0: this.setAndConfigureActive(); michael@0: michael@0: ppmm.addMessageListener('NetworkInterfaceList:ListInterface', this); michael@0: michael@0: // Used in resolveHostname(). michael@0: defineLazyRegExp(this, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$"); michael@0: defineLazyRegExp(this, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$"); michael@0: } michael@0: NetworkManager.prototype = { michael@0: classID: NETWORKMANAGER_CID, michael@0: classInfo: XPCOMUtils.generateCI({classID: NETWORKMANAGER_CID, michael@0: contractID: NETWORKMANAGER_CONTRACTID, michael@0: classDescription: "Network Manager", michael@0: interfaces: [Ci.nsINetworkManager]}), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkManager, michael@0: Ci.nsISupportsWeakReference, michael@0: Ci.nsIObserver, michael@0: Ci.nsISettingsServiceCallback]), michael@0: michael@0: // nsIObserver michael@0: michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case TOPIC_INTERFACE_STATE_CHANGED: michael@0: let network = subject.QueryInterface(Ci.nsINetworkInterface); michael@0: debug("Network " + network.name + " changed state to " + network.state); michael@0: switch (network.state) { michael@0: case Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED: michael@0: #ifdef MOZ_B2G_RIL michael@0: // Add host route for data calls michael@0: if (this.isNetworkTypeMobile(network.type)) { michael@0: gNetworkService.removeHostRoutes(network.name); michael@0: gNetworkService.addHostRoute(network); michael@0: } michael@0: // Add extra host route. For example, mms proxy or mmsc. michael@0: this.setExtraHostRoute(network); michael@0: // Dun type is a special case where we add the default route to a michael@0: // secondary table. michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { michael@0: this.setSecondaryDefaultRoute(network); michael@0: } michael@0: #endif michael@0: // Remove pre-created default route and let setAndConfigureActive() michael@0: // to set default route only on preferred network michael@0: gNetworkService.removeDefaultRoute(network); michael@0: this.setAndConfigureActive(); michael@0: #ifdef MOZ_B2G_RIL michael@0: // Update data connection when Wifi connected/disconnected michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: for (let i = 0; i < this.mRil.numRadioInterfaces; i++) { michael@0: this.mRil.getRadioInterface(i).updateRILNetworkInterface(); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: this.onConnectionChanged(network); michael@0: michael@0: // Probing the public network accessibility after routing table is ready michael@0: CaptivePortalDetectionHelper michael@0: .notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active); michael@0: break; michael@0: case Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED: michael@0: #ifdef MOZ_B2G_RIL michael@0: // Remove host route for data calls michael@0: if (this.isNetworkTypeMobile(network.type)) { michael@0: gNetworkService.removeHostRoute(network); michael@0: } michael@0: // Remove extra host route. For example, mms proxy or mmsc. michael@0: this.removeExtraHostRoute(network); michael@0: // Remove secondary default route for dun. michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { michael@0: this.removeSecondaryDefaultRoute(network); michael@0: } michael@0: #endif michael@0: // Remove routing table in /proc/net/route michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: gNetworkService.resetRoutingTable(network); michael@0: #ifdef MOZ_B2G_RIL michael@0: } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { michael@0: gNetworkService.removeDefaultRoute(network); michael@0: #endif michael@0: } michael@0: michael@0: // Abort ongoing captive portal detection on the wifi interface michael@0: CaptivePortalDetectionHelper michael@0: .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, network); michael@0: this.setAndConfigureActive(); michael@0: #ifdef MOZ_B2G_RIL michael@0: // Update data connection when Wifi connected/disconnected michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: for (let i = 0; i < this.mRil.numRadioInterfaces; i++) { michael@0: this.mRil.getRadioInterface(i).updateRILNetworkInterface(); michael@0: } michael@0: } michael@0: #endif michael@0: break; michael@0: } michael@0: #ifdef MOZ_B2G_RIL michael@0: // Notify outer modules like MmsService to start the transaction after michael@0: // the configuration of the network interface is done. michael@0: Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED, michael@0: this.convertConnectionType(network)); michael@0: #endif michael@0: break; michael@0: #ifdef MOZ_B2G_RIL michael@0: case TOPIC_INTERFACE_REGISTERED: michael@0: let regNetwork = subject.QueryInterface(Ci.nsINetworkInterface); michael@0: // Add extra host route. For example, mms proxy or mmsc. michael@0: this.setExtraHostRoute(regNetwork); michael@0: // Dun type is a special case where we add the default route to a michael@0: // secondary table. michael@0: if (regNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { michael@0: this.setSecondaryDefaultRoute(regNetwork); michael@0: } michael@0: break; michael@0: case TOPIC_INTERFACE_UNREGISTERED: michael@0: let unregNetwork = subject.QueryInterface(Ci.nsINetworkInterface); michael@0: // Remove extra host route. For example, mms proxy or mmsc. michael@0: this.removeExtraHostRoute(unregNetwork); michael@0: // Remove secondary default route for dun. michael@0: if (unregNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { michael@0: this.removeSecondaryDefaultRoute(unregNetwork); michael@0: } michael@0: break; michael@0: #endif michael@0: case TOPIC_MOZSETTINGS_CHANGED: michael@0: let setting = JSON.parse(data); michael@0: this.handle(setting.key, setting.value); michael@0: break; michael@0: case TOPIC_PREF_CHANGED: michael@0: this._manageOfflineStatus = michael@0: Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS); michael@0: debug(PREF_MANAGE_OFFLINE_STATUS + " has changed to " + this._manageOfflineStatus); michael@0: break; michael@0: case TOPIC_XPCOM_SHUTDOWN: michael@0: Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN); michael@0: Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED); michael@0: #ifdef MOZ_B2G_RIL michael@0: Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED); michael@0: Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED); michael@0: #endif michael@0: Services.obs.removeObserver(this, TOPIC_INTERFACE_STATE_CHANGED); michael@0: #ifdef MOZ_B2G_RIL michael@0: this.dunConnectTimer.cancel(); michael@0: this.dunRetryTimer.cancel(); michael@0: #endif michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: receiveMessage: function(aMsg) { michael@0: switch (aMsg.name) { michael@0: case "NetworkInterfaceList:ListInterface": { michael@0: #ifdef MOZ_B2G_RIL michael@0: let excludeMms = aMsg.json.excludeMms; michael@0: let excludeSupl = aMsg.json.excludeSupl; michael@0: let excludeIms = aMsg.json.excludeIms; michael@0: let excludeDun = aMsg.json.excludeDun; michael@0: #endif michael@0: let interfaces = []; michael@0: michael@0: for each (let i in this.networkInterfaces) { michael@0: #ifdef MOZ_B2G_RIL michael@0: if ((i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS && excludeMms) || michael@0: (i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL && excludeSupl) || michael@0: (i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS && excludeIms) || michael@0: (i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN && excludeDun)) { michael@0: continue; michael@0: } michael@0: #endif michael@0: let ips = {}; michael@0: let prefixLengths = {}; michael@0: i.getAddresses(ips, prefixLengths); michael@0: michael@0: interfaces.push({ michael@0: state: i.state, michael@0: type: i.type, michael@0: name: i.name, michael@0: ips: ips.value, michael@0: prefixLengths: prefixLengths.value, michael@0: gateways: i.getGateways(), michael@0: dnses: i.getDnses(), michael@0: httpProxyHost: i.httpProxyHost, michael@0: httpProxyPort: i.httpProxyPort michael@0: }); michael@0: } michael@0: return interfaces; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // nsINetworkManager michael@0: michael@0: registerNetworkInterface: function(network) { michael@0: if (!(network instanceof Ci.nsINetworkInterface)) { michael@0: throw Components.Exception("Argument must be nsINetworkInterface.", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: } michael@0: if (network.name in this.networkInterfaces) { michael@0: throw Components.Exception("Network with that name already registered!", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: } michael@0: this.networkInterfaces[network.name] = network; michael@0: #ifdef MOZ_B2G_RIL michael@0: // Add host route for data calls michael@0: if (this.isNetworkTypeMobile(network.type)) { michael@0: gNetworkService.addHostRoute(network); michael@0: } michael@0: #endif michael@0: // Remove pre-created default route and let setAndConfigureActive() michael@0: // to set default route only on preferred network michael@0: gNetworkService.removeDefaultRoute(network); michael@0: this.setAndConfigureActive(); michael@0: Services.obs.notifyObservers(network, TOPIC_INTERFACE_REGISTERED, null); michael@0: debug("Network '" + network.name + "' registered."); michael@0: }, michael@0: michael@0: unregisterNetworkInterface: function(network) { michael@0: if (!(network instanceof Ci.nsINetworkInterface)) { michael@0: throw Components.Exception("Argument must be nsINetworkInterface.", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: } michael@0: if (!(network.name in this.networkInterfaces)) { michael@0: throw Components.Exception("No network with that name registered.", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: } michael@0: delete this.networkInterfaces[network.name]; michael@0: #ifdef MOZ_B2G_RIL michael@0: // Remove host route for data calls michael@0: if (this.isNetworkTypeMobile(network.type)) { michael@0: gNetworkService.removeHostRoute(network); michael@0: } michael@0: #endif michael@0: this.setAndConfigureActive(); michael@0: Services.obs.notifyObservers(network, TOPIC_INTERFACE_UNREGISTERED, null); michael@0: debug("Network '" + network.name + "' unregistered."); michael@0: }, michael@0: michael@0: _manageOfflineStatus: true, michael@0: michael@0: networkInterfaces: null, michael@0: michael@0: _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE, michael@0: get preferredNetworkType() { michael@0: return this._preferredNetworkType; michael@0: }, michael@0: set preferredNetworkType(val) { michael@0: #ifdef MOZ_B2G_RIL michael@0: if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, michael@0: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE].indexOf(val) == -1) { michael@0: #else michael@0: if (val != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: #endif michael@0: throw "Invalid network type"; michael@0: } michael@0: this._preferredNetworkType = val; michael@0: }, michael@0: michael@0: active: null, michael@0: _overriddenActive: null, michael@0: michael@0: overrideActive: function(network) { michael@0: #ifdef MOZ_B2G_RIL michael@0: if (this.isNetworkTypeSecondaryMobile(network.type)) { michael@0: throw "Invalid network type"; michael@0: } michael@0: #endif michael@0: this._overriddenActive = network; michael@0: this.setAndConfigureActive(); michael@0: }, michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: isNetworkTypeSecondaryMobile: function(type) { michael@0: return (type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS || michael@0: type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL || michael@0: type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS || michael@0: type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN); michael@0: }, michael@0: michael@0: isNetworkTypeMobile: function(type) { michael@0: return (type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE || michael@0: this.isNetworkTypeSecondaryMobile(type)); michael@0: }, michael@0: michael@0: setExtraHostRoute: function(network) { michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) { michael@0: if (!(network instanceof Ci.nsIRilNetworkInterface)) { michael@0: debug("Network for MMS must be an instance of nsIRilNetworkInterface"); michael@0: return; michael@0: } michael@0: michael@0: network = network.QueryInterface(Ci.nsIRilNetworkInterface); michael@0: michael@0: debug("Network '" + network.name + "' registered, " + michael@0: "adding mmsproxy and/or mmsc route"); michael@0: michael@0: let hostToResolve = network.mmsProxy; michael@0: // Workaround an xpconnect issue with undefined string objects. michael@0: // See bug 808220 michael@0: if (!hostToResolve || hostToResolve === "undefined") { michael@0: hostToResolve = network.mmsc; michael@0: } michael@0: michael@0: let mmsHosts = this.resolveHostname([hostToResolve]); michael@0: if (mmsHosts.length == 0) { michael@0: debug("No valid hostnames can be added. Stop adding host route."); michael@0: return; michael@0: } michael@0: michael@0: gNetworkService.addHostRouteWithResolve(network, mmsHosts); michael@0: } michael@0: }, michael@0: michael@0: removeExtraHostRoute: function(network) { michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) { michael@0: if (!(network instanceof Ci.nsIRilNetworkInterface)) { michael@0: debug("Network for MMS must be an instance of nsIRilNetworkInterface"); michael@0: return; michael@0: } michael@0: michael@0: network = network.QueryInterface(Ci.nsIRilNetworkInterface); michael@0: michael@0: debug("Network '" + network.name + "' unregistered, " + michael@0: "removing mmsproxy and/or mmsc route"); michael@0: michael@0: let hostToResolve = network.mmsProxy; michael@0: // Workaround an xpconnect issue with undefined string objects. michael@0: // See bug 808220 michael@0: if (!hostToResolve || hostToResolve === "undefined") { michael@0: hostToResolve = network.mmsc; michael@0: } michael@0: michael@0: let mmsHosts = this.resolveHostname([hostToResolve]); michael@0: if (mmsHosts.length == 0) { michael@0: debug("No valid hostnames can be removed. Stop removing host route."); michael@0: return; michael@0: } michael@0: michael@0: gNetworkService.removeHostRouteWithResolve(network, mmsHosts); michael@0: } michael@0: }, michael@0: michael@0: setSecondaryDefaultRoute: function(network) { michael@0: let gateways = network.getGateways(); michael@0: for (let i = 0; i < gateways.length; i++) { michael@0: let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false; michael@0: // First, we need to add a host route to the gateway in the secondary michael@0: // routing table to make the gateway reachable. Host route takes the max michael@0: // prefix and gateway address 'any'. michael@0: let route = { michael@0: ip: gateways[i], michael@0: prefix: isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH, michael@0: gateway: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY michael@0: }; michael@0: gNetworkService.addSecondaryRoute(network.name, route); michael@0: // Now we can add the default route through gateway. Default route takes the michael@0: // min prefix and destination ip 'any'. michael@0: route.ip = isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY; michael@0: route.prefix = 0; michael@0: route.gateway = gateways[i]; michael@0: gNetworkService.addSecondaryRoute(network.name, route); michael@0: } michael@0: }, michael@0: michael@0: removeSecondaryDefaultRoute: function(network) { michael@0: let gateways = network.getGateways(); michael@0: for (let i = 0; i < gateways.length; i++) { michael@0: let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false; michael@0: // Remove both default route and host route. michael@0: let route = { michael@0: ip: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY, michael@0: prefix: 0, michael@0: gateway: gateways[i] michael@0: }; michael@0: gNetworkService.removeSecondaryRoute(network.name, route); michael@0: michael@0: route.ip = gateways[i]; michael@0: route.prefix = isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH; michael@0: route.gateway = isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY; michael@0: gNetworkService.removeSecondaryRoute(network.name, route); michael@0: } michael@0: }, michael@0: #endif // MOZ_B2G_RIL michael@0: michael@0: /** michael@0: * Determine the active interface and configure it. michael@0: */ michael@0: setAndConfigureActive: function() { michael@0: debug("Evaluating whether active network needs to be changed."); michael@0: let oldActive = this.active; michael@0: michael@0: if (this._overriddenActive) { michael@0: debug("We have an override for the active network: " + michael@0: this._overriddenActive.name); michael@0: // The override was just set, so reconfigure the network. michael@0: if (this.active != this._overriddenActive) { michael@0: this.active = this._overriddenActive; michael@0: gNetworkService.setDefaultRouteAndDNS(this.active, oldActive); michael@0: Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // The active network is already our preferred type. michael@0: if (this.active && michael@0: this.active.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED && michael@0: this.active.type == this._preferredNetworkType) { michael@0: debug("Active network is already our preferred type."); michael@0: gNetworkService.setDefaultRouteAndDNS(this.active, oldActive); michael@0: return; michael@0: } michael@0: michael@0: // Find a suitable network interface to activate. michael@0: this.active = null; michael@0: #ifdef MOZ_B2G_RIL michael@0: let defaultDataNetwork; michael@0: #endif michael@0: for each (let network in this.networkInterfaces) { michael@0: if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { michael@0: continue; michael@0: } michael@0: #ifdef MOZ_B2G_RIL michael@0: if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { michael@0: defaultDataNetwork = network; michael@0: } michael@0: #endif michael@0: this.active = network; michael@0: if (network.type == this.preferredNetworkType) { michael@0: debug("Found our preferred type of network: " + network.name); michael@0: break; michael@0: } michael@0: } michael@0: if (this.active) { michael@0: #ifdef MOZ_B2G_RIL michael@0: // Give higher priority to default data APN than seconary APN. michael@0: // If default data APN is not connected, we still set default route michael@0: // and DNS on seconary APN. michael@0: if (defaultDataNetwork && michael@0: this.isNetworkTypeSecondaryMobile(this.active.type) && michael@0: this.active.type != this.preferredNetworkType) { michael@0: this.active = defaultDataNetwork; michael@0: } michael@0: // Don't set default route on secondary APN michael@0: if (this.isNetworkTypeSecondaryMobile(this.active.type)) { michael@0: gNetworkService.setDNS(this.active); michael@0: } else { michael@0: #endif // MOZ_B2G_RIL michael@0: gNetworkService.setDefaultRouteAndDNS(this.active, oldActive); michael@0: #ifdef MOZ_B2G_RIL michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: if (this.active != oldActive) { michael@0: Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null); michael@0: } michael@0: michael@0: if (this._manageOfflineStatus) { michael@0: Services.io.offline = !this.active; michael@0: } michael@0: }, michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: resolveHostname: function(hosts) { michael@0: let retval = []; michael@0: michael@0: for (let hostname of hosts) { michael@0: // Sanity check for null, undefined and empty string... etc. michael@0: if (!hostname) { michael@0: continue; michael@0: } michael@0: michael@0: try { michael@0: let uri = Services.io.newURI(hostname, null, null); michael@0: hostname = uri.host; michael@0: } catch (e) {} michael@0: michael@0: // An extra check for hostnames that cannot be made by newURI(...). michael@0: // For example, an IP address like "10.1.1.1". michael@0: if (hostname.match(this.REGEXP_IPV4) || michael@0: hostname.match(this.REGEXP_IPV6)) { michael@0: retval.push(hostname); michael@0: continue; michael@0: } michael@0: michael@0: try { michael@0: let hostnameIps = gDNSService.resolve(hostname, 0); michael@0: while (hostnameIps.hasMore()) { michael@0: retval.push(hostnameIps.getNextAddrAsString()); michael@0: debug("Found IP at: " + JSON.stringify(retval)); michael@0: } michael@0: } catch (e) {} michael@0: } michael@0: michael@0: return retval; michael@0: }, michael@0: #endif michael@0: michael@0: convertConnectionType: function(network) { michael@0: // If there is internal interface change (e.g., MOBILE_MMS, MOBILE_SUPL), michael@0: // the function will return null so that it won't trigger type change event michael@0: // in NetworkInformation API. michael@0: if (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && michael@0: network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { michael@0: return null; michael@0: } michael@0: michael@0: if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED) { michael@0: return CONNECTION_TYPE_NONE; michael@0: } michael@0: michael@0: switch (network.type) { michael@0: case Ci.nsINetworkInterface.NETWORK_TYPE_WIFI: michael@0: return CONNECTION_TYPE_WIFI; michael@0: case Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE: michael@0: return CONNECTION_TYPE_CULLULAR; michael@0: } michael@0: }, michael@0: michael@0: // nsISettingsServiceCallback michael@0: michael@0: tetheringSettings: {}, michael@0: michael@0: initTetheringSettings: function() { michael@0: this.tetheringSettings[SETTINGS_USB_ENABLED] = false; michael@0: this.tetheringSettings[SETTINGS_USB_IP] = DEFAULT_USB_IP; michael@0: this.tetheringSettings[SETTINGS_USB_PREFIX] = DEFAULT_USB_PREFIX; michael@0: this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP; michael@0: this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP; michael@0: this.tetheringSettings[SETTINGS_USB_DNS1] = DEFAULT_DNS1; michael@0: this.tetheringSettings[SETTINGS_USB_DNS2] = DEFAULT_DNS2; michael@0: michael@0: this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP; michael@0: this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP; michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: this.tetheringSettings[SETTINGS_DUN_REQUIRED] = michael@0: libcutils.property_get("ro.tethering.dun_required") === "1"; michael@0: #endif michael@0: }, michael@0: michael@0: _requestCount: 0, michael@0: michael@0: handle: function(aName, aResult) { michael@0: switch(aName) { michael@0: case SETTINGS_USB_ENABLED: michael@0: this._oldUsbTetheringEnabledState = this.tetheringSettings[SETTINGS_USB_ENABLED]; michael@0: case SETTINGS_USB_IP: michael@0: case SETTINGS_USB_PREFIX: michael@0: case SETTINGS_USB_DHCPSERVER_STARTIP: michael@0: case SETTINGS_USB_DHCPSERVER_ENDIP: michael@0: case SETTINGS_USB_DNS1: michael@0: case SETTINGS_USB_DNS2: michael@0: case SETTINGS_WIFI_DHCPSERVER_STARTIP: michael@0: case SETTINGS_WIFI_DHCPSERVER_ENDIP: michael@0: if (aResult !== null) { michael@0: this.tetheringSettings[aName] = aResult; michael@0: } michael@0: debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]); michael@0: let index = this._usbTetheringSettingsToRead.indexOf(aName); michael@0: michael@0: if (index != -1) { michael@0: this._usbTetheringSettingsToRead.splice(index, 1); michael@0: } michael@0: michael@0: if (this._usbTetheringSettingsToRead.length) { michael@0: debug("We haven't read completely the usb Tethering data from settings db."); michael@0: break; michael@0: } michael@0: michael@0: if (this._oldUsbTetheringEnabledState === this.tetheringSettings[SETTINGS_USB_ENABLED]) { michael@0: debug("No changes for SETTINGS_USB_ENABLED flag. Nothing to do."); michael@0: break; michael@0: } michael@0: michael@0: this._requestCount++; michael@0: if (this._requestCount === 1) { michael@0: this.handleUSBTetheringToggle(aResult); michael@0: } michael@0: break; michael@0: }; michael@0: }, michael@0: michael@0: handleError: function(aErrorMessage) { michael@0: debug("There was an error while reading Tethering settings."); michael@0: this.tetheringSettings = {}; michael@0: this.tetheringSettings[SETTINGS_USB_ENABLED] = false; michael@0: }, michael@0: michael@0: getNetworkInterface: function(type) { michael@0: for each (let network in this.networkInterfaces) { michael@0: if (network.type == type) { michael@0: return network; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: _usbTetheringAction: TETHERING_STATE_IDLE, michael@0: michael@0: _usbTetheringSettingsToRead: [], michael@0: michael@0: _oldUsbTetheringEnabledState: null, michael@0: michael@0: // External and internal interface name. michael@0: _tetheringInterface: null, michael@0: michael@0: handleLastRequest: function() { michael@0: let count = this._requestCount; michael@0: this._requestCount = 0; michael@0: michael@0: if (count === 1) { michael@0: if (this.wantConnectionEvent) { michael@0: if (this.tetheringSettings[SETTINGS_USB_ENABLED]) { michael@0: this.wantConnectionEvent.call(this); michael@0: } michael@0: this.wantConnectionEvent = null; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (count > 1) { michael@0: this.handleUSBTetheringToggle(this.tetheringSettings[SETTINGS_USB_ENABLED]); michael@0: this.wantConnectionEvent = null; michael@0: } michael@0: }, michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: dunConnectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), michael@0: /** michael@0: * Callback when dun connection fails to connect within timeout. michael@0: */ michael@0: onDunConnectTimerTimeout: function() { michael@0: while (this._pendingTetheringRequests.length > 0) { michael@0: debug("onDunConnectTimerTimeout: callback without network info."); michael@0: let callback = this._pendingTetheringRequests.shift(); michael@0: if (typeof callback === 'function') { michael@0: callback(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: dunRetryTimes: 0, michael@0: dunRetryTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), michael@0: setupDunConnection: function() { michael@0: this.dunRetryTimer.cancel(); michael@0: let ril = this.mRil.getRadioInterface(this.gDataDefaultServiceId); michael@0: michael@0: if (ril.rilContext && ril.rilContext.data && michael@0: ril.rilContext.data.state === "registered") { michael@0: this.dunRetryTimes = 0; michael@0: ril.setupDataCallByType("dun"); michael@0: this.dunConnectTimer.cancel(); michael@0: this.dunConnectTimer. michael@0: initWithCallback(this.onDunConnectTimerTimeout.bind(this), michael@0: MOBILE_DUN_CONNECT_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: michael@0: if (this.dunRetryTimes++ >= this.MOBILE_DUN_MAX_RETRIES) { michael@0: debug("setupDunConnection: max retries reached."); michael@0: this.dunRetryTimes = 0; michael@0: // same as dun connect timeout. michael@0: this.onDunConnectTimerTimeout(); michael@0: return; michael@0: } michael@0: michael@0: debug("Data not ready, retry dun after " + MOBILE_DUN_RETRY_INTERVAL + " ms."); michael@0: this.dunRetryTimer. michael@0: initWithCallback(this.setupDunConnection.bind(this), michael@0: MOBILE_DUN_RETRY_INTERVAL, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: _pendingTetheringRequests: [], michael@0: _dunActiveUsers: 0, michael@0: handleDunConnection: function(enable, callback) { michael@0: debug("handleDunConnection: " + enable); michael@0: let dun = this.getNetworkInterface( michael@0: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN); michael@0: michael@0: if (!enable) { michael@0: this._dunActiveUsers--; michael@0: if (this._dunActiveUsers > 0) { michael@0: debug("Dun still needed by others, do not disconnect.") michael@0: return; michael@0: } michael@0: michael@0: this.dunRetryTimes = 0; michael@0: this.dunRetryTimer.cancel(); michael@0: this.dunConnectTimer.cancel(); michael@0: this._pendingTetheringRequests = []; michael@0: michael@0: if (dun && (dun.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED)) { michael@0: this.mRil.getRadioInterface(this.gDataDefaultServiceId) michael@0: .deactivateDataCallByType("dun"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this._dunActiveUsers++; michael@0: if (!dun || (dun.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED)) { michael@0: debug("DUN data call inactive, setup dun data call!") michael@0: this._pendingTetheringRequests.push(callback); michael@0: this.dunRetryTimes = 0; michael@0: this.setupDunConnection(); michael@0: michael@0: return; michael@0: } michael@0: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = dun.name; michael@0: callback(dun); michael@0: }, michael@0: #endif michael@0: michael@0: handleUSBTetheringToggle: function(enable) { michael@0: debug("handleUSBTetheringToggle: " + enable); michael@0: if (enable && michael@0: (this._usbTetheringAction === TETHERING_STATE_ONGOING || michael@0: this._usbTetheringAction === TETHERING_STATE_ACTIVE)) { michael@0: debug("Usb tethering already connecting/connected."); michael@0: return; michael@0: } michael@0: michael@0: if (!enable && michael@0: this._usbTetheringAction === TETHERING_STATE_IDLE) { michael@0: debug("Usb tethering already disconnected."); michael@0: return; michael@0: } michael@0: michael@0: if (!enable) { michael@0: this.tetheringSettings[SETTINGS_USB_ENABLED] = false; michael@0: gNetworkService.enableUsbRndis(false, this.enableUsbRndisResult.bind(this)); michael@0: return; michael@0: } michael@0: michael@0: this.tetheringSettings[SETTINGS_USB_ENABLED] = true; michael@0: this._usbTetheringAction = TETHERING_STATE_ONGOING; michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { michael@0: this.handleDunConnection(true, function(network) { michael@0: if (!network){ michael@0: this.usbTetheringResultReport("Dun connection failed"); michael@0: return; michael@0: } michael@0: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = network.name; michael@0: gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this)); michael@0: }.bind(this)); michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: if (this.active) { michael@0: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = this.active.name; michael@0: } else { michael@0: let mobile = this.getNetworkInterface(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE); michael@0: if (mobile) { michael@0: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = mobile.name; michael@0: } michael@0: } michael@0: gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this)); michael@0: }, michael@0: michael@0: getUSBTetheringParameters: function(enable, tetheringinterface) { michael@0: let interfaceIp; michael@0: let prefix; michael@0: let wifiDhcpStartIp; michael@0: let wifiDhcpEndIp; michael@0: let usbDhcpStartIp; michael@0: let usbDhcpEndIp; michael@0: let dns1; michael@0: let dns2; michael@0: let internalInterface = tetheringinterface.internalInterface; michael@0: let externalInterface = tetheringinterface.externalInterface; michael@0: michael@0: interfaceIp = this.tetheringSettings[SETTINGS_USB_IP]; michael@0: prefix = this.tetheringSettings[SETTINGS_USB_PREFIX]; michael@0: wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP]; michael@0: wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP]; michael@0: usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP]; michael@0: usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP]; michael@0: dns1 = this.tetheringSettings[SETTINGS_USB_DNS1]; michael@0: dns2 = this.tetheringSettings[SETTINGS_USB_DNS2]; michael@0: michael@0: // Using the default values here until application support these settings. michael@0: if (interfaceIp == "" || prefix == "" || michael@0: wifiDhcpStartIp == "" || wifiDhcpEndIp == "" || michael@0: usbDhcpStartIp == "" || usbDhcpEndIp == "") { michael@0: debug("Invalid subnet information."); michael@0: return null; michael@0: } michael@0: michael@0: return { michael@0: ifname: internalInterface, michael@0: ip: interfaceIp, michael@0: prefix: prefix, michael@0: wifiStartIp: wifiDhcpStartIp, michael@0: wifiEndIp: wifiDhcpEndIp, michael@0: usbStartIp: usbDhcpStartIp, michael@0: usbEndIp: usbDhcpEndIp, michael@0: dns1: dns1, michael@0: dns2: dns2, michael@0: internalIfname: internalInterface, michael@0: externalIfname: externalInterface, michael@0: enable: enable, michael@0: link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN michael@0: }; michael@0: }, michael@0: michael@0: notifyError: function(resetSettings, callback, msg) { michael@0: if (resetSettings) { michael@0: let settingsLock = gSettingsService.createLock(); michael@0: // Disable wifi tethering with a useful error message for the user. michael@0: settingsLock.set("tethering.wifi.enabled", false, null, msg); michael@0: } michael@0: michael@0: debug("setWifiTethering: " + (msg ? msg : "success")); michael@0: michael@0: if (callback) { michael@0: callback.wifiTetheringEnabledChange(msg); michael@0: } michael@0: }, michael@0: michael@0: enableWifiTethering: function(enable, config, callback) { michael@0: // Fill in config's required fields. michael@0: config.ifname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface; michael@0: config.internalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface; michael@0: config.externalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface; michael@0: michael@0: gNetworkService.setWifiTethering(enable, config, (function(error) { michael@0: #ifdef MOZ_B2G_RIL michael@0: // Disconnect dun on error or when wifi tethering is disabled. michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] && michael@0: (!enable || error)) { michael@0: this.handleDunConnection(false); michael@0: } michael@0: #endif michael@0: let resetSettings = error; michael@0: this.notifyError(resetSettings, callback, error); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: // Enable/disable WiFi tethering by sending commands to netd. michael@0: setWifiTethering: function(enable, network, config, callback) { michael@0: debug("setWifiTethering: " + enable); michael@0: if (!network) { michael@0: this.notifyError(true, callback, "invalid network information"); michael@0: return; michael@0: } michael@0: michael@0: if (!config) { michael@0: this.notifyError(true, callback, "invalid configuration"); michael@0: return; michael@0: } michael@0: michael@0: if (!enable) { michael@0: this.enableWifiTethering(false, config, callback); michael@0: return; michael@0: } michael@0: michael@0: this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface = network.name; michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { michael@0: this.handleDunConnection(true, function(config, callback, network) { michael@0: if (!network) { michael@0: this.notifyError(true, callback, "Dun connection failed"); michael@0: return; michael@0: } michael@0: this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = network.name; michael@0: this.enableWifiTethering(true, config, callback); michael@0: }.bind(this, config, callback)); michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: let mobile = this.getNetworkInterface(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE); michael@0: // Update the real interface name michael@0: if (mobile) { michael@0: this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = mobile.name; michael@0: } michael@0: michael@0: this.enableWifiTethering(true, config, callback); michael@0: }, michael@0: michael@0: // Enable/disable USB tethering by sending commands to netd. michael@0: setUSBTethering: function(enable, tetheringInterface, callback) { michael@0: let params = this.getUSBTetheringParameters(enable, tetheringInterface); michael@0: michael@0: if (params === null) { michael@0: gNetworkService.enableUsbRndis(false, function() { michael@0: this.usbTetheringResultReport("Invalid parameters"); michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: gNetworkService.setUSBTethering(enable, params, callback); michael@0: }, michael@0: michael@0: getUsbInterface: function() { michael@0: // Find the rndis interface. michael@0: for (let i = 0; i < this.possibleInterface.length; i++) { michael@0: try { michael@0: let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" + michael@0: this.possibleInterface[i]); michael@0: if (file.exists()) { michael@0: return this.possibleInterface[i]; michael@0: } michael@0: } catch (e) { michael@0: debug("Not " + this.possibleInterface[i] + " interface."); michael@0: } michael@0: } michael@0: debug("Can't find rndis interface in possible lists."); michael@0: return DEFAULT_USB_INTERFACE_NAME; michael@0: }, michael@0: michael@0: enableUsbRndisResult: function(success, enable) { michael@0: if (success) { michael@0: // If enable is false, don't find usb interface cause it is already down, michael@0: // just use the internal interface in settings. michael@0: if (enable) { michael@0: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface = this.getUsbInterface(); michael@0: } michael@0: this.setUSBTethering(enable, michael@0: this._tetheringInterface[TETHERING_TYPE_USB], michael@0: this.usbTetheringResultReport.bind(this)); michael@0: } else { michael@0: this.usbTetheringResultReport("Failed to set usb function"); michael@0: throw new Error("failed to set USB Function to adb"); michael@0: } michael@0: }, michael@0: michael@0: usbTetheringResultReport: function(error) { michael@0: let settingsLock = gSettingsService.createLock(); michael@0: michael@0: // Disable tethering settings when fail to enable it. michael@0: if (error) { michael@0: this.tetheringSettings[SETTINGS_USB_ENABLED] = false; michael@0: settingsLock.set("tethering.usb.enabled", false, null); michael@0: // Skip others request when we found an error. michael@0: this._requestCount = 0; michael@0: this._usbTetheringAction = TETHERING_STATE_IDLE; michael@0: #ifdef MOZ_B2G_RIL michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { michael@0: this.handleDunConnection(false); michael@0: } michael@0: #endif michael@0: } else { michael@0: if (this.tetheringSettings[SETTINGS_USB_ENABLED]) { michael@0: this._usbTetheringAction = TETHERING_STATE_ACTIVE; michael@0: } else { michael@0: this._usbTetheringAction = TETHERING_STATE_IDLE; michael@0: #ifdef MOZ_B2G_RIL michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { michael@0: this.handleDunConnection(false); michael@0: } michael@0: #endif michael@0: } michael@0: this.handleLastRequest(); michael@0: } michael@0: }, michael@0: michael@0: onConnectionChangedReport: function(success, externalIfname) { michael@0: debug("onConnectionChangedReport result: success " + success); michael@0: michael@0: if (success) { michael@0: // Update the external interface. michael@0: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = externalIfname; michael@0: debug("Change the interface name to " + externalIfname); michael@0: } michael@0: }, michael@0: michael@0: onConnectionChanged: function(network) { michael@0: if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { michael@0: debug("We are only interested in CONNECTED event"); michael@0: return; michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: // We can not use network.type only to check if it's dun, cause if it is michael@0: // shared with default, the returned type would always be default, see bug michael@0: // 939046. In most cases, if dun is required, it should not be shared with michael@0: // default. michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] && michael@0: (network.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN || michael@0: this.mRil.getRadioInterface(this.gDataDefaultServiceId) michael@0: .getDataCallStateByType("dun") === michael@0: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED)) { michael@0: this.dunConnectTimer.cancel(); michael@0: debug("DUN data call connected, process callbacks."); michael@0: while (this._pendingTetheringRequests.length > 0) { michael@0: let callback = this._pendingTetheringRequests.shift(); michael@0: if (typeof callback === 'function') { michael@0: callback(network); michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: if (!this.tetheringSettings[SETTINGS_USB_ENABLED]) { michael@0: debug("Usb tethering settings is not enabled"); michael@0: return; michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] && michael@0: network.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN && michael@0: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface === michael@0: network.name) { michael@0: debug("Dun required and dun interface is the same"); michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: if (this._tetheringInterface[TETHERING_TYPE_USB].externalInterface === michael@0: this.active.name) { michael@0: debug("The active interface is the same"); michael@0: return; michael@0: } michael@0: michael@0: let previous = { michael@0: internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface, michael@0: externalIfname: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface michael@0: }; michael@0: michael@0: let current = { michael@0: internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface, michael@0: externalIfname: network.name michael@0: }; michael@0: michael@0: let callback = (function() { michael@0: // Update external network interface. michael@0: debug("Update upstream interface to " + network.name); michael@0: gNetworkService.updateUpStream(previous, current, this.onConnectionChangedReport.bind(this)); michael@0: }).bind(this); michael@0: michael@0: if (this._usbTetheringAction === TETHERING_STATE_ONGOING) { michael@0: debug("Postpone the event and handle it when state is idle."); michael@0: this.wantConnectionEvent = callback; michael@0: return; michael@0: } michael@0: this.wantConnectionEvent = null; michael@0: michael@0: callback.call(this); michael@0: } michael@0: }; michael@0: michael@0: let CaptivePortalDetectionHelper = (function() { michael@0: michael@0: const EVENT_CONNECT = "Connect"; michael@0: const EVENT_DISCONNECT = "Disconnect"; michael@0: let _ongoingInterface = null; michael@0: let _available = ("nsICaptivePortalDetector" in Ci); michael@0: let getService = function() { michael@0: return Cc['@mozilla.org/toolkit/captive-detector;1'] michael@0: .getService(Ci.nsICaptivePortalDetector); michael@0: }; michael@0: michael@0: let _performDetection = function(interfaceName, callback) { michael@0: let capService = getService(); michael@0: let capCallback = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsICaptivePortalCallback]), michael@0: prepare: function() { michael@0: capService.finishPreparation(interfaceName); michael@0: }, michael@0: complete: function(success) { michael@0: _ongoingInterface = null; michael@0: callback(success); michael@0: } michael@0: }; michael@0: michael@0: // Abort any unfinished captive portal detection. michael@0: if (_ongoingInterface != null) { michael@0: capService.abort(_ongoingInterface); michael@0: _ongoingInterface = null; michael@0: } michael@0: try { michael@0: capService.checkCaptivePortal(interfaceName, capCallback); michael@0: _ongoingInterface = interfaceName; michael@0: } catch (e) { michael@0: debug('Fail to detect captive portal due to: ' + e.message); michael@0: } michael@0: }; michael@0: michael@0: let _abort = function(interfaceName) { michael@0: if (_ongoingInterface !== interfaceName) { michael@0: return; michael@0: } michael@0: michael@0: let capService = getService(); michael@0: capService.abort(_ongoingInterface); michael@0: _ongoingInterface = null; michael@0: }; michael@0: michael@0: return { michael@0: EVENT_CONNECT: EVENT_CONNECT, michael@0: EVENT_DISCONNECT: EVENT_DISCONNECT, michael@0: notify: function(eventType, network) { michael@0: switch (eventType) { michael@0: case EVENT_CONNECT: michael@0: // perform captive portal detection on wifi interface michael@0: if (_available && network && michael@0: network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: _performDetection(network.name, function() { michael@0: // TODO: bug 837600 michael@0: // We can disconnect wifi in here if user abort the login procedure. michael@0: }); michael@0: } michael@0: michael@0: break; michael@0: case EVENT_DISCONNECT: michael@0: if (_available && michael@0: network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: _abort(network.name); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: }()); michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: XPCOMUtils.defineLazyServiceGetter(NetworkManager.prototype, "mRil", michael@0: "@mozilla.org/ril;1", michael@0: "nsIRadioInterfaceLayer"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(NetworkManager.prototype, michael@0: "gDataDefaultServiceId", function() { michael@0: try { michael@0: return Services.prefs.getIntPref(PREF_DATA_DEFAULT_SERVICE_ID); michael@0: } catch(e) {} michael@0: michael@0: return 0; michael@0: }); michael@0: #endif michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkManager]); michael@0: michael@0: michael@0: let debug; michael@0: if (DEBUG) { michael@0: debug = function(s) { michael@0: dump("-*- NetworkManager: " + s + "\n"); michael@0: }; michael@0: } else { michael@0: debug = function(s) {}; michael@0: }