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即可