使用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
- 点赞
- 收藏
- 关注作者
评论(0)