使用golang构建DNS A记录 回复报文

举报
pdudo 发表于 2021/11/08 10:05:53 2021/11/08
【摘要】 使用golang构建DNS A记录 报文关于DNS协议,参考如下看到一篇比较好的博客,推荐一下https://yangwang.hk/?p=878官方文档https://datatracker.ietf.org/doc/html/rfc1035DNS A记录UDP 报文 协议实现: https://gitee.com/pdudo/SampleDNSTool实现了简单的A记录的项目Demo: ...

使用golang构建DNS A记录 报文

关于DNS协议,参考如下

看到一篇比较好的博客,推荐一下

https://yangwang.hk/?p=878

官方文档

https://datatracker.ietf.org/doc/html/rfc1035

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界面

http://ip:5001 即可

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 维护域名信息

显示记录
image.png

添加记录

image.png

修改记录

image.png

删除记录
image.png

1.5 测试

使用ping/nslookup/dig即可测试
image.png

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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。