前段日子一直在做公司的DNS调度程序,不过由于性能比较差,方案最终废弃掉了。两个半月心血,不想白白浪费掉,于是改了改,把商业秘密相关的部分去掉,变成了一个公共的DNS服务器。其实说的简单点,就是一个可以做DNS解析和应答的程序(废话,DNS服务器不就是干这个的)。功能比较简单,只做了A地址和CNAME的解析,安全性不涉及,性能也没有测试过,因为本身是个玩具,测性能没有意义(理论上如果用pypy的话,水平一般的机器也能跑到1万以上的QPS)。本程序多处借鉴了 isnowfy 同学的程序(相关博客:http://www.isnowfy.com/introduction-to-gevent/, github:https://github.com/isnowfy/dns),在此表示敬意。
介绍一下这个程序吧。
首先,服务器的基本思想是开通一个UDP服务器接收请求,等待接收包。如果接收到的包是DNS包,那么进行DNS包的解析,在数据库中查询域名,然后构造相应的DNS应答包,最后返回。不过这种方案就是单线程的接收->解析->应答过程,效率比较低。于是我对此进行了改造:接收到的包统一放进一个缓存中,然后,开通多条协程来取数据,进行并行处理。每条协程取一个包进行解析和应答。但是根据经(xiā)验(cāi)我知道,经常访问的域名只有那么一部分,同一个域名应该返回的是同一个应答包,那么,对所有包都解析是比较白痴的。因此,我又开了另一个缓存——一个LRU缓存。关于LRU缓存的原理和用法,可以见我之前的博客 http://www.cnblogs.com/anpengapple/p/5565461.html 。这样,获取到一个DNS包之后,就可以先在LRU缓存中进行查找,发现查询过,就直接返回(之前记得替换ID),没有查询过再进行解析、应答和存入LRU缓存。
在整个这个过程中,我使用到了:①gevent用来开协程;②gevent.Queue用来当做接收包的缓存队列;③dnslib库用来解析DNS包;④pylru库用来做LRU缓存;⑤仅使用了一个简单的文本文件作为数据库。
 
整体程序流程如下:
# 0、启动UDP服务。
class DNSServer(object):
@staticmethod
def start():
# 缓存队列,收到的请求都先放在这里,然后从这里拿数据处理
DNSServer.deq_cache = Queue(maxsize=deq_size) if deq_size > 0 else Queue()
# LRU Cache,使用近期最少使用覆盖原则
DNSServer.dns_cache = pylru.lrucache(lru_size) # 启动协程,循环处理缓存队列
gevent.spawn(_init_cache_queue) # 启动DNS服务器
print 'Start DNS server at %s:%d\n' % (ip, port)
dns_server = SocketServer.UDPServer((ip, port), DNSHandler)
dns_server.serve_forever()
# 1、接收请求包,存入缓存队列。
class DNSHandler(SocketServer.BaseRequestHandler):
def handle(self):
# 若缓存队列没有存满,把接收到的包放进缓存队列中(存满则直接丢弃包)
if not DNSServer.deq_cache.full():
# 缓存队列保存元组:(请求包,请求地址,sock)
DNSServer.deq_cache.put((self.request[0], self.client_address, self.request[1]))
# 2、从缓存队列中取数据。
def _init_cache_queue():
while True:
data, addr, sock = DNSServer.deq_cache.get()
gevent.spawn(handler, data, addr, sock)
# 3、如果请求是DNS包,解析出其查询域名。
dns.header.set_qr(dnslib.QR.RESPONSE)
qname = dns.q.qname try:
dns = dnslib.DNSRecord.parse(data)
except Exception as e:
print 'Not a DNS packet.\n', e
# 4、判断是否存在于LRU缓存中。若存在,进行5;否则,进行6。
response = DNSServer.dns_cache.get(qname)

if response:
# goto 5
else:
# goto 6
# 5、获得LRU缓存中这条DNS的应答数据,将ID替换为本条DNS查询的ID,然后返回给客户端。
response[:2] = data[:2]
sock.sendto(response, addr)
# 6、从数据库中查找这条DNS的应答,封装成DNS包,存入LRU缓存,然后返回给客户端。
answers, soa = query(str(qname).rstrip('.'))
answer_dns = pack_dns(dns, answers, soa) DNSServer.dns_cache[qname] = answer_dns.pack()
sock.sendto(answer_dns.pack(), addr)
反正大概过程就是酱婶的。我在“数据库”里面加了几条数据做实验(第一条是SOA) :
 
然后测试:
dig ccc.apple.tree @dns-ip -p dns-port
得到结果,成功解析,呕液~
 
有一点需要注意,作为数据库的文本文件如果是在windows下写的,拿到linux下用,可能会出现换行符恶心人的问题。需要先使用dos2unix这个工具转换一下,或者自己写代码。具体情况和解决办法见:http://www.cnblogs.com/anpengapple/p/5664235.html
这里使用的csv文件仅仅是为了演示方便,没有任何性能及安全方面的考虑。改进可以考虑:

第一、在开启服务器时将内容全部加载到内存,这样可以去掉LRUCache;
第二、使用redis或mysql之类的数据库;
第三、注意数据的验证,例如判断ip的正则,域名的内容等等。

 
其实作为一个DNS服务器来讲,这个程序欠缺的还很多,只能作为一个模型来参考,或者说一个玩具用来玩。大概就酱吧。本身用python来做DNS服务器就是个笑话。
完整的代码我放在github上面了,地址:https://github.com/anpengapple/apple_dns,有兴趣的同学可以拿去玩,有意见的同学可以提,反正我是不会改的。吾之懒癌逾重矣。
后记:(1)我司决定放弃powerdns,改投bind的怀抱了。虽然第二季度的绩效基本上就泡汤了,但是能用上bind还是极好的。毕竟bind用的人多,就算出问题也能有个地方问问题。而且,powerdns我已经快走投无路了。
(2)最近发现有网站转载了我的几篇博客,首先还是很高兴的,说明我写的东西还是比较有用的,得到了别人的认可,但是高兴之余觉得有点不对劲,转载不通知我一声,连转载的字样都没有出现,这令我有点不满。所以声明一下本人博客目前就只有一个,地址在:http://www.cnblogs.com/anpengapple/ 以后如果开了其他博客或者微信公众号什么的,我也会在这个博客中告知。
(3)有无聊的同学可以帮我测试一下QPS,记得在数据库中添加好数据,还有用pypy来跑。测试工具queryperf的使用见:http://www.cnblogs.com/anpengapple/p/5211557.html,pypy的安装及使用见:http://www.cnblogs.com/anpengapple/p/5586678.html

用python自建一个DNS服务器的更多相关文章

  1. IP地址,子网掩码,默认网关,DNS服务器知识详解(转)

    转自:http://www.cnblogs.com/JuneWang/p/3917697.html 为了更深入的学习TCP/IP协议,最近看了不少有关资料,收集整理记录如下,以备后面的使用和方便各位学 ...

  2. IP地址,子网掩码,默认网关,DNS服务器详解

    为了更深入的学习TCP/IP协议,最近看了不少有关资料,收集整理记录如下,以备后面的使用和方便各位学习: IP地址,子网掩码,默认网关,DNS服务器是什么意思? (一)  问题解析 001.   问: ...

  3. Windows Server 2008 R2 DNS服务器迁移

    一.实验模拟环境: Zhuyu公司有一个DNS服务器,因DNS服务器比较老旧,准备迁移至新的DNS服务器上(DNS备份也可以这么操作). 旧DNS服务器: 主机名: test-zhuAD        ...

  4. 配置域从DNS服务器以及缓存DNS服务器

    一.域从DNS服务器的作用 我们在之前上一篇随笔里有提到,DNS服务器一般有三种类型,一个是Primary DNS Server(主DNS服务器),一个是Secondary DNS Server(从D ...

  5. 如何配置DNS服务器(局域网——域名指向某个IP地址)

    单击“开始”,指向“管理工具”,然后单击“DNS”,打开 DNS 管理器.   如有必要,向管理单元添加适用的服务器,然后连接该服务器.在控制台树中,单击适用的 DNS 服务器.   在“操作”菜单上 ...

  6. linux杂谈(十八):DNS服务器的配置(一)

    原文地址: http://blog.chinaunix.net/uid-29622064-id-4242123.html 1.DNS服务器简介 域名系统(英文:Domain Name System,縮 ...

  7. 检查dns服务器是否可用

    #%windir%\system32\WindowsPowerShell\v1.0\powershell.exe D:\PSScript\ERP_Production_Script\ERPRF_Upd ...

  8. Linux系统下搭建DNS服务器——DNS原理总结

    2017-01-07 整理 DNS原理 域名到IP地址的解析过程 IP地址到域名的反向域名解析过程 抓包分析DNS报文和具体解析过程 DNS服务器搭建和配置 这个东东也是今年博主参见校招的时候被很多公 ...

  9. 使用bind实现主从DNS服务器数据同步

    一.bind简介 Linux中通常使用bind来实现DNS服务器的架设,bind软件由isc(www.isc.org)维护.在yum仓库中可以找到软件,配置好yum源,直接使用命令yum instal ...

随机推荐

  1. leetcode简单题目两道(1)

    Problem: You are playing the following Nim Game with your friend: There is a heap of stones on the t ...

  2. java.net.SocketException四大异常解决方案---转

    java.net.SocketException如何才能更好的使用呢?这个就需要我们先要了解有关这个语言的相关问题.希望大家有所帮助.那么我们就来看看有关java.net.SocketExceptio ...

  3. php对图片加水印--将一张图片作为水印加到另一张图片

    代码如下: /**  * 图片加水印(适用于png/jpg/gif格式)  *  * @param $srcImg  原图片  * @param $waterImg 水印图片  * @param $s ...

  4. js验证港澳居民通行证号码是否合规

    需求:最近要做实名验证的功能,但是验证我们要验证严谨一点,参考了网上关于验证港澳居民通行证号码的代码,总结一下. 代码: function checkHKMacao(code){ var tip = ...

  5. css内容简介(层叠样式表)

    css是对网页编辑的加色,是对其功能的渲染. 根据规范每个元素都有一个display属性,每个元素都有一个------------如div元素他的默认为block. 行内元素和块级元素 块级元素会占据 ...

  6. 使用在线工具下载YouTube视频

    YouTube上面有数不尽的视频资源,很多人都想从上面下载自己喜欢的视频,但是不得其法.那么,究竟怎样从YouTube上面下载视频呢?其实,一点也不难.只要你在Google上面搜索free youtu ...

  7. thinkphp下通过页面链接传递的参数获取一次后失效

    在thinkphp下通过页面链接传递的参数获取一次后失效,ajax内部无法再次使用.想要使用必须再次用js获取其值,通过ajax传递给后台使用. 1.通过页面链接传递参数给下一页 2.可以再下一页后台 ...

  8. FLASK实现上传下载功能

    #!-*-coding=utf-8-*- # from flask import Flask # # app = Flask(__name__) # # # @app.route('/') # def ...

  9. JS里的居民们5-数组(栈)

    编码1(栈顶在最右) 练习如何使用数组来实现栈,综合考虑使用数组的 push,pop,shift,unshift操作 基于代码,实现如按钮中描述的功能: 实现如阅读材料中,队列的相关进栈.退栈.获取栈 ...

  10. vue如何将单页面改造成多页面应用

    问题描述: 手头有一个项目是使用 vue-cli 搭建的单页面应用.项目分为了管理平台和用户查看页面,用户查看页面是很简单的页面,但是在加载过程中,却加载了整个应用的打包代码,量重且影响了响应和体验. ...