tailwind-nextjs-blog/data/blog/network-freedom-with-vps.md
Ivan Li 0b3bdfc36a
Some checks failed
continuous-integration/drone/push Build is failing
blog: 简单调整。
2023-01-23 12:28:18 +00:00

6.7 KiB
Raw Blame History

title date tags draft summary
利用一台小鸡实现网络自由 2022-10-06
SNI
TLS
Reverse Proxy
反向代理
正向代理
‘内网穿透'
Caddy
Xray
Vless
true SNI Proxy 进行 TLS 分流Caddy 对网站和 Xray 进行反向代理Xray 实现正向、反向代理(内网穿透)。

前言

由于条件所限,我在住所使用了一台 AMD 小主机运行了 PVE上面跑了许多服务他们有一个共同点——具有 Web UI。另一方面我的宽带使用共享公网 IP使用 Full Cone NAT这就导致我在外难以方便地访问内网服务。为了解决这个令人不爽的问题我准备使用 VPS 主机作为中间人,用于反射相关流量。

比较悲剧的是在撰写本文时VPS 的 443 端口被干掉……不过还好VPS 即将到期,如果不能恢复,就换别的了。

网络拓扑图

简单画一下,大概网络拓扑长下面这样:

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

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

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

www.example.com {
  encode zstd gzip
  reverse_proxy some-host:80
}

vmess.example.com {
  reverse_proxy h2c://xray:80 // vmess
}

Xray 配置

文件位于 xray/config.yml

---
inbounds:
  - tag: vless-xtls.in
    listen: 0.0.0.0
    port: 443
    protocol: vless
    settings:
      clients:
        - id: <your-uuid>
          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: <your-uuid>
      decryption: none
    streamSettings:
      network: h2
      security: none
      httpSettings:
        path: /
        host:
          - vmess.example.com
  # ... 其他配置 ...

关于反向代理及泛域名证书相关配置,参考使用 Xray、acme.sh、Docker Compose 搭建内网穿透服务

启动服务

启动服务前,确保你的配置都完成了。之后在 docker-compose.yml 同级目录下执行下面的命令:

docker compose up -d

如果有防火墙,注意放行端口。

方案浅析

上述方案主要解决了一个问题443 端口的复用。使得所有**“HTTPS 流量”**都流向 443 端口,显得非常治愈。因为使用了境外服务器,为了避免被误杀,实现了站点伪装,这样在一定程度上避免被主动探测发现问题。可是在撰写本文前,翻车了,原因可能是客户端指纹特征导致的。因为除了穿透服务的客户端是完全可控的,能通过配置开启 uTLS 来规避,但是在网关上的 Clash 客户端却没提供相关配置选项。大意了。