戴云伟,沈春苗
(1.江苏省未来网络创新研究院,江苏 南京 211111;2.南京师范大学,江苏 南京 210093)
域名系统(Domain Name System,DNS)[1-2]提供了方便记忆的域名与复杂的IP 地址之间的相互映射,促进了互联网的普及与发展。互联网服务提供商(Internet Service Provider,ISP)除了提供基础的用户接入互联网的服务外,通常还会自建DNS系统,为用户提供域名解析服务[3]。应用程序在访问互联网资源之前,一般必须先进行域名解析,以获取数据中心(Internet Data Center,IDC)服务器、内容分发网络(Content Delivery Network,CDN)服务器或缓存服务器的地址;之后,再与服务器进行数据交互。
随着互联网业务日新月异,出于利益的考虑,越来越多的用户主动或者被动修改了终端设备DNS解析服务器地址,这可能会导致DNS 请求出网,即由其他ISP 完成DNS 解析服务,因此本地运营商对于这部分请求将无法管控。很显然,运营商希望拥有对DNS 的控制调度权限,从而尽量减少跨网请求,并尽可能将用户调度至本网内请求资源。这样不仅可以降低用户的解析时延,还可以减少用户跨网访问网络资源,尤其是可以减少会消耗大量带宽的图片、语音和视频类资源,因为这会导致高昂的跨网结算费用。
本文在分析传统DNS 引流技术实现及其缺陷基 础上,提出了一种基于IP透明技术(IP_TRANSPARENT)的DNS 引流方法,不仅可以提高性能,而且部署运维也相对简单。
通过重定向完成对DNS 系统的管控后,对于运营商来说,以下列出几种有实际应用意义的场景。
如果终端设备的DNS 解析服务器被设置为不法分子搭建的服务器,此时用户若访问了恶意域名,则可能会被引导至色情站点、赌博网站、钓鱼网址等[4-6]。
2018 年,黑客利用D-Link 路由器的漏洞,入侵了至少500 个家用路由器。黑客入侵后更改受害者路由器上的DNS 配置,将受害者的DNS 请求重定向到黑客自己搭建的恶意DNS 服务器上,最终诱导原本想访问正常银行网站的受害者访问钓鱼网站,并恶意窃取受害者的银行账号密码信息[7]。
对于此类攻击,ISP 通过DNS 调度技术管控域名服务后,可以拦截DNS 请求,防止用户被定向到恶意服务器,从而保护用户数据。
ISP 网内(通常包括省内和省外)有大量IDC资源,部分ISP 还会引入大量CDN 服务器和缓存服务器,用以缓存大量热门图片、音频和视频资源。当某个用户因出网DNS 请求,导致被调度到网外的情况时,ISP 可以通过DNS 引流技术将用户引入本网内IDC 资源或者缓存服务器,避免了跨网传输。这不仅可以提高用户访问速度,还可以降低ISP 网间结算费用[8]。虽然工信部于2020 年7 月起,取消了中国移动、中国电信、中国联通间的单向结算政策,实行对等互联,互不结算;但是对于其他运营商,如广电还是需要进行跨网结算[9]。
完全掌控DNS 解析服务情况下,运营商可以获取到完整的DNS 请求响应数据。通过对数据进行挖掘,不仅可以帮助运营商提高IDC 资源引入的准确度,还可以将数据用于安全分析,如郭烜臻等人提出的重绑定攻击检测方法[10],吉星等人提出的基于DNS 日志的识别异常查询[11],以及王琪等人提出的通过处理DNS服务器日志来检测DNS隧道[12]的方法等。
基于以上场景,通过实现高效DNS 引流技术来完成DNS 调度对于ISP 来说有一定的现实意义。
如图1 所示,用户将终端A 的DNS 解析服务器地址设置为C。当需要解析域名时,正常的报文路径为:
图1 重定向工作原理
(1)终端A发出报文(MacA,IpA->MacBA,IpC),其中,MacA 表示A 的网卡物理地址(Media Access Control Address,MAC)地址,IpA 表示网卡的IP,MacBA 表示策略路由器B 与A 连接的网卡MAC 地址,IpC 为非运营商DNS 服务器C 的IP 地址;
(2)策略路由器B 根据正常的路由策略会向服务器C 转发出报文(MacBC,IpA->MacC,IpC);
(3)服务器C 收到请求报文后,发出DNS 响应报文(MacC,IpC->MacBC,IpA);
(4)策略路由器B 根据正常路由策略,转发报文(MacBA,IpC->MacA,IpA),这里刚好与第(1)步对应,解析过程结束。
DNS 重定向需要向策略路由器下发重定向策略,但不同运营商或者厂家的硬件设备,下发方式和命令都不一样。在Linux 中下发重定向路由策略类似下面的命令:
该命令表示对于目的地址为IpC 且子网掩码为255.255.255.255 的报文,通过与其直连的服务器D的IpD 地址转发出去。但是该方法会将所有流量都引入服务器D,D 需要将非DNS 报文重新转发给C服务器。如此,终端A 与服务器C 无法感知重定向服务器D 的存在。重定向参与情况下,报文的路径如下:
(1)终端A发出报文(MacA,IpA->MacBA,IpC);
(2)策略路由器B 根据重定向路由策略转发出报文(MacBD,IpA->MacD,IpC);
(3)服务器D 收到请求报文后,可以转发给运营商内部DNS 解析服务器,获取结果后,发回DNS 响应报文(MacD,IpC->MacBD,IpA);
(4)策略路由器B 根据正常路由策略,转发报文(MacBA,IpC->MacA,IpA),这里刚好与第(1)步对应,解析过程结束。
通过向策略路由器下发策略,将报文引流至重定向服务器,此种情况下,报文的目的地址为非本机。以(CentOS Linux release 7.8.2003)为例,报文进入内核以后,需要通过函数ip_route_input_slow来查找路由,以下为部分代码片段:
其中,第1 918 行会进行路由查找。因为非本机目的地址的报文最终的res.type 为RTN_UNICAST,执行至1 936 行。该行相当于判断net.ipv4.ip_forward 是否为1,若为1,那么报文就会被转发;若为0,报文在此处就会被删除。正常情况下,该值默认设置为0,所以报文会被删除。
根据之前的分析,重定向首先要解决的就是这个报文会被删除的问题。下面介绍两种实现方法。
传统的实现方式一般是基于Linux 内核中的网络地址转换(Network Address Translation,NAT),它由Netfilter 和Iptables 两个独立的部件构成[13-14]。
(1)Netfilter 组件。它是Linux 内核空间的一部分,在报文流经内核网络协议栈的关键节点设置了一系列钩子(HOOK)点,在每个HOOK 节点都设置了相应的钩子函数。同时该框架为用户提供了接口,用户可以编写程序调用接口在钩子节点处注册钩子函数。当数据分组流经每个钩子关键检测节点的时候,Linux 内核会判断是否有钩子函数注册在该钩子节点处,如果有将会根据钩子函数中定义的规则对数据分组的流向做出相应的处理。
(2)Iptables 组件。它是Netfilter 框架在用户空间的体现。使用者可以在用户空间通过Iptables对Linux 内核协议栈中定义的数据分组过滤规则进行增加、修改和删除等操作。基本命令格式为:Iptables [-t 表名]命令选项[链名][条件匹配]- j 目标动作或跳转]
具体实现方式以CentOS 7 为例,执行以下命令:
该命令会在网络地址转换(Network Address Translation,NAT)表 的NF_INET_PRE_ROUTING上注册目的地址转换(Destination Network Address Translation,DNAT)目标函数。DNAT 是NAT 的一种,可以对数据包中的目的IP 地址和目的端口进行地址转换,转换之后地址为本机地址。
该命令会将所有目的地址20.1.0.2 的报文修改为30.1.0.2。Linux 内核中有关报文的处理流程为ip_rcv->NF_INET_PRE_ROUTING 链的HOOK 函数->ip_ rcv_finish->ip_route_input_slow,因此修改报文的过程是在路由之前。这样fib_lookup 函数执行后,res.type 的值为RTN_LOCAL,代码最终会执行到1 933行,成功提交至本地。这一过程依赖于连接跟踪表,对于用户数据报协议(User Datagram Protocol,UDP)协议,一次DNS 请求,连接跟踪表会新增一条条目,该条目包含16 列,如表1 所示。
表1 连接跟踪表条目信息表
报文被修改后,应用层软件就可以接受到该报文,进行解析并提供响应,但应用层发出去的报文源地址为30.1.0.2。此时,NAT 模块在内核中再次起作用,根据连接跟踪表的记录,即将源地址还原为30.1.0.2。这样终端就无法感知这一重定向的过程。
该方法的缺陷即是DNAT 本身,因为它是一个查表的过程。为了完成映射过程,内核需要维护连接跟踪表,即每个连接一个条目,通过增、删、查等操作,来实现IP 地址的转换与还原。此外,为了实现这一过程,内核还必须使用锁机制及多CPU的之间的同步,而这一操作过程会降低整体服务性能。同时,内存占用会随着链接跟踪表条目数的增多而增加。
基于IP_TRANSPARENT 的实现方法的技术优势就在于,使用策略路由表避免了NAT 过程,从而完成重定向的过程。为了实现这一目标需要解决以下3 个问题。
3.2.1 内核如何将非本机IP 的报文转送至本机
根据第3 节的描述,目的地址非本机的报文会被删除,为了将报文送至本机,使用策略路由表来实现。
如图2 所示,报文进入路由匹配过程后,先根据rule 匹配找到路由表,然后使用该路由表中的条目进行路由匹配。这里面的local 表、main 表和default 表是内核内置的。在Linux 中,函数fib_lookup 中的传递参数res.type 将决定报文的类型。根据第2 节中的第1 927 行可知,当返回的类型为RTN_LOCAL 时,报文就会被发送至本机。通过如下配置可以实现此目的:
图2 策略路由表匹配过程
第1 行表示创建新的自定义策略路由表;第2行表示对从eth0 口进入DNS UDP 报文设置标识为0x66;第3 行表示对于标识为0x66 的报文,会进行rt_test 自定义路由表的路由查询;第4 行表示对于该路由表的所有报文都属于local 类型,也就是res.type 的返回值RTN_LOCAL。
3.2.2 如何使应用层能以非源地址发送报文
默认情况下IP 路由在发送报文之前需要查找路由,中间有一个步骤就是调用__ip_dev_find 来查找源地址所属于的网卡。显然这里要发送的源地址是非本机的,如果不做特殊处理,报文会因为无法查找到所属网卡而删除。route.c 有如下代码片段:
第2 266行表示若是设置了IP_TRANSPARENT,那么就会跳过源网卡查找阶段,这就避免了报文被删除。通过如下片段即可设置IP_TRANSPARENT选项:
第3 行setsockopt 设置了IP_TRANSPARENT 选项;第7 行将非本机源地址绑定至socket。
3.2.3 如何实现源地址的保存与还原
传统的方法由NAT 帮助完成还原,而基于IP_TRANSPARENT 的方法则必须由应用层来自己完成这一还原过程。其实现方法就是先保存源地址,发送之前再还原回源地址。为了获取接受报文的源地址,需要使用recvmsg 来接受报文而不是常用的recvfrom,因为recvmsg 可以获取源地址,并保存下来。发送的时候,将接受时保存的源地址填充至struct msghdr 结构体,再使用sendmsg 函数来发送响应报文。
以Unbound 1.13.1 为例,用于接受UDP 请求报文的函数为comm_point_udp_ancil_callback,其内部就是调用的recvmsg 函数。发送DNS 响应报文使用的函数为comm_point_send_udp_msg_if,其内部调用的也就是为sendmsg 函数。
为了降低中间设备对时延的影响,测试采用两台设备网卡直连方式,网络拓扑结构如图3 所示。
图3 实验拓扑结构
两台设备均为4 核E3-1225 CPU,千兆网卡。Client 网卡的MAC 地址为00:90:11:7e:03:6a,IP 配置为10.1.0.2,测试软件为Dnsperf 2.5.2[15]。Server网卡的Mac 地址为00:70:27:f0:00:c,IP 配置为30.1.0.2,DNS 服务软件为Unbound 1.31.1[16]。为了测试,需要配置地址解析协议(Address Resolution Protocol,ARP)和路由如下所述。
(1)Client 的配置:
(2)Server 的配置:
完成以上配置后,再选择3.1 或者3.2 的配置,就可以使用dig www.nat.test @20.1.0.2 进行测试,若可以正常解析,则证明配置成功。
为了实现DNAT,服务器需要配置iptables 如下:
iptables -t nat -I PREROUTING -p udp -d 20.1.0.0/24 -j DNAT --to 30.1.0.2
基于DNAT 技术性能测试结果如下:
可以看出每秒可以处理的请求数为160 000 左右。单个请求的平均时延为612 μs。
共测试了3 次,每秒处理请求数分别为:158 605、154 992,、160 480。平均每秒处理158 025 个请求。
测试服务器的配置参考3.2 中的说明。基于IP_TRANSPARENT 技术的性能测试结果如下:
可以看出每秒处理的请求数为200 000 左右,单个请求的平均时延为469 μs。
共测试3 次,每秒处理请求数分别为:209 017、206 483、208 769。平均每秒处理208 089 个请求。
本文介绍了几种DNS 重定向应用场景,分析了常用传统实现方法,阐述了内核对于UDP 报文处理的部分流程,再结合Netfilter/Iptables、策略路由及IP_TRANSPARENT 技术提出DNS 的重定向方法。经实验验证,特定硬件设备条件下报文处理性能提升约为25%,每秒处理约50 000 个请求。
本文方法不仅性能有所提升,而且与现网广泛使用的DNAT 技术相比,由于避免了使用连接跟踪表,不但节省了额外的解析时延和内存的开销,而且不受限于连接跟踪表的容量,因此具有较高的实际应用意义和技术参考价值。