使用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等)或添加更复杂的逻辑。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论