HTTPS 已经是互联网服务准入的基本门槛了,同时 443 端口作为 HTTPS 请求的默认端口,在虚拟主机服务的支持下,搭配的天衣无缝。

但是 Trojan 比较特殊,因为它的工作方式导致了其必须直接对接流量入口,否则其协议无法被服务端正常识别,同时为了增加服务的隐蔽性,一般会把它配置在 443 端口。但是 443 端口只有一个,虽然 Trojan 提供了「非标请求」的转发功能,但是毕竟是一个新生事物,所有流量都过它手,在稳定、性能、灵活等等方面都不够好,而且还不支持 TLS 转发。

在此背景下,我设计了一套 Trojan 和 Nginx 公用 443 端口的方案,同时也支持 Docker 部署。

首页说一下最后的成品架构:

graph LR A["Client"] -->|HTTPS Request| B("Nginx") B --> C{"dispatch"} C -->D["Trojan"] C -->E["V2Ray"] C -->F["Web"] D -->|Invalid Requst| G["Default Page"] E -->|Invalid Requst| G["Default Page"]

主要的工作就是在 dispatch 阶段的流量分发。因为都是 HTTPS 流量,所以在确定分发策略前得有一点相关知识。

在虚拟主机流行前,一个服务 IP 只会有一个 TLS 服务站点,只会有一张 SSL 证书,所以请求来了就只有一张证书,没得选直接用就好了。

后面虚拟主机流行起来,一个服务 IP 可以有多个 TLS 服务站点,那就有多个 SSL 证书,那怎么明确这次请求用哪张证书呢?

于是就有了 SNI(TLS 服务器名称指示),它要求在一个 IP 有多个 TLS 服务站点的情况下,客户端在初始 TLS 握手期间指定要连接到哪个站点,数据上的实现就是在 Client Hello 阶段里面新增一个 server_name 字段。

TLS SNI

所以它是现在支持同一个 IP 上配置多个 HTTPS 主机的基础:

server {
    listen          443 ssl;
    server_name     www.chengxiaobai.cn;
    ssl_certificate www.chengxiaobai.cn.crt;
}

server {
    listen          443 ssl;
    server_name     nothing.chengxiaobai.cn;
    ssl_certificate nothing.chengxiaobai.cn.crt;
}

我们的流量分发策略也是基于这个数据来的。

首先得明确,Trojan 是无法通过 Nginx 在 7 层进行代理的,所以它设定必须在流量入口,Nginx 都只能挂在它的后面。

但是我仍然有个疑惑:为什么不能在 http 模块下使用 proxy_pass 进行 HTTPS 反向代理 Trojan?

我尝试了一下,发现是建立连接失败了,我怀疑是 Trojan 本身对建立连接的过程做了数据校验或者本身没有完全实现 HTTP 协议(HTTP CONNECT 部分)?

http 模块的 proxy_pass 是在七层做的转发,可能需要挂载证书进行流量的解密识别,再加密请求,相当于做了一次中间人(MITM, Man-in-the-Middle)代理,又或者因为使用 HTTP CONNECT 来建立隧道而导致的链接失败?里面涉及到的东西就太多了,有空翻源码看看吧。

那无法在 7 层转发,就在 4 层转发吧。

Nginx 支持基于 SNI 的 4 层转发。简单说就是:识别 SNI 信息,然后直接转发 TCP/UDP 数据流。这个可以比 7 层的虚拟主机转发厉害太多了,该功能由 ngx_stream_ssl_preread_module 模块提供,但是 Nginx 默认不启用该模块,配置起来也很简单,需要注意的是该模块属于 stream ,不是大家常用的 http

Nginx 配置:

user  nginx;

pid   /var/run/nginx.pid;

# 其他配置保持默认即可

# 流量转发核心配置
stream {
    # 这里就是 SNI 识别,将域名映射成一个配置名
    map $ssl_preread_server_name $backend_name {
        web.cn.chengxiaobai web;
        vmess.cn.chengxiaobai vmess;
        trojan.cn.chengxiaobai trojan;
    # 域名都不匹配情况下的默认值
        default web;
    }

    # web,配置转发详情
    upstream web {
        server 127.0.0.1:10240;
    }

    # trojan,配置转发详情
    upstream trojan {
        server 127.0.0.1:10241;
    }

    # vmess,配置转发详情
    upstream vmess {
        server 127.0.0.1:10242;
    }

    # 监听 443 并开启 ssl_preread
    server {
        listen 443 reuseport;
        listen [::]:443 reuseport;
        proxy_pass  $backend_name;
        ssl_preread on;
    }
}

http {
  # 这块保持不变即可
}

简简单单几行配置,就完成了流量分发,最后将 Trojan 和 V2Ray 的配置端口修改一下和上面的配置保持一致即可。

V2Ray 搭配的 Nginx 配置,用于解密 TSL 加密信息提升整体链路性能,并且实现站点伪装:

server {
    # 开启 HTTP2 支持
    listen 10242 ssl http2;
    server_name  vmess.cn.chengxiaobai;

    gzip on;
    gzip_http_version 1.1;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_certificate      /etc/ssl/vmess.cn.chengxiaobai.crt;
    ssl_certificate_key  /etc/ssl/private/vmess.cn.chengxiaobai.key;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    # WS 协议转发
    location /fTY9Bx7c {
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:16881;
    }

    # 非标请求转发伪装
    location / {
       proxy_pass http://127.0.0.1:80;
    }
}

V2Ray 配置:

{
  "api": {
    "tag": "api"
  },
  "dns": {
    "servers": [
      "https://1.1.1.1/dns-query",
      "https://8.8.8.8/dns-query",
      "localhost"
    ]
  },
  "inbounds": [
    {
      "allocate": {
        "strategy": "always"
      },
      "port": 16881,
      "protocol": "vmess",
      "settings": {
        "clients": [
          {
            "alterId": 64,
            "id": "唯一ID",
            "level": 1
          }
        ]
      },
      "sniffing": {
        "enabled": false
      },
      "streamSettings": {
        "network": "ws",
        "security": "none", // 注意这里没有 TLS,因为 TSL 已经交给 Nginx 处理
        "wsSettings": {
          "path": "/fTY9Bx7c" // 可以复杂一点,避免嗅探
        }
      }
    }
  ],
  "log": {
    "access": "/var/log/v2ray/access.log",
    "error": "/var/log/v2ray/error.log",
    "loglevel": "error"
  },
  "outbounds": [
    {
      "protocol": "freedom",
      "settings": {
        "domainStrategy": "AsIs"
      },
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "settings": {
        "response": {
          "type": "none"
        }
      },
      "tag": "blocked"
    }
  ],
  "policy": {},
  "reverse": {},
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "ip": [
          "0.0.0.0/8",
          "10.0.0.0/8",
          "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",
          "192.0.2.0/24",
          "192.168.0.0/16",
          "198.18.0.0/15",
          "198.51.100.0/24",
          "203.0.113.0/24",
          "::1/128",
          "fc00::/7",
          "fe80::/10"
        ],
        "outboundTag": "blocked",
        "type": "field"
      }
    ]
  },
  "stats": {},
  "transport": {}
}

Trojan 配置:

{
  "run_type": "server",
  "local_addr": "127.0.0.1",
  "local_port": 10241,
  "remote_addr": "127.0.0.1",//转发回 nginx 默认页面
  "remote_port": 80,
  "password": [
    "密码"
  ],
  "log_level": 3,
  "ssl": {
    "cert": "证书地址.crt",
    "key": "证书地址.key",
    "key_password": "",
    "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384",
    "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",
    "prefer_server_cipher": true,
    "alpn": [
      "http/1.1"
    ],
    "alpn_port_override": {
      "h2": 81
    },
    "reuse_session": true,
    "session_ticket": false,
    "session_timeout": 600,
    "plain_http_response": "",
    "curves": "",
    "dhparam": ""
  },
  "tcp": {
    "prefer_ipv4": false,
    "no_delay": true,
    "keep_alive": true,
    "reuse_port": false,
    "fast_open": false,
    "fast_open_qlen": 20
  },
  "mysql": {
    "enabled": false,
    "server_addr": "127.0.0.1",
    "server_port": 3306,
    "database": "trojan",
    "username": "trojan",
    "password": "",
    "cafile": ""
  }
}

其他服务按照之前使用正常配置即可,毕竟 Nginx 可以转发到 Trojan,还可以转发到 Nginx 自身从而实现其他服务的反向代理,当前笔者使用的流量分发架构:

graph LR A["Client"] -->|HTTPS Request| B("Nginx") B -->C{"dispatch"} C -->|TCP Forward| D["Trojan"] C -->|TCP Forward|E0["Nginx WS Port"] C -->|TCP Forward|F0["Nginx Web Port"] E0-->|WS Protocol|E["V2Ray"] E0-->|Invalid Requst| G0["Nginx Default Port"] F0-->|HTTP|F["PHP"] D -->|Invalid Requst| G0 G0-->G["Default Page"]

Nginx 层统一管理收敛流量入口,整个主机只用开启 443 和 SSH 端口即可,同时各个模块都做了伪装,「非标请求」看到的都是正常的页面,而且 WS 协议的 CDN 大法完美支持。

同时得益于 Nginx 的优秀性能和对 HTTP 协议的支持力度,全部请求都可以 HTTP2。

ALL HTTP2

Youtube 速度相比之前 Trojan 直连没有明显差异。

Youtube Speed

把专业的事交给专业的人 😎 。

本作品由 程小白 创作,采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可,可自由转载、引用但需署名作者且注明文章出处。
原文地址:https://www.chengxiaobai.cn/record/trojan-shared-443-port-scheme.html