diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js index 4ae69ec3834..57e071e3c90 100644 --- a/dom/system/gonk/NetworkManager.js +++ b/dom/system/gonk/NetworkManager.js @@ -85,6 +85,46 @@ function defineLazyRegExp(obj, name, pattern) { }); } +function NetworkInterface(aNetwork) { + let ips = {}; + let prefixLengths = {}; + aNetwork.getAddresses(ips, prefixLengths); + + this.state = aNetwork.state; + this.type = aNetwork.type; + this.name = aNetwork.name; + this.ips = ips.value; + this.prefixLengths = prefixLengths.value; + this.gateways = aNetwork.getGateways(); + this.dnses = aNetwork.getDnses(); + this.httpProxyHost = aNetwork.httpProxyHost; + this.httpProxyPort = aNetwork.httpProxyPort; +} +NetworkInterface.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]), + + getAddresses: function(aIps, aPrefixLengths) { + aIps.value = this.ips.slice(); + aPrefixLengths.value = this.prefixLengths.slice(); + + return this.ips.length; + }, + + getGateways: function(aCount) { + if (aCount) { + aCount.value = this.gateways.length; + } + return this.gateways.slice(); + }, + + getDnses: function(aCount) { + if (aCount) { + aCount.value = this.dnses.length; + } + return this.dnses.slice(); + } +}; + function NetworkInterfaceLinks() { this.resetLinks(); @@ -254,14 +294,19 @@ NetworkManager.prototype = { let ips = {}; let prefixLengths = {}; let length = network.getAddresses(ips, prefixLengths); + let promises = []; + for (let i = 0; i < length; i++) { debug('Adding subnet routes: ' + ips.value[i] + '/' + prefixLengths.value[i]); - gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_ADD, - network.name, ips.value[i], prefixLengths.value[i]) - .catch((aError) => { + promises.push( + gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_ADD, + network.name, ips.value[i], prefixLengths.value[i]) + .catch(aError => { debug("_addSubnetRoutes error: " + aError); - }); + })); } + + return Promise.all(promises); }, updateNetworkInterface: function(network) { @@ -277,46 +322,57 @@ NetworkManager.prototype = { debug("Network " + network.type + "/" + network.name + " changed state to " + network.state); + // Keep a copy of network in case it is modified while we are updating. + let networkInterface = new NetworkInterface(network); + // Note that since Lollipop we need to allocate and initialize // something through netd, so we add createNetwork/destroyNetwork // to deal with that explicitly. - switch (network.state) { + switch (networkInterface.state) { case Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED: - gNetworkService.createNetwork(network.name, () => { + this._createNetwork(networkInterface.name) // Remove pre-created default route and let setAndConfigureActive() // to set default route only on preferred network - gNetworkService.removeDefaultRoute(network); - + .then(() => this._removeDefaultRoute(networkInterface)) // Set DNS server as early as possible to prevent from // premature domain name lookup. - gNetworkService.setDNS(network, () => { + .then(() => this._setDNS(networkInterface)) + .then(() => { // Add host route for data calls - if (this.isNetworkTypeMobile(network.type)) { - let currentInterfaceLinks = this.networkInterfaceLinks[networkId]; - let newLinkRoutes = network.getDnses().concat(network.httpProxyHost); - // If gateways have changed, remove all old routes first. - this._handleGateways(networkId, network.getGateways()) - .then(() => this._updateRoutes(currentInterfaceLinks.linkRoutes, - newLinkRoutes, - network.getGateways(), network.name)) - .then(() => currentInterfaceLinks.setLinks(newLinkRoutes, - network.getGateways(), - network.name)); + if (!this.isNetworkTypeMobile(networkInterface.type)) { + return; } + let currentInterfaceLinks = this.networkInterfaceLinks[networkId]; + let newLinkRoutes = networkInterface.getDnses().concat( + networkInterface.httpProxyHost); + // If gateways have changed, remove all old routes first. + return this._handleGateways(networkId, networkInterface.getGateways()) + .then(() => this._updateRoutes(currentInterfaceLinks.linkRoutes, + newLinkRoutes, + networkInterface.getGateways(), + networkInterface.name)) + .then(() => currentInterfaceLinks.setLinks(newLinkRoutes, + networkInterface.getGateways(), + networkInterface.name)); + }) + .then(() => { + if (networkInterface.type != + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { + return; + } // Dun type is a special case where we add the default route to a // secondary table. - if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { - this.setSecondaryDefaultRoute(network); - } - - this._addSubnetRoutes(network); - this.setAndConfigureActive(); - + return this.setSecondaryDefaultRoute(networkInterface); + }) + .then(() => this._addSubnetRoutes(networkInterface)) + .then(() => this.setAndConfigureActive()) + .then(() => { // Update data connection when Wifi connected/disconnected - if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && this.mRil) { + if (networkInterface.type == + Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && this.mRil) { for (let i = 0; i < this.mRil.numRadioInterfaces; i++) { this.mRil.getRadioInterface(i).updateRILNetworkInterface(); } @@ -325,53 +381,73 @@ NetworkManager.prototype = { // Probing the public network accessibility after routing table is ready CaptivePortalDetectionHelper .notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active); - + }) + .then(() => { // Notify outer modules like MmsService to start the transaction after // the configuration of the network interface is done. Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED, this.convertConnectionType(network)); + }) + .catch(aError => { + debug("updateNetworkInterface error: " + aError); }); - }); - break; case Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED: - // Remove host route for data calls - if (this.isNetworkTypeMobile(network.type)) { - this._cleanupAllHostRoutes(networkId); - } - // Remove secondary default route for dun. - if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { - this.removeSecondaryDefaultRoute(network); - } - // Remove routing table in /proc/net/route - if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { - gNetworkService.resetRoutingTable(network); - } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { - gNetworkService.removeDefaultRoute(network); - } - // Clear http proxy on active network. - if (this.active && network.type == this.active.type) { - this.clearNetworkProxy(); - } + Promise.resolve() + .then(() => { + if (!this.isNetworkTypeMobile(networkInterface.type)) { + return; + } + // Remove host route for data calls + return this._cleanupAllHostRoutes(networkId); + }) + .then(() => { + if (networkInterface.type != + Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { + return; + } + // Remove secondary default route for dun. + return this.removeSecondaryDefaultRoute(networkInterface); + }) + .then(() => { + if (networkInterface.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { + // Remove routing table in /proc/net/route + return this._resetRoutingTable(networkInterface); + } + if (networkInterface.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { + return this._removeDefaultRoute(networkInterface) + } + }) + .then(() => { + // Clear http proxy on active network. + if (this.active && networkInterface.type == this.active.type) { + this.clearNetworkProxy(); + } - // Abort ongoing captive portal detection on the wifi interface - CaptivePortalDetectionHelper - .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, network); - this.setAndConfigureActive(); - - // Update data connection when Wifi connected/disconnected - if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && this.mRil) { - for (let i = 0; i < this.mRil.numRadioInterfaces; i++) { - this.mRil.getRadioInterface(i).updateRILNetworkInterface(); - } - } - - gNetworkService.destroyNetwork(network.name, () => { - // Notify outer modules like MmsService to start the transaction after - // the configuration of the network interface is done. - Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED, - this.convertConnectionType(network)); - }); + // Abort ongoing captive portal detection on the wifi interface + CaptivePortalDetectionHelper + .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, networkInterface); + }) + .then(() => this.setAndConfigureActive()) + .then(() => { + // Update data connection when Wifi connected/disconnected + if (networkInterface.type == + Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && this.mRil) { + for (let i = 0; i < this.mRil.numRadioInterfaces; i++) { + this.mRil.getRadioInterface(i).updateRILNetworkInterface(); + } + } + }) + .then(() => this._destroyNetwork(networkInterface.name)) + .then(() => { + // Notify outer modules like MmsService to start the transaction after + // the configuration of the network interface is done. + Services.obs.notifyObservers(network, TOPIC_CONNECTION_STATE_CHANGED, + this.convertConnectionType(network)); + }) + .catch(aError => { + debug("updateNetworkInterface error: " + aError); + }); break; } }, @@ -599,45 +675,86 @@ NetworkManager.prototype = { return null; }, + _setSecondaryRoute: function(aDoAdd, aInterfaceName, aRoute) { + return new Promise((aResolve, aReject) => { + if (aDoAdd) { + gNetworkService.addSecondaryRoute(aInterfaceName, aRoute, + (aSuccess) => { + if (!aSuccess) { + aReject("addSecondaryRoute failed"); + return; + } + aResolve(); + }); + } else { + gNetworkService.removeSecondaryRoute(aInterfaceName, aRoute, + (aSuccess) => { + if (!aSuccess) { + debug("removeSecondaryRoute failed") + } + // Always resolve. + aResolve(); + }); + } + }); + }, + setSecondaryDefaultRoute: function(network) { let gateways = network.getGateways(); + let promises = []; + for (let i = 0; i < gateways.length; i++) { let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false; // First, we need to add a host route to the gateway in the secondary // routing table to make the gateway reachable. Host route takes the max // prefix and gateway address 'any'. - let route = { + let hostRoute = { ip: gateways[i], prefix: isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH, gateway: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY }; - gNetworkService.addSecondaryRoute(network.name, route); // Now we can add the default route through gateway. Default route takes the // min prefix and destination ip 'any'. - route.ip = isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY; - route.prefix = 0; - route.gateway = gateways[i]; - gNetworkService.addSecondaryRoute(network.name, route); - } - }, - - removeSecondaryDefaultRoute: function(network) { - let gateways = network.getGateways(); - for (let i = 0; i < gateways.length; i++) { - let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false; - // Remove both default route and host route. - let route = { + let defaultRoute = { ip: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY, prefix: 0, gateway: gateways[i] }; - gNetworkService.removeSecondaryRoute(network.name, route); - route.ip = gateways[i]; - route.prefix = isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH; - route.gateway = isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY; - gNetworkService.removeSecondaryRoute(network.name, route); + let promise = this._setSecondaryRoute(true, network.name, hostRoute) + .then(() => this._setSecondaryRoute(true, network.name, defaultRoute)); + + promises.push(promise); } + + return Promise.all(promises); + }, + + removeSecondaryDefaultRoute: function(network) { + let gateways = network.getGateways(); + let promises = []; + + for (let i = 0; i < gateways.length; i++) { + let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false; + // Remove both default route and host route. + let defaultRoute = { + ip: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY, + prefix: 0, + gateway: gateways[i] + }; + let hostRoute = { + ip: gateways[i], + prefix: isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH, + gateway: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY + }; + + let promise = this._setSecondaryRoute(false, network.name, defaultRoute) + .then(() => this._setSecondaryRoute(false, network.name, hostRoute)); + + promises.push(promise); + } + + return Promise.all(promises); }, /** @@ -664,8 +781,7 @@ NetworkManager.prototype = { this.active.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED && this.active.type == this._preferredNetworkType) { debug("Active network is already our preferred type."); - this._setDefaultRouteAndProxy(this.active, oldActive); - return; + return this._setDefaultRouteAndProxy(this.active, oldActive); } // Find a suitable network interface to activate. @@ -688,28 +804,33 @@ NetworkManager.prototype = { } } - if (this.active) { - // Give higher priority to default data APN than secondary APN. - // If default data APN is not connected, we still set default route - // and DNS on secondary APN. - if (defaultDataNetwork && - this.isNetworkTypeSecondaryMobile(this.active.type) && - this.active.type != this.preferredNetworkType) { - this.active = defaultDataNetwork; - } - // Don't set default route on secondary APN - if (!this.isNetworkTypeSecondaryMobile(this.active.type)) { - this._setDefaultRouteAndProxy(this.active, oldActive); - } + // Give higher priority to default data APN than secondary APN. + // If default data APN is not connected, we still set default route + // and DNS on secondary APN. + if (this.active && defaultDataNetwork && + this.isNetworkTypeSecondaryMobile(this.active.type) && + this.active.type != this.preferredNetworkType) { + this.active = defaultDataNetwork; } - if (this.active != oldActive) { - Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null); - } + return Promise.resolve() + .then(() => { + // Don't set default route on secondary APN + if (!this.active || this.isNetworkTypeSecondaryMobile(this.active.type)) { + return Promise.resolve(); + } - if (this._manageOfflineStatus) { - Services.io.offline = !this.active; - } + return this._setDefaultRouteAndProxy(this.active, oldActive); + }) + .then(() => { + if (this.active != oldActive) { + Services.obs.notifyObservers(this.active, TOPIC_ACTIVE_CHANGED, null); + } + + if (this._manageOfflineStatus) { + Services.io.offline = !this.active; + } + }); }, resolveHostname: function(network, hostname) { @@ -788,13 +909,78 @@ NetworkManager.prototype = { } }, - _setDefaultRouteAndProxy: function(network, oldInterface) { - gNetworkService.setDefaultRoute(network, oldInterface, (success) => { - if (!success) { - gNetworkService.destroyNetwork(network, function() {}); - return; - } - this.setNetworkProxy(network); + _setDNS: function(aNetwork) { + return new Promise((aResolve, aReject) => { + gNetworkService.setDNS(aNetwork, (aError) => { + if (aError) { + aReject("setDNS failed"); + return; + } + aResolve(); + }); + }); + }, + + _createNetwork: function(aInterfaceName) { + return new Promise((aResolve, aReject) => { + gNetworkService.createNetwork(aInterfaceName, (aSuccess) => { + if (!aSuccess) { + aReject("createNetwork failed"); + return; + } + aResolve(); + }); + }); + }, + + _destroyNetwork: function(aInterfaceName) { + return new Promise((aResolve, aReject) => { + gNetworkService.destroyNetwork(aInterfaceName, (aSuccess) => { + if (!aSuccess) { + debug("destroyNetwork failed") + } + // Always resolve. + aResolve(); + }); + }); + }, + + _resetRoutingTable: function(aNetwork) { + return new Promise((aResolve, aReject) => { + gNetworkService.resetRoutingTable(aNetwork, (aSuccess) => { + if (!aSuccess) { + debug("resetRoutingTable failed"); + } + // Always resolve. + aResolve(); + }); + }); + }, + + _removeDefaultRoute: function(aNetwork) { + return new Promise((aResolve, aReject) => { + gNetworkService.removeDefaultRoute(aNetwork, (aSuccess) => { + if (!aSuccess) { + debug("removeDefaultRoute failed"); + } + // Always resolve. + aResolve(); + }); + }); + }, + + _setDefaultRouteAndProxy: function(aNetwork, aOldInterface) { + return new Promise((aResolve, aReject) => { + gNetworkService.setDefaultRoute(aNetwork, aOldInterface, (aSuccess) => { + if (!aSuccess) { + gNetworkService.destroyNetwork(aNetwork, function() { + aReject("setDefaultRoute failed"); + }); + return; + } + this.setNetworkProxy(aNetwork); + aResolve(); + }); }); }, diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index 5d03c4115b3..ee3883c051f 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -2703,13 +2703,13 @@ DataCall.prototype = { if (this.state == RIL.GECKO_NETWORK_STATE_CONNECTED) { // This needs to run asynchronously, to behave the same way as the case of // non-shared apn, see bug 1059110. - Services.tm.currentThread.dispatch(function(state) { + Services.tm.currentThread.dispatch(() => { // Do not notify if state changed while this event was being dispatched, // the state probably was notified already or need not to be notified. - if (networkInterface.state == state) { + if (networkInterface.state == RIL.GECKO_NETWORK_STATE_CONNECTED) { networkInterface.notifyRILNetworkInterface(); } - }.bind(null, RIL.GECKO_NETWORK_STATE_CONNECTED), Ci.nsIEventTarget.DISPATCH_NORMAL); + }, Ci.nsIEventTarget.DISPATCH_NORMAL); return; } @@ -2829,13 +2829,15 @@ DataCall.prototype = { // Notify the DISCONNECTED event immediately after network interface is // removed from requestedNetworkIfaces, to make the DataCall, shared or // not, to have the same behavior. - Services.tm.currentThread.dispatch(function(state) { + Services.tm.currentThread.dispatch(() => { // Do not notify if state changed while this event was being dispatched, // the state probably was notified already or need not to be notified. - if (networkInterface.state == state) { + if (networkInterface.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED) { networkInterface.notifyRILNetworkInterface(); + // Clear link info after notifying NetworkManager. + this.resetLinkInfo(); } - }.bind(null, RIL.GECKO_NETWORK_STATE_DISCONNECTED), Ci.nsIEventTarget.DISPATCH_NORMAL); + }, Ci.nsIEventTarget.DISPATCH_NORMAL); } // Only deactivate data call if no more network interface needs this @@ -2861,7 +2863,6 @@ DataCall.prototype = { }, this.onDeactivateDataCallResult.bind(this)); this.state = RIL.GECKO_NETWORK_STATE_DISCONNECTING; - this.resetLinkInfo(); }, // Entry method for timer events. Used to reconnect to a failed APN