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规则来实现。

下载与安装

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)配置教程

系列

Gitalking ...

使用 Hugo 构建
主题 StackJimmy 设计