Skip to content

Migrate to Layer 3 traffic routing

近来突然不想用应用层代理(Mihomo)了;恰逢要做计网的实验,不能一直开着这样的代理,那我便换一个方案。

由于我的代理节点全部是自建的,我可以随便选择协议;所以便有了这个方案:

我们可以先用组网工具(比如我这里用的 Easytier)建立连接,然后在这个网络里面跑 GRE 隧道,这样我们就先在我们的电脑和海外的机器之间建立了一条可以被视为网络层的通道了,可以直接通过这个隧道来传输经过组网工具加密后的裸数据包;接着,我们可以使用 nftables 针对 IP 进行内核级的分流,同样直接传输原始的数据包,这样就差不多做完了。

最后的结果差不多是这样的,我们可以让所有连接电脑热点的设备(现在这个电脑可以被称为软路由了)顺利进行 traceroute 等操作,就像这样

配置 Easytier 组网

这里就不多说了;由于我们后续在用 nftables 进行分流的时候需要绕开 Easytier 的流量,所以我们需要在配置最后加一个小的 flag

[flag]
socket_mark = 131072

也就是 0x20000;这样我们就可以很方便的排除掉不应该分流的流量。

最后,我将以这样的网络进行后续的操作(已省略无关内容)

Easytier Peer
$ easytier-cli peer
| ipv4              | hostname        | cost     | lat(ms) | loss |
|-------------------|-----------------|----------|---------|------|
| 100.100.100.2/24  | archlinux       | Local    | -       | -    |
| 100.100.100.11/24 | zouter-hk       | p2p      | 63.09   | 0.0% |

建立 GRE L3 隧道

我们大概的配置是这样的

Easytier GRE GRE Interface
Router 100.100.100.2 10.200.0.2/30 gre-hk
HK VPS 100.100.100.11 10.200.0.1/30 gre-router

先加载 GRE 内核模块

sudo modprobe ip_gre
echo "ip_gre" | sudo tee -a /etc/modules-load.d/gre.conf

然后我们先在本机做好配置

本机侧(Router)

/etc/l3-router/router.conf
ET_LOCAL=100.100.100.2
ET_REMOTE=100.100.100.11

GRE_LOCAL=10.200.0.2
GRE_PEER=10.200.0.1
GRE_PREFIX=30
GRE_MTU=1280

ROUTE_TABLE=100

ROUTE_MARK=0x10000
ROUTE_MARK_MASK=0x10000

DIRECT_MARK=0x20000
DIRECT_MARK_MASK=0x20000

然后开启 IPv4 Forwarding

/etc/sysctl.d/80-l3-router.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
sudo sysctl --system

然后创建一个 bash 脚本来管理网络接口

/usr/local/sbin/l3-router-gre
#!/bin/sh
set -eu

. /etc/l3-router/router.conf

# 等待 Easytier Peer 可达
until ip -4 route get "$DIRECT_REMOTE" 2>/dev/null | grep -q "src $DIRECT_LOCAL"; do
    sleep 1
done

ip link show gre-hk >/dev/null 2>&1 && ip tunnel del gre-hk

ip tunnel add gre-hk \
    mode gre \
    local "$DIRECT_LOCAL" \
    remote "$DIRECT_REMOTE" \
    ttl 64

ip addr replace "$GRE_LOCAL/$GRE_PREFIX" dev gre-hk
ip link set gre-hk mtu "$GRE_MTU"
ip link set gre-hk up

ip route replace default dev gre-hk table "$ROUTE_TABLE"

ip rule del \
    priority 9000 \
    fwmark "$DIRECT_MARK/$DIRECT_MARK_MASK" \
    table main 2>/dev/null || true

ip rule del \
    priority 10000 \
    fwmark "$ROUTE_MARK/$ROUTE_MARK_MASK" \
    table "$ROUTE_TABLE" 2>/dev/null || true

ip rule add \
    priority 9000 \
    fwmark "$DIRECT_MARK/$DIRECT_MARK_MASK" \
    table main

ip rule add \
    priority 10000 \
    fwmark "$ROUTE_MARK/$ROUTE_MARK_MASK" \
    table "$ROUTE_TABLE"
sudo chmod +x /usr/local/sbin/l3-router-gre

再使用 systemd 来管理这个脚本

/etc/systemd/system/l3-router-gre.service
[Unit]
Description=L3 GRE tunnel over EasyTier
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/l3-router-gre
RemainAfterExit=yes
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now l3-router-gre

海外侧(HK VPS)

接下来在我们的 VPS 上配置好出口

/etc/l3-vps.conf
ET_LOCAL=100.100.100.11
ET_REMOTE=100.100.100.2

GRE_LOCAL=10.200.0.1
GRE_PREFIX=30
GRE_MTU=1280

同样配置 IPv4 Forwarding

/etc/sysctl.d/80-l3-vps.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
sudo sysctl --system

然后配有关脚本

/usr/local/sbin/l3-vps-gre
#!/bin/sh
set -eu

. /etc/l3-vps.conf

# 等待 Easytier Peer 可达
until ip -4 route get "$ET_REMOTE" 2>/dev/null | grep -q "src $ET_LOCAL"; do
    sleep 1
done

ip link show gre-router >/dev/null 2>&1 &&
    ip tunnel del gre-router

ip tunnel add gre-router \
    mode gre \
    local "$ET_LOCAL" \
    remote "$ET_REMOTE" \
    ttl 64

ip addr replace "$GRE_LOCAL/$GRE_PREFIX" dev gre-router
ip link set gre-router mtu "$GRE_MTU"
ip link set gre-router up
sudo chmod +x /usr/local/sbin/l3-vps-gre

以及相关 systemd 配置

/etc/systemd/system/l3-vps-gre.service
[Unit]
Description=L3 GRE endpoint over EasyTier
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/l3-vps-gre
RemainAfterExit=yes
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now l3-vps-gre

那么我们的第一步就做好了。现在可以用 ip a 在两边看到这个网络接口,你也可以 ping 一下看看能否连上。

接下来我们开始配 nftables

配置海外侧 nftables

首先在海外的 VPS 上配置好,让我们可以传输代理流量

/etc/l3-vps.nft
# TCP MSS Clamp,不加容易出现 MTU 相关问题
table inet l3vps_mss {
    chain forward {
        type filter hook forward priority mangle
        policy accept

        iifname "gre-router" \
            tcp flags syn \
            tcp option maxseg size set 1240

        oifname "gre-router" \
            tcp flags syn \
            tcp option maxseg size set 1240
    }
}

table ip l3vps_nat {
    chain postrouting {
        type nat hook postrouting priority srcnat
        policy accept

        ip saddr 10.200.0.2 \
            oifname "eth0" \
            masquerade
    }
}

然后是加载脚本和对应的 systemd

/usr/local/sbin/l3-vps-nft-load
#!/bin/sh
set -eu

nft list table ip l3vps_mss >/dev/null 2>&1 &&
    nft delete table ip l3vps_mss || true

nft list table ip l3vps_nat >/dev/null 2>&1 &&
    nft delete table ip l3vps_nat || true

nft -f /etc/l3-vps.nft
/etc/systemd/system/l3-vps-nft.service
[Unit]
Description=L3 VPS NAT
After=l3-vps-gre.service nftables.service
Requires=l3-vps-gre.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/l3-vps-nft-load
ExecReload=/usr/local/sbin/l3-vps-nft-load
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

然后启动他们

sudo systemctl daemon-reload
sudo chmod +x /usr/local/sbin/l3-vps-nft-load
sudo systemctl enable --now /etc/systemd/system/l3-vps-nft

接下来可以在本机测试一下

$ ping 1.1.1.1 -I gre-hk -c 5
PING 1.1.1.1 (1.1.1.1) from 10.200.0.2 gre-hk: 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=53 time=63.9 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=53 time=63.9 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=53 time=64.0 ms
64 bytes from 1.1.1.1: icmp_seq=4 ttl=53 time=64.6 ms
64 bytes from 1.1.1.1: icmp_seq=5 ttl=53 time=64.0 ms

--- 1.1.1.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4002ms
rtt min/avg/max/mdev = 63.859/64.074/64.615/0.275 ms

这样就是没问题了。接下来在本机进行分流

配置本机 nftables

这里我们先不管 IPv6,只按 IPv4 来进行分流。

/etc/l3-router/router.nft
table inet l3route {
    # 来自内网的地址
    set lan_src4 {
        type ipv4_addr
        flags interval
        auto-merge

        elements = {
            10.0.0.0/8,
            100.64.0.0/10,
            172.16.0.0/12,
            192.168.0.0/16
        }
    }

    # 不应进入 GRE 的内网地址
    set private4 {
        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.168.0.0/16,
            198.18.0.0/15,
            224.0.0.0/4,
            240.0.0.0/4,
            100.100.100.0/24
        }
    }

    # 中国 IP
    set cn4 {
        type ipv4_addr
        flags interval
        auto-merge
    }

    chain classify {
        ip daddr @private4 return # 私有/特殊地址直连

        ip daddr @cn4 return # 中国大陆公网 IP 直连

        meta mark set meta mark | 0x10000 # 其他 IPv4 走国外 GRE
    }

    chain prerouting {
        type filter hook prerouting priority mangle
        policy accept

        ip saddr @lan_src4 jump classify
    }

    chain output {
        type route hook output priority mangle
        policy accept

        meta mark & 0x20000 != 0 return # 不应被送进 GRE 的,如 Easytier

        jump classify
    }
}

table ip l3route_nat {
    chain postrouting {
        type nat hook postrouting priority srcnat
        policy accept

        oifname "gre-hk" masquerade
    }
}

接下来,我们处理中国 IPv4 的分流。随便找个地方下载,比如这里,然后用脚本转成 nftables 的格式,比如

#!/bin/sh
set -eu

SRC=/etc/l3-router/cn4.cidr
OUT=/etc/l3-router/cn4.nft
TMP="${OUT}.tmp"

{
    echo 'add element inet l3route cn4 {'
    grep -F '.' "$SRC" | sed 's/$/,/'
    echo '}'
} > "$TMP"

mv "$TMP" "$OUT"

(这里你可能需要替换一下 SRC 变量)

然后我们新建加载 nftables 的脚本和对应的服务

/usr/local/sbin/l3-router-nft-load
#!/bin/sh
set -eu

nft list table inet l3route >/dev/null 2>&1 &&
    nft delete table inet l3route || true

nft list table ip l3route_nat >/dev/null 2>&1 &&
    nft delete table ip l3route_nat || true

nft -f /etc/l3-router/router.nft

if [ -s /etc/l3-router/cn4.nft ]; then
    nft -f /etc/l3-router/cn4.nft
fi
/etc/systemd/system/l3-router-nft.service
[Unit]
Description=L3 router nftables rules
After=l3-router-gre.service nftables.service
Requires=l3-router-gre.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/l3-router-nft-load
ExecReload=/usr/local/sbin/l3-router-nft-load
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now l3-router-nft

这样就完成啦。我们可以尝试一下他是不是会自动的分流,试一下 traceroute

$ nexttrace 1.1.1.1
NextTrace v1.7.3 2026-08-23T16:13:15Z 40ac803
[NextTrace API] preferred API IP - 103.117.102.27 - 382.32ms - DMIT.NRT
IP Geo Data Provider: NextTrace-API
172.26.104.55 -> 1.1.1.1, 30 hops max, 28 byte packets, ICMP mode
1   10.200.0.1      *                         RFC1918
                                              71.71 ms / 63.12 ms / 62.89 ms
2   140.150.236.1   AS205548                  中国 香港   zouter.io
                                              63.89 ms / 63.66 ms / 63.47 ms
3   10.21.0.48      *                         RFC1918
                                              64.06 ms / 63.38 ms / 64.09 ms
4   10.21.0.62      *                         RFC1918
                                              64.19 ms / 63.42 ms / 71.51 ms
5   36.255.57.178   *                         中国 香港
    49304.hkg.equinix.com                     63.72 ms / 63.58 ms / 63.62 ms
6   36.255.57.108   AS13335                   中国 香港  Equinix Hong Kong - Cloudflare - 300Gbps cloudflare.com
    13335-hk2-ix.equinix.com                  63.90 ms / 64.07 ms / 64.12 ms
7   103.22.203.1    AS13335  [CLOUDFLARE-AP]  中国 香港   cloudflare.com
                                              71.14 ms / * ms / * ms
8   103.22.203.79   AS13335  [CLOUDFLARE-AP]  中国 香港   cloudflare.com
                                              65.25 ms / * ms / * ms
9   1.1.1.1         AS13335                   Anycast    cloudflare.com
    one.one.one.one                           64.49 ms / 63.81 ms / 64.06 ms
Trace Stopped: Destination Reached at Hop 9 (ICMP Echo Reply from 1.1.1.1)

可以看到这里成功的自动走我们的 VPS 了,这样就做完了

吗?

配置 DNS

很容易我们发现出现 DNS 污染了,如果我想同时保证校内的解析的话。这里我们直接用 MosDNS 进行分流

/etc/mosdns/config.yaml
log:
  level: info

plugins:
  - tag: geosite_cn
    type: domain_set
    args:
      files:
        - /etc/mosdns/rules/geosite_cn.txt

  - tag: forward_nju
    type: forward
    args:
      concurrent: 2
      upstreams:
        - tag: nju1
          addr: 10.28.253.4
        - tag: nju2
          addr: 10.12.253.4

  - tag: forward_cn
    type: forward
    args:
      concurrent: 2
      upstreams:
        - tag: dnspod
          addr: https://doh.pub/dns-query
          dial_addr: 1.12.12.12

        - tag: alidns
          addr: https://dns.alidns.com/dns-query
          dial_addr: 223.5.5.5

  - tag: forward_foreign
    type: forward
    args:
      concurrent: 2
      upstreams:
        - tag: cloudflare
          addr: https://cloudflare-dns.com/dns-query
          dial_addr: 1.1.1.1

        - tag: google
          addr: https://dns.google/dns-query
          dial_addr: 8.8.8.8

  - tag: seq_nju
    type: sequence
    args:
      - exec: $forward_nju

  - tag: seq_cn
    type: sequence
    args:
      - exec: $forward_cn

  - tag: seq_foreign
    type: sequence
    args:
      - exec: $forward_foreign

  - tag: main
    type: sequence
    args:
      - matches:
          - qtype 28
        exec: reject 0

      - exec: cache 8192

      - matches:
          - has_resp
        exec: accept

      - matches:
          - qname nju.edu.cn
        exec: goto seq_nju

      - matches:
          - qname $geosite_cn
        exec: goto seq_cn

      - exec: goto seq_foreign

  - tag: udp_server
    type: udp_server
    args:
      entry: main
      listen: 127.0.0.1:53

  - tag: tcp_server
    type: tcp_server
    args:
      entry: main
      listen: 127.0.0.1:53

其中,/etc/mosdns/rules/geosite_cn.txt 可以用 v2dat 转换一下得到。

启动 MosDNS,然后我们配置本机和热点的 DNS 使用这个 DNS

/etc/NetworkManager/dnsmasq-shared.d/mosdns.conf
no-resolv
server=127.0.0.1#53

这样就可以正常使用啦。

最后

如果你还想继续分流的话,可以用相同的方法连接下一台 VPS,然后在 nftables 和 MosDNS 里面配好。或者,你也可以集成一个 Mihomo 进去,参考我的配置

改 MosDNS 的配置,国外部分统一使用 Mihomo 提供的 DNS

  - tag: forward_foreign
    type: forward
    args:
      upstreams:
        - tag: mihomo
          addr: 127.0.0.1:1053

然后你可以参考我的 Mihomo 配置

dns:
  enable: true
  listen: 127.0.0.1:1053
  ipv6: false
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  # 只对需要重新分流的域名使用 fake-ip
  fake-ip-filter-mode: rule
  fake-ip-filter:
    - RULE-SET,claude,fake-ip
    - GEOSITE,anthropic,fake-ip
    - GEOSITE,category-ntp,fake-ip
    - GEOSITE,youtube,fake-ip # 单独分流 Youtube
    - GEOSITE,category-ai-!cn,fake-ip
    - GEOSITE,google,fake-ip
    - MATCH,real-ip
  # DNS 全部走 gre-hk 出口,防止 DNS 污染
  nameserver:
    - https://1.1.1.1/dns-query#gre-hk
    - https://8.8.8.8/dns-query#gre-hk
  proxy-server-nameserver:
    - https://1.1.1.1/dns-query#gre-hk
    - https://8.8.8.8/dns-query#gre-hk
tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-redirect: false
  auto-detect-interface: false
  gso: true
  gso-max-size: 65536
  # 不劫持 DNS
  dns-hijack: []
  iproute2-table-index: 2022
  iproute2-rule-index: 9500
  route-address:
    - 198.18.0.0/16
    - 160.79.104.0/21 # Claude 分流
proxy-providers:
  sprite:
    type: http
    url: https://example.com/
    override:
      interface-name: eno1 # 填你的主要上网网卡
      routing-mark: 131072 # 和 Easytier 一样不走 GRE
      ip-version: ipv4

这样就可以大部分流量都直连,小部分走 Mihomo 的 fake-ip 进行分流了。

Comments