旁路由科学上网配置笔记

之前家里科学上网一直是通过刷了个openwrt的路由装了openclash来上网的,但是受限于openwrt路由器的性能,操作openclash很是不方便,总觉得网速也受到影响,因此准备在家中内网的linux主机上配置一个旁路由用于科学上网。

网络环境:

  • 192.168.2.1 路由器
  • 192.168.2.2 旁路由 (这里我是arch系
  • 192.168.2.100 用户主机

首先是配置clash,当前clash已经改名为mihomo,直接安装

yay -Sy mihomo

实现透明代理有两种方法,一个是tun一个是tproxy,这里我选用tproxy

tproxy-port: 7894 # You can choose any available port, e.g., 7894 or 9898 [4, 6]
routing-mark: 255  # 这个值对应 iptables 规则中的 0xff
dns:
  enable: true
  listen: 0.0.0.0:53
  ipv6: false           # 禁止DNS解析IPv6
  enhanced-mode: redir-host  # 使用 redir-host 模式

  # 这些 DNS 用于解析代理服务器域名(重要!)
  default-nameserver:
    - 114.114.114.114
    - 223.5.5.5
    - 1.1.1.1
  nameserver:
    - 114.114.114.114
    - 8.8.8.8
  fallback:  # 防止 DNS 污染
    - 1.1.1.1
    - 8.8.8.8

配置关键在于设置这三部分:tproxy-port/routing-mark以及dns

我的主机上已经有了systemd-resolved会运行一个stub listener占用了53端口,因此要先配置systemd-resolved,在/etc/systemd/resolved.conf[Resolve]部分禁用stub listener并且设置dns服务器为本地地址

[Resolve]
DNS=127.0.0.1
DNSStubListener=no

另外由于我使用的是manjaro,使用network manager管理网络,我的 /etc/resolv.conf 会被network manager自动覆盖,因此还需要配置network manager。

nmcli connection show --active
# NAME                UUID                                  TYPE      DEVICE          
# Wired connection 1  5d78f62e-5624-313a-a89d-7242ca4a3e3b  ethernet  eno1          

# 把 "Wired connection 1" 替换成你自己的连接名

# 修改 IPv4 DNS
sudo nmcli connection modify "Wired connection 1" ipv4.dns "127.0.0.1"

# 修改 IPv6 DNS (可选,但建议设置为空,避免走 IPv6 DNS)
sudo nmcli connection modify "Wired connection 1" ipv6.dns ""

# 关键一步:设置 DNS 模式为“仅使用手动设置的 DNS”
sudo nmcli connection modify "Wired connection 1" ipv4.ignore-auto-dns yes
sudo nmcli connection modify "Wired connection 1" ipv6.ignore-auto-dns yes

# 重新激活
sudo nmcli connection up "Wired connection 1"

把mihomo启动起来,测试一下能否正常代理连接

curl -x socks5://127.0.0.1:7890 https://www.google.com -v 

接下来配置透明代理,直接抄的网上的,但是需要修改网卡名字、tproxy端口以及clash的标记

#!/usr/sbin/nft -f

## 清空旧规则
flush ruleset

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

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

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

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

## 本机运行了服务并且需要在公网上访问的tcp端口(本机开放在公网上的端口),仅本地局域网访问的服务端口可不用在此变量中,以半角逗号分隔
define local_tcp_port = {
        3000-4000,
        25565,     
        8000-9000,
        1200,   
}

## 要绕过的局域网内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;

        # -----------------------------------------------------------------
        # 核心修改:将最关键的绕过规则放在最顶端,确保它们最先被匹配
        # -----------------------------------------------------------------

        # 1. 绝对优先放行:目标是本地回环地址的流量 (修复你遇到的问题)
        #    这是最明确、最可靠的规则,用于防止本机服务(如DNS)被代理。
        ip daddr 127.0.0.1/32 accept

        # 2. 其次放行:Clash 自身发出的、已经打上标记的流量 (防止循环代理)
        meta mark $clash_mark accept

        # 3. 再次放行:目标地址是保留地址/内网地址的流量
        #    这条规则包含了 127.0.0.1,但放在这里作为双重保险,并处理其他内网IP。
        ip daddr @private_address_set accept

        # 4. 放行本机上其他服务的流量
        #    例如,如果你在本机运行了需要公网访问的SSH或Web服务。
        tcp sport $local_tcp_port accept
        
        # -----------------------------------------------------------------
        # 经过以上关键过滤后,剩下的流量才考虑是否需要代理
        # -----------------------------------------------------------------

        # 5. 将剩余的所有TCP/UDP流量标记,准备重路由到 prerouting 链
        ip protocol { tcp, udp } meta mark set $default_mark
    }

}

配置我稍微调整了一下,让大陆ip也走mihomo,然后通过mihomo内部规则再进行分流。

文件保存成mynftables.nft,启用tproxy还要结合路由规则设置

# 配置透明代理
ip route add local default dev eno1 table 100
ip rule add fwmark 1 lookup 100
nft -f mynftables.nft
# 启用mihomo
mihomo -f config.yaml
# 测试一下是否生效
curl ipinfo.io

生效后就可以配置成服务了,创建文件/etc/systemd/system/mihomo.service

[Unit]
Description = Mihomo 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     = mihomo -f /etc/mihomo/config.yaml

# 启动前先尝试清理,忽略可能的失败 (注意-号的用法)
ExecStartPost = -ip route del local default dev eno1 table 100
ExecStartPost = -ip rule del fwmark 1 lookup 100

# 再执行添加操作
ExecStartPost = ip route add local default dev eno1 table 100
ExecStartPost = ip rule add fwmark 1 lookup 100
ExecStartPost = nft -f <mynftables.nft path>

# 停止时,确保每个清理步骤都被尝试执行
ExecStop      = -nft flush ruleset
ExecStop      = -ip route del local default dev eno1 table 100
ExecStop      = -ip rule del fwmark 1 lookup 100

[Install]
WantedBy = multi-user.target

启用服务

sudo systemctl daemon-reload
sudo systemctl enable mihomo.service
sudo systemctl start mihomo.service

至此这个主机已经可以作为旁路由了,将其他主机的网关手动指向它就可以实现科学上网。在此基础上,我们还可以继续配置路由器的dhcp服务,让dhcp服务自动配置网关到该路由器。

首先需要将旁路由设置成静态ip,不然路由器的dhcp服务修改后我们的网关会指向自己

nmcli connection show
sudo nmcli connection modify "Wired connection 1" ipv4.method manual
sudo nmcli connection modify "Wired connection 1" ipv4.method manual ipv4.addresses 192.168.2.2/24 ipv4.gateway 192.168.2.1 
sudo nmcli connection up "Wired connection 1"

我的路由器是openwrt,配置dhcp直接在lan口的dhcp服务中添加:

3,192.168.2.2 # 配置网关
6,192.168.2.2 # 配置dns服务器

至此配置结束