Featured image of post 我的家庭网络设计思路,开启debian的旁路由之路(四)

我的家庭网络设计思路,开启debian的旁路由之路(四)

本篇主要讲解clash透明代理(Tproxy)的安装和配置,以及涉及到的IP规则和nftables规则。

前言

使用clash实现透明代理有两种方式,一种是 redirect,仅支持TCP流量,另一种是 tproxy,支持TCP和UDP流量。为了能代理UDP流量,所以我们选用 tproxy 的方式来实现。

工作流

nftables 的工作流详见 Netfilter hooks。具体Netfilter的五个链 prerouting input forward output postrouting 是什么关系,以及流向,请参考该链接来加深理解。全部的文档可以查阅本文下方 参考资料 的1和2。

注意,在旁路由自身中运行的程序都工作在应用层,而 tproxy 只能工作在 prerouting 链,所以如果我们要想透明代理旁路由自己产生的流量,要通过设置IP规则让这部分流量在进入 output 链时,将需要代理的流量转回到 prerouting 链。这就需要靠IP规则来实现。

Netfilter hooks

下载与安装

clashclash-meta (现在叫mihomo)中,我选用后者来使用(前者已经删库了)。我默认你已经按照 前文(二) 安装好了必要的软件了。以下为root用户运行的命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
## 检查最新稳定版的版本号,如果获取不到请检查网络
remote_ver=$(curl -sS https://api.github.com/repos/MetaCubeX/mihomo/releases/latest | jq -r .tag_name | sed 's|v||' | grep -v "null"); echo $remote_ver

## 下载最新稳定版(前一句有输出这一句才能正常执行)
cd /tmp
wget -q --progress=bar:dot --show-progress -O "mihomo-linux-amd64-v${remote_ver}.gz" "https://github.com/MetaCubeX/mihomo/releases/download/v${remote_ver}/mihomo-linux-amd64-v${remote_ver}.gz"

## 解压
gzip -d "mihomo-linux-amd64-v${remote_ver}.gz"

## 安装
install -ps mihomo-linux-amd64-v${remote_ver} /usr/local/bin/clash

上述 wget 这一行默认下载的是 AMD64 v3 架构的包,你可以输入以下命令查看是否支持。如若不支持,可以下载名称中带 cgo 或者 compatible 的版本,说明 在此

1
/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 --help | grep supported

如若支持 AMD64 v3 架构,输出将会是下面这样的。

1
2
3
4
5
  x86-64-v3 (supported, searched)
  x86-64-v2 (supported, searched)
  x86_64 (AT_PLATFORM; supported, searched)
  tls (supported, searched)
  x86_64 (supported, searched)

创建服务

clash.service

创建工作目录/var/lib/clash

1
mkdir -p /var/lib/clash

创建文件 /etc/systemd/system/clash.service,内容如下。请注意将 ens18 修改为你的网卡名。这时 clash 的配置文件为 /var/lib/clash/config.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[Unit]
Description = Clash-Meta tproxy daemon.
Wants       = network-online.target subconverter.service
After       = network-online.target

[Service]
Environment   = PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Type          = simple
Restart       = always
LimitNPROC    = 500
LimitNOFILE   = 1000000
ExecStartPre  = sleep 1s
ExecStart     = clash -d /var/lib/clash
ExecStartPost = ip route add local default dev ens18 table 100 ; ip rule add fwmark 1 lookup 100 ; mynftables.nft
ExecStop      = nft flush ruleset ; ip route del local default dev ens18 table 100 ; ip rule del fwmark 1 lookup 100

[Install]
WantedBy = multi-user.target

如果你比较讲究Linux哲学,可以把 ExecStart 改成下面这样,这时 clash 的配置文件为 /etc/clash/config.yaml

1
ExecStart     = clash -d /var/lib/clash -f /etc/clash/config.yaml

如果不想以root用户运行 clash ,可以自行创建用户 clash 和用户组 clash ,然后在 /etc/systemd/system/clash.service[Service] 单元下增加下面几行,同时修改 /var/lib/clash 为该普通用户所有(后续的脚本都是以root用户为运行用户进行的,如果要把 clash 服务调整为普通用户运行,请自行修改脚本)。

1
2
3
4
User  = clash
Group = clash
CapabilityBoundingSet = CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities   = CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE

mynftables.nft

注意到我们在 /etc/systemd/system/clash.serviceExecStartPost 行有执行 mynftables.nft。现在,新建 /usr/local/bin/mynftables.nft,内容如下。请注意按照注释修改,创建好后以命令 chmod +x /usr/local/bin/mynftables.nft 为它增加可执行权限。该文件会引用到另外一个文件 /var/lib/clash/geoip4_cn.nft,你可以先下载这并非是最新的文件,先保存到该位置:geoip4_cn.nft,我们将在 本系列第(七)篇 用定时任务来自动更新。

如果你的代理不支持代理 UDP 流量,可以选择解除 ip protocol udp accept 行的注释。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/sbin/nft -f

## 清空旧规则
flush ruleset

## 只处理指定网卡的流量,要和ip规则中的接口操持一致
define interface = ens18

## clash的透明代理端口
define tproxy_port = 7895

## clash打的标记(routing-mark)
define clash_mark = 666

## 常规流量标记,ip rule中加的标记,要和ip规则中保持一致,对应 "ip rule add fwmark 1 lookup 100" 中的 "1"
define default_mark = 1

## 本机运行了服务并且需要在公网上访问的tcp端口(本机开放在公网上的端口),仅本地局域网访问的服务端口可不用在此变量中,以半角逗号分隔
define local_tcp_port = {
    2222,      # ssh,按需设置
    8080-8088  # nginx webui,按需设置
}

## 要绕过的局域网内tcp流量经由本机访问的目标端口,也就是允许局域网内其他主机主动设置DNS服务器为其他服务器,而非旁路由
define lan_2_dport_tcp = {
    53     # dns查询
}

## 要绕过的局域网内udp流量经由本机访问的目标端口,也就是允许局域网内其他主机主动设置DNS服务器为其他服务器,而非旁路由;另外也允许局域网内其他主机访问远程的NTP服务器
define lan_2_dport_udp = {
    53,    # dns查询
    123    # ntp端口
}

## 保留ip地址
define private_address = {
    127.0.0.0/8,
    100.64.0.0/10,
    169.254.0.0/16,
    224.0.0.0/4,
    240.0.0.0/4,
    10.0.0.0/8,
    172.16.0.0/12,
    192.168.0.0/16
}

## 大陆ip地址
include "/var/lib/clash/geoip4_cn.nft"

table ip clash {

    ## 保留ipv4集合
    set private_address_set {
        type ipv4_addr
        flags interval
        elements = $private_address
    }

    ## 大陆ipv4集合
    set geoip4_cn_set {
        type ipv4_addr
        flags interval
        elements = $geoip4_cn
    }

    ## prerouting链
    chain prerouting {
        type filter hook prerouting priority filter; policy accept;
        ip protocol { tcp, udp } socket transparent 1 meta mark set $default_mark accept # 绕过已经建立的连接
        meta mark $default_mark goto clash_tproxy                                        # 已经打上default_mark标记的属于本机流量转过来的,直接进入透明代理
        fib daddr type { local, broadcast, anycast, multicast } accept                   # 绕过本地、单播、组播、多播地址
        tcp dport $lan_2_dport_tcp accept                                                # 绕过经由本机到目标端口的tcp流量
        udp dport $lan_2_dport_udp accept                                                # 绕过经由本地到目标端口的udp流量
        ip daddr @private_address_set accept                                             # 绕过目标地址为保留ip的地址
        ip daddr @geoip4_cn_set accept                                                   # 绕过目标地址为大陆ip的地址
        # ip protocol udp accept                                                         # 绕过全部udp流量(udp不进行透明代理)
        goto clash_tproxy                                                                # 其他流量透明代理到clash
    }

    ## 透明代理
    chain clash_tproxy {
        ip protocol { tcp, udp } tproxy to :$tproxy_port meta mark set $default_mark
    }

    ## output链
    chain output {
        type route hook output priority filter; policy accept;
        oifname != $interface accept                                   # 绕过本机内部通信的流量(接口lo)
        meta mark $clash_mark accept                                   # 绕过本机clash发出的流量
        fib daddr type { local, broadcast, anycast, multicast } accept # 绕过本地、单播、组播、多播地址
        udp dport { 53, 123 } accept                                   # 绕过本机dns查询、NTP流量
        tcp sport $local_tcp_port accept                               # 绕过本地运行了服务的tcp端口,如果并不需要从公网访问这些端口,可以注释掉本行
        ip daddr @private_address_set accept                           # 绕过目标地址为保留ip的地址
        ip daddr @geoip4_cn_set accept                                 # 绕过目标地址为大陆ip的地址
        ip protocol { tcp, udp } meta mark set $default_mark           # 其他流量重路由到prerouting
    }
}

clash配置文件

暂时先按照下面的形式形成 clash 的配置文件 /var/lib/clash/config.yaml 或者 /etc/clash/config.yaml,我们将在 本系列第(七)篇 用定时任务来自动更新订阅。我只对关键信息进行了注释,未注释的内容你都可以自行按照 官方WIKI 进行设置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
port: 7890
socks-port: 7891
mixed-port: 7893
tproxy-port: 7895  # 和mynftables.nft中的tproxy_port保持一致
routing-mark: 666  # 和mynftables.nft中的clash_mark保持一致
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
ipv6: false        # 不进行IPv6流量代理
find-process-mode: off
external-controller: 0.0.0.0:9090
secret:            # 登陆ui的密码
external-ui: ui       # webui的基础路径
external-ui-name: xd  # webui的下级路径
external-ui-url: https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
unified-delay: true
tcp-concurrent: true
experimental:
  sniff-tls-sni: true
geodata-mode: true
geodata-loader: standard
geox-url:
  geoip: https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat
  geosite: https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat
  mmdb: https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb
profile:
  tracing: true
  store-selected: true
  store-fake-ip: true
sniffer:
  enable: true
  parse-pure-ip: true
  override-destination: true
dns:
  enable: true
  ipv6: false           # 禁止DNS解析IPv6
  listen: 0.0.0.0:7874  # DNS监听端口
  use-hosts: true
  enhanced-mode: redir-host
  default-nameserver: # 建议修改为你最近的DNS服务器
    - 10.0.0.1
    - 101.226.4.6
    - 114.114.114.114
    - 116.228.111.118
    - 223.5.5.5
  nameserver:  # 建议修改为你最近的DNS服务器
    - 10.0.0.1
    - 101.226.4.6
    - 114.114.114.114
    - 116.228.111.118
    - 223.5.5.5
  fallback:
    - https://dns.cloudflare.com/dns-query
    - tls://dns.google:853
    - https://1.1.1.1/dns-query
    - tls://1.1.1.1:853
    - tls://8.8.8.8:853
    - https://public.dns.iij.jp/dns-query
    - https://jp.tiar.app/dns-query
    - https://jp.tiarap.org/dns-query
    - tls://jp.tiar.app
    - tls://dot.tiar.app
  fallback-filter:
    geoip: true
    geoip-code: CN
    geosite:
      - gfw
    ipcidr:
      - 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
      - 240.0.0.0/4
      - 255.255.255.255/32
    domain:
      - +.google.com
      - +.facebook.com
    fake-ip-filter:
    - +.*
proxies: # 以下为你的代理节点、分组及代理规则
proxy-groups:
rules:

clash其他相关文件

clash 还需要一些配套文件,在启动前先下载下来。我们将在 本系列第(七)篇 用定时任务来自动更新这些文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cd /var/lib/clash
wget -q --progress=bar:dot --show-progress -O country.mmdb https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb
wget -q --progress=bar:dot --show-progress -O geosite.dat  https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat
wget -q --progress=bar:dot --show-progress -O geoip.dat    https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat

mkdir -p ui
cd ui
wget -q --progress=bar:dot --show-progress -O xd.zip https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
unzip -oqq xd.zip
mv metacubexd-gh-pages xd

启用服务

完全配置好以后,我们可以设置 /etc/systemd/system/clash.service 为开启自动启动,并立即启动起来。

1
systemctl enable --now clash.service

后续如想查看日志,我们直接使用Debian自带的工具来查看:

1
journalctl -efu clash.service

如果日志报错 ip: RTNETLINK answers: File exists,请手动将旧的ip路由及ip规则清空,其实就是多执行几次 /etc/systemd/system/clash.serviceExecStop 这一行的命令,出现 RTNETLINK answers: No such processRTNETLINK answers: No such file or directory 为止,表示旧的ip路由及ip规则都清空了。

1
nft flush ruleset ; ip route del local default dev ens18 table 100 ; ip rule del fwmark 1 lookup 100

如果想要重启:

1
systemctl restart clash.service

查看当前的 nftables 规则,如果为空可以手动输入 mynftables.nft 以查看错误,如有错误,请自行根据输出排故。

1
nft list ruleset

访问webui:http://<IP>:9090/ui/xd,其中 9090 ui xd 均在配置文件中由你定义,分别对应 external-controller external-ui external-ui-name

执行逻辑

大致在 mynftables.nft 这个文件的注释也解释过每句的作用了,这里再捊一下。

对照着题图,局域网中的其他客户端的流量在通过旁路由时,进入 nftablesprerouting 链,然后判断第69-76行的规则,如果规则匹配就以 accept 直接收受并发往目的地,不会再转到 clash;如果全都不匹配,会在最后匹配上 goto clash_tproxy,也就是进入 ip protocol { tcp, udp } tproxy to :$tproxy_port meta mark set $default_mark 这句,这句将会把流量转到 clash。需要提一句的是第70行的规则是针对从本机 output 链发过来的流量的,没有第70行,这部分流量同样会在最后匹配到第77行的 goto clash_tproxy

经过 clash 代理后,流量会被打上标记 clash_mark,也就是第13行定义的 666clash 工作在题图中的应用层,经过 clash 处理过的流量会在 output 链中被第89行直接接收并发往目的地,不会再一次进行代理,这样就防止了流量一直在走环路的问题。

本机产生的流量类似,它们会在 output 链中先匹配第88-94行,如果匹配就直接接收并发往目的地了,而剩余没有匹配到的会在第95行被打上 $default_mark,也就是在第16行定义的 1,这个标记和上方创建的 clash.servicefwmark 11 一致。被打上这个标记的流量会按照 clash.service 定义的 ip routeip rule,重路由进入 prerouting 链,然后根据规则确定是直接接收还是转向 clash

至于 forward 转发链,我们并没有设置它,那么转发的默认 policy 就是 accept,将直接全部接受。我已经在 前文(一) 中提及了我只在主路由爱快上设置防火墙:IPv4IPv6,旁路由对这类流量直接转发,不再过滤。所以,针对运行在我NAS 10.0.0.11 上的服务以及其他众多运行在 Docker MacVLAN 网络上的容器,我直接从主路由爱快转发端口到它们即可,无需再经过旁路由额外中转。

参考资料

  1. nftables wiki

  2. nft Man Page

  3. clash nftables 透明代理(TPROXY)

  4. Project X 透明代理(TProxy)配置教程

系列

Built with Hugo
主题 StackJimmy 设计