Android network - NUD检测机制(Android 14)

news2024/9/21 18:00:30

Android network - NUD检测机制

  • 1. 前言
  • 2. 源码分析
    • 2.1 ClientModeImpl
    • 2.2 IpClient
    • 2.3 IpReachabilityMonitor

1. 前言

  在Android系统中,NUD(Neighbor Unreachable Detection)指的是网络中的邻居不可达检测机制,它用于检测设备是否能够到达特定的IP地址。当Android设备尝试与另一个设备通信时,如果发现对方不可达,它会触发NUD过程。NUD 的底层实现还是依赖kernel,Android层有服务建立通信,当kernel检测到当前网络与周边的neighbor不可达时,就会发送消息通知上层,上层处理msg

2. 源码分析

我们以 wifi 为例分析NUD检测机制,源码基于Android 14分析

2.1 ClientModeImpl

  当用户点击WiFi开关,打开WiFi后,ClientModeImpl会进入 ConnectableState 状态, enter后会调用makeIpClient

// packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
	class ConnectableState extends RunnerState {
        .....
        
        @Override
        public void enterImpl() {
            Log.d(getTag(), "entering ConnectableState: ifaceName = " + mInterfaceName);
            setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
            if (mWifiGlobals.isConnectedMacRandomizationEnabled()) {
                mFailedToResetMacAddress = !mWifiNative.setStaMacAddress(
                        mInterfaceName, MacAddressUtils.createRandomUnicastAddress());
                if (mFailedToResetMacAddress) {
                    Log.e(getTag(), "Failed to set random MAC address on ClientMode creation");
                }
            }
            mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
            updateCurrentConnectionInfo();
            mWifiStateTracker.updateState(mInterfaceName, WifiStateTracker.INVALID);
            makeIpClient();
        }
    
        ......
    }


private void makeIpClient() {
    mIpClientCallbacks = new IpClientCallbacksImpl();
    mFacade.makeIpClient(mContext, mInterfaceName, mIpClientCallbacks);
    mIpClientCallbacks.awaitCreation();
}

  makeIpClient主要是调用mFacade::makeIpClient(),通过FrameworkFacade创建IpClient,我们知道IpClient跟触发DHCP相关,而我们的NUD机制的注册会通过IpClient完成,看它带入的Callback实现

class IpClientCallbacksImpl extends IpClientCallbacks {
        private final ConditionVariable mWaitForCreationCv = new ConditionVariable(false);
        private final ConditionVariable mWaitForStopCv = new ConditionVariable(false);

        @Override
        public void onIpClientCreated(IIpClient ipClient) {
            if (mIpClientCallbacks != this) return;
            // IpClient may take a very long time (many minutes) to start at boot time. But after
            // that IpClient should start pretty quickly (a few seconds).
            // Blocking wait for 5 seconds first (for when the wait is short)
            // If IpClient is still not ready after blocking wait, async wait (for when wait is
            // long). Will drop all connection requests until IpClient is ready. Other requests
            // will still be processed.
            sendMessageAtFrontOfQueue(CMD_IPCLIENT_CREATED,
                    new IpClientManager(ipClient, getName()));
            mWaitForCreationCv.open();
        }

        @Override
        public void onPreDhcpAction() {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_PRE_DHCP_ACTION);
        }

        @Override
        public void onPostDhcpAction() {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_POST_DHCP_ACTION);
        }

        @Override
        public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
            if (mIpClientCallbacks != this) return;
            if (dhcpResults != null) {
                sendMessage(CMD_IPV4_PROVISIONING_SUCCESS, dhcpResults);
            } else {
                sendMessage(CMD_IPV4_PROVISIONING_FAILURE);
            }
        }

        @Override
        public void onProvisioningSuccess(LinkProperties newLp) {
            if (mIpClientCallbacks != this) return;
            addPasspointInfoToLinkProperties(newLp);
            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL);
            sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
            sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
        }

        @Override
        public void onProvisioningFailure(LinkProperties newLp) {
            if (mIpClientCallbacks != this) return;
            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST);
            sendMessage(CMD_IP_CONFIGURATION_LOST);
        }

        @Override
        public void onLinkPropertiesChange(LinkProperties newLp) {
            if (mIpClientCallbacks != this) return;
            addPasspointInfoToLinkProperties(newLp);
            sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
        }

        @Override
        public void onReachabilityLost(String logMsg) {
            if (mIpClientCallbacks != this) return;
            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);
            sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
        }

        @Override
        public void onReachabilityFailure(ReachabilityLossInfoParcelable lossInfo) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_IP_REACHABILITY_FAILURE, lossInfo);
        }

        @Override
        public void installPacketFilter(byte[] filter) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_INSTALL_PACKET_FILTER, filter);
        }

        @Override
        public void startReadPacketFilter() {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_READ_PACKET_FILTER);
        }

        @Override
        public void setFallbackMulticastFilter(boolean enabled) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_SET_FALLBACK_PACKET_FILTERING, enabled);
        }

        @Override
        public void setNeighborDiscoveryOffload(boolean enabled) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_CONFIG_ND_OFFLOAD, (enabled ? 1 : 0));
        }

        @Override
        public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_START_FILS_CONNECTION, 0, 0, packets);
        }

        @Override
        public void setMaxDtimMultiplier(int multiplier) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_SET_MAX_DTIM_MULTIPLIER, multiplier);
        }

        @Override
        public void onQuit() {
            if (mIpClientCallbacks != this) return;
            mWaitForStopCv.open();
        }

        boolean awaitCreation() {
            return mWaitForCreationCv.block(IPCLIENT_STARTUP_TIMEOUT_MS);
        }

        boolean awaitShutdown() {
            return mWaitForStopCv.block(IPCLIENT_SHUTDOWN_TIMEOUT_MS);
        }
    }

  IpClientCallbacks包含了多个回调,其中onReachabilityLost()这个回调,是我们跟踪NUD机制的其中一个重要回调,它发送了CMD_IP_REACHABILITY_LOST msg去通知WiFi fwk进行处理(断连wifi)。

2.2 IpClient

  我们跟进IpClient的构造函数来看下

// packages/modules/NetworkStack/src/android/net/ip/IpClient.java
    @VisibleForTesting
    public IpClient(Context context, String ifName, IIpClientCallbacks callback,
            NetworkObserverRegistry observerRegistry, NetworkStackServiceManager nssManager,
            Dependencies deps) {
        super(IpClient.class.getSimpleName() + "." + ifName);
        Objects.requireNonNull(ifName);
        Objects.requireNonNull(callback);

        mTag = getName();

        mContext = context;
        mInterfaceName = ifName;
        mDependencies = deps;
        mMetricsLog = deps.getIpConnectivityLog();
        mNetworkQuirkMetrics = deps.getNetworkQuirkMetrics();
        mShutdownLatch = new CountDownLatch(1);
        mCm = mContext.getSystemService(ConnectivityManager.class);
        mObserverRegistry = observerRegistry;
        mIpMemoryStore = deps.getIpMemoryStore(context, nssManager);

        sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
        mLog = sSmLogs.get(mInterfaceName);
        sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
        mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
        mMsgStateLogger = new MessageHandlingLogger();
        mCallback = new IpClientCallbacksWrapper(callback, mLog, mShim); //封装了传入进来的callback

        // TODO: Consider creating, constructing, and passing in some kind of
        // InterfaceController.Dependencies class.
        mNetd = deps.getNetd(mContext);
        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog);

        mMinRdnssLifetimeSec = mDependencies.getDeviceConfigPropertyInt(
                CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME);

        IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
                mMinRdnssLifetimeSec);

        mLinkObserver = new IpClientLinkObserver(
                mContext, getHandler(),
                mInterfaceName,
                new IpClientLinkObserver.Callback() {
                    @Override
                    public void update(boolean linkState) {
                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED, linkState
                                ? ARG_LINKPROP_CHANGED_LINKSTATE_UP
                                : ARG_LINKPROP_CHANGED_LINKSTATE_DOWN);
                    }

                    @Override
                    public void onIpv6AddressRemoved(final Inet6Address address) {
                        // The update of Gratuitous NA target addresses set or unsolicited
                        // multicast NS source addresses set should be only accessed from the
                        // handler thread of IpClient StateMachine, keeping the behaviour
                        // consistent with relying on the non-blocking NetworkObserver callbacks,
                        // see {@link registerObserverForNonblockingCallback}. This can be done
                        // by either sending a message to StateMachine or posting a handler.
                        if (address.isLinkLocalAddress()) return;
                        getHandler().post(() -> {
                            mLog.log("Remove IPv6 GUA " + address
                                    + " from both Gratuituous NA and Multicast NS sets");
                            mGratuitousNaTargetAddresses.remove(address);
                            mMulticastNsSourceAddresses.remove(address);
                        });
                    }

                    @Override
                    public void onClatInterfaceStateUpdate(boolean add) {
                        // TODO: when clat interface was removed, consider sending a message to
                        // the IpClient main StateMachine thread, in case "NDO enabled" state
                        // becomes tied to more things that 464xlat operation.
                        getHandler().post(() -> {
                            mCallback.setNeighborDiscoveryOffload(add ? false : true);
                        });
                    }
                },
                config, mLog, mDependencies
        );

        mLinkProperties = new LinkProperties();
        mLinkProperties.setInterfaceName(mInterfaceName);

        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);

        // Anything the StateMachine may access must have been instantiated
        // before this point.
        configureAndStartStateMachine();

        // Anything that may send messages to the StateMachine must only be
        // configured to do so after the StateMachine has started (above).
        startStateMachineUpdaters();
    }

  我们可以看到IpClient会封装一次传进来的Callback参数,但只是简单的wrapper,我们最关心的onReachabilityLost()回调也是。

  这里相关的初始化准备工作就完成了。NUD肯定要在用户连接了网络之后,检测才会有意义;当获取IP开始后,会进入IpClient::RunningState:

    class RunningState extends State {
        private ConnectivityPacketTracker mPacketTracker;
        private boolean mDhcpActionInFlight;

        @Override
        public void enter() {
            mApfFilter = maybeCreateApfFilter(mCurrentApfCapabilities);
            // TODO: investigate the effects of any multicast filtering racing/interfering with the
            // rest of this IP configuration startup.
            if (mApfFilter == null) {
                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
            }

            mPacketTracker = createPacketTracker();
            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);

            final int acceptRa =
                    mConfiguration.mIPv6ProvisioningMode == PROV_IPV6_LINKLOCAL ? 0 : 2;
            if (isIpv6Enabled() && !startIPv6(acceptRa)) {
                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
                enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6);
                return;
            }

            if (isIpv4Enabled() && !isUsingPreconnection() && !startIPv4()) {
                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
                enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV4);
                return;
            }

            final InitialConfiguration config = mConfiguration.mInitialConfig;
            if ((config != null) && !applyInitialConfig(config)) {
                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
                enqueueJumpToStoppingState(DisconnectCode.DC_INVALID_PROVISIONING);
                return;
            }

            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
                doImmediateProvisioningFailure(
                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
                enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPREACHABILITYMONITOR);
                return;
            }
        }
        ......
    }

  其中会调用startIpReachabilityMonitor(),去创建IpReachabilityMonitor对象,NUD相关的操作都会分派给它处理

    private boolean startIpReachabilityMonitor() {
        try {
            mIpReachabilityMonitor = mDependencies.getIpReachabilityMonitor(
                    mContext,
                    mInterfaceParams,
                    getHandler(),
                    mLog,
                    new IpReachabilityMonitor.Callback() {
                        @Override
                        public void notifyLost(InetAddress ip, String logMsg, NudEventType type) {
                            final int version = mCallback.getInterfaceVersion();
                            if (version >= VERSION_ADDED_REACHABILITY_FAILURE) {
                                final int reason = nudEventTypeToInt(type);
                                if (reason == INVALID_REACHABILITY_LOSS_TYPE) return;
                                final ReachabilityLossInfoParcelable lossInfo =
                                        new ReachabilityLossInfoParcelable(logMsg, reason);
                                mCallback.onReachabilityFailure(lossInfo);
                            } else {
                                mCallback.onReachabilityLost(logMsg);
                            }
                        }
                    },
                    mConfiguration.mUsingMultinetworkPolicyTracker,
                    mDependencies.getIpReachabilityMonitorDeps(mContext, mInterfaceParams.name),
                    mNetd);
        } catch (IllegalArgumentException iae) {
            // Failed to start IpReachabilityMonitor. Log it and call
            // onProvisioningFailure() immediately.
            //
            // See http://b/31038971.
            logError("IpReachabilityMonitor failure: %s", iae);
            mIpReachabilityMonitor = null;
        }

        return (mIpReachabilityMonitor != null);
    }

  构造IpReachabilityMonitor对象时,实现了一个IpReachabilityMonitor.Callback()回调接口,它会调用IpClient的Callback wrapper通知onReachabilityLost()事件。

  NUD是为了探测周边neighbor的可达性,所以它在一次WiFi网络连接完成、拿到连接信息之后,再去开始触发探测比较正常,WiFi连接之后,ConnectModeState收到wpa_supplicant通知的连接完成事件

  我们来看一次完整的wifi连接时,状态机的变化

 rec[0]: time=06-20 01:45:37.298 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_IPCLIENT_CREATED screen=on 0 0
 rec[1]: time=06-20 01:45:37.492 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_ENABLE_RSSI_POLL screen=on 1 0
 rec[2]: time=06-20 01:45:37.544 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_SET_SUSPEND_OPT_ENABLED screen=on 0 0
 rec[3]: time=06-20 01:45:37.587 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_RESET_SIM_NETWORKS screen=on 0 0
 rec[4]: time=06-20 01:45:37.587 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_RESET_SIM_NETWORKS screen=on 0 0
 rec[5]: time=06-20 01:46:49.169 processed=ConnectableState org=DisconnectedState dest=L2ConnectingState what=CMD_START_CONNECT screen=on 1 1010 targetConfigKey="iPhone"WPA_PSK BSSID=null targetBssid=2e:bb:5f:ef:01:91 roam=false
 rec[6]: time=06-20 01:46:49.170 processed=ConnectingOrConnectedState org=L2ConnectingState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid:  bssid: 00:00:00:00:00:00 nid: -1 frequencyMhz: 0 state: INTERFACE_DISABLED
 rec[7]: time=06-20 01:46:49.172 processed=ConnectingOrConnectedState org=L2ConnectingState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid:  bssid: 00:00:00:00:00:00 nid: -1 frequencyMhz: 0 state: DISCONNECTED
 rec[8]: time=06-20 01:46:49.176 processed=ConnectingOrConnectedState org=L2ConnectingState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid: "iPhone" bssid: 2e:bb:5f:ef:01:91 nid: 1 frequencyMhz: 0 state: ASSOCIATING
 rec[9]: time=06-20 01:46:49.325 processed=ConnectingOrConnectedState org=L2ConnectingState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid: "iPhone" bssid: 2e:bb:5f:ef:01:91 nid: 1 frequencyMhz: 0 state: ASSOCIATED
 rec[10]: time=06-20 01:46:49.325 processed=ConnectableState org=L2ConnectingState dest=<null> what=ASSOCIATED_BSSID_EVENT screen=on 0 0 BSSID=2e:bb:5f:ef:01:91 Target Bssid=2e:bb:5f:ef:01:91 Last Bssid=2e:bb:5f:ef:01:91 roam=false
 rec[11]: time=06-20 01:46:49.327 processed=ConnectingOrConnectedState org=L2ConnectingState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid: "iPhone" bssid: 2e:bb:5f:ef:01:91 nid: 1 frequencyMhz: 0 state: FOUR_WAY_HANDSHAKE
 rec[12]: time=06-20 01:46:49.353 processed=ConnectingOrConnectedState org=L2ConnectingState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid: "iPhone" bssid: 2e:bb:5f:ef:01:91 nid: 1 frequencyMhz: 0 state: GROUP_HANDSHAKE
 rec[13]: time=06-20 01:46:49.367 processed=ConnectingOrConnectedState org=L2ConnectingState dest=L3ProvisioningState what=NETWORK_CONNECTION_EVENT screen=on 1 false 2e:bb:5f:ef:01:91 nid=1 "iPhone"WPA_PSK last=
 rec[14]: time=06-20 01:46:49.381 processed=ConnectingOrConnectedState org=L3ProvisioningState dest=<null> what=SUPPLICANT_STATE_CHANGE_EVENT screen=on 0 0 ssid: "iPhone" bssid: 2e:bb:5f:ef:01:91 nid: 1 frequencyMhz: 0 state: COMPLETED
 rec[15]: time=06-20 01:46:49.398 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_CONFIG_ND_OFFLOAD screen=on 1 0
 rec[16]: time=06-20 01:46:49.399 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_SET_FALLBACK_PACKET_FILTERING screen=on enabled=true
 rec[17]: time=06-20 01:46:49.404 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 
 rec[18]: time=06-20 01:46:49.405 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_SET_MAX_DTIM_MULTIPLIER screen=on maximum multiplier=1
 rec[19]: time=06-20 01:46:49.528 processed=L2ConnectedState org=L3ProvisioningState dest=<null> what=CMD_PRE_DHCP_ACTION screen=on 0 0 txpkts=10,0,0
 rec[20]: time=06-20 01:46:49.530 processed=L2ConnectedState org=L3ProvisioningState dest=<null> what=CMD_PRE_DHCP_ACTION_COMPLETE screen=on uid=1000 0 0
 rec[21]: time=06-20 01:46:49.646 processed=L2ConnectedState org=L3ProvisioningState dest=<null> what=CMD_POST_DHCP_ACTION screen=on 
 rec[22]: time=06-20 01:46:49.647 processed=L2ConnectedState org=L3ProvisioningState dest=<null> what=CMD_IPV4_PROVISIONING_SUCCESS screen=on com.android.wifi.x.android.net.DhcpResultsParcelable{baseConfiguration: IP address 172.20.10.2/28 Gateway 172.20.10.1  DNS servers: [ 172.20.10.1 ] Domains , leaseDuration: 85536, mtu: 0, serverAddress: 172.20.10.1, vendorInfo: ANDROID_METERED, serverHostName: iPhone, captivePortalApiUrl: null}
 rec[23]: time=06-20 01:46:49.647 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4r
 rec[24]: time=06-20 01:46:49.647 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dns
 rec[25]: time=06-20 01:46:49.648 processed=L2ConnectedState org=L3ProvisioningState dest=L3ConnectedState what=CMD_IP_CONFIGURATION_SUCCESSFUL screen=on 0 0
 rec[26]: time=06-20 01:46:49.653 processed=L2ConnectedState org=L3ConnectedState dest=<null> what=CMD_ONESHOT_RSSI_POLL screen=on 0 0 "iPhone" 2e:bb:5f:ef:01:91 rssi=-70 f=2437 sc=null link=65 tx=0.3, 0.0, 0.0 rx=0.0 bcn=0 [on:0 tx:0 rx:0 period:832291253] from screen [on:0 period:832291253] score=0
 rec[27]: time=06-20 01:46:50.256 processed=L3ConnectedState org=L3ConnectedState dest=<null> what=CMD_NETWORK_STATUS screen=on 1 0
 rec[28]: time=06-20 01:46:51.024 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dns
 rec[29]: time=07-04 16:09:02.894 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dns v6r v6dns
 rec[30]: time=07-04 16:09:03.869 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dns v6 v6r v6dns
 rec[31]: time=07-04 16:09:03.871 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_SET_MAX_DTIM_MULTIPLIER screen=on maximum multiplier=2
 rec[32]: time=07-04 16:09:04.574 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dns v6 v6r v6dns
 rec[33]: time=07-04 16:09:11.381 processed=L3ConnectedState org=L3ConnectedState dest=<null> what=what:131383 screen=on
 rec[34]: time=07-04 16:09:11.792 processed=L2ConnectedState org=L3ConnectedState dest=<null> what=CMD_ONESHOT_RSSI_POLL screen=on 0 0 "iPhone" 2e:bb:5f:ef:01:91 rssi=-63 f=2437 sc=null link=57 tx=7.9, 0.0, 0.0 rx=4.5 bcn=0 [on:0 tx:0 rx:0 period:1261342139] from screen [on:0 period:2093633392] score=0
 rec[35]: time=07-04 16:09:29.250 processed=L2ConnectedState org=L3ConnectedState dest=<null> what=CMD_ONESHOT_RSSI_POLL screen=on 0 0 "iPhone" 2e:bb:5f:ef:01:91 rssi=-66 f=2437 sc=null link=26 tx=2.8, 0.0, 0.0 rx=1.7 bcn=0 [on:0 tx:0 rx:0 period:17458] from screen [on:0 period:2093650850] score=0

  根据log和代码,我们发现在DHCP之后,wifi状态机进到L3ConnectedState时,会收到CMD_ONESHOT_RSSI_POLL 消息

// packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
    class L3ConnectedState extends RunnerState {
        L3ConnectedState(int threshold) {
            super(threshold, mWifiInjector.getWifiHandlerLocalLog());
        }

        @Override
        public void enterImpl() {
            if (mVerboseLoggingEnabled) {
                log("Enter ConnectedState  mScreenOn=" + mScreenOn);
            }
			.......
			
            updateLinkLayerStatsRssiAndScoreReport(); // 发送CMD_ONESHOT_RSSI_POLL
            .......
        }
        ......
    }

    private void updateLinkLayerStatsRssiAndScoreReport() {
        sendMessage(CMD_ONESHOT_RSSI_POLL);
    }

  CMD_ONESHOT_RSSI_POLL被接受到后会调用IpClient::confirmConfiguration()确认网络配置

                case CMD_ONESHOT_RSSI_POLL: {
                    if (!mEnableRssiPolling) {
                        updateLinkLayerStatsRssiDataStallScoreReport();
                    }
                    break;
                }

        /**
         * Fetches link stats, updates Wifi Data Stall, Score Card and Score Report.
         */
        private WifiLinkLayerStats updateLinkLayerStatsRssiDataStallScoreReport() {
            ......

            if (mWifiScoreReport.shouldCheckIpLayer()) {
                if (mIpClient != null) {
                    mIpClient.confirmConfiguration();
                }
                mWifiScoreReport.noteIpCheck();
            }

            mLastLinkLayerStats = stats;
            return stats;
        }
    }

  然后执行probe链路上的neighbours网络

// packages/modules/NetworkStack/src/android/net/ip/IpClient.java
    /**
     * Confirm the provisioning configuration.
     */
    public void confirmConfiguration() {
        sendMessage(CMD_CONFIRM);
    }

	......
	
class RunningState extends State {
......
        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case CMD_STOP:
                    transitionTo(mStoppingState);
                    break;
 
                case CMD_START:
                    logError("ALERT: START received in StartedState. Please fix caller.");
                    break;
 
                case CMD_CONFIRM:
                    // TODO: Possibly introduce a second type of confirmation
                    // that both probes (a) on-link neighbors and (b) does
                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
                    // roams.
                    if (mIpReachabilityMonitor != null) {
                        mIpReachabilityMonitor.probeAll();
                    }
                    break;
......
       }
}

  这里会发现所有的操作都会由IpReachabilityMonitor处理

2.3 IpReachabilityMonitor

  我们再回头看IpReachabilityMonitor的构造实现

// packages/modules/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
    @VisibleForTesting
    public IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h,
            SharedLog log, Callback callback, boolean usingMultinetworkPolicyTracker,
            Dependencies dependencies, final IpConnectivityLog metricsLog, final INetd netd) {
        if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");

        mContext = context;
        mInterfaceParams = ifParams;
        mLog = log.forSubComponent(TAG);
        mCallback = callback;
        mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
        mCm = context.getSystemService(ConnectivityManager.class);
        mDependencies = dependencies;
        mMulticastResolicitEnabled = dependencies.isFeatureEnabled(context,
                IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true /* defaultEnabled */);
        mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureEnabled(context,
                IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION,
                false /* defaultEnabled */);
        mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context,
                IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION,
                false /* defaultEnabled */);
        mMetricsLog = metricsLog;
        mNetd = netd;
        Preconditions.checkNotNull(mNetd);
        Preconditions.checkArgument(!TextUtils.isEmpty(mInterfaceParams.name));

        // In case the overylaid parameters specify an invalid configuration, set the parameters
        // to the hardcoded defaults first, then set them to the values used in the steady state.
        try {
            int numResolicits = mMulticastResolicitEnabled
                    ? NUD_MCAST_RESOLICIT_NUM
                    : INVALID_NUD_MCAST_RESOLICIT_NUM;
            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
        } catch (Exception e) {
            Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
        }
        setNeighbourParametersForSteadyState();

        mIpNeighborMonitor = dependencies.makeIpNeighborMonitor(h, mLog,
                (NeighborEvent event) -> {
                    if (mInterfaceParams.index != event.ifindex) return;
                    if (!mNeighborWatchList.containsKey(event.ip)) return;

                    final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);

                    // TODO: Consider what to do with other states that are not within
                    // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
                    if (event.nudState == StructNdMsg.NUD_FAILED) {
                        // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
                        // attempts fail, trigger the neighbor lost event and disconnect.
                        mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
                        handleNeighborLost(prev, event);
                    } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
                        handleNeighborReachable(prev, event);
                    }
                });
        mIpNeighborMonitor.start();
        mIpReachabilityMetrics = dependencies.getIpReachabilityMonitorMetrics();
    }

  mCallback保存了我们传入的Callback对象,它实现了notifyLost()函数;IpNeighborMonitor会接受、解析来自kernel的packet,包含了我们需要monitor哪些IP,以及接收NUD lost的结果,并调用handleNeighborLost()进行接下去通知WiFi framework NUD lost结果的处理。

  IpNeighborMonitor接收来自IpReachabilityMonitor的处理,创建IpNeighborMonitor的时候,传入了一个用lambda表达式创建的NeighborEventConsumer对象,它实现了accept函数,主要处理:

1、解析从kernel上报的需要监听的IP地址集,它保存在mNeighborWatchList集合中

2、判断当前的event是不是通知NUD_FAILED,如果是就调用handleNeighborLost()处理:

// frameworks/libs/net/common/device/com/android/net/module/util/ip/IpNeighborMonitor.java
public class IpNeighborMonitor extends NetlinkMonitor {
    private static final String TAG = IpNeighborMonitor.class.getSimpleName();
    private static final boolean DBG = false;
    private static final boolean VDBG = false;

    /**
     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
     * for the given IP address on the specified interface index.
     *
     * @return 0 if the request was successfully passed to the kernel; otherwise return
     *         a non-zero error code.
     */
    public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
        if (DBG) Log.d(TAG, msgSnippet);

		// 创建一个Netlink消息,用于请求内核执行邻居探测。
        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);

        try {
        	// 使用NetlinkUtils发送一个单次内核消息。
            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
        } catch (ErrnoException e) {
            Log.e(TAG, "Error " + msgSnippet + ": " + e);
            return -e.errno;
        }

        return 0;
    }

    /**
     * An event about a neighbor.
     */
    public static class NeighborEvent {
        public final long elapsedMs;
        public final short msgType;
        public final int ifindex;
        @NonNull
        public final InetAddress ip;
        public final short nudState;
        @NonNull
        public final MacAddress macAddr;

        public NeighborEvent(long elapsedMs, short msgType, int ifindex, @NonNull InetAddress ip,
                short nudState, @NonNull MacAddress macAddr) {
            this.elapsedMs = elapsedMs;
            this.msgType = msgType;
            this.ifindex = ifindex;
            this.ip = ip;
            this.nudState = nudState;
            this.macAddr = macAddr;
        }

        boolean isConnected() {
            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
        }

        public boolean isValid() {
            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
        }

        @Override
        public String toString() {
            final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
            return j.add("@" + elapsedMs)
                    .add(stringForNlMsgType(msgType, NETLINK_ROUTE))
                    .add("if=" + ifindex)
                    .add(ip.getHostAddress())
                    .add(StructNdMsg.stringForNudState(nudState))
                    .add("[" + macAddr + "]")
                    .toString();
        }
    }

    /**
     * A client that consumes NeighborEvent instances.
     * Implement this to listen to neighbor events.
     */
    public interface NeighborEventConsumer {
        // 每个在netlink套接字上接收到的邻居事件都会传递到这里。
        // 子类应过滤感兴趣的事件。
        /**
         * Consume a neighbor event
         * @param event the event
         */
        void accept(NeighborEvent event);
    }

    private final NeighborEventConsumer mConsumer;

    public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
        super(h, log, TAG, NETLINK_ROUTE, OsConstants.RTMGRP_NEIGH);
        mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
    }

    @Override
    public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
        if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
            mLog.e("non-rtnetlink neighbor msg: " + nlMsg);
            return;
        }

        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) nlMsg;
        final short msgType = neighMsg.getHeader().nlmsg_type;
        final StructNdMsg ndMsg = neighMsg.getNdHeader();
        if (ndMsg == null) {
            mLog.e("RtNetlinkNeighborMessage without ND message header!");
            return;
        }

        final int ifindex = ndMsg.ndm_ifindex;
        final InetAddress destination = neighMsg.getDestination();
        final short nudState =
                (msgType == RTM_DELNEIGH)
                ? StructNdMsg.NUD_NONE
                : ndMsg.ndm_state; // 获取邻居状态。

        final NeighborEvent event = new NeighborEvent(
                whenMs, msgType, ifindex, destination, nudState,
                getMacAddress(neighMsg.getLinkLayerAddress()));

        if (VDBG) {
            Log.d(TAG, neighMsg.toString());
        }
        if (DBG) {
            Log.d(TAG, event.toString());
        }

        mConsumer.accept(event); // 将邻居事件传递给IpReachabilityMonitor进行处理。
    }

    private static MacAddress getMacAddress(byte[] linkLayerAddress) {
        if (linkLayerAddress != null) {
            try {
                return MacAddress.fromBytes(linkLayerAddress);
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
            }
        }

        return null;
    }
}

  IpNeighborMonitor的代码中startKernelNeighborProbe这个方法用于请求内核执行邻居可达性探测(IPv4 ARP或IPv6 ND)对于给定的IP地址和指定的接口索引。processNetlinkMessage方法是NetlinkMonitor类的一个覆盖方法,用于处理Netlink消息。

  至于具体解析packet的过程这里仔细叙述了,简单分析下IpNeighborMonitor的几个父类的代码即可,下面是他的父类关系图。
在这里插入图片描述

// frameworks/libs/net/common/device/com/android/net/module/util/ip/NetlinkMonitor.java
public class NetlinkMonitor extends PacketReader {
    ......
    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
            int family, int bindGroups, int sockRcvbufSize) {
        super(h, NetlinkUtils.DEFAULT_RECV_BUFSIZE);
        mLog = log.forSubComponent(tag);
        mTag = tag;
        mFamily = family;
        mBindGroups = bindGroups;
        mSockRcvbufSize = sockRcvbufSize;
    }

    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
            int family, int bindGroups) {
        this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
    }

    @Override
    protected FileDescriptor createFd() {
        FileDescriptor fd = null;

        try {
            fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
            if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
                try {
                    Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
                } catch (ErrnoException e) {
                    Log.wtf(mTag, "Failed to set SO_RCVBUF to " + mSockRcvbufSize, e);
                }
            }
            Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
            NetlinkUtils.connectSocketToNetlink(fd);

            if (DBG) {
                final SocketAddress nlAddr = Os.getsockname(fd);
                Log.d(mTag, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
            }
        } catch (ErrnoException | SocketException e) {
            logError("Failed to create rtnetlink socket", e);
            closeSocketQuietly(fd);
            return null;
        }

        return fd;
    }

    @Override
    protected void handlePacket(byte[] recvbuf, int length) {
        final long whenMs = SystemClock.elapsedRealtime();
        final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
        byteBuffer.order(ByteOrder.nativeOrder());

        while (byteBuffer.remaining() > 0) {
            try {
                final int position = byteBuffer.position();
                final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer, mFamily);
                if (nlMsg == null || nlMsg.getHeader() == null) {
                    byteBuffer.position(position);
                    mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
                    break;
                }

                if (nlMsg instanceof NetlinkErrorMessage) {
                    mLog.e("netlink error: " + nlMsg);
                    continue;
                }

                processNetlinkMessage(nlMsg, whenMs);
            } catch (Exception e) {
                mLog.e("Error handling netlink message", e);
            }
        }
    }
    
	......

    protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { }
}
// frameworks/libs/net/common/device/com/android/net/module/util/PacketReader.java
public abstract class PacketReader extends FdEventsReader<byte[]> {

    public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;

    protected PacketReader(Handler h) {
        this(h, DEFAULT_RECV_BUF_SIZE);
    }

    protected PacketReader(Handler h, int recvBufSize) {
        super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]);
    }

    @Override
    protected final int recvBufSize(byte[] buffer) {
        return buffer.length;
    }

    /**
     * Subclasses MAY override this to change the default read() implementation
     * in favour of, say, recvfrom().
     *
     * Implementations MUST return the bytes read or throw an Exception.
     */
    @Override
    protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
        return Os.read(fd, packetBuffer, 0, packetBuffer.length);
    }
}
public abstract class FdEventsReader<BufferType> {
    private static final String TAG = FdEventsReader.class.getSimpleName();
    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
    private static final int UNREGISTER_THIS_FD = 0;

    @NonNull
    private final Handler mHandler;
    @NonNull
    private final MessageQueue mQueue;
    @NonNull
    private final BufferType mBuffer;
    @Nullable
    private FileDescriptor mFd;
    private long mPacketsReceived;

    protected static void closeFd(FileDescriptor fd) {
        try {
            SocketUtils.closeSocket(fd);
        } catch (IOException ignored) {
        }
    }

    protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) {
        mHandler = h;
        mQueue = mHandler.getLooper().getQueue();
        mBuffer = buffer;
    }

    @VisibleForTesting
    @NonNull
    protected MessageQueue getMessageQueue() {
        return mQueue;
    }

    /** Start this FdEventsReader. */
    public boolean start() {
        if (!onCorrectThread()) {
            throw new IllegalStateException("start() called from off-thread");
        }

        return createAndRegisterFd();
    }

    /** Stop this FdEventsReader and destroy the file descriptor. */
    public void stop() {
        if (!onCorrectThread()) {
            throw new IllegalStateException("stop() called from off-thread");
        }

        unregisterAndDestroyFd();
    }

    @NonNull
    public Handler getHandler() {
        return mHandler;
    }

    protected abstract int recvBufSize(@NonNull BufferType buffer);

    /** Returns the size of the receive buffer. */
    public int recvBufSize() {
        return recvBufSize(mBuffer);
    }

    public final long numPacketsReceived() {
        return mPacketsReceived;
    }

    @Nullable
    protected abstract FileDescriptor createFd();
    
    protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer)
            throws Exception;

    protected void handlePacket(@NonNull BufferType recvbuf, int length) {}

    protected boolean handleReadError(@NonNull ErrnoException e) {
        logError("readPacket error: ", e);
        return true; // by default, stop reading on any error.
    }

    protected void logError(@NonNull String msg, @Nullable Exception e) {}

    protected void onStart() {}

    protected void onStop() {}

    private boolean createAndRegisterFd() {
        if (mFd != null) return true;

        try {
            mFd = createFd();
        } catch (Exception e) {
            logError("Failed to create socket: ", e);
            closeFd(mFd);
            mFd = null;
        }

        if (mFd == null) return false;

        getMessageQueue().addOnFileDescriptorEventListener(
                mFd,
                FD_EVENTS,
                (fd, events) -> {
                    if (!isRunning() || !handleInput()) {
                        unregisterAndDestroyFd();
                        return UNREGISTER_THIS_FD;
                    }
                    return FD_EVENTS;
                });
        onStart();
        return true;
    }

    protected boolean isRunning() {
        return (mFd != null) && mFd.valid();
    }

    // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
    private boolean handleInput() {
        while (isRunning()) {
            final int bytesRead;

            try {
                bytesRead = readPacket(mFd, mBuffer);
                if (bytesRead < 1) {
                    if (isRunning()) logError("Socket closed, exiting", null);
                    break;
                }
                mPacketsReceived++;
            } catch (ErrnoException e) {
                if (e.errno == OsConstants.EAGAIN) {
                    // We've read everything there is to read this time around.
                    return true;
                } else if (e.errno == OsConstants.EINTR) {
                    continue;
                } else {
                    if (!isRunning()) break;
                    final boolean shouldStop = handleReadError(e);
                    if (shouldStop) break;
                    continue;
                }
            } catch (Exception e) {
                if (isRunning()) logError("readPacket error: ", e);
                break;
            }

            try {
                handlePacket(mBuffer, bytesRead);
            } catch (Exception e) {
                logError("handlePacket error: ", e);
                Log.wtf(TAG, "Error handling packet", e);
            }
        }

        return false;
    }

    private void unregisterAndDestroyFd() {
        if (mFd == null) return;

        getMessageQueue().removeOnFileDescriptorEventListener(mFd);
        closeFd(mFd);
        mFd = null;
        onStop();
    }

    private boolean onCorrectThread() {
        return (mHandler.getLooper() == Looper.myLooper());
    }
}

  从关系上来看IpNeighborMonitor.()start会创建出一个获取kernel netlink消息的socket,并持续获取kernel的消息。

  总的来说,FdEventsReader 提供了一个基础的事件读取框架,PacketReader 扩展了这个框架以读取数据包,NetlinkMonitor 进一步扩展了这个框架以处理 Netlink 消息,而 IpNeighborMonitor 专门处理 IP 邻居消息。每个类都在前一个类的基础上添加了更具体的逻辑和功能。

  这时候基本框架流程已经梳理清楚了,但本着将流程跟完的原则,再看下前面还未分析的IpReachabilityMonitor::probeAll()调用,以及wifi framework的处理

// packages/modules/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
    public void probeAll(boolean dueToRoam) {
        setNeighbourParametersPostRoaming();

        final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
        if (!ipProbeList.isEmpty()) {
            mDependencies.acquireWakeLock(getProbeWakeLockDuration());
        }

        for (InetAddress ip : ipProbeList) {
            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
            mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
                     ip.getHostAddress(), rval));
            logEvent(IpReachabilityEvent.PROBE, rval);
        }
        mLastProbeTimeMs = SystemClock.elapsedRealtime();
        if (dueToRoam) {
            mLastProbeDueToRoamMs = mLastProbeTimeMs;
        } else {
            mLastProbeDueToConfirmMs = mLastProbeTimeMs;
        }
    }

  probeAll()中会遍历mNeighborWatchList的IP地址,分别对其进行NUD检测

  通过Netlink机制请求kernel进行probe后,Framework能做的就是等待结果了;如果kernel检测遇到了NUD失败,这个信息经过packet解析、封装成event之后,会由IpNeighborMonitor::NeighborEventConsumer mConsumer处理,mConsumer也就是IpReachabilityMonitor创建IpNeighborMonitor时,用lambda表达式创建的对象

        mIpNeighborMonitor = dependencies.makeIpNeighborMonitor(h, mLog,
                (NeighborEvent event) -> {
                    if (mInterfaceParams.index != event.ifindex) return;
                    if (!mNeighborWatchList.containsKey(event.ip)) return;

                    final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);

                    // TODO: Consider what to do with other states that are not within
                    // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
                    if (event.nudState == StructNdMsg.NUD_FAILED) {
                        // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
                        // attempts fail, trigger the neighbor lost event and disconnect.
                        mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
                        handleNeighborLost(prev, event);
                    } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
                        handleNeighborReachable(prev, event);
                    }
                });

  如果NeighborEvent的msg是NUD_FAILED,说明NUD检测失败,需要通知给上层这个事件

    private void handleNeighborLost(@Nullable final NeighborEvent prev,
            @NonNull final NeighborEvent event) {
        final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);

        ......
        
        final boolean lostProvisioning =
                (mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned())
                        || (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned()
                                && !ignoreIncompleteIpv6Neighbor);
        final NudEventType type = getNudFailureEventType(isFromProbe(),
                isNudFailureDueToRoam(), lostProvisioning);

        if (lostProvisioning) {
            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
            Log.w(TAG, logMsg);
            // TODO: remove |ip| when the callback signature no longer has
            // an InetAddress argument.
            mCallback.notifyLost(ip, logMsg, type);
        }
        logNudFailed(event, type);
    }

  随之会通过比较当前两个IP的配置信息来判断,连接是否已经不可达了,如果是就会通过mCallback对象回调notifyLost()通知上层,由前面可知这个Callback对象经过了几次封装,为了节省时间我们直接看ClientModeImpl中最原始的那个Callback实现

    class IpClientCallbacksImpl extends IpClientCallbacks {
        ......

        @Override
        public void onReachabilityLost(String logMsg) {
            if (mIpClientCallbacks != this) return;
            mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);
            sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
        }

        @Override
        public void onReachabilityFailure(ReachabilityLossInfoParcelable lossInfo) {
            if (mIpClientCallbacks != this) return;
            sendMessage(CMD_IP_REACHABILITY_FAILURE, lossInfo);
        }
	......
    }

  IpClientCallbacksImpl::onReachabilityLost()会被调用,并发送CMD_IP_REACHABILITY_LOST msg,看该msg的处理过程

    class L2ConnectedState extends RunnerState {

        @Override
        public boolean processMessageImpl(Message message) {
				......
                case CMD_IP_REACHABILITY_LOST: {
                    if (mVerboseLoggingEnabled && message.obj != null) log((String) message.obj);
                    mWifiDiagnostics.triggerBugReportDataCapture(
                            WifiDiagnostics.REPORT_REASON_REACHABILITY_LOST);
                    mWifiMetrics.logWifiIsUnusableEvent(mInterfaceName,
                            WifiIsUnusableEvent.TYPE_IP_REACHABILITY_LOST);
                    mWifiMetrics.addToWifiUsabilityStatsList(mInterfaceName,
                            WifiUsabilityStats.LABEL_BAD,
                            WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST, -1);
                    if (mWifiGlobals.getIpReachabilityDisconnectEnabled()) {
                        handleIpReachabilityLost();
                    } else {
                        logd("CMD_IP_REACHABILITY_LOST but disconnect disabled -- ignore");
                    }
                    break;
                }
			......
            }
            ......
        }

    private void handleIpReachabilityLost() {
        mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
                getConnectedWifiConfiguration(),
                WifiBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, mWifiInfo.getRssi());
        mWifiScoreCard.noteIpReachabilityLost(mWifiInfo);
        mWifiInfo.setInetAddress(null);
        mWifiInfo.setMeteredHint(false);

        // Disconnect via supplicant, and let autojoin retry connecting to the network.
        mWifiNative.disconnect(mInterfaceName);
        updateCurrentConnectionInfo();
    }

L2ConnectedState会处理CMD_IP_REACHABILITY_LOST msg,如果mIpReachabilityDisconnectEnabled配置为true,就会去主动disconnect WiFi,如果不想断连wifi 就将其配置为false即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1907201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

雅特力AT32F423开启FPU跟不开启FPU性能差异

在大多数Cortex-M的处理器中调试端口包含一个32位的自由运行计数器&#xff0c;它可以计算 CPU 的时钟周期。计数器是 Debug 观察和跟踪(DWT)模块的一部分&#xff0c;可以很容易地用于测量代码的执行时间。下面的代码是启用和初始化这个特性非常有用。 注意测试需要重新断电上…

浅谈信息技术高效课堂管理:策略、技巧与实践

引言&#xff1a; 在信息化教育的浪潮中&#xff0c;信息技术课程正逐渐成为学校教育体系中的重要组成部分。然而&#xff0c;信息技术课堂的特殊性——高互动性、高度依赖电子设备&#xff0c;给课堂管理带来了前所未有的挑战。如何在保证教学效率的同时&#xff0c;维护良好…

钡铼技术低成本解决方案:注塑机OPC UA接入的智慧选择

在工业4.0与“中国制造2025”战略的引领下&#xff0c;传统注塑机行业正面临前所未有的转型升级挑战。为了在这场技术革新浪潮中保持竞争力&#xff0c;积极接纳并实施OPC UA标准已成为必然趋势。那么&#xff0c;对于传统注塑机而言&#xff0c;如何平滑过渡实现对OPC UA的支持…

C++入门(C语言过渡)

文章目录 前言一、C关键字二、命名空间三、C输入&输出四、缺省参数五、函数重载六、引用七、inline八、nullptr总结 前言 C是一种通用的、高级的、静态类型的编程语言&#xff0c;它在20世纪80年代由丹尼斯里奇创建的C语言基础上发展而来。以下是C发展的一些重要里程碑。 1…

Linux 常见命令使用(超详细版)

文章目录 基本echo关机、重启命令上传下载工具rz/sz<、>、>>ls管道clearcdpwd 文件处理类命令mkdirrmdirtouchcpmv 文件查看类命令catmorelessheadtail 用户管理命令useraddpasswdusermoduserdelgroupaddgroupdel 文件属性类命令chmodchownchgrpln 文件压缩类命令t…

【免费数字孪生平台】零代码制作智慧农业蔬菜大棚可视化

一&#xff0e;智慧农业的价值 智慧农业&#xff0c;作为农业中的智慧经济形态&#xff0c;是现代科学技术与农业种植深度融合的产物。它通过将物联网、云计算、大数据、人工智能等现代信息技术集成应用于农业生产中&#xff0c;实现了农业生产的无人化、自动化和智能化管理。…

C语言下的文件详解

主要内容 文件概述文件指针文件的打开与关闭文件的读写 文件 把输入和输出的数据以文件的形式保存在计算机的外存储器上&#xff0c;可以确保数据能随时使用&#xff0c;避免反复输入和读取数据 文件概述 文件是指一组相关数据的有序集合 文件是存储数据的基本单位&#…

刘永先独家访谈:揭秘格行随身WiFi万店战略背后的“难而正确”之路

在随身WiFi市场鱼龙混杂、乱象频出的背景下&#xff0c;格行随身WiFi却毅然选择了一条少有人走的路——大规模布局线下门店。近日&#xff0c;每日财经新闻的记者有幸专访了格行随身WiFi的创始人刘永先&#xff0c;深入探讨了这一战略决策背后的考量与坚持。&#xff08;文章转…

DearLicy主题 | 小众化小清新风格的博客主题源码 | Typecho主题模版

DearLicy主题&#xff0c;一款小众化小清新风格的博客主题 主题支持Typecho所支持的所有版本PHP 简约、小众、优雅 安装教程 1.将主题上传至/usr/themes/文件夹下解压 2.后台进行启用 3.访问前台查看效果 图片&#xff1a;

Android Studio 的Gradle下载慢,Gradle切换下载源

看图 下面的文字地址因为转义符号的问题&#xff0c;https后面少了一个斜杠看图片进行补充&#xff0c;直接复制不知道能不能用 distributionUrlhttps://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip

单片机学习(17)--AD/DA

AD/DA 16.1AD/DA的基础知识1.AD/DA介绍2.硬件电路模型3.硬件电路4.运算放大器5.运放电路6.DA原理6.AD原理7.AD/DA性能指标8.XPT2046 16.2AD模数转换&DA数模转换1.AD模数转换&#xff08;1&#xff09;工程目录&#xff08;2&#xff09;main.c函数&#xff08;3&#xff09…

这个暑假,带娃就交给华为儿童手表5 Pro吧

一年一度孩子们最期待的暑期终于到啦&#xff01;在这个充足的时间段里&#xff0c;孩子们可以尽情的释放他们的热情与好奇心&#xff0c;家长们也可以努力为孩子们创造更多的回忆。但是&#xff0c;不少家长暑期带娃总是发愁&#xff0c;宝贝们玩的多&#xff0c;家长们需要注…

10分钟使用网站构建框架hugo本地搭建个人网站并快速上线详细教程

文章目录 前言1. 安装环境2. 配置环境变量与hugo安装2.1 创建程序目录2.2 配置环境变量2.3 查看程序版本 3. 创建博客网站3.1 创建站点3.2 在站点中创建一篇文章3.3 为网站添加主题 4. 本地访问测试5. 安装内网穿透工具6. 配置公网地址7. 配置固定公网地址 前言 今天和大家分享…

计算机类期刊横纵向对比

备注&#xff1a;综合影响因子更具针对性&#xff0c;将科技类期刊和人文社科期刊的影响力考虑&#xff0c;更加聚焦于某一特定科学领域&#xff1b;复合影响因子是基于期刊、学位论文、以及会议论文等多个类型的文献作为计算基础。 两者都是通过前两年发表的可被引文献在统计年…

算法重新刷题

基础算法 前缀和 一维前缀和 [USACO16JAN] Subsequences Summing to Sevens S - 洛谷 这一题主要是需要结合数学知识来求解&#xff0c; #include <iostream> #include <cstring> #include <cstdio> #include <algorithm>using namespace std;con…

CANopen协议---PDO使用配置

1、CANopen知识回顾 在上一讲中&#xff0c;已经对CANopen的基本结构和整体内容进行了一番梳理&#xff0c;本笔记主要整理了一下CANopen如何配置PDO&#xff0c;实现数据周期性自动上传和控制信号快速发送等操作。 CANopen协议开发梳理总结笔记教程-CSDN博客文章浏览阅读920次…

uniapp父页面调用子页面 组件方法记录

文章目录 导文如何点击父页面&#xff0c;触发子页面函数先写一个子页面的基础内容父元素 如何点击父页面&#xff0c;修改子页面的值先写一个子页面的基础内容父元素 导文 如何点击父页面&#xff0c;触发子页面函数&#xff1f; 如何点击父页面&#xff0c;修改子页面的值&am…

LVGL移植与VS模拟器使用

一、移植文件介绍 二、移植部分 第一步&#xff1a;创建LVGL文件夹 第二步&#xff1a; 构造LVGL文件夹&#xff1a;LVGL - GUI - lvgl - 第三步&#xff1a;添加文件 3.1 从examples中添加2个.c文件 3.2 从src中添加文件 draw文件 extra文件 第四步&#xff1a; 三、Ke…

WordPress作品设计素材图片站资讯文章教程uigreat主题

主题介绍 uigreat主题是一款wordpress作品主题&#xff0c;发布设计作品素材文章&#xff0c;适合作品展示、设计等站点使用等&#xff0c;这款主题都非常合适。 1、自适应设计&#xff0c;PC、平板、手机等均可正常浏览&#xff1b; 2、图片缩略图可自定义高度&#xff0c;主…

2005-2023年各省居民人均消费支出、城镇居民人均消费支出、农村居民人均消费支出数据(无缺失)

2005-2023年各省居民人均消费支出、城镇居民人均消费支出、农村居民人均消费支出数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;2005-2023年 2、来源&#xff1a;国家统计局、统计年鉴 3、范围&#xff1a;31省 4、指标&#xff1a;全体居民人均消费支出、城镇居…