Bug 1126222 - Part 2: Use promise to ensure execution order in NetworkManager. r=echen

This commit is contained in:
Jessica Jong 2015-05-07 00:57:00 -04:00
parent 7bc34d3788
commit 2ed08371fa
2 changed files with 309 additions and 122 deletions

View File

@ -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();
});
});
},

View File

@ -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