
简介这是一款面向家长的 Windows 上网管控工具针对儿童过度上网、访问不良网站等家庭场景提供网址过滤、上网时段限制、密码锁屏、断网/关机及应用程序监控等功能既适合家庭用户直接部署也适合学习 C# 桌面应用开发的技术人员研究参考。资源包共 40 个文件约 4.11MB以 11 个 C# 源码文件为核心附带可直接运行的 EXE 程序、DLL 库、ICO 图标与 RESX 界面资源并包含项目解决方案、Manifest 清单及 xaddin 扩展配置覆盖从界面设计到网址检测、锁屏控制和定时策略的完整模块。已有 457 人学习下载代码结构清晰主程序分为锁屏、主界面与配置管理等多个窗体便于按功能点切入阅读。通过部署与源码研读读者既能快速拥有一套可用的上网控制程序也能理解家长控制类工具的核心实现逻辑、配置流程与常见排错思路进而二次开发适配自身需求。1. 上网家长控制软件先搞清楚它拦的是流量还是终端晚上十点半你发现孩子还在刷游戏视频——手机上的管控 App 早就被删了密码改了也能被猜出来。单纯装一个家长控制软件在终端上本质上是在跟孩子争夺设备的控制权而设备在孩子手里这场仗你很难赢。真正可行的思路是把管控点从终端挪到网络链路上不管孩子手里拿的是手机、平板还是电脑只要他想上网流量就必须经过你控制的那道闸门。这就是上网家长控制软件能做到的事。这份资源是一套基于策略引擎和网关 DNS 过滤的家长控制实现包含部署脚本、策略模板、日志分析工具和一份设备豁免清单模板。它解决的问题很聚焦统一管理家庭网络里所有终端的上网行为按人、按时间、按内容分类做放行或阻断。适合家里设备多、孩子有一定动手能力、单纯 App 限制已经失效的场景。接下来我会从控制点的选型逻辑讲起逐步拆到配置文件和具体踩坑点。2. 控制点选在哪从网关侧做统一过滤的实现逻辑与选型对比2.1 终端侧与网络侧两种控制点的本质差别终端侧控制就是在孩子的手机或电脑上装 App通过系统权限去拦截应用和网址。这种方式有一个绕不开的死穴管控逻辑跑在被管控的设备上权限之争永远存在。孩子可以卸载 App、关掉通知、断网重试甚至恢复出厂设置。即便你用了设备管理器权限也只是把对抗升级而不是消除对抗。网络侧控制则把决策点放到家庭网络的出口位置。终端发出的所有流量都要经过网关网关在 DNS 解析阶段就能知道这台设备想访问哪个域名再根据策略决定放行还是阻断。孩子可以改自己的设备设置但改不了网关上的策略。两种方式的差异可以看这张对比表。对比项终端侧 AppDNS 过滤透明网关过滤部署位置每台终端路由器或旁路网关链路中间被卸载绕过的难度低中高覆盖设备范围仅安装了 App 的设备所有指向该 DNS 的设备所有经过网关的设备是否支持按设备细分策略依赖于 App 实现支持支持维护成本每台设备单独配置集中维护一份策略集中维护但需关注链路稳定性这个资源选的是“旁路网关 DNS 策略”的组合控制网关并不强制所有流量穿越它而是作为家庭网络的 DNS 服务器存在。这样做的好处是部署风险小即使网关临时宕机终端还能通过主路由自带 DNS 上网不至于全家断网。代价是管控粒度停留在域名层做不到内容层的关键词过滤。对家长场景来说域名层控制已经覆盖了绝大多数需求——赌博、色情、游戏平台、短视频域名这些本来就可以按域名分类处理。2.2 策略引擎的判定逻辑谁、什么时段、访问什么一个可用的上网控制策略引擎最终要回答三个问题这台设备是谁的当前时间在不在允许时段内目标域名属于哪个分类。三个问题在一条判定链上顺序执行优先级从高到低。我用一段简化伪代码说明核心流程def decide(device_mac, domain, now): # 第一步豁免设备永远放行比如家长的手机和电脑 if device_mac in trusted_devices: return PolicyAction.ALLOW # 第二步找到这台设备绑定的策略 policy binding_policy.get(device_mac) if not policy: return policy.default_action # 第三步当前时间是否落在允许时段内 if not is_time_in_slots(now, policy.time_slots): return PolicyAction.BLOCK # 第四步域名分类匹配 category classify_domain(domain) if category in policy.block_categories: return PolicyAction.BLOCK if category in policy.allow_categories: return PolicyAction.ALLOW # 兜底未命中任何分类时走策略的默认动作 return policy.default_action这段代码的顺序是刻意设计的豁免判断在最前因为家长设备不能被策略误伤时段判断在分类之前因为非学习时段所有娱乐域名直接拦掉查询更快。classify_domain用的是后缀匹配树*.game.com这类规则会统一归到游戏分类。这里的time_slots支持按星期区分周一到周五和周末可以给不同时间段。2.3 为什么默认不做 HTTPS 解密很多人问域名过滤遇到 HTTPS 会不会失效。答案是如果只做 DNS 层的解析阻断HTTPS 流量根本到不了连接阶段就被拦了因为终端解析不到正确 IP。只有当孩子使用加密 DNS 或直连 IP 时DNS 过滤才会失效这时需要配合 SNI 层审计来兜底。这个资源包默认不做 HTTPS 中间人解密原因很实际启用解密需要在每台设备上安装根证书孩子可以拒绝安装家长要伺候多台设备的证书更新运维成本高且容易翻车。域名层控制能覆盖 90% 以上场景剩余部分交给 SNI 审计和 IP 段策略处理。这也是我认为家长控制产品一个比较理性的取舍。3. 部署一套可用的控制网关环境、拓扑与最小验证路径3.1 网络拓扑先想清楚你的网关机放哪部署前不要急着敲命令先确认网络拓扑。最稳的方式是旁路模式网关机与主路由处于同一局域网主路由的 DHCP 把 DNS 字段指向网关机的 IP。终端上网流程不变只是每次域名解析都问一下网关机。如果你想要更强的管控效果可以把网关机串在主路由和交换机之间做成透明网关。这种模式下所有流量都会经过这台机器能在网络层拦截直连 IP 的流量但部署复杂度明显提升——需要配置桥接或路由转发规则一旦网关机宕机全家都会断网。我第一次做这种项目时就在这上面翻过车所以我的建议是先用旁路模式验证策略是否合理确认没问题再考虑要不要升级成强制模式。部署模式设备改动管控强度网络故障风险旁路 DNS只需改 DHCP 的 DNS 字段域名级低透明网关串接链路域名 连接级高强制网关网关故障自动放行串接链路 脚本守护域名 连接级中3.2 部署步骤目录结构与启动脚本资源包解压后目录结构大概长这样/opt/netguard/ ├── conf/ │ ├── policy.yaml # 主策略文件 │ └── categories.yaml # 域名分类表 ├── netguard-core/ │ └── main.py # 策略服务入口 ├── netguard-dns/ │ └── dns_server.py # DNS 过滤服务 ├── scripts/ │ ├── deploy.sh # 初始化部署脚本 │ └── reload.sh # 重新加载策略 └── logs/ └── access.log # 访问日志部署脚本的核心内容大致如下#!/bin/bash set -euo pipefail BASE_DIR/opt/netguard cd $BASE_DIR echo 1/4 安装系统依赖 apt-get update apt-get install -y python3 python3-pip dnsutils curl echo 2/4 安装 Python 依赖 pip3 install -r app/requirements.txt echo 3/4 校验策略文件语法 python3 netguard-core/main.py --config conf/policy.yaml --check echo 4/4 启动 DNS 服务与策略服务 nohup python3 netguard-dns/dns_server.py --config conf/policy.yaml \ --port 53 --log logs/access.log logs/dns.log 21 nohup python3 netguard-core/main.py --config conf/policy.yaml \ logs/core.log 21 echo 部署完成DNS 服务监听端口 53--check参数会在启动前校验 YAML 格式和策略优先级配置--port 53指定 DNS 服务监听端口默认 53 不用动日志文件分流到dns.log和core.log排查问题时能分清是策略引擎异常还是 DNS 响应异常。3.3 最小验证路径先不接全家的流量部署完不要急着把主路由的 DHCP 改掉。先拿一台测试设备走最小验证路径手动把这台设备的 DNS 改成网关机 IP然后在设备上访问一个被分类为游戏的域名。# 在测试设备上用命令行或图形界面把 DNS 改为 # 网关机内网 IP例如 192.168.1.100 # 验证解析是否被阻断 nslookup game-example.com 192.168.1.100 # 预期输出Cant find game-example.com: No answer # 验证正常网站是否可访问 curl -I https://www.example.edu.cn # 预期输出HTTP/2 200这一步走通说明 DNS 过滤链路是通的。然后再去改主路由 DHCP让所有设备走网关 DNS。最小验证路径最大的价值是出问题时影响范围只限一台设备不会让全家网络陷入未知状态。我每次调试策略变更都会先这样隔离验证避免规则写错导致家里所有设备上不了网。4. 把策略文件写成可维护的规则集分类、时段与设备绑定4.1 策略文件的三个层级策略、分类、设备策略文件不建议只写一个黑名单列表那样跟运维 hosts 文件没有区别。可维护的策略文件应该分成三层顶层是策略policy中间是分类category底层是设备绑定binding。策略决定允许什么、阻断什么分类决定哪些域名属于同一个业务类型设备绑定决定哪些设备应用哪份策略。这样做的好处很直接孩子长大一点你只需要新建一个宽松一点的策略再把设备绑定改一行某个游戏平台换了域名你只要把它加进game分类所有引用这个分类的策略会同时生效。4.2 分类表域名后缀匹配与优先级规则categories.yaml是域名分类表资源包里的示例结构如下version: 1 categories: game: display_name: 游戏 domains: - game-example.com - *.game-center.com ip_ranges: - 203.0.113.0/24 video_stream: display_name: 短视频与直播 domains: - video-example.com - *.short-video.com gambling: display_name: 赌博 domains: - gambling-example.com ip_ranges: - 198.51.100.0/24域名匹配采用后缀最长匹配原则*.game-center.com会匹配该域名下的所有子域名ip_ranges用于处理那些不经过域名解析、直接通过 IP 访问的流量。分类名建议用英文小写加下划线避免中文做键名在日志统计时出现编码问题。匹配优先级上精确域名优于通配符通配符优于 IP 段。4.3 时间槽与设备绑定把策略挂到具体设备policy.yaml是主策略文件它负责把策略、分类和具体设备串起来。我常用的写法是这样的version: 1 policies: strict_child: description: 学龄期工作日严格模式 default_action: block time_slots: - days: [1,2,3,4,5] start: 08:00 end: 22:00 allow_categories: [education, reading] block_categories: [game, video_stream, social, gambling] - days: [6,7] start: 09:00 end: 22:00 allow_categories: [education, reading] block_categories: [gambling] observe_categories: [social] relaxed_teen: description: 初高中生周末宽松模式 default_action: allow time_slots: - days: [6,7] start: 10:00 end: 23:00 block_categories: [gambling] device_bindings: AA:BB:CC:DD:EE:FF: policy: strict_child description: 孩子卧室平板 11:22:33:44:55:66: policy: relaxed_teen description: 孩子作业电脑default_action: block是兜底动作未命中任何分类的域名默认阻断适合低龄孩子的设备default_action: allow则相反适合只需要屏蔽特定类型域名的场景。observe_categories是一个很实用的参数列入该分类的域名不会真正阻断但会被记录到日志用于观察新规则是否误伤。设备绑定支持用 MAC 地址精确匹配也可以改成 IP 段匹配后者适合 DHCP 分配不固定的网络。需要注意的是MAC 绑定在设备开启随机 MAC 功能时会失效这一点后面避坑章节会详细说。加载策略使用reload.sh#!/bin/bash python3 netguard-core/main.py --config conf/policy.yaml --reload--reload不会重启进程而是向运行中的策略服务发送 SIGHUP 信号让它重新读取配置文件并重建匹配树。配置文件修改后这个操作可以在秒级生效不会中断正在进行的网络请求。改完配置后我一般会立刻查一下日志确认没有因为语法错误导致策略服务进入异常状态。5. 避坑接入家庭网络后最容易翻车的五个环节5.1 现象域名在阻断列表里HTTPS 网站还是能打开原因DNS 解析被拦截后终端可能回退到系统内置的加密 DNS或 App 内部使用自定义 DNS 解析绕过了网关的 DNS 服务。DNS 过滤只能拦解析阶段拦不住直连 IP。解决在网关机上启用 SNI 审计模块从握手阶段读取目标域名。只要域名命中阻断分类直接重置 TCP 连接。具体做法在netguard-dns的配置里加一行enable_sni_filter: true。同时把ip_ranges里的游戏或赌博服务器网段也加入阻断作为第二道防线。5.2 现象孩子把设备 DNS 改成外部 DNS管控立即失效原因策略服务只监听局域网内的 DNS 请求一旦终端不再向网关发 DNS 查询域名过滤就无从谈起。解决在主路由上配置 DNS 强制转发把流向任何外部 IP 的 UDP 53 端口数据都重定向到网关机并屏蔽常见公共 DNS 的 IP 地址。同时建议在策略文件里把block_encrypted_dns设为true阻断境内常见的加密 DNS 服务。这样孩子手动改 DNS 的结果是域名解析超时而不是换一个解析通道继续上网。5.3 现象家长自己也被策略挡住网课平台被误杀原因策略文件里没有维护家长设备的豁免名单。很多家长在配置时只关注“阻断什么”忽略了“放行谁”。解决在policy.yaml里单独维护一个白名单设备组trusted_devices: - mac: AA:BB:CC:DD:EE:01 description: 家长手机 - mac: AA:BB:CC:DD:EE:02 description: 家长电脑判定顺序是trusted_devices优先于任何策略。另外很多网课平台用了通用的 CDN 域名可能被归入video_stream分类。这种误杀很难提前预判我建议对网课类域名单独建一个education分类并在严格策略的allow_categories中显式引用。新接入的网课平台未收录时改分类比临时加 IP 白名单要可靠得多。5.4 现象策略文件改了重新加载后不生效原因策略服务进程没有收到重载信号或者配置文件里的缩进格式错了被语法检查跳过。YAML 对缩进极其敏感block_categories下的两个空格和四个空格可能造成完全不同的解析结果。解决先校验再重载python3 netguard-core/main.py --config conf/policy.yaml --check python3 netguard-core/main.py --config conf/policy.yaml --reload--check如果没有任何输出说明 YAML 语法和策略结构没问题。修改配置后养成习惯把旧的策略文件备份一份防止改乱了想回退却没有后悔药。5.5 现象网关机宕机或重启全家设备无法解析域名原因终端把 DNS 指向网关机之后网关机一旦不可用所有依赖它的设备都会出现域名解析超时。轻则是网页打不开重则是所有 App 请求全部卡住。解决在部署时加一个 watchdog 脚本定时探测策略服务进程#!/bin/bash while true; do if ! pgrep -f netguard-dns /dev/null; then echo DNS 进程异常临时恢复直连上游 DNS echo nameserver 223.5.5.5 /tmp/resolv.conf.netguard # 通知主路由把 DHCP 的 DNS 改回默认值 /opt/netguard/scripts/critical_disable.sh fi sleep 30 done这个脚本做的事情是探测到 DNS 进程退出后自动执行一次全局放行让终端回退到主路由默认 DNS。风险是短暂失去管控但换来的是家庭网络整体可用。如果做透明网关模式建议在网关上再加一条硬件看门狗或常态化的健康检查接口避免人在外面无法远程处理时全家断网。6. 验证规则是否真的生效日志、灰度放行与自测清单6.1 日志字段与 Top 10 命中分析策略服务运行后logs/access.log会按行记录每一次域名判定的结果。日志格式固定为timestamp | device_mac | domain_name | category | action | policy_name 2024-11-20 21:14:33 | AA:BB:CC:DD:EE:FF | game-example.com | game | block | strict_child 2024-11-20 21:15:02 | AA:BB:CC:DD:EE:FF | edu.example.com | education | allow | strict_child用一段简单的 Python 脚本就能统计出最常触达的阻断域名from collections import Counter block_counter Counter() with open(logs/access.log, r, encodingutf-8) as f: for line in f: parts line.strip().split( | ) if len(parts) 6 and parts[4] block: block_counter[parts[2]] 1 for domain, count in block_counter.most_common(10): print(f{count:5d} {domain})统计结果的用处是反推分类表是否合理如果某个域名频繁命中block说明孩子确实常访问分类配置生效如果某个域名从来没出现在日志里可能是设备已经绕过了网关 DNS或者是该域名根本没被访问过。日志分析之后顺手把高频命中的域名加进对应分类的domains列表能有效减少后续匹配的开销。6.2 灰度放行三步法新策略不要直接推给全家人。我现在习惯每次改规则都走一套三步流程阶段操作持续时间观察把新分类加入observe_categories只记日志不阻断24 小时小范围阻断只在一台测试设备上启用阻断策略2 小时全量放行确认无异常后把策略同步到所有设备组长期observe_categories的价值在于它给你一份“如果真正阻断会误伤谁”的预判清单。之前某次把网课平台域名误归入video_stream如果直接全量阻断孩子晚上就没法上课了。观察阶段日志里的 allow 命中非常明显的暴露出这个分类归属错误让我有机会在真正影响使用前修正。6.3 自测清单把自己当成孩子去试绕过手段验证规则是否真的生效最有效的方法是假设自己是一个想绕过管控的孩子。我每次改完配置都会过一遍这个清单先试改设备 DNS 到外部地址再试直接访问被阻断域名的 IP然后试卸载重装管控相关 App最后试连接手机热点。前两项是网关层管控是否生效的直接检验后两项属于终端侧行为网关管控本身无法解决但日志里能看到设备访问记录方便后续约法三章。从那以后我每次调整策略文件都会强制走一遍同样的流程先最小验证再灰度观察最后全量推送再把自测清单完整跑一遍。这个习惯帮我挡掉过不少隐蔽的翻车事故毕竟配置文件的语法可以通过检查但分类归属和流量行为只有真实请求才能暴露。希望这份拆解能帮你在第一次部署时少走一点弯路把管控做到既有效又不至于随时准备拔网线。本文还有配套的精品资源点击获取