diff options
Diffstat (limited to 'dom/network/EthernetManager.js')
-rw-r--r-- | dom/network/EthernetManager.js | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/dom/network/EthernetManager.js b/dom/network/EthernetManager.js new file mode 100644 index 000000000..4b11e5666 --- /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]); |