dns协议实现

Catalogue
  1. 1. @封装发送包体
    1. 1.1. @header头封装
    2. 1.2. @Count 封装
    3. 1.3. @Question 封装
    4. 1.4. @发送数据包
  2. 2. @解析响应包体
    1. 2.1. @获取Answer包体
    2. 2.2. @解析Answer address

@封装发送包体

1
2
3
4
5
6
//protocol/appliction/dns/endpoint.go

h := header.DNS(make([]byte,12))
h.Setheader(e.ID)
h.SetCount(1,0,0,0)
h.SetQuestion(e.Domain,1,1)

首先创建一个Dns的字节数组,默认给12字节大小,因为header头固定为12字节大小

@header头封装

主要是初始化ID 和 一些flag标志位

1
2
3
4
5
6
7
8
9
10
11
12
h.SetHeader(e.ID)

//Setheader
func (d DNS) Setheader(id uint16){
d.setID(id)
d.setFlag(0,0,0,0,1,0,0)
}
//setID 将前两个字节 初始化id
func (d DNS)setID(id uint16){
//set id
binary.BigEndian.PutUint16(d[ID:], id)
}

设置标志位,都给默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//SetFlag
//QR 表示请求还是响应
//OPCODE 1表示反转查询;2表示服务器状态查询。3~15目前保留,以备将来使用
//AA 表示响应的服务器是否是权威DNS服务器。只在响应消息中有效。
//TC 指示消息是否因为传输大小限制而被截断
//RD 该值在请求消息中被设置,响应消息复用该值。如果被设置,表示希望服务器递归查询。但服务器不一定支持递归查询
//RA 。该值在响应消息中被设置或被清除,以表明服务器是否支持递归查询。
//Z 保留备用
//RCODE: 该值在响应消息中被设置。取值及含义如下:
//0:No error condition,没有错误条件;
//1:Format error,请求格式有误,服务器无法解析请求;
//2:Server failure,服务器出错。
//3:Name Error,只在权威DNS服务器的响应中有意义,表示请求中的域名不存在。
//4:Not Implemented,服务器不支持该请求类型。
//5:Refused,服务器拒绝执行请求操作。
//6~15:保留备用
func (d DNS) setFlag(QR uint16, OPCODE uint16, AA uint16, TC uint16, RD uint16, RA uint16, RCODE uint16) {
//set flag
op := QR<<15 + OPCODE<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
binary.BigEndian.PutUint16(d[OP:],op)
}

到这里包头header4字节就算封装好了

@Count 封装

因为是查询包体,只需要设置query count即可,现在只支持单条查询,所以默认给1

1
2
3
4
5
6
7
8
9
10
11
//SetCount
func (d DNS) SetCount(qd,an,ns,qa uint16) {
//SetQdcount
binary.BigEndian.PutUint16(d[QDCOUNT:], qd)
//SetAncount
binary.BigEndian.PutUint16(d[ANCOUNT:] ,an)
//SetNscount
binary.BigEndian.PutUint16(d[NSCOUNT:],ns)
//SetQAcount
binary.BigEndian.PutUint16(d[ARCOUNT:],qa)
}

这里每个标志位占2字节,总共8字节,加上上面的header4字节 总共12字节

@Question 封装

这里主要是将需要查询的域名写入包体中,这里有个地方需要计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func (d *DNS)SetQuestion(domain string,qtype,qclass uint16){
for _,b := range d.getDomain(domain) {
*d = append((*d),b)
}
//d.setDomain(domain)
q := DNSQuestion{
QuestionType: qtype,
QuestionClass: qclass,
}
var buffer bytes.Buffer
binary.Write(&buffer,binary.BigEndian,*d)
binary.Write(&buffer,binary.BigEndian,q)
*d = buffer.Bytes()
}
func (d *DNS)getDomain(domain string) []byte {
var (
buffer bytes.Buffer
segments []string = strings.Split(domain, ".")
)
for _, seg := range segments {
binary.Write(&buffer, binary.BigEndian, byte(len(seg)))
binary.Write(&buffer, binary.BigEndian, []byte(seg))
}
binary.Write(&buffer, binary.BigEndian, byte(0x00))

return buffer.Bytes()
}

  • 首先计算待查询的域名动态字节并返回
  • 最后在封装DNSQuestion4字节追加到末尾
  • 这里基本完成了所有的请求包的构建

@发送数据包

dns是基于dns协议查询,直接将上面进行udp发送即可

1
2
3
4
5
6
7
8
9
10
11
//sendQuery udp query dns
func (e *Endpoint) sendQuery () ( *[]header.DNSResource ,error ) {

if err := e.c.Connect();err != nil {
return nil,err
}
if err := e.c.Write(*e.req) ; err != nil {
return nil,err
}
return e.parseResp()
}

@解析响应包体

主要就是接收udp响应数据,注意:==udp当前实现是 如果对端不可访问。在read时才会接收到icmp错误控制消息==

1
2
3
4
5
6
7
8
9
10
11
12
//parseResp
//解析响应
func (e *Endpoint) parseResp() (*[]header.DNSResource,error){
rsp,err := e.c.Read()
if err != nil {
return nil,err
}
p := header.DNS(rsp)
e.resp = &p
e.answer = p.GetAnswer(e.Domain)
return e.parseAnswer()
}

@获取Answer包体

主要是计算三个count计数总和,判断总共有多少条响应记录

剩下的是挨着字节数遍历读取即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//GetAnswer
func (d DNS) GetAnswer(domain string) *[]DNSResource {
//answer 起始地址
asLen := DOMAIN + len(d.getDomain(domain)) + 4

answer := []DNSResource{}
for i := 0; i < (int(d.GetANCount() + d.GetNSCount() + d.GetARCount())) ;i ++ {
rs := DNSResource{}
//判断是不是指针 pointer地址
if checkP := d[asLen]; checkP >> 6 == 3 {
//pointer := (d[asLen] & 0x3F << 8) + d[asLen+1]
rs.Name = binary.BigEndian.Uint16(d[asLen:asLen+2])
asLen += 2
rs.Type = DNSResourceType(binary.BigEndian.Uint16(d[asLen:asLen+2]))
asLen += 2
rs.Class = binary.BigEndian.Uint16(d[asLen:asLen+2])
asLen += 2
rs.TTL = binary.BigEndian.Uint32(d[asLen:asLen+4])
asLen += 4
rs.RDlen = binary.BigEndian.Uint16(d[asLen:asLen+2])
asLen += 2
rs.RData = d[asLen:asLen+int(rs.RDlen)]
asLen += int(rs.RDlen)
answer = append(answer,rs)
}
}
return &answer
}

@解析Answer address

这里主要解析A类型 和Cname类型,基本满足场景了

1
2
3
4
5
6
7
8
9
10
11
func (e *Endpoint) parseAnswer()(*[]header.DNSResource,error){
for i := 0; i < len(*e.answer) ; i++ {
switch (*e.answer)[i].Type {
case header.A:
(*e.answer)[i].Address = e.parseAName((*e.answer)[i].RData)
case header.CNAME:
(*e.answer)[i].Address = e.parseCName((*e.answer)[i].RData)
}
}
return e.answer,nil
}