代码工匠

Walking The Long Road.

blackhole开发日记II

2013-2-17

时隔一个月,再次迎来更新!

过年的时候产生了一个想法,解决DNS劫持和DNS污染的问题:

收集一些可靠DNS服务器列表,使用同一个本地端口向这些服务器挨个发送请求,同时监听这个端口的返回包。拿到第一条返回包作为结果返回,并且继续监听,收集所有返回包,做为判定DNS劫持和DNS拦截的依据,最终将正确结果缓存。实现的方式是NIO,复用DatagramChannel。

经过测试,gfw在实现DNS污染的同时,不会丢弃掉回包,因此此方案是可行的,而且比起UDP转TCP的方式,理论上速度会快很多。


2013-2-25

成功完成了blackhole在windows下的配置。后来网上搜到有人做过差不多的工具AntiDnsPollutionhttp://xijie.wordpress.com/2011/05/29/%E5%87%A0%E4%B8%AA%E9%98%B2%E6%AD%A2dns%E6%B1%A1%E6%9F%93%E7%9A%84%E6%96%B9%E6%B3%95/,还比这个简单。有点失望,安安心心做好工作才是王道啊。

以后技术方面还是要多积累才行。不过blackhole项目还是有自己的优势的,低延迟、支持缓存、可持久化、可定制都是其优点。好好把任务完成吧。


2013-4-2

一次故障排查的经过,详解操作系统DNS重试机制

出现故障

昨天下午的时候,发现微博有人私信我。原来是一个用户(应该是在oschina上找到了BlackHole),在公司内网使用了BlackHole作为内部DNS Server(因为BlackHole配置比较简单嘛),然后发现,在大规模访问量下,会出现浏览器破页的现象,而且越来越严重。

当时第一反应很开心,自己鼓捣的这个东西确实派上了用场。后来跟他沟通,与我偶尔遇到的情况是一样的:浏览网页的时候偶尔会有DNS解析不到,使用nslookup结果无问题,但是在终端下ping显示找不到host。使用sudo killall -HUP mDNSResponder刷新系统缓存后,该请求解析正常。

初步检查

怀疑是操作系统缓存了错误的结果。因为BlackHole在实现的时候有一个trick的技巧:在转发请求的时候,因为发送和返回的设计是异步的,所以需要一个key来将请求和响应对应起来。因为使用了Map结构,所以担心条目太多导致内存泄漏,所以直接使用了DNS头的ID作为key,这个ID是一个16位的整数,空间足够小,不用担心泄漏问题。但是特殊情况下,如果ID冲突,甚至可能发生返回错误的响应的问题。

但是后来进行了尝试,对某个请求,返回伪造的相应体,并分别尝试了question区伪造和answer区伪造两个方法。但是发现,操作系统能够检查出错误的响应体并进行过滤,然后再重试!于是,这个假设的故障其实是不存在的。

按部就班

感觉“蒙”的方法总是不准确,还是老老实实按部就班的来吧。个人觉得找bug最好的方法就是重现并记录现场,比没头苍蝇乱找,或者一遍一遍看代码靠谱多了。发现chrome自带了一个工具net-internel可以查看DNS缓存情况,打开之后发现,确实有部分DNS出现了error: -105 (ERR_NAME_NOT_RESOLVED)的错误。

因为错误情况非常少且跟输入无关,靠debug是行不通了,只能靠log。在程序中将一个DNS query从接收到转发、返回都打印了log。经过测试发现,原来某些请求返回了空结果!

原因是我模仿Servlet的ServletContext机制,使用了一个ThreadLocal来记录一些状态,其中就有一条是:ServerContext.hasRecord(),表示是否已经存在答案体。结果这个设计并未经过仔细推敲,里面存在一个重大问题:ThreadLocal是单个线程对应的上下文,而我在主方法中使用了线程池ThreadPoolExecutor,而实际上线程池的线程是复用的!也就是说我这次请求,会拿到另外请求的上下文,所以有些请求本来没有记录,却当作有记录,结果返回了空响应!而且因为这个ThreadLocal变量没有清理机制,所以后来会有越来越多的空响应。

而DNS server返回空响应(有完整的header和question)也就相当于说,我正常处理了这条请求,但是这个domain是没有数据的,你下次别查了。操作系统的甄别能力是有限的,遇到这个空响应,它就傻傻的相信了,并且缓存了起来。

至此故障成功定位。解决方案:去掉这个半成品的ServerContext,改为参数传递。改天详细研究一下Servlet的上下文保存机制才行。

总结:操作系统DNS缓存及重试机制

经过这次排查,也发现了操作系统DNS的一些机制:

  • 操作系统能够在一定范围内识别不正确的DNS响应(DNS头ID、question区、answer区name错误),并进行重试。

  • 操作系统会缓存空记录。

  • 某些浏览器(例如chrome)会在DNS返回空记录的情况下,让缓存立即过期。这相当于浏览器级别的重试机制。浏览器是能够清除操作系统缓存的。

经过这番折腾,BlackHole之前一直存在的一个问题也算是解决了。有人把它应用到企业内网,还是挺令人鼓舞的。

Add a comment