DNS网络协议初探

640 阅读8分钟

DNS协议初探

本文正在参与 “网络协议必知必会”征文活动

DNS 使用TCP还是UDP协议?

DNS 迭代查询 和 递归查询 是什么?

如何构建DNS报文?

1. 什么是DNS

​ DNS: (Domain Name System) 域名系统, 用户在使用常用网络软件,例如: 浏览器,邮件 等,但是这些服务需要指定服务器的ip地址和端口号,这个难以记忆,大家更喜欢易读、有一定含义的名字,DNS就能满足这个要求,实现将域名映射为IP地址,这个就称为域名解析。目前互联网 DNS为分布式数据库, 域名服务器分布在世界各地,每个服务器也只存储了部分域名信息。

1.1 域名层次化

​ 域名结构由标号序列组成,标点符号用点隔开, 例如: juejin.cn. www.jd.com. 可总结为: ....三级域名.二级域名.顶级域名.根域名(可省略)

截止目前为止,全球上共有13个根服务器 ,从 a - m

# yum -y install bind-utils
# dig | grep root | awk '{print $NF}' | sort
a.root-servers.net.
b.root-servers.net.
c.root-servers.net.
d.root-servers.net.
e.root-servers.net.
f.root-servers.net.
g.root-servers.net.
h.root-servers.net.
i.root-servers.net.
j.root-servers.net.
k.root-servers.net.
l.root-servers.net.
m.root-servers.net.
# 

顶级域名分类:

  • 国家顶级域名nTLD
    • cn: 表示中国
    • us: 表示美国
    • jp: 表示日本
    • ...
  • 通用顶级域名gTLD
    • com: 表示工商企业
    • net: 表示网络提供商
    • gov: 政府专用
    • ...
  • 基础结构域名
    • arpa

1.2 域名服务器分类

​ 域名服务器以区为单位,可以分为: 1. 根域名服务器 2. 顶级域名服务器 3. 权威域名服务器 4. 中间域名服务器

根域名服务器

全球共有13个根域名服务器,可通过 dig 获取。

顶级域名服务器

TLD服务器,负责管理该顶级域名下的二级域名。

权威域名服务器

负载一个区的域名服务器,保存该区的域名 和 IP地址映射关系。

中间服务器

不属于如上三种服务器,就是中间域名服务器。

1.3 通过实例来判断服务器分类

本次查询,DNS服务器信息如下

  • 本地域名服务器: 61.139.2.69

  • 根域名服务器: a.root-servers.net (198.41.0.4#53)

  • 顶级域名服务器: g.dns.cn (66.198.183.65#53)

  • 权威域名服务器: vip4.alidns.com (47.113.183.36#53)

利用dig追踪日志如下

dig +trace 可以追踪请求

# dig +trace juejin.cn. @61.139.2.69 

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.7 <<>> +trace juejin.cn. @61.139.2.69
;; global options: +cmd
.                       28351   IN      NS      a.root-servers.net.
.                       28351   IN      NS      b.root-servers.net.
.                       28351   IN      NS      c.root-servers.net.
.                       28351   IN      NS      d.root-servers.net.
.                       28351   IN      NS      e.root-servers.net.
.                       28351   IN      NS      f.root-servers.net.
.                       28351   IN      NS      g.root-servers.net.
.                       28351   IN      NS      h.root-servers.net.
.                       28351   IN      NS      i.root-servers.net.
.                       28351   IN      NS      j.root-servers.net.
.                       28351   IN      NS      k.root-servers.net.
.                       28351   IN      NS      l.root-servers.net.
.                       28351   IN      NS      m.root-servers.net.
;; Received 228 bytes from 61.139.2.69#53(61.139.2.69) in 3 ms

cn.                     172800  IN      NS      c.dns.cn.
cn.                     172800  IN      NS      g.dns.cn.
cn.                     172800  IN      NS      b.dns.cn.
cn.                     172800  IN      NS      ns.cernet.net.
cn.                     172800  IN      NS      e.dns.cn.
cn.                     172800  IN      NS      f.dns.cn.
cn.                     172800  IN      NS      a.dns.cn.
cn.                     172800  IN      NS      d.dns.cn.
cn.                     86400   IN      DS      57724 8 2 5D0423633EB24A499BE78AA22D1C0C9BA36218FF49FD95A4CDF1A4AD 97C67044
cn.                     86400   IN      RRSIG   DS 8 1 86400 20211211170000 20211128160000 14748 . gM8pprqRpkmDpcu6kNU5kffmW1jo9dmT/CjK7g9dUH4F2purVO1Txiyr RszAgzMWe7HmxeLLEN1s0p2vxbQvQ0uQZn7DMA5eJWbNf/rINyT6vmMK BndvUuTJ74wnEkiXY8Cviim597TEFWl5w7Z9Bn0FwM2nzt5OHBDben24 Fca3vbIXK3Q2n1cDbpO01We/VbiUrgcxlNAxm68wC8gWwLypFNFDXw5V tHVwwX4NsKG1si6n5lyuKramPj+GM9YV/htDNSZKzjEyHTrp/lfwQoxX Eju+cOhmFvnnSFRLS+9EyVmil+i822M2QyVbLhmwjkW8pdER+RxXd+pv vrm6gA==
;; Received 700 bytes from 198.41.0.4#53(a.root-servers.net) in 223 ms

juejin.cn.              86400   IN      NS      vip3.alidns.com.
juejin.cn.              86400   IN      NS      vip4.alidns.com.
3QDAQA092EE5BELP64A74EBNB8J53D7E.cn. 21600 IN NSEC3 1 1 10 AEF123AB 3QM14FQ32F1CJFTP8D3J5BCTNP5BIELO NS SOA RRSIG DNSKEY NSEC3PARAM
3QDAQA092EE5BELP64A74EBNB8J53D7E.cn. 21600 IN RRSIG NSEC3 8 2 21600 20211225190654 20211125181736 38388 cn. B735xzQYqVTspNxPes9yYW+EnnN1GuaKhRhI4UuowLiDdRqwrk1I/+3F aqzWerS2SUO+nhzcSzl+NeiIwBBuOjyh/NgF15e1WBHvc8PR2cG3HXSo rD3usJlX1rlaOZH6EP5k/VkMSktveAeJo3foOF104UXuljG0yRQqp+4v sh0=
DEFPMSCATA3DEUN2HJMIGDHN15FBH1AM.cn. 21600 IN NSEC3 1 1 10 AEF123AB DGCV05BN5EJCBB4F86S87BCR5CJOA3IC NS DS RRSIG
DEFPMSCATA3DEUN2HJMIGDHN15FBH1AM.cn. 21600 IN RRSIG NSEC3 8 2 21600 20211225184833 20211125181738 38388 cn. ghEDLlzom97T/4SL7znLbN5PKD0qyInh6VgcDw6dZPmoDy7eDd0Gk1Vw VoSSnn7dBp7AK8zEkAygKg28QiNFgPlWDlUtM37R3H3OGqfnJRiJ7R85 n/HSMchAunBtMgkSrtmXUVryzUm5inyi/FxH2iVc4fgGQZlLNmZr5BG+ lqQ=
;; Received 577 bytes from 66.198.183.65#53(g.dns.cn) in 274 ms

juejin.cn.              600     IN      CNAME   juejin.cn.w.cdngslb.com.
;; Received 75 bytes from 47.113.183.36#53(vip4.alidns.com) in 46 ms

# 

解析请求图大致如下

image.png

1.4 DNS递归解析和迭代解析

递归查询: 在进行查询时,若没有被查询的域名信息,则进行代理查询,直至查询到IP地址/或者是查询失败 , 然后返回给本地域名服务器。

迭代查询: 不会进行代理查询请求,而是将结果或者是下一跳域名服务器返回给本地域名服务器。

图示

迭代查询

image.png

递归查询

image.png

2. DNS报文解析

参考 rfc1035 datatracker.ietf.org/doc/html/rf…

请求报文

image.png

响应报文

image.png

2.1 DNS 整个报文格式

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+

2.2 Head: 请求头

                                    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 查询/响应编号

作为 查询 和 应答标识ID

QR: 1bit 报文类型

0: 查询

1: 响应

Opcode: 4bit 查询的类型

0: 标准查询

1: 反查询

2: 服务器状态请求

3-15: 暂时闲置

AA: 1bit 权威应答

1: 是

0: 否

TC: 1bit 超出最大允许的长度(UDP包为512字节)

UDP 包为 512 字节,若返回报文超过512个字节,则需要将状态标志位 TC 置为1 然后将报文返回给客户端,客户端收到后会再次使用 TCP 进行查询请求

1: 超出最大长度,截断

0: 正常

RD: 1bit 期望递归查询

1: 需要递归查询

0: 不需要递归查询

RA: 1bit 可用递归 , 用于响应报文中

1: 支持递归

0: 不支持递归

Z: 3bit

保留

RCODE: 4bit 响应代码

0: 没有错误

1: 格式错误

2: 服务器故障

3: 名称错误

4: 不支持请求类型的查询

5: 服务器拒绝查询

6-15: 备用

2.3 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: 表示查询的域名

每个标签由一个8字节的长度 和 响应的字符组成

例如 juejin.com

在报文中QNAME应该展示为

6 j u e j in 3 c o m 0

image.png

QTYPE: 16bit 请求的类型

1: A

2: NS

3: MD

4: MF

5: CNAME

6: SOA

7: MB

8: MG

9: MR

10: NULL

11: WKS

12: PTR

13: HINFO

14: MINFO

15: MX

16: TXT

QCLASS: 16bit 请求的方式

1: 一般网络请求

2: CSNET

3: CHAOS

4: Hesiod

2.4 Answer 响应报文

                                    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                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

**NAEM: 16bit **

资源记录域名 , 采用 2个byte 来表示 , CO 为固定标记位,代表后面的值为偏移量

    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    | 1  1|                OFFSET                   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

TYPE 16bit

RDATA资源类型

CLASS: 16bit

请求类型

TTL: 32bit

缓存时间,为0则不能缓存

RDLENGTH: 16bit

RDATA字节数

RDATA: RDLENGTH byte

资源记录值

3. DNS golang 代码案例

3.1 获取请求报文

获取/生成A记录报文Demo 代码放在了 gitee 上 gitee.com/pdudo/Sampl…

代码

package main

import (
	"fmt"
	"gitee.com/pdudo/SampleDNSTool"
	"log"
	"net"
)

func main() {


	var dnsInfo SampleDNSTool.DNSInfo

	udpconn ,err := net.ListenUDP("udp",&net.UDPAddr{
		IP: net.IPv4(0,0,0,0),
		Port: 53,
	})
	
	if err != nil {
		log.Fatal("listen udp error" , err)
	}

	for {
		buf := make([]byte,1024)
		n , err := udpconn.Read(buf[:])
		if err != nil {
			log.Println("udpconn error " , err)
		}

		dnsInfo.GetHeader(buf[:n])
		dnsInfo.GetQuestion(buf[:n])

		fmt.Println("DNS 查询ID: " ,dnsInfo.Header.ID ,
			"HeaderStatus:" , dnsInfo.Header.HeaderStatus ,
			"QCount:" , dnsInfo.Header.QCOUNT,
			"Qname:" , dnsInfo.QueryInfo.QNAMEString ,
			"QTYPE:",dnsInfo.QueryInfo.QTYPE ,
			"QCLASS:",dnsInfo.QueryInfo.QCLASS)
	}
}

启动服务器 并且 使用nslookup模拟请求

# go build
# ./testDNSQuery

客户端

# nslookup juejin.com 127.0.0.1
;; connection timed out; no servers could be reached

# 

程序输出

# ./testDNSQuery
DNS 查询ID:  27284 HeaderStatus: {0 0 0 0 1 0 0 0} QCount: 1 Qname: juejin.com QTYPE: 1 QCLASS: 1
DNS 查询ID:  27284 HeaderStatus: {0 0 0 0 1 0 0 0} QCount: 1 Qname: juejin.com QTYPE: 1 QCLASS: 1
DNS 查询ID:  27284 HeaderStatus: {0 0 0 0 1 0 0 0} QCount: 1 Qname: juejin.com QTYPE: 1 QCLASS: 1


生成响应报文

修改代码

package main

import (
    "fmt"
    "gitee.com/pdudo/SampleDNSTool"
    "log"
    "net"
)

func main() {

    var dnsInfo SampleDNSTool.DNSInfo

    udpconn ,err := net.ListenUDP("udp",&net.UDPAddr{
        IP: net.IPv4(0,0,0,0),
        Port: 53,
    })

    if err != nil {
        log.Fatal("listen udp error" , err)
    }

    for {
        buf := make([]byte,1024)
        n , fromudpaddr , err := udpconn.ReadFromUDP(buf[:])
        if err != nil {
            log.Println("udpconn error " , err)
        }

        dnsInfo.GetHeader(buf[:n])
        dnsInfo.GetQuestion(buf[:n])

        fmt.Println("DNS收到请求 , 查询ID: " ,dnsInfo.Header.ID ,
            "HeaderStatus:" , dnsInfo.Header.HeaderStatus ,
            "QCount:" , dnsInfo.Header.QCOUNT,
            "Qname:" , dnsInfo.QueryInfo.QNAMEString ,
            "QTYPE:",dnsInfo.QueryInfo.QTYPE ,
            "QCLASS:",dnsInfo.QueryInfo.QCLASS)

        // 构建回复报文
        if (1 == dnsInfo.QueryInfo.QTYPE) {
            ip := make([]byte,4)
            ip[0] = 192
            ip[1] = 168
            ip[2] = 111
            ip[3] = 129
            res :=  dnsInfo.GenerateAnswers(buf[:n],ip,0,1)

            //udpconn.Write(res)
            udpconn.WriteToUDP(res,fromudpaddr)
        }
    }
}

利用nslookup查看效果

# nslookup juejin.com 127.0.0.1
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
Name:   juejin.com
Address: 192.168.111.129
;; connection timed out; no servers could be reached

var code = "df7cfb57-4300-4caa-a3bc-3a489833b53c"