关于wifi的禁用分析(Android R)

0. 前言

在我们分析wifi问题的过程钟,会收到一部分用户反馈的无法连接指定wifi的bug。通过分析,原因是因为android将此AP进行了临时禁用,而造成这部分的原因一般有以下的情况:密码错误、认证失败等。临时禁用的目的,是防止自动反复的连接此AP,影响用户体验。

1. WifiMonitor

WifiMonitor是用来监听来自wpa_supplicantwificond事件的,并将事件通过广播给ClientModeImpl来进行处理事件。

public class WifiMonitor {
    private static final String TAG = "WifiMonitor";

    /* Supplicant events reported to a state machine */
    private static final int BASE = Protocol.BASE_WIFI_MONITOR;
    //...
        /* Password failure and EAP authentication failure */
    public static final int AUTHENTICATION_FAILURE_EVENT         = BASE + 7;

        /**
     * Broadcast the authentication failure event to all the handlers registered for this event.
     *
     * @param iface Name of iface on which this occurred.
     * @param reason Reason for authentication failure. This has to be one of the
     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_NONE},
     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_TIMEOUT},
     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_WRONG_PSWD},
     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_EAP_FAILURE}
     * @param errorCode Error code associated with the authentication failure event.
     *               A value of -1 is used when no error code is reported.
     */
    public void broadcastAuthenticationFailureEvent(String iface, int reason, int errorCode) {
        sendMessage(iface, AUTHENTICATION_FAILURE_EVENT, reason, errorCode);
    }
}

上面这边就是注册AUTHENTICATION_FAILURE_EVENT以及广播,startMonitor开始监控,一旦收到wpa_supplicant的此事件就开始广播。

2. ClientModeImpl

 private void registerForWifiMonitorEvents()  {
     //...
     mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
            getHandler());
     mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
            mWifiMetrics.getHandler());
     mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
            mSupplicantStateTracker.getHandler());
     //...
 }

注册WifiMonitor事件有三个ClientModeImplWifiMetricsSupplicantStateTracker

WifiMetrics

主要为了生成的无线连接度量提供存储的,包含以下几类:

  1. 聚合的连接统计(连接数,故障数,…)

  2. 离散的连接事件统计(时间,持续时间,失败代码,…)

  3. 路由的细节(技术类型,认证类型,…)

  4. 扫描的状态

SupplicantStateTracker

主要是为了跟踪wpa_supplicant的状态变化并提供功能,这是根据这些状态变化而定的:

  • 检测不确定循环的失败的WPA握手
  • 身份验证失败处理

ClientModeImpl

                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                    stopIpClient(); //停止ip相关服务
                    mWifiDiagnostics.captureBugReportData(
                            WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);//bugreport日志相关
                    int disableReason = WifiConfiguration.NetworkSelectionStatus
                            .DISABLED_AUTHENTICATION_FAILURE;
                    reasonCode = message.arg1;
                    WifiConfiguration targetedNetwork =
                            mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
                    // Check if this is a permanent wrong password failure.判断是否是密码错误类型
                    //下面会提到这个
                    if (isPermanentWrongPasswordFailure(mTargetNetworkId, reasonCode)) {
                        disableReason = WifiConfiguration.NetworkSelectionStatus
                                .DISABLED_BY_WRONG_PASSWORD;
                        if (targetedNetwork != null) {
                            mWrongPasswordNotifier.onWrongPasswordError(
                                    targetedNetwork.SSID);
                        }
                    } else if (reasonCode == WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
                        //EAP failure类型
                        int errorCode = message.arg2;
                        if (targetedNetwork != null && targetedNetwork.enterpriseConfig != null
                                && targetedNetwork.enterpriseConfig.isAuthenticationSimBased()) {
                            mEapFailureNotifier.onEapFailure(errorCode, targetedNetwork);
                        }
                        handleEapAuthFailure(mTargetNetworkId, errorCode);
                        if (errorCode == WifiNative.EAP_SIM_NOT_SUBSCRIBED) {
                            disableReason = WifiConfiguration.NetworkSelectionStatus
                                .DISABLED_AUTHENTICATION_NO_SUBSCRIPTION;
                        }
                    }
                    mWifiConfigManager.updateNetworkSelectionStatus(
                            mTargetNetworkId, disableReason);
                    mWifiConfigManager.clearRecentFailureReason(mTargetNetworkId);

                    //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
                    switch (reasonCode) {
                        case WifiManager.ERROR_AUTH_FAILURE_NONE:
                            level2FailureReason =
                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE;
                            break;
                        case WifiManager.ERROR_AUTH_FAILURE_TIMEOUT:
                            level2FailureReason =
                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT;
                            break;
                        case WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD:
                            level2FailureReason =
                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
                            break;
                        case WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE:
                            level2FailureReason =
                                    WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE;
                            break;
                        default:
                            level2FailureReason =
                                    WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN;
                            break;
                    }
                    reportConnectionAttemptEnd(
                            WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                            WifiMetricsProto.ConnectionEvent.HLF_NONE,
                            level2FailureReason);
                    if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD && reasonCode
                            != WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
                        mWifiInjector.getWifiLastResortWatchdog()
                                .noteConnectionFailureAndTriggerIfNeeded(
                                        getTargetSsid(),
                                        (mLastBssid == null) ? mTargetBssid : mLastBssid,
                                        WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
                    }
                    break;

上面这部分就是当监听到底层传过来的AUTHENTICATION_FAILURE_EVENT事件,根据reasoncode来细分一下失败的类型。

isPermanentWrongPasswordFailure

    /**
     * Determine if the specified auth failure is considered to be a permanent wrong password
     * failure. The criteria for such failure is when wrong password error is detected
     * and the network had never been connected before.
     *
     * For networks that have previously connected successfully, we consider wrong password
     * failures to be temporary, to be on the conservative side.  Since this might be the
     * case where we are trying to connect to a wrong network (e.g. A network with same SSID
     * but different password).
     */
    private boolean isPermanentWrongPasswordFailure(int networkId, int reasonCode) {
        if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD) {
            return false;
        }
        WifiConfiguration network = mWifiConfigManager.getConfiguredNetwork(networkId);
        if (network != null && network.getNetworkSelectionStatus().hasEverConnected()) {
            return false;
        }
        return true;
    }

解释的很清楚,这个函数就是来确定指定的身份验证失败是否被认为是永久错误的密码失败。而这种失败的标准是在检测到错误的密码错误时,网络以前从未连接过。对于之前已经成功连接的网络,我们认为错误的密码失败只是暂时的,是保守的。因为这可能是我们试图连接到一个错误的网络的情况(例如,一个网络有相同的SSID但不同的密码)。

ERROR_AUTH_FAILURE_EAP_FAILURE

                    } else if (reasonCode == WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
                        int errorCode = message.arg2;
                        if (targetedNetwork != null && targetedNetwork.enterpriseConfig != null
                                && targetedNetwork.enterpriseConfig.isAuthenticationSimBased()) {
                            mEapFailureNotifier.onEapFailure(errorCode, targetedNetwork);
                        }
                        handleEapAuthFailure(mTargetNetworkId, errorCode);
                        if (errorCode == WifiNative.EAP_SIM_NOT_SUBSCRIBED) {
                            disableReason = WifiConfiguration.NetworkSelectionStatus
                                .DISABLED_AUTHENTICATION_NO_SUBSCRIPTION;
                        }
                    }

    private void handleEapAuthFailure(int networkId, int errorCode) {
        WifiConfiguration targetedNetwork =
                mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
        if (targetedNetwork != null) {
            switch (targetedNetwork.enterpriseConfig.getEapMethod()) {
                case WifiEnterpriseConfig.Eap.SIM:
                case WifiEnterpriseConfig.Eap.AKA:
                case WifiEnterpriseConfig.Eap.AKA_PRIME:
                    if (errorCode == WifiNative.EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED) {
                        mWifiCarrierInfoManager.resetCarrierKeysForImsiEncryption(targetedNetwork);
                    }
                    break;
                default:
                    // Do Nothing
            }
        }
    }

3. updateNetworkSelectionStatus

更新WiFi configuration里的状态

    /**
     * Update a network's status (both internal and public) according to the update reason and
     * its current state.
     *
     * @param config network to be updated.
     * @param reason reason code for update.
     * @return true if the input configuration has been updated, false otherwise.
     */
    private boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
        if (reason != NetworkSelectionStatus.DISABLED_NONE) {

            // Do not update SSID blacklist with information if this is the only
            // SSID be observed. By ignoring it we will cause additional failures
            // which will trigger Watchdog.
            if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION
                    || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE
                    || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) {
                if (mWifiInjector.getWifiLastResortWatchdog().shouldIgnoreSsidUpdate()) {
                    if (mVerboseLoggingEnabled) {
                        Log.v(TAG, "Ignore update network selection status "
                                    + "since Watchdog trigger is activated");
                    }
                    return false;
                }
            }

            networkStatus.incrementDisableReasonCounter(reason);
            // For network disable reasons, we should only update the status if we cross the
            // threshold.
            int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
            int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason);
            if (disableReasonCounter < disableReasonThreshold) {
                if (mVerboseLoggingEnabled) {
                    Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
                            + " for reason "
                            + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason)
                            + " is " + networkStatus.getDisableReasonCounter(reason)
                            + " and threshold is " + disableReasonThreshold);
                }
                return true;
            }
        }
        return setNetworkSelectionStatus(config, reason);
    }

shouldIgnoreSsidUpdate

/**
 * Helper function to check if we should ignore SSID update.
 * @return true if should ignore SSID update
 */
public boolean shouldIgnoreSsidUpdate() {
    return mWatchdogAllowedToTrigger //是否是看门狗允许的
            && isSingleSsidRecorded() //是不是唯一的ssid记录
            && checkIfAtleastOneNetworkHasEverConnected(); //是不是之前连接过的
}

当当前的AP是当前WifiConnectivityManager唯一可候选的AP,并且这个AP是之前连接上的,那么就会忽略,不进行更新状态,不disable它。这个也是wifi禁用的例外。

getNetworkSelectionDisableThreshold

增加该configuration相应失败reason的计数,每个reason有个阈值,当达到阈值的时候就disable这个AP

        /**
         * increment the counter of a specific failure reason
         * @param reason a specific failure reason
         * @exception throw IllegalArgumentException for illegal input
         * @hide
         */
        public void incrementDisableReasonCounter(int reason) {
            if (reason >= DISABLED_NONE && reason < NETWORK_SELECTION_DISABLED_MAX) {
                mNetworkSeclectionDisableCounter[reason]++;
            } else {
                throw new IllegalArgumentException("Illegal reason value: " + reason);
            }
        }

这边会为AP network selection每一个reason都添加一个计数。我们看一下reason:

        // Quality Network disabled reasons
        /** Default value. Means not disabled. */
        public static final int DISABLED_NONE = 0;
        /**
         * The starting index for network selection disabled reasons.
         * @hide
         */
        public static final int NETWORK_SELECTION_DISABLED_STARTING_INDEX = 1;
        /**
         * The starting index for network selection temporarily disabled reasons.
         * @hide
         */
//从这开始为临时禁用的reason
        public static final int TEMPORARILY_DISABLED_STARTING_INDEX = 1;
        /** This network is disabled because of multiple association rejections. */
        //由于多次关联被拒绝,此网络被禁用
        public static final int DISABLED_ASSOCIATION_REJECTION = 1;
        /** This network is disabled because of multiple authentication failure. */
        //由于多次认证被拒绝,此网络被禁用
        public static final int DISABLED_AUTHENTICATION_FAILURE = 2;
        /** This network is disabled because of multiple DHCP failure. */
        //由于多次DHCP失败,此网络被禁用
        public static final int DISABLED_DHCP_FAILURE = 3;
        /** This network is temporarily disabled because it has no Internet access. */
        //这个网络暂时被禁用,因为它没有互联网访问
        public static final int DISABLED_NO_INTERNET_TEMPORARY = 4;
        /**
         * The starting index for network selection permanently disabled reasons.
         * @hide
         */
//从这开始为永久禁用的reason
        public static final int PERMANENTLY_DISABLED_STARTING_INDEX = 5;
        /** This network is disabled due to absence of user credentials */
        //由于缺少用户凭据,此网络被禁用
        public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 5;
        /**
         * This network is permanently disabled because it has no Internet access and the user does
         * not want to stay connected.
         */
        public static final int DISABLED_NO_INTERNET_PERMANENT = 6;
        /** This network is disabled due to WifiManager disabling it explicitly. */
        //这个网络是禁用的,因为WifiManager明确禁用它
        public static final int DISABLED_BY_WIFI_MANAGER = 7;
        /** This network is disabled due to wrong password. */
        //这个网络是禁用的,因为密码错误
        public static final int DISABLED_BY_WRONG_PASSWORD = 8;
        /** This network is disabled because service is not subscribed. */
        //由于服务未被订阅,此网络已被禁用
        public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 9;
        /**
         * All other disable reasons should be strictly less than this value.
         * @hide
         */
        public static final int NETWORK_SELECTION_DISABLED_MAX = 10;

从这边可以看到临时禁用以及永久禁用的reason,至于什么时候被判断成相应的reason,根据实际的情况走相应的判断流程,例如:

            case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                stopIpClient();
                mWifiDiagnostics.captureBugReportData(
                        WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);
                int disableReason = WifiConfiguration.NetworkSelectionStatus
                        .DISABLED_AUTHENTICATION_FAILURE;
                //这边判断赋值
                reasonCode = message.arg1;

到这个地方还有一个没有解释,也就是阈值。每个reason有个阈值,当达到阈值的时候就disable这个AP

我们可以看到每个reason的阈值,比如auth失败5次,就会被临时禁用。

setNetworkSelectionStatus

updateNetworkSelectionStatus返回值是setNetworkSelectionStatus(config, reason),这个函数就是根据更新的resons和当前的状态去设置相应的网络状态。

    /**
     * Sets a network's status (both internal and public) according to the update reason and
     * its current state.
     *
     * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
     * public {@link WifiConfiguration#status} field if the network is either enabled or
     * permanently disabled.
     *
     * @param config network to be updated.
     * @param reason reason code for update.
     * @return true if the input configuration has been updated, false otherwise.
     */
    private boolean setNetworkSelectionStatus(WifiConfiguration config, int reason) {
        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
        if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
            Log.e(TAG, "Invalid Network disable reason " + reason);
            return false;
        }
        if (reason == NetworkSelectionStatus.DISABLED_NONE) {
            setNetworkSelectionEnabled(config);
            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
        } else if (reason < NetworkSelectionStatus.PERMANENTLY_DISABLED_STARTING_INDEX) {
            setNetworkSelectionTemporarilyDisabled(config, reason);
        } else {
            setNetworkSelectionPermanentlyDisabled(config, reason);
            setNetworkStatus(config, WifiConfiguration.Status.DISABLED);
        }
        localLog("setNetworkSelectionStatus: configKey=" + config.getKey()
                + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
                + networkStatus.getNetworkSelectionDisableReasonString());
        saveToStore(false);
        return true;
    }

从上面就可以看出有两个核心的函数:setNetworkSelectionTemporarilyDisabledsetNetworkSelectionPermanentlyDisabled,这两个也就是我们上面说的临时禁用和永久禁用的函数实现。

4. 临时禁用恢复

第一种情况被恢复是由于临时禁用有一个超时时间,当超时时间到了之后,被WifiConnectivityManager扫描到,就会被恢复。这个超时时间其实在上面那张图介绍disable reason时,第三个参数就是每个disable reason所设定的超时时间。

第二种情况被恢复是重启。

这里介绍一个WifiConfigStore.xml,这个是用来保存所连接的wifi的配置信息的。

下面这个类就是将这个xml文件序列化,变成相应的object。

parseFromXml,这个就是解析xml的函数。

这边也解释的很清楚,当重启的时候不保存临时的黑名单禁用。

剑气纵横三万里

“为什么要努力?” “想去的地方很远,想要的东西很贵,喜欢的人很优秀,父母的白发,朋友的约定,周围人的嘲笑,以及,天生傲骨。”

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐

暂无内容!