--- title: 利用一台小鸡实现网络自由 date: '2022-10-06' tags: ['SNI', 'TLS', 'Reverse Proxy', '反向代理', '正向代理', ‘内网穿透', 'Caddy', 'Xray', 'Vless'] draft: false summary: SNI Proxy 进行 TLS 分流;Caddy 对网站和 Xray 进行反向代理;Xray 实现正向、反向代理(内网穿透)。 --- ## 前言 由于条件所限,我在住所使用了一台 AMD 小主机运行了 PVE,上面跑了许多服务,他们有一个共同点——具有 Web UI。另一方面,我的宽带使用共享公网 IP,使用 Full Cone NAT,这就导致我在外难以方便地访问内网服务。为了解决这个令人不爽的问题,我准备使用 VPS 主机作为中间人,用于反射相关流量。 比较悲剧的是,在撰写本文时,VPS 的 443 端口被干掉……不过还好,VPS 即将到期,如果不能恢复,就换别的了。 ## 网络拓扑图 简单画一下,大概网络拓扑长下面这样: ```mermaid graph TB; subgraph CLIENT BROWSER(Browser) XRAY-C(Xray) GIT-C(Git) end subgraph VPS SNI-PROXY(SNI Proxy) CADDY-S(Caddy) XRAY-S(Xray) end subgraph HOME CADDY-H(Caddy) XRAY-H(Xray) GIT-S(Git) end BROWSER--https://www.example.com:443-->SNI-PROXY--https://www.example.com:443-->CADDY-S BROWSER--https://home.example.com:443-->SNI-PROXY--https://home.example.com:443-->XRAY-H--https://home.example.com:443-->CADDY-H XRAY-C--vless://vless.example.com:443-->SNI-PROXY--vless://vless.example.com:443-->XRAY-S XRAY-C--vmess://vmess.example.com:443-->SNI-PROXY--vmess://vmess.example.com:443-->CADDY-S--vmess://vmess.example.com WITHOUT LTS-->XRAY-S BROWSER--http://www.example.com:80-->CADDY-S GIT-C--git://git.example.com:2222-->XRAY-S--git://git.example.com:2222-->XRAY-H--git://git.example.com:2222-->GIT-S ``` 从上图中可以看到,VPS 公网 443 端口由 SNI Proxy 监听,80 端口由 Caddy 监听,2222 端口作为 家庭 Git 服务器的透传端口,由 Xray 直接监听并反射(relay)给家庭 Git 服务。 SNI Proxy 通过对 TLS 的 SNI 对流量进行区分,将访问部署在家庭中的网站的流量直接转发给 Xray,再由 Xray 根据规则转发给家庭中的 Caddy 服务,最后由家庭中的 Caddy 服务将 HTTPS 流量转为 HTTP 流量,并转发给目标服务。对于 VLESS 流量,将转发给 Xray 对应端口处理;对于 vmess 和其他网站流量,转发给 Caddy 的 443 端口处理。 Caddy 接收到 443 端口的流量,将根据域名等规则处理并转发给对应服务,如果是 vmess 流量,将扒掉 TLS 层并交由 Xray 处理。Caddy 还接收 80 端口的流量,这部分流量基本上都转发给对应 HTTP 服务进行处理。 ## 部署 ### Docker Compose ```yml version: '3.9' networks: caddy: xray: volumes: caddy-data: caddy-config: acme-sh-data: services: xray: image: teddysun/xray container_name: xray restart: always ports: - '2222:2222' networks: - xray volumes: - ./xray/config.yml:/etc/xray/config.yml - acme-sh-data:/certs command: 'xray -c=/etc/xray/config.yml' caddy: image: caddy:2 container_name: caddy restart: always ports: - '80:80' networks: - caddy - xray volumes: - $PWD/caddy/Caddyfile:/etc/caddy/Caddyfile - $PWD/site:/srv - caddy-data:/data - caddy-config:/config - acme-sh-data:/certs sniproxy: image: tommylau/sniproxy container_name: sniproxy restart: always networks: - caddy - xray ports: - '443:443' volumes: - $PWD/sniproxy:/etc/sniproxy - /var/log/sniproxy:/var/log/sniproxy acme.sh: image: neilpang/acme.sh:dev container_name: acme.sh restart: always volumes: - acme-sh-data:/acme.sh env_file: acme.env command: 'daemon' ``` ### SNI Proxy 配置 文件位于 `sniproxy/sniproxy.conf ```conf user nobody group nogroup # PID file pidfile /var/run/sniproxy.pid error_log { # Log to the daemon syslog facility # syslog deamon # Alternatively we could log to file filename /var/log/sniproxy/sniproxy.log # Control the verbosity of the log priority notice } listen 443 { proto tls table https_hosts access_log { filename /var/log/sniproxy/https_access.log priority notice } } table https_hosts { vmess.example.com caddy:443 # vmess vless.example.com xray:443 # VLESS .*\.example\.com xray:8443 # HTTPS tunnel www.example.com caddy:443 # VPS vhost } ``` ### Caddy 配置 文件位于 `caddy/Caddyfile`。 ```caddyfile www.example.com { encode zstd gzip reverse_proxy some-host:80 } vmess.example.com { reverse_proxy h2c://xray:80 // vmess } ``` ### Xray 配置 文件位于 `xray/config.yml` ```yml --- inbounds: - tag: vless-xtls.in listen: 0.0.0.0 port: 443 protocol: vless settings: clients: - id: flow: xtls-rprx-direct decryption: none streamSettings: network: tcp security: xtls xtlsSettings: serverName: vless.example.com alpn: - h2 - http/1.1 certificates: - certificateFile: /certs/*.example.com/fullchain.cer keyFile: /certs/*.example.com/*.example.com.key falklbacks: - name: 'vmess.ivanli.cc' dist: 80 xver: 1 - dest: 'caddy:80' xver: 1 - listen: 0.0.0.0 port: 80 protocol: vmess settings: clients: - id: decryption: none streamSettings: network: h2 security: none httpSettings: path: / host: - vmess.example.com # ... 其他配置 ... ``` 关于反向代理及泛域名证书相关配置,参考[使用 Xray、acme.sh、Docker Compose 搭建内网穿透服务](https://ivanli.cc/blog/build-an-frp-using-xray-acme.sh-docker-compose)。 ### 启动服务 启动服务前,确保你的配置都完成了。之后在 `docker-compose.yml` 同级目录下执行下面的命令: ```bash docker compose up -d ``` 如果有防火墙,注意放行端口。 ## 方案浅析 上述方案主要解决了一个问题,443 端口的复用。使得所有**“HTTPS 流量”**都流向 443 端口,显得非常治愈。因为使用了境外服务器,为了避免被误杀,实现了站点伪装,这样在一定程度上避免被主动探测发现问题。可是在撰写本文前,翻车了,原因可能是客户端指纹特征导致的。因为除了穿透服务的客户端是完全可控的,能通过配置[开启 uTLS 来规避](https://t.ivanli.cc/UFImlX),但是在网关上的 Clash 客户端却没提供相关配置选项。大意了。