安装好了 Debian 路由器之后,咱们是不是有一个应用是几乎玩软路由的人都喜欢的?没错,那就是透明代理。今天小玲就来教大家在安装完 Debian 路由器之后,通过 Clash 和 nftables 实现透明代理。本教程的设置均基于 《使用 Debian 作为路由器》的设置。

咱们要使用的 Clash 是 Docker 版的。这样安装起来更方便一些。Clash 模式采用 redir-host,因为小玲习惯了 redir-host 模式了,用 fake-ip 模式的话,查网站的 IP 地址会多多少少有一些不方便。

创建用户组

首先新建一个名为 clash 的用户组,因为之后咱们的 Clash 要不光能代理局域网内其他设备的流量,还要代理自身发出的流量。所以要让 Clash 运行在一个特定的用户组上。

sudo groupadd -r clash

查看 clash 用户组的 gid。

cat /etc/group | grep clash

咱们可以看到,小玲创建的 clash 用户组的 gid 是 997。这个数值会根据你的环境的不同而不同,使用时请更改为你自己的 gid。

clash:x:997:

编写配置文件

首先先把 Clash 的配置文件 config.yaml 准备好,小玲这里准备了一个。注意这里的 DNS 监听端口是 1053。待会咱们需要把 Dnsmasq 的上游 DNS 改为本机的 1053 端口。小玲不建议直接让 Clash 监听 53 端口,因为 Clash 的 hosts 功能没办法给一个域名同时指定 IPv4 和 IPv6 地址。如果你用不到 hosts 功能,让 Clash 直接监听 53 端口也是可以的,这样就可以让 Dnsmasq 不提供 DNS 服务,改由 Clash 提供 DNS 服务了。

mkdir -p ~/config/clash
vim ~/config/clash/config.yaml
port: 7890
socks-port: 7891
redir-port: 7892
tproxy-port: 7893
allow-lan: true
mode: rule
log-level: error
external-controller: 0.0.0.0:9090
secret: ""
ipv6: true
bind-address: "*"
hosts:
dns:
  enable: true
  listen: :1053
  ipv6: true
  enhanced-mode: redir-host
  nameserver:
    - udp://[2001:4860:4860::8888]:53
    - udp://[2001:4860:4860::8844]:53
  fallback:
  fallback-filter:
    geoip: true
    ipcidr:
      - 240.0.0.0/4
      - 0.0.0.0/32
proxies:
proxy-providers:
proxy-groups:
rules:

编写 docker-compose.yml 文件。

vim ~/docker-compose.yml
version: "3"
services:
  clash:
    cap_add:
      - NET_ADMIN
    container_name: clash
    image: dreamacro/clash:v1.7.1
    # 这里的 997 请改成你的 gid。
    user: "0:997"
    network_mode: host
    restart: always
    volumes:
      - ./config/clash:/root/.config/clash
      - /etc/group:/etc/group:ro
      - /etc/passwd:/etc/passwd:ro
  # 这里省略已有的 service

这里之所以使用 1.7.1 的 Clash 是因为这个版本之后的 Clash,redir-host 模式不好用了。

修改 dnsmasq.conf 文件。

vim ~/config/dnsmasq/dnsmasq.conf
server=::1#1053
cache-size=0

cache-size=0 是为了禁止 Dnsmasq 缓存 DNS 查询结果,这样每次 DNS 请求都会转发到 Clash 那里。

编辑 /etc/rc.local 文件。

sudo vim /etc/rc.local

默认内容如下。

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

exit 0

将下面的内容添加到 exit 0 的前面。

ip rule add fwmark 1 lookup 100
ip route add local default dev lo table 100
ip -6 rule add fwmark 1 table 101
ip -6 route add local ::/0 dev lo table 101

Debian 11 里默认是没有 rc.local 文件的,而且 rc-local 服务器默认是关闭的1。如果没有文件的话请自己创建 /etc/rc.local 文件,并通过 sudo chmod +x /etc/rc.local 命令赋予执行权限。如果 rc-local 服务没有开启请通过 sudo systemctl enable --now rc-local 开启。

保存后执行一下刚刚添加的这 4 条命令,让这些命令立即生效。因为 /etc/rc.local 文件开机时系统都会执行一次,所以以后不用再手动执行这些命令了。

sudo ip rule add fwmark 1 lookup 100
sudo ip route add local default dev lo table 100
sudo ip -6 rule add fwmark 1 table 101
sudo ip -6 route add local ::/0 dev lo table 101

编辑 /etc/nftables.conf 文件。注意这里小玲只是省略的无关的规则,请对比原来的文件,把新规则添加进去。切勿直接用下面的内容覆盖原来的文件。

vim /etc/nftables.conf
table inet filter
delete table inet filter

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;
        # 这里请添加下面这 2 条规则,防止有人扫描你的端口,
        # 然后通过连接 HTTP 代理或者 Socks5 代理通过你的宽带上网。
        iifname "ppp0" tcp dport { 7890, 7891, 7892, 7893, 9090 } drop
        iifname "ppp0" udp dport { 7893 } drop
    }

    chain forward {
        type filter hook forward priority filter; policy accept;
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}

table inet clash
delete table inet clash

table inet clash {
    # 定义直连的 IP 段
    set ipv4_addr {
        type ipv4_addr
        flags interval
        elements = {
            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,
            240.0.0.0/4
        }
    }

    set ipv6_addr {
        type ipv6_addr
        flags interval
        elements = {
            ::ffff:0.0.0.0/96,
            64:ff9b::/96,
            100::/64,
            2001::/32,
            2001:10::/28,
            2001:20::/28,
            2001:db8::/32,
            2002::/16,
            fc00::/7,
            fe80::/10
        }
    }

    chain clash-tproxy {
        # 不代理未指定地址,本地地址,广播地址,组播地址。
        fib daddr type { unspec, local, anycast, multicast } return
        # 不代理直连 IP 段。
        ip daddr @ipv4_addr return
        ip6 daddr @ipv6_addr return
        # 不代理 NTP 服务器的端口,
        # 小玲发现不加上这条规则会导致局域网内的设备无法通过 NTP 服务器同步时间。
        udp dport { 123 } return
        # 最后不符合上面条件的流量全部送入 Clash。
        meta l4proto { tcp, udp } meta mark set 1 tproxy to :7893 accept
    }

    chain clash-mark {
        # 不代理未指定地址,本地地址,广播地址,组播地址。
        fib daddr type { unspec, local, anycast, multicast } return
        # 不代理保留地址。
        ip daddr @ipv4_addr return
        ip6 daddr @ipv6_addr return
        # 不代理 NTP 服务器的端口,
        # 小玲发现不加上这条规则会路由器自身无法通过 NTP 服务器同步时间。
        udp dport { 123 } return
        # 最后不符合上面条件的流量打上标记
        meta mark set 1
    }

    chain mangle-output {
        type route hook output priority mangle; policy accept;
        # 代理 TCP 和 UDP 协议,gid 不是 997,从本地发送到外部的流量。
        # 这句是路由器自身的流量走透明代理的关键规则。
        meta l4proto { tcp, udp } skgid != 997 ct direction original jump clash-mark
    }

    chain mangle-prerouting {
        type filter hook prerouting priority mangle; policy accept;
        # 代理从 lo 网卡和 enp1s0 网卡进入的 TCP 和 UDP 协议,从本地发送到外部的流量。
        # 这句是局域网的设备的流量走透明代理的关键规则。
        iifname { lo, enp1s0 } meta l4proto { tcp, udp } ct direction original jump clash-tproxy
    }
}

要代理路由器自身发出的流量时请确保 Clash 发出的流量不经过透明代理,否则会引起回环。这就需要规则里的 gid 设置正确了。

之所以用 iifname { lo, enp1s0 } 限制入口网卡是为了防止 bridge 网络模式下的 Docker 容器发出的流量走透明代理。如果不加上这个限制会导致 bridge 网络模式下的 Docker 容器直接访问不了外网。小玲现在还没有办法让 bridge 网络模式下的 Docker 容器通过 Tproxy 走透明代理。如果你有办法,不妨在评论区告诉小玲。lo 网卡是必须写上去的,如果不写路由器自身发出的流量就不能走代理。enp1s0 请改成你的 LAN 口网卡名称。

启动 Clash

重启 Dnsmasq

sudo docker-compose -f ~/docker-compose.yml restart dnsmasq

启动 Clash

sudo docker-compose -f ~/docker-compose.yml up -d

重载 nftables。

sudo systemctl reload nftables

好了,现在在浏览器打开 http://yacd.haishan.me/?hostname=<你的路由器的 IP 地址>&port=9090&secret=#/ 看看有没有流量数据。如果有,那么透明代理就设置成功啦。现在你的路由器已经能代理局域网和路由器自身的流量了。如果想让路由器上面的 Docker 容器也走透明代理,请把这个容器的网络模式设为 host