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.mzph.cn/web/40113.shtml

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

相关文章

一文了解常见DNS问题

当企业的DNS出现故障时&#xff0c;为不影响企业的正常运行&#xff0c;团队需要能够快速确定问题的性质和范围。那么有哪些常见的DNS问题呢&#xff1f; 域名解析失败&#xff1a; 当您输入一个域名&#xff0c;但无法获取到与之对应的IP地址&#xff0c;导致无法访问相应的网…

获取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…

昇思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该如何正确选择使用。 一、什么是…

个人引导页+音乐炫酷播放器(附加源码)

个人引导页音乐炫酷播放器 效果图部分源码完整源码领取下期更新内容 效果图 部分源码 //网站动态标题开始 var OriginTitile document.title, titleTime; document.addEventListener("visibilitychange", function() {if (document.hidden) {document.title "…

Python学习从0开始——Kaggle实践可视化001

Python学习从0开始——Kaggle实践可视化001 一、创建和加载数据集二、数据预处理1.按name检查&#xff0c;处理重复值&#xff08;查重&#xff09;2.查看存在缺失值的列并处理&#xff08;缺失值处理&#xff09;2.1按行或列查看2.2无法推测的数据2.3可由其它列推测的数据 3.拆…

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…

深入分析 Android BroadcastReceiver (九)

文章目录 深入分析 Android BroadcastReceiver (九)1. Android 广播机制的扩展应用与高级优化1.1 广播机制的扩展应用1.1.1 示例&#xff1a;有序广播1.1.2 示例&#xff1a;粘性广播1.1.3 示例&#xff1a;局部广播 1.2 广播机制的高级优化1.2.1 示例&#xff1a;使用 Pending…

空调计费系统是什么,你知道吗

空调计费系统是一种通过对使用空调的时间和能源消耗进行监测和计量来进行费用计算的系统。它广泛应用于各种场所&#xff0c;如家庭、办公室、商场等&#xff0c;为用户提供了方便、准确的能源使用管理和费用控制。 可实现功能 智能计费&#xff1a;中央空调分户计费系统通过智…