这个DNS系列现在有以下几篇文章
DNS EDNS DNSSEC DNS over HTTPS 完整代码请看DNS
DNS Header
1 | 1 1 1 1 1 1 |
header在dns请求和应答中相同的,查询中有Query Section,应答中有answer section.
- ID: 2字节,应答中也有ID,可以用来判断是否为我们请求的应答
- QR:1bit,请求时为0,应答时为1
- Opcode:4bit 通常值为0(标准查询),其他值为1(反向查询)和2(服务器状态请求),[3,15]保留值
- AA:1bit authoritative answer,在应答中才有效
- TC:1bit 表示可截断
- RD:1bit 期望使用递归查询
- RA:1bit 在应答中返回,返回服务器是否支持递归查询
- Z: Reserved for future use.
- RCODE:4bit,应答码,代表返回的状态
- 0 No Error
- 1 Format error 格式错误
- 2 Server failure 服务器失败
- 3 Name Error 查询域名错误
- 4 Not Implemented 未实现的查询方式
- 5 Refused 拒绝
- 6-15 Reserved for future use.
- QDCOUNT 请求的个数
- ANCOUNT 应答的个数
- NSCOUNT 无符号16bit整数表示报文授权段中的授权记录数。
- ARCOUNT 无符号16bit整数表示报文附加段中的附加记录数。
查询
1 | 1 1 1 1 1 1 |
QNAME:不定长,格式为域名以点分割的长度,末尾以0结尾,例如:
1
www.google.com -> 3www6google3com0
QTYPE:查询的类型
常用的我们需要知道A为IPV4,AAAA为IPV61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19TYPE value and meaning
A 1 a host address
NS 2 an authoritative name server
MD 3 a mail destination (Obsolete - use MX)
MF 4 a mail forwarder (Obsolete - use MX)
CNAME 5 the canonical name for an alias
SOA 6 marks the start of a zone of authority
MB 7 a mailbox domain name (EXPERIMENTAL)
MG 8 a mail group member (EXPERIMENTAL)
MR 9 a mail rename domain name (EXPERIMENTAL)
NULL 10 a null RR (EXPERIMENTAL)
WKS 11 a well known service description
PTR 12 a domain name pointer
HINFO 13 host information
MINFO 14 mailbox or mail list information
MX 15 mail exchange
TXT 16 text strings
AAAA 28 <- https://www.ietf.org/rfc/rfc3596.txtQCLASS:无符号16bit整数表示查询的类,比如,IN代表Internet.
现在我们来完成生成请求的代码
使用全局变量来存储请求的类型
1 | type reqType [2]byte |
这里需要注意有些字段是以位的大小来存储的,需要使用移位运算符来计算(在完成这个协议前,我写了socks5,因为socks5中全部为字节的大小,可能有人会出现与我相同的错误,所以需要注意一下)
1 | func creatRequest(domain string, reqType reqType) []byte { |
这里我们已经得到了完整的请求字节数组,然后使用UDP协议发起请求就行了:
1 | // 此处以www.google.com及1111 DNS为例 |
接下来我们就需要解析应答请求了,来获取我们需要的数据.
应答
应答数据中具有header和请求字段,所以我们可以先写一个resovle header来分析header.
之前我们完成了域名编码的代码,就是3www6google3com0
这个,这里我们还需要完成一个解析的.
1 | func getName(c []byte, all []byte) (name string, x []byte) { |
之后就可以来分析header了:
1 | func resolveHeader(req []byte, answer []byte) (anCount int, answerSection []byte, err error) { |
之后就是我们真正需要的数据,Answer section
1 | 1 1 1 1 1 1 |
- NAME:不定长与之前QNAME相同,这里会使用省略字段:前两bit为11表示压缩格式,而后面跟的14bit表示的是Name所在的位置相对于DNS首部的偏移值
如:之前的header数组中出现过3www6google3com0,且处的位置为12,这里就可以用192 12两个字节来省略(192是因为11000000,前两位为11) - QTYPE:与之前的TYPE相同
- CLASS:与之前的QCLASS相同
- TTL: 就是TTL 可以使用搜索引擎查询一下
- RDLENGTH: RDATA的长度
这个Answer section与之前的header不同,并不是只出现一次,与header中ANCOUNT有关,如果ANCOUNT不为1,后面就会出现多个,要使用循环来获取所有应答数据
1 | // resolve answer |
虽然这里我只处理了A,和AAAA的请求类型,但是每个类型的数据格式在rfc1035都有详细记录,想要自己实现并不难,毕竟我们已经完成的大部分的数据解析,剩下的一点也应该问题不大.