Android 4.4 设置DNS及DNS解析请求流程分析
1,将网络连接获取的DNS设置到系统中
1.1DNS获取后先设置LinkProperty
在Android Frameworks中不同的网络interface(eth0、wlan0等)都会有一个独立的LinkProperties对象来保持自己的网络信息
1.2通知ConnectivityService DNS信息已获取成功
不管是dhcp、pppoe或者是静态设置,在成功获取dns服务器地址后都会发送该消息,而ConnectivityService会处理该消息
1.3EVENT_STATE_CHANGED消息的处理
handleConnect函数接下来的执行过程中会进行所有的网络信息设置,包括ip,dns,route等,这里我们只看DNS
先获取对应interface的Linkproperty对象,然后从LinkProperty中取出所有的DNS,再进行DNS更新设置
1.4设置DNS
上面的mNetd实际上就是Android framework中netd的代理,NetworkManangermentService
这里会通进入netd进程设置DNS了,备注这里相当与用命令进行DNS设置,例如:ndc resolver setifdns eth0 dns1 dns2 ...
netd接收到resolver命令后,就会进入对应的command runCommand函数
到这里才算真正进入DNS模块,android DNS模块在libc中
看到这里,重点resolv_cache_info结构体,我们的DNS就是保存在这个结构提中了,此时DNS就算设置完成了
这里还有一个重点就是最多保存的DNS个数为MAXNS,这个值默认是为3的,代表我们dns请求最多尝试3个dns服务器
2,DNS解析请求
应用需要进行DNS解析,主要会用到两个接口,这里我们直接从c层开始,java层的层层封装就略过了,c层DNS解析两个接口:
gethostbyname和getaddrinfo,这里我们已getaddrinfo为例了(分析流程中会列出主要过程)
进入android_getaddrinfoforiface函数后,会出现以下这个判断
由于getaddrinfo接口一般是由应用进程调用,而应用进程是不会设置ANDROID_DNS_MODE这个环境变量的
const char* cache_mode = getenv("ANDROID_DNS_MODE");
所以必然会进入if中,接下来我们看android_getaddrinfo_proxy函数带我们去了哪里?
android_getaddrinfo_proxy函数内,会向/dev/socket/dnsproxyd这个socket写入“getaddrinfo xxxxxxxx”信息,那么这个/dev/socket/dnsproxyd是谁创建的呢?大家可以去看看init.rc,这个是在启动netd进程时,会创建该socket,既然由netd创建,那么netd应该也会负责监听,netd会收到socket dnsproxyd传来的getaddrinfo数据,启动一个GetAddrInfoHandler线程run起来(是不是感觉绕了一圈,直接getaddrinfo调用下去不就好了,为什么非要又绕到netd里面来,大家可以研究一下)
GetAddrInfoHandler线程跑起来后,又调用android_getaddrinfoforiface这个前面已经出现过一次的函数,我们又要重新回到libc中的dns模块了,但这次回去肯定流程会有所不同的
这次依然会再次碰到这个if判断条件,但此时我们已切换到了netd进程空间,netd进程在启动时就会设置环境变量ANDROID_DNS_MODE为local,所以这次我们不会再进入if中了,该往下跑了
经过一系列的协议类型,socket类型等判断后,就会进入explore_fqdn函数中了
explore_fqdn函数主要作用就是依次去调用_files_getaddrinfo,_dns_getaddrinfo和_yp_getaddrinfo函数
_files_getaddrinfo函数这里不细分析了,其实就是去读取/etc/hosts文件中提前配置好的一些域名与ip对,相当于优先在dns文件缓存中去寻找解析结果,这里我们主要分析_dns_getaddrinfo函数
_dns_getaddrinfo函数中会去配置dns请求发送4A还是A,还是都发送,一般如果是调用了java层的dns解析接口的话,默认4A和A请求都会发送
接下来进入res_searchN函数,getanswer函数是取解析结果的
res_searchN函数中会有两个比较重要的点:
_resolv_populate_res_for_iface函数,这个函数的作用就是获取我们将要请求的dns地址
再次见到resolv_cache_info结构体,这个就是我们前面讲的设置DNS时,保存DNS地址的地方,现在我们就要从里面获取我们要发送请求的DNS地址了(注意设置时是按顺序存的,取得时候也是按顺序取得,所以DNS请求目标地址也是按设置顺序来的),这里也出现了MAXNS这个宏,再次限制了最多向MAXNS个dns服务器发送请求,这里我们还要记住一个变量statp->nscount,这个变量里面就保存了我们取出来的DNS服务器地址个数,这个后面会用到
res_querydomainN函数,这个函数接下来会用获取到的DNS服务器地址来发出DNS请求,这个函数比较简单,就是将参数继续传给res_queryN函数
res_queryN函数中的for循环的就是请求类型,4A和A是不是都发送,再进入res_nsend函数就是真正发送请求的地方了
已经入res_nsend函数,就是首先判断DNS缓存中是否有我们需要的解析的地址,没有缓存就发送dns解析请求
发送DNS请求的循环控制,内层循环用到的次数控制就是DNS服务器个数,而外层循环就是retry次数,这个retry次数默认也是个宏定义的定值2,所以我们想改retry次数,可以修改这个宏定义,当然如果使用了resolve.conf配置文件,就可以通过配置option参数来控制DNS请求的一下行为,这个大家可以自行研究一下resolve.conf文件在DNS模块中的使用,解析等过程
接下来进入send_dg函数,send_dg函数就会调用socket接口函数connect,sendto等发送DNS请求报文了,这里我们关注一点
每次请求超时时间的控制
这里就是获取和计算本次DNS请求超时时间的地方,这个超时时间时根据statp->retrans变量计算出来的,而statp->retrans变量在DNS模块初始化的时候会设一个默认值,这个值也是宏定义
statp->retrans = RES_TIMEOUT;
#define RES_TIMEOUT 5 /* min. seconds between retries */
如果想修改超时时间,我想你找到地方了吧,当然和retry次数一样,你也可用通过配置resolve.conf文件中的option来设置,Android是默认不适用resolve.conf配置文件的,你也可以自行打开宏开关
到这里DNS设置和请求的主要函数调用过程都分析了一遍,欢迎大家指正!