在
不久前CloudFlare正式为CDN全面启用了ECH(Encrypted Client Hello)功能,搭配DoH可以实现加密真实的SNI(Server Name Indication),而明文显示Public Name。如经过CloudFlare CDN 并启用ECH后,明文SNI为cloudflare-ech.com
加密后的真实SNI至少在客户端和CloudFlare CDN之间是无法被第三方获知的,从而提供更强的隐私保障,并且由于不显示真实的SNI,根据SNI进行阻断连接的方法更加受到限制,除非将所有SNI为cloudflare-ech.com
的连接一同阻断。
我们可以使用 Curl 作为测试ECH和HTTP/3连接的工具,目前这些功能依然还处于测试过程中,并且实现了HTTP/3的ssl库非常多(如openssl/openssl
, quictls/openssl
, cloudflare/quiche
等,官方编译指南),但是貌似只有cloudflare/quiche
同时支持ECH和HTTP/3。
编译quiche需要使用到rust,并且有版本要求
sudo apt install git net-tools cmake build-essential libssl-dev zlib1g-dev libbrotli-dev libzstd-dev autoconf automake libtool libpsl-dev libnghttp2-dev pkg-config curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh . "$HOME/.cargo/env" git clone --recursive -b 0.22.0 https://github.com/cloudflare/quiche git clone https://github.com/curl/curl cd quiche cargo build --package quiche --release --features ffi,pkg-config-meta,qlog ln -s libquiche.so target/release/libquiche.so.0 mkdir quiche/deps/boringssl/src/lib ln -vnf $(find target/release -name libcrypto.a -o -name libssl.a) quiche/deps/boringssl/src/lib/ cd ../curl autoreconf -fi ./configure LDFLAGS="-Wl,-rpath,$PWD/../quiche/target/release" --with-openssl=$PWD/../quiche/quiche/deps/boringssl/src --with-quiche=$PWD/../quiche/target/release --enable-ech --with-zlib --with-brotli --with-zstd --enable-versioned-symbols make
编译完后程序在./src/curl
,以下是版本信息和测试示例,可选--http3
代替--http2
,可以发现sni=encrypted
:
# /root/curl/src/curl --version curl 8.11.0-DEV (x86_64-pc-linux-gnu) libcurl/8.11.0-DEV BoringSSL zlib/1.2.13 brotli/1.0.9 zstd/1.5.4 libidn2/2.3.3 libpsl/0.21.2 nghttp2/1.52.0 quiche/0.22.0 Release-Date: [unreleased] Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp ws wss Features: alt-svc AsynchDNS brotli ECH HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Largefile libz NTLM PSL SSL threadsafe UnixSockets zstd # /root/curl/src/curl -vv --ech true --http2 --doh-url https://one.one.one.one/dns-query https://v2ex.com/cdn-cgi/trace 07:38:45.515882 [0-0] * Some HTTPS RR to process 07:38:45.516165 [0-0] * Host v2ex.com:443 was resolved. 07:38:45.516217 [0-0] * IPv6: 2606:4700:10::6814:30b4, 2606:4700:10::ac43:23d3, 2606:4700:10::6814:2fb4 07:38:45.516265 [0-0] * IPv4: 172.67.35.211, 104.20.48.180, 104.20.47.180 07:38:45.516323 [0-0] * [HTTPS-CONNECT] added 07:38:45.516378 [0-0] * [HTTPS-CONNECT] connect, init 07:38:45.516430 [0-0] * [HTTPS-CONNECT] connect, check h21 07:38:45.516509 [0-0] * Trying [2606:4700:10::6814:30b4]:443... 07:38:45.516606 [0-0] * Immediate connect fail for 2606:4700:10::6814:30b4: Network is unreachable 07:38:45.516693 [0-0] * [HTTPS-CONNECT] connect -> 0, done=0 07:38:45.516746 [0-0] * [HTTPS-CONNECT] adjust_pollset -> 0 socks 07:38:45.516802 [0-0] * [HTTPS-CONNECT] connect, check h21 07:38:45.516865 [0-0] * Trying [2606:4700:10::ac43:23d3]:443... 07:38:45.516928 [0-0] * Immediate connect fail for 2606:4700:10::ac43:23d3: Network is unreachable 07:38:45.516989 [0-0] * [HTTPS-CONNECT] connect -> 0, done=0 07:38:45.517081 [0-0] * [HTTPS-CONNECT] adjust_pollset -> 0 socks 07:38:45.517152 [0-0] * [HTTPS-CONNECT] connect, check h21 07:38:45.517212 [0-0] * Trying [2606:4700:10::6814:2fb4]:443... 07:38:45.517284 [0-0] * Immediate connect fail for 2606:4700:10::6814:2fb4: Network is unreachable 07:38:45.517353 [0-0] * Trying 172.67.35.211:443... 07:38:45.517459 [0-0] * [HTTPS-CONNECT] connect -> 0, done=0 07:38:45.517508 [0-0] * [HTTPS-CONNECT] adjust_pollset -> 1 socks 07:38:45.519573 [0-0] * [HTTPS-CONNECT] connect, check h21 07:38:45.519672 [0-0] * ECH: ECHConfig from DoH HTTPS RR 07:38:45.519733 [0-0] * ECH: imported ECHConfigList of length 71 07:38:45.519782 [0-0] * ALPN: curl offers h2,http/1.1 07:38:45.519996 [0-0] * TLSv1.2 (OUT), TLS handshake, Client hello (1): 07:38:45.526047 [0-0] * CAfile: /etc/ssl/certs/ca-certificates.crt 07:38:45.526104 [0-0] * CApath: /etc/ssl/certs 07:38:45.526135 [0-0] * [HTTPS-CONNECT] connect -> 0, done=0 07:38:45.526187 [0-0] * [HTTPS-CONNECT] adjust_pollset -> 1 socks 07:38:45.526244 [0-0] * [HTTPS-CONNECT] connect, check h21 07:38:45.526319 [0-0] * TLSv1.2 (IN), TLS handshake, Server hello (2): 07:38:45.526378 [0-0] * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): 07:38:45.526627 [0-0] * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): 07:38:45.526680 [0-0] * TLSv1.3 (IN), TLS handshake, Certificate (11): 07:38:45.526833 [0-0] * TLSv1.3 (IN), TLS handshake, CERT verify (15): 07:38:45.527941 [0-0] * TLSv1.3 (IN), TLS handshake, Finished (20): 07:38:45.528020 [0-0] * TLSv1.3 (OUT), TLS handshake, Finished (20): 07:38:45.528129 [0-0] * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / [blank] / UNDEF 07:38:45.528162 [0-0] * ALPN: server accepted h2 07:38:45.528201 [0-0] * Server certificate: 07:38:45.528234 [0-0] * subject: CN=v2ex.com 07:38:45.528260 [0-0] * start date: Oct 22 23:27:50 2024 GMT 07:38:45.528291 [0-0] * expire date: Jan 20 23:27:49 2025 GMT 07:38:45.528332 [0-0] * subjectAltName: host "v2ex.com" matched cert's "v2ex.com" 07:38:45.528367 [0-0] * issuer: C=US; O=Let's Encrypt; CN=E5 07:38:45.528404 [0-0] * SSL certificate verify ok. 07:38:45.528442 [0-0] * [HTTPS-CONNECT] connect+handshake h21: 12ms, 1st data: 8ms 07:38:45.528497 [0-0] * [HTTP/2] [0] created h2 session 07:38:45.528539 [0-0] * [HTTP/2] [0] -> FRAME[SETTINGS, len=18] 07:38:45.528577 [0-0] * [HTTP/2] [0] -> FRAME[WINDOW_UPDATE, incr=1048510465] 07:38:45.528610 [0-0] * [HTTP/2] cf_connect() -> 0, 1, 07:38:45.528632 [0-0] * [HTTPS-CONNECT] connect -> 0, done=1 07:38:45.528654 [0-0] * Connected to v2ex.com (172.67.35.211) port 443 07:38:45.528678 [0-0] * using HTTP/2 07:38:45.528735 [0-0] * [HTTP/2] [1] OPENED stream for https://v2ex.com/cdn-cgi/trace 07:38:45.528759 [0-0] * [HTTP/2] [1] [:method: GET] 07:38:45.528786 [0-0] * [HTTP/2] [1] [:scheme: https] 07:38:45.528815 [0-0] * [HTTP/2] [1] [:authority: v2ex.com] 07:38:45.528847 [0-0] * [HTTP/2] [1] [:path: /cdn-cgi/trace] 07:38:45.528897 [0-0] * [HTTP/2] [1] [user-agent: curl/8.11.0-DEV] 07:38:45.528955 [0-0] * [HTTP/2] [1] [accept: */*] 07:38:45.528995 [0-0] * [HTTP/2] [1] submit -> 87, 0 07:38:45.529055 [0-0] * [HTTP/2] [1] -> FRAME[HEADERS, len=41, hend=1, eos=1] 07:38:45.529095 [0-0] * [HTTP/2] [0] egress: wrote 114 bytes 07:38:45.529134 [0-0] * [HTTP/2] [1] cf_send(len=87) -> 87, 0, eos=1, h2 windows 65535-65535 (stream-conn), buffers 0-0 (stream-conn) 07:38:45.529196 [0-0] > GET /cdn-cgi/trace HTTP/2 07:38:45.529196 [0-0] > Host: v2ex.com 07:38:45.529196 [0-0] > User-Agent: curl/8.11.0-DEV 07:38:45.529196 [0-0] > Accept: */* 07:38:45.529196 [0-0] > 07:38:45.529468 [0-0] * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): 07:38:45.529503 [0-0] * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): 07:38:45.529579 [0-0] * [HTTP/2] [0] ingress: read 40 bytes 07:38:45.529614 [0-0] * [HTTP/2] [0] <- FRAME[SETTINGS, len=18] 07:38:45.529656 [0-0] * [HTTP/2] [0] MAX_CONCURRENT_STREAMS: 100 07:38:45.529709 [0-0] * [HTTP/2] [0] ENABLE_PUSH: TRUE 07:38:45.529752 [0-0] * [HTTP/2] [1] DRAIN select_bits=1 07:38:45.529811 [0-0] * [HTTP/2] [0] <- FRAME[WINDOW_UPDATE, incr=2147418112] 07:38:45.529853 [0-0] * [HTTP/2] [0] progress ingress: inbufg=0 07:38:45.529901 [0-0] * [HTTP/2] [0] progress ingress: done 07:38:45.529944 [0-0] * [HTTP/2] [0] -> FRAME[SETTINGS, ack=1] 07:38:45.530005 [0-0] * [HTTP/2] [0] egress: wrote 9 bytes 07:38:45.530024 [0-0] * [HTTP/2] [1] cf_recv(len=102400) -> -1 81, window=0/65535, connection 1048576000/1048576000 07:38:45.530056 [0-0] * Request completely sent off 07:38:45.530096 [0-0] * [HTTP/2] [0] progress ingress: done 07:38:45.530131 [0-0] * [HTTP/2] [1] cf_recv(len=102400) -> -1 81, window=0/65535, connection 1048576000/1048576000 07:38:45.530354 [0-0] * [HTTP/2] [0] ingress: read 9 bytes 07:38:45.530399 [0-0] * [HTTP/2] [0] <- FRAME[SETTINGS, ack=1] 07:38:45.530433 [0-0] * [HTTP/2] [0] progress ingress: inbufg=0 07:38:45.530472 [0-0] * [HTTP/2] [0] progress ingress: done 07:38:45.530504 [0-0] * [HTTP/2] [1] cf_recv(len=102400) -> -1 81, window=0/65536, connection 1048576000/1048576000 07:38:45.532209 [0-0] * [HTTP/2] [0] ingress: read 152 bytes 07:38:45.532238 [0-0] < HTTP/2 200 07:38:45.532281 [0-0] * [HTTP/2] [1] local window update by 10420224 07:38:45.532310 [0-0] * [HTTP/2] [1] status: HTTP/2 200 07:38:45.532353 [0-0] < date: Fri, 25 Oct 2024 07:38:45 GMT 07:38:45.532374 [0-0] * [HTTP/2] [1] header: date: Fri, 25 Oct 2024 07:38:45 GMT 07:38:45.532432 [0-0] < content-type: text/plain 07:38:45.532483 [0-0] * [HTTP/2] [1] header: content-type: text/plain 07:38:45.532525 [0-0] < access-control-allow-origin: * 07:38:45.532551 [0-0] * [HTTP/2] [1] header: access-control-allow-origin: * 07:38:45.532582 [0-0] < server: cloudflare 07:38:45.532609 [0-0] * [HTTP/2] [1] header: server: cloudflare 07:38:45.532664 [0-0] < cf-ray: 8d808c846f57cf0a-SJC 07:38:45.532705 [0-0] * [HTTP/2] [1] header: cf-ray: 8d808c846f57cf0a-SJC 07:38:45.532731 [0-0] < x-frame-options: DENY 07:38:45.532764 [0-0] * [HTTP/2] [1] header: x-frame-options: DENY 07:38:45.532800 [0-0] < x-content-type-options: nosniff 07:38:45.532827 [0-0] * [HTTP/2] [1] header: x-content-type-options: nosniff 07:38:45.532860 [0-0] < expires: Thu, 01 Jan 1970 00:00:01 GMT 07:38:45.532893 [0-0] * [HTTP/2] [1] header: expires: Thu, 01 Jan 1970 00:00:01 GMT 07:38:45.532919 [0-0] < cache-control: no-cache 07:38:45.532957 [0-0] * [HTTP/2] [1] header: cache-control: no-cache 07:38:45.532982 [0-0] * [HTTP/2] [1] <- FRAME[HEADERS, len=143, hend=1, eos=0] 07:38:45.533022 [0-0] < 07:38:45.533091 [0-0] * [HTTP/2] [1] DRAIN select_bits=1 07:38:45.533138 [0-0] * [HTTP/2] [0] progress ingress: inbufg=0 07:38:45.533179 [0-0] * [HTTP/2] [0] ingress: read 212 bytes fl=464f176 h=v2ex.com ip=143.198.50.203 ts=1729841925.826 visit_scheme=https uag=curl/8.11.0-DEV colo=SJC sliver=none http=http/2 loc=US tls=TLSv1.3 sni=encrypted warp=off gateway=off rbi=off kex=X25519 07:38:45.533249 [0-0] * [HTTP/2] [1] <- FRAME[DATA, len=203, eos=0, padlen=0] 07:38:45.533280 [0-0] * [HTTP/2] [1] DATA, window=203/10485760 07:38:45.533308 [0-0] * [HTTP/2] [0] progress ingress: inbufg=0 07:38:45.533365 [0-0] * [HTTP/2] [0] ingress: read 9 bytes 07:38:45.533402 [0-0] * [HTTP/2] [1] <- FRAME[DATA, len=0, eos=1, padlen=0] 07:38:45.533435 [0-0] * [HTTP/2] [1] DATA, window=203/10485760 07:38:45.533463 [0-0] * [HTTP/2] [1] CLOSED 07:38:45.533496 [0-0] * [HTTP/2] [1] DRAIN select_bits=1 07:38:45.533539 [0-0] * [HTTP/2] [0] progress ingress: inbufg=0 07:38:45.533579 [0-0] * [HTTP/2] [1] DRAIN select_bits=1 07:38:45.533617 [0-0] * [HTTP/2] [0] progress ingress: done 07:38:45.533651 [0-0] * [HTTP/2] [1] returning CLOSE 07:38:45.533672 [0-0] * [HTTP/2] handle_stream_close -> 0, 0 07:38:45.533707 [0-0] * [HTTP/2] [1] cf_recv(len=102400) -> 0 0, window=-1/-1, connection 1048575797/1048576000 07:38:45.533751 [0-0] * Connection #0 to host v2ex.com left intact
不建议安装测试版curl,比较麻烦,一种方法是添加alias(推荐),在.zshrc
或.bashrc
末尾添加
alias curl='/root/src/curl/src/curl'
注意修改为你的文件位置
再 source .zshrc
或.bashrc