基于 ArchLinux 构建旁路由

发布:

前言🔗

其实这个标题并不正确,正确的应该是基于通用 Linux 系统构建旁路由,只不过我使用的是 ArchLinux,所以下面的教程以此为基础。

为什么要基于通用 Linux 系统构建旁路由?常见的旁路由构建方式有虚拟机或 Docker 容器,这两种方法各有优缺点。

虚拟机拥有独立且完整的网络堆栈,最易于使用,但网络性能稍差一些,由于需要预先分配固定的物理内存,对物理内存偏小的设备不友好。

Docker 容器没有独立的网络堆栈,较易于使用,但使用过程容易遇到问题,由于不需要预先分配固定的物理内存,对物理内存偏小的设备较为友好。

那么基于已有的 Linux 系统构建旁路由呢?配置比较复杂,但是性能优秀、自由度高、内存需求低,不需要维护一个单独的系统或容器,支持运行任意 Linux 系统的设备。

如果你也想使用 ArchLinux 或通用 Linux 系统构建旁路由,这篇教程会教你怎么做。

由于大部分旁路由的用途是使用代理软件,所以下面的教程仅包含如何配置代理软件和 DNS,如果有其他需求,可以加入 QQ 群与我探讨。

以下方法并不适用于运行 Docker 的系统,会导致 Docker 容器无法联网,有一些变通方法但不完美。由于我现在使用 podman,所以暂时不会去研究让 Docker 完美兼容的方法,如果你有解决方法也可以告知于我。

准备工作🔗

  1. 安装 nftables:sudo pacman -S nftables iptables-nft

  2. 禁用已安装的防火墙并重启系统,比如 firewalld 和 ufw,确保没有其他软件干扰 nftables,可以使用 sudo nft list ruleset 命令查看 nftables 规则集,确保规则集是空的,除非你已经在使用 nftables。

  3. 安装一个支持 TPROXY 的代理软件,我没有对基于 eBPF 的代理软件做过测试,另外,systemd-networkd 和代理软件的 tun 模式搭配使用时可能会出现一些奇怪的问题,所以最好使用支持 TPROXY 的代理软件。

  4. 最后,配置并启动你的代理软件,可以先不管 TPROXY 的部分,HTTP 或 SOCKS 代理协议可以正常工作即可。

正式开始🔗

修改代理软件的配置文件🔗

启用代理软件的 TPROXY 相关设置,以 mihomo 为例,编辑配置文件并加入以下行:

# 出站数据包的 SO_MARK
routing-mark: 666
# TPROXY 监听端口
tproxy-port: 1234

注意,仅仅需要让代理软件启用 TPROXY 监听端口即可,不需要它自动设置路由和规则。

修改代理软件的 systemd 服务文件🔗

透明代理需要修改路由表才能正常工作,可以通过修改代理软件的 systemd 文件实现自动修改路由表。假设你的代理软件是 mihomo,使用命令 sudo systemctl edit mihomo.service 修改 systemd 服务文件,添加以下内容到 ### Edits below this comment will be discarded 行之前。

如果你像我一样使用 systemd-networkd,也可以使用它管理路由表。

[Service]
ExecStartPre=/bin/sh -c '/usr/bin/ip route add local default dev lo proto static scope host table 999 && /usr/bin/ip rule add from all fwmark 0x400/0x400 lookup 999 proto static priority 1000'
ExecStopPost=/bin/sh -c '/usr/bin/ip rule delete from all fwmark 0x400/0x400 lookup 999 proto static priority 1000; /usr/bin/ip route delete local default dev lo proto static scope host table 999'

保存文件并重启代理软件的服务,使用 ip ruleip route show table 999 命令查看路由规则和路由表,输出应该类似下面这样。

> ip rule
0:	from all lookup local
1000:	from all fwmark 0x400/0x400 lookup 999 proto static
32766:	from all lookup main
32767:	from all lookup default

> ip route show table 999
local default dev lo proto static scope host

添加 nftables 规则🔗

如果你已经在使用 nftables,务必将以下规则合并到现有的规则。
如果你已经创建了 input 链,请将 socket transparent 1 accept 规则添加到 input 链起始位置。

接下来添加 nftables 规则,编辑文件 /etc/nftables.conf,删除文件的所有内容,然后添加以下内容:

#!/usr/bin/nft -f

define tproxy_mark = 1024

table inet firewall  {
    set ipv4_reserved_address {
        type ipv4_addr
        flags interval
        auto-merge
        elements = {
            0.0.0.0/8,
            10.0.0.0/8,
            100.64.0.0/10,
            127.0.0.0/8,
            169.254.0.0/16,
            172.16.0.0/12,
            192.0.0.0/24,
            192.0.2.0/24,
            192.88.99.0/24,
            192.168.0.0/16,
            198.18.0.0/15,
            198.51.100.0/24,
            203.0.113.0/24,
            224.0.0.0/4,
            233.252.0.0/24,
            240.0.0.0/4,
            255.255.255.255/32
        }
    }

    chain prerouting { }

    chain output { }
}

delete chain inet firewall prerouting
delete chain inet firewall output

table inet firewall  {
    chain prerouting {
        type filter hook prerouting priority mangle; policy accept

        ct direction reply return

        ip daddr @ipv4_reserved_address return comment "skip from ipv4 reserved address packets"

        # 如果你的代理软件的 TPROXY 端口不是 1234,请更改下面的端口到对应端口。
        meta nfproto ipv4 meta l4proto { tcp, udp } meta mark set meta mark | $tproxy_mark tproxy ip to :1234 accept
    }

    chain output {
        type route hook output priority mangle; policy accept

        ct direction reply return

        # 确保代理软件出站数据包的 SO_MARK 是 666
        meta mark 666 return comment "skip proxy out packets"

        ip daddr @ipv4_reserved_address return comment "skip to ipv4 reserved address packets"

        meta nfproto ipv4 meta l4proto { tcp, udp } meta mark set meta mark | $tproxy_mark comment "send to proxy"
    }
}

重启 nftables.service 并测试透明代理是否正常工作,把局域网其他设备的网关设置为当前设备的 IP 并测试是否正常。

如果全部正常工作,还可以看下我之前写的关于 OpenWrt 策略路由的教程《使用 OpenWrt 策略路由把流量转发到旁路网关》,增强稳定性和灵活性。

DNS 重定向🔗

如果看了我写的教程《使用 OpenWrt 策略路由把流量转发到旁路网关》,里面有讲到重定向公共 DNS 到本地实现无感故障转移,要在 nftables 实现这个功能很简单。

首先确保你的系统有一个可用的 DNS 服务器,代理软件内置的也好,独立的 DNS 服务器也罢,确保局域网的其他设备可以使用它解析域名。

然后编辑 /etc/nftables.conf 文件并添加以下行:

define fake_dns = { 180.76.76.76 }

table inet firewall  {
    chain prerouting_nat { }
    chain output_nat { }
}

delete chain inet firewall prerouting_nat
delete chain inet firewall output_nat

table inet firewall  {
    chain prerouting_nat {
        type nat hook prerouting priority dstnat; policy accept

        # 6653 是本地 DNS 服务器的端口,根据你的配置修改。
        ip daddr $fake_dns meta l4proto { tcp, udp } th dport 53 redirect to :6653
    }

    chain output_nat {
        type nat hook output priority mangle; policy accept

        ip daddr $fake_dns meta l4proto { tcp, udp } th dport 53 redirect to :6653
    }
}

重启 nftables.service 并测试 DNS 重定向是否正常工作。

小贴士🔗

由于 Linux 路由表查询的特性,如果你需要某个软件绕过代理,可以让它绑定到某个接口,这样就可以使其绕过代理。

比如可以添加 --interface 参数让 curl 绕过代理,例如:curl --interface eth0 ip.sb

如果软件不支持绑定接口,也可以通过修改 nftables 规则,使用 uid, cgroups 等条件使特定软件绕过代理。


现在,你可以完全享受由 ArchLinux 驱动的旁路由了!以上只是最简单的示例,更多复杂的配置等你去探索。


评论由 giscus 驱动,基于 GitHub Discussions。