DNS 报文结构和个人 DNS 解析代码实现——解决 getaddrinfo() 阻塞问题
实际应用中发现一个问题,在某些国家/ 地区的某些 ISP 提供的网络中,程序在请求 DNS 以连接一些服务器的时候,有时候会因为 ISP 的 DNS 递归查询太慢,导致设备端认为 DNS 超时了,无法获取服务器 IP。
给用户的解决方案是:请不要用 ISP 自动分配的 DNS server,改用 8.8.8.8 就解决了。
但是让用户这么配置太麻烦、也太不友好了。于是我就思考:能不能自己实现 DNS 服务,当 ISP 的 DNS 请求超时或者失败的时候,就从内部直接向 8.8.8.8 请求 DNS 信息,可以不?
如果要使用 gethostbyname()
和 getaddrinfo()
来解决这个问题的话,方案是将 /etc/resolve.conf
修改了。但这并不是正确的办法,因为这种改法一来不准确,二来会影响系统其他 DNS 请求。可行的方案是:自己构建 DNS 请求,并且自己解析获得我们需要的 IP 信息。
本文地址:https://segmentfault.com/a/1190000009369381
Reference
DNS 这样一个在网络互联中算是一个比较简单的协议,实现我如此简单的需求,居然没有哪个参考资料能够覆盖我需要的知识点……
我自己也进行了抓包,抓包的时候,建议不直接向权威的 DNS server 发送请求,而是向网关、路由器等提供 DNS 中继的服务器发,这样可以获得比下面最后一个参考资料更多的信息。
《用 TCP / IP 进行网际互联(第五版)——原理、协议与结构(第五版)》,Douglas E. Comer
《计算机网络(第5版)》,Andrew S. Tannenbaum, David J. Wetherall:男神塔能鲍姆教授!
DNS Protocol
DNS Reference Information:有各种 type 的说明
Domain Name System (DNS) Parameters:有各种参数的总集合
DNS Name Notation and Message Compression Technique
RFC-1035
对 DNS 报文的理解
DNS message解析:这篇文章也挺仔细地说明了 DNS 报文结构,图形控可以看
利用 WireShark 进行 DNS 协议分析
DNS 基本概念
简要整理一些和本文相关的点:
DNS 的本质是发明了一种层次的、基于域的命名方案,并且用一个分布式数据库系统加以实现。DNS 的主要作用是将主机名映射成 IP 地址。
DNS 解析的发起端一般是互联网 Server / Client 模型中的 client 端(以下称 client 端,指的就是发起 DNS 解析的一端),现在大部分的 C 语言 client 端都使用 getaddrinfo()
实现。以前一般用 gethostbyname()
因为一些原因不再推荐使用了,并且也只支持 IPv4。
DNS 解析中,DNS server 开放的端口应当是 53 端口。当 client 端作出请求时,server 返回的不仅仅是 IP 信息,还包含于该域名相关联的资源记录。
仅仅从一个域名 URL 中,我们不能区分这是一个域名还是某个对象(主机)名。域名的总长度应小于等于 255 个字节
,域名的每一段则必须小于等于 63 字节
。
DNS 报文格式
DNS 请求的格式和响应格式差不多,就不单独讲了。从 UDP 数据包的正文部分算起,DNS 报文的结构按顺序如下:
数据类型 | Ethereal 里的名字 | 说明 |
---|---|---|
uint16_t
|
Transaction ID | 标识符。下文说明 |
uint16_t
|
Flags | 参数。下文说明 |
uint16_t
|
Questions | 询问列表的数目 |
uint16_t
|
Answer RRs | (直接) 的回答数 |
uint16_t
|
Authority RRs | 认证机构数目(仅响应包里有) |
uint16_t
|
Additional RRs | 附加信息数目(仅响应包里有) |
variable | Queries | 请求数据的正文。请求包中只有这个。响应包也会附上原本的请求数据 |
variable | Answers | 响应数据的正文 |
variable | Authortative name servers | 域名管理机构数据 |
variable | Additional records | 附加信息数据 |
- Transaction ID:这是由 client 端指定的标识数据,DNS server 会将这个字段原样返回,client 端可以用来区分不同的 DNS 请求
- RR:Resource Record 的缩写
Flags
16 bits 的值,各部分按顺序如下(按顺序:位号、Ethereal 名称、说明):
- Bit 15,Response:0 表示查询,1 表示响应(query / response)
Bit 14~11, Opcode:查询类型——请求和响应包都适用:
0
:普通查询(最常用的)1
:反向查询2
:服务器状态请求3
:通知4
:更新(貌似是用在 DDNS 的?)
- Bit 10, Authoritative:用于响应包,判断服务器是否一个认证的域服务器
- Bit 9, Truncated:报文是否被截断了。收发包都用
- Bit 8, Recursion desired:收发包都用,表示是否需要用递归。作为 client 端,最好置 1,要不然 DNS 不执行递归查询,将有很多数据没能查到
- Bit 7, Recursion available:响应包用,表示服务器是否有能力使用递归查询
- Bit 6:这个数据段,Ethereal 说是保留位,而书中表示数据是否是鉴别的——求确认
- Bit 5, Answer authenticated:数据是否被服务器鉴定过(貌似抓到的包里都是 0)
- Bit 4, Reserved
Bit 3~0, Reply code:响应状态码,如下(参见 Micrisoft 资料 的 “DNS update message flags field” 小节):
0
:OK1
:查询格式错误2
:服务器内部错误3
:名字不存在4
:这个错误码不支持5
:请求被拒绝6
:name 在不应当出现时出现(什么鬼)7
:RR 设置不存在8
:RR 设置应当存在但是却不存在(什么鬼)9
:服务器不具备改管理区的权限10
:name 不在管理区中
资源记录(RR)的格式
每一条 RR 的格式如下:
数据类型 | Ethereal 里的名字 | 说明 |
---|---|---|
variable | Name | 资源的域名——其实前文已经出现了 |
uint16_t
|
Type | 类型。下文说明 |
uint16_t
|
Class |
大多数是 0x0001,代表 IN
|
uint32_t
|
Time to Live | TTL 秒数 |
uint16_t
|
Data length | 当前 RR 剩余部分的长度 |
variable | RR 主数据 |
如果是请求数据的话,那么 TTL、Data Length 和 RR 主数据都不需要
Type
的大部分值在 RFC-1035 中定义,此外的一些在其他文档定义(比如 IPv6)。我会用到的有:
1
:“A
”,表示 IPv4 地址2
:“NS
”,域名服务器的名字28
:“AAAA
”,表示 IPv6 地址5
:“CNAME
”,规范名,经常会有一个 CNAME 跟着一票 A 和 AAAA
域名压缩显示
这一部分直接参考的是 RFC-1035 的 “4.1.4. Message Compression”小节。
RR 中的 Name 字段,有三种表示方法(不是官方分类,而是本人自己分的):
完整域名表示
比如表示 “www.google.com” 这样一个完整的域名,需要以下16个字节:
B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | B13 | B14 | B15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
\3
|
w
|
w
|
w
|
\6
|
g
|
o
|
o
|
g
|
l
|
e
|
\3
|
c
|
o
|
m
|
\0
|
注意这里并不是把谷歌的 URL 使用简单的 char *
字符串复制上去,而是将每一段都分割开来。本例子中将域名分成了三段,分别是 www
, google
, com
。每一段开头都会有一个字节,表示后面跟着的那段域名的字节长度。最后当读到 \0
的时候,表示不再有数据了(这里和 char *
的 \0
含义有一点不同,虽然形式上是一样的)
标号表示
前文我们提到,域名的每一段,最长不能超过 63 个字节,因此在表示域名段长度的这个字节的最高两位
(0xC0
),必然是 0。这就引申出了这里的第二种用法。
这种表示法中,相当于一个指针,指代 DNS 报文中的某一个域名段。在解析一段 RR 数据段时,需要判断域长度嘛,判断的逻辑是:
- 如果最高两位是 00,则表示上面第一种
- 如果最高两位是 11,则表示这是一个压缩表示法。这一个字节
去掉最高两位
后剩下的6位,以及接下来的 8 位总共 14 位长的数据,指向 DNS 数据报文中的某一段域名(不一定是完整域名,参见第三种),可以算是指针吧。
比如 0xC150,表示从 DNS 正文(UDP payload)的 offset = 0x0150
处所表示的域名。0x0150 是将 0xC150 最高两位清零得到的数字。
混合表示
这就是上面两种的混合表示。比如说,我们假设前文表示 www.google.com
的完整域名的数据段处于 DNS 报文偏移 0x20 处,那么有以下几种可能的用法:
0xC020
:自然就表示www.google.com
了0xC024
:从完整域名的第二段开始,指代google.com
0x016DC024
:其中0x6d
就是字符m
,因而0x016D
单独指代字符串m
;而第二段0xC024
则指代google.com
,因此整段表示m.google.com
分析工具
除了 Ethereal 之外,推荐的分析工具有:
- Wireshark:抓包工具
- BIND:DNS 服务器,可以安装在你的开发环境上,用来观察和生成 DNS 响应。FTP 地址、简单教程
代码实现
代码实现在我用来研究 epoll()
的分支中,GitHub 工程在此,许可证为 LGPL。
实现逻辑上其实还是挺简单的,照着上面提到的原理实现就好了。大部分的代码和本文无关,只需要看里面的 AMCDns.c / h 文件即可。
我的这些代码可以完全代替阻塞的 getaddrinfo()
函数,甚至也可以集成到异步 I/O 库中。使用流程如下:
- 调用
socket()
创建一个 UDP 套接字并bind()
- 调用
AMCDns_GetDefaultServer()
获取系统默认配置的 DNS 服务器 - 如果不使用系统默认的 DNS 服务器,则需要使用 struct addrinfo 类型来指定。
- 调用
AMCDns_SendRequest()
请求指定域名的 IP 信息 - 调用
AMCDns_RecvAndResolve()
获取摘要的或完整的响应。 - 调用
AMCDns_FreeResult()
清除 DNS 响应数据以避免内存泄露 close()
掉 socket
DNS 报文结构和个人 DNS 解析代码实现——解决 getaddrinfo() 阻塞问题相关推荐
- DNS报文格式及抓包解析
报文结构 DNS的报文结构如下,其中黄色为基础部分,绿色为问题部分,蓝色为资源记录部分.资源记录部分只在响应包中出现. 基础部分 1.事务ID 16bit,DNS报文的标识,请求报文和对应的响应报文的 ...
- 如何突破DNS报文的512字节限制
"DNS协议大家都应该很熟悉,最近有同学问到如何获得UDP承载的超过512字节的DNS报文,借此机会,我们一起了解下DNS协议与报文长度有关的一些细节." 本文将讨论的是DNS协议 ...
- 移动和云环境下的报文传输流程--理解DNS解析、CDN资源下发、公网传输流程、数据中心网络
DNS解析 手机打开app时,首先需要解析网站的域名. 在手机运营商所在的互联网区域中,有一个本地DNS,手机向本地DNS请求解析DNS.如果本地DNS有缓存,则直接返回:如果本地DNS没有缓存,则递 ...
- 【RHCE】DNS域名解析服务器及正反向解析与主从同步简单配置
目录 域名解析服务器的介绍 因特网的域名结构 域名服务器的类型划分 DNS域名解析的过程 搭建DNS服务器 实验 实验一:正向解析 [vim /named/named.localhost] [roo ...
- 阿里DNS:用LibFuzzer照亮DNS代码的死角
1 引言 2018年11月初,国内某云解析服务提供商出现大规模服务不可用故障,在业界引起了不小的震动,以下是官方的故障复盘公告: 技术复盘中很明确地说明了此次故障的起因:大量恶意请求报文攻击触发了软件 ...
- DNS服务详解及正向解析与反向解析
现在想想,我们平时在浏览器上浏览网页时,一般是不是使用的域名去访问的呢,为什么要使用域名呢,使用域名的好处是什么呢,带着这些问题,深入思考才能真正的学好DNS. 要想了解DNS,就必须先知道域名这个概 ...
- https网络编程——HTTP协议的简介、HTTP报文结构和代理、HTTP加速解析方法(哈希加速、协议状态机)
参考:HTTP协议的简介及其工作原理等 地址:https://qingmu.blog.csdn.net/article/details/108046553?spm=1001.2014.3001.550 ...
- DNS概述和DNS服务器部署(详细正向解析)
文章目录 DNS概述和DNS服务器部署(正向解析) 一.简介 二.域名 2.1 域名层次结构 2.2 查询方式 2.3 DNS域名解析过程 三 .DNS服务器类型 四.DNS服务器部署 4.1.BIN ...
- DNS云学堂 | 快速定位DNS解析异常问题,牢记这四种DNS状态码
DNS的状态码在进行故障排查的时候起着至关重要的作用.在DNS的维护中会经常遇到DNS解析异常问题,通过DNS的状态码可以初步判断DNS解析的异常问题.本期云学堂通过详解DNS状态码的定义,给出常见状 ...
- JAVA实现报文解析:协议的数据类型和完整的报文结构解析
**JAVA实现GBT32960报文解析系列文章链接:** JAVA实现GBT32960报文解析(一):协议的数据类型和完整的报文结构解析 JAVA实现GBT32960报文解析(二):数据包结构解析源 ...
最新文章
- Markdown通用的常用语法说明
- 【转】python2与python3的主要区别
- ubuntu 安装pycharm
- python常用操作符_Python--3常用操作符
- QDoc C ++特定的配置变量
- 转载:Java编程风格与命名规范整理
- java 中方法里的参数_【Java基础】12、java中方法的参数传递机制
- 关于计算机工作的诗歌,提高计算机工作及上网效率的方法
- 如何在本机安装mysql_机器人之如何在本机安装MySQL,并配置电脑为数据库服务器...
- 图解cgroup架构中cgroup与css之间的多对多的关系
- 感受MapXTreme2004
- HBase学习笔记:HBase数据库介绍
- 我要偷偷的学Python,然后惊呆所有人(第九天)
- 字母异位词(anagram)的不同复杂度实现
- sun.net.ftp.FtpClient介绍
- 代码设置margintop_如何通过代码设置TextView的Margin参数?
- java1.8离线安装包_离线安装JDK1.8
- 堡垒机和防火墙有什么区别?
- jenkins邮件使用自定义变量
- 攻防世界-PWN-house of grey-Linux系统文件泄漏