summaryrefslogtreecommitdiffstats
path: root/dom/network/EthernetManager.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/network/EthernetManager.js')
-rw-r--r--dom/network/EthernetManager.js655
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]);