DNS分为查询请求和查询响应,两者的报文结构基本相同。DNS报文格式如下表展示
0 15 | 16 31 |
事务ID(Transaction ID) | 标志(Flags) |
问题计数 (Questions) | 回答资源记录数(Answer RRs) |
权威名称服务器计数(Authority RRs) | 附加资源记录数(Additional RRs) |
查询问题区域(Queries) | |
回答问题区域(Answers) | |
权威名称服务器区域(Authoritative nameservers) | |
附加信息区域(Additional records) |
上表中显示了DNS的报文格式。其中,事务ID、标志、问题计数、回答资源记录数、权威名称服务器计数、附加资源记录数,这6个字段是DNS的报文首部,各占两个字节,共12个字节
整个DNS格式主要分为3部分内容,即基础结构部分、问题部分、资源记录部分。
DNS报文的基础结构部分指的是报文首部,如下表所示
事务ID(Transaction ID) | 标志(Flags) |
问题计数 (Questions) | 回答资源记录数(Answer RRs) |
权威名称服务器计数(Authority RRs) | 附加资源记录数(Additional RRs) |
其中字段含义如下:
标志字段(Flags)的子字段解释:
1bit | 4bit | 1bit | 1bit | 1bit | 1bit | 3bit | 4bit |
QR | Opcode | AA | TC | RD | RA | Zero | rcode |
问题部分指的是报文格式中查询问题区域(Queries)部分。该部分用来显示DNS查询请求的问题,通常只有一个问题。该部分包含正在进行的查询信息,包含查询名(被查询主机名字)、查询类型、查询类
Queries
0 15 | 16 31 |
Name(查询名,长度不固定) | |
Type(查询类型) | Class(查询类) |
类型 | 助记符 | 说明 |
1 | A | 由域名获得ipv4地址 |
2 | NS | 查询域名服务器 |
3 | MD | 指定邮件接收站(已过时,使用mx代替) |
4 | MF | 指定邮件中转站(已过时,使用mx代替) |
5 | CNAME | 查询规范名称,别名 |
6 | SOA | 指定DNS区域的的”起始授权机构“ |
7 | MB | 指定邮箱域名 |
8 | MG | 指定邮件组成员 |
9 | MR | 指定邮件重命名域名 |
10 | NULL | 指定空的资源记录 |
11 | WKS | 描述已知服务 |
12 | PTR | 如果查询是IP地址,则指定计算机名;否则指定指向其他信息的指针 |
13 | HINFO | 主机信息,计算机cpu以及操作系统类型 |
14 | MINFO | 指定邮箱或邮件列表信息 |
15 | MX | 指定邮件交换器 |
16 | TXT | 指定文本信息 |
28 | AAAA | 由域名获得IPv6地址 |
100 | UINFO | 指定用户信息 |
101 | UID | 指定用户标识符 |
102 | GID | 指定组名的组标识符 |
252 | AXFR | 传送整个区的请求 |
255 | ANY | 对所有记录的请求 |
值 | 标识符 | 描述 |
1 | IN | 指定Internet类别,即UDP |
2 | CSNET | 指定CSNET类别(已过时) |
3 | CHAOS | 指定CHaos类别 |
4 | HESIOD | 指定MIT Athena Hesiod类别 |
255 | ANY | 指定任何以前列出的通配符 |
资源记录部分是指 DNS 报文格式中的最后三个字段,包括回答问题区域字段、权威名称服务器区域字段、附加信息区域字段。这三个字段均采用一种称为资源记录的格式,格式如下图所示。
ps:资源记录部分只在DNS响应包中才会出现。
1、DNS请求包
Domain Name System (query)
Transaction ID: 0x0001 #事务ID
Flags: 0x0100 Standard query #报文中的标志字段
0... .... .... .... = Response: Message is a query #QR字段,值为0,表示是个请求包
.000 0... .... .... = Opcode: Standard query (0) #Opcode字段,值为0,标准查询(域名查IP)
.... .. 0. .... .... = Truncated: Message is not truncated #TC,表明未被截断
.... ... 1 .... .... = Recursion desired: Do query recursively #RD:递归查询
.... .... . 0.. .... = Z: reserved (0) #Z:保留字段,必须为0
.... .... ... 0 .... = Non-authenticated data: Unacceptable
Questions: 1 #问题计数,只有一个问题
Answer RRs: 0 #回答资源记录数
Authority RRs: 0 #权威名称服务器计数
Additional RRs: 0 #附加资源记录数
PS:上述为第一个请求包中基础结构部分,其中有些注意点:
2、DNS响应包
Domain Name System (response)
Transaction ID: 0x0001 #事务id,同请求包,表示0x0001的响应包
Flags: 0x8580 Standard query response, No error
1... .... .... .... = Response: Message is a response #QR字段,值为1,表示是响应包
.000 0... .... .... = Opcode: Standard query (0) #Opcode字段,值为0,表示标准查询
.... .1.. .... .... = Authoritative: Server is an authority for domain #AA字段,值为1,表示服务器为权威服务器
.... ..0. .... .... = Truncated: Message is not truncated #TC字段,值为0,表示未被截断
.... ...1 .... .... = Recursion desired: Do query recursively #RD字段,值为1,表明是递归查询
.... .... 1... .... = Recursion available: Server can do recursive queries #RA字段,值为1,表明支持递归查询
.... .... .0.. .... = Z: reserved (0) #Z,保留字段,必须为0,占3bit
.... .... ..0. .... = Answer authenticated: Answer/authority portion was not authenticated by the server
.... .... ...0 .... = Non-authenticated data: Unacceptable
.... .... .... 0000 = Reply code: No error (0) #rcode,返回码,值为0,表示没有错误
Questions: 1 #问题计数,这里有一个问题
Answer RRs: 1 #回答资源记录数1
Authority RRs: 0
Additional RRs: 0
以上输出信息中加粗部分为 DNS 响应包比请求包中多出来的字段信息,这些字段信息只能出现在响应包中。在输出信息最后可以看到 Answer RRs,Authority RRs,Additional RRs 都有了相应的值(不一定全为 0)。
1、DNS请求包
第一个请求包
Queries #问题部分
22.1.168.192.in-addr.arpa: type PTR, class IN #
Name: 22.1.168.192.in-addr.arpa #查询名字段,这里请求域名服务器
[Name Length: 25]
[Label Count: 6]
Type: PTR (domain name PoinTeR) (12) #查询类型字段,这里为PTR类型,表示域名指针)
Class: IN (0x0001) #查询类字段,这里为互联网地址
第二个请求包:
Queries
www.youzi.com.xxxxxxx.com: type A, class IN #问题部分
Name: www.youzi.com.xxxxxxx.com #查询名字段,这里请求域名为www.youzi.com
[Name Length: 25]
[Label Count: 5]
Type: A (Host Address) (1) #查询类型字段,这里为A类型
Class: IN (0x0001) #查询类字段,这里为互联网地址
如上所示,第一个请求包是访问dns服务器,类型为PTR;第二个请求包才包含要请求解析的域名信息,类型为A,那么第二个请求的响应信息也应该为A类型。
2、DNS响应包
第一个响应包:
Queries #响应包问题部分
22.1.168.192.in-addr.arpa: type PTR, class IN
Name: 22.1.168.192.in-addr.arpa #查询名字段
[Name Length: 25]
[Label Count: 6]
Type: PTR (domain name PoinTeR) (12) #查询类型字段,这里为PTR类型
Class: IN (0x0001) #查询类字段,这里为互联网地址
Queries
www.youzi.com.xxxxxxx.com: type A, class IN
Name: www.youzi.com.xxxxxxx.com #查询名字段,这里请求域名为www.youzi.com
[Name Length: 25]
[Label Count: 5]
Type: A (Host Address) (1) #查询类型字段,这里为A类型
Class: IN (0x0001) #查询类字段,这里为互联网地址
从上述包中可知,响应包中的查询类型和请求包是一致 的。
注:资源记录部分,只会在DNS响应包中才会出现
1、DNS响应包的资源记录部分的字段信息,如图
附加信息区域
2、回答问题区域字段的解析
#第一个响应包的资源记录部分
Answers
22.1.168.192.in-addr.arpa: type PTR, class IN, yyyyyyy.xxxxxxx.com #资源记录部分
Name: 22.1.168.192.in-addr.arpa #域名字段, here the name servers
Type: PTR (domain name PoinTeR) (12) #类型字段, here PTR, domain name pointer
Class: IN (0x0001) #类字段,
Time to live: 1200 (20 minutes) #ttl:生存时间
Data length: 21 #数据长度
Domain Name: yyyyyyy.xxxxxxx.com #域名
一条A记录查询的响应资源部分
Answers
www.youzi.com: type CNAME, class IN, cname youzi.com #资源记录部分
Name: www.youzi.com #域名字段,请求域名为www.youzi.com
Type: CNAME (Canonical NAME for an alias) (5) #类型字段,这里为CNAME
Class: IN (0x0001) #类字段
Time to live: 3432 (57 minutes, 12 seconds) #ttl,生存时间
Data length: 2 #数据长度
CNAME: youzi.com #资源数据,这里为别名
youzi.com: type A, class IN, addr 65.181.111.8
Name: youzi.com
Type: A (Host Address) (1)
Class: IN (0x0001)
Time to live: 3432 (57 minutes, 12 seconds)
Data length: 4
Address: 65.181.111.8 #资源数据,这里为IP地址
ps:请求包部分:
Queries
www.youzi.com: type A, class IN
Name: www.youzi.com
[Name Length: 13]
[Label Count: 3]
Type: A (Host Address) (1)
Class: IN (0x0001)
其中,name的值为www.youzi.com,表示dns请求的域名的为www.youzi.com,类型为A,表示要获取该域名对应的 IP地址,最后的Address表明获取到的IP地址。
3、权威名称服务器区域字段的资源记录部分信息
Authoritative nameservers
xxxxxx.com: type SOA, class IN, mname yyyyyyy.xxxxxxx.com #dns区域的起始授权机构
Name: xxxxxxx.com #授权机构名
Type: SOA (Start Of a zone of Authority) (6) #类型,当前为SOA
Class: IN (0x0001) #类,网络类别,即UDP
Time to live: 60 (1 minute) #生存时间
Data length: 42 #数据长度
Primary name server: yyyyyyy.xxxxxxx.com #数据部分
Responsible authority's mailbox: hostmaster
Serial Number: 17030351
Refresh Interval: 900 (15 minutes)
Retry Interval: 600 (10 minutes)
Expire limit: 86400 (1 day)
Minimum TTL: 60 (1 minute)
4、附件信息区域(引用数据)
Additional records #“附加信息区域”字段
dns.baidu.com: type A, class IN, addr 202.108.22.220 #资源记录部分
Name: dns.baidu.com #“权威名称服务器”名称
Type: A (Host Address) (1) #类型字段, 这里为A类型
Class: IN (0x0001)
Time to live: 5
Data length: 4
Address: 202.108.22.220 #“权威名称服务器”的IP地址
ns2.baidu.com: type A, class IN, addr 61.135.165.235 #资源记录部分
Name: ns2.baidu.com #“权威名称服务器”名称
Type: A (Host Address) (1) #类型字段, 这里为A类型
Class: IN (0x0001)
Time to live: 5
Data length: 4
Address: 61.135.165.235 #“权威名称服务器”的IP地址
ns3.baidu.com: type A, class IN, addr 220.181.37.10 #资源记录部分
Name: ns3.baidu.com #“权威名称服务器”名称
Type: A (Host Address) (1) #类型字段, 这里为A类型
Class: IN (0x0001)
Time to live: 5
Data length: 4
Address: 220.181.37.10 #“权威名称服务器”的IP地址
ns4.baidu.com: type A, class IN, addr 220.181.38.10 #资源记录部分
Name: ns4.baidu.com #“权威名称服务器”名称
Type: A (Host Address) (1) #类型字段, 这里为A类型
Class: IN (0x0001)
Time to live: 5
Data length: 4
Address: 220.181.38.10 #“权威名称服务器”的IP地址
ns7.baidu.com: type A, class IN, addr 180.76.76.92 #资源记录部分
Name: ns7.baidu.com #“权威名称服务器”名称
Type: A (Host Address) (1) #类型字段, 这里为A类型
Class: IN (0x0001)
Time to live: 5
Data length: 4
Address: 180.76.76.92 #“权威名称服务器”的IP地址
package main
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"strings"
)
// dns报文基础结构部分
type DNSHeader struct {
ID uint16 //事务ID
Flag uint16 //标志
QuestionCount uint16 //请求问题数
AnswerRRS uint16 //回答资源记录数
AuthorityRRS uint16 //权威名称服务器数
AdditionalRRS uint16 // 附加资源记录数
}
// 设置标志
func (header *DNSHeader) SetFlag(QR uint16, OperationCode uint16, AuthoritativeAnswer uint16, Truncation uint16, RecursionDesired uint16, RecursionAvailable uint16, ResponseCode uint16) {
//其中QR占1bit,opcode 占4bit, AA 占1bit, TC 占1bit, RD 占1bit, RA 占1bit,Zero占3bit,Rcode 占4bit
header.Flag = QR<<15 + OperationCode<<11 + AuthoritativeAnswer<<10 + Truncation<<9 + RecursionDesired<<8 + RecursionAvailable<<7 + ResponseCode
}
// dns报文问题部分 quertes
type DNSQuery struct {
QuestionType uint16 //请求类型
QuestionClass uint16 //请求类
}
func ParseDomainName(domain string) []byte {
//要将域名解析成相应的格式,例如
//"www.google.com"会被解析成"0x03www0x06google0x03com0x00"
//就是长度+内容,长度+内容……最后以0x00结尾
var (
buffer bytes.Buffer
segments []string = strings.Split(domain, ".") //以 . 分割域名
)
for _, seg := range segments {
binary.Write(&buffer, binary.BigEndian, byte(len(seg))) //使用大端字节序 比如www.baidu.com, 第一个seg就是www,这里就是byte(3),为0x03
binary.Write(&buffer, binary.BigEndian, []byte(seg)) //这里第一个为www,此切片为[w,w,w]==》[119,119,119]==>[0x77,0x77,0x77],大端字节序后77 77 77
fmt.Printf("seg为 %s, 长度为 %d \t 经过大端字节序后", seg, len(seg))
fmt.Println(buffer.Bytes())
}
binary.Write(&buffer, binary.BigEndian, byte(0x00))
fmt.Printf("\n解析完后,对应的域名格式为 %v\n", buffer.Bytes())
return buffer.Bytes()
}
func main() {
var (
dns_header DNSHeader
dns_question DNSQuery
)
//填充dns首部
dns_header.ID = 0xFFFF
dns_header.SetFlag(0, 0, 0, 0, 1, 0, 0)
dns_header.QuestionCount = 1
dns_header.AnswerRRS = 0
dns_header.AuthorityRRS = 0
dns_header.AdditionalRRS = 0
//填充dns查询首部
dns_question.QuestionType = 1 //ipv4
dns_question.QuestionClass = 1 //A 查询
var (
conn net.Conn
err error
buffer bytes.Buffer
)
//DNS服务器端口一般是53,且基于UDP协议
if conn, err = net.Dial("udp", "192.168.1.22:53"); err != nil { //211.137.191.26为dns服务器IP
fmt.Println(err.Error())
return
}
defer conn.Close()
//buffer中是我们要发送的数据,里面的内容是dns首部 + 查询内容 + dns查询首部
binary.Write(&buffer, binary.BigEndian, dns_header)
fmt.Printf("加入dns_header,此时值为 %v\n进入域名解析中。。。\n", buffer.Bytes())
binary.Write(&buffer, binary.BigEndian, ParseDomainName("www.baidu.com"))
fmt.Printf("继续加入域名解析后的格式,此时值为 %v\n", buffer.Bytes())
binary.Write(&buffer, binary.BigEndian, dns_question)
fmt.Printf("继续加入dns_question,此时值为 %v\n", buffer.Bytes())
if _, err := conn.Write(buffer.Bytes()); err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("发送成功")
}