ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

事务ID错乱惊魂:esp32-c3-adblock如何根治上游转发应答串包问题

事务ID错乱惊魂:esp32-c3-adblock如何根治上游转发应答串包问题 事务ID错乱惊魂esp32-c3-adblock如何根治上游转发应答串包问题【免费下载链接】esp32-c3-adblockPi-hole-class DNS ad-blocker on a $2 ESP32-C3 (no PSRAM): 537k domains as 40-bit FNV-1a hashes in flash, binary-searched. UDP DNS sinkhole web dashboard. https://youtube.com/shorts/RaxszOUMi8E?featureshare项目地址: https://gitcode.com/GitHub_Trending/es/esp32-c3-adblockesp32-c3-adblock 是一个运行在2 美元 ESP32-C3无需 PSRAM上的Pi-hole 风格 DNS 广告拦截器它把 53.7 万个广告域名以 40 位 FNV-1a 哈希的形式存进 flash约 10 毫秒完成一次拦截判定只占约 50 KB 内存命中黑名单的域名直接黑洞掉未命中的则转发给上游 DNS 并回传应答。而在这个转发-回传环节早期版本曾出现过一个极难排查的DNS 应答串包顽疾——本文就来复盘这场事务ID错乱惊魂的成因以及它被彻底根治的完整思路。一、惊魂现场一次转发超时引发的全员串包先看清一次域名解析的路径手机/电脑 → esp32-c3-adblock → 上游 DNS默认 Quad9DNS 走 UDP每条查询都携带一个事务IDtxid应答必须带着相同的 txid 返回——这是客户端确认这封应答是回给我的的唯一凭证。惊魂的根源在于两个事实C3 上所有客户端共享同一个上游 UDP 套接字UDP 应答超时后迟到的数据包不会自动消失而是滞留在套接字缓冲区里。于是链条就断了A 的查询转发给上游后超时C3 认为失败下一位用户 B 的查询进来C3 顺手把缓冲区里 A 那条迟到应答转发给了 BB 的应答又被给了 C……正如源码注释里那句every answer shifted by one每条应答都错位了一位。用户视角的表现是某个网站随机解析到错误 IP、时好时坏、无法复现——典型的间歇性故障靠猜 IP、猜时间都查不出来。二、根治方案forwardUpstream 的四道关卡完整修复集中在 src/main.cpp 的 forwardUpstream 函数即 Issue #10 的修复可以拆成四步第 1 步转发前先清空套接字残留行李每次转发前先释放半读缓冲区再循环排空、丢弃所有上一轮遗留的旧应答main.cpp L248-L249保证发新查询之前套接字是干净的。第 2 步每次转发都换一张全新随机身份证转发出去的查询不再沿用旧事务ID而是每次用esp_random()生成一个全新随机 txidmain.cpp L251。旧应答的 txid 永远对不上新 ID即使没被清空也天然失效。第 3 步三重校验只收确实属于本次查询的应答等待应答期间main.cpp L258-L266不是来者不拒而是同时满足三个条件才接受校验项防住的风险源地址、源端口 上游 DNS防止局域网里任意设备伪造应答应答 txid 本次新生成的随机 ID证明应答确实针对我的查询问题区question section与原始查询逐字节一致最后一道保险域名问的是同一个等待策略用的是1 秒截止时间而不是重试次数main.cpp L258截止时间一到就放弃避免在慢上游上空耗也避免过早放弃。第 4 步回传前换回客户的身份证通过校验后C3 把报文头恢复成客户端自己的原始事务IDmain.cpp L267再发回去。对客户端而言这就像自己的查询得到了直接应答——中间的换 ID 转发完全透明。 一句话总结这套机制清残留 → 换新 ID → 严格对账 → 换回原 ID。四步缺一不可清残留防脏读新 ID 断旧账对账防冒牌换回保证客户端无感知。三、为什么这个修复对广告拦截器是生死线C3 挡在全网络每台设备的解析路径上答错一次就是错误的网站或连接失败用户会直接怪罪这个小白盒设备只有约 50 KB 可用内存养不起连接池、复杂状态机——清残留 随机 ID 三重对账是超低资源环境下最稳的工程解这也是所有 UDP 服务的通用教训永远不要假设套接字里只有自己的包收到任何数据都要先验证它是发给谁的。四、三分钟上手验证你的 C3 不再串包烧录固件与 blocklist 文件系统后上电打开仪表盘http://c3adblock.local把任意设备的 DNS 指向 C3 的 IP或作为二级解析器挂在主 DNS 之后用两条命令验证README.md 中 Use it 一节dig C3的IP doubleclick.net # - 0.0.0.0命中黑名单已拦截 dig C3的IP github.com # - 真实 IP转发上游应答一一对应只要被拦的域名稳定返回 0.0.0.0、放行的域名拿到正确 IPID 错乱就已经被根治。五、相关文件速查文件说明src/main.cpp固件主程序DNS 黑洞、上游转发、Web 仪表盘、OTA 与 WiFi 配网src/page.h仪表盘页面PROGMEM 内嵌 HTMLtools/build_blocklist.py拦截名单构建器hosts/域名列表/Adblock 规则 → 排序哈希表tools/test_build_blocklist.py构建器单元测试partitions.csv分区表黑名单容量与固件 OTA 的取舍platformio.iniPlatformIO 构建配置C3 SuperMini / 经典 ESP32README.md完整文档硬件、烧录、安全模型与踩坑记录✅ 打印一个 3D 外壳hardware/esp32-c3-supermini-enclosure.stl插进路由器背后空着的 USB 口就是一台免电源、免维护的家用 DNS 广告拦截器。【免费下载链接】esp32-c3-adblockPi-hole-class DNS ad-blocker on a $2 ESP32-C3 (no PSRAM): 537k domains as 40-bit FNV-1a hashes in flash, binary-searched. UDP DNS sinkhole web dashboard. https://youtube.com/shorts/RaxszOUMi8E?featureshare项目地址: https://gitcode.com/GitHub_Trending/es/esp32-c3-adblock创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进