PVE 容器搭建 Xray reality 透明网关

Posted by     "风舞山湖" on Wednesday, August 23, 2023

Xray-core 现在使用了 reality 传输协议。这具有更好的隐蔽特性,可以极大地模仿普通网页的流量。引用其官方介绍:

若用 REALITY 取代 TLS,可消除服务端 TLS 指纹特征,仍有前向保密性等,且证书链攻击无效,安全性超越常规 TLS可以指向别人的网站,无需自己买域名、配置 TLS 服务端,更方便,实现向中间人呈现指定 SNI 的全程真实 TLS。

模仿的网站也可以在配置文件中指定。对于服务端、客户端使用的 xray,只要使用最新版本(例如 pre-release v1.8.3)的就可以支持 reality 协议,并不需要单独安装 reality 相关插件。下面是在 PVE 上使用 LXC 容器搭建一个 xray 客户端并使用 reality 传输协议,并将其用作透明网关。

首先在 PVE 创建一个 debian LXC 容器。之所以选择 debian,是因为它的文件系统里集成的工具较为齐全。如果追求精简,那么在 alpine 容器上开始搭建也可以,但中间需要解决一堆软件依赖问题。CPU 使用 1 核心,1GB RAM 和 16GB disk。Xray-core 在运行时使用的内存其实不大,一般不超过 200MB。创建容器时可以开启特权模式、关闭防火墙。为了便于后面往容器里复制文件,在安装完启动后,开启 ROOT 用户的 SSH 登录。在 /etc/ssh/sshd_config 中添加:

PermitRootLogin yes

在 debiam 容器里 Xray 客户端配置文件 local.json 如下,由于 json 不支持注释,所以下面文件分段说明。

{
    "log": {
      "loglevel": "warning",
      "error": "/root/xray/error.log",
      "access": "/root/xray/access.log"
    },

日志记录基本为 warning,刚开始配置或者检查问题时可以设置为 debug,记录详细的信息。

    "inbounds": [
      {
        "tag": "all-in",
        "port": 35791,
        "protocol": "dokodemo-door",
        "settings": {
          "network": "tcp,udp",
          "followRedirect": true
        },
        "sniffing": {
          "enabled": true,
          "destOverride": [
            "http",
            "tls"
          ]
        },
        "streamSettings": {
          "sockopt": {
            "tproxy": "tproxy"
          }
        }
      }
    ],

inbounds 时入站条目。Xray 客户端侦听 35791 端口上的所有数据,由于做透明网关,所以 protocoldokodemo-door,即入站的数据格式不限。

    "outbounds": [
      {
        "tag": "direct",
        "protocol": "freedom",
        "settings": {
          "domainStrategy": "UseIPv4"
        },
        "streamSettings": {
          "sockopt": {
            "mark": 2
          }
        }
      },
      {
        "tag": "proxy",
        "protocol": "vless",
        "settings": {
          "vnext": [
              {
                  "address": "example.com",
                  "port": 443,
                  "users": [
                      {
                          "id": "XXXXXXXXX",
                          "flow": "xtls-rprx-vision",
                          "encryption": "none"
                      }
                  ]
              }
          ]
        },
        "streamSettings": {
          "network": "tcp",
          "security": "reality",
          "realitySettings": {
            "show": false,
            "fingerprint": "chrome",
            "serverName": "www.microsoft.com",
            "publicKey": "xxxxxxxxxx",
            "shortId": "xxxx",
            "spiderX": "/"
          },
          "sockopt": {
            "mark": 2
          }
        }
      },
      {
        "tag": "block",
        "protocol": "blackhole",
        "settings": {
          "response": {
            "type": "http"
          }
        }
      },
      {
        "tag": "dns-out",
        "protocol": "dns",
        "settings": {
          "address": "8.8.8.8",
          "clientIP": "1.2.3.4"
        },
        "proxySettings": {
          "tag": "proxy"
        },
        "streamSettings": {
          "sockopt": {
            "mark": 2
          }
        }
      }
    ],

outbounds 是出站条目。tag:direct 是默认出站条目,不匹配规则的流量都从这个出去,不经过代理。tag:block 是黑洞条目,将广告等流量引导到这里。tag:dns-out 用于 DNS 查询。在这个配置里面将劫持所有发送到本网关 53 端口的 DNS 查询,然后通过代理发送到 8.8.8.8 查询。由于代理服务器通常位于海外,所以clientIP 可以用于通知服务器以指定 IP 位置查询 DNS。这里的 IP 可以使用离你最近的某个公网 IP,如电信、联通给你家光猫、路由器分配的 DNS 服务器 IP 地址。tag:proxy 就是和代理服务器的 reality 连接配置。"address": "example.com" 里面改为自己的代理服务器的 IP 或者域名。port 建议使用 443。id 用于服务器区分不同的客户端,可以使用 xray uuid 生成。streamSettingssecurity 设置使用 reality 协议。serverName 是将要模仿的流量对象网站。publicKey 密钥使用 xray x25519 生成,这里填写公钥,私钥则填写在服务端的配置文件。shortid 可使用十六进制的 0 到 f,长度为 2 的倍数,长度上限为 16。这里的 shortid 需要出现在服务端配置的 shortids 中。

    "dns": {
      "hosts": {
        "my.own.domain": "6.6.6.6"
      },
      "servers": [
        {
          "address": "223.5.5.5",
          "port": 53,
          "domains": [
            "geosite:cn"
          ],
          "expectIPs": [
            "geoip:cn"
          ]
        },
        "8.8.8.8",
        "8.8.4.4",
        "https+local://doh.dns.sb/dns-query"
      ]
    },

配置内置的 DNS,由于在路由阶段, 解析域名为 IP, 并且根据域名解析得到的 IP 进行规则匹配以分流。hosts 可以手动配置 DNS 条目,例如内部网络使用的域名。

    "routing": {
      "domainStrategy": "IPIfNonMatch",
      "rules": [
        {
          "type": "field",
          "inboundTag": [
            "all-in"
          ],
          "port": 53,
          "outboundTag": "dns-out"
        },
        {
          "type": "field",
          "ip": [
            "geoip:cn"
          ],
          "outboundTag": "direct"
        },
        {
          "type": "field",
          "domain": [
            "geosite:category-ads-all"
          ],
          "outboundTag": "block"
        },
        {
          "type": "field",
          "domain": [
            "geosite:geolocation-!cn"
          ],
          "outboundTag": "proxy"
        },
        {
          "type": "field",
          "ip": [
            "91.108.0.0/16",
            "109.239.140.0/24",
            "149.154.160.0/20",
            "2001:67c:4e8::/48",
            "2001:b28:f23d::/48",
            "2001:b28:f23f::/48"
          ],
          "outboundTag": "proxy"
        }
      ]
    }
  }

路由规则,其实是数据分流规则。将不同类型的数据转发到代理、直连或者黑洞。最后一部分是 TG 相关的 IP,可以酌情使用。

服务端的配置则相对简单,功能和上面一样,就不再分段解释。如果有多个客户端使用的话,在 inbounds->settings->clients 中添加多个对应的 id,shortIds 也是同样。

{
    "log": {
      "loglevel": "warning",
      "access": "/var/log/xray/access.log", 
      "error": "/var/log/xray/error.log"
    },
    "dns": {
      "servers": [
        "https+local://dns.google/dns-query",
        "https+local://1.1.1.1/dns-query",
        "localhost"
      ]
    },

    "routing": {
      "domainStrategy": "IPIfNonMatch",
      "rules": [
        {
          "type": "field",
          "ip": [
            "geoip:private" 
          ],
          "outboundTag": "block" 
        },
        {
          "type": "field",
          "domain": [
            "geosite:category-ads-all" 
          ],
          "outboundTag": "block"
        }
      ]
    },

    "inbounds": [
      {
        "listen": "0.0.0.0",
        "port": 443,
        "protocol": "vless",
        "settings": {
          "clients": [
              {
                  "id": "XXXXXXXXX",
                  "flow": "xtls-rprx-vision"
              },
              {
                   "id": "XXXXXXXXX",
                   "flow": "xtls-rprx-vision"
              }
          ],
          "decryption": "none"
        },
        "streamSettings": {
          "network": "tcp",
          "security": "reality",
          "realitySettings": {
              "show": false,
              "dest": "www.microsoft.com:443",
              "xver": 0,
              "serverNames": [
                  "www.microsoft.com"
              ],
              "privateKey": "xxxxxxxxx",
              "minClientVer": "",
              "maxClientVer": "",
              "maxTimeDiff": 0,
              "shortIds": [
                  "xxxx",
                  "xxxx"
              ]
          }
        },
        "sniffing": {
          "enabled": true,
          "destOverride": [
              "http",
              "tls"
          ]
        }
      }
    ],
  
    "outbounds": [
      {
        "tag": "direct",
        "protocol": "freedom"
      },
      {
        "tag": "block",
        "protocol": "blackhole"
      }
    ],
    "policy": {
      "levels": {
          "0": {
              "handshake": 3,
              "connIdle": 180
          }
      }
    }

  }

在客户端做透明代理时,需要运行下面命令:

ip route add local default dev lo table 100
ip rule add fwmark 1 table 100

将下面文件保存为 nft.conf,并设置可执行权限 chmod a+x nft.conf,然后运行该文件。其中RESERVED_IP 定义的私有地址,以及侦听 53 端口的网关地址根据实际情况调整。

#!/usr/sbin/nft -f

flush ruleset

define RESERVED_IP = {
    10.0.0.0/16,
    10.16.0.0/16,
    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,
    224.0.0.0/4,
    240.0.0.0/4,
    255.255.255.255/32
}

table ip xray {
        chain prerouting {
                type filter hook prerouting priority mangle; policy accept;
                ip daddr $RESERVED_IP return
                ip daddr 10.20.1.0/24 tcp dport != 53 return
                ip daddr 10.20.1.0/24 udp dport != 53 return
                ip protocol tcp tproxy to 127.0.0.1:35791 meta mark set 1
                ip protocol udp tproxy to 127.0.0.1:35791 meta mark set 1
        }
        chain output {
                type route hook output priority mangle; policy accept;
                ip daddr $RESERVED_IP return
                ip daddr 10.20.1.0/24 tcp dport != 53 return
                ip daddr 10.20.1.0/24 udp dport != 53 return
                meta mark 2 return
                ip protocol tcp meta mark set 1
                ip protocol udp meta mark set 1
        }
}

在客户端和服务器上运行 xray -c config.json