引言

如果参考本文章,请确保您有一定的 Linux 和 Cli 使用水平,佑奈在这里会省略很多内容,直接说明最关键的和 CloudFlare 相关的 CDN 部分。

如果想跳过本文冗长的废话,只想看最后找到的原因和解决方案的话,请直接前往 最后

开始

v2ray 作为开源代理解决方案,经历了时间的推敲,也有了很大的用户群体。佑奈这边一直是坚持自己搭建自己的服务,从 Shadowsocks,SSR,到现在的 v2ray,经历了好多时代,所以现在也有在看网络上关于 v2ray 的资料和资源。

早在今年年初的时候因为疫情的消息影响,自己的服务不太稳定,v2ray 本身的 KCP 协议在当时被封杀的很严重,就想着去试试好早之前看到的一个 v2ray 配置文件模版 —— KiraKira/vTemplate

图源:KiraKira/vTemplate

也就是在当时,想着要不要试试看 ws + tls(WebSocket + TLS)这样的配置方案,相比 KCP 使用 UDP 作为协议而言,虽然有速度上的减损,但是稳定性应该会好不少;虽然在佑奈写这篇文章的时候,封锁的力度稍为降低了一些,在上周的时候如果直接检测到 KCP 就是直接封锁的。

抱着这样的心态,当时就开始了折腾。

第一次 v2ray ws+tls

根据 vTemplate 的配置文件,也参考了来自白话文教程关于 WebSocket + TLS + Nginx 的页面。自己尝试开始配置一个全新的服务器。

当时是这样的,因为考虑到有地方说 ws+tls 搭配一个可以直接访问的网站会更好,就想着用 Nginx 作为前置的页面服务器来接受请求,这样的话还可以在同一个 VPS 上跑好几个测试用的页面,所以佑奈就选择了这样的系统和软件配置:
Debian 10 + v2ray (通过官方脚本安装)+ Nginx

安装 v2ray

v2ray 安装很简单,用官网提供的脚本直接安装:

$ bash <(curl -L -s https://install.direct/go.sh)

直接照搬了 vTemplate 的配置文件写法,然后专门改了 path 和 Host

...
"streamSettings": {
  "network": "ws", 
  "security": "auto", 
  "wsSettings": {
    "path": "/根据很多博客的建议,这里填写了随机生成的码", 
    "headers": {
      "Host": "node1.v3.domain.com"
    }
  }
}
...

配置 Nginx

然后就是 Nginx 的配置文件:

server {
  listen  443 ssl;
  ssl on;
  ssl_certificate       /etc/v2ray/v2ray.crt;
  ssl_certificate_key   /etc/v2ray/v2ray.key;
  ssl_protocols         TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers           HIGH:!aNULL:!MD5;
  server_name           node1.v3.domain.com;
        location /随机码 { # 与 V2Ray 配置中的 path 保持一致
                proxy_redirect off;
                proxy_pass http://127.0.0.1:10000;#假设WebSocket监听在环回地址的10000端口上
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $http_host;

                # Show realip in v2ray access.log
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

申请 SSL

本身也没有什么特别的东西,然后开始准备申请自己的证书。

还是像往常一样,先跑去 CloudFlare 把自己的 A 记录填上自己的 VPS IP,然后用 ACME.sh 申请:

安装 ACME.sh

$ curl  https://get.acme.sh | sh
$ source .bashrc

使用 ACME.sh 申请

$ systemctl stop nginx
$ ~/.acme.sh/acme.sh --issue -d node2.v3.domain.com --standalone -k ec-256
$ ~/.acme.sh/acme.sh --installcert -d node2.v3.domain.com --fullchainpath /etc/v2ray/v2ray.crt --keypath /etc/v2ray/v2ray.key --ecc

这样就把 crt 和 key 放在指定的位置了。

于是高兴的把 Nginx 和 v2ray 跑起来……

去 Google Chrome 里面输入了 google.com 做例行测试————

结果居然连不上!

打开 v2ray log 看了看,发现这么一行:

2020/02/03 09:54:00 [Warning] failed to handler mux client connection > 
v2ray.com/core/proxy/vmess/outbound: failed to find an available destination > 
v2ray.com/core/common/retry: [v2ray.com/core/transport/internet/websocket: 
failed to dial WebSocket > v2ray.com/core/transport/internet/websocket: failed 
to dial to (wss://node1.v3.domain.com/path):  > remote error: 
tls: handshake failure] > v2ray.com/core/common/retry: all retry attempts failed

(思考状… failed to dial WebSocket ? tls: handshake failure ?

这个就很迷惑了啊!

然后就开始查资料:

打开了第一个结果:

啊,原来是这样,那应该就是证书的 CDN 问题。

想了想自己的配置方法(只写了 443 端口的反代理配置),并不是很想再去写一个 80 端口的配置,就直接选择把 CloudFlare 的 CDN 关掉了。

返回到 Google Chrome —— 现在可以访问了!测试了 YouTube,能够正常访问。

然后佑奈当晚就去睡觉了,也没有再多管什么事情…

第二天的折腾

第二天想了想还是不行,希望能够找到一个比较好的解决方案。继续翻昨天的搜索结果,看到了这样的回复:

突然想起来自己的这个域名一直都是映射 IP,并没有设置过 CloudFlare 的那个 SSL 等级。于是跑去修改了 CF 的 SSL 等级为 Full,再来尝试连接:

还是不行。打开 log:

2020/02/03 09:56:45 [Warning] failed to handler mux client connection > 
v2ray.com/core/proxy/vmess/outbound: failed to find an available destination > 
v2ray.com/core/common/retry: [v2ray.com/core/transport/internet/websocket: 
failed to dial WebSocket > v2ray.com/core/transport/internet/websocket: 
failed to dial to (ws://node1.v3.domain.com:443/path): 400 Bad Request > 
websocket: bad handshake] > v2ray.com/core/common/retry: all retry attempts failed

发现自己获得了新的错误:400 Bad Request

更迷惑了。

然后跟着好多 issue 下面的回复提出来的不同情况下的不同解决方法:

比如:

  • Nginx 的 path 后面应该还有一个 /:/path/
  • v2ray 自己的 path 应该也是 /path/
  • Host 关键字大小写问题
  • v2ray 的 tls 设置
  • 本地 v2ray client 的 tls 设置(勾选允许不安全的连接
  • 还有关闭 Nginx 只使用 v2ray 自己的 WebSocket

统统不能解决以上的问题。

(生气 (╯°Д°)╯︵ /(.□ . )

这是什么问题嘛。

当然,佑奈也有试着去重置所有的设定,甚至是重新写配置文件,还有 Nginx 的配置内容直接写到 nginx.conf 文件里,依然不奏效。(于是——

放弃了。 CDN 咱们还是先不用了。因为也有人提到说:

v2ray-core/issues#1945: MassSmith 的回复

想了想,先放弃吧,一天又度过了。(还要一点一点把服务器还原回去… (。﹏。*)

额外的补充

当时在 Debug 域名解析和 SSL 的时候,发现点了 CDN 之后 Mac 还能连一会儿,但是 Windows 端和各移动平台就再也连不上服务器了,于是最后发现 Windows 每次被更改 DNS 的 CDN 设置的时候,原节点的连接就再也连不上了,除非手动 ipconfig /flushdns 然后等一会儿才行。

所以根本就是 TTL 刷新问题啊?

那为什么连不上呢???(。﹏。*)

算了算了。

第二次 v2ray ws+tls

某天和 Cocoa 聊天的时候,就被推荐说要不要去试试 v2ray ws+tls + CF CDN,佑奈说大概是没有用的(因为上面说的那个经历,真的也许大概 maybe 不会有什么效果的!)然后还是自己好奇地去点了一下那个 CloudFlare 可爱的橙色小云朵。

用 Mac 这边的 Google Chrome 刷新了一下 Google 首页:

诶?可以打开!

然后关掉了 v2ray 再打开,再刷新 Google 首页:

诶???怎么可以用了???

然后去到了 Windows:

無法連上這個網站

找不到 google.com 的伺服器 IP 位址

ヽ(`⌒´)ノ┻━┻︵ ┻━┻ 这又是什么玄学操作啊?

当然,好景不长,

Mac 也访问不到了。好,又是 TTL 的锅。

佑奈向 Cocoa 发问:CDN 之后,除了 Mac 其他都访问不到了,怎么办?

开始漫长的 Debug

Nginx 还是之前那个:

server {
  listen  443 ssl;
  ssl on;
  ssl_certificate       /etc/v2ray/v2ray.crt;
  ssl_certificate_key   /etc/v2ray/v2ray.key;
  ssl_protocols         TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers           HIGH:!aNULL:!MD5;
  server_name           node1.v3.domain.com;
        location /PATH { # 与 V2Ray 配置中的 path 保持一致
            proxy_redirect off;
            proxy_pass http://127.0.0.1:3000; #假设WebSocket监听在环回地址的10000端口上
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
               proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;

            # Show realip in v2ray access.log
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

然后这个是 Cocoa 那边可用的 ws+tls CDN Nginx 配置:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate       /path/to/fullchain.pem;
    ssl_certificate_key   /path/to/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;

    ssl_protocols         TLSv1.2 TLSv1.3;
    ssl_ciphers           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;
    ssl_prefer_server_ciphers off;

    server_name           subdomain.domain.com;
    location /ray {
        if ($http_upgrade != "websocket") {
                return 404;
        }
        proxy_redirect off;
        proxy_pass http://127.0.0.1:8443;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        # Show real IP in v2ray access.log
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

佑奈拿起来对比了一下,这好像没有什么太大的区别啊? 然后怀疑了 cipher 的问题。

DNS 的锅吗?

与此同时… 开始检查 DNS 是不是没有更新还是怎么样…

第一次 nslookup: IP 还是原位置

再运行一次:居然变了!果然是你 DNS 在搞事情,更新了之后 Mac 也连不上了。

IP 的锅吗?

因为佑奈的 VPS 有两个 IP,忘记了主 IP 有 Port Fowarding,把 Cocoa 的公钥放上去之后给了副 IP,结果 SSH 连不上去,还质疑了一下是不是 IP 的访问问题。(在处理这个 ws+tls 的问题的前两天,佑奈因为 DNS 解锁服务和 IP 干扰的问题换了副 IP,此时的出口依然是主 IP,但是 v2ray 依然可以继续使用,所以这里问题排查完成,和 IP 无关

acme.sh 的锅吗?

然后开始怀疑 acme.sh 和 Certbot 是不是有什么区别,又去单独看了 Let’s Encrypt 那边的说明:

下列 ACME 客户端由第三方提供。Let’s Encrypt 不控制或审查第三方客户端,也不能保证其安全性或可靠性。

此列表上的所有客户端都支持ACMEv2 API (RFC 8555)。

Bash

  • GetSSL (bash, also automates certs on remote hosts via ssh)
  • acme.sh (Compatible to bash, dash and sh)

acme.sh 在客户端列表里面啊。

而且也都是 ACME API 的规范,哪有什么区别?然后特意去单独看了看 acme.sh 申请常规的网页 SSL 证书是不是类似的:答案是 是的啊

继续陷入迷惑。

然后过了一会儿,Mac 又可以了!去 ip.sb 检查了一下 IP 地址,也是 VPS 的出口地址?!

这又是什么玄学。

然后开始怀疑 v2ray 客户端的问题。可是 v2ray 客户端都是跑的 v2ray-core 啊,TLS 和 400 Bad Request 又不会涉及这么多版本。(何况是最新的

Nginx 是你的锅吗?

然后和 Cocoa 互相检查了自己的 Nginx,开始找 Nginx 的麻烦了。

$ [email protected]:~# uname -a
Linux japan 4.19.0-6-amd64 #1 SMP Debian 4.19.67-2+deb10u2 (2019-11-11) x86_64 GNU/Linux
[email protected]:~# nginx -v
nginx version: nginx/1.14.2

果然:佑奈这边 Debian 10 - Nginx 1.14.2;Cocoa 那边 Ubuntu 18.04 - Nginx 1.14.0

好,锁定你了。

想起来几年前那段时光,作为萌新被 CentOS 折磨到痛苦的时候,怀疑是不是 Debian 的 minimal install 有问题。(可是事实也不是啊

然后 Cocoa 去到了佑奈的 VPS 上直接操作,发现把 nginx 的配置文件和证书文件换成 Cocoa 自己的就好了,可以完美连接 v2ray ws+tls ヽ(`⌒´)ノ┻━┻︵ ┻━┻

所以是配置文件的问题咯?然后 Cocoa 把佑奈的证书拿了下来——

发现佑奈的 SSL 公钥是 Elliptic Curve Public Key

对比了一下 Cocoa 的 SSL 公钥是 RSA

这时候就晕了,这个 Elliptic Curve 是什么东西??

椭圆曲线密码学???

往前翻了翻 acme.sh 的命令:

$ ~/.acme.sh/acme.sh --issue -d node2.v3.domain.com --standalone -k ec-256

因为是跟着教程走的 acme,以为 ec-256 是必要的。结果是因为这个参数生成了 ECC 证书。

我们认为这个就是最终的问题!

SSL 是你的锅吗?

于是就开始把证书换成 RSA,重新使用 CloudFlare 的 API 和 Certbot 重新申请…

https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/

Cocoa 说 ECC 看起来 CloudFlare 应该是支持的样子…

Cocoa:莫非是 Win 客户端不支持?

佑奈这边把 RSA 换好了之后 Nginx 也配置好了:

很高兴的去访问了页面:

ヽ(`⌒´)ノ┻━┻︵ ┻━┻ 这个太玄学了太玄学了太玄学了(

看了看自己的证书是不是 OK 的:

这不都是对的吗?!哪里出问题了呢???!

TLS1.3 是你的锅吗?

改了之后还是不行 XD

然后又是一波疯狂的 Google Chrome 无法连接 画面。

ヽ(`⌒´)ノ┻━┻︵ ┻━┻

直到… Cocoa 说了一句话:莫非是三级域名的问题?

我不信我不信我不信

三级域名是你的锅吗?????

真的真的申请了很多很多的 SSL 证书,而且不爽就删掉(x

之后又是很流利的一波证书申请和 Nginx 重新配置…

然后又刷新了一下…

震撼猫猫,居然真的是三级域名的锅!

最后

和 Cocoa 把能怀疑的问题全部怀疑了一遍,当时把相关的 issue 照来了都没有办法修好的问题:居然是 CloudFlare 的 CDN 证书不支持 三级域名

SSL covers only example.com and .example.com. To get down to *..example.com, you need a dedicated certificate with Custom Hostnames so you can cover something like www.sub.example.com

https://community.cloudflare.com/t/third-level-domain/63383

这不是 v2ray 教程和各位博客主写的不好,

这也不是 Let’s Encrypt 不够良心提供不了足够的 SSL 加密方法,

这也不是可怜的 Debian 和 Nginx 的问题。

这是 CloudFlare 不好好写清楚的原因啊!

你不支持三级域名的时候怎么不说呢, Wildcard 和 Subdomain 倒是写的很清楚…

这个问题来源于佑奈想要用域名分级的办法来管理各个服务器的地址,是为了方便自己维护和操作…

可是 CloudFlare 这个要求… 也不写在文档上,就… ヽ(`⌒´)ノ┻━┻︵ ┻━┻

所以如果你的 v2ray ws + tls + CloudFlare CDN 方案怎么更改都改不过的话,请考虑你的域名是不是超出了 CloudFlare 的限制

引用和感谢

本文由 花見佑奈 和 香織可可 一同完成,期间使用的资源和连接以及引用都已经在下面附上链接。

再次感谢 v2ray 开发组和每一个用户的付出。

KiraKira/vTemplate: https://github.com/KiriKira/vTemplate

白话文教程: https://toutyrater.github.io/

v2ray-core/issues833: WebSocket+TLS+nginx与Cloudflare的问题: https://github.com/v2ray/v2ray-core/issues/833

v2ray-core/issues998: Websocket不套CDN能正常使用,但是套了CDN就不行了: https://github.com/v2ray/v2ray-core/issues/998

Let’s Encrypt ACME 客户端: https://letsencrypt.org/zh-cn/docs/client-options/

CloudFlare ECC: https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/

Third Level Domain - CloudFlare Community: https://community.cloudflare.com/t/third-level-domain/63383


技术      Linux v2ray CloudFlare CDN

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!