在第1部分关于强制香港WiFi用户使用中国大陆DNS服务之后,这部分分析了当使用WiFi时,如何从最近的三星手机固件(截至2020年9月)生成qq.com上的定期DNS查询(如捕获DNS查询的用户所看到的)。
TL;DR:这些查询被同时发送到所有注册了WiFi连接的DNS服务器,包括在第1部分检查的情况下添加时的114.114.114.114。当满足以下前提条件时(取决于设置和链路条件),每隔20到60秒(取决于设置和链路条件),即在mCurrentMode不等于0的情况下连接WiFi(与一些已知设备连接除外),以及当屏幕打开时,这些查询将直接使用WiFi连接链路发送,而不通过配置的VPN/私有DNS。大概是因为它们打算用于测试WiFi网络。查询返回的IP地址不会被进一步使用,除非检查它们是否在私有IP范围内(在这种情况下,检查被认为是失败的)。
以下是对这些发现的技术性(阅读:非常无聊)描述。跳到末尾,查看禁用家庭WiFi DNS查询的提示。
为了更好地说明源代码,创建了一个新的Github存储库,其中显示了与第一部分相同的wifi-service.jar的反编译版本。它使用不同的反编译器模式(JADX)来尝试将更多代码反编译成Java,即使在正确性存在问题的情况下也是如此。存储库位于SM-N9750-TGY-1。
该逻辑的主控类同样位于文件WifiConnectivityMonitor or.java中的com.android.server.wifi.WifiConnectivityMonitor,下。
正如在上一部分中简要提到的,该类实现了一个状态机,该状态机表示被监视的WiFi连接的状态。这些状态接受消息并通过采取行动和/或转换到另一状态来响应它们。
另外,子类NetworkStatsAnalyzer负责监控连接和信号强度。分析器是一个Android处理程序,它在事件循环中使用消息。这些消息可以由父状态机从分析器本身或从其他子类发送,以控制分析器的活动。
我们上下文中最相关的消息定义如下(除非另有指定,否则行号是反编译代码中的WifiConnectivityMonitor or.java文件的行号):
发送到分析器以控制检查循环生命周期的消息(第115-117、254行):
私有静态最终INT ACTIVITY_CHECK_POLL=135221; 私有静态最终INT ACTIVATION_CHECK_START=135219; 私有静态最终INT ACTIVITY_CHECK_STOP=135220; ..。 私有静态最终INT NETWORK_STAT_CHECK_DNS=135223;
当手机屏幕打开时,Android将把android.intent.action.SCREEN_ON意图发送到这个类的网络接收器(第1149行),然后再将EVENT_SCREEN_ON发送到WifiConnectivityMonitor状态机。如果状态处于EvaluatedState[注1]之下,则第2138行附近的processMessage()方法处理消息(为简洁起见,代码中省略了对WifiConnectivityMonitor的一些引用):
案例EVENT_SCREEN_ON://135176 ..。 如果(mCurrentMode!=0){ SendMessage(obtainMessage(135188/*CMD_RSSI_FETCH*/,mRssiFetchToken,0); If(isValidState()&;&;getCurrentState()!=mLevel2State){ IF(mNetworkStatsAnalyzer!=NULL){ MNetworkStatsAnalyzer.sendEmptyMessage(ACTIVITY_CHECK_START);//135219 } StartEleCheck(); } 如果(mCurrentMode==1||mLinkDetectMode==1){ SendMessage(obtainMessage(CMD_TRAFFIC_POLL/*135193*/,mTrafficPollToken,0); } } ..。
这意味着,当mCurrentMode为非零时,WiFi已连接,并且连接不是与某些已知设备连接,它将向NetworkStatsAnalyzer发送ACTIVATION_CHECK_START消息。(它还将执行其他操作,如调用startEleCheck()来发现设备,但这些操作与我们的上下文无关。)。
消息ACTIVATION_CHECK_START也会从其他几个位置发送到NetworkStatsAnalyzer。下面是当WifiConnectivityMonitor进入有效状态(即在WiFi连接时)时触发的有效状态的enter()方法的摘录(行3077-)。
当mCurrentMode从0切换到有效状态下的其他值时,也会发送ACTIVATION_CHECK_START(第3112行)。加在一起,确保所有到达该状态的路径,即Wifi已连接、屏幕打开、mCurrentMode为非零时,都将触发ACTIVATION_CHECK_START。
请注意,一旦进入有效状态,NETWORK_STAT_CHECK_DNS也会发送到NetworkStatsAnalyzer。让我们先来看看这条消息做了什么。
下面显示了处理上述消息的NetworkStatsAnalyzer处理程序循环的框架,暂时省略了ACTIVITY_CHECK_POLL和ACTIVATION_CHECK_STOP的主体。(第4157-、4477-、5107-行)。
私有类NetworkStatsAnalyzer扩展处理程序 { ..。 私有布尔型mDnsInterrupted=false; 私有布尔型mDnsQuerded=false; Private long mLastDnsCheckTime=0; 私有布尔型mPollingStarted=false; 私有布尔值mPublicDnsCheckProcess=false; 私有布尔型mSkipRemainingDnsResults=false; ..。 Public void handleMessage(消息字符串){ Long Now=SystemClock.elapsedRealtime(); Final long elapsedRealtime=SystemClock.elapsedRealtime(); 最终WifiInfo WifiInfo=WifiConnectivityMonitor.this.syncGetCurrentWifiInfo(); Final int What=字符串。什么; 开关(什么){ ..。 案例活动_检查_开始: 如果(this.mPollingStarted)中断; 如果(WifiConnectivityMonitor.this.isMobileHotspot())断裂; If(this.isBackhaulDetectionEnabled()){ This.sendEmptyMessage(TCP_BACKHAUL_DETECTION_START);//135226 } This.sendEmptyMessage(ACTIVITY_CHECK_POLL); WifiConnectivityMonitor.this.initNetworkStatHistory(); This.mLastRssi=wifiInfo.getRssi(); This.mPollingStarted=true; 断掉; 案例活动_检查_轮询: ..。 断掉; 案例活动_检查_停止: ..。 断掉; 案例network_stat_check_dns: 如果(!WifiConnectivityMonitor.this.isMobileHotspot()){ CheckPublicDns(); } 断掉; } } }。
如果WiFi不是移动热点,则在WiFi连接时发送的消息network_stat_check_dns将调用方法checkPublicDns()。方法如下(第4262-行)。
Public void checkPublicDns(){ 如果(WifiConnectivityMonitor.this.inChinaNetwork()){ MPublicDnsCheckProcess=false; 返回; } MPublicDnsCheckProcess=true; MNsaQcStep=1; WifiConnectivityMonitor wifiConnectivityMonitor=WifiConnectivityMonitor or.this; 字符串字符串=wifiConnectivityMonitor.mParam.DEFAULT_URL_STRING; DnsThread mDnsThread=new DnsThread(TRUE,STR,this,10000); MDnsThread.start(); WifiConnectivityMonitor or.this.mDnsThreadID=mDnsThread.getId(); 如果(WifiConnectivityMonitor or.DBG){ Log.d(Tag,";Wait public DnsThread Results[";+WifiConnectivityMonitor or.this.mDnsThreadID+";]";); } }。
我们看到使用了第一部分中讨论的ChinaNetwork()方法中有问题的部分。可以看出,上述代码处理的是中国境外的“正常”情况。在WiFi连接时,它将使用常量DEFAULT_URL_STRING中的地址(如第I部分所示,硬编码为“www.google.com”)在名为DnsThread的单独线程中启动异步DNS查询。此操作在每次WiFi连接开始时执行一次。但是,如果inChinaNetwork()返回TRUE,例如在香港的电话,它只会将标志mPublicDnsCheckProcess设置为FALSE并返回,跳过Google的DNS查询。
然后我们查看Activity_Check_Start。收到ACTIVATION_CHECK_START后,NetworkStatsAnalyzer除其他操作外,会向其自身发送ACTIVITY_CHECK_POLL消息,并将标志mPollingStarted设置为TRUE。这将启动检查循环,如下所述。在执行这些操作之前,它将确保尚未通过检查mPollingStarted来启动循环,并且WiFi连接没有充当热点。
ACTIVATION_CHECK_POLL轮询循环主体的主要流控制逻辑如下(行4509-)。
案例活动_检查_轮询: 如果(WifiConnectivityMonitor or.this.SMARTCM_DBG){ Log.i(tag,";mPollingStarted:";+mPollingStarted); } 如果(!mPollingStarted){ 断掉; } IF(WifiConnectivityMonitor or.this.mCurrentBssid!=NULL)&;&; (WifiConnectivityMonitor or.this.mCurrentBssid!=WifiConnectivityMonitor or.this.mEmptyBssid){ WifiConnectivityMonitor.this.mIWCChannel.sendMessage(CMD_IWC_ACTIVITY_CHECK_POLL);//135376 Int rssi2=wifiInfo.getRssi(); 如果(rssi2<;-90){ 如果(!WifiConnectivityMonitor.this.mClientModeImpl.isConnected()){ Log.i(标签,";已断开:";+rssi2); RemoveMessages(ACTIVATION_CHECK_POLL); SendEmptyMessage(ACTIVATION_CHECK_STOP); 断掉; } 如果(rssi2<;-95){ 如果(rssi2==-127)断开; Rssi2=-95; } } /* *检查正文暂时省略 */ RemoveMessages(ACTIVATION_CHECK_POLL); SendEmptyMessageDelayed(ACTIVATION_CHECK_POLL,1000L); ..。 断掉; } Log.e(tag,";currentBssid为空。";); This.removeMessages(ACTIVATION_CHECK_POLL); This.sendEmptyMessage(ACTIVATION_CHECK_STOP); 断掉;
在继续进行各种检查之前,上面的代码确保mPollingStarted标志保持打开,并且WiFi保持连接(通过检查BSSID是否为空,或者信号是否弱并且从ClientModeImpl返回的状态指示断开)(详细信息如下)。
在每次运行检查代码之后,它以1000ms(即1秒)的延迟向自身重新发送ACTIVATION_CHECK_POLL消息,实质上形成每秒运行一次的循环。换句话说,当屏幕和WiFi都打开时,三星手机每秒都会运行这个在标准Android手机上找不到的检查循环!(此外,如果mCurrentMode>;=2,则它还运行另一个类似的循环来进行“回程检测”,这里不做介绍)。
另一方面,如果WiFi已断开,则循环将终止-ACTIVATION_CHECK_POLL不会再次发送,而是向自身发送ACTIVATION_CHECK_STOP。
案例活动_检查_停止: RemoveMessages(ACTIVATION_CHECK_POLL); RemoveMessages(TCP_BACKTH_DETACTION_START);//135226 MPollingStarted=false; MPublicDnsCheckProcess=false; ..。 MDnsQuerned=false; MDnsInterrupted=False; ..。 断掉;
上面的代码删除任何挂起的ACTIVATION_CHECK_POLL消息,并重置mPollingStarted标志和其他标志。
回到上面省略的主要检查主体,在做了一些关于数据包吞吐量和信号强度的统计之后(这里没有涉及),代码检查是否需要进行DNS查询测试(第4618行-,请注意,反编译的代码在扩展嵌套结构时有一些问题。下面的代码摘录已更正该问题)。
如果(!WifiConnectivityMonitor or.this.mIsScanning;&;!WifiConnectivityMonitor.this.mIsInRoamSession &;&;!WifiConnectivityMonitor.this.mIsInDhcpSession&;&;WifiConnectivityMonitor or.this.mIsScreenOn) { 如果(!this.mPublicDnsCheckProcess&;&;WifiConnectivityMonitor or.this.mCurrentMode!=0){ If(this.mDnsQuerailed){//如果DNS查询正在进行 …… IF(WifiConnectivityMonitor or.SMARTCM_DBG){ Log.i(tag,";正在等待DNS响应或质量结果!";); } 布尔停止QC=FALSE; 如果(WifiConnectivityMonitor or.this.mCurrentMode==3){ 如果(WifiConnectivityMonitor.this.mInAggGoodStateNow){ StopQC=TRUE; } }ELSE IF(DiffRx&>=((Long)WifiConnectivityMonitor.this.mParam.mGoodRxPacketsBase)&;&;rxBytesPerPacket>;500){ StopQC=TRUE; }ELSE IF(DiffTxBytes>;=100000){ StopQC=TRUE; } 如果(StopQC){ IF(WifiConnectivityMonitor or.SMARTCM_DBG){ Log.i(标记,";好处方!不需要一直评价质量!";); } 如果(MDnsQuerded){ MSkipRemainingDnsResults=true; MDnsQuerned=false; MDnsInterrupted=False; } } }其他{ /* *检查DNS的进一步代码,见下文 */ } } }。
前3个条件是为了避免在网络可能发生变化时的一些瞬态条件下进行DNS查询。第5项避免同时进行初始检查和定期检查。其他条件用于重新验证条件。
此外,如果mDnsQuered为true,则前一个DNS查询仍在进行中,不会考虑新的查询。
相反,如果确定连接处于良好状态(使用这里没有详细说明的几个指标),它会将标志mSkipRemainingDnsResults设置为true。从对日志消息和变量名的文字读取来看,其意图似乎是在网络状况良好的情况下只执行一次DNS查询。但是,需要注意的是,代码中没有任何内容实际读取mSkipRemainingDnsResults标志!即使连接良好且稳定,DNS检查查询也将继续在循环中发出。目前还不清楚这是一个漏洞,还是故意的,尽管有评论,比如,由于随后的设计更改。
If(WifiConnectivityMonitor or.this.mCurrentMode!=3||!WifiConnectivityMonitor.this.mInAggGoodStateNow){ IF(DiffRx>;0||DiffTx>;0){ IF(NOW-mLastDnsCheckTime>;((Long)(WifiConnectivityMonitor or.this.mCurrentMode==3?30000:60000){ IF(WifiConnectivityMonitor or.SMARTCM_DBG){ Log.d(Tag,";定期DNS检查触发器(简单连接测试)-上次DNS检查是";+ ((NOW 4-mLastDnsCheckTime)/1000)+";几秒钟前。#34;); } MNsaQcTrigger=44; NedCheckInternetIsAlive=true; } } //其他条件设置需要检查InternetIsAlive标志; 如果(NeedCheckInternetIsAlive){ 如果(现在-mLastDnsCheckTime>;=20000){ MCumulativePoorRx.clear(); MSkipRemainingDnsResults=false; MDnsQuerned=true; MNsaQcStep=1; INT TIMEOUT MS=10000; 如果(WifiConnectivityMonitor or.this.mCurrentMode==3){ 超时MS=5000; } String dnsTargetUrl2=WifiConnectivityMonitor.this.mParam.DEFAULT_URL_STRING; 如果(WifiConnectivityMonitor.this.inChinaNetwork()){ DnsTargetUrl=";www.qq.com";; }其他{ DnsTargetUrl=dnsTargetUrl2; } DnsThread mDnsThread=new DnsThread(true,dnsTargetUrl,this,(Long)timeoutMS); MDnsThread.start(); MLastDnsCheckTime=NOW; WifiConnectivityMonitor or.this.mDnsThreadID=mDnsThread.getId(); 如果(WifiConnectivityMonitor or.DBG){ Log.d(Tag,";Wait Need Check DnsThread Results[";+WifiConnectivityMonitor or.this.mDnsThreadID+";]";); } } } }。
第一部分确定在qq.com上查询DNS的“最低”频率,只要有任何WiFi活动。如果正常情况下距离上次DNS检查超过60秒,则将标志Need CheckInternetIsAlive设置为TRUE,但是如果mCurrentMode==3,这意味着“主动WiFi到蜂窝切换”模式(即,需要高WiFi质量或者切换到移动数据,因此需要更积极的检查),则将间隔缩短到30秒。(但在主动模式下,只有在连接未处于为主动模式定义的“良好”状态时才会触发)。请记住,当屏幕打开时,此循环每秒运行一次,这意味着检查将大约每隔60(或30)秒运行一次,方法是设置nedCheckInternetIsAlive标志以指示需要检查。
根据收集到的网络指标,在后续代码(省略)中有更多的标准来打开NeedCheckInternetIsAlive标志。其效果是在检测到WiFi网络的不良状态时增加检查。在某些情况下,这可能会增加DNS到qq.com的频率。但是通过稍后的检查,频率被限制为最多每20秒一次。
最后,代码将dnsTargetUrl设置为www.qq.com(假设inChinaNetwork()在我们的上下文中为真)。否则,它将查询DEFAULT_URLSTRING,即Google),并使用DnsThread类异步执行查询,就像checkPublicDns()下的“正常”情况一样。它还记录当前时间,以便将来计算经过的时间。
以下是DnsThread类的摘录。(仅显示run()下mForce==true的情况)(第6855行-)。
公共最终类DnsThread扩展了Thread{ ..。 私有最终CountDownLatch锁存器=新的CountDownLatch(1); 公共DnsThread(布尔值强制、字符串url、处理程序处理程序、长超时){ MCallBackHandler=处理程序; 如果(超时&>=1000){ MTimeout=超时; } MForce=力; Mul=url; } 公共无效运行(){ WifiConnectivityMonitor.this.mAnalyticsDisconnectReason=0; 如果(MForce){ HandlerThread dnsPingerThread=new HandlerThread(";dnsPingerThread";); DnsPingerThread.start(); 尝试{ MDnsPingerHandler=new DnsPingerHandler(dnsPingerThread.getLooper(),mCallBackHandler,getId()); MDnsPingerHandler.sendDnsPing(this.mUrl,this.mTimeout); 如果(!latch.await(mTimeout,TimeUnit.MILLISECONDS)){ 如果(WifiConnectivityMonitor or.DBG){ Log.d(Tag,";DNS_CHECK_TIMEOUT[";+getId()+";-F]-锁存超时";); } MCallBackHandler.sendMessage(WifiConnectivityMonitor.this.obtainMessage(WifiConnectivityMonitor.RESULT_DNS_CHECK,3,-1,NULL)); }其他{ MCallBackHandler.sendMessage(WifiConnectivityMonitor.this.obtainMessage(WifiConnec
实质上,当调用DnsThread的run()方法时,它会创建一个Android HandlerThread,使用其循环程序创建一个新的DnsPingerHandler,并调用DnsPingerHandler的sendDnsPing()来请求它执行DNS查询。然后,它使用具有所需超时的CountDownLatch在此线程上等待查询完成,并通过RESULT_DNS_CHECK消息返回结果。
私有类DnsPingerHandler扩展了Handler{ 处理程序mCallbackHandler; 私有DnsCheck mDnsPingerCheck; 中间长; Public DnsPingerHandler(Looper looper,Handler callbackHandler,long id){ 超级(活套); MDnsPingerCheck=新建DnsCheck(This,";WifiConnectivityMonitor.DnsPingerHandler";); MCallbackHandler=callbackHandler; MID=id; } Public void sendDnsPing(字符串url,长超时){ 如果(!mDnsPingerCheck.requestDnsQuerying(1,(Int)timeout,url)){ 如果(WifiConnectivityMonitor or.DBG){ Log.e(DnsThread.TAG,";DNS列表为空,需要检查质量";); } IF(DnsThread.this.mCallBackHandler!=NULL){ DnsThread.this.mCallBackHandler.sendMessage(obtainMessage(WifiConnectivityMonitor.RESULT_DNS_CHECK,3,-1,NULL)); DnsThread.this.latch.countDown(); } } } }。
DnsPingerHandler依次调用类DnsCheck的requestDnsQuerying()。下面列出了DnsCheck类下的requestDnsQuerying()方法。(第7150行-)。
公共类DnsCheck{ 个人隐私列表mDnsServerList=空; 个人分发名单mDnsList; 私人DnsPinger mDnsPinger; Private HashMap<;Integer,Integer>;mIdDnsMap=new HashMap<;>;(); 公共DnsCheck(处理程序处理程序,字符串标记){ MDnsPinger=新的DnsPinger(WifiConnectivityMonitor.this.mContext,标签,handler.getLooper(),Handler,1); MDnsCheckTAG=标签; MDnsPinger.setCurrentLinkProperties(WifiConnectivityMonitor.this.mLinkProperties); } Public Boolean requestDnsQuerying(int num,int timeoutMS,string url){ 列出DNS; 请求的布尔值=FALSE; MDnsList=new ArrayList(); 如果(!(WifiConnectivityMonitor.this.mLinkProperties==NULL||(dnses=WifiConnectivityMonitor.this.mLinkProperties.getDnsServers())==NULL||dnses.size()==0)){ MDnsServerList=new ArrayList(Dns); } List dnses2=mDnsServerList; 如果(dnses2!=NULL){ MDnsList.addAll(Dnses2); } 整数个Dnses。
.