用python自建一个DNS服务器

前段日子一直在做公司的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

转载于:https://www.cnblogs.com/anpengapple/p/5664500.html

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用 Python 编写一个 DNS 服务器,你可以使用 `dnspython` 库来处理 DNS 请求和响应。以下是一个简单的示例: 首先,确保你已经安装了 `dnspython` 库,可以使用以下命令进行安装: ``` pip install dnspython ``` 然后,创一个 Python 脚本(例如 `dns_server.py`),并使用以下代码编写 DNS 服务器: ```python import dns.message import dns.resolver import dns.query from dnslib import DNSRecord, QTYPE def handle_dns_request(data): request = DNSRecord.parse(data) qname = str(request.q.qname) qtype = request.q.qtype # 查询真实的 DNS 服务器 resolver = dns.resolver.Resolver() response = resolver.query(qname, qtype) # 构 DNS 响应 dns_response = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q) for answer in response: rrtype = QTYPE[answer.rdtype] dns_response.add_answer(RR(qname, getattr(QTYPE, rrtype), rdata=RDMAP[rrtype](answer.to_text()))) return dns_response.pack() # DNS 服务器地址和端口 server_address = '127.0.0.1' server_port = 53 # 创 UDP socket 并监听指定地址和端口 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((server_address, server_port)) print(f"DNS server is running on {server_address}:{server_port}...") while True: data, addr = sock.recvfrom(1024) # 处理 DNS 请求 response_data = handle_dns_request(data) # 发送 DNS 响应 sock.sendto(response_data, addr) ``` 这个示例使用了 `dnspython` 和 `dnslib` 库来处理 DNS 请求和响应。它接收来自客户端的 DNS 请求,并将其转发到真实的 DNS 服务器上进行查询,然后将查询结果返回给客户端。 请注意,上述示例仅提供了一个基本的框架,你可能需要根据实际情况做出适当的修改和扩展。例如,添加错误处理、缓存功能等。 运行上述脚本后,你可以将你的计算机的 DNS 设置为 `127.0.0.1`,以便使用你编写的 DNS 服务器进行域名解析。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值