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