前言
本文分析dns工作过程及原理,给一个简单的dns实现代码流程,并针对linux及android
下实现dns的不同,分别分析,供学习dns参考。
一、DNS功能
DNS(Domain Name System,域名系统),dns用于进行域名解析,说白了,就是给出一个
主机名,你给我找出该主机名对应的ip地址。例如:给你www.baidu.com的主机名,你给
我查出对应的ip地址:163.177.151.109。一些主机名还会有别名,如www.baidu.com就
有别名www.a.shifen.com,甚至不止一个别名,或一个别名有2个ip地址。在linux机子
上,运行nslookup(name service lookup)就是进行域名解析。如下面:
~$ nslookup www.baidu.com
Server: 127.0.0.1
Address: 127.0.0.1#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 163.177.151.109
Name: www.a.shifen.com
Address: 163.177.151.110
二、DNS的原理
上面介绍了dns的功能,那要实现dns的功能,dns需要做什么工作?主要就3点:1、记录dns
- 服务器的ip地址(请求进行域名解析的地方);2、向dns服务器请求进行域名解析(dns
实质工作),3、缓存已经解析过的主机名与ip地址对应关系(这样已缓存的就不需要再向
dns服务器请求解析了)。
一个dns的解析过程如下:
1、 应用程序请求一个主机名解析,如:应用程序跟dns说,我需要知道www.baidu.com
的ip地址,你给我查一下;
2、 Dns先在本地缓存中查询,若查到,返回ip地址给应用,流程结束。若没查到, 进行
下一步;
3、 Dns把需要查询的主机名打包进dns数据包,然后把dns数据包发送到dns服务;
4、 DNS服务器返回查询的主机名的dns数据包;
5、 Dns解析数据包,把主机名对应的ip地址返回给应用程序;
下图为网卡抓到的dns数据包的收发,请求www.baidu.com的ip地址:
下图为dns运行流程:
上面是从本机看到的情况,实际上在向DNS服务器请求时,所请求的DNS服务器也不一
定知道主机名对应的ip地址,就会向上一级DNS请求,更多资料请自行查找dns协议。
下面代码片段为freescale小系统上的dns处理过程,看起来流程还是比较清晰简单的:
wiced_result_t dns_client_hostname_lookup( const char* hostname, wiced_ip_address_t* address, uint32_t timeout_ms )
{
/* 省略部分代码*/
...
/* Create socket to send packets */
if ( wiced_udp_create_socket( &socket, WICED_ANY_PORT, WICED_STA_INTERFACE ) != WICED_SUCCESS )
{
return WICED_ERROR;
}
while ( ipv4_address_found == WICED_FALSE && remaining_time > 0)
{
/* Send DNS query messages */
for ( a = 0; a < dns_server_address_count; a++ )
{
wiced_ip_address_t host_ipv6_address;
uint16_t available_space;
/* 创建dns数据包 */
if ( wiced_packet_create_udp( &socket, (uint16_t) ( sizeof(dns_message_header_t) + sizeof(dns_question_t) + hostname_length ), &packet, (uint8_t**) &iter.header, &available_space ) != WICED_SUCCESS )
{
goto exit;
}
/* 省略部分代码*/
...
/* 发送dns数据包 */
if ( wiced_udp_send( &socket, &dns_server_address_array[a], DNS_PORT, packet ) != WICED_SUCCESS )
{
wiced_packet_delete( packet );
goto exit;
}
/* 省略部分代码*/
...
}
/* Attempt to receive response packets */
while ( ipv4_address_found == WICED_FALSE && remaining_time > 0 )
{
/* 省略部分代码*/
...
/* 接收dns数据包 */
if ( wiced_udp_receive( &socket, &packet, remaining_time ) == WICED_SUCCESS )
{
uint16_t data_length;
uint16_t available_data_length;
wiced_bool_t answer_found = WICED_FALSE;
/* Extract the data */
result = wiced_packet_get_data( packet, 0, (uint8_t**) &iter.header, &data_length, &available_data_length );
if ( ( result != WICED_SUCCESS ) || ( data_length < sizeof(dns_message_header_t) ) )
{
goto exit;
}
/* 解析dns数据包,解析出主机名的ip地址 */
/* 省略部分代码*/
...
wiced_packet_delete( packet );
}
/* 省略部分代码*/
...
}
}
exit:
wiced_udp_delete_socket( &socket );
/* 省略部分代码*/
...
if ( ipv4_address_found == WICED_TRUE )
{
*address = resolved_ipv4_address;
return WICED_SUCCESS;
}
/* 省略部分代码*/
...
return WICED_ERROR;
}
三、Linux的dns实现
网上搜索Linux dns时,都是一些linux dns怎么配置的,配置host.conf,
resolv.conf文件,配置完后,重启一下network就好了,至于为什么配置这些文件,dns
又是在哪里执行的,都没有说明。
在Linux上执行nslookup(name service lookup)或ping(ping主机名,如:ping
www.baidu.com)都会执行dns的动作。执行dns时都是调用gethostbyname函数,
gethostbyname函数,传入主机名,dns执行成功后,返回主机名对应的ip地址。
而gethostbyname函数的实现是在c库中完成的,也就是dns的整个执行过程都是由c库实
现,c库中dns实现过程与上一节介绍的流程原理是相同的,只是还多了一些配置文件用于
设置参数、dns ip地址什么的,就是前面提到的host.conf,resolv.conf文件,这就是为什
么Linux的dns只需要配置host.conf,resolv.conf文件,不需要再做其它工作的原因,因为
c库已经替你完成dns的实际工作。在Linux嵌入式系统中,若发现dns不能正常工作,那就
看一下host.conf,resolv.conf等文件是否配置正确,还有就是dns运行使用的c库在机子上
有没有。
想了解c库怎么实现dns的,请自行下载c库源码分析:http://www.gnu.org/software/libc/
四、Android的dns实现
Android也是搭建在Linux之上,而在Android下就没见到host.conf,resolv.conf这
些文件,但Android的dns也是能正常工作的。这是因为Android重写了libc库的dns实现
部分,重写的dns代码在android\bionic\libc\dns目录。重写后的dns,请求dns的接口,
有gethostbyname(兼容以前的c库接口)及android_gethostbynamefornet(给
android使用)接口,但根据不同的dns请求发起者,走的流程有点不一样,其中还涉及到
了netd的处理。Android java层请求dns时,是通过netd进行,netd调用
android_gethostbynamefornet接口。而Android下编译的ping程序是不通过netd而直
接调用gethostbyname接口,但最终还是要走到netd,由netd调用
android_gethostbynamefornet接口。Android下dns的请求流程如下图:
gethostbyname_internal中判断是否为netd调用下来的依据是,
- 1
- 2
getenv(“ANDROID_DNS_MODE”)返回是否不为NULL并且值为“local”,而netd启动
时会设置setenv(“ANDROID_DNS_MODE”, “local”, 1),而ping程序不会。
五、Linux的dns缓存
Linux的dns,在配置resolv.conf文件的时候,第一个dns ip地址配置为nameserver
127.0.0.1,也就是从本地dns缓存中查找,查找不到时再根据第二个、第三个dns ip地址
进行dns请求。
六、第三方dns库
有时候我们不想使用编译工具中libc自带的dns库,也可以移植第三
方的dns库,如c-ares dns库,该dns库支持多种平台。例如:linux
平台上,它与linux的配置完全兼容,可以在现有的linux dns配置文
件上,直接编译c-ares库放到机子上即可使用。