Back
Featured image of post 记一次SSL握手异常的诊断

记一次SSL握手异常的诊断

背景

家里的一些服务从外部访问出错了,日常用的Nextcloud等都无法连接。表现为从公司或腾讯云等无法建立连接(通过域名加端口方式访问),但是从内部访问一切正常。这个服务是从Unifi的路由器USG中做了端口转发到达的。

诊断过程

k3s集群问题乎?

查看对应转发服务日志,可看到TLS handshake errortls: client using inappropriate protocol fallback

1
2
3
4
│ 2023/07/16 00:01:20 http: TLS handshake error from 10.42.2.1:10272: write tcp 10.42.2.98:8080->10.42.2.1:10272: write: connection reset by peer
│ 2023/07/16 00:01:21 http: TLS handshake error from 10.42.2.1:1883: write tcp 10.42.2.98:8080->10.42.2.1:1883: write: connection reset by peer
│ 2023/07/16 00:01:21 http: TLS handshake error from 10.42.2.1:62249: write tcp 10.42.2.98:8080->10.42.2.1:62249: write: connection reset by peer
│ 2023/07/16 00:01:21 http: TLS handshake error from 10.42.2.1:21301: tls: client using inappropriate protocol fallback

这个服务是部署在k3s集群内的。日志中10.42.2.1是哪个IP?它既非pod IP也非svc IP,和GPT4交谈后,提醒了我:

虚拟网络接口的 IP 地址:若你使用的网络插件(如 Flannel, Calico)为每个 Pod 创建了虚拟网络接口,那么 10.42.2.1 可能是某个虚拟网络接口的 IP 地址。

经查证发现是CNI0的IP,10.42.2.98是目标Pod IP,但是SSL会话出错了,可能是什么原因?因为最近集群出过一点问题,作为k3s的master节点在一台NUC上挂了一次。故第一反应是集群网络层异常了。尝试重启k3s server和agent,问题旧。这也不能证明集群没有问题,为此我设想建立一个不在集群的内部服务并对外提供服务,如果它能复现此问题,说明多半和集群无关?

说干就干,可以基于docker快速部署一个带证书的Nginx,直接叫GPT给我相关的nginx.conf配置文件以及docker-compose就好,分分钟起来。然后在路由上配置好对应的端口转发,同样的问题再次出现!!

到这一步,大概说明此非集群问题。

防火墙问题乎?

为了搜索更多线索,使用curl发起对应HTTPS调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ curl -vks <公网域名>:<公网port>
* TCP_NODELAY set
* Connected to xxURL (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* OpenSSL SSL_connect: Connection reset by peer in connection to

看起来SSL失败了,GPT建议使用openssl来调试一下,依据建议执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ openssl s_client  -connect <公网IP>:<公网port>
CONNECTED(00000003)
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 283 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

我把上面的结果和GPT聊了一下,它提供如下建议,我觉得挺有道理。

Tip

根据你提供的信息,从内部网络可以正常访问Service,但从外部访问时无法建立安全连接。同时,从外部使用openssl诊断时,提示没有可用的peer证书。这可能是由以下几个原因导致的:

  1. USG防火墙规则配置问题:请检查USG的防火墙规则,确保外部流量已正确转发至Service所在的VLAN。同时,请检查是否允许了外部到Service的HTTPS流量(端口6443)。
  2. SSL证书配置问题:请检查Service(如Nginx)的SSL证书配置,确保已正确配置了证书文件(包括证书链、私钥等)。此外,请确保证书文件的权限设置允许Service读取。
  3. NAT配置问题:请检查USG的NAT(网络地址转换)配置,确保外部IP地址(<家公网IP>)已正确映射到内部Service的IP地址和端口(6443)。
  4. 路由配置问题:请检查内部网络的路由配置,确保Service和USG之间的通信路径是正确的。这可能涉及到VLAN间路由和子网掩码的配置。

诊断思路:

  1. 首先,检查USG的防火墙规则和NAT配置,确保外部流量能正确转发到Service所在的VLAN,且允许了外部到Service的HTTPS流量(端口6443)。
  2. 其次,检查Service(如Nginx)的SSL证书配置,确保已正确配置了证书文件(包括证书链、私钥等),并检查证书文件的权限设置。
  3. 最后,检查内部网络的路由配置,确保Service和USG之间的通信路径是正确的。这可能涉及到VLAN间路由和子网掩码的配置。

GPT认为可能是防火墙配置问题,可是我最近几个月没有操作防火墙的任何设置,为什么会出现这个情况呢?但是内部能访问从外部又失败,确实这个承担外部流量入口的USG又是有重大嫌疑的。

看了几遍防火墙配置没有线索,通过SSH进入USG的后台,抓包看看:

 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
ubnt:~$ sudo tcpdump -i pppoe0 'tcp port 9443' -vvv
tcpdump: listening on pppoe0, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
08:37:35.401397 IP (tos 0x0, ttl 56, id 0, offset 0, flags [DF], proto TCP (6), length 64)
    <外部机器IP>.58459 > <家公网IP>.9443: Flags [S], cksum 0xd6ee (correct), seq 4221321934, win 65535, options [mss 1380,nop,wscale 6,nop,nop,TS val 1330558720 ecr 0,sackOK,eol], length 0
08:37:35.403015 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    <家公网IP>.9443 > <外部机器IP>.58459: Flags [S.], cksum 0x7234 (correct), seq 3327231958, ack 4221321935, win 64296, options [mss 1380,sackOK,TS val 2836038481 ecr 1330558720,nop,wscale 7], length 0
08:37:35.425535 IP (tos 0x0, ttl 56, id 27529, offset 0, flags [DF], proto TCP (6), length 52)
    <外部机器IP>.58459 > <家公网IP>.9443: Flags [.], cksum 0x93bf (correct), seq 1, ack 1, win 2052, options [nop,nop,TS val 1330558742 ecr 2836038481], length 0
08:37:35.669777 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    <家公网IP>.9443 > <外部机器IP>.58459: Flags [R], cksum 0x4614 (correct), seq 3327231959, win 0, length 0
08:37:35.695736 IP (tos 0x0, ttl 56, id 0, offset 0, flags [DF], proto TCP (6), length 64)
    <外部机器IP>.58460 > <家公网IP>.9443: Flags [S], cksum 0x887a (correct), seq 823364050, win 65535, options [mss 1380,nop,wscale 6,nop,nop,TS val 4282767617 ecr 0,sackOK,eol], length 0
08:37:35.697061 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    <家公网IP>.9443 > <外部机器IP>.58460: Flags [S.], cksum 0x6c51 (correct), seq 2698929042, ack 823364051, win 64296, options [mss 1380,sackOK,TS val 2836038775 ecr 4282767617,nop,wscale 7], length 0
08:37:35.719122 IP (tos 0x0, ttl 56, id 50732, offset 0, flags [DF], proto TCP (6), length 52)
    <外部机器IP>.58460 > <家公网IP>.9443: Flags [.], cksum 0x8ddb (correct), seq 1, ack 1, win 2052, options [nop,nop,TS val 4282767640 ecr 2836038775], length 0
08:37:35.960153 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    <家公网IP>.9443 > <外部机器IP>.58460: Flags [R], cksum 0x8fca (correct), seq 2698929043, win 0, length 0
08:37:35.986325 IP (tos 0x0, ttl 56, id 0, offset 0, flags [DF], proto TCP (6), length 64)
    <外部机器IP>.58463 > <家公网IP>.9443: Flags [S], cksum 0x2ade (correct), seq 631961525, win 65535, options [mss 1380,nop,wscale 6,nop,nop,TS val 3122075983 ecr 0,sackOK,eol], length 0
08:37:35.987785 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    <家公网IP>.9443 > <外部机器IP>.58463: Flags [S.], cksum 0xd58d (correct), seq 2762446797, ack 631961526, win 64296, options [mss 1380,sackOK,TS val 2836039066 ecr 3122075983,nop,wscale 7], length 0
08:37:36.006128 IP (tos 0x0, ttl 56, id 12181, offset 0, flags [DF], proto TCP (6), length 52)
    <外部机器IP>.58463 > <家公网IP>.9443: Flags [.], cksum 0xf715 (correct), seq 1, ack 1, win 2052, options [nop,nop,TS val 3122076008 ecr 2836039066], length 0
08:37:36.006155 IP (tos 0x0, ttl 56, id 12209, offset 0, flags [DF], proto TCP (6), length 211)
    <外部机器IP>.58463 > <家公网IP>.9443: Flags [P.], cksum 0x34ac (correct), seq 1:160, ack 1, win 2052, options [nop,nop,TS val 3122076008 ecr 2836039066], length 159
08:37:36.010798 IP (tos 0x0, ttl 61, id 17891, offset 0, flags [DF], proto TCP (6), length 52)
    <家公网IP>.9443 > <外部机器IP>.58463: Flags [F.], cksum 0xfc65 (correct), seq 8, ack 160, win 502, options [nop,nop,TS val 2836039089 ecr 3122076008], length 0
08:37:36.036664 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    <家公网IP>.9443 > <外部机器IP>.58463: Flags [R], cksum 0x57c3 (correct), seq 2762446798, win 0, length 0

一个个看这个包还有点麻烦,试了一下GPT能否快速从中提炼有效信息,它果然可以。从后端服务机器上也抓了包,表现上类似,GPT给的回复是:

Tip

从你的数据包捕获中,我们看到了一些有趣的事情。在这里,我们可以明确地看到服务器对 PSH, ACK 包的回应。这个包通常用于开始TLS握手。

此外,我们也看到了一个 RST 包。令人惊讶的是,来自客户端的 ACK 包被服务器正确地响应后,随即就发送了 RST 包。这种行为可能表明服务器遇到了错误,无法处理这个连接,因此它选择将其重置。

以下是一些可能的原因:

  • 服务端故障或配置问题:后端服务器(10.42.2.98)上运行的软件可能在尝试处理请求时遇到错误。例如,它可能没有正确配置,或者它不支持客户端尝试使用的特定TLS版本或密码套件。请检查后端服务器的日志以获取更多信息。
  • 网络设备干扰:服务端和USG设备之间的某个网络设备可能正在尝试修改或拦截流量,而这可能导致了上述行为。例如,某些类型的网络设备(如IPS、IDS或代理服务器)可能会阻止特定类型的TLS流量。请检查这些设备的配置并确保它们不会影响TLS流量。

建议你进一步检查服务端的设置以及与服务端之间的网络路径,看看是否存在可能导致这种行为的任何问题。

为了快速排查是否防火墙问题,借助于Unifi的定期备份,很方便的将配置恢复到了一个月前(期间我也没改过配置,并且可以肯定当时服务等肯定是正常的)。问题依旧!! 我还是不死心,觉得不是它还会是谁呢?于是查看其iptables规则,放通各种限制等尝试,都没有解决。

比如,在UBNT终端,查看iptables规则(没错:GPT也懂iptables,并且能从一堆规则中分析所关注的内容):

sudo iptables -L -n -v

也在Unifi Controller中开启了相关规则的日志记录,查看连接日志:

sudo tail -f /var/log/messages

端口转发没有问题,也没有包被丢弃等(看iptables表的统计数据)。问题到底在哪里?这可太神奇了吧!以至于凌晨2点了还没困意oO_Oo。星期天的白天除了带娃,一有空也继续看看此问题。找不到其它想法,求助于技术群的小伙伴,可能上下文关联太多描述不清,没有特别的线索出现。

不经意的联想

突然想起来因为旁路由的设置,我的机器网关设置的都是OpenWRT的IP地址。最近因为玩chatGPT插件等,将代理切换到openclash以便使用一些规则。莫不是这家伙在捣蛋?

赶紧停掉openclash,果不其然,问题消失了!切换到Pa**wall好像也没出现此问题(以前一直用它)。有点欣喜又有点无奈,我怎么忘记最近网络的变动,它也算是一环呢!

查看规则列表,我们确实会定义对于局域网的一些不走代理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 - DOMAIN-SUFFIX,ip6-localhost,🎯 全球直连
 - DOMAIN-SUFFIX,ip6-loopback,🎯 全球直连
 - DOMAIN-SUFFIX,lan,🎯 全球直连
 - DOMAIN-SUFFIX,local,🎯 全球直连
 - DOMAIN-SUFFIX,localhost,🎯 全球直连
 - IP-CIDR,0.0.0.0/8,🎯 全球直连,no-resolve
 - IP-CIDR,10.0.0.0/8,🎯 全球直连,no-resolve
 - IP-CIDR,100.64.0.0/10,🎯 全球直连,no-resolve
 - IP-CIDR,127.0.0.0/8,🎯 全球直连,no-resolve
 - IP-CIDR,172.16.0.0/12,🎯 全球直连,no-resolve
 - IP-CIDR,192.168.0.0/16,🎯 全球直连,no-resolve
 - IP-CIDR,198.18.0.0/16,🎯 全球直连,no-resolve

不过我理解这只是目标地为这些IP的命中后直连,我的场景中目地IP应该是外部发起连接的IP。但连接应该是外部主动发起,为什么openclash会将被动连接建立的回包发给了代理呢,难道不区分主动/被动?这个目前我仍旧无法解释,有懂openclash的同学欢迎给出解释。

但是既然知道是它,做法就比较简单了,只需要如下两个开关选择其一就可以解决问题。

  • 既然我用的是非标端口,那放通常用端口也就OK。
  • 既然因为回包错误被代理,那使其绕过XXIP地址也是一个选项。

openclash配置 题外话:有时在想,国内程序员经常因为某种能力和网络作斗争也是件悲哀的事:( 不过倒是能学点网络知识,只能这么想聊以慰藉啦。

尾声

“事出反常必有妖”。这个事情的出现在近几天,我的定位过程还是走了一些弯路。因为人的记忆力有限,不能快速将一些事情作关联,像在查此问题时,完全忘记了前几天修改过openclash,改后它运转的好好的,你就忘记这事了,没想到带来了如此的副作用。

早上开车上班路上,我在想若能把基础设施完全声明式,可能能有效解决哪里变动不自知的问题。这包括:

  • 路由器的配置。基于EdgeOS的USG是很容易做到这一点的,但要打通自动化更新路由还有点路要走。
  • OpenWRT系统及插件声明式。插件的配置或许不经意间被改错了,以往一般从旧的快照恢复,然而不知道具体哪些修改引入的?

声明式的Linux系统我知道有一个NixOS了,最近打算学习一下。那么,声明式的路由在哪里?(所有插件的版本,参数均是声明的?)好像还有很长的路要走哩。

PS:本次诊断大量借助AI辅助,在效率上有所突破,诊断问题很少因某知识点不够完备受阻,但能否解决问题,思路上比较看