用go实现dns请求

一、DNS报文格式详解

1.1 报文格式

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个字节

1.2 结构组成

整个DNS格式主要分为3部分内容,即基础结构部分、问题部分、资源记录部分。

1.2.1 基础结构部分(header头部)

DNS报文的基础结构部分指的是报文首部,如下表所示

事务ID(Transaction ID) 标志(Flags)
问题计数 (Questions) 回答资源记录数(Answer RRs)
权威名称服务器计数(Authority RRs)     附加资源记录数(Additional RRs)

其中字段含义如下:

  1. 事务ID(Transaction ID):DNS报文的ID标识。对于请求报文和其对应的应答报文,该字段的值是相同的,主要是通过其区分DNS应答报文响应的是哪个请求
  2. 标志(Flag):DNS报文中标志字段,其中又分为8个子字段:QR、Opcode、AA、TC、RD、RA、Z、rcode
  3. 问题计数(Questions):DNS查询请求的数目
  4. 回答资源记录数(Answer RRs):DNS响应的数目
  5. 权威名称服务器计数(Authority RRs):权威名称服务器的数目
  6. 附加资源记录数(Additonal RRs):额外的记录数目(权威名称服务器对应IP地址的数目)

标志字段(Flags)的子字段解释:

1bit 4bit 1bit 1bit 1bit 1bit 3bit 4bit
QR Opcode AA TC RD RA Zero rcode
  1. QR(Question/Response):查询请求/响应的标志信息。查询请求时,值为0;响应时,值为1
  2. Opcode:操作码。其中, 0代表标准查询;1表示反向查询;2表示服务器状态查询
  3. AA(Authoritative):授权应答,该字段在响应报文中有效。值为1时,表示名称服务器是权威服务器;值为0时,表示不是权威服务器
  4. TC(Truncated):表示是否被截断,值为1时,表示响应报文已超过512字节并且已被截断,只返回前512字节
  5. RD(Recursion Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。该标志告诉服务器必须处理整个查询,这种方式被称为一个递归查询。如果该位值为0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询
  6. RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为1时,表示服务器支持递归查询
  7. Zero:保留字段,在所有的请求和应答报文中,它的值必须为0
  8. rcode(Reply code):返回码字段,表示响应的差错状态,当值为0时,表示没有错误;当值为1时,表示报文格式错误(Format error),服务器不能理解请求的报文; 当值为2时,表示域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求; 当值为3时,表示名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在; 当值为4时,表示查询类型不支持(Not Implemented),即域名服务器不支持查询类型;当值为5时,表示拒绝(Refused), 一般时服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。

1.2.2 问题部分

问题部分指的是报文格式中查询问题区域(Queries)部分。该部分用来显示DNS查询请求的问题,通常只有一个问题。该部分包含正在进行的查询信息,包含查询名(被查询主机名字)、查询类型、查询类

Queries

0                                                     15 16                                                   31
                                      Name(查询名,长度不固定)
           Type(查询类型)                  Class(查询类)
  • 查询名:长度不固定,不填充字节,一般表示反向查询,即由ip地址反查域名
    • 格式: 5baidu3com0   : 5表示后面域名的长度,3同理,最后必须为0
  • 查询类型:DNS查询请求的资源类型。通常查询类型为A类型,表示由域名获取对应的IP地址
类型 助记符 说明
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,表示TCP/IP互联网地址
标识符 描述
1 IN 指定Internet类别,即UDP
2 CSNET 指定CSNET类别(已过时)
3 CHAOS 指定CHaos类别
4 HESIOD 指定MIT Athena Hesiod类别
255 ANY 指定任何以前列出的通配符

 1.2.3 资源记录部分

资源记录部分是指 DNS 报文格式中的最后三个字段,包括回答问题区域字段、权威名称服务器区域字段、附加信息区域字段。这三个字段均采用一种称为资源记录的格式,格式如下图所示。

用go实现dns请求_第1张图片

  • 域名:DNS请求的域名
  • 类型:资源记录的类型,与问题部分中的查询类型值是一样的
  • 类:地址类型,与问题部分中查询类是一样的
  • 生存时间:以秒为单位,表示资源记录的生命周期,一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间。它同时也可以表明该资源记录的稳定程度,稳定的信息会被分配一个很大的值。
  • 资源数据长度:资源数据的长度
  • 资源数据:表示按查询段要求返回的相关资源记录的数据。

ps:资源记录部分只在DNS响应包中才会出现。

1.3 抓包详解

1.3.1 基础结构部分报文分析

1、DNS请求包

用go实现dns请求_第2张图片

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:上述为第一个请求包中基础结构部分,其中有些注意点:

  • AA标志:该段置空,主要在响应报文中
  • Answer RRs、Authority RRs、Additional RRs为0,因为在请求中还没有响应的查询结果信息。需要在响应包中查看

2、DNS响应包

用go实现dns请求_第3张图片

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.3.2 问题部分报文分析

1、DNS请求包

第一个请求包

用go实现dns请求_第4张图片

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)  #查询类字段,这里为互联网地址

第二个请求包: 

用go实现dns请求_第5张图片

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响应包

第一个响应包:

用go实现dns请求_第6张图片

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)  #查询类字段,这里为互联网地址

用go实现dns请求_第7张图片

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)   #查询类字段,这里为互联网地址

从上述包中可知,响应包中的查询类型和请求包是一致 的。

1.3.3 资源记录部分报文分析

注:资源记录部分,只会在DNS响应包中才会出现

1、DNS响应包的资源记录部分的字段信息,如图

用go实现dns请求_第8张图片

用go实现dns请求_第9张图片

附加信息区域

用go实现dns请求_第10张图片

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、权威名称服务器区域字段的资源记录部分信息

用go实现dns请求_第11张图片

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地址

二、使用go实现DNS请求

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("发送成功")


}

用go实现dns请求_第12张图片

用go实现dns请求_第13张图片

你可能感兴趣的:(golang,linux,运维,服务器,golang,开发语言,后端)