tailwind-nextjs-blog/data/blog/network-freedom-with-vps.md
Ivan Li 11b9017a07
All checks were successful
continuous-integration/drone Build is passing
blog: 利用 SNI 路由 TLS 连接实现端口复用。
2022-10-06 18:02:31 +08:00

241 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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: <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 搭建内网穿透服务](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 客户端却没提供相关配置选项。大意了。