初级项目_本地DNS服务器
资料参考:通过Wireshark抓包分析谈谈DNS域名解析的那些事儿 - 朱季谦 - 博客园 (cnblogs.com)
源码链接:Gintoki-jpg/Local_DNS_Server: 大二下学期根据老师要求实现一个简易DNS服务器,了解并掌握相关知识点 (github.com)
一、实验准备
1.DNS简介
1.1 DNS服务器
DNS,全称Domain Name System,即域名系统,它提供的作用是将域名和IP地址相互映射。最通俗的理解,它就像是Java里key-value形式的Map,key是域名,value是对应映射的IP地址,通过map.get(域名),可得到域名对应的IP地址。
DNS服务器也是类似域名空间树一样的树结构,依次分为根域名服务器
(知道所有的顶级域名服务器的域名和IP,最重要,它要是瘫痪,整个DNS就完蛋),然后是顶级域名服务器
(管理二级域名),其次是权限域名服务器
(负责区的域名服务器),最后是本地域名服务器
(也叫默认域名服务器),本地域名服务器离主机很近(书上说不超过几个路由器),速度很快,其实本地域名服务器本质不属于域名服务器架构。
DNS实际上是由一个分层的DNS服务器实现的分布式数据库
和一个让主机能够查询分布式数据库的应用层协议
组成。因此,要了解DNS的工作原理,需要从以上两个方面入手:
- 在实际工作中,DNS服务器是带缓存的。即DNS服务器在每次收到DNS请求时,都会先查询自身数据库包括缓存中有无要查询的主机名的ip,若有且没有过期,则直接响应该ip,否则才会按流程进行查询;而服务器在每次收到响应信息后,都会将响应信息缓存起来;
- 若在最近的DNS服务器上,无法解析到域名对应的IP地址时,那么最近的DNS服务器就会类似充当一个中介角色,帮助客户端去其他DNS服务器寻找,看看哪台DNS服务器上可以找到该域名对应的IP;
- 任何一台DNS服务器,都存储了根域名的IP地址,
根域名服务器不做解析
,更像是一位指路人;
1.2 查询方式
下面是DNS常用的两种查询方式,具体选择哪个可以自己决定:
DNS递归查询:如果主机所询问的本地域名服务器不知道被查询的域名的IP地址,那么本地域名服务器就以DNS客户端的身份(递归思想),向根域名服务器继续发出查询报文(替主机查询),不让主机自己进行查询。递归查询返回的结果或者是IP,或者报错。这是从上到下的递归查询过程
DNS迭代+递归查询:当根域名服务器收到本地域名服务器的查询请求,要么给出ip,要么通知本地域名服务器下一步应该去请求哪一个顶级域名服务器查询(并告知本地域名服务器自己知道的顶级域名的IP),让本地域名服务器继续查询,而不是替他查询。同理,顶级域名服务器无法返回IP的时候,也会通知本地域名服务器下一步向谁查询(查询哪一个权限域名服务器)……这是一个迭代过程。
1.3 DNS报文格式
DNS请求与响应的格式是一致的,其整体分为Header、Question、Answer、Authority、Additional5部分
我们也可以将DNS简单分为基础结构部分(Header)、问题部分(Question)、资源记录部分(3A)
a)Header部分
b)Question部分
Question部分的每一个实体的格式如下
c)Answer、Authority、Additional部分
Answer、Authority、Additional部分格式一致,每部分都由若干实体组成,每个实体即为一条RR
1.4 ID映射表
程序需要考虑如下两个问题:
- 多客户端并发:允许多个客户端(可能会位于不同的多个计算机)的并发查询,即:允许第一个查询尚未得到答案前就启动处理另外一个客户端查询请求(DNS报头中ID字段的作用)
- 超时处理:由于UDP的不可靠性,考虑求助外部DNS服务器(中继)却不能得到应答或者收到迟到应答的情形
对于第一个问题,UDP本身支持并发,所以只需要实现ID转换的功能即可
我们只需要借助ID转换表即可实现上述功能
2.域名解析的步骤
- 首先,会根据域名从浏览器缓存当中获取,若能获取到,直接返回对应的IP地址;若获取失败,会尝试获取操作系统本地的域名解析系统,即在hosts文件检查是否有对应的域名映射,若能找到,直接获取其映射的IP地址返回(在hosts文件里存储的域名与IP地址映射,一般都是针对IP比较稳定且经常用的,例如工作当中的一些线上开发环境或者测试环境等域名,如果是IP变化比较频繁或者是根本就不知道IP是啥的,这类情况就无法通过hosts文件进行配置获取,只能通过
网络访问DNS服务器
去获取) - 通过网络访问DNS服务器的方式获取IP首先会先去本地区域的DNS服务器找(即PC网络设置中配置的DNS服务器);
- 理论上,若在最近的DNS服务器上,无法解析到域名对应的IP地址时,那么最近的DNS服务器就会类似充当一个中介角色,帮助客户端去其他DNS服务器寻找,看看哪台DNS服务器上可以找到该域名对应的IP;
3.实验要求
设计一个DNS服务器程序,读入“域名-IP地址”对照表,当客户端查询域名对应的IP地址时,用域名检索该对照表,实现下列三种情况:
检索结果为普通IP地址,则向客户返回这个地址(即DNS服务器功能)
检索结果为IP地址0.0.0.0,则向客户端返回“域名不存在”的报错消息(即不良网站拦截功能)
表中未检到该域名,则向实际的本地DNS服务器发出查询,并将结果返给客户端(即DNS中继功能)
注意:应考虑多个计算机上的客户端同时查询的情况,需要进行消息ID的转换
二、代码剖析
这部分直接分析c代码,头文件放在最后说或者直接忽略,主要掌握每个函数的作用以及DNS服务器整体的运作流程;
参考资料(18条消息) MAKEWORD(2,2)使用_bingqingsuimeng的博客-CSDN博客
首先是main函数入口,主要进行了服务器初始化操作以及启动服务器持续监听
1 |
|
C程序按照从上到下的默认顺序进行编译,所以我们参照编译器的思维方式逐个分析main函数中使用到的函数层层深入
1.-handle_arguments()
处理⽤户shell窗口输⼊,根据参数配置本地DNS服务器
1 |
|
2.+initTable()
初始化“域名-IP 地址”对照表table内存以及cache高速缓存
1 |
|
3.+insert_to_inter()
启动DNS服务器时,读取txt文件中的url-ip映射数据,并将这些数据加入到table中
1 |
|
4.*initialize_id_table()
初始化ID表,这部分和内存机制没什么关系,与报文处理相关;
1 |
|
前面介绍过,ID表的图示如下(这只是其中一个结构体,本质上ID表是一个结构体数组)
ID表的结构定义如下
1 |
|
5.-initialize_socket()
标准的socket网络编程步骤
初始化之前先声明两个常用地址结构体,网络编程中IP地址和端口号等都是封装在一个结构体当中的;
1 |
|
初始化操作实际就是进行一些绑定
1 |
|
6.*handle_server_message()
1 |
|
7.*handle_client_message()
下面这两个部分应该是核心代码部分了,所以会分篇幅详细介绍,这里先简单展示一下相关代码
1 |
|
三、核心部分详解
1.处理来自服务器的消息
疑问:是否添加了相关的函数能够缓存来自外部服务器的响应的URL-IP映射?
有的,addDataToTable(url, ip, 1);
1.1 解析Header头部
1 |
|
1.2 解析Question实体
1 |
|
1.2.1 URL转换函数
1 |
|
1.3 解析ANSWER实体
分析来自外部DNS服务器的ANSWER,其中ANSWER|AUTHORITY|ADDITIONAL的实体均为RR(这里规定只分析ANSWER即可,因为AUTHORITY是权威/域服务器的记录,ADDITIONAL是可以被使用的附加有用信息,这些现阶段来说没用)
1 |
|
1.4 转发报文
本地DNS服务器需要将收到的来自外部服务器的响应转发
给对应的DNS客户端(代理作用的体现)
1 |
|
2.处理来自客户端的消息
这里我们还是忽略一些变量的定义,主要抽象的介绍以下本地DNS服务器如何处理来自客户端(Shell、浏览器)的请求
2.1 解析Question实体
下面直接略过请求的Header(没什么意义),获取Question中有价值的信息
1 |
|
2.2 判断报文类型
判断客户端发来的是IPV6报文还是IPV4报文,请求IPV6的报文只能中继给外部的服务器处理(咱们的DNS只做了处理IPV4的功能)
1 |
|
2.2.1 ID转换函数
1 |
|
2.3 本地查询
本地查询是指在Cache或Table中查找需要的URL-IP映射
1 |
|
2.4 *构造响应报文
Header首部包含事物ID、标志等字段
其中标志字段又分为如下若干字段
QR:响应报文值为1
……
rcode(Reply code):返回码字段,表示响应的差错状态。
当值为 0 时,表示没有错误;
当值为 1 时,表示报文格式错误(Format error),服务器不能理解请求的报文;
当值为 2 时,表示域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求;
当值为 3 时,表示名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;
当值为 4 时,表示查询类型不支持(Not Implemented),即域名服务器不支持查询类型;
当值为 5 时,表示拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答;
当然这一步是基于若在本地查询到URL-IP映射,要是本地查不到就参照1所述中转外部DNS的响应即可
1 |
|
四、演示过程
2022/7/22 14:28 刚刚在演示的时候显示socket绑定失败,猜测原因大概是端口号被占用,所以重启电脑之后重新启动了一次发现绑定成功,使用的是D:\My_code\C++\项目集合\DNS服务器演讲版\bin\Debug下的dnsrelay.exe作为演示服务器
1.启动
2.测试
2.1 中继功能
在配置⽂件中不存在www.baidu.com
对应的IP地址,故服务器进⾏中继并将从外部收到的响应报⽂ 转发给客户端
2.2 屏蔽功能
在配置⽂件中我们将008.cn域名对应的IP地址屏蔽,故尽管能在本地DNS服务器中查询到该域名对 应的IP,但是在构造响应报⽂进⾏响应时会进⾏屏蔽处理
2.3 服务器功能
在配置⽂件中添加了sohu对应的IP地址为61.135.181.175,当客户端请求该域名对应的IP地址时服 务器可以成功进⾏响应
3.说明
从上面的测试我们可以看出基本功能是能够全部正常实现的,但是我们仔细查看返回值可能还会有一点懵逼不知道是怎么回事,这里简单做一下说明
nslookup会分别发送ipv4和ipv6的请求报文,假如都当作ipv4来处理则导致最后返回的结果都是ipv4的