【实用github项目】人人有墙建-OpenGFW
本文最后更新于 2024-05-22,
若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益, 请联系我 删除。
本站只有Telegram群组为唯一交流群组, 点击加入
文章内容有误?申请成为本站文章修订者或作者? 向站长提出申请
OpenGFW 是一个 Linux 上灵活、易用、开源的 DIY GFW 实现,并且在许多方面比真正的 GFW 更强大。为何让那些掌权者独享乐趣?是时候把权力归还给人民,人人有墙建了。立即安装可以部署在家用路由器上的网络主权 - 你也能是老大哥。
项目地址: https://github.com/apernet/OpenGFW/tree/master
项目文档: https://gfw.dev/
Telegram 群组: https://t.me/OpGFW
功能
- 完整的 IP/TCP 重组,各种协议解析器
- HTTP, TLS, QUIC, DNS, SSH, SOCKS4/5, WireGuard, OpenVPN, 更多协议正在开发中
- Shadowsocks, VMess 等 "全加密流量" 检测 (https://gfw.report/publications/usenixsecurity23/zh/)
- Trojan 协议检测
- [开发中] 基于机器学习的流量分类
- 同等支持 IPv4 和 IPv6
- 基于流的多核负载均衡
- 连接 offloading
- 基于 expr 的强大规则引擎
- 规则可以热重载 (发送
SIGHUP
信号) - 灵活的协议解析和修改框架
- 可扩展的 IO 实现 (目前只有 NFQueue)
- [开发中] Web UI
使用场景
- 广告拦截
- 家长控制
- 恶意软件防护
- VPN/代理服务滥用防护
- 流量分析 (纯日志模式)
- 助你实现你的独裁野心
对于我来说只是想让使用这台服务器代理的人不能访问一些乱七八糟的网站罢了(服务器借给别人作为代理服务器了)
以下是官方文档,使用其实也是非常简单,你只需要去github下载编译好的二进制文件(你也可以自己用golang来编译)然后只需要写好 config.yaml
配置文件和 rules.yaml
规则文件即可。需要限制的协议和网站主要也是在rules.yaml
里面编写
构建与运行
构建
export CGO_ENABLED=0
go build
运行
export OPENGFW_LOG_LEVEL=debug
./OpenGFW -c config.yaml rules.yaml
其中 config.yaml
是配置文件,rules.yaml
是规则文件。
pcap 文件回放模式
./OpenGFW -p your.pcap -c config.yaml rules.yaml
在 pcap 模式下,规则中的所有动作都没有任何效果。此模式主要用于调试。
OpenWrt
OpenGFW 在 OpenWrt 23.05 上测试可用(其他版本应该也可以,暂时未经验证)。
安装依赖:
opkg install nftables kmod-nft-queue kmod-nf-conntrack-netlink
样例配置
io:
queueSize: 1024
rcvBuf: 4194304
sndBuf: 4194304
local: true
rst: false
workers:
count: 4
queueSize: 64
tcpMaxBufferedPagesTotal: 65536
tcpMaxBufferedPagesPerConn: 16
tcpTimeout: 10m
udpMaxStreams: 4096
# 指定的 geoip/geosite 档案路径
# 如果未设置,将自动从 https://github.com/Loyalsoldier/v2ray-rules-dat 下载
# ruleset:
# geoip: geoip.dat
# geosite: geosite.dat
replay:
realtime: false
规则文件
规则文件是规则的集合,用于定义对匹配到的连接进行何种操作。一个规则的基本格式如下:
- name: block v2ex https
action: block
log: true
expr: string(tls?.req?.sni) endsWith "v2ex.com"
其中 name
是规则的名称,action
是进行的动作,log
是是否记录日志,expr
是匹配表达式。action
和 log
中必须至少存在一个。
可供匹配的数据来自分析器,请参考 分析器文档。
支持的动作 (action)
动作 | TCP | UDP |
---|---|---|
allow |
放行连接,不再处理后续的包。 | 放行连接,不再处理后续的包。 |
block |
阻断连接,不再处理后续的包。 | 阻断连接,不再处理后续的包。 |
drop |
效果同 block 。 |
丢弃触发规则的包,但继续处理同一流中的后续包。 |
modify |
效果同 allow 。 |
用指定的修改器修改触发规则的包,然后继续处理同一流中的后续包。 |
规则样例
记录 SNI 中包含特定关键字的连接
- name: log horny people
log: true
expr: let sni = string(tls?.req?.sni); sni contains "porn" || sni contains "hentai"
阻断访问 v2ex.com 的 HTTP/HTTPS/QUIC 连接
- name: block v2ex http
action: block
expr: string(http?.req?.headers?.host) endsWith "v2ex.com"
- name: block v2ex https
action: block
expr: string(tls?.req?.sni) endsWith "v2ex.com"
- name: block v2ex quic
action: block
expr: string(quic?.req?.sni) endsWith "v2ex.com"
阻断并记录 Shadowsocks, VMess, Trojan 连接
- name: block shadowsocks and vmess
action: block
log: true
expr: fet != nil && fet.yes
- name: block trojan
action: block
log: true
expr: trojan != nil && trojan.yes
将 v2ex.com 域名 DNS 污染到 0.0.0.0 和 ::
- name: v2ex dns poisoning
action: modify
modifier:
name: dns
args:
a: "0.0.0.0"
aaaa: "::"
expr: dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})
阻断 SOCKS 代理访问 google.com:80
- name: block google socks
action: block
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
根据握手响应阻断 WireGuard
- name: block wireguard by handshake response
action: drop
expr: wireguard?.handshake_response?.receiver_index_matched == true
根据 GeoSite 数据库阻断 Bilibili 的所有域名
- name: block bilibili geosite
action: block
expr: geosite(string(tls?.req?.sni), "bilibili")
根据 GeoIP 数据库阻断所有目标 IP 为中国的连接
- name: block CN geoip
action: block
expr: geoip(string(ip.dst), "cn")
根据 CIDR 阻断特定 IP 段的连接
- name: block cidr
action: block
expr: cidr(string(ip.dst), "192.168.0.0/16")
阻断 Xray Reality/ShadowTLS 连接
原理:Xray Reality/ShadowTLS 等协议的 TLS 握手是 "盗用" 其他正常网站的,但连接的目标 IP 是代理服务器而并非这些网站的真正 IP。因此可以通过 DNS 查询 SNI 域名解析到的地址,如果连接的目标 IP 不在这些地址中,则阻断连接。
Warning
为了尽量降低误伤,下面提供的规则中除了使用系统默认 DNS 外还通过另外两个服务器进行查询,只要目标 IP 在任何结果中出现则放行。请根据实际网络环境对规则进行调整。如果域名无法解析,则 lookup
函数会出错导致此条规则出错,也不会阻断连接。
- name: SNI mismatch
action: block
expr: tls?.req?.sni != nil && ip.dst not in concat(lookup(tls.req.sni), lookup(t
分析器
分析器是 OpenGFW 的重要组件之一,作用是分析连接,检查是否是支持的协议,并从该连接中提取信息,作为提供给规则引擎的属性,以便与用户提供的规则进行匹配。OpenGFW 会自动分析提供的规则中引用了哪些分析器,仅启用需要的分析器。
本文档列出了每个分析器提供的属性。所有列出的属性都可以在规则中使用。
Tip
许多分析器并非一次性提供全部属性,而是会随着传输进行逐步增加/更新字段。例如,当一个 HTTP 连接发送了请求但还没有收到响应时,HTTP 分析器只会有 req
部分而没有 resp
。每个连接会在任何属性发生变化时匹配一次规则。规则需要能正确处理需要的字段为 nil
的情况。(如 http != nil && http.resp != nil
或利用 ?
操作符 http?.resp
)
内置
每个连接都会有以下内置属性:
{
"id": 123456,
"proto": "tcp", // tcp, udp
"ip": {
"src": "123.123.123.123",
"dst": "7.8.9.0"
},
"port": {
"src": 1234,
"dst": 80
}
}
阻止到 8.8.8.8
的 UDP 流量:
- name: Block 8.8.8.8 UDP
action: block
expr: ip.dst == "8.8.8.8" && proto == "udp"
DNS (TCP & UDP)
对于请求:
{
"dns": {
"aa": false,
"id": 41953,
"opcode": 0,
"qr": false,
"questions": [
{
"class": 1,
"name": "www.google.com",
"type": 1
}
],
"ra": false,
"rcode": 0,
"rd": true,
"tc": false,
"z": 0
}
}
对于相应:
{
"dns": {
"aa": false,
"answers": [
{
"a": "142.251.32.36",
"class": 1,
"name": "www.google.com",
"ttl": 255,
"type": 1
}
],
"id": 41953,
"opcode": 0,
"qr": true,
"questions": [
{
"class": 1,
"name": "www.google.com",
"type": 1
}
],
"ra": true,
"rcode": 0,
"rd": true,
"tc": false,
"z": 0
}
}
丢弃 www.google.com
的 DNS 请求:
- name: Block Google DNS
action: drop
expr: dns != nil && !dns.qr && any(dns.questions, {.name == "www.google.com"})
FET (全加密连接)
请阅读论文 https://www.usenix.org/system/files/usenixsecurity23-wu-mingshi.pdf 以获取更多信息。
{
"fet": {
"ex1": 3.7560976,
"ex2": true,
"ex3": 0.9512195,
"ex4": 39,
"ex5": false,
"yes": false
}
}
屏蔽全加密连接:
- name: Block suspicious proxy traffic
action: block
expr: fet != nil && fet.yes
HTTP
{
"http": {
"req": {
"headers": {
"accept": "*/*",
"host": "ipinfo.io",
"user-agent": "curl/7.81.0"
},
"method": "GET",
"path": "/",
"version": "HTTP/1.1"
},
"resp": {
"headers": {
"access-control-allow-origin": "*",
"content-length": "333",
"content-type": "application/json; charset=utf-8",
"date": "Wed, 24 Jan 2024 05:41:44 GMT",
"referrer-policy": "strict-origin-when-cross-origin",
"server": "nginx/1.24.0",
"strict-transport-security": "max-age=2592000; includeSubDomains",
"via": "1.1 google",
"x-content-type-options": "nosniff",
"x-envoy-upstream-service-time": "2",
"x-frame-options": "SAMEORIGIN",
"x-xss-protection": "1; mode=block"
},
"status": 200,
"version": "HTTP/1.1"
}
}
}
屏蔽对 ipinfo.io
的 HTTP 请求:
- name: Block ipinfo.io HTTP
action: block
expr: http != nil && http.req != nil && http.req.headers != nil && http.req.headers.host == "ipinfo.io"
SSH
{
"ssh": {
"server": {
"comments": "Ubuntu-3ubuntu0.6",
"protocol": "2.0",
"software": "OpenSSH_8.9p1"
},
"client": {
"comments": "IMHACKER",
"protocol": "2.0",
"software": "OpenSSH_8.9p1"
}
}
}
屏蔽 SSH:
- name: Block SSH
action: block
expr: ssh != nil
TLS
{
"tls": {
"req": {
"alpn": ["h2", "http/1.1"],
"ciphers": [
4866, 4867, 4865, 49196, 49200, 159, 52393, 52392, 52394, 49195, 49199,
158, 49188, 49192, 107, 49187, 49191, 103, 49162, 49172, 57, 49161,
49171, 51, 157, 156, 61, 60, 53, 47, 255
],
"compression": "AA==",
"random": "UqfPi+EmtMgusILrKcELvVWwpOdPSM/My09nPXl84dg=",
"session": "jCTrpAzHpwrfuYdYx4FEjZwbcQxCuZ52HGIoOcbw1vA=",
"sni": "ipinfo.io",
"supported_versions": [772, 771],
"version": 771,
"ech": true
},
"resp": {
"cipher": 4866,
"compression": 0,
"random": "R/Cy1m9pktuBMZQIHahD8Y83UWPRf8j8luwNQep9yJI=",
"session": "jCTrpAzHpwrfuYdYx4FEjZwbcQxCuZ52HGIoOcbw1vA=",
"supported_versions": 772,
"version": 771
}
}
}
屏蔽对 ipinfo.io
的 TLS 请求:
- name: Block ipinfo.io TLS
action: block
expr: tls != nil && tls.req != nil && tls.req.sni == "ipinfo.io"
QUIC
QUIC 解析器的格式与 TLS 一样,但是目前只支持请求 (req) 部分:
{
"quic": {
"req": {
"alpn": ["h3"],
"ciphers": [4865, 4866, 4867],
"compression": "AA==",
"ech": true,
"random": "FUYLceFReLJl9dRQ0HAus7fi2ZGuKIAApF4keeUqg00=",
"session": "",
"sni": "quic.rocks",
"supported_versions": [772],
"version": 771
}
}
}
屏蔽对 quic.rocks
的 QUIC 请求:
- name: Block quic.rocks QUIC
action: block
expr: quic != nil && quic.req != nil && quic.req.sni == "quic.rocks"
Trojan (代理协议)
{
"trojan": {
"seq": [682, 4540, 1310, 1031],
"yes": true
}
}
屏蔽 Trojan 连接:
- name: Block Trojan
action: block
expr: trojan != nil && trojan.yes
Warning
Trojan 检测目前依赖基于流量特征的启发式算法,并不保证完全准确。有大概 0.6% 的假阳性率和 10% 的假阴性率。像上述规则这样直接屏蔽所有疑似 Trojan 的连接可能导致误伤正常 TLS 连接。目前建议使用日志模式记录下 IP 地址,进行额外的人工审查。
SOCKS
SOCKS4:
{
"socks": {
"version": 4,
"req": {
"cmd": 1,
"addr_type": 1, // same as socks5
"addr": "1.1.1.1",
// for socks4a
// "addr_type": 3,
// "addr": "google.com",
"port": 443,
"auth": {
"user_id": "user"
}
},
"resp": {
"rep": 90, // 0x5A(90) granted
"addr_type": 1,
"addr": "1.1.1.1",
"port": 443
}
}
}
SOCKS5 无验证:
{
"socks": {
"version": 5,
"req": {
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
"addr": "google.com",
"port": 80,
"auth": {
"method": 0 // 0x00: no auth, 0x02: username/password
}
},
"resp": {
"rep": 0, // 0x00: success
"addr_type": 1, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
"addr": "198.18.1.31",
"port": 80,
"auth": {
"method": 0 // 0x00: no auth, 0x02: username/password
}
}
}
}
SOCKS5 带验证:
{
"socks": {
"version": 5,
"req": {
"cmd": 1, // 0x01: connect, 0x02: bind, 0x03: udp
"addr_type": 3, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
"addr": "google.com",
"port": 80,
"auth": {
"method": 2, // 0x00: no auth, 0x02: username/password
"username": "user",
"password": "pass"
}
},
"resp": {
"rep": 0, // 0x00: success
"addr_type": 1, // 0x01: ipv4, 0x03: domain, 0x04: ipv6
"addr": "198.18.1.31",
"port": 80,
"auth": {
"method": 2, // 0x00: no auth, 0x02: username/password
"status": 0 // 0x00: success, 0x01: failure
}
}
}
}
屏蔽到 google.com:80
的 SOCKS 请求,以及带有用户名 foobar
的 SOCKS 请求:
- name: Block SOCKS google.com:80
action: block
expr: string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80
- name: Block SOCKS user foobar
action: block
expr: socks?.req?.auth?.method == 2 && socks?.req?.auth?.username == "foobar"
WireGuard
{
"wireguard": {
"message_type": 1, // 0x1: handshake_initiation, 0x2: handshake_response, 0x3: packet_cookie_reply, 0x4: packet_data
"handshake_initiation": {
"sender_index": 0x12345678
},
"handshake_response": {
"sender_index": 0x12345678,
"receiver_index": 0x87654321,
"receiver_index_matched": true
},
"packet_data": {
"receiver_index": 0x12345678,
"receiver_index_matched": true
},
"packet_cookie_reply": {
"receiver_index": 0x12345678,
"receiver_index_matched": true
}
}
}
屏蔽 WireGuard:
# 误伤: 高
- name: Block all WireGuard-like traffic
action: block
expr: wireguard != nil
# 误伤: 中
- name: Block WireGuard by handshake_initiation
action: drop
expr: wireguard?.handshake_initiation != nil
# 误伤: 低
- name: Block WireGuard by handshake_response
action: drop
expr: wireguard?.handshake_response?.receiver_index_matched == true
# 误伤: 极低
- name: Block WireGuard by packet_data
action: block
expr: wireguard?.packet_data?.receiver_index_matched == true
OpenVPN
OpenVPN 分析器对于 TCP 和 UDP 模式都可以检测。注意如果你的 OpenVPN 配置包含了 tls-crypt
则不能正常工作,因为在这种情况下连接会被一个预共享密钥加密,成为全加密连接。
{
"openvpn": {
"rx_pkt_cnt": 88,
"tx_pkt_cnt": 23
}
}
屏蔽 OpenVPN(如果检测到超过 50 个 OpenVPN 包,防止误伤):
- name: Block OpenVPN
action: block
expr: openvpn != nil && openvpn.rx_pkt_cnt + openvpn.tx_pkt_cnt > 50
内置函数
除了 expr 本身内置的函数以外,我们还提供了一些额外的内置函数,可以在表达式中使用:
cidr
cidr(ip: string, cidr: string) -> bool
检查一个 IP 地址是否在一个 CIDR 范围内。示例:
- name: block cidr
action: block
expr: cidr(string(ip.dst), "192.168.0.0/16")
geoip
geoip(ip: string, country: string) -> bool
检查一个 IP 地址是否来自一个国家,使用来自 https://github.com/Loyalsoldier/v2ray-rules-dat 的数据。
示例:
- name: block CN geoip
action: block
expr: geoip(string(ip.dst), "cn")
geosite
geosite(domain: string, category: string) -> bool
检查一个域名是否属于一个特定的类别,使用来自 https://github.com/Loyalsoldier/v2ray-rules-dat 的数据。
示例:
- name: block bilibili geosite
action: block
expr: geosite(string(tls?.req?.sni), "bilibili")
lookup
lookup(domain: string) -> list<string>
lookup(domain: string, server: string) -> list<string>
对指定的域名进行 DNS 查询,返回 IP 地址列表(同时包括 A 和 AAAA 记录)。如果未指定服务器地址,则使用系统默认的 DNS。此操作使用标准 DNS 协议(不是 DNS over TLS、DNS over HTTPS 等),且服务器地址必须同时包括 IP 地址和端口(如 8.8.8.8:53
)。
示例:
- name: SNI mismatch
log: true
expr: tls?.req?.sni != nil && ip.dst not in lookup(tls.req.sni)