Python3 简易DNS服务器实现

news2025/5/19 1:25:23

使用Python3开发一个简单的DNS服务器,支持配置资源记录(RR),并能通过dig命令进行查询。

让自己理解DNS原理

实现方案

我们将使用socketserverdnslib库来构建这个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

注意事项

  1. 端口权限:DNS服务器需要使用53端口,在Unix-like系统上需要root权限。

  2. 防火墙设置:确保防火墙允许UDP 53端口的传入连接。

  3. 系统DNS缓存:测试时可能需要清除本地DNS缓存:

    • macOS: sudo killall -HUP mDNSResponder
    • Linux: 取决于发行版,可能是systemd-resolve --flush-caches
  4. 性能考虑:这个实现是单线程的,对于高负载环境,可以考虑使用多线程或异步IO。

  5. 安全性:生产环境应考虑添加DNS查询限制、防止DNS放大攻击等安全措施。

这个实现提供了基本的DNS服务器功能,你可以根据需要扩展更多记录类型(MX, NS, SOA等)或添加更复杂的逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377371.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Win32 API】 lstrcmpA()

作用 比较两个字符字符串(比较区分大小写)。 lstrcmp 函数通过从第一个字符开始检查,若相等,则检查下一个,直到找到不相等或到达字符串的末尾。 函数 int lstrcmpA(LPCSTR lpString1, LPCSTR lpString2); 参数 lpStr…

(C语言)超市管理系统 (正式版)(指针)(数据结构)(清屏操作)(文件读写)

目录 前言: 源代码: product.h product.c fileio.h fileio.c main.c 代码解析: 一、程序结构概述 二、product.c 函数详解 1. 初始化商品列表 Init_products 2. 添加商品 add_product 3. 显示商品 display_products 4. 修改商品 mo…

NAT转换和ICMP

NAT nat原理示意 nat实现 ICMP ICMP支持主机或路由器: 差错或异常报告网络探寻 2类icmp报文: 差错报告报文(5种) 目的不可达源抑制--拥塞控制超时&超期--TTL超时参数问题--问题报文丢弃重定向--不应该由这个路由器转发&a…

【专利信息服务平台-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…

BUUCTF——web刷题第一页题解

共31题,admin那题没有,因为环境问题,我做的非常卡 目录 极客大挑战 2019]Havefun [HCTF 2018]WarmU [ACTF2020 新生赛]Include [ACTF2020 新生赛]Exec [GXYCTF2019]Ping Ping Ping [SUCTF 2019]EasySQL [极客大挑战 2019]LoveSQL [极…

哪个品牌的智能对讲机好用?推荐1款,能扛事更智能

在专业通信领域,智能对讲机早已突破传统设备的局限,成为集通信、调度、数据传输于一体的智能化终端。面对复杂多变的作业环境,用户对设备的稳定性、通信效率和智能化水平提出了更高要求。但是,市面上产品同质化严重,部…

【Win32 API】 lstrcpyA()

作用 将字符串复制到指定的字符串缓冲区。 函数 LPSTR lstrcpyA(LPSTR lpString1, LPCSTR lpString2); 参数 lpString1 类型:LPTSTR 一个缓冲区,用于接收由 lpString2 参数指向的字符串的内容。 缓冲区必须足够大才能包含字符串,包括终止…

Vue3——Watch侦听器

目录 手动指定监听对象 侦听ref对象 侦听ref对象中的某个属性 reactive写法 watchEffect 自动侦听 多源侦听 一次性侦听器 watch 是⼀个⽤于观察和响应Vue响应式系统中数据变化的⽅法。它允许你指定⼀个数据源(可以是 响应式引⽤、计算属性、组件的属性等&#xf…

Go的单测gomock及覆盖率命令

安装gomock: go get github.com/golang/mock/gomockgo get github.com/golang/mock/mockgen 使用 mockgen 生成 mock 代码: 参考 mockgen -sourceservice/user.go -destinationservice/mocks/mock_user_service.go -packagemocks go test -coverprofilecoverage.out…

Leetcode209做题笔记

力扣209 题目分析:想象一个窗口遍历着这个数组,不断扩大右边界,让r。往窗口中添加数字: 此时我们找到了这个窗口,它的和满足了大于等于target的条件,题目让我求最短的,那么我们就尝试来缩短它&…

Suna: 开源多面手 AI 代理

GitHub:GitHub - kortix-ai/suna: Suna - Open Source Generalist AI Agent 更多AI开源软件:发现分享好用的AI工具、AI开源软件、AI模型、AI变现 - 小众AI Suna 是一个完全开源的 AI 助手,可帮助您轻松完成实际任务。通过自然对话&#xff0c…

25-05-16计算机网络学习笔记Day1

深入剖析计算机网络:今日学习笔记总结 本系列博客源自作者在大二期末复习计算机网络时所记录笔记,看的视频资料是B站湖科大教书匠的计算机网络微课堂,每篇博客结尾附书写笔记(字丑见谅哈哈) 视频链接地址 一、计算机网络基础概念 &#xf…

12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建

文章目录 一、如何实现一条用例,实现覆盖所有用例的测试1、结合数据驱动:编辑一条用例,外部导入数据实现循环测试2、用例体:实现不同用例的操作步骤对应的断言 二、实战1、项目路径总览2、common 文件夹下的代码文件3、keywords 文…

动态IP赋能业务增效:技术解构与实战应用指南

在数字化转型加速的今天,IP地址作为网络通信的基础设施,其技术特性正深刻影响着企业业务架构的效率与安全性。动态IP(Dynamic IP)作为互联网资源分配的核心机制,早已突破传统认知中的"临时地址"定位&#xf…

【Java ee初阶】http(1)

HTTP 全称为“超文本传输协议”,由名字可知,这是一个基于文本格式的协议,而TCP,UDP,以太网,IP...都是基于二进制格式的协议。 如何区别该协议是基于哪种格式的协议? 形如这种协议格式&#xf…

day18-数据结构引言

一、 概述 数据结构:相互之间存在一种或多种特定关系的数据元素的集合。 1.1 特定关系: 1. 逻辑结构 2.物理结构(在内存当中的存储关系) 逻辑结构物理结构集合,所有数据在同一个集合中,关系平等顺…

我开源了一个免费在线工具!UIED Tools

UIED Tools - 免费在线工具集合 最近更新:修改了文档说明,优化了项目结构介绍 这是设计师转开发的第一个开源项目,bug和代码规范可能有些欠缺。 这是一个功能丰富的免费在线工具集合网站,集成了多种实用工具,包括 AI …

什么时候可以开始学习深度学习?

咱们先来聊聊机器学习和深度学习的关系~ 这个问题其实挺常见的,之前我也跟不少同事、同学聊过。最近有好几个同学也聊过。 简单说,深度学习是机器学习的一个子集,两者不是并列关系,而是“包含”关系。 你可以这么理解&#xff…

初学python的我开始Leetcode题8-5

提示:100道LeetCode热题-8-5主要是二叉树相关,包括三题:路径总和 III、二叉树的最近公共祖先、二叉树中的最大路径和。由于初学,所以我的代码部分仅供参考。 前言 二叉树完结撒花~ 下一次的图论会是一些有趣的应用案例~ 提示&am…

构建RAG混合开发---PythonAI+JavaEE+Vue.js前端的实践

写在前文:之所以设计这一套流程,是因为 Python在前沿的科技前沿的生态要比Java好,而Java在企业级应用层开发比较活跃; 毕竟许多企业的后端服务、应用程序均采用Java开发,涵盖权限管理、后台应用、缓存机制、中间件集成…