基于Libevent转发的内网端口暴露(四):添加DNS代理的功能

  之前所说,这应当是这个项目最后一个功能了,到此我觉得个人PC端上网的解决方案都弄完了。
sshinner

  其实,国内上网环境可以用极度恶劣来形容:DNS投毒、域名劫持、网页串改等,这种环境如果在不走代理的情况下想安心点上网,那么干净的DNS和HTTPS算是可以在基本面上得到一些保证。其实国家一直号召宽带中国、光纤入户,但实际让大家的感受就是上网变得越来越慢,越来越难。
  之前个人上网的DNS使用的是dnscrypt-proxy的解决方法,在Linux和Windows平台下都可用,其原理就是在本地建立DNS代理服务器侦听#53号端口,然后把本机的DNS服务器设置成127.0.0.1,代理服务一旦接受到DNS解析请求,就会向远程主机请求解析结果,然后返回给本地。毫无疑问,这个过程中的数据是加密传输的(用的叫什么libsodium库,不过寡人还没怎么听说过),以此保证你得到的DNS解析结果是干净的。当然个人感觉这也会有缺点,比如大流量网站都会有CDN做优化,根据你访问地址帮你解析到更快的服务器IP,而DNS代理的方式估计享受不到这种优化吧,其利弊全靠个人取舍。
  然后我又有想法了:其实大部分人用8.8.8.8,8.8.4.4这种国外知名的DNS服务器就可以了,但是你在国内直接这样设置到电脑上,由于UDP协议的特性,解析速度和返回的结果都得不到保证。为此,那么我侦听本地#53端口,把本地所有DNS请求的UDP数据包内容进行封装加密发送到国外服务器,远端服务器解析后再将结果加密返回回来,本地代理程序解密后再转成UDP包返回给客户端不就行了???

  好,这个逻辑十分简单,流程也十分清晰,在此简单描述一下:
  1. SRV先启动,CLIENT_DAEMON启动后连接SRV进行认证;
  2. CLIENT_DAEMON建立UDP套接字并绑定侦听本地DNS请求端口(#53是特权端口,需要root权限),然后建立对应struct event EV_READ事件侦听daemon_ev;
  3. CLIENT_DAEMON连接SRV,发送HD_CMD_DNS请求,SRV将其分配到对应的线程池处理;
  4. 线程池处理请求,建立对应的bufferevent,同时建立UDP socket,并建立对应的struct event EV_READ事件侦听srv_ev,然后向CLIENT_DAEMON发送HD_CMD_DNS_ACT激活;
  5. 客户端收到DNS解析请求后,记录PORT和RequestID对应关系,然后将请求加密后用TCP发送到SRV;SRV接受到数据后,向DNS服务器(8.8.8.8:53)发送该请求;srv_ev异步接收到数据后,加密后返回给CLIENT_DAEMON;
  6. CLIENT_DAEMON解密数据,获取头部的RequestID,通过查表找到本地的请求端口,将数据通过UDP方式返回;

  此外,在实现上,需要几点说明的是:
  1. 无论国外的互联网环境多么的好,我们都假定DNS的解析是耗费时间的,所以这个过程必定是要异步处理的;
  2. 虽然Libevent含有evdns的库,但是用处跟我们是不一样的,我们只需要转发解析结果就好了,而evdns对结果进行了解析和处理(还记得sockets5代理那里描述的么),所以此处不会用到这个模块;
  3. 当初DNS设计选用UDP可能就是效率方面的考虑,所以这里在DAEMON和SRV之间建立一个专用的TCP长链接,TCP三次握手对于一个DNS请求来说开销有点大,而且如果像之前sockets5代理那样每个请求都拆建一次,那效率肯定会惨不忍睹的;
  4. UDP通信简单的,但是程序处理的流程就复杂一些。目前在客户端记录requestID/Port的方式进行转发的。然后在参考文献中还看到一个有意思的东西,最初的BIND和MS Windows NT都是用的可预测的requestID,每次请求都是之前的requestID+1,那么网络嗅探就可以截获DNS请求或返回包,获取requestID然后就可以预测接下来的requestID进行攻击了(由于DNS是分布式的,所以这种攻击不仅针对客户端,还可以针对本地DNS服务器攻击)。不过现在的requestID都是随机的,下面记录的结果也说明了这一点。目前我这种简单的requestID-Port映射单机没什么问题,如果扩展到局域网为其它多主机做DNS代理,还需要额外的工作才行。

  运行效果图:
  果真现在requestID是随机产生的了
端口记录

  客户计算机请求DNS解析的效果
运行效果图


  暂时写的比较乱,欢迎指正!

本文完!

参考