diff options
author | Paul Jensen <pauljensen@google.com> | 2014-05-09 12:47:55 -0400 |
---|---|---|
committer | Lorenzo Colitti <lorenzo@google.com> | 2014-05-14 03:56:34 -0700 |
commit | ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7 (patch) | |
tree | 1e20685b6f4d6bb1581f89d10288504c4698cc6e /services | |
parent | 992f25257938ecc0378514f21c6e6e6375272976 (diff) | |
download | frameworks_base-ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7.zip frameworks_base-ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7.tar.gz frameworks_base-ca8f16ad14819ba17f5ff3d2e2bf6fbc9bbaa9f7.tar.bz2 |
Add NetworkMonitor.
At present the network evaluation / captive portal detection
is disabled pending addition of API to bind socket to network.
Change-Id: I5d1f5dc86d4dd9481d52dd45d6da0732054c8315
Diffstat (limited to 'services')
3 files changed, 440 insertions, 23 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6cc738b..752dce9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -31,7 +31,6 @@ import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.ConnectivityServiceProtocol.NetworkFactoryProtocol; -import static android.net.ConnectivityServiceProtocol.NetworkMonitorProtocol; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; @@ -131,6 +130,7 @@ import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; +import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.PacManager; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; @@ -2932,12 +2932,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateNetworkInfo(nai, info); break; } - case NetworkMonitorProtocol.EVENT_NETWORK_VALIDATED: { + case NetworkMonitor.EVENT_NETWORK_VALIDATED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; handleConnectionValidated(nai); break; } - case NetworkMonitorProtocol.EVENT_NETWORK_LINGER_COMPLETE: { + case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; handleLingerComplete(nai); break; @@ -3068,21 +3068,39 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Exception removing network: " + e); } notifyNetworkCallbacks(nai, NetworkCallbacks.LOST); + nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED); mNetworkAgentInfos.remove(nai); // Since we've lost the network, go through all the requests that // it was satisfying and see if any other factory can satisfy them. + final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>(); for (int i = 0; i < nai.networkRequests.size(); i++) { NetworkRequest request = nai.networkRequests.valueAt(i); NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId); if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) { mNetworkForRequestId.remove(request.requestId); - // TODO Check if any other live network will work sendUpdatedScoreToFactories(request, 0); + NetworkAgentInfo alternative = null; + for (Map.Entry entry : mNetworkAgentInfos.entrySet()) { + NetworkAgentInfo existing = (NetworkAgentInfo)entry.getValue(); + if (existing.networkInfo.isConnected() && + request.networkCapabilities.satisfiedByNetworkCapabilities( + existing.networkCapabilities) && + (alternative == null || + alternative.currentScore < existing.currentScore)) { + alternative = existing; + } + } + if (alternative != null && !toActivate.contains(alternative)) { + toActivate.add(alternative); + } } } if (nai.networkRequests.get(mDefaultRequest.requestId) != null) { removeDataActivityTracking(nai); } + for (NetworkAgentInfo networkToActivate : toActivate) { + networkToActivate.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + } } } @@ -5070,7 +5088,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), nextNetId(), new NetworkInfo(networkInfo), new LinkProperties(linkProperties), - new NetworkCapabilities(networkCapabilities), currentScore); + new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai)); } @@ -5238,14 +5256,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { currentNetwork.networkRequests.remove(nr.requestId); currentNetwork.networkListens.add(nr); if (currentNetwork.networkRequests.size() == 0) { - // TODO tell current Network to go to linger state - - // fake the linger state: - Message message = Message.obtain(); - message.obj = currentNetwork; - message.what = NetworkMonitorProtocol.EVENT_NETWORK_LINGER_COMPLETE; - mTrackerHandler.sendMessage(message); - + currentNetwork.networkMonitor.sendMessage( + NetworkMonitor.CMD_NETWORK_LINGER); notifyNetworkCallbacks(currentNetwork, NetworkCallbacks.LOSING); } } @@ -5301,7 +5313,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { //BatteryStatsService.getService().noteNetworkInterfaceType(iface, netType); // } catch (RemoteException e) { } notifyNetworkCallbacks(newNetwork, NetworkCallbacks.AVAILABLE); - } else { + } else if (newNetwork.networkRequests.size() == 0) { if (VDBG) log("Validated network turns out to be unwanted. Tear it down."); newNetwork.asyncChannel.disconnect(); } @@ -5331,13 +5343,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } updateLinkProperties(networkAgent, null); notifyNetworkCallbacks(networkAgent, NetworkCallbacks.PRECHECK); - // TODO - kick the network monitor - - // Fake things by sending self a NETWORK_VALIDATED msg - Message message = Message.obtain(); - message.obj = networkAgent; - message.what = NetworkMonitorProtocol.EVENT_NETWORK_VALIDATED; - mTrackerHandler.sendMessage(message); + networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) { networkAgent.asyncChannel.disconnect(); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 4747487..0c568b7 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -16,15 +16,18 @@ package com.android.server.connectivity; +import android.content.Context; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; +import android.os.Handler; import android.os.Messenger; import android.util.SparseArray; import com.android.internal.util.AsyncChannel; +import com.android.server.connectivity.NetworkMonitor; import java.util.ArrayList; @@ -40,6 +43,8 @@ public class NetworkAgentInfo { public LinkProperties linkProperties; public NetworkCapabilities networkCapabilities; public int currentScore; + public final NetworkMonitor networkMonitor; + // The list of NetworkRequests being satisfied by this Network. public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>(); @@ -50,7 +55,8 @@ public class NetworkAgentInfo { public final AsyncChannel asyncChannel; public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, int netId, NetworkInfo info, - LinkProperties lp, NetworkCapabilities nc, int score) { + LinkProperties lp, NetworkCapabilities nc, int score, Context context, + Handler handler) { this.messenger = messenger; asyncChannel = ac; network = new Network(netId); @@ -58,7 +64,7 @@ public class NetworkAgentInfo { linkProperties = lp; networkCapabilities = nc; currentScore = score; - + networkMonitor = new NetworkMonitor(context, handler, this); } public String toString() { diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java new file mode 100644 index 0000000..47789b1 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.content.Context; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.Message; +import android.os.SystemProperties; +import android.provider.Settings; + +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.connectivity.NetworkAgentInfo; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URL; + +/** + * {@hide} + */ +public class NetworkMonitor extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "NetworkMonitor"; + private static final String DEFAULT_SERVER = "clients3.google.com"; + private static final int SOCKET_TIMEOUT_MS = 10000; + + private static final int BASE = Protocol.BASE_NETWORK_MONITOR; + + /** + * Inform NetworkMonitor that their network is connected. + * Initiates Network Validation. + */ + public static final int CMD_NETWORK_CONNECTED = BASE + 1; + + /** + * Inform ConnectivityService that the network is validated. + * obj = NetworkAgentInfo + */ + public static final int EVENT_NETWORK_VALIDATED = BASE + 2; + + /** + * Inform NetworkMonitor to linger a network. The Monitor should + * start a timer and/or start watching for zero live connections while + * moving towards LINGER_COMPLETE. After the Linger period expires + * (or other events mark the end of the linger state) the LINGER_COMPLETE + * event should be sent and the network will be shut down. If a + * CMD_NETWORK_CONNECTED happens before the LINGER completes + * it indicates further desire to keep the network alive and so + * the LINGER is aborted. + */ + public static final int CMD_NETWORK_LINGER = BASE + 3; + + /** + * Message to self indicating linger delay has expired. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_LINGER_EXPIRED = BASE + 4; + + /** + * Inform ConnectivityService that the network LINGER period has + * expired. + * obj = NetworkAgentInfo + */ + public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5; + + /** + * Message to self indicating it's time to check for a captive portal again. + * TODO - Remove this once broadcast intents are used to communicate with + * apps to log into captive portals. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_CAPTIVE_PORTAL_REEVALUATE = BASE + 6; + + /** + * Message to self indicating it's time to evaluate a network's connectivity. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_REEVALUATE = BASE + 7; + + /** + * Message to self indicating network evaluation is complete. + * arg1 = Token to ignore old messages. + * arg2 = HTTP response code of network evaluation. + */ + private static final int EVENT_REEVALUATION_COMPLETE = BASE + 8; + + /** + * Inform NetworkMonitor that the network has disconnected. + */ + public static final int CMD_NETWORK_DISCONNECTED = BASE + 9; + + /** + * Force evaluation even if it has succeeded in the past. + */ + public static final int CMD_FORCE_REEVALUATION = BASE + 10; + + private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; + // Default to 30s linger time-out. + private static final int DEFAULT_LINGER_DELAY_MS = 30000; + private final int mLingerDelayMs; + private int mLingerToken = 0; + + private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 5000; + private int mCaptivePortalReevaluateToken = 0; + + // Negative values disable reevaluation. + private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay"; + // Default to 5s reevaluation delay. + private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000; + private final int mReevaluateDelayMs; + private int mReevaluateToken = 0; + + private final Context mContext; + private final Handler mConnectivityServiceHandler; + private final NetworkAgentInfo mNetworkAgentInfo; + + private String mServer; + private boolean mIsCaptivePortalCheckEnabled = false; + + private State mDefaultState = new DefaultState(); + private State mOfflineState = new OfflineState(); + private State mValidatedState = new ValidatedState(); + private State mEvaluatingState = new EvaluatingState(); + private State mCaptivePortalState = new CaptivePortalState(); + private State mLingeringState = new LingeringState(); + + public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) { + // Add suffix indicating which NetworkMonitor we're talking about. + super(TAG + networkAgentInfo.name()); + + mContext = context; + mConnectivityServiceHandler = handler; + mNetworkAgentInfo = networkAgentInfo; + + addState(mDefaultState); + addState(mOfflineState, mDefaultState); + addState(mValidatedState, mDefaultState); + addState(mEvaluatingState, mDefaultState); + addState(mCaptivePortalState, mDefaultState); + addState(mLingeringState, mDefaultState); + setInitialState(mOfflineState); + + mServer = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_SERVER); + if (mServer == null) mServer = DEFAULT_SERVER; + + mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); + mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY, + DEFAULT_REEVALUATE_DELAY_MS); + + // TODO: Enable this when we're ready. + // mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), + // Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + + start(); + } + + private class DefaultState extends State { + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_NETWORK_LINGER: + if (DBG) log("Lingering"); + transitionTo(mLingeringState); + break; + case CMD_NETWORK_CONNECTED: + if (DBG) log("Connected"); + transitionTo(mEvaluatingState); + break; + case CMD_NETWORK_DISCONNECTED: + if (DBG) log("Disconnected"); + transitionTo(mOfflineState); + break; + case CMD_FORCE_REEVALUATION: + if (DBG) log("Forcing reevaluation"); + transitionTo(mEvaluatingState); + break; + default: + break; + } + return HANDLED; + } + } + + private class OfflineState extends State { + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + return NOT_HANDLED; + } + } + + private class ValidatedState extends State { + @Override + public void enter() { + if (DBG) log("Validated"); + mConnectivityServiceHandler.sendMessage( + obtainMessage(EVENT_NETWORK_VALIDATED, mNetworkAgentInfo)); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_NETWORK_CONNECTED: + transitionTo(mValidatedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class EvaluatingState extends State { + private class EvaluateInternetConnectivity extends Thread { + private int mToken; + EvaluateInternetConnectivity(int token) { + mToken = token; + } + public void run() { + sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal()); + } + } + + @Override + public void enter() { + sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_REEVALUATE: + if (message.arg1 != mReevaluateToken) + break; + // If network provides no internet connectivity adjust evaluation. + if (mNetworkAgentInfo.networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + // TODO: Try to verify something works. Do all gateways respond to pings? + transitionTo(mValidatedState); + } + // Kick off a thread to perform internet connectivity evaluation. + Thread thread = new EvaluateInternetConnectivity(mReevaluateToken); + thread.run(); + break; + case EVENT_REEVALUATION_COMPLETE: + if (message.arg1 != mReevaluateToken) + break; + int httpResponseCode = message.arg2; + if (httpResponseCode == 204) { + transitionTo(mValidatedState); + } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { + transitionTo(mCaptivePortalState); + } else { + if (mReevaluateDelayMs >= 0) { + Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + sendMessageDelayed(msg, mReevaluateDelayMs); + } + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + // TODO: Until we add an intent from the app handling captive portal + // login we'll just re-evaluate after a delay. + private class CaptivePortalState extends State { + @Override + public void enter() { + Message message = obtainMessage(CMD_CAPTIVE_PORTAL_REEVALUATE, + ++mCaptivePortalReevaluateToken, 0); + sendMessageDelayed(message, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_CAPTIVE_PORTAL_REEVALUATE: + if (message.arg1 != mCaptivePortalReevaluateToken) + break; + transitionTo(mEvaluatingState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class LingeringState extends State { + @Override + public void enter() { + Message message = obtainMessage(CMD_LINGER_EXPIRED, ++mLingerToken, 0); + sendMessageDelayed(message, mLingerDelayMs); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_NETWORK_CONNECTED: + // Go straight to active as we've already evaluated. + transitionTo(mValidatedState); + break; + case CMD_LINGER_EXPIRED: + if (message.arg1 != mLingerToken) + break; + mConnectivityServiceHandler.sendMessage( + obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo)); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + /** + * Do a URL fetch on a known server to see if we get the data we expect. + * Returns HTTP response code. + */ + private int isCaptivePortal() { + if (!mIsCaptivePortalCheckEnabled) return 204; + + String urlString = "http://" + mServer + "/generate_204"; + if (DBG) log("Checking " + urlString); + HttpURLConnection urlConnection = null; + Socket socket = null; + int httpResponseCode = 500; + try { + URL url = new URL(urlString); + if (false) { + // TODO: Need to add URLConnection.setNetwork() before we can enable. + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + httpResponseCode = urlConnection.getResponseCode(); + } else { + socket = new Socket(); + // TODO: setNetworkForSocket(socket, mNetworkAgentInfo.network.netId); + InetSocketAddress address = new InetSocketAddress(url.getHost(), 80); + // TODO: address = new InetSocketAddress( + // getByNameOnNetwork(mNetworkAgentInfo.network, url.getHost()), 80); + socket.connect(address); + BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream())); + OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream()); + writer.write("GET " + url.getFile() + " HTTP/1.1\r\n\n"); + writer.flush(); + String response = reader.readLine(); + if (response.startsWith("HTTP/1.1 ")) { + httpResponseCode = Integer.parseInt(response.substring(9, 12)); + } + } + if (DBG) log("isCaptivePortal: ret=" + httpResponseCode); + } catch (IOException e) { + if (DBG) log("Probably not a portal: exception " + e); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // Ignore + } + } + } + return httpResponseCode; + } +} |