问题

Nginx 自从 1.25.0 版本以来对 QUIC 的支持已被合并入 mainline,对于想体验的用户而言可以直接使用官方发布的 nginx docker 镜像,非常方便。

但是我的服务器上的 nginx 使用了 SNI 分流,源于 Shadow TLSXray Reality 等新一代基于 TLS 的代理协议的需求。这些代理协议并不能由 nginx 代为处理 TLS 层(和之前可以使用 gPRC/WebSocket 等作为数据传输方式的协议不同),但为了实现最好的伪装效果,使用 443/tcp 端口是有必要的(伪装的白名单目标网站一般情况下也只会在 443/tcp 端口开放 HTTPS 服务)。因此 443/tcp 端口的复用是必要的。

如果要让 SNI 分流和 QUIC 共存,在原来的 SNI 分流配置上只需要给每个 server 加上 listen 443 quic 即可。示例配置如下。

配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
http {
    
    # ...

    server {
        server_name example.com;

        # 443/tcp 已经被 nginx stream 占用,不能再次监听
        # listen 443 ssl http2 reuseport so_keepalive=on;
        # listen [::]:443 ssl http2 reuseport so_keepalive=on;

        # 监听 443/udp 端口并启用 QUIC
        # ref: https://nginx.org/en/docs/http/ngx_http_v3_module.html
        listen 443 quic reuseport;
        listen [::]:443 quic reuseport;

        # 监听 unix domain socket 以接受 stream 传送过来的连接,也可以使用本地端口
        # 接受 proxy_protocol,否则 log 中显示的链接源地址都是 unix:
        listen unix:/dev/shm/nginx-example.sock ssl http2 proxy_protocol;
        set_real_ip_from unix:;  # 只对于来自 unix domain socket 的连接覆盖其源地址
        real_ip_header proxy_protocol;

        add_header Alt-Svc 'h3=":443"; ma=86400';  # used to advertise the availability of HTTP/3

        # ...
    }

    server {
        server_name foo.example.com;

        # 可以多个域名共享 443/udp
        listen 443 quic;
        listen [::]:443 quic;

        listen unix:/dev/shm/nginx-example-foo.sock ssl http2 proxy_protocol;
        set_real_ip_from unix:;
        real_ip_header proxy_protocol;

        add_header Alt-Svc 'h3=":443"; ma=86400';  # used to advertise the availability of HTTP/3

        # ...
    }
}

stream {

    # ...

    # 根据 TLS SNI 分流
    map $ssl_preread_server_name $name {
        example.com             unix:/dev/shm/nginx-example.sock;
        foo.example.com         unix:/dev/shm/nginx-example-foo.sock;
        learn.microsoft.com     127.0.0.1:8443;  # 用于 shadow-tls/xray-reality 等
        default                 unix:/dev/shm/nginx-default.sock;
    }

    server {
        # 监听 443/tcp 并根据 SNI 分流
        listen 443 reuseport so_keepalive=on;
        listen [::]:443 reuseport so_keepalive=on;
        proxy_pass $name;
        ssl_preread on;
        proxy_protocol on;
    }

}

测试

目前 curl/wget mainline 还没有支持 QUIC,可以使用 ymuski/curl-http3 这个 docker 镜像:

1
2
3
4
5
6
7
8
$ docker run -it --rm ymuski/curl-http3 curl https://static.monsoon-cs.moe/public/ --http3 -IL

HTTP/3 200
server: nginx/1.25.2
date: Tue, 26 Sep 2023 14:52:29 GMT
content-type: text/html; charset=utf-8
strict-transport-security: max-age=63072000
alt-svc: h3=":443"; ma=86400

参考