Xray透明代理配置方案,旁路由模式支持代理Docker流量


传统的代理,常见的有 HTTP 代理、SOCKS 代理,比如 Windows 系统你可以在 网络和 Internet - 代理 中进行设置,对浏览器的访问生效。如果是其他的程序,就要看支不支持系统代理的配置了,如果不支持的话,还需要类似 SocksCap 之类的程序去劫持应用程序的请求来使用代理。相对于传统的代理,透明代理则无需配置系统代理,而是通过配置路由规则实现代理,这对几乎所有的应用程序都有效。这里介绍一个 Linux 系统基于 Xray 配置透明代理的方案,这样除了本机可以使用代理,Docker 等服务也可以使用代理,还可以支持代理局域网中其他机器的请求。

安装 Xray

Github仓库

首先,我们需要先安装 Xray,当然你也可以选择使用 V2ray,这里我们采用官方脚本进行安装。

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install -u root

安装完毕后,默认的文件位置如下:

installed: /etc/systemd/system/xray.service
installed: /etc/systemd/system/[email protected]

installed: /usr/local/bin/xray
installed: /usr/local/etc/xray/*.json

installed: /usr/local/share/xray/geoip.dat
installed: /usr/local/share/xray/geosite.dat

installed: /var/log/xray/access.log
installed: /var/log/xray/error.log

国内对github的访问向来是时断时续的,如果安装失败可以等一等,或者使用科学上网工具,这里推荐可以 注册 一个,目前有免费流量可以薅羊毛。

接下来,我们需要修改 /usr/local/etc/xray/config.json 文件,在 inbounds 中开启透明代理入口,下面是一个简单的例子。

{
  "log": {
    "loglevel": "warning",
    "error": "/var/log/xray/error.log",
    "access": "/var/log/xray/access.log"
  },
  "inbounds": [
    {
      "tag": "all-in",
      "port": 12345,
      "protocol": "dokodemo-door",
      "settings": {
        "network": "tcp,udp",
        "followRedirect": true
      },
      "sniffing": {
        "enabled": true,
        "destOverride": ["http", "tls"]
      },
      "streamSettings": {
        "sockopt": {
          "tproxy": "tproxy"
        }
      }
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {
        "domainStrategy": "UseIPv4"
      },
      "streamSettings": {
        "sockopt": {
          "mark": 2
        }
      }
    },
    {
      "tag": "proxy",
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "服务端域名",
            "port": 443,
            "users": [
              {
                "id": "UUID",
                "flow": "xtls-rprx-vision",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "xtls",
        "sockopt": {
          "mark": 2
        }
      }
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {
        "response": {
          "type": "http"
        }
      }
    },
    {
      "tag": "dns-out",
      "protocol": "dns",
      "settings": {
        "address": "8.8.8.8"
      },
      "proxySettings": {
        "tag": "proxy"
      },
      "streamSettings": {
        "sockopt": {
          "mark": 2
        }
      }
    }
  ],
  "dns": {
    "hosts": {
      "服务端域名": "服务端 IP"
    },
    "servers": [
      {
        "address": "119.29.29.29",
        "port": 53,
        "domains": ["geosite:cn"],
        "expectIPs": ["geoip:cn"]
      },
      {
        "address": "223.5.5.5",
        "port": 53,
        "domains": ["geosite:cn"],
        "expectIPs": ["geoip:cn"]
      },
      "8.8.8.8",
      "1.1.1.1",
      "https+local://doh.dns.sb/dns-query"
    ]
  },
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "inboundTag": ["all-in"],
        "port": 53,
        "outboundTag": "dns-out"
      },
      {
        "type": "field",
        "ip": ["8.8.8.8", "1.1.1.1"],
        "outboundTag": "proxy"
      },
      {
        "type": "field",
        "domain": ["geosite:category-ads-all"],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "domain": ["geosite:geolocation-!cn"],
        "outboundTag": "proxy"
      },
      {
        "type": "field",
        "ip": ["geoip:telegram"],
        "outboundTag": "proxy"
      }
    ]
  }
}

上面配置中,端口 12345 就是用来接收透明代理请求的入口,从该入口进入的请求都会根据路由规则,凡是解析为国外 IP 的请求都会走 proxy 的出口代理,你需要按你的实际配置进行修改。详细的配置可以参考 官方文档

配置透明代理

Github仓库

透明代理的配置比较复杂,这里推荐使用上面的开源工具 ss-tproxy 进行自动配置。首先,需要安装依赖项 chinadns-ng,用来实现 DNS 分流。

首先,我们从 chinadns-ng 下载最新版本,选择 chinadns-ng+wolfssl 开头的,然后根据你的 CPU 架构进行选择。

# x86_64架构
curl -o chinadns-ng https://github.com/zfl9/chinadns-ng/releases/download/2024.12.22/chinadns-ng+wolfssl@x86_64-linux-musl@x86_64@fast+lto
# arm64架构
curl -o chinadns-ng https://github.com/zfl9/chinadns-ng/releases/download/2024.12.22/chinadns-ng+wolfssl@aarch64-linux-musl@generic+v8a@fast+lto
# 安装
chmod +x chinadns-ng
mv chinadns-ng /usr/local/bin

接下来我们安装 ss-tproxy

git clone https://github.com/zfl9/ss-tproxy
cd ss-tproxy
chmod +x ss-tproxy
install ss-tproxy /usr/local/bin
install -d /etc/ss-tproxy
install -m 644 *.conf *.txt *.ext /etc/ss-tproxy
install -m 644 ss-tproxy.service /etc/systemd/system
systemctl daemon-reload
systemctl enable ss-tproxy

然后修改 /etc/ss-tproxy/ss-tproxy.conf 配置文件,每个配置项的作用配置文件中已经有详细的说明,这里只列出最关键的几项。

## mode
#mode='global'  # 全局:{ignlist}走直连,其他走代理
#mode='gfwlist' # 黑名单:{gfwlist}走代理,其他走直连 (回国模式也是这个,后面有详细说明)
mode='chnroute' # 大陆白名单:{gfwlist}走代理,{ignlist,chnlist,chnroute}走直连,其他走代理
                         
## ipv4/6
ipv4='true'     # 是否对ipv4启用'透明代理': true启用 false不启用
ipv6='true'     # 是否对ipv6启用'透明代理': true启用 false不启用
                         
## tproxy
tproxy='true'   # true:  TPROXY(tcp)   + TPROXY(udp) ## 纯 tproxy 模式 ##
                # false: REDIRECT(tcp) + TPROXY(udp) ## redirect 模式  ##

## tcponly
tcponly='false' # true:仅代理TCP流量 | false:代理TCP和UDP流量

## selfonly                           
selfonly='true' # true: 只代理ss-tproxy主机(本机)传出的流量

## proxy
proxy_tcpport='12345'    # ss/ssr/v2ray/ipt2socks 等本机进程的 TCP 监听端口,该端口支持"透明代理"
proxy_udpport='12345'    # ss/ssr/v2ray/ipt2socks 等本机进程的 UDP 监听端口,该端口支持"透明代理"

这里端口 12345 要和前面 Xray 的入站端口对应上。

配置完成后,第一次执行前还需要进行一些准备工作。

# 更新 gfwlist.txt
ss-tproxy update-gfwlist
# 更新 chnlist.txt
ss-tproxy update-chnlist
# 更新 chnroute.txt
ss-tproxy update-chnroute
# 将 Xray 运行的用户组设置为 proxy
ss-tproxy set-proxy-group xray
sed -i '/^User=/a Group=proxy' /etc/systemd/system/xray.service
systemctl daemon-reload
systemctl restart xray
# 将 chinadns-ng 运行的用户组设置为 proxy-dns
ss-tproxy set-dns-group chinadns-ng

现在,执行 ss-tproxy start 就可以启动透明代理了。

[root@localhost ~]# ss-tproxy start
mode:           chnroute
proxy/tcp:      [running]
proxy/udp:      [running]
chinadns:       [running]

启动成功后,本地的流量就会根据地域进行分流,需要转发的流量经由透明代理的 12345 端口转发给 Xray 处理。

代理局域网流量

代理局域网流量需要修改 /etc/ss-tproxy/ss-tproxy.conf 中的 selfonly 配置项为 false,这样你前面安装的透明代理服务就可以作为旁路由来使用了。

然后需要代理的主机需要修改网关为透明代理服务器的 IP,即可实现流量代理,而无需设置系统代理了。

代理 Docker 流量

做完前面的步骤后,你会发现本机 Docker 容器里依然无法通过代理上网,当然最简单的方式是使用 --network=host 参数启动容器以使用本机网络代理上网。但是如果你想使用网桥模式时,会发现 Docker 发出的流量无法被代理,原因分析如下。

Docker 默认会加载 br_netfilter 内核模块,用于桥接网络,也就是 docker0br_netfilter 引入了几个内核参数,位于 /proc/sys/net/bridge/ 目录,主要关心这两个:

  • bridge-nf-call-iptables:在桥接层(2层)调用 iptables(3层)规则,默认为 1(启用)
  • bridge-nf-call-ip6tables:在桥接层(2层)调用 ip6tables(3层)规则,默认为 1(启用)

bridge(网桥)是 Linux 内核在软件层面实现的一个虚拟“交换机”。

网桥/数据链路层/L2 上下文调用 网络层/L3 是有问题的,违反了网络分层模型;因为 iptables 相关代码都是假设它们在 网络层/L3 上下文被调用,这是完全合理的假设;在网桥中启用 call-iptables 功能后,这些 iptables 代码可能就会出现问题,因为调用这些函数时,所处的上下文是 网桥/数据链路层/L2 层。

相关参考:Bridge filtering with nftables

回到透明代理,按照正常的顺序,会先调用 IP 层的 ip_rcv_core 函数,然后再调用 netfilterPREROUTING 钩子,调用 xt_TPROXY 的代码,执行透明代理操作。

call-iptables 的情况下,br_netfilter 做了很多特殊处理,保证一个数据包不会多次进入同一个 netfilter 钩子。因此,调用 ip_rcv_core 后,数据包不会进入 IP 层的 PREROUTING 钩子,也就是说 tproxy_tg4 不会调用第二次。

总结:我们需要将 call-iptables 改为 0,禁止在 L2 调用 L3 代码,如下修改 /etc/ss-tproxy/ss-tproxy.conf 中的 pre_start 钩子函数:

pre_start() {
    if is_tcp_tproxy || is_enabled_udp; then
        modprobe br_netfilter # 确保模块已加载,否则 sysctl 会提示找不到条目
        sysctl -wq net.bridge.bridge-nf-call-iptables=0
        sysctl -wq net.bridge.bridge-nf-call-ip6tables=0
        sysctl -wq net.bridge.bridge-nf-call-arptables=0 # 与透明代理无关,不关也可以
    fi
}

现在我们重启 ss-tproxy restart 之后,就可以在网桥模式下代理 Docker 容器的流量了。但是如果你使用了 UFW 的话,会发现还是无法代理上网,经过分析发现在 iptablesfilterufw-not-local 规则链中被 DROP 了,所以需要将来自于 docker0 的流量放行。

iptables -t filter -I ufw-not-local -i docker0 -j RETURN

你也可以将规则加入到 /usr/lib/systemd/system/docker.serviceExecStartPostExecStopPost 中,如下:

...

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStartPost=/usr/sbin/iptables -t filter -I ufw-not-local -i docker0 -j RETURN
ExecStopPost=/usr/sbin/iptables -t filter -D ufw-not-local -i docker0 -j RETURN
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutStartSec=0
RestartSec=2
Restart=always

...

文章作者: Mingy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mingy !
评论
  目录