diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 5905fd47bcd..cf98e6481a7 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -453,6 +453,8 @@ @RESPATH@/components/DOMWifiManager.manifest @RESPATH@/components/DOMWifiP2pManager.js @RESPATH@/components/DOMWifiP2pManager.manifest +@RESPATH@/components/EthernetManager.js +@RESPATH@/components/EthernetManager.manifest @RESPATH@/components/NetworkInterfaceListService.js @RESPATH@/components/NetworkInterfaceListService.manifest @RESPATH@/components/NetworkManager.js diff --git a/dom/network/EthernetManager.js b/dom/network/EthernetManager.js new file mode 100644 index 00000000000..4b11e566663 --- /dev/null +++ b/dom/network/EthernetManager.js @@ -0,0 +1,655 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* 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/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const TOPIC_INTERFACE_STATE_CHANGED = "network-interface-state-changed"; + +const ETHERNET_NETWORK_IFACE_PREFIX = "eth"; +const DEFAULT_ETHERNET_NETWORK_IFACE = "eth0"; + +const INTERFACE_IPADDR_NULL = "0.0.0.0"; +const INTERFACE_GATEWAY_NULL = "0.0.0.0"; +const INTERFACE_PREFIX_NULL = 0; +const INTERFACE_MACADDR_NULL = "00:00:00:00:00:00"; + +const NETWORK_INTERFACE_UP = "up"; +const NETWORK_INTERFACE_DOWN = "down"; + +const IP_MODE_DHCP = "dhcp"; +const IP_MODE_STATIC = "static"; + +const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled"; + +XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager", + "@mozilla.org/network/manager;1", + "nsINetworkManager"); + +XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService", + "@mozilla.org/network/service;1", + "nsINetworkService"); + +let debug; +function updateDebug() { + let debugPref = false; // set default value here. + try { + debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED); + } catch (e) {} + + if (debugPref) { + debug = function(s) { + dump("-*- EthernetManager: " + s + "\n"); + }; + } else { + debug = function(s) {}; + } +} +updateDebug(); + +// nsINetworkInterface + +function EthernetInterface(attr) { + this.info.state = attr.state; + this.info.type = attr.type; + this.info.name = attr.name; + this.info.ipMode = attr.ipMode; + this.info.ips = [attr.ip]; + this.info.prefixLengths = [attr.prefixLength]; + this.info.gateways = [attr.gateway]; + this.info.dnses = attr.dnses; + this.httpProxyHost = ""; + this.httpProxyPort = 0; +} +EthernetInterface.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]), + + updateConfig: function(config) { + debug("Interface " + this.info.name + " updateConfig " + JSON.stringify(config)); + this.info.state = (config.state != undefined) ? + config.state : this.info.state; + this.info.ips = (config.ip != undefined) ? [config.ip] : this.info.ips; + this.info.prefixLengths = (config.prefixLength != undefined) ? + [config.prefixLength] : this.info.prefixLengths; + this.info.gateways = (config.gateway != undefined) ? + [config.gateway] : this.info.gateways; + this.info.dnses = (config.dnses != undefined) ? config.dnses : this.info.dnses; + this.httpProxyHost = (config.httpProxyHost != undefined) ? + config.httpProxyHost : this.httpProxyHost; + this.httpProxyPort = (config.httpProxyPort != undefined) ? + config.httpProxyPort : this.httpProxyPort; + this.info.ipMode = (config.ipMode != undefined) ? + config.ipMode : this.info.ipMode; + }, + + info: { + getAddresses: function(ips, prefixLengths) { + ips.value = this.ips.slice(); + prefixLengths.value = this.prefixLengths.slice(); + + return this.ips.length; + }, + + getGateways: function(count) { + if (count) { + count.value = this.gateways.length; + } + return this.gateways.slice(); + }, + + getDnses: function(count) { + if (count) { + count.value = this.dnses.length; + } + return this.dnses.slice(); + } + } +}; + +// nsIEthernetManager + +/* + * Network state transition diagram + * + * ---------- enable --------- connect ----------- disconnect -------------- + * | Disabled | -----> | Enabled | -------> | Connected | <----------> | Disconnected | + * ---------- --------- ----------- connect -------------- + * ^ | | | + * | disable | | | + * ----------------------------------------------------------------------- + */ + +function EthernetManager() { + debug("EthernetManager start"); + + // Interface list. + this.ethernetInterfaces = {}; + + // Used to memorize last connection information. + this.lastStaticConfig = {}; + + Services.obs.addObserver(this, "xpcom-shutdown", false); +} + +EthernetManager.prototype = { + classID: Components.ID("a96441dd-36b3-4f7f-963b-2c032e28a039"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIEthernetManager]), + + ethernetInterfaces: null, + lastStaticConfig: null, + + observer: function(subject, topic, data) { + switch (topic) { + case "xpcom-shutdown": + debug("xpcom-shutdown"); + + this._shutdown(); + + Services.obs.removeObserver(this, "xpcom-shutdown"); + break; + } + }, + + _shutdown: function() { + debug("Shuting down"); + (function onRemove(ifnameList) { + if (!ifnameList.length) { + return; + } + + let ifname = ifnameList.shift(); + this.removeInterface(ifname, { notify: onRemove.bind(this, ifnameList) }); + }).call(this, Object.keys(this.ethernetInterfaces)); + }, + + get interfaceList() { + return Object.keys(this.ethernetInterfaces); + }, + + scan: function(callback) { + debug("Scan"); + + gNetworkService.getInterfaces(function(success, list) { + let ethList = []; + + if (!success) { + if (callback) { + callback.notify(ethList); + } + return; + } + + for (let i = 0; i < list.length; i++) { + debug("Found interface " + list[i]); + if (!list[i].startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) { + continue; + } + ethList.push(list[i]); + } + + if (callback) { + callback.notify(ethList); + } + }); + }, + + addInterface: function(ifname, callback) { + debug("Add interface " + ifname); + + if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) { + if (callback) { + callback.notify(false, "Invalid interface."); + } + return; + } + + if (this.ethernetInterfaces[ifname]) { + if (callback) { + callback.notify(true, "Interface already exists."); + } + return; + } + + gNetworkService.getInterfaceConfig(ifname, function(success, result) { + if (!success) { + if (callback) { + callback.notify(false, "Netd error."); + } + return; + } + + // Since the operation may still succeed with an invalid interface name, + // check the mac address as well. + if (result.macAddr == INTERFACE_MACADDR_NULL) { + if (callback) { + callback.notify(false, "Interface not found."); + } + return; + } + + this.ethernetInterfaces[ifname] = new EthernetInterface({ + state: result.link == NETWORK_INTERFACE_UP ? + Ci.nsINetworkInfo.NETWORK_STATE_DISABLED : + Ci.nsINetworkInfo.NETWORK_STATE_ENABLED, + name: ifname, + type: Ci.nsINetworkInfo.NETWORK_TYPE_ETHERNET, + ip: result.ip, + prefixLength: result.prefix, + ipMode: IP_MODE_DHCP + }); + + // Register the interface to NetworkManager. + gNetworkManager.registerNetworkInterface(this.ethernetInterfaces[ifname]); + + debug("Add interface " + ifname + " succeeded with " + + JSON.stringify(this.ethernetInterfaces[ifname])); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }, + + removeInterface: function(ifname, callback) { + debug("Remove interface " + ifname); + + if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) { + if (callback) { + callback.notify(false, "Invalid interface."); + } + return; + } + + if (!this.ethernetInterfaces[ifname]) { + if (callback) { + callback.notify(true, "Interface does not exist."); + } + return; + } + + // Make sure interface is disable before removing. + this.disable(ifname, { notify: function(success, message) { + // Unregister the interface from NetworkManager and also remove it from + // the interface list. + gNetworkManager.unregisterNetworkInterface(this.ethernetInterfaces[ifname]); + delete this.ethernetInterfaces[ifname]; + + debug("Remove interface " + ifname + " succeeded."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)}); + }, + + updateInterfaceConfig: function(ifname, config, callback) { + debug("Update interface config with " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + if (!config) { + if (callback) { + callback.notify(false, "No config to update."); + } + return; + } + + // Network state can not be modified externally. + if (config.state) { + delete config.state; + } + + let currentIpMode = iface.info.ipMode; + + // Update config. + this.ethernetInterfaces[iface.info.name].updateConfig(config); + + // Do not automatically re-connect if the interface is not in connected + // state. + if (iface.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + if (callback) { + callback.notify(true, "ok"); + } + return; + } + + let newIpMode = this.ethernetInterfaces[iface.info.name].info.ipMode; + + if (newIpMode == IP_MODE_STATIC) { + this._setStaticIP(iface.info.name, callback); + return; + } + if ((currentIpMode == IP_MODE_STATIC) && (newIpMode == IP_MODE_DHCP)) { + gNetworkService.stopDhcp(iface.info.name, function(success) { + if (success) { + debug("DHCP for " + iface.info.name + " stopped."); + } + }); + + // Clear the current network settings before do dhcp request, otherwise + // dhcp settings could fail. + this.disconnect(iface.info.name, { notify: function(success, message) { + if (!success) { + if (callback) { + callback.notify("Disconnect failed."); + } + return; + } + this._runDhcp(iface.info.name, callback); + }.bind(this) }); + return; + } + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }, + + enable: function(ifname, callback) { + debug("Enable interface " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + // Interface can be only enabled in the state of disabled. + if (iface.info.state != Ci.nsINetworkInfo.NETWORK_STATE_DISABLED) { + if (callback) { + callback.notify(true, "Interface already enabled."); + } + return; + } + + let ips = {}; + let prefixLengths = {}; + iface.info.getAddresses(ips, prefixLengths); + let config = { ifname: iface.info.name, + ip: ips.value[0], + prefix: prefixLengths.value[0], + link: NETWORK_INTERFACE_UP }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd Error."); + } + return; + } + + this.ethernetInterfaces[iface.info.name].updateConfig({ + state: Ci.nsINetworkInfo.NETWORK_STATE_ENABLED + }); + + debug("Enable interface " + iface.info.name + " succeeded."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }.bind(this)); + }, + + disable: function(ifname, callback) { + debug("Disable interface " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + if (iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_DISABLED) { + if (callback) { + callback.notify(true, "Interface already disabled."); + } + return; + } + + if (iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + gNetworkService.stopDhcp(iface.info.name, function(success) { + if (success) { + debug("DHCP for " + iface.info.name + " stopped."); + } + }); + } + + let ips = {}; + let prefixLengths = {}; + iface.info.getAddresses(ips, prefixLengths); + let config = { ifname: iface.info.name, + ip: ips.value[0], + prefix: prefixLengths.value[0], + link: NETWORK_INTERFACE_DOWN }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd Error."); + } + return; + } + + this.ethernetInterfaces[iface.info.name].updateConfig({ + state: Ci.nsINetworkInfo.NETWORK_STATE_DISABLED + }); + + debug("Disable interface " + iface.info.name + " succeeded."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }.bind(this)); + }, + + connect: function(ifname, callback) { + debug("Connect interface " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + // Interface can only be connected in the state of enabled or + // disconnected. + if (iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_DISABLED || + iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + if (callback) { + callback.notify(true, "Interface " + ifname + " is not available or " + + " already connected."); + } + return; + } + + if (iface.info.ipMode == IP_MODE_DHCP) { + this._runDhcp(iface.info.name, callback); + return; + } + + if (iface.info.ipMode == IP_MODE_STATIC) { + if (this._checkConfigNull(iface) && this.lastStaticConfig[iface.info.name]) { + debug("Connect with lastStaticConfig " + + JSON.stringify(this.lastStaticConfig[iface.info.name])); + this.ethernetInterfaces[iface.info.name].updateConfig( + this.lastStaticConfig[iface.info.name]); + } + this._setStaticIP(iface.info.name, callback); + return; + } + + if (callback) { + callback.notify(false, "IP mode is wrong or not set."); + } + }.bind(this)); + }, + + disconnect: function(ifname, callback) { + debug("Disconnect interface " + ifname); + + this._ensureIfname(ifname, callback, function(iface) { + // Interface can be only disconnected in the state of connected. + if (iface.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + if (callback) { + callback.notify(true, "Interface is already disconnected"); + } + return; + } + + let config = { ifname: iface.info.name, + ip: INTERFACE_IPADDR_NULL, + prefix: INTERFACE_PREFIX_NULL, + link: NETWORK_INTERFACE_UP }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd error."); + } + return; + } + + // Stop dhcp daemon. + gNetworkService.stopDhcp(iface.info.name, function(success) { + if (success) { + debug("DHCP for " + iface.info.name + " stopped."); + } + }); + + this.ethernetInterfaces[iface.info.name].updateConfig({ + state: Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED, + ip: INTERFACE_IPADDR_NULL, + prefixLength: INTERFACE_PREFIX_NULL, + gateway: INTERFACE_GATEWAY_NULL + }); + + gNetworkManager.updateNetworkInterface(this.ethernetInterfaces[ifname]); + + debug("Disconnect interface " + iface.info.name + " succeeded."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }.bind(this)); + }, + + _checkConfigNull: function(iface) { + let ips = {}; + let prefixLengths = {}; + let gateways = iface.info.getGateways(); + iface.info.getAddresses(ips, prefixLengths); + + if (ips.value[0] == INTERFACE_IPADDR_NULL && + prefixLengths.value[0] == INTERFACE_PREFIX_NULL && + gateways[0] == INTERFACE_GATEWAY_NULL) { + return true; + } + + return false; + }, + + _ensureIfname: function(ifname, callback, func) { + // If no given ifname, use the default one. + if (!ifname) { + ifname = DEFAULT_ETHERNET_NETWORK_IFACE; + } + + let iface = this.ethernetInterfaces[ifname]; + if (!iface) { + if (callback) { + callback.notify(true, "Interface " + ifname + " is not available."); + } + return; + } + + func.call(this, iface); + }, + + _runDhcp: function(ifname, callback) { + debug("runDhcp with " + ifname); + + if (!this.ethernetInterfaces[ifname]) { + if (callback) { + callback.notify(false, "Invalid interface."); + } + return; + } + + gNetworkService.dhcpRequest(ifname, function(success, result) { + if (!success) { + if (callback) { + callback.notify(false, "DHCP failed."); + } + return; + } + + debug("DHCP succeeded with " + JSON.stringify(result)); + + // Clear last static network information when connecting with dhcp mode. + if (this.lastStaticConfig[ifname]) { + this.lastStaticConfig[ifname] = null; + } + + this.ethernetInterfaces[ifname].updateConfig({ + state: Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED, + ip: result.ipaddr_str, + gateway: result.gateway_str, + prefixLength: result.prefixLength, + dnses: [result.dns1_str, result.dns2_str] + }); + + gNetworkManager.updateNetworkInterface(this.ethernetInterfaces[ifname]); + + debug("Connect interface " + ifname + " with DHCP succeeded."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }, + + _setStaticIP: function(ifname, callback) { + let iface = this.ethernetInterfaces[ifname]; + if (!iface) { + if (callback) { + callback.notify(false, "Invalid interface."); + } + return; + } + + let ips = {}; + let prefixLengths = {}; + iface.info.getAddresses(ips, prefixLengths); + + let config = { ifname: iface.info.name, + ip: ips.value[0], + prefix: prefixLengths.value[0], + link: NETWORK_INTERFACE_UP }; + gNetworkService.setInterfaceConfig(config, function(success) { + if (!success) { + if (callback) { + callback.notify(false, "Netd Error."); + } + return; + } + + // Keep the lastest static network information. + let ips = {}; + let prefixLengths = {}; + let gateways = iface.info.getGateways(); + iface.info.getAddresses(ips, prefixLengths); + + this.lastStaticConfig[iface.info.name] = { + ip: ips.value[0], + prefixLength: prefixLengths.value[0], + gateway: gateways[0] + }; + + this.ethernetInterfaces[ifname].updateConfig({ + state: Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED, + }); + + gNetworkManager.updateNetworkInterface(this.ethernetInterfaces[ifname]); + + debug("Connect interface " + ifname + " with static ip succeeded."); + + if (callback) { + callback.notify(true, "ok"); + } + }.bind(this)); + }, +} + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EthernetManager]); diff --git a/dom/network/EthernetManager.manifest b/dom/network/EthernetManager.manifest new file mode 100644 index 00000000000..d25a069e129 --- /dev/null +++ b/dom/network/EthernetManager.manifest @@ -0,0 +1,2 @@ +component {a96441dd-36b3-4f7f-963b-2c032e28a039} EthernetManager.js +contract @mozilla.org/ethernetManager;1 {a96441dd-36b3-4f7f-963b-2c032e28a039} diff --git a/dom/network/interfaces/moz.build b/dom/network/interfaces/moz.build index 8c82fe85474..e2aef2cb121 100644 --- a/dom/network/interfaces/moz.build +++ b/dom/network/interfaces/moz.build @@ -12,6 +12,7 @@ XPIDL_SOURCES += [ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': XPIDL_SOURCES += [ + 'nsIEthernetManager.idl', 'nsINetworkStatsServiceProxy.idl', ] diff --git a/dom/network/interfaces/nsIEthernetManager.idl b/dom/network/interfaces/nsIEthernetManager.idl new file mode 100644 index 00000000000..2b92dc88f0d --- /dev/null +++ b/dom/network/interfaces/nsIEthernetManager.idl @@ -0,0 +1,137 @@ +/* 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/. */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(2a3ad56c-edc0-439f-8aae-900b331ddf49)] +interface nsIEthernetManagerCallback : nsISupports +{ + /** + * Callback function used to report the success of different operations. + * + * @param success + * Boolean value indicates the success of an operation. + * @prarm message + * Message reported in the end of operation. + */ + void notify(in boolean success, in DOMString message); +}; + +[scriptable, function, uuid(1746e7dd-92d4-43fa-8ef4-bc13d0b60353)] +interface nsIEthernetManagerScanCallback : nsISupports +{ + /** + * Callback function used to report the result of scan function. + * + * @param list + * List of available ethernet interfaces. + */ + void notify(in jsval list); +}; + +/** + * An internal idl provides control to ethernet interfaces. + */ +[scriptable, uuid(81750c87-bb3b-4724-b955-834eafa53fd1)] +interface nsIEthernetManager : nsISupports +{ + /** + * List of exisiting interface name. + */ + readonly attribute jsval interfaceList; + + /** + * Scan available ethernet interfaces on device. + * + * @param callback + * Callback function. + */ + void scan(in nsIEthernetManagerScanCallback callback); + + /** + * Add a new interface to the interface list. + * + * @param ifname + * Interface name. Should be the form of "eth*". + * @param callback + * Callback function. + */ + void addInterface(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Remove an existing interface from the interface list. + * + * @param ifname + * Interface name. + * @param Callback + * Callback function. + */ + void removeInterface(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Update a conifg of an existing interface in the interface list. + * + * @param ifname + * Interface name. + * @param config + * .ip: IP address. + * .prefixLength: Mask length. + * .gateway: Gateway. + * .dnses: DNS addresses. + * .httpProxyHost: HTTP proxy host. + * .httpProxyPort: HTTP proxy port. + * .ipMode: IP mode, can be 'dhcp' or 'static'. + * @param callback + * Callback function. + */ + void updateInterfaceConfig(in DOMString ifname, + in jsval config, + in nsIEthernetManagerCallback callback); + + /** + * Enable networking of an existing interface in the interface list. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void enable(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Disable networking of an existing interface in the interface list. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void disable(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Make an existing interface connect to network. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void connect(in DOMString ifname, + in nsIEthernetManagerCallback callback); + + /** + * Disconnect a connected interface in the interface list. + * + * @param ifname + * Interface name. + * @param callback + * Callback function. + */ + void disconnect(in DOMString ifname, + in nsIEthernetManagerCallback callback); +}; diff --git a/dom/network/moz.build b/dom/network/moz.build index b42c30734f8..6a3488d4b8b 100644 --- a/dom/network/moz.build +++ b/dom/network/moz.build @@ -51,6 +51,8 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': EXTRA_COMPONENTS += [ + 'EthernetManager.js', + 'EthernetManager.manifest', 'NetworkStatsManager.js', 'NetworkStatsManager.manifest', 'NetworkStatsServiceProxy.js', diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js index 4a63a01e7c4..ee75195a92a 100644 --- a/dom/system/gonk/NetworkManager.js +++ b/dom/system/gonk/NetworkManager.js @@ -14,9 +14,9 @@ Cu.import("resource://gre/modules/Promise.jsm"); const NETWORKMANAGER_CONTRACTID = "@mozilla.org/network/manager;1"; const NETWORKMANAGER_CID = - Components.ID("{33901e46-33b8-11e1-9869-f46d04d25bcc}"); + Components.ID("{1ba9346b-53b5-4660-9dc6-58f0b258d0a6}"); -const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInfo.NETWORK_TYPE_WIFI; +const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET; XPCOMUtils.defineLazyGetter(this, "ppmm", function() { return Cc["@mozilla.org/parentprocessmessagemanager;1"] @@ -426,7 +426,8 @@ NetworkManager.prototype = { return this.removeSecondaryDefaultRoute(extNetworkInfo); }) .then(() => { - if (extNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) { + if (extNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI || + extNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_ETHERNET) { // Remove routing table in /proc/net/route return this._resetRoutingTable(extNetworkInfo.name); } @@ -499,6 +500,43 @@ NetworkManager.prototype = { networkInterfaceLinks: null, + _networkTypePriorityList: [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET, + Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE], + get networkTypePriorityList() { + return this._networkTypePriorityList; + }, + set networkTypePriorityList(val) { + if (val.length != this._networkTypePriorityList.length) { + throw "Priority list length should equal to " + + this._networkTypePriorityList.length; + } + + // Check if types in new priority list are valid and also make sure there + // are no duplicate types. + let list = [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET, + Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE]; + while (list.length) { + let type = list.shift(); + if (val.indexOf(type) == -1) { + throw "There is missing network type"; + } + } + + this._networkTypePriorityList = val; + }, + + getPriority: function(type) { + if (this._networkTypePriorityList.indexOf(type) == -1) { + // 0 indicates the lowest priority. + return 0; + } + + return this._networkTypePriorityList.length - + this._networkTypePriorityList.indexOf(type); + }, + get allNetworkInfo() { let allNetworkInfo = {}; @@ -516,8 +554,9 @@ NetworkManager.prototype = { return this._preferredNetworkType; }, set preferredNetworkType(val) { - if ([Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, - Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE].indexOf(val) == -1) { + if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, + Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) { throw "Invalid network type"; } this._preferredNetworkType = val; @@ -532,9 +571,9 @@ NetworkManager.prototype = { _overriddenActive: null, overrideActive: function(network) { - let type = network.info.type; - if ([Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, - Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE].indexOf(type) == -1) { + if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, + Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) { throw "Invalid network type"; } @@ -835,15 +874,28 @@ NetworkManager.prototype = { // Set active only for default connections. if (network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_WIFI && - network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE) { + network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE && + network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_ETHERNET) { continue; } - this._activeNetwork = network; if (network.info.type == this.preferredNetworkType) { + this._activeNetwork = network; debug("Found our preferred type of network: " + network.info.name); break; } + + // Initialize the active network with the first connected network. + if (!this._activeNetwork) { + this._activeNetwork = network; + continue; + } + + // Compare the prioriy between two network types. If found incoming + // network with higher priority, replace the active network. + if (this.getPriority(this._activeNetwork.type) < this.getPriority(network.type)) { + this._activeNetwork = network; + } } return Promise.resolve() @@ -927,8 +979,9 @@ NetworkManager.prototype = { // 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 (aNetworkInfo.type != Ci.nsINetworkInfo.NETWORK_TYPE_WIFI && - aNetworkInfo.type != Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE) { + if (aNetworkInfo.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && + aNetworkInfo.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE && + aNetworkInfo.type != Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) { return null; } @@ -941,6 +994,8 @@ NetworkManager.prototype = { return CONNECTION_TYPE_WIFI; case Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE: return CONNECTION_TYPE_CELLULAR; + case Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET: + return CONNECTION_TYPE_ETHERNET; } }, @@ -1189,4 +1244,4 @@ XPCOMUtils.defineLazyGetter(NetworkManager.prototype, "mRil", function() { return null; }); -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkManager]); \ No newline at end of file +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkManager]); diff --git a/dom/system/gonk/NetworkManager.manifest b/dom/system/gonk/NetworkManager.manifest index 172af047fa6..995fa65598e 100644 --- a/dom/system/gonk/NetworkManager.manifest +++ b/dom/system/gonk/NetworkManager.manifest @@ -1,3 +1,3 @@ # NetworkManager.js -component {33901e46-33b8-11e1-9869-f46d04d25bcc} NetworkManager.js -contract @mozilla.org/network/manager;1 {33901e46-33b8-11e1-9869-f46d04d25bcc} +component {1ba9346b-53b5-4660-9dc6-58f0b258d0a6} NetworkManager.js +contract @mozilla.org/network/manager;1 {1ba9346b-53b5-4660-9dc6-58f0b258d0a6} diff --git a/dom/system/gonk/nsINetworkInterface.idl b/dom/system/gonk/nsINetworkInterface.idl index 4e815819c4d..bd40e751a17 100644 --- a/dom/system/gonk/nsINetworkInterface.idl +++ b/dom/system/gonk/nsINetworkInterface.idl @@ -4,7 +4,7 @@ #include "nsISupports.idl" -[scriptable, uuid(f439ab5d-64bd-4a6c-8863-30235fa784d2)] +[scriptable, uuid(4816a559-5620-4cb5-8433-ff0b25e6622f)] interface nsINetworkInfo : nsISupports { const long NETWORK_STATE_UNKNOWN = -1; @@ -12,6 +12,8 @@ interface nsINetworkInfo : nsISupports const long NETWORK_STATE_CONNECTED = 1; const long NETWORK_STATE_DISCONNECTING = 2; const long NETWORK_STATE_DISCONNECTED = 3; + const long NETWORK_STATE_ENABLED = 4; + const long NETWORK_STATE_DISABLED = 5; /** * Current network state, one of the NETWORK_STATE_* constants. @@ -30,6 +32,7 @@ interface nsINetworkInfo : nsISupports const long NETWORK_TYPE_MOBILE_IMS = 5; const long NETWORK_TYPE_MOBILE_DUN = 6; const long NETWORK_TYPE_MOBILE_FOTA = 7; + const long NETWORK_TYPE_ETHERNET = 8; /** * Network type. One of the NETWORK_TYPE_* constants. diff --git a/dom/system/gonk/nsINetworkManager.idl b/dom/system/gonk/nsINetworkManager.idl index 1ac0c05ee97..0da1237969d 100644 --- a/dom/system/gonk/nsINetworkManager.idl +++ b/dom/system/gonk/nsINetworkManager.idl @@ -10,7 +10,7 @@ interface nsINetworkInterface; /** * Manage network interfaces. */ -[scriptable, uuid(e5ffe335-078e-4b25-87f1-02429bd2e458)] +[scriptable, uuid(1ba9346b-53b5-4660-9dc6-58f0b258d0a6)] interface nsINetworkManager : nsISupports { /** @@ -62,6 +62,15 @@ interface nsINetworkManager : nsISupports */ readonly attribute jsval allNetworkInfo; + /** + * Priority list of network types. An array of + * nsINetworkInterface::NETWORK_TYPE_* constants. + * + * The piror position of the type indicates the higher priority. The priority + * is used to determine route when there are multiple connected networks. + */ + attribute jsval networkTypePriorityList; + /** * The preferred network type. One of the * nsINetworkInterface::NETWORK_TYPE_* constants.