在使用consul做docker容器服务化的过程中,使用到了dnsmasq做DNS请求转发,于是研究了下DNS协议的一些内容,如有错漏希望大家指正。
1 DNS基础
DNS是一种基于TCP/IP应用的分布式数据库,提供了主机名字和IP地址的转换以及有关电子邮件的选路信息等。在应用看来,对DNS的访问时通过一个地址解析器(resolver)来完成的,在Unix/Linux中,这个解析器主要通过两个函数gethostbyname
和gethostbyaddr
来访问的。
DNS的名字空间和Unix文件系统很相似,具有层次结构,如下图所示。每个节点有个最多63个字符的标识。其中顶层域有arpa(地用作地址到名字转换的特殊域),com,edu,gov,int,mil,net,org(com到org这7个称为普通域或者组织域),cn, ae,us...(这些为国家代码,称为国家域)。许多国家将二级域组织成类似普通域的结构,比如.edu.cn则是我国的教育机构的二级域名,比如大学的官网一般都是这种域名,如 www.hust.edu.cn
是华中科大的域名。
没有机构管理域名层次结构中的每个标识,而只有一个机构NIC负责分配顶级域和委派其他指定区域的授权机构。一个独立管理DNS子树的就称为一个区域,一个常见的区域是二级域,比如hust.edu,二级域通常将它们的区域划分为更小的区域,比如大学一般会根据院系划分区域,比如 sse.hust.edu.cn是华中科大的软件学院域名,而cs.hust.edu.cn是华中科大的计算机学院域名。
一旦一个区域的授权机构被委派后,它就要负责给该区域提供多个名字服务器(Linux中的/etc/resolv.conf中的nameserver就是这个)并负责域名的管理。当名字服务器中没有域名信息时,则需要与其他名字服务器联系,当然不是所有的名字服务器都知道如何同其他名字服务器联系,但是他们都知道同根的名字服务器联系。主名字服务器必须知道根服务器的IP地址(注意,不是域名,必须是IP地址),这个可以预先配置好。根服务器则知道所有二级域中的每个授权名字服务器的名字和IP地址,这是一个反复的过程,也就是根服务器告诉了处理请求的名字服务器与另一个名字服务器联系从而最终得到对应信息。
DNS的一个基本特性是使用高速缓存,当一个名字服务器收到映射信息后,会将信息存储在高速缓存中,加速后面相同请求的查找效率。
2 DNS请求流程(递归查询和迭代查询)
先说明一下Linux系统的DNS解析的流程,其他系统除了一些细节不同,大体类似:
-
我们的系统里面会有一个
/etc/resolv.conf
文件,这里面的nameserver那些行就是指定的DNS服务器的地址。比如我的电脑里面的就是10.0.1.13
,而这里的options参数是用来设置nameserver请求超时时间以及尝试请求次数的。# /etc/resolv.conf.head can replace this line nameserver 10.0.1.13 options timeout:2 options attempts:3
当我们请求域名解析的时候,会先向resolv.conf中配置的一个nameserver发送DNS请求,请求顺序基于/etc/resolv.conf里面指定的顺序,如果得到了解析结果,则返回。如果第一个nameserver没有解析结果,则马上向第二个nameserver发送解析请求。当然,如果第一个nameserver不是解析不了域名,而是自身的DNS服务已经挂掉或者不是DNS服务器,则会等待timeout(默认为5秒)后再向第二个nameserver发送请求,一共尝试attempts(默认为2)次。(当然如果你的电脑里面的/etc/hosts文件里面有指定域名和ip记录,而且
/etc/nsswitch.conf
里面设置了hosts:files dns
的话,则会先从/etc/hosts
里面解析)。要注意的是,resolv.conf中的options设置只对使用了glibc的resolver的才会有效,比如对getent,ping等工具是有效的,而像dig,nslookup是不会用到resolver的,所以它们用的是自身设置的超时和尝试次数。另外,不同的工具对resolv.conf中的attempts选项的解析也不一样,在debian8.8中使用getent发现在所有的nameserver的DNS请求失败情况下尝试次数为attempts*4
,而使用ping尝试次数却是attempts*2
次,也就是说如果你设置attempts为2,最终getent会对每个nameserver分别请求8次,ping则是对每个nameserver分别请求4次。通常的我们的resolv.conf中配置的是本地DNS服务器(非权威DNS服务器)地址,客户端到本地DNS服务器通常是用的递归查询。也就是说,在终端输入
dig www.163.com
的时候,会先向本地DNS服务器 10.0.1.3发送DNS请求,然后等待10.0.1.3返回解析结果。而本地DNS服务器10.0.1.3会先检查自己的缓存,如果缓存中有域名解析记录,则直接返回应答。如果没有记录,如果本地DNS服务器只是一个作为一个DNS代理,比如dnsmasq这种,则它需要向它的上游DNS服务器发起递归查询。而如果本地DNS服务器不是作为代理使用,则它需要发起迭代查询,即先从配置获取根服务器列表(根服务器有13个IP),然后本地DNS服务器作为客户端选择一个根服务器发送查询请求,根域名服务器不支持递归查询,它只是返回顶级域名服务器列表(com的顶级DNS服务器列表,如a.gtld-servers.net
等);然后本地DNS服务器选择一个顶级域名服务器列表发送查询请求,顶级域名服务器也不支持递归查询,它只是返回授权服务器列表(ourglb0.com
的授权服务器列表,如dns1.ourglb0.org
);最后本地DNS服务器选择一个授权服务器发送查询请求,获取域名的A记录。如果域名本身做了CNAME的,则还需要对CNAME后的域名做额外查询。整个过程可以通过dig +trace www.163.com
查看。递归查询和迭代查询的一个示例如下图所示,其中cis.poly.edu到dns.poly.edu的为递归查询,而dns.poly.edu向其他DNS服务器发出的查询则为迭代查询。
- 根DNS服务器现在有13个域名编号,从a.root-servers.net到m.root-servers.net,当然这不是说根DNS服务器就只有13台,根据维基百科的数据,到2014年10月,13个编号根服务器一共是504台,分布在全球各地,大部分通过任播(Anycast)技术,编号相同的根服务器使用同一个IP,504台根服务器总共只使用13个IP。
3 DNS报文分析
DNS查询和响应的报文格式如下,它由12字节长的首部和4个可变长度字段组成。格式如下:
头部 | |
---|---|
标识 | 标志 |
问题数 | 资源记录数 |
授权资源记录数 | 额外资源记录数 |
内容字段 |
---|
查询问题 |
回答(资源记录数可变) |
授权(资源记录数可变) |
额外信息(资源记录数可变) |
先看一个例子,后面再针对例子说明各个字段的含义:
先打开wireshark,监听dns协议,然后命令行运行 dig www.163.com
,此时返回的信息如下:
ssj@ssj-mbp ~/Prog $ dig www.163.com
; <<>> DiG 9.9.7-P3 <<>> www.163.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60537
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 5, ADDITIONAL: 3
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.163.com. IN A
;; ANSWER SECTION:
www.163.com. 535 IN CNAME www.163.com.lxdns.com.
www.163.com.lxdns.com. 536 IN CNAME 163.xdwscache.ourglb0.com.
163.xdwscache.ourglb0.com. 38 IN A 122.13.74.252
163.xdwscache.ourglb0.com. 38 IN A 157.255.20.6
163.xdwscache.ourglb0.com. 38 IN A 112.90.246.87
;; AUTHORITY SECTION:
ourglb0.com. 2143 IN NS dns4.ourglb0.info.
ourglb0.com. 2143 IN NS dns3.ourglb0.org.
ourglb0.com. 2143 IN NS dns1.ourglb0.org.
ourglb0.com. 2143 IN NS dns2.ourglb0.info.
ourglb0.com. 2143 IN NS dns5.ourglb0.org.
;; ADDITIONAL SECTION:
dns1.ourglb0.org. 2493 IN A 150.138.173.201
dns5.ourglb0.org. 10 IN A 221.202.204.225
;; Query time: 78 msec
;; SERVER: 10.0.1.13#53(10.0.1.13)
;; WHEN: Sat Aug 05 16:52:20 CST 2017
;; MSG SIZE rcvd: 306
可以看到DNS请求用的UDP协议封装的,忽略前面的以太网,IP,UDP头部,我们只关注DNS协议的具体数据包内容。DNS协议首部分为标识和标志,问题数,资源记录数,授权资源记录数,额外资源记录数。下面一一分析:
3.1 DNS协议头部
标识:
标识由客户端程序设定并由服务器返回结果,客户端程序通过它来确定响应与查询是否匹配,示例中标识字段为0xb182
。
标志:
而16bit的标志字段则分为若干子字段如下:
1 | 4 | 1 | 1 | 1 | 1 | 3 | 4 |
---|---|---|---|---|---|---|---|
QR | opcode | AA | TC | RD | RA | Z AD CD | rcode |
- QR:1bit,0表示查询报文,1表示响应报文。
- opcode:4bit,通常为0(标准查询),其他值有1(反向查询),2(服务器状态查询)。示例中请求报文这个值为0。
- AA:1bit,表示授权回答。在请求报文中不用设置该字段,响应报文中该字段如果为1,表示该名字服务器是授权于这个域的,如果为0,则不是。示例中为0,表示10.0.1.13不是这个域名的授权服务器。
- TC:1bit,表示可以截断的。使用UDP时,它表示当应答长度超过512字节时,只返回前512字节。示例中为0,表示没有截断。
- RD:1bit,表示“期望递归”。这个bit可以在查询中设置,并在响应中返回。该标志告知名字服务器必须处理这个查询,也称之为递归查询。如果该位为0,且被请求名字服务器没有一个授权回答,则返回一个能解答该查询的其他名字服务器列表,这就是迭代查询。示例中为1,表示期望递归查询。
- RA:1bit,表示“可用递归”,响应中使用。如果名字服务器支持递归查询,则在响应中将该位设置为1,示例中就是设置的1,表示该名字服务器支持递归查询。除了某些根服务器之外,大部分名字服务器都提供递归查询。
- Z, AD, CD:3bit,Z位和CD位通常为0。AD位在响应中设置(示例中的请求中AD位为1原是dig设置的,通常情况为0),在响应中只有所有返回的数据都是认证过的或者符合名字服务器安全策略的才能置位AD位,否则必须是0。CD(check disabled)为0,表示resolver会对名字服务器返回的数据进行检查,名字服务器只能返回认证过的数据。
- rcode:4bit,响应中才有的字段,表示返回码。通常为0(没有出错)和3(名字错误)。名字错误只能从一个授权服务器返回,它表示在查询中指定的域名不存在。示例中返回0,表示没有出错。
问题数: 请求包为1,响应包为1.
资源记录数: 请求包为0,响应包为5.
授权资源记录数: 请求包为0,响应包为5.
额外资源记录数: 请求包为0,响应包为1.
3.2 DNS协议内容字段
3.2.1 查询内容字段
查询问题
查询问题的每个问题格式如下:查询名,查询类型和查询类。
查询名是 www.163.com 存储格式是 3(计数) w w w 3(计数) 1 6 3 3(计数) c o m
查询类型:在这里是00 01,一个字节,对应类型A。常用的查询类型对应的数值和描述如下:
查询类型 | 数值 | 描述 |
---|---|---|
A | 1 | IP地址 |
NS | 2 | 名字服务器 |
CNAME | 5 | 规范名称 |
PTR | 12 | 指针记录 |
HINPO | 13 | 主机信息 |
MX | 15 | 邮件交换记录 |
AXFR | 252 | 对区域转换的请求 |
*/ANY | 255 | 对所有记录的请求 |
查询类:这里是00 01,即对应IN。指互联网地址,通常这个值为1.
额外信息(Additional Data)
额外信息这里有EDNS(扩展DNS)的内容,EDNS允许请求方通知名字服务器返回大的数据包。这种新的资源记录叫OPT(即伪资源记录),OPT不能被缓存和转发,只存储在DNS请求和响应的额外信息段中。注意,用dig的时候在请求中通常会有额外信息这一项,一般的DNS请求没有该项,如果不想在请求中加EDNS,则可以使用dig +noedns xxx.com
。EDNS的记录格式如下:
- Name:名字,1字节,通常为0,也就是Root。
- Type:类型,2字节,为0x0029,即41,表示为OPT。
- UDP payload size:请求方的UDP payload大小,2字节,这里为0x1000,即4096字节。
- RCODE: 返回状态码,1字节。加上之前的头部的4位返回码,一共可以有12位返回码,表示更多的返回类型。
- VERSION: EDNS版本,1字节。通常为0.
- Z:保留字段,2字节,通常为0.
- RDATA LEN: 可变消息长度,这里为0.
- RDATA: 可变消息的数据,这里为空。
3.2.2 响应内容字段
查询问题
由响应包可知,查询问题部分与查询请求基本一致。
答案
接下来是答案部分,这里一共有5条记录。答案的格式如下:
域名,类型,类,TTL,资源数据长度,资源数据。
;; ANSWER SECTION:
www.163.com. 535 IN CNAME www.163.com.lxdns.com.
www.163.com.lxdns.com. 536 IN CNAME 163.xdwscache.ourglb0.com.
163.xdwscache.ourglb0.com. 38 IN A 122.13.74.252
163.xdwscache.ourglb0.com. 38 IN A 157.255.20.6
163.xdwscache.ourglb0.com. 38 IN A 112.90.246.87
第1条记录为CNAME类型记录。首先是16个字节0xc00c表示域名,这里用的压缩的方式,转换成二进制是0x11000000 00001100即16个字节的前两位为11,这表示它是一个16bit的指针而不是8bit的计数字节,后面的14位说明域名针对标识的偏移位置为12,即为查询问题字段的域名开始位置。接下来的1个字节为记录类型0x05,也就是CNAME类型,然后2个字节是0x0001,即类为IN。接着4字节0x00000111为生存时间TTL为273,接下来2个字节0x0014是资源数据长度为20,后面的20个字节为规范名字www.163.com.lxdns.com,注意最后的com是域名结尾部分,也是用的压缩的方式,即用的前2位标识,后面14位指定偏移位置为20.
第2条记录也是CNAME的。www.163.com.lxdns.com CNAME到163.xdwscache.ourglb0.com。
第3条记录为A记录,163.xdwscache.ourglb0.com的一个IP地址为122.90.246.87,TTL为38,数据长度为4字节,数据内容为IP地址。后面的2条记录也全都为163.xdwscache.ourglb0.com域名的A记录。
也就是说 www.163.com的别名是 www.163.com.lxdns.com,而www.163.com.lxdns.com的别名又是 163.xdwscache.ourglb0.com。最终解析163.xdwscache.ourglb0.com域名有5个对应的IP地址。
授权
接着是五条授权服务器记录,如下:
;; AUTHORITY SECTION:
ourglb0.com. 2143 IN NS dns4.ourglb0.info.
ourglb0.com. 2143 IN NS dns3.ourglb0.org.
ourglb0.com. 2143 IN NS dns1.ourglb0.org.
ourglb0.com. 2143 IN NS dns2.ourglb0.info.
ourglb0.com. 2143 IN NS dns5.ourglb0.org.
分别对应5条授权服务器记录,第1条如下,其他4条类似:
- 域名: ourglb0.com
- TTL: 2143
- 类: IN,即internet地址
- 类型: NS,即名字服务器。
- 名字服务器:dns2.ourglb0.info
授权服务器知道后,如果我们直接向授权服务器的IP发送dns查询请求,可以看到响应中的AA字段会设置为1,表示该名字服务器是授权于该域的。运行 dig @150.138.173.201 163.xdwscache.ourglb0.com
可以看到AA设置为了1.
额外信息
额外信息记录有2条,如下所示,A记录的格式,记录的是授权服务器的ip地址。
;; ADDITIONAL SECTION:
dns1.ourglb0.org. 2493 IN A 150.138.173.201
dns5.ourglb0.org. 10 IN A 221.202.204.225
4 指针查询
前面说到的都是正向查询,即根据域名查询IP地址,还有一种查询是指针查询,与域名查询IP相反,由IP地址查询对应的域名。注意到顶级域arpa就是用来做这个的,如果没有arpa顶级域,那么要查询IP地址对应的域名是很麻烦的。使用 dig -x xxx.xxx.xxx.xxx
可以发送指针查询来查询一个IP对应的域名。
5 使用dnsmasq搭建DNS代理
在项目中使用到consul做服务发现,而consul本身搭载了一个DNS服务器,我们需要将consul的服务域名如 redis.service.consul
的解析指向consul的DNS,而将其他域名解析使用系统本身的DNS。
使用apt-get install dnsmasq
安装好dnsmasq后,配置文件/etc/dnsmasq.conf
如下设置,另外添加/etc/dnsmasq.d/consul.conf
文件,内容为server=/consul/172.17.42.1#8600
(其中 172.17.42.1:8600 是consul的DNS服务器监听地址),而/etc/resolv.dnsmasq.conf
文件内容跟/etc/resolv.conf
一致,里面存储上游DNS服务器列表地址。
###/etc/dnsmasq.conf内容###
resolv-file=/etc/resolv.dnsmasq.conf
interface=eth0
cache-size=10000 #设置缓存大小
conf-dir=/etc/dnsmasq.d/,*.conf
max-cache-ttl=600 #设置最大缓存时间
这样的效果就是,针对consul的域名全部转发到consul的DNS服务器处理,而其他的域名则由resolv.dnsmasq.conf
文件中的DNS服务器进行解析。需要注意的一点是,对于转发到consul的域名解析请求,dnsmasq不会做缓存,而通过resolv-file中的DNS服务器解析的请求,则会缓存起来,下次请求只要缓存没有过期就会从缓存中返回。
6 参考资料
- 《TCP/IP协议详解》第14章-DNS:域名系统
- 《计算机网络-自顶向下的分析方法》
- https://zh.wikipedia.org/wiki/%E6%A0%B9%E7%B6%B2%E5%9F%9F%E5%90%8D%E7%A8%B1%E4%BC%BA%E6%9C%8D%E5%99%A8