顿狈厂协议与应用介绍1. DNS - DNS 协议及应用介绍
Jeff.Rao
chenlin.rao@renren-inc.com
人人网
May 25, 2012
2. 什么是 DNS
? 网络的原始时代
? We are the cowboys over the WWW(wild wild west)~ BUT
? 采用 IP 地址访问——真滴记不住啊有木有 ??!!
? 在 NIC 的 hosts.txt 里记录主机名与 IP 地址的映射关系
? NIC 互联网信息中心,不是网络适配器 ~~
? 几天 NIC 更新一次,大家去下载—— 12306 抢票的感觉有木有 !!
? "We will encourage you to develop the three great virtues of a
programmer: laziness, impatience, and hubris." -- LarryWall
? 大家都想要自己独立的够酷的名字空间
? DNS(Domain Name System) 诞生了
4. DNS 组成
? 域名空间和资源记录
? 域名空间是一个树状结构
? 资源记录是和名字相关的数据
? 名字服务器
? 服务器端程序,用来保留域名空间和资源记录
? 一般只保存域名空间的一个子集,作为这个子集的权威。
? 一个子集内的信息又算一个区 (zone)
? 其他信息通过其他名字服务器查询
? 解析器
? 客户端程序,可以访问至少一个名字服务器,接收返回的
结果或者转向其他名字服务器查询
5. 域名空间
? 树状结构的每个结点都对应一个资源集 ( 可能为空 )
? 每个结点的标记为 0-63 字节
? 0 字节标记为根记录 (.)
? 标记对大小写不敏感
? 结点的域名由从结点到根的标记连接组成
? 每个域名的结点最多不超过 127 个
? 一般实现中域名长度不超过 255 字节
6. DNS 查询过程
? 递归查询
? 要点:“请对方辩友正面回答!”
? 客户端请求必须得到一个 yes or no 的响应——
NXDOMAIN 或 NOERROR
? 一般情况下,电脑客户端都会使用递归查询
? 迭代查询
? 要点:“今人不见古时月,今月曾经照古人”
? 服务器端 ( 今人 ) 返回一个可能知道该域名 ( 古
月 ) 解析结果的名字服务器 ( 古人 ) ,然后客户端
重新去那台查询
7. DNS 查询过程
? 举例:
? 电脑发出“可递归” (RFC1034 中定义的 "recursive") 查询的请求到自己配置的 LDNS
服务器上
? LDNS 检查自己的权威认证区——一般是没有的
? LDNS 检查自己的非权威缓存信息
? 有,返回结果,完毕
? 没有,向其他 NS 查询
? 配置了 forwarder 的话,只向 forwarder 的 NS 发请求,这个请求依然是“可递归”
? 否则向根 DNS 发出“迭代” (RFC1034 中的大多数情况下仅仅把它叫 "non-
recursive" ,偶尔叫 iterative) 请求
? 全世界只有 13 个根 DNS ,所以根是不会响应递归请求的
? 根 DNS 返回顶级域 (.com..net..org. 等等 )DNS 的 IP 给 LDNS
? LDNS 向顶级域权威 DNS 发出“迭代”请求
? 顶级域权威 DNS 返回对应一级域的权威 DNS 的 IP
? LDNS 向一级域权威 DNS 发出“迭代”请求
? 一级域权威 DNS 查找到对应主机名的资源记录,返回结果
? LDNS 将结果返回给电脑,结束本次“递归”查询
9. DNS 查询结构体
? 前面讲的是 RFC1034 《 DOMAIN NAMES -
DOMAIN CONCEPTS AND FACILITIES 》里关
于 DNS 的概念阐述。
? 然后说一下 RFC1035 《 DOMAIN NAMES -
IMPLEMENTATION AND SPECIFICATION 》里
对于 DNS 的实现阐述。
? ftp://ftp.rfc-editor.org/in-notes/rfc1035.txt
10. UDP 报文的结构
? 一、报头
? 1 1 1 1 1 1
? 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | ID |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | QDCOUNT |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | ANCOUNT |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | NSCOUNT |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | ARCOUNT |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? 这张图大家都用,汗 ~ 顶上一个数字表示这列是 1bit ,后面 10-15bit 分成两行写了,
……
11. UDP 报文的结构
? ID :占 2bytes ,客户端生成,服务器端返回时靠这个对应客户端的请求
? QR(Question Response) :占 1bit , 0 代表查询, 1 代表响应
? Opcode :占 4bits , 0 代表标准查询, 1 代表反向查询, 2 代表服务器状态
查询, 3-15 没啥用,过去还有个完全查询已经废弃了
? AA(Authoritative Answer) :占 1bit , 1 代表权威响应, 0 代表非权威响应
? TC(TrunCation) :占 1bit ,是否截断报文—— UDP 一个报文只有 512bytes
? RD(Recursion Desired) :占 1bit ,客户端生成,服务器端沿袭, 1 代表启用
递归查询
? RA(Recursion Available) :占 1bit ,服务器端生成, 1 代表服务器支持递归
查询
? 这里我们看到实际没有对于迭代的设置,所以 RFC 中更多的把迭代叫成非递
归
? Z :占 3bits ,保留字段,设 0
? RCODE :占 4bits ,服务器端生成的响应返回码。 0: 正常, 1: 格式错
误, 2:DNS 错误, 3: 域名不存在, 4: 查询类型不支持, 5: 拒绝查询
? QDCOUNT :占 2bytes ,查询记录的个数
? ANCOUNT :占 2bytes ,回复记录的个数
? NSCOUNT :占 2bytes ,权威记录的个数
? ARCOUNT :占 2bytes ,额外记录的个数
12. UDP 报文的结构
? 二、查询
? 1 1 1 1 1 1
? 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | |
? / QNAME /
? / /
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | QTYPE |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | QCLASS |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
13. UDP 报文的结构
? QNAME :一般就是查询的域名了。被编码成 labels 序列
,每个 label 包括: 1byte 表示后续字符串长度 + 字符串。
最后一个 label 以 0 长度 + 空字符串表示域名结束
? QNAME 可能是奇数个 btyes ,不需要填充完整
? 使用 label 的原因是为了压缩报文,因为可能
一个域名查询多个 TYPE ,所以后面的重复 label 可以用
指针代替
? 在目前的 DNS 实现中,对一个报文发送多个
查询的情况,要求 QNAME 必须一致,否则递归查询碰上
两个不同 QNAME 权威 NS 不一致的情况就完蛋鸟 ~
? QTYPE :查询类型
? QCLASS :查询协议
14. UDP 报文的结构
? 三、响应
? 1 1 1 1 1 1
? 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | |
? / /
? / NAME /
? | |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | TYPE |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | CLASS |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | TTL |
? | |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
? | RDLENGTH |
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
? / RDATA /
? / /
? +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
15. UDP 报文的结构
? RDLENGTH :占 2bytes ,表示 RDATA 的长度
? RDATA :响应记录,长度与格式不定。比如最常见的话
,如果 TYPE==A , CLASS==IN ,那么 RDATA 就是占
位 4bytes 的 ARPA 网络地址
? http://www.iana.org/assignments/dns-parameters
? 四、权威
? 五、附加
? 这两个格式和响应一样,不赘述
? 对于附加 (Additional) 资源记录,在 EDNS 和 DNSSEC
中会用来存放 OPT 记录数据,分别用来支持超过 512 字
节的 UDF 包和确认是否支持 DNSSEC 。
16. 资源记录 (Resource Record)
? 常见 RR(TYPE) :
? SOA(Start Of Authority)
? 放在 zone 的最开始,有且仅有一个。用来描述负责这个 zone 的名字服务器,版本信
息等。举例 BIND 配置说明 :
? @ IN SOA renren.com. root.renren.com. (
? 2012051401 ; Serial
? 3600 ; Refresh
? 300 ; Retry
? 3600000 ; Expire
? 3600 ) ; Minimum
? @ 代表整个 zone
? renren.com. 代表本机主机名
? root.renren.com. 代表本机管理员邮箱 root@renren.com
? serial 代表 zone 版本,每次变动都应该自增,方便从服务器校验同步
? refresh 代表从服务器同步间隔——现在一般采用主服务器 notify 的方式
? expire 废弃时间——超过这个时间从依然连不上主,就自杀……
? minimum 代表 zone 内记录的默认 TTL ,非权威 DNS 做缓存时不能超过这个时间
17. 资源记录 (Resource Record)
? 常见记录 RR(TYPE) :
? A(Address)
? 主机名对应的 IPv4 地址
? AAAA 代表的就是 IPv6 地址,因为 IPv6 是 128 位, 4 倍于 IPv4 的
32 位,所以写 4 个 A
? CNAME(Canonical NAME)
? 别名
? DNAME ,在 RFC2672 中定义, bind9 里已经实现,类似 CNAME 。但 CNAME 之后
对应的是具体主机,而 DNAME 用处是别名掉整个域
? TXT
? 返回文本信息,可能用来描述这个记录
? 但其实有 HINFO 专门做主机信息描述资源记录
? MX(Mail Exchanger)
? 邮件服务器主机,比 A 记录多一个优先级定义,数值越低越优先
18. 资源记录 (Resource Record)
? MX(Mail Exchanger)
? 邮件服务器主机,比 A 记录多一个优先级定义,数值越低越优先
? NS(Name Server)
? 本 zone 的权威名字服务器
? PTR(Pointer Record)
? 主要用来实现反向解析
? 小提示:互联网上, 60% 的 DNS 查询是反向解析,正向只有 40%
? 邮件传输等协议都依赖反解, nslookup 默认也有反解,可见附 1 脚本
测试
? TSIG(Transaction Signature)
? 签名加密动态更新记录
19. 资源记录 (Resource Record)
? 协议类 RR(class):
? IN(internet)
? CS(csnet)
? HS(hesiod)
? CH(chaos)
? 虽然都没见过,不过还是要修正前面 A 记
录的说明, 32 位 IP 地址只是 IN 情况下
的;如果是 CH ,应该是 16 位八进制
Chaos 地址域名
20. 资源记录 (Resource Record)
? 查询类 RR(QTYPE):
? AXFR(Authoritative Zone Transfer)
? 请求传输整个 zone 的资源
? IXFR(Increamental Zone Transfer)
? AXFR 的改进版,根据 serial 数值传输变更了的资源
? MAILA
? 废弃了,现在都是用 MX
? *
? 请求所有记录
? http://en.wikipedia.org/wiki/PTR_record
21. DNS 应用
? DNS 负载均衡
? 因为 A 记录可以返回多个,所以在 LVS 没有诞生的中古时代,人们
是使用 DNS 做负载均衡的
? 问题:没有健康检查,没有权重设置,没有哈希分布,没有……
? 动态 DNS
? 上面这些其实都不是问题。通过自定义函数返回不同的 IP 即
可。 DNS 做负载均衡真正的问题是:
? TTL 时间内没办法改变客户端访问路由!
? 所以最后动态 DNS 的应用场合就变成了 CDN 系统全局调度器。
22. CDN 系统内的 Dynamtic DNS
? 要点:根据 ip 地址段对应的 view ,返回不同的 IP 地址
? 常见应用: Bind9 、 TinyDNS 、 WINMyDNS
? 运维难点:收集足够准确的 IP 段信息
? F5 的 GTM 首选算法是 RTT ,但是我们注
意到: DNS 首选协议是 UDP 的,只有在 package 大于
512 字节和 zone transfer 的时候采用 TCP 。 UDP 木有
RTT 的概念 ~
? 所以我们就知道了 GTM 是被动 RTT 的了。
? GTM 工作原理: NS 返回一台 GTM 的 ip 给
LDNS , LDNS 请求到该 ip ; GTM 在就近路由表中查
询,没有的话,联系全网其他 GTM 共同对 LDNS 发起
TCP 请求计算 RTT ,汇总 RTT 结果更新路由表返回最近
结果。
23. CDN 系统内的 Dynamtic DNS
? 小设想:一般情况下,我们都说 DDNS 上收到的 localDNS 的 IP ,真的么?
? 上面的协议实现部分,我们其实并没有看到 DNS 报文里有哪存放 ip 的,也
就是说: ip 还是 TCP/IP 层的事情。
? 实际根据的是迭代查询的原理!
? 那么我们如果在上海搭建一个 'recursion yes;' 的 bind ,然后在北京的 dns
上配置 forwarder 到上海,最终权威 NS 建连的 IP 是上海!
? 好吧,正过来想一想:如果每个客户端上都自带一个支持迭代查询的 dnsproxy ,那么
ip 收集也就方便了。
? 小提示:对于动态返回 ip ,可见附 2 脚本
24. 偏门武器谱
? 一本正经说这么多,最后来几个有趣的 DNS 应用:
? 反垃圾邮件白名单
? dig TXT 21cn.com
? 天天背单词之英汉字典
? echo "function j() { dig $1.jianbing.org txt +short | perl
-pe's/(d{1,3})/chr $1/eg; s/"//g' }" >> ~/.bashrc
? source !$
? j apple
? 翻墙
? http://code.kryo.se/iodine/
? http://blog.codingnow.com/2011/06/dns_tunnel.html
25. ? #!/human/mouth
? use active;
? use ME;
? use Topic qw/DNS/;
? my $topic = Topic::DNS->new( slide => 'Spork');
? my $me = ME->bind([ qw/mouth ears/ ]);
? while( $me->say($topic) or my $ears = $me->listen() ) {
? kill STOP => $me->say($topic);
? my $question = <$ears>;
? say " 这个问题问得好! ";
? say $ears $topic->answer($question);
? kill CONT => $me->say($topic);
? };
? say " 真的木问题鸟 ";
? $topic->close();
? __END__
26. 附1
? use 5.014;
? use Net::DNS::Nameserver;
? my $ns = new Net::DNS::Nameserver( LocalPort => 5353,
? ReplyHandler => &reply_handler,
? Verbose => 1
? );
? $ns->main_loop;
? sub reply_handler {
? my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_;
? my ($rcode, @ans, @auth, @add);
? $query->print;
? if ($qtype eq "A" && $qname =~ m/foobar.com$/ ) {
? my ($ttl, $rdata) = (3600, "10.1.2.3");
? my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
? push @ans, $rr;
? $rcode = "NOERROR";
? }else{
? $rcode = "NXDOMAIN";
? }
? return ($rcode, @ans, @auth, @add, { aa => 1 });
? }
27. 附2
? use 5.014;
? use Net::IP::Match::Regexp qw/create_iprange_regexp match_ip/;
? use Stanford::DNS;
? use Stanford::DNSserver;
? my $hostmaster = 'admin.renren-inc.com';
? my $hostname = '127.0.0.1';
? my $soa = rr_SOA($hostname, $hostmaster, time(), 3600, 1800, 86400, 0);
? my $ns = Stanford::DNSserver->new( listen_on => [ $hostname ],
? debug => 1,
? logfunc => sub { say },
? daemon => 'no',);
? my $host = 'chenlin.rao.som.renren-inc.com';
? my $regexp = create_iprange_regexp({ '127.0.0.1/32' => 'localhost', '10.0.0.0/8 =>
'ethernet',});
? my $iplist = { $host => { ethernet => '10.10.10.10', localhost => '12.7.0.10'}, };
? $ns->add_static( $host, T_SOA, $soa );
? $ns->add_static( $host, T_NS, rr_NS($hostmaster) );
? $ns->add_dynamic( $host => &dyn_req );
? $ns->answer_queries();
28. 附2续
? sub dyn_req {
? my ($domain,$residual,$qtype,$qclass,$dm,$from) = @_;
? my $ttl = 3600;
? if ( $qtype == T_A ) {
? my $ip = dyn_match($domain, $from);
? $dm->{'answer'} .= dns_answer(QPTR, T_A, C_IN, $ttl, rr_A($ip));
? $dm->{'ancount'} += 1;
? return 1;
? };
? if ( $qtype == T_TXT ) {
? if ( $domain =~ m/^(w+.w+).som.(renren-inc.com)/ ) {
? $dm->{'answer'} .= dns_answer(QPTR, T_TXT, C_IN, $ttl, rr_TXT("Email
address: $1@$2"));
? $dm->{'ancount'} += 1;
? };
? };
? if ( ! $dm->{ancount} ) {
? $dm->{rcode} = NXDOMAIN;
? };
? };
29. 附 2 再续
? sub dyn_match {
? my ( $domain, $from ) = @_;
? my $area = match_ip($from, $regexp);
? return ($1<<24)|($2<<16)|($3<<8)|$4 if $iplist->{"$host"}->{"$area"} =~ m/
(d{1,3}).(d{1,3}).(d{1,3}).(d{1,3})/;
? };