使用python3开发一个简单的dns服务器,支持配置资源记录(rr),并能通过dig
命令进行查询。
让自己理解dns原理
实现方案
我们将使用socketserver
和dnslib
库来构建这个dns服务器。dnslib
库能帮助我们处理dns协议的复杂细节。
1. 安装依赖
首先确保安装了dnslib
库:
pip install dnslib
2. dns服务器实现代码
#!/usr/bin/env python3 import socketserver from dnslib import dnsrecord, dnsheader, qtype, rr, a, aaaa, cname, txt from dnslib.server import dnsserver, baseresolver class simpleresolver(baseresolver): """ 自定义dns解析器,包含我们配置的资源记录 """ def __init__(self): # 初始化资源记录字典 self.records = { # a记录 (域名 -> ipv4) 'example.com.': [ rr('example.com.', qtype.a, rdata=a('93.184.216.34'), ttl=60), rr('example.com.', qtype.a, rdata=a('93.184.216.35'), ttl=60) ], # aaaa记录 (域名 -> ipv6) 'ipv6.example.com.': [ rr('ipv6.example.com.', qtype.aaaa, rdata=aaaa('2606:2800:220:1:248:1893:25c8:1946'), ttl=60) ], # cname记录 (别名) 'www.example.com.': [ rr('www.example.com.', qtype.cname, rdata=cname('example.com.'), ttl=60) ], # txt记录 'txt.example.com.': [ rr('txt.example.com.', qtype.txt, rdata=txt('this is a test txt record'), ttl=60) ] } def resolve(self, request, handler): """ 处理dns查询请求 """ reply = request.reply() qname = request.q.qname qtype = request.q.qtype # 记录查询日志 print(f"received query: {qname} (type: {qtype[qtype]})") # 检查是否有匹配的记录 if str(qname) in self.records: for record in self.records[str(qname)]: if record.rtype == qtype or qtype == qtype.any: reply.add_answer(record) return reply def main(): """ 启动dns服务器 """ resolver = simpleresolver() # 创建dns服务器,监听udp 53端口 server = dnsserver( resolver, port=53, address="0.0.0.0", # 监听所有接口 tcp=false # 仅使用udp ) print("starting dns server on port 53...") try: server.start() except keyboardinterrupt: server.stop() print("\ndns server stopped") if __name__ == '__main__': main()
3. 运行服务器
由于dns服务需要使用53端口,在linux/macos上需要以root权限运行:
sudo python3 dns_server.py
4. 使用dig测试
打开另一个终端窗口,使用dig
命令测试:
# 查询a记录 dig @127.0.0.1 example.com # 输出中有 # example.com. 60 in a 93.184.216.34 # example.com. 60 in a 93.184.216.35 # 查询aaaa记录 dig @127.0.0.1 -t aaaa ipv6.example.com # 查询cname记录 dig @127.0.0.1 -t cname www.example.com # 查询txt记录 dig @127.0.0.1 -t txt txt.example.com
进阶功能
1. 从配置文件加载资源记录
我们可以改进代码,从json文件加载资源记录:
import json class configurableresolver(baseresolver): def __init__(self, config_file='dns_config.json'): self.records = {} self.load_config(config_file) def load_config(self, config_file): with open(config_file) as f: config = json.load(f) for domain, records in config.items(): self.records[domain] = [] for record in records: rtype = record['type'].upper() if rtype == 'a': self.records[domain].append( rr(domain, qtype.a, rdata=a(record['value']), ttl=record.get('ttl', 60)) ) elif rtype == 'aaaa': self.records[domain].append( rr(domain, qtype.aaaa, rdata=aaaa(record['value']), ttl=record.get('ttl', 60)) ) elif rtype == 'cname': self.records[domain].append( rr(domain, qtype.cname, rdata=cname(record['value']), ttl=record.get('ttl', 60)) ) elif rtype == 'txt': self.records[domain].append( rr(domain, qtype.txt, rdata=txt(record['value']), ttl=record.get('ttl', 60)) )
示例配置文件dns_config.json
:
{ "example.com.": [ {"type": "a", "value": "93.184.216.34", "ttl": 300}, {"type": "a", "value": "93.184.216.35", "ttl": 300} ], "ipv6.example.com.": [ {"type": "aaaa", "value": "2606:2800:220:1:248:1893:25c8:1946"} ], "www.example.com.": [ {"type": "cname", "value": "example.com."} ], "txt.example.com.": [ {"type": "txt", "value": "this is a test txt record"} ] }
2. 添加日志记录
我们可以添加更详细的日志记录:
import logging # 配置日志 logging.basicconfig( level=logging.info, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.filehandler('dns_server.log'), logging.streamhandler() ] ) class loggingresolver(baseresolver): def resolve(self, request, handler): client_ip = handler.client_address[0] qname = request.q.qname qtype = qtype[request.q.qtype] logging.info(f"query from {client_ip}: {qname} (type: {qtype})") reply = super().resolve(request, handler) if reply.rr: for answer in reply.rr: logging.info(f"responded with: {answer}") else: logging.warning(f"no records found for {qname} (type: {qtype})") return reply
注意事项
端口权限:dns服务器需要使用53端口,在unix-like系统上需要root权限。
防火墙设置:确保防火墙允许udp 53端口的传入连接。
系统dns缓存:测试时可能需要清除本地dns缓存:
- macos:
sudo killall -hup mdnsresponder
- linux: 取决于发行版,可能是
systemd-resolve --flush-caches
性能考虑:这个实现是单线程的,对于高负载环境,可以考虑使用多线程或异步io。
安全性:生产环境应考虑添加dns查询限制、防止dns放大攻击等安全措施。
这个实现提供了基本的dns服务器功能,你可以根据需要扩展更多记录类型(mx, ns, soa等)或添加更复杂的逻辑。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论