序
啊我操短视频怎么这么坏!我真想口了他们!👍👍👍🌹🌹🌹
家里人是不是经常给向你转发玄学垃圾、民粹暴论?甚至「脱口而脱出」那既不新鲜也不美味的谬误,一库一库,噗叽啪,很响啊!
带着计网基础打算成为赛博老大哥,于是~你的目标是:
- 家中的老人:阻断内容推送「精准诈骗」,阻断「智人 TV」相关的转发对你的智识污染。之后也许你会考虑让他们转用其他媒体。
- 电丸の小鬼:「小鬼,我要控制你了」
- 小🍥孩子:肯定不吃你这一套,然后把这个给她看!
设备一旦离开了受审查的网络,就脱离了控制;若能在设备上进行主动审查,确实是最万无一失的方案。 如果设备支持设置 Private DNS,将其设置为你搭设的 DNS over TLS 服务器,也可以对 DNS 请求进行过滤,但是对于由应用程序发起的加密查询,这不起作用。 也许可以给他们装一个 CA 证书用于中间人攻击,这样可以让他们的设备在任何网络上都无法脱身,但是这样做要接触和调试物理设备。 对于没有解 OEM Bootloader 锁的设备,只能通过 VPN 连接对设备流量代理与解密。
本篇不含有以下元素:
- DNS 抢答
- 深度包检测
- 主动探测
旁路方案:用于过滤和欺骗的 DNS
[!TIP]
与「DNS 抢答」不同,本文的方式会直接对下游设备指定要使用的 DNS 的地址。
用什么 DNS 服务器套件?
AdGuard Home,为过滤而生,带 Web 配置界面,附带 DHCPv4 服务器,可以代替网关路由器的 DHCPv4 服务器。
该怎么向设备下发 DNS 地址?
DNS 地址自动配置需要借助 DHCPv4 和 Router Advertisement 下发到设备。
与 IPv4 不同:DHCPv6 并不是 IPv6 常用的网络自动配置方案,而是 SLAAC 和 NDP RA。 因此你需要在网关路由器上配置 RA 报文的内容,而不是 DHCPv6,后者无所谓。
DHCPv4
- 如果你的网关路由器可以指定 DNS 地址,那填入你搭设的 DNS 的地址。
- 如果没有,禁用你网关路由器的 DHCPv4 服务器,然后搭建一个网内唯一的 DHCPv4 服务器,配置其下发的 DNS 地址指向你的 DNS 服务器。
IPv6 NDP RA
由 RA 下发的 DNS 地址,只能在网关路由器上配置,可以是 fe80:: 这样的本地链路地址。
对于被过滤的查询,应该返回怎样的响应?
视情况,二选一:
- 拒绝,使握手立刻失败:
- NXDOMAIN
- 空地址「
::」「0.0.0.0」
- 丢包,使握手永无响应:返回一个拥有 「可路由前缀」 然而 「不可达」 的地址,比如 「
192.168.254.254」
||dns.google$dnsrewrite=0.0.0.0
||dns.google$dnsrewrite=192.168.254.254
更进一步:在路径上串联中间设备
桥接
为了监视数据包,需要在数据包路径中加入一个中间设备,用多于 2 个网口的机器,组一个桥接网络,其中一个网口接上游,其余的网口接下游,使数据包流经该桥接网络,以使之对软件可见。
见「附录」。
启用 netfilter 桥接数据包转发
需要 br_netfilter 内核模块。
桥接网络中的 IP 包将对 iptables 可见。
modprobe br_netfilter
#sysctl net.bridge.bridge-nf-call-ip6tables=1
#sysctl net.ipv6.conf.br0.forwarding=1
sysctl net.ipv4.ip_forward=1
br_netfilter 已知问题
IPv6 桥接数据包转发(启用 net.bridge.bridge-nf-call-ip6tables 时)下游设备没有收到 RA 报文,导致下游设备没有 IPv6 地址用。
捕获模式下的 WireShark
- 监听物理网口,不要监听桥接
- 从入口到出口的包经过了 iptables 的过滤,出入口的流中的包是有差别的
核心科技:「和谐」才有共处
使用 OpenGFW 和 iptables 对目标进行 TCP 连接重置。
对于使用 DNS over HTTPS,或大陆服务商自营的与 DoH 类似的 HTTP DNS 变种,污染无法触及查询。
但是大陆封锁了 Cloudflare ECH,事实上反对 Encrypted Client-Hello,因此对于大陆服务商与用户而言 SNI 的暴露是必然的,所以识别 SNI 并屏蔽连接总是可行的,无论是 TCP/TLS 还是 QUIC 握手包中的 SNI 都在裸奔。
国之重器:OpenGFW
OpenGFW 可以解析多种数据包中的字段,包括但不限于:
- TCP/TLS
- QUIC
还有
- HTTP
- HTTPS
- SOCKS
- Xray 等隧道代理
配置 OpenGFW 本身
# /etc/opengfw.yaml
io:
queueSize: 1024
queueNum: 100
rcvBuf: 4194304
sndBuf: 4194304
# 监视 FORWARD 数据包
local: false
# 对被认定应阻止的连接进行重置
rst: true
workers:
count: 4
queueSize: 64
tcpMaxBufferedPagesTotal: 65536
tcpMaxBufferedPagesPerConn: 16
tcpTimeout: 10m
udpMaxStreams: 4096
配置 OpenGFW 规则
# /etc/opengfw-rules.yaml
- name: Reset TCP/TLS
action: block
expr: |
let sni = string(tls?.req?.sni);
sni contains "www.baidu" ||
sni contains "mi.com" ||
sni contains "sogou" ||
sni contains "so.com" ||
sni contains "search.we" ||
sni contains "telemetry"
- name: Block QUIC
action: block
expr: string(quic?.req?.sni) endsWith "taobao.com"
opengfw -c /etc/opengfw.yaml /etc/opengfw-rules.yaml
社会信用:iptables
如果只是针对 TCP,OpenGFW 反而不是一个比 iptables 更简单的选择。
对于像 SNI 这种,明文中包含特定内容的,我们可以将「他们」列入暂时管制名单:不许你上网! 可以对付像「微信」这种使用 HTTP DNS 的服务。
对于在「你目前的配置」下总是漏网的连接,可以采取:对于 TCP,每捕获一次特征
- 必需:持续 60s 重置到目标主机的连接,通常只会影响客户端到目标 CDN 的连接,够用
- 可选:持续 2s 重置来自源主机的连接,直接断网,属于过正矫枉,会破坏网络整体观感
阻断时长根据情况调整,如果其他服务与视频服务不共用同一个 CDN,把时间拉长到 36000s 也行。
还可以把 --rcheck 替换为 --update,只要源主机不再发起符合特征的连接,对应的网络服务就会在设定时长后恢复,否则计时器会被重置为设定时长。
# 这些 xt_recent 规则用于记录地址并在设定的时间内持续阻断
iptables -A FORWARD -p tcp -m recent --name bad_src --rsource --rcheck --second 2 -j REJECT --reject-with tcp-reset
iptables -A FORWARD -p tcp -m recent --name bad_dst --rdest --rcheck --second 60 -j REJECT --reject-with tcp-reset
# 匹配数据包明文中的子串,比如 SNI
for K in `cat /etc/sni.list | con`; do
iptables -A FORWARD -p tcp -m string --algo bm --string "$K" -m recent --name bad_src --rsource --set -j REJECT --reject-with tcp-reset
iptables -A FORWARD -p tcp -m string --algo bm --string "$K" -m recent --name bad_dst --rdest --set -j REJECT --reject-with tcp-reset
done
下游路由器
如果还搭设了下游路由器用作受审设备的网络入口,那可以为其建一个 chain 专门处理它:
iptables -N gfw
# 假定下游路由器的地址是 192.168.0.10
iptables -A FORWARD -p tcp -s 192.168.0.10 -j gfw
iptables -A gfw -p tcp -m recent --name bad_src --rsource --rcheck --second 2 -j REJECT --reject-with tcp-reset
iptables -A gfw -p tcp -m recent --name bad_dst --rdest --rcheck --second 60 -j REJECT --reject-with tcp-reset
for K in `cat /etc/sni.list | con`; do
iptables -A gfw -p tcp -m string --algo bm --string "$K" -m recent --name bad_src --rsource --set -j REJECT --reject-with tcp-reset
iptables -A gfw -p tcp -m string --algo bm --string "$K" -m recent --name bad_dst --rdest --set -j REJECT --reject-with tcp-reset
done
其他考虑
华为路由器
我在「v2ex」「恩山无线」等论坛上了解到「华为」有路由器型号具有屏蔽「微信视频号」的选项,具体实现方法不知。 想省事的可以考虑。
QUIC
此时应用 OpenGFW 来解析 QUIC 握手包字段。
QUIC 在大陆还没有完全普及,运营商对 UDP 的 QoS 可能会比较低,大陆大部分服务商还在用 TCP,暂时不需要针对 QUIC 连接;而且 QUIC 需要更多计算来解密握手包,审查成本会更高。 至于 UDP 承载的服务相当多,考虑到还有不少专有协议,阻断会比较麻烦。
br_netfilter 转发 IPv6 开启后,反而致使下游收不到 RA 报文
我不知道如何解决;如果下游有 IPv6 硬需求的话,自寻出路吧。
可能面临的现实困境
[!TIP]
阻断只能得一时安宁,还得对家人做后期工作。
呼叫装维人员上门
可能会将你的中间设备从路径中移除,或者将你的光猫设置重置。
目标迁至其他网络
只能对设备物理干预。
绿坝行为:在设备上进行限制,比如「青少年模式」之类的应用锁。
另寻其他短视频平台
这个比较难办,如果执意要阻断这些短视频平台,可以从下游的 DNS 查询历史记录中寻找目标。 魔高一尺,道高一丈。
建议为家里人寻找其他兴趣活动,或者转移到质量较好的长媒体,哪怕付点订阅费呢。
暴露后如何解释?
这个真就艺术吧,谁知道呢,最好别发生。
观察记录
依赖明文 DNS 的服务
例如:
- 搜索引擎,比如「百度」「搜狗」
- 字节跳动/抖音/火山/西瓜/头条
- 快手
对查询返回欺骗结果即可。
比如,如果你想迫使下游使用 Microsoft Bing,可以把其他的搜索引擎全部封掉,然后在设备上装 Firefox,把 Bing 设为默认。
微信视频号
播放「微信视频号」时,在重置「微信」的全部连接后,用 tcpdump 观察到了很多明文的 JSON,看上去是控制媒体播放的反馈信息,但是尚不明确对连接是否有影响。
「微信视频号」比较难杀,有漏网的连接,可采用上文中「根据特征识别、阻断目标地址」的手段。
规则列表
DNS 污染
通过污染下游的 DNS 记录,可使依赖明文 DNS 查询的下游客户端与域名对应的服务失联。
除此之外,一并在 AdGuard Home 的面板上开启对「TikTok」等服务的过滤。
# AdGuard Home 过滤规则
/.*douyin.*/
/.*ixigua.*/
/.*huoshan.*/
/.*tiktok.*/
||bytedance.com^
||snssdk.com^
||bytecdn.cn^
||bdurl.net^
||yximgs.com^
||ksapisrv.com^
||inkuai.com^
||kwimgs.com^
||gifshow.com^
||ndcimgs.com^
||ks-live.com^
/.*kuai.*/
/.*kwai.*/
||ximalaya.com^
||himalaya.com^
||xmcdn.com^
||netstart.cn^
明文特征
结合 DNS 查询历史记录和 WireShark 抓包寻找服务的特征,将 SNI 加入到 iptables 过滤规则中。
# 把明文 JSON 作为特征
":{
# 微信视频号
flv.wxqcloud
video.qq
snsvideo
stodownload
voipfinder
愿智人 TV 不会来打扰你和家里人(大概?)
附录
适用于 systemd-networkd 的桥接网络配置示例
# 桥接设备
# /etc/systemd/network/br0.netdev
[NetDev]
Name=br0
Kind=bridge
MACAddress=fe:fe:fe:fe:fe:fe
# 桥接网络
# /etc/systemd/network/br0.network
[Match]
Name=br0
[Link]
RequiredForOnline=routable
# 如果通过 DHCP 获取地址:
[Network]
DHCP=yes
# 如果你的设备正好是网络中仅有的 DHCPv4 服务器,那你需要为设备指定静态地址:
[Network]
Address=192.168.1.2/24
Gateway=192.168.1.1
Gateway=fe80::1
DNS=192.168.1.1
DNS=2606:4700:4700::1111
# 网口桥接
# /etc/systemd/network/en.network
[Match]
# 通配符,假设有多个 Ethernet 网口 enp1s0 enp2s0 enp3s0
Name=en*
[Network]
Bridge=br0