Bug 1000040 - Part 2: Implement EthernetManager.js. r=vchange,vyang
authorJohn Shih <jshih@mozilla.com>
Thu, 29 May 2014 16:35:06 +0800
changeset 192586 78ba6f47a5a7369a2cc8eb682ded9225ea1dac1a
parent 192585 f54af6237ce94f9f01c549c3edd3505e3019ba33
child 192587 506038ddee2c864921317eb7cc23a0731ace94e2
push id27090
push usercbook@mozilla.com
push dateMon, 07 Jul 2014 13:07:36 +0000
treeherdermozilla-central@085eea991bb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvchange, vyang
bugs1000040
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1000040 - Part 2: Implement EthernetManager.js. r=vchange,vyang
b2g/installer/package-manifest.in
dom/network/interfaces/moz.build
dom/network/interfaces/nsIEthernetManager.idl
dom/network/src/EthernetManager.js
dom/network/src/EthernetManager.manifest
dom/network/src/moz.build
dom/system/gonk/NetworkManager.js
dom/system/gonk/NetworkManager.manifest
dom/system/gonk/nsINetworkManager.idl
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -400,16 +400,18 @@
 @BINPATH@/components/nsSidebar.js
 
 ; WiFi, NetworkManager, NetworkStats
 #ifdef MOZ_WIDGET_GONK
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 @BINPATH@/components/DOMWifiP2pManager.js
 @BINPATH@/components/DOMWifiP2pManager.manifest
+@BINPATH@/components/EthernetManager.js
+@BINPATH@/components/EthernetManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
 @BINPATH@/components/NetworkInterfaceListService.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkService.js
 @BINPATH@/components/NetworkService.manifest
 @BINPATH@/components/NetworkStatsManager.js
 @BINPATH@/components/NetworkStatsManager.manifest
--- a/dom/network/interfaces/moz.build
+++ b/dom/network/interfaces/moz.build
@@ -13,12 +13,13 @@ XPIDL_SOURCES += [
     'nsITCPSocketChild.idl',
     'nsITCPSocketParent.idl',
     'nsIUDPSocketChild.idl',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     XPIDL_SOURCES += [
         'nsIDOMNetworkStatsManager.idl',
+        'nsIEthernetManager.idl',
         'nsINetworkStatsServiceProxy.idl',
     ]
 
 XPIDL_MODULE = 'dom_network'
new file mode 100644
--- /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(a96441dd-36b3-4f7f-963b-2c032e28a039)]
+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: dnses.
+   *        .httpProxyHost: http proxy host.
+   *        .httpProxyPort: http porxy 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);
+};
new file mode 100644
--- /dev/null
+++ b/dom/network/src/EthernetManager.js
@@ -0,0 +1,619 @@
+/* -*- 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 DEBUG = false;
+function debug(s) {
+  if (DEBUG) {
+    dump("-*- EthernetManager: " + s + "\n");
+  }
+}
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+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";
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+                                   "@mozilla.org/network/manager;1",
+                                   "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+                                   "@mozilla.org/network/service;1",
+                                   "nsINetworkService");
+
+
+// nsINetworkInterface
+
+function EthernetInterface(attr) {
+  this.state = attr.state;
+  this.type = attr.type;
+  this.name = attr.name;
+  this.ipMode = attr.ipMode;
+  this.ips = [attr.ip];
+  this.prefixLengths = [attr.prefixLength];
+  this.gateways = [attr.gateway];
+  this.dnses = attr.dnses;
+  this.httpProxyHost = "";
+  this.httpProxyPort = 0;
+}
+EthernetInterface.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+  updateConfig: function(config) {
+    debug("Interface " + this.name + " updateConfig " + JSON.stringify(config));
+    this.state = (config.state != undefined) ?
+                  config.state : this.state;
+    this.ips = (config.ip != undefined) ? [config.ip] : this.ips;
+    this.prefixLengths = (config.prefixLength != undefined) ?
+                         [config.prefixLength] : this.prefixLengths;
+    this.gateways = (config.gateway != undefined) ?
+                    [config.gateway] : this.gateways;
+    this.dnses = (config.dnses != undefined) ? config.dnses : this.dnses;
+    this.httpProxyHost = (config.httpProxyHost != undefined) ?
+                          config.httpProxyHost : this.httpProxyHost;
+    this.httpProxyPort = (config.httpProxyPort != undefined) ?
+                          config.httpProxyPort : this.httpProxyPort;
+    this.ipMode = (config.ipMode != undefined) ?
+                   config.ipMode : this.ipMode;
+  },
+
+  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("addInterfaces " + 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.nsINetworkInterface.NETWORK_STATE_DISABLED :
+                        Ci.nsINetworkInterface.NETWORK_STATE_ENABLED,
+        name:         ifname,
+        type:         Ci.nsINetworkInterface.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 + " success with " +
+            JSON.stringify(this.ethernetInterfaces[ifname]));
+
+      if (callback) {
+        callback.notify(true, "ok");
+      }
+    }.bind(this));
+  },
+
+  removeInterface: function(ifname, callback) {
+    debug("removeInterface");
+
+    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 + " success.");
+
+      if (callback) {
+        callback.notify(true, "ok");
+      }
+    }.bind(this)});
+  },
+
+  updateInterfaceConfig: function(ifname, config, callback) {
+    debug("interfaceConfigUpdate 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.ipMode;
+
+      // Update config.
+      this.ethernetInterfaces[iface.name].updateConfig(config);
+
+      // Do not automatically re-connect if the interface is not in connected
+      // state.
+      if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+        return;
+      }
+
+      let newIpMode = this.ethernetInterfaces[iface.name].ipMode;
+      if (newIpMode == IP_MODE_STATIC) {
+        this._setStaticIP(iface.name, callback);
+        return;
+      }
+      if ((currentIpMode == IP_MODE_STATIC) && (newIpMode == IP_MODE_DHCP)) {
+        gNetworkService.stopDhcp(iface.name);
+        // Clear the current network settings before do dhcp request, otherwise
+        // dhcp settings could fail.
+        this.disconnect(iface.name, { notify: function(success, message) {
+          if (!success) {
+            if (callback) {
+              callback.notify("Disconnect failed.");
+            }
+            return;
+          }
+          this._runDhcp(iface.name, callback);
+        }.bind(this) });
+        return;
+      }
+
+      if (callback) {
+        callback.notify(true, "ok");
+      }
+    }.bind(this));
+  },
+
+  enable: function(ifname, callback) {
+    debug("enable with " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      // Interface can be only enabled in the state of disabled.
+      if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_DISABLED) {
+        if (callback) {
+          callback.notify(true, "already enabled.");
+        }
+        return;
+      }
+
+      let ips = {};
+      let prefixLengths = {};
+      iface.getAddresses(ips, prefixLengths);
+      let config = { ifname: iface.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.name].updateConfig({
+          state: Ci.nsINetworkInterface.NETWORK_STATE_ENABLED
+        });
+
+        debug("Interface " + iface.name + " enable success.");
+
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+      }.bind(this));
+    }.bind(this));
+  },
+
+  disable: function(ifname, callback) {
+    debug("disable with " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_DISABLED) {
+        if (callback) {
+          callback.notify(true, "Interface is already disabled.");
+        }
+        return;
+      }
+
+      if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        gNetworkService.stopDhcp(iface.name);
+      }
+
+      let ips = {};
+      let prefixLengths = {};
+      iface.getAddresses(ips, prefixLengths);
+      let config = { ifname: iface.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.name].updateConfig({
+          state: Ci.nsINetworkInterface.NETWORK_STATE_DISABLED
+        });
+
+        debug("Disable interface " + iface.name + " success.");
+
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+      }.bind(this));
+    }.bind(this));
+  },
+
+  connect: function(ifname, callback) {
+    debug("connect wtih " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      // Interface can only be connected in the state of enabled or
+      // disconnected.
+      if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_DISABLED ||
+          iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        if (callback) {
+          callback.notify(true, "Interface " + ifname + " is not available or "
+                                 + " already connected.");
+        }
+        return;
+      }
+
+      if (iface.ipMode == IP_MODE_DHCP) {
+        this._runDhcp(iface.name, callback);
+        return;
+      }
+
+      if (iface.ipMode == IP_MODE_STATIC) {
+        if (this._checkConfigNull(iface) && this.lastStaticConfig[iface.name]) {
+          debug("connect with lastStaticConfig " +
+                JSON.stringify(this.lastStaticConfig[iface.name]));
+          this.ethernetInterfaces[iface.name].updateConfig(
+            this.lastStaticConfig[iface.name]);
+        }
+        this._setStaticIP(iface.name, callback);
+        return;
+      }
+
+      if (callback) {
+        callback.notify(false, "Ip mode is wrong or not set.");
+      }
+    }.bind(this));
+  },
+
+  disconnect: function(ifname, callback) {
+    debug("disconnect");
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      // Interface can be only disconnected in the state of connected.
+      if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        if (callback) {
+          callback.notify(true, "interface is already disconnected");
+        }
+        return;
+      }
+
+      let config = { ifname: iface.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.name);
+
+        this.ethernetInterfaces[iface.name].updateConfig({
+          state:        Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
+          ip:           INTERFACE_IPADDR_NULL,
+          prefixLength: INTERFACE_PREFIX_NULL,
+          gateway:      INTERFACE_GATEWAY_NULL
+        });
+
+        Services.obs.notifyObservers(this.ethernetInterfaces[iface.name],
+                                     TOPIC_INTERFACE_STATE_CHANGED,
+                                     null);
+
+        debug("Disconnect interface " + iface.name + " success.");
+
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+      }.bind(this));
+    }.bind(this));
+  },
+
+  _checkConfigNull: function(iface) {
+    let ips = {};
+    let prefixLengths = {};
+    let gateways = iface.getGateways();
+    iface.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]) {
+      callback.notify(false, "Invalid interface.");
+      return
+    }
+
+    gNetworkService.runDhcp(ifname, function(success, result) {
+      if (!success) {
+        callback.notify(false, "Dhcp failed.");
+        return;
+      }
+
+      debug("Dhcp success 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.nsINetworkInterface.NETWORK_STATE_CONNECTED,
+        ip:           result.ip,
+        gateway:      result.gateway,
+        prefixLength: result.prefix,
+        dnses:        [result.dns1, result.dns2]
+      });
+
+      Services.obs.notifyObservers(this.ethernetInterfaces[ifname],
+                                   TOPIC_INTERFACE_STATE_CHANGED,
+                                   null);
+
+      debug("Connect interface " + ifname + "with dhcp success.");
+
+      callback.notify(true, "ok");
+    }.bind(this));
+  },
+
+  _setStaticIP: function(ifname, callback) {
+    let iface = this.ethernetInterfaces[ifname];
+    if (!iface) {
+      callback.notify(false, "Invalid interface.");
+      return;
+    }
+
+    let ips = {};
+    let prefixLengths = {};
+    iface.getAddresses(ips, prefixLengths);
+
+    let config = { ifname: iface.name,
+                   ip:     ips.value[0],
+                   prefix: prefixLengths.value[0],
+                   link:   NETWORK_INTERFACE_UP };
+    gNetworkService.setInterfaceConfig(config, function(success) {
+      if (!success) {
+        callback.notify(false, "Netd Error.");
+        return;
+      }
+
+      // Keep the lastest static network information.
+      let ips = {};
+      let prefixLengths = {};
+      let gateways = iface.getGateways();
+      iface.getAddresses(ips, prefixLengths);
+
+      this.lastStaticConfig[iface.name] = {
+        ip:           ips.value[0],
+        prefixLength: prefixLengths.value[0],
+        gateway:      gateways[0]
+      };
+
+      this.ethernetInterfaces[ifname].updateConfig({
+        state: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED,
+      });
+
+      Services.obs.notifyObservers(this.ethernetInterfaces[ifname],
+                                   TOPIC_INTERFACE_STATE_CHANGED,
+                                   null);
+
+      debug("Connect interface " + ifname + "with static ip success.");
+
+      callback.notify(true, "ok");
+    }.bind(this));
+  },
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EthernetManager]);
new file mode 100644
--- /dev/null
+++ b/dom/network/src/EthernetManager.manifest
@@ -0,0 +1,2 @@
+component {a96441dd-36b3-4f7f-963b-2c032e28a039} EthernetManager.js
+contract @mozilla.org/ethernetManager;1 {a96441dd-36b3-4f7f-963b-2c032e28a039}
--- a/dom/network/src/moz.build
+++ b/dom/network/src/moz.build
@@ -39,16 +39,18 @@ EXTRA_COMPONENTS += [
 ]
 
 EXTRA_PP_COMPONENTS += [
     'TCPSocket.js',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     EXTRA_COMPONENTS += [
+        'EthernetManager.js',
+        'EthernetManager.manifest',
         'NetworkStatsManager.js',
         'NetworkStatsManager.manifest',
         'NetworkStatsServiceProxy.js',
         'NetworkStatsServiceProxy.manifest',
     ]
     EXPORTS.mozilla.dom.network += [
         'NetUtils.h',
     ]
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -8,19 +8,19 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/systemlibs.js");
 
 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.nsINetworkInterface.NETWORK_TYPE_WIFI;
+const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET;
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
   return Cc["@mozilla.org/parentprocessmessagemanager;1"]
          .getService(Ci.nsIMessageBroadcaster);
 });
@@ -271,17 +271,18 @@ NetworkManager.prototype = {
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
               this.removeSecondaryDefaultRoute(network);
             }
 #endif
             // Remove routing table in /proc/net/route
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
               gNetworkService.resetRoutingTable(network);
 #ifdef MOZ_B2G_RIL
-            } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
+            } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
+                       network.type == Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) {
               gNetworkService.removeDefaultRoute(network);
 #endif
             }
 
             // Abort ongoing captive portal detection on the wifi interface
             CaptivePortalDetectionHelper
               .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, network);
             this.setAndConfigureActive();
@@ -362,17 +363,19 @@ NetworkManager.prototype = {
         return interfaces;
       }
     }
   },
 
   getNetworkId: function(network) {
     let id = "device";
 #ifdef MOZ_B2G_RIL
-    if (this.isNetworkTypeMobile(network.type)) {
+    if (this.isNetworkTypeEthernet(network.type)) {
+      id = network.name.substring(3);
+    } else if (this.isNetworkTypeMobile(network.type)) {
       if (!(network instanceof Ci.nsIRilNetworkInterface)) {
         throw Components.Exception("Mobile network not an nsIRilNetworkInterface",
                                    Cr.NS_ERROR_INVALID_ARG);
       }
       id = "ril" + network.serviceId;
     }
 #endif
 
@@ -414,27 +417,61 @@ NetworkManager.prototype = {
   },
 
   _manageOfflineStatus: true,
 
   networkInterfaces: null,
 
   _dataDefaultServiceId: 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);
+  },
+
   _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE,
   get preferredNetworkType() {
     return this._preferredNetworkType;
   },
   set preferredNetworkType(val) {
-#ifdef MOZ_B2G_RIL
     if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
-         Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE].indexOf(val) == -1) {
-#else
-    if (val != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
-#endif
+         Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+         Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) {
       throw "Invalid network type";
     }
     this._preferredNetworkType = val;
   },
 
   active: null,
   _overriddenActive: null,
 
@@ -456,16 +493,20 @@ NetworkManager.prototype = {
             type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN);
   },
 
   isNetworkTypeMobile: function(type) {
     return (type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
             this.isNetworkTypeSecondaryMobile(type));
   },
 
+  isNetworkTypeEthernet: function(type) {
+    return (type == Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET);
+  },
+
   setExtraHostRoute: function(network) {
     if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) {
       if (!(network instanceof Ci.nsIRilNetworkInterface)) {
         debug("Network for MMS must be an instance of nsIRilNetworkInterface");
         return;
       }
 
       network = network.QueryInterface(Ci.nsIRilNetworkInterface);
@@ -596,21 +637,33 @@ NetworkManager.prototype = {
       if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
         continue;
       }
 #ifdef MOZ_B2G_RIL
       if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
         defaultDataNetwork = network;
       }
 #endif
-      this.active = network;
       if (network.type == this.preferredNetworkType) {
+        this.active = network;
         debug("Found our preferred type of network: " + network.name);
         break;
       }
+
+      // Initialize the active network with the first connected network.
+      if (!this.active) {
+        this.active = network;
+        continue;
+      }
+
+      // Compare the prioriy between two network types. If found incoming
+      // network with higher priority, replace the active network.
+      if (this.getPriority(this.active.type) < this.getPriority(network.type)) {
+        this.active = network;
+      }
     }
     if (this.active) {
 #ifdef MOZ_B2G_RIL
       // Give higher priority to default data APN than seconary APN.
       // If default data APN is not connected, we still set default route
       // and DNS on seconary APN.
       if (defaultDataNetwork &&
           this.isNetworkTypeSecondaryMobile(this.active.type) &&
@@ -675,29 +728,32 @@ NetworkManager.prototype = {
   },
 #endif
 
   convertConnectionType: function(network) {
     // 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 (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI &&
-        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
+        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
+        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) {
       return null;
     }
 
     if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED) {
       return CONNECTION_TYPE_NONE;
     }
 
     switch (network.type) {
       case Ci.nsINetworkInterface.NETWORK_TYPE_WIFI:
         return CONNECTION_TYPE_WIFI;
       case Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE:
         return CONNECTION_TYPE_CULLULAR;
+      case Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET:
+        return CONNECTION_TYPE_ETHERNET;
     }
   },
 
   // nsISettingsServiceCallback
 
   tetheringSettings: {},
 
   initTetheringSettings: function() {
--- 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}
--- a/dom/system/gonk/nsINetworkManager.idl
+++ b/dom/system/gonk/nsINetworkManager.idl
@@ -4,24 +4,26 @@
 
 #include "nsISupports.idl"
 
 interface nsIWifiTetheringCallback;
 
 /**
  * Information about networks that is exposed to network manager API consumers.
  */
-[scriptable, uuid(cb62ae03-6bda-43ff-9560-916d60203d33)]
+[scriptable, uuid(8f9ab9e0-72c1-4874-80c7-8143353b979f)]
 interface nsINetworkInterface : nsISupports
 {
   const long NETWORK_STATE_UNKNOWN = -1;
   const long NETWORK_STATE_CONNECTING = 0;
   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.
    *
    * When this changes, network interface implementations notify the
    * 'network-interface-state-changed' observer notification.
    */
   readonly attribute long state;
@@ -29,16 +31,17 @@ interface nsINetworkInterface : nsISuppo
   const long NETWORK_TYPE_UNKNOWN     = -1;
   const long NETWORK_TYPE_WIFI        = 0;
   const long NETWORK_TYPE_MOBILE      = 1;
   const long NETWORK_TYPE_MOBILE_MMS  = 2;
   const long NETWORK_TYPE_MOBILE_SUPL = 3;
   const long NETWORK_TYPE_WIFI_P2P    = 4;
   const long NETWORK_TYPE_MOBILE_IMS  = 5;
   const long NETWORK_TYPE_MOBILE_DUN  = 6;
+  const long NETWORK_TYPE_ETHERNET    = 7;
 
   /**
    * Network type. One of the NETWORK_TYPE_* constants.
    */
   readonly attribute long type;
 
   /**
    * Name of the network interface. This identifier is unique.
@@ -92,17 +95,17 @@ interface nsINetworkInterface : nsISuppo
    */
   void getDnses([optional] out unsigned long count,
                 [array, size_is(count), retval] out wstring dnses);
 };
 
 /**
  * Manage network interfaces.
  */
-[scriptable, uuid(3ea50550-4b3c-11e3-8f96-0800200c9a66)]
+[scriptable, uuid(1ba9346b-53b5-4660-9dc6-58f0b258d0a6)]
 interface nsINetworkManager : nsISupports
 {
   /**
    * Register the given network interface with the network manager.
    *
    * Consumers will be notified with the 'network-interface-registered'
    * observer notification.
    *
@@ -131,16 +134,25 @@ interface nsINetworkManager : nsISupport
    * Object containing all known network connections, keyed by their
    * network id. Network id is composed of a sub-id + '-' + network
    * type. For mobile network types, sub-id is 'ril' + service id; for
    * non-mobile network types, sub-id is always 'device'.
    */
   readonly attribute jsval networkInterfaces;
 
   /**
+   * 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.
    *
    * This attribute is used for setting default route to favor
    * interfaces with given type.  This can be overriden by calling
    * overrideDefaultRoute().
    */
   attribute long preferredNetworkType;