前言:本文采用倒叙梳理,之前梳理流程没记下来忘了,现在再来一遍,所以说梳理什么的还是做个备忘比较好。
看网络校验相关log经常能看到如下log打印
log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
这边相当于把校验结果上报上来了,具体怎么传到Settings后续再看,大概是更新的时候会判断一下Capabilities中包含哪种类型,比如是可上网、不能上网还是需要登录上网
对应代码
private boolean maybeHandleNetworkMonitorMessage(Message msg) { switch (msg.what) { default: return false; case NetworkMonitor.EVENT_NETWORK_TESTED: { final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); if (nai == null) break; final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID); final boolean wasValidated = nai.lastValidated; final boolean wasDefault = isDefaultNetwork(nai); final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; if (DBG) { final String logMsg = !TextUtils.isEmpty(redirectUrl) ? " with redirect to " + redirectUrl : ""; log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg); } if (valid != nai.lastValidated) { if (wasDefault) { metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity( SystemClock.elapsedRealtime(), valid); } final int oldScore = nai.getCurrentScore(); nai.lastValidated = valid; nai.everValidated |= valid; updateCapabilities(oldScore, nai, nai.networkCapabilities); // If score has changed, rebroadcast to NetworkFactories. b/17726566 if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); } updateInetCondition(nai); // Let the NetworkAgent know the state of its network Bundle redirectUrlBundle = new Bundle(); redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl); nai.asyncChannel.sendMessage( NetworkAgent.CMD_REPORT_NETWORK_STATUS, (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), 0, redirectUrlBundle); if (wasValidated && !nai.lastValidated) { handleNetworkUnvalidated(nai); } break; }这里可以看到是处理一个发来的消息,该消息为NetworkMonitor.EVENT_NETWORK_TESTED
搜了下NetworkMonitor.EVENT_NETWORK_TESTED就是NetworkMonitor自己发来的
jiatai@jiatai:~/expand/aosp/P_source/frameworks$ grep "EVENT_NETWORK_TESTED" ./ -nr --include="*.java" ./base/services/core/java/com/android/server/connectivity/NetworkMonitor.java:135: // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. ./base/services/core/java/com/android/server/connectivity/NetworkMonitor.java:140: // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. ./base/services/core/java/com/android/server/connectivity/NetworkMonitor.java:161: public static final int EVENT_NETWORK_TESTED = BASE + 2; ./base/services/core/java/com/android/server/connectivity/NetworkMonitor.java:392: EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj)); ./base/services/core/java/com/android/server/connectivity/NetworkMonitor.java:510: mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, ./base/services/core/java/com/android/server/ConnectivityService.java:2226: case NetworkMonitor.EVENT_NETWORK_TESTED: {当校验为NETWORK_TEST_RESULT_VALID,则表明该网络是可上网的或者是captive portal( 强制门户认证)的,但是注明是用户决定原样使用?
当校验是NETWORK_TEST_RESULT_INVALID,则表明该网络是不可上网的或者提示用户登录的或者用户不想使用的captive portal( 强制门户认证)
进入到ValidatedState就会发出来一个校验成功的消息,上面注释表明是成功校验或者用户原样使用,或者校验跳过会走到这里。
梳理流程之前先看下树的结构
addState(mDefaultState); addState(mMaybeNotifyState, mDefaultState); addState(mEvaluatingState, mMaybeNotifyState); addState(mCaptivePortalState, mMaybeNotifyState); addState(mEvaluatingPrivateDnsState, mDefaultState); addState(mValidatedState, mDefaultState); setInitialState(mDefaultState);有6个状态,大概是评估中和评估完成状态。
我们搜一下什么状态会transitionTo(mValidatedState)
// Being in the EvaluatingState State indicates the Network is being evaluated for internet // connectivity, or that the user has indicated that this network is unwanted. private class EvaluatingState extends State { private int mReevaluateDelayMs; private int mAttempts; @Override public void enter() { // If we have already started to track time spent in EvaluatingState // don't reset the timer due simply to, say, commands or events that // cause us to exit and re-enter EvaluatingState. if (!mEvaluationTimer.isStarted()) { mEvaluationTimer.start(); } sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); if (mUidResponsibleForReeval != INVALID_UID) { TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); mUidResponsibleForReeval = INVALID_UID; } mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; mAttempts = 0; } @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken || mUserDoesNotWant) return HANDLED; // Don't bother validating networks that don't satisfy the default request. // This includes: // - VPNs which can be considered explicitly desired by the user and the // user's desire trumps whether the network validates. // - Networks that don't provide Internet access. It's unclear how to // validate such networks. // - Untrusted networks. It's unsafe to prompt the user to sign-in to // such networks and the user didn't express interest in connecting to // such networks (an app did) so the user may be unhappily surprised when // asked to sign-in to a network they didn't want to connect to in the // first place. Validation could be done to adjust the network scores // however these networks are app-requested and may not be intended for // general usage, in which case general validation may not be an accurate // measure of the network's quality. Only the app knows how to evaluate // the network so don't bother validating here. Furthermore sending HTTP // packets over the network may be undesirable, for example an extremely // expensive metered network, or unwanted leaking of the User Agent string. if (!isValidationRequired()) { validationLog("Network would not satisfy default request, not validating"); transitionTo(mValidatedState); return HANDLED; } mAttempts++; // Note: This call to isCaptivePortal() could take up to a minute. Resolving the // server's IP addresses could hit the DNS timeout, and attempting connections // to each of the server's several IP addresses (currently one IPv4 and one // IPv6) could each take SOCKET_TIMEOUT_MS. During this time this StateMachine // will be unresponsive. isCaptivePortal() could be executed on another Thread // if this is found to cause problems. CaptivePortalProbeResult probeResult = isCaptivePortal(); if (probeResult.isSuccessful()) { // Transit EvaluatingPrivateDnsState to get to Validated // state (even if no Private DNS validation required). transitionTo(mEvaluatingPrivateDnsState); } else if (probeResult.isPortal()) { notifyNetworkTestResultInvalid(probeResult.redirectUrl); mLastPortalProbeResult = probeResult; transitionTo(mCaptivePortalState); } else { final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); sendMessageDelayed(msg, mReevaluateDelayMs); logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); notifyNetworkTestResultInvalid(probeResult.redirectUrl); if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { // Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); } mReevaluateDelayMs *= 2; if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; } } return HANDLED;这个状态很容易看出来是校验的主体流程,一进这个状态就给自己发个CMD_REEVALUATE消息进行网络的校验。
/** * 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 + 6;if (message.arg1 != mReevaluateToken || mUserDoesNotWant) return HANDLED;
滞后的或者用户不想要的消息会跳过
// Don't bother validating networks that don't satisfy the default request. // This includes: // - VPNs which can be considered explicitly desired by the user and the // user's desire trumps whether the network validates. // - Networks that don't provide Internet access. It's unclear how to // validate such networks. // - Untrusted networks. It's unsafe to prompt the user to sign-in to // such networks and the user didn't express interest in connecting to // such networks (an app did) so the user may be unhappily surprised when // asked to sign-in to a network they didn't want to connect to in the // first place. Validation could be done to adjust the network scores // however these networks are app-requested and may not be intended for // general usage, in which case general validation may not be an accurate // measure of the network's quality. Only the app knows how to evaluate // the network so don't bother validating here. Furthermore sending HTTP // packets over the network may be undesirable, for example an extremely // expensive metered network, or unwanted leaking of the User Agent string.Google翻译调整了一下
//不要验证不满足默认请求的网络。 // 这包括: // - 可以被用户明确要求的VPN和 //用户的愿望胜过网络是否有效。 // - 不提供Internet访问的网络。目前还不清楚如何 //验证这样的网络。 // - 不受信任的网络。提示用户登录是不安全的 //这样的网络和用户没有表达对连接的兴趣 //这样的网络(应用程序确实如此),这样用户可能会感到非常惊讶 //要求登录他们不想连接的网络 //可以进行验证以调整网络分数 //但是这些网络是应用程序请求的,可能不适合 //一般用法,在这种情况下,一般验证可能不准确 //衡量网络的质量。只有应用知道如何评估 //网络所以不要在这里验证。另外发送HTTP //网络上的数据包可能是不受欢迎的,例如极端的 //昂贵的计量网络,或用户代理字符串不必要的泄漏。大致分为3类
用户想要连的网络不提供网络访问的网络不受信任的网络if (!isValidationRequired()) { validationLog("Network would not satisfy default request, not validating"); transitionTo(mValidatedState); return HANDLED; }
校验不需要则直接跳转到ValidatedState,这里跳过逻辑应该是上来注释说的,看了下有点复杂,先跳过,待续
mAttempts++; // Note: This call to isCaptivePortal() could take up to a minute. Resolving the // server's IP addresses could hit the DNS timeout, and attempting connections // to each of the server's several IP addresses (currently one IPv4 and one // IPv6) could each take SOCKET_TIMEOUT_MS. During this time this StateMachine // will be unresponsive. isCaptivePortal() could be executed on another Thread // if this is found to cause problems. CaptivePortalProbeResult probeResult = isCaptivePortal(); if (probeResult.isSuccessful()) { // Transit EvaluatingPrivateDnsState to get to Validated // state (even if no Private DNS validation required). transitionTo(mEvaluatingPrivateDnsState); } else if (probeResult.isPortal()) { notifyNetworkTestResultInvalid(probeResult.redirectUrl); mLastPortalProbeResult = probeResult; transitionTo(mCaptivePortalState); } else { final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); sendMessageDelayed(msg, mReevaluateDelayMs); logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); notifyNetworkTestResultInvalid(probeResult.redirectUrl); if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { // Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); } mReevaluateDelayMs *= 2; if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; } } return HANDLED;
校验结果的判断流程,注释表明isCaptivePortal是耗时操作,如果发现问题新起一个线程比较好。
看起来有三种校验结果
probeResult.isSuccessful()probeResult.isPortal()校验不成功jiatai@jiatai:~/expand/aosp/P_source/frameworks$ find -iname CaptivePortalProbeResult.java ./base/core/java/android/net/captiveportal/CaptivePortalProbeResult.java public static final int SUCCESS_CODE = 204; public static final int FAILED_CODE = 599; public static final int PORTAL_CODE = 302; public boolean isSuccessful() { return mHttpResponseCode == SUCCESS_CODE; } public boolean isPortal() { return !isSuccessful() && (mHttpResponseCode >= 200) && (mHttpResponseCode <= 399); } public boolean isFailed() { return !isSuccessful() && !isPortal(); }
结果判断是通过CaptivePortalProbeResult的mHttpResponseCode来进行鉴定的。
这里再看下校验不成功逻辑
final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); sendMessageDelayed(msg, mReevaluateDelayMs); logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); notifyNetworkTestResultInvalid(probeResult.redirectUrl); if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) { // Don't continue to blame UID forever. TrafficStats.clearThreadStatsUid(); } mReevaluateDelayMs *= 2; if (mReevaluateDelayMs > MAX_REEVALUATE_DELAY_MS) { mReevaluateDelayMs = MAX_REEVALUATE_DELAY_MS; } private void notifyNetworkTestResultInvalid(Object obj) { mConnectivityServiceHandler.sendMessage(obtainMessage( EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj)); } // Start mReevaluateDelayMs at this value and double. private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000;校验不成功会上报不成功,另外会在1s后继续校验,后续间隔时间会翻倍,即1s-2s-4s,上限是600s,即2^9-2^10,即最大间隔是512s-600s,相当于校验不成功的网站后续是以10min的间隔不间断校验么
突然发现wiki百科被封了。。。
PAC指的是Proxy auto-config,自动配置代理,具体的后续再看
大概会通过pacUrl和mUseHttps判断具体走哪个校验流程。至于mUseHttps初始化是如下数据库,默认启用
public boolean getUseHttpsValidation() { return mSettings.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; }
HttpsUrl
mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl()); private String getCaptivePortalServerHttpsUrl() { return mSettings.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL); } // Default configuration values for captive portal detection probes. // TODO: append a random length parameter to the default HTTPS url. // TODO: randomize browser version ids in the default User-Agent String. private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";HttpUrl
mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(settings, context)); public static String getCaptivePortalServerHttpUrl( NetworkMonitorSettings settings, Context context) { return settings.getSetting( context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL); } private static final String DEFAULT_HTTP_URL = "http://connectivitycheck.gstatic.com/generate_204";
如名所述,起了两个线程进行http和https的校验
网络校验流程梳理了个大概,关键在于isCaptivePortal的执行以及返回值。具体如何校验的看了有点痛苦,从注释来讲
/** * Do a URL fetch on a known web server to see if we get the data we expect. * @return a CaptivePortalProbeResult inferred from the HTTP response. */
从服务器访问url,看是否获取到我们想要的数据?