参考资料
前提条件
- Linux主机,已安装 nftables,已安装并使用 systemd 作为启动管理、网络配置管理( systemd-networkd )。
- 本机已运行支持 Tproxy 的代理软件,监听端口以下以 2500 为例。
配置流程
传统的方式如下,这也是网络上绝大多数文章写烂了的。原文照抄的Hysteria,点上面的链接能看到完整内容。
ip rule add fwmark 0x1 lookup 100
ip route add local default dev lo table 100
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local default dev lo table 100
第一句让 fwmark 为 1 的流量,查询路由表 100,第二句是让路由表100的流量都转发到本地lo设备。
define TPROXY_MARK=0x1
define HYSTERIA_USER=hysteria
define HYSTERIA_TPROXY_PORT=2500
define TPROXY_L4PROTO={ tcp, udp }
define BYPASS_IPV4={
0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16,
172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/3
}
define BYPASS_IPV6={ ::/128 }
table inet hysteria_tproxy {
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
meta l4proto $TPROXY_L4PROTO socket transparent 1 counter mark set $TPROXY_MARK
socket transparent 0 counter return
ip daddr $BYPASS_IPV4 counter return
ip6 daddr $BYPASS_IPV6 counter return
ip6 daddr != 2000::/3 counter return
meta l4proto $TPROXY_L4PROTO counter tproxy to :$HYSTERIA_TPROXY_PORT meta mark set $TPROXY_MARK accept
}
}
table inet hysteria_tproxy_local {
chain output {
type route hook output priority mangle; policy accept;
meta skuid $HYSTERIA_USER counter return
ip daddr $BYPASS_IPV4 counter return
ip6 daddr $BYPASS_IPV6 counter return
ip6 daddr != 2000::/3 counter return
meta l4proto $TPROXY_L4PROTO counter meta mark set $TPROXY_MARK
}
}
以上注释很清晰,需要注意的是:首先此配置仅适用于系统中单独创建了 hysteria 用户来运行透明代理服务,所以才有下面的写法,不过我的代理服务也是用了专门的用户,但是这个貌似没起作用,采用了其他方法来避免环路。
- meta skuid $HYSTERIA_USER counter return
systemd-networkd 实现策略路由
这部分的作用跟传统方法中那四条 ip 命令一样,只是因为传统方式的命令重启后会消失,我们需要想办法让它开机自动运行。既然用 systemd-networkd 管理网络,自然要研究它的方式:
在 /etc/systemd/network 新增一个.network的配置文件,名称随意:
$ cat 13-tproxy.network
[Match]
Name = lo
[RoutingPolicyRule]
FirewallMark = 1
Table = 100
Priority = 100
Family=both
[Route]
Table = 100
Destination=0.0.0.0/0 Type = local
[Route]
Table = 100
Destination=::/0 Type = local
设置好后 systemctl restart systemd-networkd 或重启,按如下方式检验:
$ sudo ip rule list
0: from all lookup local
100: from all fwmark 0x1 lookup 100 proto static 32766: from all lookup main
32767: from all lookup default
$ sudo ip -6 rule list
0: from all lookup local
100: from all fwmark 0x1 lookup 100 proto static 32766: from all lookup main
k$ sudo ip -d r s t all |grep table\ 100
local default dev lo table 100 proto static scope host
说明设置正确。 再来说我的避免环路的方法,贴一下我的 nftable Tproxy部分:
define TPROXY_MARK=0x1
define PROXY_USER=999
define TPROXY_PORT=2500
define TPROXY_L4PROTO={ tcp, udp }
define BYPASS_IPV4={
100.64.0.0/10, 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32 }
define BYPASS_IPV6={
FE80::/10, FC00::/7, ::1/128, 2001:db8::/32 }
table inet tproxy-lan {
set bypass_ipv4 {
type ipv4_addr
flags interval
auto-merge
elements = { $BYPASS_IPV4 }
}
set bypass_ipv6 {
type ipv6_addr
flags interval
elements = { $BYPASS_IPV6 }
}
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
meta l4proto $TPROXY_L4PROTO socket transparent 1 mark set $TPROXY_MARK comment "重定向到透明代理"
socket transparent 0 counter return
ip daddr @bypass_ipv4 counter return
ip6 daddr @bypass_ipv6 counter return
ip6 daddr != 2000::/3 counter return
meta l4proto $TPROXY_L4PROTO tproxy to :$TPROXY_PORT meta mark set $TPROXY_MARK accept
}
chain output {
type route hook output priority mangle; policy accept;
ip daddr @bypass_ipv4 counter return
ip6 daddr @bypass_ipv6 counter return
meta skuid $PROXY_USER counter return comment "代理本身流量不走代理"
ip daddr 172.5.6.0/24 udp dport !=53 counter return comment "本地流量不走代理,dns请求除外"
ip daddr 172.5.6.0/24 tcp dport !=53 counter return comment "本地流量不走代理,dns请求除外"
ip6 daddr != 2000::/3 counter return
meta l4proto $TPROXY_L4PROTO counter meta mark set $TPROXY_MARK
}
}