Swoole 没有内置 dns_server 类,自定义 DNS 服务器需用 Swoole\Server(UDP 模式)手动解析/构造 DNS 报文;其与协程 DNS 客户端 lookup() 角色相反,前者为服务端,后者为客户端。

Swoole 本身没有 dns_server 这个内置类或函数,所谓“自定义 DNS 服务器”实际是用 Swoole\Server(UDP 模式)手动解析/构造 DNS 报文实现的,不是调用某个开箱即用的 dns_server 组件。
下面分几个关键点说清楚区别和实操要点:
免费 DNS 与邮件安全分析(IntoDNS.ai):包括 DNSSEC、SPF、DKIM、DMARC、MTA-STS、BIMI、SMTP STARTTLS、FCrDNS、黑名单、发件人要求及报告。
UDP Server 手写 DNS 协议 vs. Swoole 内置协程 DNS 查询
这是最常被混淆的两类用途:
- 你用 Swoole\Server 监听 53 端口、接收二进制 DNS 查询包、解析 Header/Question、拼 Response —— 这是在「实现一个 DNS 服务器」;
- 而 SwooleCoroutineDNS::lookup() 是「作为 DNS 客户端」发查询请求,走系统 DNS 或指定 upstream,返回 IP —— 它不监听端口,也不处理报文格式。
两者角色完全相反:一个是服务端(server),一个是客户端(client)。
常见错误是试图在 on('Packet') 回调里调用 lookup() 去查域名,这既没意义(你已收到原始查询)、又低效(绕路再查一次),还可能因协程上下文丢失出错。
域名编码与指针压缩必须手动处理
DNS 报文里的域名不是明文字符串,而是标签长度+标签+结尾 \x00 的二进制格式,例如 www.example.com 编码为 \x03www\x07example\x03com\x00。
如果你直接把 PHP 字符串 "www.example.com" 写进响应体,客户端会解析失败,表现为超时或 NXDOMAIN。
构建答案部分时还要注意:
- 答案中的域名字段通常复用问题部分的偏移(即“指针压缩”,如 \xc0\x0c),不能重复编码
- TTL 必须是 uint32_t 网络字节序(用 pack('N', $ttl))
- A 记录数据长度固定为 4 字节,IP 需用 inet_pton('127.0.0.1') 转成二进制
端口 53 权限与协议支持限制
Linux 下绑定 53 端口需要 root 权限或 cap_net_bind_service 能力,非 root 用户启动会报 Permission denied。
Swoole UDP Server 默认只处理 UDP 查询,但真实 DNS 服务器需支持 TCP 回退(当响应 > 512 字节或 EDNS0 启用时):
- UDP 模式无法可靠传输大于 512 字节的响应(除非启用 EDNS0,但客户端兼容性差)
- TCP 模式需额外起一个 Swoole\Server(TCP 类型),监听同一端口,解析方式完全不同(有长度前缀、需处理粘包)
- 大多数简单场景只做 UDP + A 记录响应,就别硬上 TCP,否则调试成本陡增
响应头标志位和计数字段不能填错
DNS 响应 Header 的 12 字节结构必须严格对齐,尤其以下字段:
- 事务 ID(Transaction ID)必须原样回传,否则客户端丢包
- QR 标志位(bit 15)必须设为 1(表示 Response)
- RCODE(bits 0–3)填 0 表示 NoError,填 3 表示 NXDOMAIN
- ANCOUNT(Answer Count)要准确反映你塞了几条资源记录,填 0 但后面又写了 Answer 数据,客户端直接忽略整个响应
- 最好用 unpack('n6', $header) 辅助校验字段顺序,避免手工偏移算错
真正难的不是“怎么写”,而是“怎么让其他 DNS 客户端(比如 dig、systemd-resolved、甚至 Windows nslookup)能稳定认出你的响应”。很多看似跑通的 demo,在真实网络环境里会被各种客户端静默丢弃——问题往往出在字节序、域名编码、TTL 类型或标志位组合上。

















