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

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.mfbz.cn/a/774270.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LVS FILTER UNUSED OPTION

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 过滤一些版图与spice网表对不上的器件。 一般后端遇不到这个问题,因为通常是需要写到网表中的decap没有写出来造成的,如下图。

ceph-volume inventory KeyError: ‘TYPE‘ 处理

是否有人跟我一样碰到这样的情况 执行ceph-volume inventory报错 还好有错误日志可以看 [2024-07-05 11:40:40,540][ceph_volume.process][INFO ] Running command: /usr/sbin/blkid -c /dev/null -p /dev/ceph-c5fd6684-3851-49ab-bd44-f6743a79e24f/osd-block-42d41cd1-82…

半导体切割研磨废水的处理技术

半导体切割研磨废水处理是一个复杂而关键的过程&#xff0c;其废水主要来源于切割、研磨等工艺环节&#xff0c;这些过程中使用的化学品、冷却水、洗涤水等会产生含有重金属、有机物、酸碱度不稳定以及高浓度硅化合物等污染物的废水。针对这些废水的特性&#xff0c;半导体行业…

2025深圳国际消费电子展览会

2025深圳国际消费电子展览会 时间&#xff1a;2025年06月25-27日 地点&#xff1a;深圳国际会展中心(新馆) 详询主办方陆先生 I38&#xff08;前三位&#xff09; I82I&#xff08;中间四位&#xff09; 9I72&#xff08;后面四位&#xff09; 展会介绍&#xff1a; 20…

SRC实战:无中生有的接口和参数

今天分享的这个漏洞怎么说呢&#xff0c;虽然也是个高危&#xff0c;但是其实挺简单的&#xff0c;一个很eazy的越权&#xff0c;但是我觉得多多少少还是有点意思吧。 0x00 这是一个移动端的应用程序&#xff0c;前面比较常规&#xff0c;模拟器 BP&#xff0c;跑了一下所有…

你的机器人购物新体验——安全、高效、无忧

如果你跟我一样&#xff0c;对找到那些“恰到好处”的商品充满渴望&#xff0c;那么&#xff0c;让我来告诉你为什么BFT会成为你的下一个购物“心头好”。 BFT的优势 高效安全的支付体系&#xff1a;BFT交易系统保障了交易的安全性和透明性&#xff0c;让你的每一笔消费都安全…

python解析Linux top 系统信息并生成动态图表(pandas和matplotlib)

文章目录 0. 引言1. 功能2.使用步骤3. 程序架构流程图结构图 4. 数据解析模块5. 图表绘制模块6. 主程序入口7. 总结8. 附录完整代码 0. 引言 在性能调优和系统监控中&#xff0c;top 命令是一种重要工具&#xff0c;提供了实时的系统状态信息&#xff0c;如 CPU 使用率、内存使…

我爱服务器——LVM实战学习

后来呀&#xff0c;天亮之前毕业后踏入服务器领域了。。。。。。 LVM&#xff08;Logical Volume Manager&#xff09;是一个高级的磁盘管理框架&#xff0c;它允许用户将多个物理硬盘组合成一个逻辑卷&#xff0c;从而提供更大的存储空间、更高的灵活性和更好的数据管理能力。…

创业新选择:乐财业,让财税事业更简!

在市场需求和国家政策的双重驱动之下&#xff0c;【乐财业智慧财税赋能平台】基于鹦鹉智能财税系统、完善的税务资源供应链&#xff0c;财税专家团等&#xff0c;首创为企业提供“业财税”一体化的一站式财税解决方案&#xff0c;让财税生意更好做。 乐财业通过全国布局线下渠道…

Windows中Git的使用(2024最新版)

Windows中Git的使用 获取ssh keys本地绑定邮箱初始化本地仓库添加到本地缓存区提交到本地缓存区切换本地分支为main关联远程分支推送到GitHub查看推送日志 Git 2020年发布了新的默认分支名称"main"&#xff0c;取代了"master"作为主分支的名称。操作有了些…

MySQL数据库主从复制+mycat读写分离+MHA实操

目录 一、主从复制 1.1 主从复制简介 1.2 MySQL支持的复制类型 1.3 主从复制的工作过程 1.4 主从复制的同步模式 1.4.1 异步复制&#xff08;Asynchronous replication&#xff09; 1.4.2 全同步复制&#xff08;Fully synchronous replication&#xff09; 1.4.3 半同…

React+TS前台项目实战(二十三)-- 基于属性自定义数值显示组件Decimal封装

文章目录 前言Decimal组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天要封装的Decimal 组件&#xff0c;是通过传入的属性进行定制化显示数值&#xff0c;在渲染时&#xff0c;会根据不同的情况显示整数部分、小数部分和单位&#xff0c;支持自定义样式…

Go源码--context包

简介 Context 是go语言比较重要的且也是比较复杂的一个结构体&#xff0c;Context主要有两种功能: 取消信号&#xff1a;包括直接取消&#xff08;涉及的结构体&#xff1a;cancelCtx ; 涉及函数&#xff1a;WithCancel&#xff09;和携带截止日期的取消&#xff08;涉及结构…

【Java安装】windows10+JDK21+IDEA

文章目录 一、JDK安装1. 下载完成后按照自己需要的位置安装2. 配置环境变量2.1 JAVA_HOME变量2.2 PATH配置 3. 验证4. helloworld 二、IDEA安装三、IDEA-HelloWorld 一、JDK安装 JDK安装链接 1. 下载完成后按照自己需要的位置安装 2. 配置环境变量 2.1 JAVA_HOME变量 安装…

JVM原理(三):JVM对象回收判定机制与回收算法

如何判断一个对象是否存活(即是否还分配在堆中)&#xff0c;那看他是否还在用。 1. 引用计数算法 这是一种判断方式&#xff0c;相应的方法就是&#xff1a;如果一个对象被引用&#xff0c;那将被引用的对象中的一个计数器加一&#xff0c;引用失效就减一。在任何时刻引用计数…

QT实现GIF动图显示(小白版,可直接copy使用)

需要你自己提前设置好动图的位置&#xff0c;本例中存放于"/Users/PLA/PLA/PLA.gif widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMovie> #include <QLabel>class Widget : public QWidget {Q_OBJECTpublic:explicit Wid…

mssql查询历史执行过的语句日志

SELECT deqs.creation_time,dest.text AS [SQL Text],deqs.execution_count,deqs.total_elapsed_time,deqs.total_worker_time FROM sys.dm_exec_query_stats AS deqs CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest--where dest.text like %这个是我的条件&#…

基于Springboot的智慧养老中心管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于Springboot的智慧养老中心管理系统,…

全景图三维3D模型VR全景上传展示H5开发

全景图三维3D模型VR全景上传展示H5开发 3D互动体验平台的核心功能概览 兼容广泛格式&#xff1a;支持OBJ、FBX、GLTF等主流及前沿3D模型格式的无缝上传与展示&#xff0c;确保创意无界。 动态交互探索&#xff1a;用户可自由旋转、缩放、平移模型&#xff0c;深度挖掘每一处…

默安逐日实验室:XDP的应用实践

1. 网络数据包是如何进入进计算机的 众所周知&#xff0c;网络数据包通常需要在TCP/IP协议栈中进行处理&#xff0c;但网络数据包并不直接进入TCP/IP协议栈&#xff1b;相反&#xff0c;他们直接进入网络接口。因此&#xff0c;在数据包进入 TCP/IP 堆栈之前&#xff0c;它们已…