/* -*- 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]);