
简介计算机网络课程设计中的DNS服务器实验资料包面向北邮大二下学生及计算机网络初学者用于完成DNS服务器搭建与域名解析实验。资料围绕域名系统展开详细介绍DNS分布式数据库、根域到子域的层次结构以及A、AAAA、MX、CNAME、PTR等常见记录类型同时梳理递归查询与迭代查询流程、权威服务器与缓存服务器的职责分工并说明基于UDP 53端口的DNS报文交互和C语言套接字编程要点。资源共4个文件含2个txt说明文档、1个C源文件和1个头文件压缩包仅9KB结构精简但覆盖实验核心。已有166人学习查看适合作为课程设计参考或计网实验入门。读者可借助其中的配置示例、代码框架和DNS relay设置快速理解DNS查询处理逻辑也可结合dig或nslookup完成调试验证为后续网络编程与服务器开发打下基础。1. 计网课设里的 DNS 服务器实验为什么搭起来不难讲清楚却要花心思学期末总会有一批类似“BUPT大二下计网课程设计DNS服务器实验.zip”的压缩包在同学之间传来传去里面通常是报告模板、答辩 PPT 和一份转发再转发的代码。很多人以为把 zip 交上去就完事结果答辩时被问一句“你搭的这个服务器收到一个查询报文后到底做了什么”就卡住。这个课设的核心不是“装一个 DNS 软件”而是要求你亲手搭建一条完整的域名解析链路并且能讲清楚权威解析、反向解析、转发和缓存各自干了什么。这篇文章按课设的实际评分逻辑把 DNS 服务器的部署、配置、代码实现和现场演示技巧拆开讲适合正在赶课设、想拿高分而不是只求过检的同学。2. DNS 报文与解析流程实验开始前要讲清楚的三件事答辩老师最爱问的第一个问题通常是“一次 DNS 查询是怎么走的”。这个问题答好了后面所有配置命令都只是验证。答不好哪怕你 dig 结果全对分数也会打折。所以先从协议链路讲起再落到报文头部和功能清单最后给出一套适合课设的拓扑。2.1 一次浏览器访问背后的递归与迭代在浏览器地址栏里输入www.test.lab并回车时第一步其实不是发 DNS 查询。浏览器先查本地 hosts 文件——Windows 在C:\Windows\System32\drivers\etc\hostsLinux 在/etc/hosts——没命中才会往网卡配置里写的 DNS 服务器地址发包。这个 DNS 服务器地址通常由 DHCP 下发给客户端它扮演的角色是“递归解析器”替客户端把整个查询链路走完。如果这个递归解析器收到的域名恰好属于自己负责的 zone它就直接查自己的 zone 文件返回结果这个行为叫“权威回答”。如果域名在自己的 zone 里找不到它就根据配置里的forwarders把查询转给上一级 DNS拿到结果后缓存一段时间再返回给客户端。很多课设失败的原因就是把“递归服务器”和“权威服务器”这两个角色混在一起讨论。我在课设里一般画这样一条链路作为答辩图浏览器 → 客户端 resolv.conf 里的 DNS → 你的 DNS 服务器named ├→ zone 文件命中返回权威答案AA1 └→ 未命中转发给公网 DNS缓存后返回这条链路能回答“你的服务器什么时候是权威的什么时候只是中转”这个高频问题。记住一个关键点只有设置了 zone 的域名你的服务器才叫权威其他域名它只是缓存加转发。2.2 报文头部的 12 个字节QR、Opcode、AA、RD、RADNS 报文头部固定是 12 字节。前两个字节是 Transaction ID客户端用它来匹配请求和响应接下来的 Flags 字段里课设必考的几个位是位名含义课设里的判断方法QR0 表示查询1 表示响应dig 响应包中为 1Opcode0 表示标准查询通常是 0不用深究AAAuthoritative Answer权威回答用dig 127.0.0.1 www.test.lab看aa标志TCTruncated报文被截断出现时表示响应超过 512 字节RDRecursion Desired期望递归客户端发出的查询里为 1RARecursion Available服务器支持递归你的 named 开启 recursion 后为 1QNAME 的编码也是一个考点。www.test.lab在报文里不是直接 ASCII 字符串而是每个标签前面加一个长度字节03 77 77 77 04 74 65 73 74 03 6c 61 62 00。末尾的00表示根。用手抓包看到这段十六进制时能指出来说明你对协议字段是真理解而不是只会跑命令。答辩时能说出“AA1 说明这台服务器对 test.lab 拥有权威回答RA1 说明它同时支持递归”这比贴一堆配置文件截图有用得多。2.3 正向、反向、转发、缓存功能清单与实验拓扑按课设评分表拆开看DNS 服务器实验至少要覆盖 5 个功能点安装并启动 DNS 服务软件能响应dig查询配置正向 zone让www.test.lab解析到指定 IP配置反向 zone让指定 IP 能解析回域名配置转发或递归让服务器也能解析外部域名用抓包工具或dig输出验证上面每一项。实验拓扑我建议用 VMware 开两台虚拟机不要图省事在一台机器上又当客户端又当服务器。一台装 Ubuntu Server 或 UOS Server 20IP 固定为192.168.50.10专门跑 named 服务另一台做客户端IP 设为192.168.50.20网卡 DNS 指向192.168.50.10。网络模式选 NAT 或仅主机都行但别用桥接。桥接模式下宿舍路由器的 DNS 会干扰测试结果你可能查到一个“来自上级路由器的缓存回答”导致反复怀疑自己配置写错了。3. 用 BIND9 在 Ubuntu 上搭建权威 DNS 服务器named.conf 到 zone 文件的完整配置选 BIND9 而不是其他方案的理由很简单它是 Linux 上最主流的权威 DNS 实现配置文件结构清晰每一项都能对上课程设计评分表的检查点。Ubuntu 和 UOS Server 20 的安装路径几乎一样后面我会把差异指出来。3.1 安装 bind9 后的最小 named.conf.local安装命令是sudo apt install bind9装完后目录结构如下/etc/bind/named.conf是主入口它会 includenamed.conf.options、named.conf.local和named.conf.default-zones。要改的文件只有两个options 管全局行为local 管 zone 声明。先编辑/etc/bind/named.conf.local把自定义 zone 加进去// /etc/bind/named.conf.local zone test.lab { type master; file /etc/bind/db.test.lab; }; zone 50.168.192.in-addr.arpa { type master; file /etc/bind/db.192.168.50; };这段配置声明了两个 zone正向的test.lab和反向的50.168.192.in-addr.arpa。type master表示这台服务器是该 zone 的权威主服务器直接读写本地文件。反向 zone 的名字是把 IP 段192.168.50.0/24反过来写尾部加上in-addr.arpa。注意named.conf.local里 zone 名末尾不需要加点BIND9 会自动补但如果在named-checkzone命令里单独传 zone 名就需要手动补上末尾的点。这里的核心参数是file路径。BIND9 在 Ubuntu 上以bind用户运行所以 zone 文件要放在/etc/bind/下并且确保权限是644、属主是bind或root。我见过有人把 zone 文件放在/root/下结果 named 启动时读不到文件一直报permission denied。3.2 db.test.lab 正向 zone 文件与反向 zone 文件正向 zone 文件定义了域名到 IP 的映射。在/etc/bind/db.test.lab写入$TTL 600 IN SOA ns1.test.lab. admin.test.lab. ( 2025041501 ; serial 3600 ; refresh 1800 ; retry 604800 ; expire 600 ) ; negative TTL IN NS ns1.test.lab. ns1 IN A 192.168.50.10 www IN A 192.168.50.10 mail IN A 192.168.50.20第一行的$TTL 600是默认 TTL表示这条记录默认被缓存 600 秒。SOA 记录是整个 zone 的“版本说明”括号里第一行2025041501是 serial格式通常是“日期 当天修改序号”今天是 2025 年 4 月 15 日就写 2025041501如果你一天里改了两次第二次就要写成 2025041502。serial 不递增从服务器和缓存就不会刷新这是整个实验里最容易丢分的一个点。NS 记录声明了该 zone 的权威服务器是ns1.test.labns1、www、mail三条 A 记录分别映射了不同的主机名。注意所有以域名结尾的字段比如ns1.test.lab末尾都必须有圆点否则 BIND9 会把test.lab再拼上去变成ns1.test.lab.test.lab。反向 zone 文件/etc/bind/db.192.168.50写成$TTL 600 IN SOA ns1.test.lab. admin.test.lab. ( 2025041501 ; serial 3600 ; refresh 1800 ; retry 604800 ; expire 600 ) ; negative TTL IN NS ns1.test.lab. 10 IN PTR ns1.test.lab. 10 IN PTR www.test.lab. 20 IN PTR mail.test.lab.PTR 记录是反向解析的核心。“10”在这里表示 IP 最后一个八位组配合 zone 名50.168.192.in-addr.arpa就完整组成了10.50.168.192.in-addr.arpa。注意192.168.50.10 - 10.50.168.192.in-addr.arpa这个反写方向几乎每个做课设的同学都会至少在这错一次。PTR 记录的 rdata 部分同样要记得末尾加点www.test.lab.而不是www.test.lab。3.3 用 named-checkzone 和 dig 验证配置报错输出怎么看写完配置文件不要急着重启服务。先跑两个检查命令能省掉大部分排错时间sudo named-checkconf /etc/bind/named.conf sudo named-checkzone test.lab /etc/bind/db.test.labnamed-checkconf检查语法如果有括号不匹配或分号缺失它会直接报行号。named-checkzone检查 zone 文件内部逻辑比如 SOA 记录缺了字段、PTR 记录反向段写错、末尾遗漏圆点。输出OK说明基础没问题再重启服务sudo systemctl restart named重启后用 dig 验证正向解析dig 127.0.0.1 www.test.lab A short正常情况下输出192.168.50.10。如果不加short在 Answer Section 里能看到一条完整的 A 记录。反向验证用-x参数dig 127.0.0.1 -x 192.168.50.10 short输出www.test.lab.或ns1.test.lab.都算对。如果你在 UOS Server 20 上做apt install bind9包名相同唯一区别是部分 UOS 版本的 systemd 服务名可能显示为bind9.service的别名用systemctl status named和systemctl status bind9两个命令都试一下哪个有响应就用哪个看日志。4. 从零写一个迷你 DNS 服务器Python 方案与抓包验证有些学校课设要求里明确写了“实现 DNS 协议”这意味着你不能只装 BIND9 交差得有一份自己能讲解代码逻辑的实现。自己手撸完整的 DNS 二进制解析工作量大且容易出错通常做法是用dnslib库把报文解析和构造封装好自己专注实现查询逻辑。这样既能讲代码又不会在十六进制解码上耗掉整个周末。4.1 dnslib 实现支持 A 记录和 PTR 记录的最小服务器先安装依赖pip install dnslib。然后写一个 UDP 服务监听 53 端口收到查询后从请求里提取域名和查询类型在内存字典里查映射构造响应返回。完整代码如下#!/usr/bin/env python3 import socket from dnslib import DNSRecord, QTYPE, RR mapping { www.test.lab: 192.168.50.10, ns1.test.lab: 192.168.50.10, mail.test.lab: 192.168.50.20, } sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((0.0.0.0, 53)) print(mini DNS server running on :53) while True: data, addr sock.recvfrom(512) request DNSRecord.parse(data) qname str(request.q.qname)[:-1] # 去掉末尾的点 qtype QTYPE[request.q.qtype] reply DNSRecord(qr1, aa1) # 响应、权威回答标志 reply.q request.q if qtype A and qname in mapping: reply.add_answer(RR(qname, qtypeA, ttl600, rdatamapping[qname])) elif qtype PTR and qname.endswith(.50.168.192.in-addr.arpa): octet qname.split(.)[0] ptr_map {10: www.test.lab, 20: mail.test.lab} if octet in ptr_map: reply.add_answer( RR(qname, qtypePTR, ttl600, rdataptr_map[octet] .) ) sock.sendto(reply.pack(), addr)这段代码的核心在DNSRecord(qr1, aa1)。qr1把响应标志位置 1告诉客户端这是响应而不是另一个查询aa1设置权威回答标志告诉客户端“这个答案来自权威服务器”。request.q回填到响应里是为了让客户端能通过 Transaction ID 匹配到刚才发出的那个查询。RR()的三个参数要细看rdatamapping[qname]生成的是 A 记录数据ttl600与前面 BIND9 配置里的 TTL 保持一致PTR 记录构造时给域名加了末尾的圆点。这里有个细节recvfrom(512)的 512 字节缓冲区是 DNS over UDP 的传统上限如果查询响应超过 512 字节就要走 TCP 或者 EDNS0课设场景下几乎不会触发。运行需要 root 权限因为监听 53 端口属于特权端口sudo python3 dns_server.py然后另开一个终端用 dig 测试dig 127.0.0.1 www.test.lab A short看到192.168.50.10就说明迷你 DNS 服务器已经把报文完整走了一遍。如果卡住了大概率是端口被 systemd-resolved 占住先按下一章的方法处理。4.2 dig 与 tcpdump 抓包从十六进制里读出响应标志代码能跑只是第一步答辩时需要证明你理解线上报文。常用做法是用 tcpdump 把 53 端口的流量抓下来sudo tcpdump -i any port 53 -nn -vv再执行一条查询dig 127.0.0.1 www.test.lab Atcpdump 输出里会出现一收一发的两条 UDP 报文。发出那条有Flags [q]表示是一个标准查询返回那条会有Flags [q. aa rd ra]或者类似组合。这里的aa就是 Authoritative Answer对应你代码里DNSRecord(qr1, aa1)设的那个标志位ra表示服务器支持递归。如果想看最原始的十六进制报文给 tcpdump 加-X参数sudo tcpdump -i any port 53 -nn -X在输出里找03 77 77 77对照 2.2 节讲过的 QNAME 编码方式现场指给老师看03是长度为 3 的标签77 77 77是ASCII码的www。能把这个指出来比说十句“我实现了DNS”都有说服力。4.3 时间不够时的后悔药用 dnsmasq 兜底如果 deadline 只剩半天且课设没有硬性要求“手写协议解析”可以直接换 dnsmasq 方案完成功能点。它的配置比 BIND9 简单得多天然自带递归、缓存和转发能力。在/etc/dnsmasq.conf里加两行address/test.lab/192.168.50.10 address/www.test.lab/192.168.50.10重启服务后整个test.lab域都解析到192.168.50.10外部域名也能正常走系统配置的公网 DNS。这是最省事的兜底路径。但要注意dnsmasq 和 BIND9 不能同时跑在同一台机器上它们都抢 53 端口。用 dnsmasq 之前先把 named 停掉sudo systemctl stop named sudo systemctl disable named sudo systemctl restart dnsmasq这个方案作为“后悔药”是可以的但答辩时如果老师问“dnsmasq 和你手写的服务器有什么区别”你要能说出来dnsmasq 是一个完整的转发加缓存服务器你只有时间熟悉它的配置如果想展示协议理解还是要靠 4.1 那套 Python 代码。5. DNS 实验里最常见的 5 个坑从 53 端口占用到反向解析 NXDOMAIN课设做 DNS 服务器实验配置写错只占失败原因的很小一部分大部分翻车发生在环境问题。这一章把高频问题按现象到解决的方式列出来可以当作排错手册用。5.1 现象named 启动失败日志显示 Address already in use这是 Ubuntu 和 UOS 平台最典型的问题。执行systemctl start named后状态是 failed用journalctl -u named看日志里面有could not listen on UDP socket: Address already in use。原因Ubuntu 18.04 之后的 systemd-resolved 默认占用了 53 端口BIND9 抢不到。解决方法是先停掉 systemd-resolved再手动管理 resolv.confsudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved sudo rm -f /etc/resolv.conf echo nameserver 127.0.0.1 | sudo tee /etc/resolv.conf这里把 resolv.conf 指向 127.0.0.1 是把本机 DNS 交给你的 named。注意如果改成 127.0.0.1 后apt update变慢或失败通常是因为 named 的转发还没配置好先把外网域名解析问题按 5.2 解决。5.2 现象test.lab 能解析但 baidu.com 超时自定义域名正常外部域名全部 NXDOMAIN 或超时。原因通常是 named 的forwarders未配置或recursion被关闭。BIND9 默认配置里只监听本地且没写转发目标。在/etc/bind/named.conf.options里加上options { directory /var/cache/bind; recursion yes; allow-query { any; }; forwarders { 223.5.5.5; 119.29.29.29; }; forward only; };这里两个公网 DNS 的选型要注意223.5.5.5 是阿里 DNS119.29.29.29 是腾讯 DNSPod在国内网络环境下连通性比 8.8.8.8 稳定得多。forward only表示所有未命中 zone 的查询一律转给上游不自己迭代。改完重启 named再dig 127.0.0.1 baidu.com验证。如果还超时检查虚拟机出网是否正常——ping 223.5.5.5能通再排查别的。5.3 现象虚拟机里 dig 正常真机浏览器打不开服务器配置没有任何问题在虚拟机里dig 192.168.50.10 www.test.lab返回正确但回到宿主机用浏览器访问www.test.lab就是打不开。原因有三层按出现频率排第一宿主机网卡的 DNS 没有指向192.168.50.10还在用路由器下发的地址第二VMware NAT 模式的 DHCP 会给虚拟机下发自己的 DNS导致查询根本没到你的 named第三浏览器启用了 DoHDNS over HTTPS它直接走https://dns.google/dns-query这类加密通道完全绕过系统 DNS。解决手动把宿主机网卡 DNS 改成192.168.50.10浏览器地址栏输入edge://settings/privacyEdge或chrome://settings/securityChrome里关闭“使用安全 DNS”最后用nslookup www.test.lab确认系统层解析已经指向你的服务器。5.4 现象反向解析总是 NXDOMAINdig -x 192.168.50.10返回 NXDOMAIN但正向解析完全正常。这是反向 zone 配置写错的最典型表现。原因基本是三类。zone 声明写错了段IP 是 192.168.50.x反向 zone 应该是50.168.192.in-addr.arpa有人会写成192.168.50.in-addr.arpa这在实际的 DNS 树里不存在。第二类是 PTR 记录里少了最左段PTR 名称写成10.50.168.192.in-addr.arpa但 zone 文件里只写了10要确保和 zone 名拼接后完整。第三类是 PTR 记录 rdata 末尾漏了圆点。排错方法是在服务器上直接查dig 127.0.0.1 10.50.168.192.in-addr.arpa PTR如果 authority 段显示50.168.192.in-addr.arpa说明 zone 声明对了如果查询名变成10.192.168.50.in-addr.arpa之类说明反写顺序错了。还有一种情况是 dig 提示 SERVFAIL说明 zone 文件语法有问题用 3.3 节的named-checkzone 50.168.192.in-addr.arpa /etc/bind/db.192.168.50定位。5.5 现象改了 zone 文件dig 结果还是旧 IP在 zone 文件里把www.test.lab的 A 记录从192.168.50.10改成192.168.50.30重启 named 后dig 结果还是旧地址。这个坑几乎每个人都踩直接原因是 SOA 里的 serial 没递增。BIND9 不会根据文件的 mtime 自动重载 zone。每次修改 zone 文件后serial 必须比上一次大否则从服务器和缓存都会认为“zone 没有变化”继续用内存里的旧记录。正确流程是改文件 → serial 递增 →sudo named-checkzone→sudo rndc reload test.lab。rndc reload是重载指定 zone比整个重启服务更优雅答辩时说出来是加分项。养成这个习惯serial 用YYYYMMDDNN格式比如 2025041502每天改动不超过 99 次就够用。检查当前是否生效可以用dig 127.0.0.1 www.test.lab A noall answer如果服务器和客户端在不同机器客户端那边还有一层缓存dig 时加norecurse可以避免递归缓存干扰判断。6. 用 Wireshark 验证权威响应交报告前最后 10 分钟的检查习惯接近交作业时我习惯把整套实验按“协议级验证”跑一遍而不只是看 dig 输出。这个习惯帮我在答辩前发现过很多隐蔽问题。第一步是抓包留存证物sudo tcpdump -i any port 53 -w /tmp/dns.pcap然后在另一终端执行完整的查询序列正向 A 记录、反向 PTR、外部域名转发。CtrlC 结束抓包后把 pcap 传到宿主机用 Wireshark 打开。过滤表达式用dns.flags.response 1把所有响应报文过滤出来。点开其中一条www.test.lab的响应看 Flags 区域里 Authoritative Answer 的位是否为 1对应的是 2.2 节讲的 AA 标志。如果这条响应是从你的 named 或 Python 服务器发的AA 必然为 1如果是从上级缓存返回的AA 为 0。这个区别在报告里截图标注出来就是一条完整的“权威解析”证据链。第二个有用的实操是验证 TTL 递减。前面配置里 TTL 设置为 600第一次查询响应里 Answer 段的 TTL 是 600过几十秒再查一次数字会变小说明缓存机制在工作。如果 TTL 一直是 600 不变说明客户端每次绕过了缓存或者你的解析器每次都触发了权威刷新。第三个习惯是演示norecurse和recurse的区别。dig norecurse 127.0.0.1 www.test.lab A时named 只会查本地 zone查不到就直接返回空 answer不会帮你转发recurse时才会走 forwarders。答辩现场演示这个对比能让老师一眼看出你理解“递归”和“权威”的区别。我在自己的课设里吃过亏当时只会在报告上写“配置成功、解析正常”结果老师追问“RA 标志位你在抓包里见过没”我愣住了。后来每次改完 zone都先 tcpdump 抓一把再继续下一步。这个习惯保留到现在希望你也能用得上。本文还有配套的精品资源点击获取