海思 Hi3798MV100 C 语言 DNS 广告过滤方案

海思 Hi3798MV100 C 语言 DNS 广告过滤方案

原创 本文遵循CC 4.0 BY-SA版权协议

声明:本项目为独立开发的技术原型,仅用于嵌入式网络编程学习研究;项目和AdGuard及其官方产品无任何从属、衍生关系。Adblock仅指代通用过滤规则语法格式。

本文介绍基于C语言从零实现轻量级DNS广告过滤器,专为海思Hi3798MV100等低算力ARMv7嵌入式设备优化。参考主流Adblock风格DNS过滤产品的业务模型,拆分为26个模块,采用桩化开发、逐模块接入策略,在x86环境验证后交叉编译至armhf平台。关键技术包括异步上游DNS、Adblock语法过滤引擎、线程安全规则热更新及JSON Web API。实测显著降低CPU与内存开销,规避Go语言实现版本在老旧硬件上的GC抖动与OOM问题。

代码放在:https://gitee.com/aiguodexiaolou/adg-c-demo 大框架已经拉起来了,属于毛坯原型,大家可以根据自身需求继续完善细节。 C语言移植性强,可适配多种嵌入式设备,因此选择原生C作为开发语言。 本工程为C语言独立原型实现,并非基于任何第三方Go源码复制或二次衍生开发。 ⚠️重要说明:本项目属于技术原型 Demo,是独立开发的轻量DNS过滤实现,和AdGuard官方产品无关。配套 C 源码正在整理,仓库会持续更新。

当前广告规则硬编码写在源码内部,如果修改过滤规则需要重新编译程序; 后端 JSON API 完整可用,机顶盒端不自带网页管理后台,直接访问 ip:8080 返回404;网页仪表盘需要在本地电脑运行配套脚本使用; 仅适配海纳思 HiNAS Ubuntu20.04 hi3798mv100 armhf;不兼容普通 OpenWrt 固件。

本文记录一次完整的实战工程:在海思Hi3798MV100老机顶盒上部署同类Go语言DNS过滤程序时,频繁出现死机重启问题。排查后判断是Go运行时在低算力嵌入式设备资源开销过大,因此采用原生C语言开发DNS广告过滤核心,交叉编译部署到机顶盒,接管家庭DNS完成局域网广告过滤。所有技术细节均经过真机验证,适合对DNS、网络编程、嵌入式交叉编译感兴趣的读者。文末附源码开源说明。

目录

前言:Go版本在老机顶盒遇到的问题 一、项目背景与目标 二、工程架构:业务模型拆分为 26 个模块 三、编译演进:x86 先跑通,再交叉编译 四、关键技术点与端到端验证 五、交叉编译 ARM:全程踩坑实录 六、部署到海思盒子 七、规则管理 八、踩坑清单(完整汇总) 九、经验总结 十、后续规划 十一、源码开源:完整工程包发布 十二、作者署名与发布小贴士

前言:Go版本在老机顶盒遇到的问题

老电视盒子Hi3798mv100普遍为ARMv7架构,单核/双核A9,内存只有512MB‑1GB,eMMC闪存性能弱,内核版本老旧。直接运行Go语言开发的DNS过滤程序会出现典型故障现象:空载运行还算稳定,局域网设备变多、DNS查询流量上涨,CPU持续占满,温度快速飙升,触发温控降频,最后整机死机重启。

Go 在弱硬件上面的痛点

  1. GC垃圾回收抖动:Go的三色标记并发GC,在内存紧张环境会频繁触发扫描,瞬时抢占CPU,功耗升高、温度上涨,最终触发死机重启。
  2. Runtime自带内存开销:Go静态编译二进制自带完整runtime、goroutine调度器,程序启动就会预分配大量内存,内存开销显著高于C语言DNS程序。
  3. Goroutine调度开销:goroutine并发模型在多核服务器表现优秀,但在单核弱CPU环境,大量协程调度会带来额外消耗;这类程序同时处理DNS解析、广告过滤、日志统计,并发请求上来后调度开销会被进一步放大。

注意区分:Go语言本身可以正常交叉编译ARMv7,不是编译器不兼容;是runtime运行时开销,在CPU、内存极度受限设备上难以承受。大量Hi3798机顶盒用户部署这类Go版DNS过滤程序都遇到过"空载没事,流量上来就死机重启",和本次现象完全吻合。

C语言实现带来的改善

C没有runtime、没有自动垃圾回收,内存全部手动管理:

  • 编译产物二进制体积小巧,启动无额外内存预分配;
  • 不存在GC停顿、GC抢占CPU的问题;
  • 可以精细管控内存分配、连接池,按需申请、按需释放;
  • GCC编译器对老旧ARMv7指令集优化成熟,对老硬件更加友好。

补充:成熟C实现的dnsmasq也广泛用于OpenWrt机顶盒,但dnsmasq缺少Adblock高级过滤语法、查询统计这类增值能力。

额外容易忽略的死机诱因(多重因素叠加)

  • eMMC存储IO瓶颈:Hi3798内置闪存性能差,程序持续写查询日志,高频磁盘IO拉高CPU、加剧发热;很多用户部署时没有关闭查询日志。
  • 内核与驱动缺陷:老盒子内核版本老旧,网络、USB驱动不稳定;程序跑在USB外置存储时,USB链路异常也会造成进程卡死,表象类似CPU故障。
  • OOM内存溢出:Go版本内存占用高,内存耗尽触发Linux OOM killer,杀掉进程,极端情况整机卡死;C实现内存可控,大幅降低OOM概率。
  • 硬件温控限制:老机顶盒散热简陋,CPU负载一高温度快速触达阈值,硬件看门狗直接重启;C实现降低平均负载,温度下降,死机现象随之消失。

折中备选方案(不想从零开发C工程)

  1. dnsmasq‑full + shell脚本自动更新hosts规则,纯C原生,资源占用极低;缺点缺少Web面板,过滤能力弱。
  2. stubby(C实现DoT上游) + dnsmasq组合,实现加密DNS+本地域名过滤。
  3. Go版本裁剪编译:编译时关闭统计、查询日志、WebUI,减少goroutine与内存开销,仅能缓解问题,无法消除Go runtime本身开销;高压力场景依旧会GC抖动,适合临时救急。

一、项目背景与目标

1.1 我们要做什么

目标非常直接:不使用这类Go语言实现的DNS过滤程序,改用C语言从零实现DNS广告过滤核心,交叉编译后部署到海思 Hi3798MV100 机顶盒,接管家庭DNS,实现局域网DNS广告过滤。

📌重要:本为原型Demo,不支持开箱即用网页后台,仅提供JSON接口,网页仪表盘需要本地运行脚本。

1.2 硬性约束(贯穿全程)

约束 说明
原生 C 工具链 编译必须用 gcc/cc,禁止 g++,禁止包装/解释层
工程修复红线 禁止修改业务模块代码,只允许改工程配置 / CMake / 黏合层 / 桩(stub)
不买额外硬件 不购买 TTL 串口线等调试设备,盒子控制走 SSH / Web / ttyd 免线通道

1.3 运行环境

  • 编译机:VMware Kali Linux(x86_64,192.168.118.129,用户 kali/kali123)
  • 目标机:海思盒子 hi3798mv100,armv7l(32 位 ARM),Ubuntu 20.04 海纳思 HiNAS 系统(192.168.3.150,root/ecoo1234)
  • 家庭路由器:华为 AX3 Pro(PPPoE 拨号,DHCP 下发 DNS)

二、工程架构:业务模型拆分为 26 个模块

参考主流Adblock风格DNS过滤产品的业务逻辑,拆分为M01~M26模块,整体分为6层。

模块 名称 层级 作用
M01 core_util L0 基础设施 核心工具库(内存、字符串、arena内存池)
M02 logging L0 日志模块
M03 threadpool L0 线程池与任务队列
M04 lru_cache L0 通用 LRU 缓存库
M05 event_loop L1 传输层 事件循环适配层(libuv)
M06 http_client L1 HTTP(S) 客户端(libcurl)
M07 tls_mgr L1 TLS 证书管理
M08 dns_msg L2 DNS 协议 DNS 消息编解码
M09 net_util L2 网络工具库
M10 dns_server L3 核心服务 DNS 监听与请求处理链(核心)
M11 filter_engine L3 过滤规则引擎(支持Adblock语法)
M12 rule_updater L3 规则列表更新器【未接入主程序】
M13 upstream_mgr L3 上游 DNS 客户端管理(异步转发)
M14 dns_cache L3 DNS缓存(命中/写回)
M15 access_control L3 客户端访问控制
M16 dns64 L3 DNS64(IPv4/IPv6 转换)
M17 querylog L4 业务模块 查询日志
M18 stats L4 统计(总请求/拦截/缓存命中/TopN)
M23 config L5 管理配置 配置管理
M24 web_api L5 Web 后台API(仅JSON接口,无静态网页分发)
M25 app L6 应用层 应用装配与生命周期
M21 ip_hijack - 源码残缺未接入

说明:M19~M26中间缺失编号模块,在原型演进中逐步补齐;核心业务链路以M10 dns_server为骨架,形成完整DNS请求处理链。 源码包内adg_c/下按M01‑M26建立对应目录,每个目录存放.c/.h源码、CMakeLists、单元测试tests,未完成模块保留stubs桩文件。

三、编译演进:x86 先跑通,再交叉编译

3.1 桩化最小编译闭环

优先保证工程可以编译出最小可执行二进制,不追求完整功能。 未完成模块使用stubs桩实现,CMake编译列表移除真实模块源码; 补齐头文件、宏、缺失符号; appmain.c移除未完成模块的初始化、销毁逻辑;

编译:

复制代码
cd build && make -j4

使用dig验证53端口应答,桩模式返回固定测试IP。 编译入口固定为 cd /home/kali/adg_c/build && make -j4,工程根目录无顶层Makefile。

3.2 逐模块接入真实代码

每个模块统一接入流程,降低变更风险:

  1. 备份原有桩文件,替换为真实业务源码;
  2. 编写mXX_compat桥接兼容层,完成桩契约与真实API转换;
  3. CMake移除stub,加入真实源码+compat桥接层;
  4. 增量编译,执行模块自带单元测试。

3.3 业务链接入主程序

M10 责任链 DNS完整请求处理链: precheck / special / access / filter / cache / upstream / stats / querylog / cache_store

过滤引擎 node_filter_request调用fe_check_request执行过滤; 支持Adblock语法:||domain^子域匹配、@@白名单、基础正则规则; 使用atomic快照,保证规则热更新线程安全。

统计桥接 完成桩结构体st_entry_t向真实统计结构体映射; NXDOMAIN / REFUSED应答标记为拦截; 设置DNS_REQ_FLAG_CACHE_HIT标志统计缓存命中。

异步上游DNS M13 um_resolve实现异步回调,使用DNS_CHAIN_SUSPEND挂起请求,等待上游DNS应答,不阻塞事件循环。

Web API 说明 ⚠️仅实现JSON接口:/stats、/querylog、/health;没有实现静态文件分发,访问根路径/返回404。网页dashboard.html为独立静态文件,机顶盒程序不会自动对外提供。

四、关键技术点与端到端验证

4.1 验证方法

dig验证DNS解析与拦截

复制代码
dig @127.0.0.1 黑名单域名   # 广告域名返回NXDOMAIN
dig @127.0.0.1 example.com  # 正常域名返回NOERROR

curl调用JSON接口查看统计

复制代码
curl 127.0.0.1:8080/stats
curl '127.0.0.1:8080/querylog?limit=10'
curl 127.0.0.1:8080/health

网页仪表盘:在本地电脑执行python server.py代理访问静态dashboard.html,机顶盒端无法直接打开网页面板。

4.2 实测数据

场景 结果
x86功能验证 广告黑名单返回NXDOMAIN,正常域名解析正常;stats、querylog计数完全匹配实际请求
缓存命中验证 重复dig同一域名,cache_hits计数+1
海思盒子真机部署 内置50条广告域名全部拦截;baidu、example可以正常解析

五、交叉编译 ARM:全程踩坑实录

5.1 环境准备

盒子为armv7l 32位,无法本机编译,在Kali做交叉编译。 安装交叉编译工具链

复制代码
apt install gcc-arm-linux-gnueabihf

构建sysroot环境 从ports.ubuntu.com下载focal armhf开发包,解压到/home/kali/focal-cross,包含libc、libuv、libcurl、openssl、linux‑libc‑dev内核头文件。

toolchain‑armhf.cmake

复制代码
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_FIND_ROOT_PATH /home/kali/focal-cross)
set(CMAKE_SYSROOT /home/kali/focal-cross)

5.2 三个致命坑

  1. linux/errno.h: No such file or directory ------ sysroot缺少内核头文件,补linux‑libc‑devarmhf包。
  2. libcurl/libuv未定义引用 ------ 增加链接参数:-Wl,--allow-shlib-undefined
  3. pthread符号未定义 ------ sysroot内libpthread.so是绝对路径符号链接,会加载宿主机glibc的stub;修改为相对符号链接,编译增加 -lpthread。

5.3 产物确认

复制代码
file adg_c_dns

输出示例:

复制代码
ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=b22572dcd4cdcfb0726132de84e1ca685dbff11e, for GNU/Linux 3.2.0, not stripped

GLIBC需求 ≤2.28,兼容Ubuntu20.04; 依赖:libpthread.so.0 / libuv.so.1 / libcurl.so.4 / libssl.so.1.1 / libcrypto.so.1.1 / libc.so.6。

5.4 编译注意事项

修改CMakeLists后必须删除build目录重新cmake;仅修改黏合层可以增量编译。 后台编译命令:

复制代码
nohup bash -c "make -j4 > /tmp/build.log 2>&1; echo EXIT=$? > /tmp/exit.log" &

六、部署到海思盒子

6.1 部署步骤

SFTP上传二进制到/opt/adg_c_dns/adg_c_dns

复制代码
chmod +x /opt/adg_c_dns/adg_c_dns

创建systemd服务文件 /etc/systemd/system/adg_c_dns.service

复制代码
[Unit]
Description=adg-c-dns C语言DNS过滤原型
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/adg_c_dns
ExecStart=/opt/adg_c_dns/adg_c_dns
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target

启动并设置开机自启

复制代码
systemctl daemon-reload
systemctl enable adg_c_dns.service
systemctl start adg_c_dns.service

验证运行状态

复制代码
systemctl is-active adg_c_dns.service
ss -tulpn | grep ':53 '
dig @127.0.0.1 www.baidu.com

6.2 接管局域网DNS

路由器DHCP DNS填写机顶盒IP 192.168.3.150,局域网全部设备自动使用本DNS。

七、规则管理

广告过滤规则硬编码在黏合层appmain.c函数adg_install_filter_rules()内部,使用static const char rules\[\]存储Adblock语法规则。 ⚠️修改规则流程:修改源码数组 → 重新编译 → 上传二进制覆盖 → 重启服务。不支持运行时加载外部规则文件。 默认内置50条常见广告、追踪域名。

八、踩坑清单(完整汇总)

# 坑点 说明 / 解法
1 编译入口错用工程根目录 报"无 Makefile",必须用 build/ 子目录
2 zsh 通配符 --include=*.c 报 nomatch 改用 bash -c 或引号包裹
3 三引号字符串不能嵌套 本地生成脚本不要嵌套三引号,独立存为py文件再上传
4 接入真实上游模块后请求挂起超时 M10 dns_req_resume异步分支标志问题,临时修改pending_resume=true修复,触及业务代码修改红线
5 事件循环没有注入,IO不执行 M13需要注入主uv_loop_t实例,否则定时器、IO不会触发
6 桩代码掩盖依赖问题 stub同步模拟返回,接入真实异步逻辑后才暴露bug
7 调试手段 fprintf打印、strace、tcpdump‑i any port 53,回环接口也要抓包
8 pkill杀进程 使用pkill -x adg_c_dns精确匹配;避免‑f误杀shell脚本
9 LRU缓存容量分片 capacity会按分片均分;需要精确容量设置shard_count=1
10 统计日志时间戳缺失 querylog写入不会自动填充timestamp,上层调用时赋值
11 CMake变更必须重生成构建目录 只修改黏合层源码可以增量编译
12 sysroot libpthread符号链接 绝对路径链接会加载宿主机库,改为相对链接

九、经验总结

9.1 给复现者的建议

准备:x86 Linux编译环境 + armv7海思盒子; 开发顺序:桩最小闭环 → x86功能调通 → 交叉编译 → 真机部署; 交叉编译坑最多,对照踩坑清单可以节省大量时间; 遵守工程红线:业务模块尽量不动,改动集中在黏合层、CMake配置、stub桩。

9.2 方法论沉淀

先闭环,再完善,先用桩跑通整体流程,再逐个替换真实模块,风险可控; compat桥接隔离变更,业务代码不动,接口适配全部放在桥接层; 先提取真实函数签名,再写装配代码,避免设想API和真实代码不一致; 交叉编译三件套:正确工具链、完整sysroot内核头文件、修复符号链接; 三层验证:digDNS功能 + curl接口校验统计 + 盒子真机测试。

十、后续规划

  1. M21 ip_hijack模块补全接入;
  2. M12 rule_updater接入,实现规则运行时更新,不再需要改源码重编译;
  3. ARM平台并发压力、稳定性调优;
  4. HTTP增加静态文件分发,实现盒子端直接访问网页仪表盘;
  5. 完成aarch64交叉编译,适配更多arm盒子。

十一、源码开源:完整工程包发布

仓库地址:https://gitee.com/aiguodexiaolou/adg-c-demo

11.1 工程目录结构

  • adg_c/:顶层构建脚本,支持x86/armhf
  • CMakeLists.txt:编译配置必读
  • README.md:项目限制、编译运行依赖必读
  • LICENSE:协议声明
  • toolchain‑armhf.cmake:armhf交叉编译配置
  • cmake/:备用cmake片段
  • third_party/uthash.h:第三方哈希库
  • 黏合层/:appmain、兼容桥接代码
  • webui/:dashboard.html + server.py本地调试脚本
  • deploy/:systemd示例服务文件
  • M01‑M26:全部模块源码,保留stubs桩、tests单元测试

11.2 编译使用指引

x86本机编译:

复制代码
cd adg_c
mkdir build && cd build
cmake .. && make -j4

armhf交叉编译:

复制代码
apt install gcc-arm-linux-gnueabihf
# 提前准备好focal-cross sysroot
cd adg_c
mkdir build_focal && cd build_focal
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-armhf.cmake .. && make -j4

编译产物:adg_c_dns。 二进制上传盒子,参考第六章systemd配置部署;网页面板只能本地电脑运行server.py。

11.3 重要工程限制(必读)

  • M12 rule_updater未接入;广告规则硬编码,修改规则需要重新编译;
  • M21 ip_hijack残缺,部分模块stub桩实现;
  • 暂不支持DoH / DoT、DHCP服务;
  • HTTP仅JSON接口,机顶盒端没有网页管理后台;
  • 仅适配 armhf Ubuntu20.04海纳思HiNAS;不兼容OpenWrt;
  • 原型Demo,不建议直接用于7×24小时生产环境。

11.4 开源协议 声明

本工程为C语言独立原型实现,并非基于AdGuard官方Go源码复制或二次衍生开发; 源码使用MIT协议; 第三方依赖:libuv(MIT)、libcurl、OpenSSL(Apache‑2.0)、uthash(BSD); 本文档文字:CC‑BY‑NC‑SA 4.0 国际协议,转载需要署名、禁止商用、衍生作品同协议。


相关推荐
疯狂成瘾者1 小时前
海康威视 WSS 视频取流原理:6014/559 端口、Nginx 代理与 TLS 证书问题
运维·nginx·音视频
ruxshui1 小时前
Hadoop 生产运维实战手册
大数据·运维·hadoop
科技小E1 小时前
模型训完≠能用:深度学习推理工作站DLTM推理验证如何把住“最后一公里”质量关
服务器·深度学习·自动化
张文君2 小时前
Ubuntu iSCSI CHAP认证与Win11挂载
linux·运维·ubuntu
程序员-Benothing2 小时前
Linux防火墙配置教程:iptables、firewalld、nftables、ufw
linux·运维·服务器
Black蜡笔小新2 小时前
中央厨房配餐看不见风险高?EasyCVR可视化监管方案设计
运维·安全·easycvr
木雷坞2 小时前
Docker Compose端口映射排查:用port和inspect定位网页访问失败
运维·docker·容器
龙亘川2 小时前
电子印章安全审计管理业务建模:从证书密钥、安全策略到审计预警的全链路闭环
运维·人工智能·安全·智慧城市·开源软件·数据可视化
瑞克-桑切斯4 小时前
企业生产环境:https部署+nginx高可用(业务不中断)
linux·服务器·数据库