dom/system/gonk/NetworkManager.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/system/gonk/NetworkManager.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1342 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.11 +
    1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.13 +Cu.import("resource://gre/modules/Services.jsm");
    1.14 +Cu.import("resource://gre/modules/FileUtils.jsm");
    1.15 +Cu.import("resource://gre/modules/systemlibs.js");
    1.16 +
    1.17 +const NETWORKMANAGER_CONTRACTID = "@mozilla.org/network/manager;1";
    1.18 +const NETWORKMANAGER_CID =
    1.19 +  Components.ID("{33901e46-33b8-11e1-9869-f46d04d25bcc}");
    1.20 +
    1.21 +const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
    1.22 +
    1.23 +XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
    1.24 +                                   "@mozilla.org/settingsService;1",
    1.25 +                                   "nsISettingsService");
    1.26 +XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
    1.27 +  return Cc["@mozilla.org/parentprocessmessagemanager;1"]
    1.28 +         .getService(Ci.nsIMessageBroadcaster);
    1.29 +});
    1.30 +
    1.31 +XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
    1.32 +                                   "@mozilla.org/network/dns-service;1",
    1.33 +                                   "nsIDNSService");
    1.34 +
    1.35 +XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
    1.36 +                                   "@mozilla.org/network/service;1",
    1.37 +                                   "nsINetworkService");
    1.38 +
    1.39 +const TOPIC_INTERFACE_STATE_CHANGED  = "network-interface-state-changed";
    1.40 +const TOPIC_INTERFACE_REGISTERED     = "network-interface-registered";
    1.41 +const TOPIC_INTERFACE_UNREGISTERED   = "network-interface-unregistered";
    1.42 +const TOPIC_ACTIVE_CHANGED           = "network-active-changed";
    1.43 +const TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
    1.44 +const TOPIC_PREF_CHANGED             = "nsPref:changed";
    1.45 +const TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
    1.46 +const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
    1.47 +const PREF_MANAGE_OFFLINE_STATUS     = "network.gonk.manage-offline-status";
    1.48 +
    1.49 +const POSSIBLE_USB_INTERFACE_NAME = "rndis0,usb0";
    1.50 +const DEFAULT_USB_INTERFACE_NAME  = "rndis0";
    1.51 +const DEFAULT_3G_INTERFACE_NAME   = "rmnet0";
    1.52 +const DEFAULT_WIFI_INTERFACE_NAME = "wlan0";
    1.53 +
    1.54 +// The kernel's proc entry for network lists.
    1.55 +const KERNEL_NETWORK_ENTRY = "/sys/class/net";
    1.56 +
    1.57 +const TETHERING_TYPE_WIFI = "WiFi";
    1.58 +const TETHERING_TYPE_USB  = "USB";
    1.59 +
    1.60 +const WIFI_FIRMWARE_AP            = "AP";
    1.61 +const WIFI_FIRMWARE_STATION       = "STA";
    1.62 +const WIFI_SECURITY_TYPE_NONE     = "open";
    1.63 +const WIFI_SECURITY_TYPE_WPA_PSK  = "wpa-psk";
    1.64 +const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
    1.65 +const WIFI_CTRL_INTERFACE         = "wl0.1";
    1.66 +
    1.67 +const NETWORK_INTERFACE_UP   = "up";
    1.68 +const NETWORK_INTERFACE_DOWN = "down";
    1.69 +
    1.70 +const TETHERING_STATE_ONGOING = "ongoing";
    1.71 +const TETHERING_STATE_IDLE    = "idle";
    1.72 +const TETHERING_STATE_ACTIVE  = "active";
    1.73 +
    1.74 +// Settings DB path for USB tethering.
    1.75 +const SETTINGS_USB_ENABLED             = "tethering.usb.enabled";
    1.76 +const SETTINGS_USB_IP                  = "tethering.usb.ip";
    1.77 +const SETTINGS_USB_PREFIX              = "tethering.usb.prefix";
    1.78 +const SETTINGS_USB_DHCPSERVER_STARTIP  = "tethering.usb.dhcpserver.startip";
    1.79 +const SETTINGS_USB_DHCPSERVER_ENDIP    = "tethering.usb.dhcpserver.endip";
    1.80 +const SETTINGS_USB_DNS1                = "tethering.usb.dns1";
    1.81 +const SETTINGS_USB_DNS2                = "tethering.usb.dns2";
    1.82 +
    1.83 +// Settings DB path for WIFI tethering.
    1.84 +const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
    1.85 +const SETTINGS_WIFI_DHCPSERVER_ENDIP   = "tethering.wifi.dhcpserver.endip";
    1.86 +
    1.87 +// Settings DB patch for dun required setting.
    1.88 +const SETTINGS_DUN_REQUIRED = "tethering.dun.required";
    1.89 +
    1.90 +// Default value for USB tethering.
    1.91 +const DEFAULT_USB_IP                   = "192.168.0.1";
    1.92 +const DEFAULT_USB_PREFIX               = "24";
    1.93 +const DEFAULT_USB_DHCPSERVER_STARTIP   = "192.168.0.10";
    1.94 +const DEFAULT_USB_DHCPSERVER_ENDIP     = "192.168.0.30";
    1.95 +
    1.96 +const DEFAULT_DNS1                     = "8.8.8.8";
    1.97 +const DEFAULT_DNS2                     = "8.8.4.4";
    1.98 +
    1.99 +const DEFAULT_WIFI_DHCPSERVER_STARTIP  = "192.168.1.10";
   1.100 +const DEFAULT_WIFI_DHCPSERVER_ENDIP    = "192.168.1.30";
   1.101 +
   1.102 +const IPV4_ADDRESS_ANY                 = "0.0.0.0";
   1.103 +const IPV6_ADDRESS_ANY                 = "::0";
   1.104 +
   1.105 +const IPV4_MAX_PREFIX_LENGTH           = 32;
   1.106 +const IPV6_MAX_PREFIX_LENGTH           = 128;
   1.107 +
   1.108 +const PREF_DATA_DEFAULT_SERVICE_ID     = "ril.data.defaultServiceId";
   1.109 +const MOBILE_DUN_CONNECT_TIMEOUT       = 30000;
   1.110 +const MOBILE_DUN_RETRY_INTERVAL        = 5000;
   1.111 +const MOBILE_DUN_MAX_RETRIES           = 5;
   1.112 +
   1.113 +// Connection Type for Network Information API
   1.114 +const CONNECTION_TYPE_CULLULAR  = 0;
   1.115 +const CONNECTION_TYPE_BLUETOOTH = 1;
   1.116 +const CONNECTION_TYPE_ETHERNET  = 2;
   1.117 +const CONNECTION_TYPE_WIFI      = 3;
   1.118 +const CONNECTION_TYPE_OTHER     = 4;
   1.119 +const CONNECTION_TYPE_NONE      = 5;
   1.120 +
   1.121 +let DEBUG = false;
   1.122 +
   1.123 +// Read debug setting from pref.
   1.124 +try {
   1.125 +  let debugPref = Services.prefs.getBoolPref("network.debugging.enabled");
   1.126 +  DEBUG = DEBUG || debugPref;
   1.127 +} catch (e) {}
   1.128 +
   1.129 +function defineLazyRegExp(obj, name, pattern) {
   1.130 +  obj.__defineGetter__(name, function() {
   1.131 +    delete obj[name];
   1.132 +    return obj[name] = new RegExp(pattern);
   1.133 +  });
   1.134 +}
   1.135 +
   1.136 +/**
   1.137 + * This component watches for network interfaces changing state and then
   1.138 + * adjusts routes etc. accordingly.
   1.139 + */
   1.140 +function NetworkManager() {
   1.141 +  this.networkInterfaces = {};
   1.142 +  Services.obs.addObserver(this, TOPIC_INTERFACE_STATE_CHANGED, true);
   1.143 +#ifdef MOZ_B2G_RIL
   1.144 +  Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, true);
   1.145 +  Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, true);
   1.146 +#endif
   1.147 +  Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
   1.148 +  Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false);
   1.149 +
   1.150 +  try {
   1.151 +    this._manageOfflineStatus =
   1.152 +      Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
   1.153 +  } catch(ex) {
   1.154 +    // Ignore.
   1.155 +  }
   1.156 +  Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false);
   1.157 +
   1.158 +  // Possible usb tethering interfaces for different gonk platform.
   1.159 +  this.possibleInterface = POSSIBLE_USB_INTERFACE_NAME.split(",");
   1.160 +
   1.161 +  // Default values for internal and external interfaces.
   1.162 +  this._tetheringInterface = Object.create(null);
   1.163 +  this._tetheringInterface[TETHERING_TYPE_USB] = {
   1.164 +    externalInterface: DEFAULT_3G_INTERFACE_NAME,
   1.165 +    internalInterface: DEFAULT_USB_INTERFACE_NAME
   1.166 +  };
   1.167 +  this._tetheringInterface[TETHERING_TYPE_WIFI] = {
   1.168 +    externalInterface: DEFAULT_3G_INTERFACE_NAME,
   1.169 +    internalInterface: DEFAULT_WIFI_INTERFACE_NAME
   1.170 +  };
   1.171 +
   1.172 +  this.initTetheringSettings();
   1.173 +
   1.174 +  let settingsLock = gSettingsService.createLock();
   1.175 +  // Read usb tethering data from settings DB.
   1.176 +  settingsLock.get(SETTINGS_USB_IP, this);
   1.177 +  settingsLock.get(SETTINGS_USB_PREFIX, this);
   1.178 +  settingsLock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
   1.179 +  settingsLock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
   1.180 +  settingsLock.get(SETTINGS_USB_DNS1, this);
   1.181 +  settingsLock.get(SETTINGS_USB_DNS2, this);
   1.182 +  settingsLock.get(SETTINGS_USB_ENABLED, this);
   1.183 +
   1.184 +  // Read wifi tethering data from settings DB.
   1.185 +  settingsLock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
   1.186 +  settingsLock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
   1.187 +
   1.188 +  this._usbTetheringSettingsToRead = [SETTINGS_USB_IP,
   1.189 +                                      SETTINGS_USB_PREFIX,
   1.190 +                                      SETTINGS_USB_DHCPSERVER_STARTIP,
   1.191 +                                      SETTINGS_USB_DHCPSERVER_ENDIP,
   1.192 +                                      SETTINGS_USB_DNS1,
   1.193 +                                      SETTINGS_USB_DNS2,
   1.194 +                                      SETTINGS_USB_ENABLED,
   1.195 +                                      SETTINGS_WIFI_DHCPSERVER_STARTIP,
   1.196 +                                      SETTINGS_WIFI_DHCPSERVER_ENDIP];
   1.197 +
   1.198 +  this.wantConnectionEvent = null;
   1.199 +  this.setAndConfigureActive();
   1.200 +
   1.201 +  ppmm.addMessageListener('NetworkInterfaceList:ListInterface', this);
   1.202 +
   1.203 +  // Used in resolveHostname().
   1.204 +  defineLazyRegExp(this, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
   1.205 +  defineLazyRegExp(this, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
   1.206 +}
   1.207 +NetworkManager.prototype = {
   1.208 +  classID:   NETWORKMANAGER_CID,
   1.209 +  classInfo: XPCOMUtils.generateCI({classID: NETWORKMANAGER_CID,
   1.210 +                                    contractID: NETWORKMANAGER_CONTRACTID,
   1.211 +                                    classDescription: "Network Manager",
   1.212 +                                    interfaces: [Ci.nsINetworkManager]}),
   1.213 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkManager,
   1.214 +                                         Ci.nsISupportsWeakReference,
   1.215 +                                         Ci.nsIObserver,
   1.216 +                                         Ci.nsISettingsServiceCallback]),
   1.217 +
   1.218 +  // nsIObserver
   1.219 +
   1.220 +  observe: function(subject, topic, data) {
   1.221 +    switch (topic) {
   1.222 +      case TOPIC_INTERFACE_STATE_CHANGED:
   1.223 +        let network = subject.QueryInterface(Ci.nsINetworkInterface);
   1.224 +        debug("Network " + network.name + " changed state to " + network.state);
   1.225 +        switch (network.state) {
   1.226 +          case Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED:
   1.227 +#ifdef MOZ_B2G_RIL
   1.228 +            // Add host route for data calls
   1.229 +            if (this.isNetworkTypeMobile(network.type)) {
   1.230 +              gNetworkService.removeHostRoutes(network.name);
   1.231 +              gNetworkService.addHostRoute(network);
   1.232 +            }
   1.233 +            // Add extra host route. For example, mms proxy or mmsc.
   1.234 +            this.setExtraHostRoute(network);
   1.235 +            // Dun type is a special case where we add the default route to a
   1.236 +            // secondary table.
   1.237 +            if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
   1.238 +              this.setSecondaryDefaultRoute(network);
   1.239 +            }
   1.240 +#endif
   1.241 +            // Remove pre-created default route and let setAndConfigureActive()
   1.242 +            // to set default route only on preferred network
   1.243 +            gNetworkService.removeDefaultRoute(network);
   1.244 +            this.setAndConfigureActive();
   1.245 +#ifdef MOZ_B2G_RIL
   1.246 +            // Update data connection when Wifi connected/disconnected
   1.247 +            if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
   1.248 +              for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
   1.249 +                this.mRil.getRadioInterface(i).updateRILNetworkInterface();
   1.250 +              }
   1.251 +            }
   1.252 +#endif
   1.253 +
   1.254 +            this.onConnectionChanged(network);
   1.255 +
   1.256 +            // Probing the public network accessibility after routing table is ready
   1.257 +            CaptivePortalDetectionHelper
   1.258 +              .notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active);
   1.259 +            break;
   1.260 +          case Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED:
   1.261 +#ifdef MOZ_B2G_RIL
   1.262 +            // Remove host route for data calls
   1.263 +            if (this.isNetworkTypeMobile(network.type)) {
   1.264 +              gNetworkService.removeHostRoute(network);
   1.265 +            }
   1.266 +            // Remove extra host route. For example, mms proxy or mmsc.
   1.267 +            this.removeExtraHostRoute(network);
   1.268 +            // Remove secondary default route for dun.
   1.269 +            if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
   1.270 +              this.removeSecondaryDefaultRoute(network);
   1.271 +            }
   1.272 +#endif
   1.273 +            // Remove routing table in /proc/net/route
   1.274 +            if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
   1.275 +              gNetworkService.resetRoutingTable(network);
   1.276 +#ifdef MOZ_B2G_RIL
   1.277 +            } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
   1.278 +              gNetworkService.removeDefaultRoute(network);
   1.279 +#endif
   1.280 +            }
   1.281 +
   1.282 +            // Abort ongoing captive portal detection on the wifi interface
   1.283 +            CaptivePortalDetectionHelper
   1.284 +              .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, network);
   1.285 +            this.setAndConfigureActive();
   1.286 +#ifdef MOZ_B2G_RIL
   1.287 +            // Update data connection when Wifi connected/disconnected
   1.288 +            if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
   1.289 +              for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
   1.290 +                this.mRil.getRadioInterface(i).updateRILNetworkInterface();
   1.291 +              }
   1.292 +            }
   1.293 +#endif
   1.294 +            break;
   1.295 +        }
   1.296 +#ifdef MOZ_B2G_RIL
   1.297 +        // Notify outer modules like MmsService to start the transaction after
   1.298 +        // the configuration of the network interface is done.
   1.299 +        Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED,
   1.300 +                                     this.convertConnectionType(network));
   1.301 +#endif
   1.302 +        break;
   1.303 +#ifdef MOZ_B2G_RIL
   1.304 +      case TOPIC_INTERFACE_REGISTERED:
   1.305 +        let regNetwork = subject.QueryInterface(Ci.nsINetworkInterface);
   1.306 +        // Add extra host route. For example, mms proxy or mmsc.
   1.307 +        this.setExtraHostRoute(regNetwork);
   1.308 +        // Dun type is a special case where we add the default route to a
   1.309 +        // secondary table.
   1.310 +        if (regNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
   1.311 +          this.setSecondaryDefaultRoute(regNetwork);
   1.312 +        }
   1.313 +        break;
   1.314 +      case TOPIC_INTERFACE_UNREGISTERED:
   1.315 +        let unregNetwork = subject.QueryInterface(Ci.nsINetworkInterface);
   1.316 +        // Remove extra host route. For example, mms proxy or mmsc.
   1.317 +        this.removeExtraHostRoute(unregNetwork);
   1.318 +        // Remove secondary default route for dun.
   1.319 +        if (unregNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
   1.320 +          this.removeSecondaryDefaultRoute(unregNetwork);
   1.321 +        }
   1.322 +        break;
   1.323 +#endif
   1.324 +      case TOPIC_MOZSETTINGS_CHANGED:
   1.325 +        let setting = JSON.parse(data);
   1.326 +        this.handle(setting.key, setting.value);
   1.327 +        break;
   1.328 +      case TOPIC_PREF_CHANGED:
   1.329 +        this._manageOfflineStatus =
   1.330 +          Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
   1.331 +        debug(PREF_MANAGE_OFFLINE_STATUS + " has changed to " + this._manageOfflineStatus);
   1.332 +        break;
   1.333 +      case TOPIC_XPCOM_SHUTDOWN:
   1.334 +        Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
   1.335 +        Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED);
   1.336 +#ifdef MOZ_B2G_RIL
   1.337 +        Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
   1.338 +        Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
   1.339 +#endif
   1.340 +        Services.obs.removeObserver(this, TOPIC_INTERFACE_STATE_CHANGED);
   1.341 +#ifdef MOZ_B2G_RIL
   1.342 +        this.dunConnectTimer.cancel();
   1.343 +        this.dunRetryTimer.cancel();
   1.344 +#endif
   1.345 +        break;
   1.346 +    }
   1.347 +  },
   1.348 +
   1.349 +  receiveMessage: function(aMsg) {
   1.350 +    switch (aMsg.name) {
   1.351 +      case "NetworkInterfaceList:ListInterface": {
   1.352 +#ifdef MOZ_B2G_RIL
   1.353 +        let excludeMms = aMsg.json.excludeMms;
   1.354 +        let excludeSupl = aMsg.json.excludeSupl;
   1.355 +        let excludeIms = aMsg.json.excludeIms;
   1.356 +        let excludeDun = aMsg.json.excludeDun;
   1.357 +#endif
   1.358 +        let interfaces = [];
   1.359 +
   1.360 +        for each (let i in this.networkInterfaces) {
   1.361 +#ifdef MOZ_B2G_RIL
   1.362 +          if ((i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS && excludeMms) ||
   1.363 +              (i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL && excludeSupl) ||
   1.364 +              (i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS && excludeIms) ||
   1.365 +              (i.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN && excludeDun)) {
   1.366 +            continue;
   1.367 +          }
   1.368 +#endif
   1.369 +          let ips = {};
   1.370 +          let prefixLengths = {};
   1.371 +          i.getAddresses(ips, prefixLengths);
   1.372 +
   1.373 +          interfaces.push({
   1.374 +            state: i.state,
   1.375 +            type: i.type,
   1.376 +            name: i.name,
   1.377 +            ips: ips.value,
   1.378 +            prefixLengths: prefixLengths.value,
   1.379 +            gateways: i.getGateways(),
   1.380 +            dnses: i.getDnses(),
   1.381 +            httpProxyHost: i.httpProxyHost,
   1.382 +            httpProxyPort: i.httpProxyPort
   1.383 +          });
   1.384 +        }
   1.385 +        return interfaces;
   1.386 +      }
   1.387 +    }
   1.388 +  },
   1.389 +
   1.390 +  // nsINetworkManager
   1.391 +
   1.392 +  registerNetworkInterface: function(network) {
   1.393 +    if (!(network instanceof Ci.nsINetworkInterface)) {
   1.394 +      throw Components.Exception("Argument must be nsINetworkInterface.",
   1.395 +                                 Cr.NS_ERROR_INVALID_ARG);
   1.396 +    }
   1.397 +    if (network.name in this.networkInterfaces) {
   1.398 +      throw Components.Exception("Network with that name already registered!",
   1.399 +                                 Cr.NS_ERROR_INVALID_ARG);
   1.400 +    }
   1.401 +    this.networkInterfaces[network.name] = network;
   1.402 +#ifdef MOZ_B2G_RIL
   1.403 +    // Add host route for data calls
   1.404 +    if (this.isNetworkTypeMobile(network.type)) {
   1.405 +      gNetworkService.addHostRoute(network);
   1.406 +    }
   1.407 +#endif
   1.408 +    // Remove pre-created default route and let setAndConfigureActive()
   1.409 +    // to set default route only on preferred network
   1.410 +    gNetworkService.removeDefaultRoute(network);
   1.411 +    this.setAndConfigureActive();
   1.412 +    Services.obs.notifyObservers(network, TOPIC_INTERFACE_REGISTERED, null);
   1.413 +    debug("Network '" + network.name + "' registered.");
   1.414 +  },
   1.415 +
   1.416 +  unregisterNetworkInterface: function(network) {
   1.417 +    if (!(network instanceof Ci.nsINetworkInterface)) {
   1.418 +      throw Components.Exception("Argument must be nsINetworkInterface.",
   1.419 +                                 Cr.NS_ERROR_INVALID_ARG);
   1.420 +    }
   1.421 +    if (!(network.name in this.networkInterfaces)) {
   1.422 +      throw Components.Exception("No network with that name registered.",
   1.423 +                                 Cr.NS_ERROR_INVALID_ARG);
   1.424 +    }
   1.425 +    delete this.networkInterfaces[network.name];
   1.426 +#ifdef MOZ_B2G_RIL
   1.427 +    // Remove host route for data calls
   1.428 +    if (this.isNetworkTypeMobile(network.type)) {
   1.429 +      gNetworkService.removeHostRoute(network);
   1.430 +    }
   1.431 +#endif
   1.432 +    this.setAndConfigureActive();
   1.433 +    Services.obs.notifyObservers(network, TOPIC_INTERFACE_UNREGISTERED, null);
   1.434 +    debug("Network '" + network.name + "' unregistered.");
   1.435 +  },
   1.436 +
   1.437 +  _manageOfflineStatus: true,
   1.438 +
   1.439 +  networkInterfaces: null,
   1.440 +
   1.441 +  _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE,
   1.442 +  get preferredNetworkType() {
   1.443 +    return this._preferredNetworkType;
   1.444 +  },
   1.445 +  set preferredNetworkType(val) {
   1.446 +#ifdef MOZ_B2G_RIL
   1.447 +    if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
   1.448 +         Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE].indexOf(val) == -1) {
   1.449 +#else
   1.450 +    if (val != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
   1.451 +#endif
   1.452 +      throw "Invalid network type";
   1.453 +    }
   1.454 +    this._preferredNetworkType = val;
   1.455 +  },
   1.456 +
   1.457 +  active: null,
   1.458 +  _overriddenActive: null,
   1.459 +
   1.460 +  overrideActive: function(network) {
   1.461 +#ifdef MOZ_B2G_RIL
   1.462 +    if (this.isNetworkTypeSecondaryMobile(network.type)) {
   1.463 +      throw "Invalid network type";
   1.464 +    }
   1.465 +#endif
   1.466 +    this._overriddenActive = network;
   1.467 +    this.setAndConfigureActive();
   1.468 +  },
   1.469 +
   1.470 +#ifdef MOZ_B2G_RIL
   1.471 +  isNetworkTypeSecondaryMobile: function(type) {
   1.472 +    return (type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS ||
   1.473 +            type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL ||
   1.474 +            type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS ||
   1.475 +            type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN);
   1.476 +  },
   1.477 +
   1.478 +  isNetworkTypeMobile: function(type) {
   1.479 +    return (type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
   1.480 +            this.isNetworkTypeSecondaryMobile(type));
   1.481 +  },
   1.482 +
   1.483 +  setExtraHostRoute: function(network) {
   1.484 +    if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) {
   1.485 +      if (!(network instanceof Ci.nsIRilNetworkInterface)) {
   1.486 +        debug("Network for MMS must be an instance of nsIRilNetworkInterface");
   1.487 +        return;
   1.488 +      }
   1.489 +
   1.490 +      network = network.QueryInterface(Ci.nsIRilNetworkInterface);
   1.491 +
   1.492 +      debug("Network '" + network.name + "' registered, " +
   1.493 +            "adding mmsproxy and/or mmsc route");
   1.494 +
   1.495 +      let hostToResolve = network.mmsProxy;
   1.496 +      // Workaround an xpconnect issue with undefined string objects.
   1.497 +      // See bug 808220
   1.498 +      if (!hostToResolve || hostToResolve === "undefined") {
   1.499 +        hostToResolve = network.mmsc;
   1.500 +      }
   1.501 +
   1.502 +      let mmsHosts = this.resolveHostname([hostToResolve]);
   1.503 +      if (mmsHosts.length == 0) {
   1.504 +        debug("No valid hostnames can be added. Stop adding host route.");
   1.505 +        return;
   1.506 +      }
   1.507 +
   1.508 +      gNetworkService.addHostRouteWithResolve(network, mmsHosts);
   1.509 +    }
   1.510 +  },
   1.511 +
   1.512 +  removeExtraHostRoute: function(network) {
   1.513 +    if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) {
   1.514 +      if (!(network instanceof Ci.nsIRilNetworkInterface)) {
   1.515 +        debug("Network for MMS must be an instance of nsIRilNetworkInterface");
   1.516 +        return;
   1.517 +      }
   1.518 +
   1.519 +      network = network.QueryInterface(Ci.nsIRilNetworkInterface);
   1.520 +
   1.521 +      debug("Network '" + network.name + "' unregistered, " +
   1.522 +            "removing mmsproxy and/or mmsc route");
   1.523 +
   1.524 +      let hostToResolve = network.mmsProxy;
   1.525 +      // Workaround an xpconnect issue with undefined string objects.
   1.526 +      // See bug 808220
   1.527 +      if (!hostToResolve || hostToResolve === "undefined") {
   1.528 +        hostToResolve = network.mmsc;
   1.529 +      }
   1.530 +
   1.531 +      let mmsHosts = this.resolveHostname([hostToResolve]);
   1.532 +      if (mmsHosts.length == 0) {
   1.533 +        debug("No valid hostnames can be removed. Stop removing host route.");
   1.534 +        return;
   1.535 +      }
   1.536 +
   1.537 +      gNetworkService.removeHostRouteWithResolve(network, mmsHosts);
   1.538 +    }
   1.539 +  },
   1.540 +
   1.541 +  setSecondaryDefaultRoute: function(network) {
   1.542 +    let gateways = network.getGateways();
   1.543 +    for (let i = 0; i < gateways.length; i++) {
   1.544 +      let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false;
   1.545 +      // First, we need to add a host route to the gateway in the secondary
   1.546 +      // routing table to make the gateway reachable. Host route takes the max
   1.547 +      // prefix and gateway address 'any'.
   1.548 +      let route = {
   1.549 +        ip: gateways[i],
   1.550 +        prefix: isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH,
   1.551 +        gateway: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY
   1.552 +      };
   1.553 +      gNetworkService.addSecondaryRoute(network.name, route);
   1.554 +      // Now we can add the default route through gateway. Default route takes the
   1.555 +      // min prefix and destination ip 'any'.
   1.556 +      route.ip = isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY;
   1.557 +      route.prefix = 0;
   1.558 +      route.gateway = gateways[i];
   1.559 +      gNetworkService.addSecondaryRoute(network.name, route);
   1.560 +    }
   1.561 +  },
   1.562 +
   1.563 +  removeSecondaryDefaultRoute: function(network) {
   1.564 +    let gateways = network.getGateways();
   1.565 +    for (let i = 0; i < gateways.length; i++) {
   1.566 +      let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false;
   1.567 +      // Remove both default route and host route.
   1.568 +      let route = {
   1.569 +        ip: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY,
   1.570 +        prefix: 0,
   1.571 +        gateway: gateways[i]
   1.572 +      };
   1.573 +      gNetworkService.removeSecondaryRoute(network.name, route);
   1.574 +
   1.575 +      route.ip = gateways[i];
   1.576 +      route.prefix = isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH;
   1.577 +      route.gateway = isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY;
   1.578 +      gNetworkService.removeSecondaryRoute(network.name, route);
   1.579 +    }
   1.580 +  },
   1.581 +#endif // MOZ_B2G_RIL
   1.582 +
   1.583 +  /**
   1.584 +   * Determine the active interface and configure it.
   1.585 +   */
   1.586 +  setAndConfigureActive: function() {
   1.587 +    debug("Evaluating whether active network needs to be changed.");
   1.588 +    let oldActive = this.active;
   1.589 +
   1.590 +    if (this._overriddenActive) {
   1.591 +      debug("We have an override for the active network: " +
   1.592 +            this._overriddenActive.name);
   1.593 +      // The override was just set, so reconfigure the network.
   1.594 +      if (this.active != this._overriddenActive) {
   1.595 +        this.active = this._overriddenActive;
   1.596 +        gNetworkService.setDefaultRouteAndDNS(this.active, oldActive);
   1.597 +        Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null);
   1.598 +      }
   1.599 +      return;
   1.600 +    }
   1.601 +
   1.602 +    // The active network is already our preferred type.
   1.603 +    if (this.active &&
   1.604 +        this.active.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED &&
   1.605 +        this.active.type == this._preferredNetworkType) {
   1.606 +      debug("Active network is already our preferred type.");
   1.607 +      gNetworkService.setDefaultRouteAndDNS(this.active, oldActive);
   1.608 +      return;
   1.609 +    }
   1.610 +
   1.611 +    // Find a suitable network interface to activate.
   1.612 +    this.active = null;
   1.613 +#ifdef MOZ_B2G_RIL
   1.614 +    let defaultDataNetwork;
   1.615 +#endif
   1.616 +    for each (let network in this.networkInterfaces) {
   1.617 +      if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
   1.618 +        continue;
   1.619 +      }
   1.620 +#ifdef MOZ_B2G_RIL
   1.621 +      if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
   1.622 +        defaultDataNetwork = network;
   1.623 +      }
   1.624 +#endif
   1.625 +      this.active = network;
   1.626 +      if (network.type == this.preferredNetworkType) {
   1.627 +        debug("Found our preferred type of network: " + network.name);
   1.628 +        break;
   1.629 +      }
   1.630 +    }
   1.631 +    if (this.active) {
   1.632 +#ifdef MOZ_B2G_RIL
   1.633 +      // Give higher priority to default data APN than seconary APN.
   1.634 +      // If default data APN is not connected, we still set default route
   1.635 +      // and DNS on seconary APN.
   1.636 +      if (defaultDataNetwork &&
   1.637 +          this.isNetworkTypeSecondaryMobile(this.active.type) &&
   1.638 +          this.active.type != this.preferredNetworkType) {
   1.639 +        this.active = defaultDataNetwork;
   1.640 +      }
   1.641 +      // Don't set default route on secondary APN
   1.642 +      if (this.isNetworkTypeSecondaryMobile(this.active.type)) {
   1.643 +        gNetworkService.setDNS(this.active);
   1.644 +      } else {
   1.645 +#endif // MOZ_B2G_RIL
   1.646 +        gNetworkService.setDefaultRouteAndDNS(this.active, oldActive);
   1.647 +#ifdef MOZ_B2G_RIL
   1.648 +      }
   1.649 +#endif
   1.650 +    }
   1.651 +
   1.652 +    if (this.active != oldActive) {
   1.653 +      Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null);
   1.654 +    }
   1.655 +
   1.656 +    if (this._manageOfflineStatus) {
   1.657 +      Services.io.offline = !this.active;
   1.658 +    }
   1.659 +  },
   1.660 +
   1.661 +#ifdef MOZ_B2G_RIL
   1.662 +  resolveHostname: function(hosts) {
   1.663 +    let retval = [];
   1.664 +
   1.665 +    for (let hostname of hosts) {
   1.666 +      // Sanity check for null, undefined and empty string... etc.
   1.667 +      if (!hostname) {
   1.668 +        continue;
   1.669 +      }
   1.670 +
   1.671 +      try {
   1.672 +        let uri = Services.io.newURI(hostname, null, null);
   1.673 +        hostname = uri.host;
   1.674 +      } catch (e) {}
   1.675 +
   1.676 +      // An extra check for hostnames that cannot be made by newURI(...).
   1.677 +      // For example, an IP address like "10.1.1.1".
   1.678 +      if (hostname.match(this.REGEXP_IPV4) ||
   1.679 +          hostname.match(this.REGEXP_IPV6)) {
   1.680 +        retval.push(hostname);
   1.681 +        continue;
   1.682 +      }
   1.683 +
   1.684 +      try {
   1.685 +        let hostnameIps = gDNSService.resolve(hostname, 0);
   1.686 +        while (hostnameIps.hasMore()) {
   1.687 +          retval.push(hostnameIps.getNextAddrAsString());
   1.688 +          debug("Found IP at: " + JSON.stringify(retval));
   1.689 +        }
   1.690 +      } catch (e) {}
   1.691 +    }
   1.692 +
   1.693 +    return retval;
   1.694 +  },
   1.695 +#endif
   1.696 +
   1.697 +  convertConnectionType: function(network) {
   1.698 +    // If there is internal interface change (e.g., MOBILE_MMS, MOBILE_SUPL),
   1.699 +    // the function will return null so that it won't trigger type change event
   1.700 +    // in NetworkInformation API.
   1.701 +    if (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI &&
   1.702 +        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
   1.703 +      return null;
   1.704 +    }
   1.705 +
   1.706 +    if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED) {
   1.707 +      return CONNECTION_TYPE_NONE;
   1.708 +    }
   1.709 +
   1.710 +    switch (network.type) {
   1.711 +      case Ci.nsINetworkInterface.NETWORK_TYPE_WIFI:
   1.712 +        return CONNECTION_TYPE_WIFI;
   1.713 +      case Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE:
   1.714 +        return CONNECTION_TYPE_CULLULAR;
   1.715 +    }
   1.716 +  },
   1.717 +
   1.718 +  // nsISettingsServiceCallback
   1.719 +
   1.720 +  tetheringSettings: {},
   1.721 +
   1.722 +  initTetheringSettings: function() {
   1.723 +    this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
   1.724 +    this.tetheringSettings[SETTINGS_USB_IP] = DEFAULT_USB_IP;
   1.725 +    this.tetheringSettings[SETTINGS_USB_PREFIX] = DEFAULT_USB_PREFIX;
   1.726 +    this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
   1.727 +    this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
   1.728 +    this.tetheringSettings[SETTINGS_USB_DNS1] = DEFAULT_DNS1;
   1.729 +    this.tetheringSettings[SETTINGS_USB_DNS2] = DEFAULT_DNS2;
   1.730 +
   1.731 +    this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
   1.732 +    this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP]   = DEFAULT_WIFI_DHCPSERVER_ENDIP;
   1.733 +
   1.734 +#ifdef MOZ_B2G_RIL
   1.735 +    this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
   1.736 +      libcutils.property_get("ro.tethering.dun_required") === "1";
   1.737 +#endif
   1.738 +  },
   1.739 +
   1.740 +  _requestCount: 0,
   1.741 +
   1.742 +  handle: function(aName, aResult) {
   1.743 +    switch(aName) {
   1.744 +      case SETTINGS_USB_ENABLED:
   1.745 +        this._oldUsbTetheringEnabledState = this.tetheringSettings[SETTINGS_USB_ENABLED];
   1.746 +      case SETTINGS_USB_IP:
   1.747 +      case SETTINGS_USB_PREFIX:
   1.748 +      case SETTINGS_USB_DHCPSERVER_STARTIP:
   1.749 +      case SETTINGS_USB_DHCPSERVER_ENDIP:
   1.750 +      case SETTINGS_USB_DNS1:
   1.751 +      case SETTINGS_USB_DNS2:
   1.752 +      case SETTINGS_WIFI_DHCPSERVER_STARTIP:
   1.753 +      case SETTINGS_WIFI_DHCPSERVER_ENDIP:
   1.754 +        if (aResult !== null) {
   1.755 +          this.tetheringSettings[aName] = aResult;
   1.756 +        }
   1.757 +        debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
   1.758 +        let index = this._usbTetheringSettingsToRead.indexOf(aName);
   1.759 +
   1.760 +        if (index != -1) {
   1.761 +          this._usbTetheringSettingsToRead.splice(index, 1);
   1.762 +        }
   1.763 +
   1.764 +        if (this._usbTetheringSettingsToRead.length) {
   1.765 +          debug("We haven't read completely the usb Tethering data from settings db.");
   1.766 +          break;
   1.767 +        }
   1.768 +
   1.769 +        if (this._oldUsbTetheringEnabledState === this.tetheringSettings[SETTINGS_USB_ENABLED]) {
   1.770 +          debug("No changes for SETTINGS_USB_ENABLED flag. Nothing to do.");
   1.771 +          break;
   1.772 +        }
   1.773 +
   1.774 +        this._requestCount++;
   1.775 +        if (this._requestCount === 1) {
   1.776 +          this.handleUSBTetheringToggle(aResult);
   1.777 +        }
   1.778 +        break;
   1.779 +    };
   1.780 +  },
   1.781 +
   1.782 +  handleError: function(aErrorMessage) {
   1.783 +    debug("There was an error while reading Tethering settings.");
   1.784 +    this.tetheringSettings = {};
   1.785 +    this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
   1.786 +  },
   1.787 +
   1.788 +  getNetworkInterface: function(type) {
   1.789 +    for each (let network in this.networkInterfaces) {
   1.790 +      if (network.type == type) {
   1.791 +        return network;
   1.792 +      }
   1.793 +    }
   1.794 +    return null;
   1.795 +  },
   1.796 +
   1.797 +  _usbTetheringAction: TETHERING_STATE_IDLE,
   1.798 +
   1.799 +  _usbTetheringSettingsToRead: [],
   1.800 +
   1.801 +  _oldUsbTetheringEnabledState: null,
   1.802 +
   1.803 +  // External and internal interface name.
   1.804 +  _tetheringInterface: null,
   1.805 +
   1.806 +  handleLastRequest: function() {
   1.807 +    let count = this._requestCount;
   1.808 +    this._requestCount = 0;
   1.809 +
   1.810 +    if (count === 1) {
   1.811 +      if (this.wantConnectionEvent) {
   1.812 +        if (this.tetheringSettings[SETTINGS_USB_ENABLED]) {
   1.813 +          this.wantConnectionEvent.call(this);
   1.814 +        }
   1.815 +        this.wantConnectionEvent = null;
   1.816 +      }
   1.817 +      return;
   1.818 +    }
   1.819 +
   1.820 +    if (count > 1) {
   1.821 +      this.handleUSBTetheringToggle(this.tetheringSettings[SETTINGS_USB_ENABLED]);
   1.822 +      this.wantConnectionEvent = null;
   1.823 +    }
   1.824 +  },
   1.825 +
   1.826 +#ifdef MOZ_B2G_RIL
   1.827 +  dunConnectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   1.828 +  /**
   1.829 +   * Callback when dun connection fails to connect within timeout.
   1.830 +   */
   1.831 +  onDunConnectTimerTimeout: function() {
   1.832 +    while (this._pendingTetheringRequests.length > 0) {
   1.833 +      debug("onDunConnectTimerTimeout: callback without network info.");
   1.834 +      let callback = this._pendingTetheringRequests.shift();
   1.835 +      if (typeof callback === 'function') {
   1.836 +        callback();
   1.837 +      }
   1.838 +    }
   1.839 +  },
   1.840 +
   1.841 +  dunRetryTimes: 0,
   1.842 +  dunRetryTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   1.843 +  setupDunConnection: function() {
   1.844 +    this.dunRetryTimer.cancel();
   1.845 +    let ril = this.mRil.getRadioInterface(this.gDataDefaultServiceId);
   1.846 +
   1.847 +    if (ril.rilContext && ril.rilContext.data &&
   1.848 +        ril.rilContext.data.state === "registered") {
   1.849 +      this.dunRetryTimes = 0;
   1.850 +      ril.setupDataCallByType("dun");
   1.851 +      this.dunConnectTimer.cancel();
   1.852 +      this.dunConnectTimer.
   1.853 +        initWithCallback(this.onDunConnectTimerTimeout.bind(this),
   1.854 +                         MOBILE_DUN_CONNECT_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
   1.855 +      return;
   1.856 +    }
   1.857 +
   1.858 +    if (this.dunRetryTimes++ >= this.MOBILE_DUN_MAX_RETRIES) {
   1.859 +      debug("setupDunConnection: max retries reached.");
   1.860 +      this.dunRetryTimes = 0;
   1.861 +      // same as dun connect timeout.
   1.862 +      this.onDunConnectTimerTimeout();
   1.863 +      return;
   1.864 +    }
   1.865 +
   1.866 +    debug("Data not ready, retry dun after " + MOBILE_DUN_RETRY_INTERVAL + " ms.");
   1.867 +    this.dunRetryTimer.
   1.868 +      initWithCallback(this.setupDunConnection.bind(this),
   1.869 +                       MOBILE_DUN_RETRY_INTERVAL, Ci.nsITimer.TYPE_ONE_SHOT);
   1.870 +  },
   1.871 +
   1.872 +  _pendingTetheringRequests: [],
   1.873 +  _dunActiveUsers: 0,
   1.874 +  handleDunConnection: function(enable, callback) {
   1.875 +    debug("handleDunConnection: " + enable);
   1.876 +    let dun = this.getNetworkInterface(
   1.877 +      Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN);
   1.878 +
   1.879 +    if (!enable) {
   1.880 +      this._dunActiveUsers--;
   1.881 +      if (this._dunActiveUsers > 0) {
   1.882 +        debug("Dun still needed by others, do not disconnect.")
   1.883 +        return;
   1.884 +      }
   1.885 +
   1.886 +      this.dunRetryTimes = 0;
   1.887 +      this.dunRetryTimer.cancel();
   1.888 +      this.dunConnectTimer.cancel();
   1.889 +      this._pendingTetheringRequests = [];
   1.890 +
   1.891 +      if (dun && (dun.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED)) {
   1.892 +        this.mRil.getRadioInterface(this.gDataDefaultServiceId)
   1.893 +          .deactivateDataCallByType("dun");
   1.894 +      }
   1.895 +      return;
   1.896 +    }
   1.897 +
   1.898 +    this._dunActiveUsers++;
   1.899 +    if (!dun || (dun.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED)) {
   1.900 +      debug("DUN data call inactive, setup dun data call!")
   1.901 +      this._pendingTetheringRequests.push(callback);
   1.902 +      this.dunRetryTimes = 0;
   1.903 +      this.setupDunConnection();
   1.904 +
   1.905 +      return;
   1.906 +    }
   1.907 +    this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = dun.name;
   1.908 +    callback(dun);
   1.909 +  },
   1.910 +#endif
   1.911 +
   1.912 +  handleUSBTetheringToggle: function(enable) {
   1.913 +    debug("handleUSBTetheringToggle: " + enable);
   1.914 +    if (enable &&
   1.915 +        (this._usbTetheringAction === TETHERING_STATE_ONGOING ||
   1.916 +         this._usbTetheringAction === TETHERING_STATE_ACTIVE)) {
   1.917 +      debug("Usb tethering already connecting/connected.");
   1.918 +      return;
   1.919 +    }
   1.920 +
   1.921 +    if (!enable &&
   1.922 +        this._usbTetheringAction === TETHERING_STATE_IDLE) {
   1.923 +      debug("Usb tethering already disconnected.");
   1.924 +      return;
   1.925 +    }
   1.926 +
   1.927 +    if (!enable) {
   1.928 +      this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
   1.929 +      gNetworkService.enableUsbRndis(false, this.enableUsbRndisResult.bind(this));
   1.930 +      return;
   1.931 +    }
   1.932 +
   1.933 +    this.tetheringSettings[SETTINGS_USB_ENABLED] = true;
   1.934 +    this._usbTetheringAction = TETHERING_STATE_ONGOING;
   1.935 +
   1.936 +#ifdef MOZ_B2G_RIL
   1.937 +    if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
   1.938 +      this.handleDunConnection(true, function(network) {
   1.939 +        if (!network){
   1.940 +          this.usbTetheringResultReport("Dun connection failed");
   1.941 +          return;
   1.942 +        }
   1.943 +        this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = network.name;
   1.944 +        gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this));
   1.945 +      }.bind(this));
   1.946 +      return;
   1.947 +    }
   1.948 +#endif
   1.949 +
   1.950 +    if (this.active) {
   1.951 +      this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = this.active.name;
   1.952 +    } else {
   1.953 +      let mobile = this.getNetworkInterface(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
   1.954 +      if (mobile) {
   1.955 +        this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = mobile.name;
   1.956 +      }
   1.957 +    }
   1.958 +    gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this));
   1.959 +  },
   1.960 +
   1.961 +  getUSBTetheringParameters: function(enable, tetheringinterface) {
   1.962 +    let interfaceIp;
   1.963 +    let prefix;
   1.964 +    let wifiDhcpStartIp;
   1.965 +    let wifiDhcpEndIp;
   1.966 +    let usbDhcpStartIp;
   1.967 +    let usbDhcpEndIp;
   1.968 +    let dns1;
   1.969 +    let dns2;
   1.970 +    let internalInterface = tetheringinterface.internalInterface;
   1.971 +    let externalInterface = tetheringinterface.externalInterface;
   1.972 +
   1.973 +    interfaceIp = this.tetheringSettings[SETTINGS_USB_IP];
   1.974 +    prefix = this.tetheringSettings[SETTINGS_USB_PREFIX];
   1.975 +    wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
   1.976 +    wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
   1.977 +    usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
   1.978 +    usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
   1.979 +    dns1 = this.tetheringSettings[SETTINGS_USB_DNS1];
   1.980 +    dns2 = this.tetheringSettings[SETTINGS_USB_DNS2];
   1.981 +
   1.982 +    // Using the default values here until application support these settings.
   1.983 +    if (interfaceIp == "" || prefix == "" ||
   1.984 +        wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
   1.985 +        usbDhcpStartIp == "" || usbDhcpEndIp == "") {
   1.986 +      debug("Invalid subnet information.");
   1.987 +      return null;
   1.988 +    }
   1.989 +
   1.990 +    return {
   1.991 +      ifname: internalInterface,
   1.992 +      ip: interfaceIp,
   1.993 +      prefix: prefix,
   1.994 +      wifiStartIp: wifiDhcpStartIp,
   1.995 +      wifiEndIp: wifiDhcpEndIp,
   1.996 +      usbStartIp: usbDhcpStartIp,
   1.997 +      usbEndIp: usbDhcpEndIp,
   1.998 +      dns1: dns1,
   1.999 +      dns2: dns2,
  1.1000 +      internalIfname: internalInterface,
  1.1001 +      externalIfname: externalInterface,
  1.1002 +      enable: enable,
  1.1003 +      link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
  1.1004 +    };
  1.1005 +  },
  1.1006 +
  1.1007 +  notifyError: function(resetSettings, callback, msg) {
  1.1008 +    if (resetSettings) {
  1.1009 +      let settingsLock = gSettingsService.createLock();
  1.1010 +      // Disable wifi tethering with a useful error message for the user.
  1.1011 +      settingsLock.set("tethering.wifi.enabled", false, null, msg);
  1.1012 +    }
  1.1013 +
  1.1014 +    debug("setWifiTethering: " + (msg ? msg : "success"));
  1.1015 +
  1.1016 +    if (callback) {
  1.1017 +      callback.wifiTetheringEnabledChange(msg);
  1.1018 +    }
  1.1019 +  },
  1.1020 +
  1.1021 +  enableWifiTethering: function(enable, config, callback) {
  1.1022 +    // Fill in config's required fields.
  1.1023 +    config.ifname         = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
  1.1024 +    config.internalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
  1.1025 +    config.externalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface;
  1.1026 +
  1.1027 +    gNetworkService.setWifiTethering(enable, config, (function(error) {
  1.1028 +#ifdef MOZ_B2G_RIL
  1.1029 +      // Disconnect dun on error or when wifi tethering is disabled.
  1.1030 +      if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
  1.1031 +          (!enable || error)) {
  1.1032 +        this.handleDunConnection(false);
  1.1033 +      }
  1.1034 +#endif
  1.1035 +      let resetSettings = error;
  1.1036 +      this.notifyError(resetSettings, callback, error);
  1.1037 +    }).bind(this));
  1.1038 +  },
  1.1039 +
  1.1040 +  // Enable/disable WiFi tethering by sending commands to netd.
  1.1041 +  setWifiTethering: function(enable, network, config, callback) {
  1.1042 +    debug("setWifiTethering: " + enable);
  1.1043 +    if (!network) {
  1.1044 +      this.notifyError(true, callback, "invalid network information");
  1.1045 +      return;
  1.1046 +    }
  1.1047 +
  1.1048 +    if (!config) {
  1.1049 +      this.notifyError(true, callback, "invalid configuration");
  1.1050 +      return;
  1.1051 +    }
  1.1052 +
  1.1053 +    if (!enable) {
  1.1054 +      this.enableWifiTethering(false, config, callback);
  1.1055 +      return;
  1.1056 +    }
  1.1057 +
  1.1058 +    this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface = network.name;
  1.1059 +
  1.1060 +#ifdef MOZ_B2G_RIL
  1.1061 +    if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
  1.1062 +      this.handleDunConnection(true, function(config, callback, network) {
  1.1063 +        if (!network) {
  1.1064 +          this.notifyError(true, callback, "Dun connection failed");
  1.1065 +          return;
  1.1066 +        }
  1.1067 +        this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = network.name;
  1.1068 +        this.enableWifiTethering(true, config, callback);
  1.1069 +      }.bind(this, config, callback));
  1.1070 +      return;
  1.1071 +    }
  1.1072 +#endif
  1.1073 +
  1.1074 +    let mobile = this.getNetworkInterface(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
  1.1075 +    // Update the real interface name
  1.1076 +    if (mobile) {
  1.1077 +      this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = mobile.name;
  1.1078 +    }
  1.1079 +
  1.1080 +    this.enableWifiTethering(true, config, callback);
  1.1081 +  },
  1.1082 +
  1.1083 +  // Enable/disable USB tethering by sending commands to netd.
  1.1084 +  setUSBTethering: function(enable, tetheringInterface, callback) {
  1.1085 +    let params = this.getUSBTetheringParameters(enable, tetheringInterface);
  1.1086 +
  1.1087 +    if (params === null) {
  1.1088 +      gNetworkService.enableUsbRndis(false, function() {
  1.1089 +        this.usbTetheringResultReport("Invalid parameters");
  1.1090 +      });
  1.1091 +      return;
  1.1092 +    }
  1.1093 +
  1.1094 +    gNetworkService.setUSBTethering(enable, params, callback);
  1.1095 +  },
  1.1096 +
  1.1097 +  getUsbInterface: function() {
  1.1098 +    // Find the rndis interface.
  1.1099 +    for (let i = 0; i < this.possibleInterface.length; i++) {
  1.1100 +      try {
  1.1101 +        let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" +
  1.1102 +                                      this.possibleInterface[i]);
  1.1103 +        if (file.exists()) {
  1.1104 +          return this.possibleInterface[i];
  1.1105 +        }
  1.1106 +      } catch (e) {
  1.1107 +        debug("Not " + this.possibleInterface[i] + " interface.");
  1.1108 +      }
  1.1109 +    }
  1.1110 +    debug("Can't find rndis interface in possible lists.");
  1.1111 +    return DEFAULT_USB_INTERFACE_NAME;
  1.1112 +  },
  1.1113 +
  1.1114 +  enableUsbRndisResult: function(success, enable) {
  1.1115 +    if (success) {
  1.1116 +      // If enable is false, don't find usb interface cause it is already down,
  1.1117 +      // just use the internal interface in settings.
  1.1118 +      if (enable) {
  1.1119 +        this._tetheringInterface[TETHERING_TYPE_USB].internalInterface = this.getUsbInterface();
  1.1120 +      }
  1.1121 +      this.setUSBTethering(enable,
  1.1122 +                           this._tetheringInterface[TETHERING_TYPE_USB],
  1.1123 +                           this.usbTetheringResultReport.bind(this));
  1.1124 +    } else {
  1.1125 +      this.usbTetheringResultReport("Failed to set usb function");
  1.1126 +      throw new Error("failed to set USB Function to adb");
  1.1127 +    }
  1.1128 +  },
  1.1129 +
  1.1130 +  usbTetheringResultReport: function(error) {
  1.1131 +    let settingsLock = gSettingsService.createLock();
  1.1132 +
  1.1133 +    // Disable tethering settings when fail to enable it.
  1.1134 +    if (error) {
  1.1135 +      this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
  1.1136 +      settingsLock.set("tethering.usb.enabled", false, null);
  1.1137 +      // Skip others request when we found an error.
  1.1138 +      this._requestCount = 0;
  1.1139 +      this._usbTetheringAction = TETHERING_STATE_IDLE;
  1.1140 +#ifdef MOZ_B2G_RIL
  1.1141 +      if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
  1.1142 +        this.handleDunConnection(false);
  1.1143 +      }
  1.1144 +#endif
  1.1145 +    } else {
  1.1146 +      if (this.tetheringSettings[SETTINGS_USB_ENABLED]) {
  1.1147 +        this._usbTetheringAction = TETHERING_STATE_ACTIVE;
  1.1148 +      } else {
  1.1149 +        this._usbTetheringAction = TETHERING_STATE_IDLE;
  1.1150 +#ifdef MOZ_B2G_RIL
  1.1151 +        if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
  1.1152 +          this.handleDunConnection(false);
  1.1153 +        }
  1.1154 +#endif
  1.1155 +      }
  1.1156 +      this.handleLastRequest();
  1.1157 +    }
  1.1158 +  },
  1.1159 +
  1.1160 +  onConnectionChangedReport: function(success, externalIfname) {
  1.1161 +    debug("onConnectionChangedReport result: success " + success);
  1.1162 +
  1.1163 +    if (success) {
  1.1164 +      // Update the external interface.
  1.1165 +      this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = externalIfname;
  1.1166 +      debug("Change the interface name to " + externalIfname);
  1.1167 +    }
  1.1168 +  },
  1.1169 +
  1.1170 +  onConnectionChanged: function(network) {
  1.1171 +    if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
  1.1172 +      debug("We are only interested in CONNECTED event");
  1.1173 +      return;
  1.1174 +    }
  1.1175 +
  1.1176 +#ifdef MOZ_B2G_RIL
  1.1177 +    // We can not use network.type only to check if it's dun, cause if it is
  1.1178 +    // shared with default, the returned type would always be default, see bug
  1.1179 +    // 939046. In most cases, if dun is required, it should not be shared with
  1.1180 +    // default.
  1.1181 +    if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
  1.1182 +        (network.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN ||
  1.1183 +         this.mRil.getRadioInterface(this.gDataDefaultServiceId)
  1.1184 +           .getDataCallStateByType("dun") ===
  1.1185 +         Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED)) {
  1.1186 +      this.dunConnectTimer.cancel();
  1.1187 +      debug("DUN data call connected, process callbacks.");
  1.1188 +      while (this._pendingTetheringRequests.length > 0) {
  1.1189 +        let callback = this._pendingTetheringRequests.shift();
  1.1190 +        if (typeof callback === 'function') {
  1.1191 +          callback(network);
  1.1192 +        }
  1.1193 +      }
  1.1194 +      return;
  1.1195 +    }
  1.1196 +#endif
  1.1197 +
  1.1198 +    if (!this.tetheringSettings[SETTINGS_USB_ENABLED]) {
  1.1199 +      debug("Usb tethering settings is not enabled");
  1.1200 +      return;
  1.1201 +    }
  1.1202 +
  1.1203 +#ifdef MOZ_B2G_RIL
  1.1204 +    if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
  1.1205 +        network.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN &&
  1.1206 +        this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
  1.1207 +        network.name) {
  1.1208 +      debug("Dun required and dun interface is the same");
  1.1209 +      return;
  1.1210 +    }
  1.1211 +#endif
  1.1212 +
  1.1213 +    if (this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
  1.1214 +        this.active.name) {
  1.1215 +      debug("The active interface is the same");
  1.1216 +      return;
  1.1217 +    }
  1.1218 +
  1.1219 +    let previous = {
  1.1220 +      internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
  1.1221 +      externalIfname: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface
  1.1222 +    };
  1.1223 +
  1.1224 +    let current = {
  1.1225 +      internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
  1.1226 +      externalIfname: network.name
  1.1227 +    };
  1.1228 +
  1.1229 +    let callback = (function() {
  1.1230 +      // Update external network interface.
  1.1231 +      debug("Update upstream interface to " + network.name);
  1.1232 +      gNetworkService.updateUpStream(previous, current, this.onConnectionChangedReport.bind(this));
  1.1233 +    }).bind(this);
  1.1234 +
  1.1235 +    if (this._usbTetheringAction === TETHERING_STATE_ONGOING) {
  1.1236 +      debug("Postpone the event and handle it when state is idle.");
  1.1237 +      this.wantConnectionEvent = callback;
  1.1238 +      return;
  1.1239 +    }
  1.1240 +    this.wantConnectionEvent = null;
  1.1241 +
  1.1242 +    callback.call(this);
  1.1243 +  }
  1.1244 +};
  1.1245 +
  1.1246 +let CaptivePortalDetectionHelper = (function() {
  1.1247 +
  1.1248 +  const EVENT_CONNECT = "Connect";
  1.1249 +  const EVENT_DISCONNECT = "Disconnect";
  1.1250 +  let _ongoingInterface = null;
  1.1251 +  let _available = ("nsICaptivePortalDetector" in Ci);
  1.1252 +  let getService = function() {
  1.1253 +    return Cc['@mozilla.org/toolkit/captive-detector;1']
  1.1254 +             .getService(Ci.nsICaptivePortalDetector);
  1.1255 +  };
  1.1256 +
  1.1257 +  let _performDetection = function(interfaceName, callback) {
  1.1258 +    let capService = getService();
  1.1259 +    let capCallback = {
  1.1260 +      QueryInterface: XPCOMUtils.generateQI([Ci.nsICaptivePortalCallback]),
  1.1261 +      prepare: function() {
  1.1262 +        capService.finishPreparation(interfaceName);
  1.1263 +      },
  1.1264 +      complete: function(success) {
  1.1265 +        _ongoingInterface = null;
  1.1266 +        callback(success);
  1.1267 +      }
  1.1268 +    };
  1.1269 +
  1.1270 +    // Abort any unfinished captive portal detection.
  1.1271 +    if (_ongoingInterface != null) {
  1.1272 +      capService.abort(_ongoingInterface);
  1.1273 +      _ongoingInterface = null;
  1.1274 +    }
  1.1275 +    try {
  1.1276 +      capService.checkCaptivePortal(interfaceName, capCallback);
  1.1277 +      _ongoingInterface = interfaceName;
  1.1278 +    } catch (e) {
  1.1279 +      debug('Fail to detect captive portal due to: ' + e.message);
  1.1280 +    }
  1.1281 +  };
  1.1282 +
  1.1283 +  let _abort = function(interfaceName) {
  1.1284 +    if (_ongoingInterface !== interfaceName) {
  1.1285 +      return;
  1.1286 +    }
  1.1287 +
  1.1288 +    let capService = getService();
  1.1289 +    capService.abort(_ongoingInterface);
  1.1290 +    _ongoingInterface = null;
  1.1291 +  };
  1.1292 +
  1.1293 +  return {
  1.1294 +    EVENT_CONNECT: EVENT_CONNECT,
  1.1295 +    EVENT_DISCONNECT: EVENT_DISCONNECT,
  1.1296 +    notify: function(eventType, network) {
  1.1297 +      switch (eventType) {
  1.1298 +        case EVENT_CONNECT:
  1.1299 +          // perform captive portal detection on wifi interface
  1.1300 +          if (_available && network &&
  1.1301 +              network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
  1.1302 +            _performDetection(network.name, function() {
  1.1303 +              // TODO: bug 837600
  1.1304 +              // We can disconnect wifi in here if user abort the login procedure.
  1.1305 +            });
  1.1306 +          }
  1.1307 +
  1.1308 +          break;
  1.1309 +        case EVENT_DISCONNECT:
  1.1310 +          if (_available &&
  1.1311 +              network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
  1.1312 +            _abort(network.name);
  1.1313 +          }
  1.1314 +          break;
  1.1315 +      }
  1.1316 +    }
  1.1317 +  };
  1.1318 +}());
  1.1319 +
  1.1320 +#ifdef MOZ_B2G_RIL
  1.1321 +XPCOMUtils.defineLazyServiceGetter(NetworkManager.prototype, "mRil",
  1.1322 +                                   "@mozilla.org/ril;1",
  1.1323 +                                   "nsIRadioInterfaceLayer");
  1.1324 +
  1.1325 +XPCOMUtils.defineLazyGetter(NetworkManager.prototype,
  1.1326 +                            "gDataDefaultServiceId", function() {
  1.1327 +  try {
  1.1328 +    return Services.prefs.getIntPref(PREF_DATA_DEFAULT_SERVICE_ID);
  1.1329 +  } catch(e) {}
  1.1330 +
  1.1331 +  return 0;
  1.1332 +});
  1.1333 +#endif
  1.1334 +
  1.1335 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkManager]);
  1.1336 +
  1.1337 +
  1.1338 +let debug;
  1.1339 +if (DEBUG) {
  1.1340 +  debug = function(s) {
  1.1341 +    dump("-*- NetworkManager: " + s + "\n");
  1.1342 +  };
  1.1343 +} else {
  1.1344 +  debug = function(s) {};
  1.1345 +}

mercurial