使用golang构建DNS A记录 回复报文
使用golang构建DNS A记录 报文
关于DNS协议,参考如下
看到一篇比较好的博客,推荐一下
官方文档
DNS A记录UDP 报文 协议实现: https://gitee.com/pdudo/SampleDNSTool
实现了简单的A记录的项目Demo: https://gitee.com/pdudo/SampleDNS2
1. 快速体验
1.1 Docker 体验
启动
# docker run -d --restart=always --name dns-server -p 53:53/udp -p 53:53 -p 5001:5001 docker.io/2859413527/sample-dns
访问web界面
1.2 二进制包体验
SampleDNS2 使用Redis作为数据库,请提前构建Redis数据库
1.2.1 下载可执行文件
wget https://gitee.com/pdudo/SampleDNS2/attach_files/873359/download/SampleDNS2-v0.2-alpha-linux-amd64
1.2.2 创建配置文件
# cat conf.yaml 
# Global
Global:
  RedisHost: "127.0.0.1:6379" # 存放记录的redis数据库
  Auth: "" # 密码
  DB: 0 # 库
# DNS
DNS:
  bind: "0.0.0.0:53" # DNS服务器监听TCP/UDP套接字
  ProxyDNS: "114.114.114.114" # 代理DNS服务器
  ProxyDNSPort: 53 # 代理服务器端口
# Web
Web:
  bind: "0.0.0.0:5001" # Web http 监听地址
#
1.3.3 执行程序
# ./SampleDNS2 
2021/11/06 22:12:42 conf value &{{127.0.0.1:6379  0} {0.0.0.0:53 114.114.114.114 53} {0.0.0.0:5001}}
2021/11/06 22:12:42 load conf file : conf.yaml
2021/11/06 22:12:42 connect redis successful {127.0.0.1:6379  0}
2021/11/06 22:12:42 Server Start  0.0.0.0:5001
2021/11/06 22:12:45 DNS Server TCP Start successful
2021/11/06 22:12:45 DNS Server UDP Start successful  0.0.0.0:53
1.3 编译体验
SampleDNS2 使用Redis作为数据库,请提前构建Redis数据库
1.3.1 clone 代码
# mkdir $GOPATH/src/gitee.com/pdudo 
# cd $GOPATH/src/gitee.com/pdudo 
# git clone https://gitee.com/pdudo/SampleDNSTool
# git clone https://gitee.com/pdudo/SampleDNS2
1.3.2 编译代码
# cd $GOPATH/src/gitee.com/pdudo/SampleDNS2
# go build
参照 1.2 二进制包
1.4 Web 维护域名信息
显示记录

添加记录

修改记录

删除记录

1.5 测试
使用ping/nslookup/dig即可测试

2. 协议概述
1字节(bite) = 8位(bit)
16位 = 2字节
2.1 格式
参考: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1
    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+
Header: 报文头
Question: 查询的请求
Answer: 恢复的消息
Authority: 权威服务器信息
Additional: 附加信息资源
2.2 Header
参考文档: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1
Header 头
                                    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                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
ID: 16bit(2byte) , 由程序生成的16位标识符,用于查询和回复
状态码 16bit(2byte)
 QR: 1 bit 消息类型, 0为查询(请求报文) 1为回复(回复报文)
 Opcode: 4bit 查询类型, 0: 标准查询 1: 反向查询 3:服务器状态请求 3-15: 保留
 AA: 1bit 权威应答,若回复为权威服务器发出来的为1,若不是则为0
 TC: 1bit 截断,若消息长度大于允许的长度(512byte)则为1,否则为0
 RD: 1bit 若需要递归查询设为1,否则为0,若设置了RD,他将指示用于递归查询的服务器于响应报文中
 RA: 1bit 若设置为1,则服务器支持递归,若为0,则不支持递归
 Z: 3bit, 保留
 RCODE: 4bit 响应代码, 为0: 无错误 1: 格式错误 2: 服务器故障 3: 名称错误 4: 未实现 5: 拒绝服务
QDCOUNT: 16bit(2byte) 问题部分条目
ANCOUNT:16bit(2byte) 答案部分条目
NSCOUNT: 16bit(2byte) 权威资源条目
ARCOUNT: 16bit(2byte)附加部分条目
2.3 Question
参考文档: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.2
Question 报文
                                    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                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
QNAME: 可变长的 , 由一系列标签组成,标签则又有实际的位数值 和 实际的数据组成
QTYPE: 16bit(2byte) 域名资源类型
 类型参考: https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2
 查询A记录时,该值应该为 1
QCLASS: 16bit(2byte) 请求的方式
 请求方式参考: https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.4
 一般请求,应该为 1 即为 “IN”
2.4 Resource record
响应报文
                                    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                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
NAME: 16bit(2byte) 记录所属的域名, 这里记录的是标志位 (一般来说,为 OxC00C , CO 为固定标记位,代表后面的值为偏移量, OC 为请求报文QNAME的偏移量(因为header为12byte))
 关于偏移量参考: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4
TYPE: 16bit(2byte) RDATA的资源类型
CLASS: 16bit(2byte) 指定RDATA字段请求类型
TTL: 32bit(4byte) 指定缓存时间,若为0 则不能缓存
RDLENGTH: 16bit(2byte) 指定RDATA的字节数
RDATA: 可变长的, 资源记录值
3. 代码实现
DNS 报文使用的是 Big endian
gitee地址: https://gitee.com/pdudo/SampleDNSTool
3.1 定义结构体
package SampleDNSTool
type DNSInfo struct {
	Header DNSHeader
	QueryInfo Queries
	QueryStep QueriesStep
	AnswerInfo Answers
}
// Header
type DNSHeader struct {
	ID uint16 // ID
	HeaderStatus HeaderInfo // QR QPCODE AA TC RD RA Z RCODE
	QCOUNT uint16 //QCOUNT
	ANCOUNT uint16 //ANCOUNT
	NSCOUNT uint16 // NSCOUNT
	ARCOUNT uint16 // ARCOUNT
}
// Header Status
type HeaderInfo struct {
	QR uint8 // 1 bit
	Opcode uint8 // 4 bit
	AA uint8 // 1bit
	TC uint8 // 1 bit
	RD uint8 // 1 bit
	RA uint8 // 1 bit
	Z uint8 // 3 bit
	RCODE uint8 // 4bit
}
// Question
type Queries struct {
	QNAME []byte //QNAME
	QNAMEString string //QNAMEString
	QTYPE uint16  //QTYPE
	QCLASS uint16 //QClass
}
// Resource record
type Answers struct {
	NAME uint16 // NAME
	TYPE uint16 // TYPE
	CLASS uint16 // CLASS
	TTL uint32 // TTL
	RDLENGTH uint16 // RELENGTH
	RDATA []byte // RDATA
}
// Question step
type QueriesStep struct {
	QueryStart int // headEnd Question start
	QueriesEnd int //Question end
	QueriesDomainEnd int // QNAME end
}
3.2 获取Header信息
// 获取 Header
func (dnsInfo *DNSInfo)GetHeader(buf []byte) {
	HeaderByte := buf[:12]
	startID := 0
	dnsInfo.Header.ID = binary.BigEndian.Uint16(HeaderByte[startID:startID+2])
	startID += 2
	headerStatus := binary.BigEndian.Uint16(HeaderByte[startID:startID+2])
	startID += 2
	dnsInfo.Header.HeaderStatus.QR = uint8(headerStatus << 15)
	dnsInfo.Header.HeaderStatus.Opcode = uint8((headerStatus << 1) >> 12)
	dnsInfo.Header.HeaderStatus.AA = uint8((headerStatus << 5) >> 15)
	dnsInfo.Header.HeaderStatus.TC = uint8((headerStatus << 6) >> 15)
	dnsInfo.Header.HeaderStatus.RD = uint8((headerStatus << 7) >> 15)
	dnsInfo.Header.HeaderStatus.RA = uint8((headerStatus << 8) >> 15)
	dnsInfo.Header.HeaderStatus.Z = uint8((headerStatus << 9) >> 13)
	dnsInfo.Header.HeaderStatus.RCODE = uint8( (headerStatus << 12) >> 12)
	dnsInfo.Header.QCOUNT = binary.BigEndian.Uint16(HeaderByte[startID:startID+2])
	startID += 2
	dnsInfo.Header.ANCOUNT = binary.BigEndian.Uint16(HeaderByte[startID:startID+2])
	startID += 2
	dnsInfo.Header.NSCOUNT = binary.BigEndian.Uint16(HeaderByte[startID:startID+2])
	startID += 2
	dnsInfo.Header.ARCOUNT = binary.BigEndian.Uint16(HeaderByte[startID:startID+2])
	startID += 2
}
3.3 获取Question
// 获取 查询报文
func (dnsInfo *DNSInfo)GetQuestion(buf []byte)() {
		QueriesByte := buf[12:]
		startID := 0
		var qNameLen uint8
		buffer := bytes.NewBuffer(QueriesByte[startID:startID+1])
		startID += 1
		binary.Read(buffer,binary.BigEndian,&qNameLen)
		var qDomainName string
		for qNameLen > 0 {
			if "" != qDomainName {
				qDomainName += "."
			}
			qDomainName += string(QueriesByte[startID:startID+int(qNameLen)])
			startID += int(qNameLen)
			buffer = bytes.NewBuffer(QueriesByte[startID:startID+1])
			startID += 1
			binary.Read(buffer,binary.BigEndian,&qNameLen)
		}
		endDomainNmaeLen := startID
	dnsInfo.QueryInfo.QNAMEString = qDomainName
	dnsInfo.QueryInfo.QNAME = QueriesByte[:endDomainNmaeLen]
	dnsInfo.QueryInfo.QTYPE = binary.BigEndian.Uint16(QueriesByte[startID:startID+2])
	startID += 2
	dnsInfo.QueryInfo.QCLASS = binary.BigEndian.Uint16(QueriesByte[startID:startID+2])
	startID += 2
	var step QueriesStep
	step.QueryStart = 12
	step.QueriesDomainEnd = endDomainNmaeLen
	step.QueriesEnd = startID+step.QueryStart
	dnsInfo.QueryStep = step
}
3.4 生成Resource record报文
// 生成相应报文
func (dnsInfo *DNSInfo)GenerateAnswers(buf []byte, RDATA []byte, ResponseCode uint16 , QType uint8)([]byte) {
	msgBuf := make([]byte,102400)
	// header
	startID := 0
	binary.BigEndian.PutUint16(msgBuf[startID:startID+2],dnsInfo.Header.ID)
	startID += 4
	binary.BigEndian.PutUint16(msgBuf[startID:startID+2],1)
	startID += 2
	if 1 == QType {
		binary.BigEndian.PutUint16(msgBuf[startID:startID+2],uint16(len(RDATA)/4))
		startID += 2
	} else {
		binary.BigEndian.PutUint16(msgBuf[startID:startID+2],1)
		startID += 2
	}
	binary.BigEndian.PutUint16(msgBuf[startID:startID+2],0)
	startID += 2
	binary.BigEndian.PutUint16(msgBuf[startID:startID+2],0)
	startID += 2
	// Query
	for i:=dnsInfo.QueryStep.QueryStart;i<dnsInfo.QueryStep.QueriesEnd;i++ {
		msgBuf[startID] = buf[i];
		startID += 1
	}
	var headerInfo uint16
	headerInfo = 0
	headerInfo += 1 << 15
	headerInfo += 1 << 8
	headerInfo += 1 << 7
	if 512 < (startID + (len(RDATA)/4 * 16)) {
		headerInfo += 1 << 9
		headerInfo += ResponseCode
		binary.BigEndian.PutUint16(msgBuf[2:4],headerInfo)
		return msgBuf[:startID]
	}
	headerInfo += 0 << 9
	headerInfo += ResponseCode
	binary.BigEndian.PutUint16(msgBuf[2:4],headerInfo)
	// A 记录
	if 1 == QType {
		var _i int
		var i int
		for i=0;i<len(RDATA)/4;i++ {
			// answer
			binary.BigEndian.PutUint16(msgBuf[startID:startID+2],3<<14 + uint16(dnsInfo.QueryStep.QueryStart))
			startID += 2
			binary.BigEndian.PutUint16(msgBuf[startID:startID+2],dnsInfo.QueryInfo.QTYPE)
			startID += 2
			binary.BigEndian.PutUint16(msgBuf[startID:startID+2],dnsInfo.QueryInfo.QCLASS)
			startID += 2
			binary.BigEndian.PutUint32(msgBuf[startID:startID+4],0)
			startID += 4
			binary.BigEndian.PutUint16(msgBuf[startID:startID+2],4)
			startID += 2
			for _i=4*i;_i<((4*i)+4);_i++ {
				msgBuf[startID] = RDATA[_i]
				startID++
			}
		}
	}
	return msgBuf[:startID]
}
实现了简单的A记录的项目Demo: https://gitee.com/pdudo/SampleDNS2
                    
                
                
            
        
浙公网安备 33010602011771号