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.javaclass ConnectableState extends RunnerState {.....@Overridepublic 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);@Overridepublic 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();}@Overridepublic void onPreDhcpAction() {if (mIpClientCallbacks != this) return;sendMessage(CMD_PRE_DHCP_ACTION);}@Overridepublic void onPostDhcpAction() {if (mIpClientCallbacks != this) return;sendMessage(CMD_POST_DHCP_ACTION);}@Overridepublic void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {if (mIpClientCallbacks != this) return;if (dhcpResults != null) {sendMessage(CMD_IPV4_PROVISIONING_SUCCESS, dhcpResults);} else {sendMessage(CMD_IPV4_PROVISIONING_FAILURE);}}@Overridepublic 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);}@Overridepublic void onProvisioningFailure(LinkProperties newLp) {if (mIpClientCallbacks != this) return;mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST);sendMessage(CMD_IP_CONFIGURATION_LOST);}@Overridepublic void onLinkPropertiesChange(LinkProperties newLp) {if (mIpClientCallbacks != this) return;addPasspointInfoToLinkProperties(newLp);sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);}@Overridepublic void onReachabilityLost(String logMsg) {if (mIpClientCallbacks != this) return;mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);}@Overridepublic void onReachabilityFailure(ReachabilityLossInfoParcelable lossInfo) {if (mIpClientCallbacks != this) return;sendMessage(CMD_IP_REACHABILITY_FAILURE, lossInfo);}@Overridepublic void installPacketFilter(byte[] filter) {if (mIpClientCallbacks != this) return;sendMessage(CMD_INSTALL_PACKET_FILTER, filter);}@Overridepublic void startReadPacketFilter() {if (mIpClientCallbacks != this) return;sendMessage(CMD_READ_PACKET_FILTER);}@Overridepublic void setFallbackMulticastFilter(boolean enabled) {if (mIpClientCallbacks != this) return;sendMessage(CMD_SET_FALLBACK_PACKET_FILTERING, enabled);}@Overridepublic void setNeighborDiscoveryOffload(boolean enabled) {if (mIpClientCallbacks != this) return;sendMessage(CMD_CONFIG_ND_OFFLOAD, (enabled ? 1 : 0));}@Overridepublic void onPreconnectionStart(List<Layer2PacketParcelable> packets) {if (mIpClientCallbacks != this) return;sendMessage(CMD_START_FILS_CONNECTION, 0, 0, packets);}@Overridepublic void setMaxDtimMultiplier(int multiplier) {if (mIpClientCallbacks != this) return;sendMessage(CMD_SET_MAX_DTIM_MULTIPLIER, multiplier);}@Overridepublic 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@VisibleForTestingpublic 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() {@Overridepublic void update(boolean linkState) {sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED, linkState? ARG_LINKPROP_CHANGED_LINKSTATE_UP: ARG_LINKPROP_CHANGED_LINKSTATE_DOWN);}@Overridepublic 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);});}@Overridepublic 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;@Overridepublic 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() {@Overridepublic 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 0rec[1]: time=06-20 01:45:37.492 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_ENABLE_RSSI_POLL screen=on 1 0rec[2]: time=06-20 01:45:37.544 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_SET_SUSPEND_OPT_ENABLED screen=on 0 0rec[3]: time=06-20 01:45:37.587 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_RESET_SIM_NETWORKS screen=on 0 0rec[4]: time=06-20 01:45:37.587 processed=ConnectableState org=DisconnectedState dest=<null> what=CMD_RESET_SIM_NETWORKS screen=on 0 0rec[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=falserec[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_DISABLEDrec[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: DISCONNECTEDrec[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: ASSOCIATINGrec[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: ASSOCIATEDrec[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=falserec[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_HANDSHAKErec[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_HANDSHAKErec[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: COMPLETEDrec[15]: time=06-20 01:46:49.398 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_CONFIG_ND_OFFLOAD screen=on 1 0rec[16]: time=06-20 01:46:49.399 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_SET_FALLBACK_PACKET_FILTERING screen=on enabled=truerec[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=1rec[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,0rec[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 0rec[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 v4rrec[24]: time=06-20 01:46:49.647 processed=ConnectableState org=L3ProvisioningState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dnsrec[25]: time=06-20 01:46:49.648 processed=L2ConnectedState org=L3ProvisioningState dest=L3ConnectedState what=CMD_IP_CONFIGURATION_SUCCESSFUL screen=on 0 0rec[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=0rec[27]: time=06-20 01:46:50.256 processed=L3ConnectedState org=L3ConnectedState dest=<null> what=CMD_NETWORK_STATUS screen=on 1 0rec[28]: time=06-20 01:46:51.024 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_UPDATE_LINKPROPERTIES screen=on 0 0 v4 v4r v4dnsrec[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 v6dnsrec[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 v6dnsrec[31]: time=07-04 16:09:03.871 processed=ConnectableState org=L3ConnectedState dest=<null> what=CMD_SET_MAX_DTIM_MULTIPLIER screen=on maximum multiplier=2rec[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 v6dnsrec[33]: time=07-04 16:09:11.381 processed=L3ConnectedState org=L3ConnectedState dest=<null> what=what:131383 screen=onrec[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=0rec[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.javaclass L3ConnectedState extends RunnerState {L3ConnectedState(int threshold) {super(threshold, mWifiInjector.getWifiHandlerLocalLog());}@Overridepublic 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 {
......@Overridepublic 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@VisibleForTestingpublic 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;@NonNullpublic final InetAddress ip;public final short nudState;@NonNullpublic 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);}@Overridepublic 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 */ };}@Overridepublic 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);}@Overrideprotected 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;}@Overrideprotected 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)]);}@Overrideprotected 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.*/@Overrideprotected 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;@NonNullprivate final Handler mHandler;@NonNullprivate final MessageQueue mQueue;@NonNullprivate final BufferType mBuffer;@Nullableprivate 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@NonNullprotected 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();}@NonNullpublic 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;}@Nullableprotected 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.javapublic 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 {......@Overridepublic void onReachabilityLost(String logMsg) {if (mIpClientCallbacks != this) return;mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);}@Overridepublic 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 {@Overridepublic 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.pswp.cn/web/40113.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/40113.shtml
英文地址,請注明出處:http://en.pswp.cn/web/40113.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據驅動測試實踐:Postman 中使用數據文件的指南

Postman 是一個強大的 API 開發和測試工具&#xff0c;它支持數據驅動測試&#xff0c;允許測試者使用外部數據文件來驅動測試&#xff0c;實現測試用例的參數化。數據驅動測試可以顯著提高測試效率&#xff0c;減少重復工作&#xff0c;并允許測試用例覆蓋更廣泛的輸入場景。本…

一文了解常見DNS問題

當企業的DNS出現故障時&#xff0c;為不影響企業的正常運行&#xff0c;團隊需要能夠快速確定問題的性質和范圍。那么有哪些常見的DNS問題呢&#xff1f; 域名解析失敗&#xff1a; 當您輸入一個域名&#xff0c;但無法獲取到與之對應的IP地址&#xff0c;導致無法訪問相應的網…

【代碼隨想錄算法訓練營第五十九天|卡碼網110.字符串接龍、105.有向圖的完全可達性、106.島嶼的周長】

文章目錄 卡碼網110.字符串接龍105.有向圖的完全可達性106.島嶼的周長 卡碼網110.字符串接龍 這題是在字符串上進行廣搜&#xff0c;字符串廣搜是對一個字符串按照位置來搜索&#xff0c;與原字符串只有一個位置字符不同那么就是在原字符串的基礎上距離加1。因此需要一個字典來…

獲取VC賬號,是成為亞馬遜供應商的全面準備與必要條件

成為亞馬遜的供應商&#xff0c;擁有VC&#xff08;Vendor Central&#xff09;賬號&#xff0c;是眾多制造商和品牌所有者的共同目標。這不僅代表了亞馬遜對供應商的高度認可&#xff0c;也意味著獲得了更多的銷售機會和更廣闊的市場前景。 全面準備與必要條件是獲取VC賬號的關…

代碼轉換成AST語法樹移除無用代碼console.log、import

公司中代碼存在大量,因此產生 可以使用 @babel/parser 解析代碼生成 AST (抽象語法樹),然后使用 @babel/traverse 進行遍歷并刪除所有的 console.log 語句,最后使用 @babel/generator 生成修改后的代碼。 這里有一個網址,可以線上解析代碼轉換成AST語法樹: https://astex…

Python爬蟲康復訓練——筆趣閣《神魂至尊》

還是話不多說&#xff0c;很久沒寫爬蟲了&#xff0c;來個bs4康復訓練爬蟲&#xff0c;正好我最近在看《神魂至尊》&#xff0c;爬個txt文件下來看看 直接上代碼 """ 神魂至尊網址-https://www.bqgui.cc/book/1519/ """ import requests from b…

【C++】 解決 C++ 語言報錯:未定義行為(Undefined Behavior)

文章目錄 引言 未定義行為&#xff08;Undefined Behavior, UB&#xff09;是 C 編程中非常危險且難以調試的錯誤之一。未定義行為發生時&#xff0c;程序可能表現出不可預測的行為&#xff0c;導致程序崩潰、安全漏洞甚至硬件損壞。本文將深入探討未定義行為的成因、檢測方法…

零基礎STM32單片機編程入門(七)定時器PWM波輸出實戰含源碼視頻

文章目錄 一.概要二.PWM產生框架圖三.CubeMX配置一個TIME輸出1KHZ&#xff0c;占空比50%PWM波例程1.硬件準備2.創建工程3.測量波形結果 四.CubeMX工程源代碼下載五.講解視頻鏈接地址六.小結 一.概要 脈沖寬度調制(PWM)&#xff0c;是英文“Pulse Width Modulation”的縮寫&…

通過營銷本地化解鎖全球市場

在一個日益互聯的世界里&#xff0c;企業必須接觸到全球各地的不同受眾。營銷本地化是打開這些全球市場的關鍵。它包括調整營銷材料&#xff0c;使其與不同地區的文化和語言細微差別產生共鳴。以下是有效的營銷本地化如何推動您的全球擴張&#xff0c;并用實際例子來說明每一點…

UrbanGPT: Spatio-Temporal Large Language Models

1.文章信息 本次介紹的文章是2024年arxiv上一篇名為《UrbanGPT: Spatio-Temporal Large Language Models》的文章&#xff0c;UrbanGPT旨在解決城市環境中的時空預測問題&#xff0c;通過大語言模型&#xff08;LLM&#xff09;的強大泛化能力來應對數據稀缺的挑戰。 2.摘要 Ur…

SQLAlchemy批量操作數據

批量插入 session.bulk_insert_mappings(ModelClass, list(dict()))批量更新 session.bulk_update_mappings(ModelClass, list(dict())

Flutter的生命周期方法

Flutter的生命周期執行時機可以分為兩個主要部分&#xff1a;Flutter本身的組件生命周期&#xff08;widget生命周期&#xff09;和平臺相關的應用程序生命周期&#xff08;APP生命周期&#xff09;。 Widget生命周期 Widget生命周期可以細分為三個階段&#xff1a; 初始化階…

centos ssh一鍵升級到9.8版本腳本

背景 前端時間暴露出ssh漏洞&#xff0c;需要將服務器ssh版本&#xff0c;目前ssh版本最新版為9.8&#xff0c;故在服務器測試&#xff0c;準備將所有服務器ssh版本升級。腳本在centos7.6上親測可用。#!/bin/bash #Author Mr zhangECHO_GREEN() {echo -e "\033[32m $1...…

昇思MindSpore學習總結九——FCN語義分割

1、語義分割 圖像語義分割&#xff08;semantic segmentation&#xff09;是圖像處理和機器視覺技術中關于圖像理解的重要一環&#xff0c;AI領域中一個重要分支&#xff0c;常被應用于人臉識別、物體檢測、醫學影像、衛星圖像分析、自動駕駛感知等領域。 語義分割的目的是對圖…

【楚怡杯】職業院校技能大賽 “Python程序開發”賽項樣題三

Python程序開發實訓 &#xff08;時量&#xff1a;240分鐘&#xff09; 中國XX 實訓說明 注意事項 1. 請根據提供的實訓環境&#xff0c;檢查所列的硬件設備、軟件清單、材料清單是否齊全&#xff0c;計算機設備是否能正常使用。 2. 實訓結束前&#xff0c;在實訓平臺提供的…

從數據到智能,英智私有大模型助力企業實現數智化發展

在數字化時代&#xff0c;數據已經成為企業最重要的資源。如何將這些數據轉化為實際的業務價值&#xff0c;是每個企業面臨的重要課題。英智利用業界領先的清洗、訓練和微調技術&#xff0c;對企業數據進行深度挖掘和分析&#xff0c;定制符合企業業務場景的私有大模型&#xf…

篩選有合并單元格的數據

我們經常會使用合并單元格&#xff0c;比如下面表格&#xff0c;因為一個部門中會有不同的員工&#xff0c;就會出現如下表格&#xff1a; 但是當按部門去篩選的時候&#xff0c;會發現并不是我們預期的結果&#xff0c;部門列有空值&#xff0c;每個部門只有第一行數據可以被…

虛幻引擎 快速的色度摳圖 Chroma Key 算法

快就完了 ColorTolerance_PxRange為容差&#xff0c;這里是0-255的輸入&#xff0c;也就是px單位&#xff0c;直接用0-1可以更快 Key為目標顏色

PySide6 實現資源的加載:深入解析與實戰案例

目錄 1. 引言 2. 加載內置資源 3. 使用自定義資源文件&#xff08;.qrc&#xff09; 創建.qrc文件 編譯.qrc文件 加載資源 4. 動態加載UI文件 使用Qt Designer設計UI 加載UI文件 5. 注意事項與最佳實踐 6. 結論 在開發基于PySide6的桌面應用程序時&…

什么是 DDoS 攻擊及如何防護DDOS攻擊

自進入互聯網時代&#xff0c;網絡安全問題就一直困擾著用戶&#xff0c;尤其是DDOS攻擊&#xff0c;一直威脅著用戶的業務安全。而高防IP被廣泛用于增強網絡防護能力。今天我們就來了解下關于DDOS攻擊&#xff0c;以及可以防護DDOS攻擊的高防IP該如何正確選擇使用。 一、什么是…