zoukankan      html  css  js  c++  java
  • 使用golang构建DNS A记录 回复报文

    使用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

    添加记录

    image

    修改记录

    image

    删除记录

    image

    1.5 测试

    使用ping/nslookup/dig即可测试

    image

    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

    欢迎转发! 请保留源地址: https://www.cnblogs.com/NoneID
  • 相关阅读:
    Cocoa中对日期和时间的处理 NSDate
    Fragment(一)--Fragment用法常见问题
    Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
    # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#
    Android笔记--Bitmap(三) 针对不用Android版本的位图管理
    Java 语言中一个字符占几个字节?
    编码格式
    Volley解析(一)--Volley的使用
    Android笔记--Bitmap(二)内存管理
    Android笔记--Bitmap
  • 原文地址:https://www.cnblogs.com/NoneID/p/15519060.html
Copyright © 2011-2022 走看看