分类 计算机网络 下的文章

随着https的不断普及,目前几乎所有的网站都开启了https,但是有时候网站内的部分资源还是http的,此时因为浏览器的安全策略,浏览器默认会拦截这些资源。导致网站无法正常显示,出现报错:

Mixed Content: The page at 'https://www.xxx.com/' was loaded over HTTPS, but requested an insecure stylesheet 'http://www.xxx.com/xxx.css'. This request has been blocked; the content must be served over HTTPS.

解决方案

http协议中提供了一个头部:

Content-Security-Policy: upgrade-insecure-requests

当它被设置的时候,浏览器会自动将https网站内部的http资源请求升级为https,避免出现mixed content问题。

nginx配置方式:

add_header Content-Security-Policy upgrade-insecure-requests;

参考:Mixed Content

一、创建CA

CA全称是CertificateAuthority,意思是证书颁发机构。只有当CA被认为是受信任的颁发机构时,经过该CA颁发出来的证书才属于受信任的证书。否则,认为证书是不受信任的。

为了生成一个自签名的CA,需要先生成CA私钥:

openssl genrsa -des3 -out rootCA.key 4096

执行命令后,需要输入一个密码作为私钥的密码,后续通过该私钥来生成或者签发证书都要用到这个私钥。

生成私钥后,执行以下命令生成证书:

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

执行后会进入交互式界面,需要输入CN相关信息。等待根证书生成后,一个CA也就创建完成了,此时要做的是生成用户证书,并使用CA证书对它签名,然后就可以用了。

二、生成用户证书

生成用户证书的私钥:

openssl genrsa -out www.maqian.work.key 2048

生成csr,csr是一个证书签名的请求,后面生成证书需要使用这个csr文件:

openssl req -new -key www.maqian.work.key -out www.maqian.work.csr

这一步也需要输入地区、CN等相关的信息,输入完成后,使用CA证书签发用户证书:

openssl x509 -req -in www.maqian.work.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out www.maqian.work.crt -days 500 -sha256

此时,得到的www.maqian.work.keywww.maqian.key.pem就是用户端的私钥和证书。

三、chrome错误

错误码:NET::ERR_CERT_COMMON_NAME_INVALID

错误信息:此服务器无法证实它就是 www.maqian.cn - 它的安全证书没有指定主题备用名称。这可能是因为某项配置有误或某个攻击者拦截了您的连接。

解决方案

新建一个文件ext.ini,写入以下内容:

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.dyxmq.cn
DNS.2 = *.maqian.xin
DNS.3 = *.maqian.io
DNS.4 = *.maqian.co
DNS.5 = *.maqian.cn

[alt_names]下面填写上所有的域名即可,然后签发证书的时候带上参数:

openssl x509 ... -extfile ext.ini

一、问题现象

使用自签名的证书后,chrome报错此服务器无法证实它就是 www.maqian.cn - 它的安全证书没有指定主题备用名称。这可能是因为某项配置有误或某个攻击者拦截了您的连接,错误码是NET::ERR_CERT_COMMON_NAME_INVALID

二、问题原因

生成证书的时候没有加上备用名称字段,目前的浏览器校验证书都需要这个字段。

三、解决方案

生成证书的时候需要添加上备用名称(subjectAltName)扩展字段。

使用openssl添加subjectAltName扩展

创建一个文件ext.ini,填入以下内容:

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.dyxmq.cn

在DNS.1的地方填写上自己的域名,如果有多个域名,可以按照规律DNS.1/DNS.2/DNS.3/...来添加。

同时还支持IP地址的形式,填入IP.1 = x.x.x.x就可以了。

最近公司网络一直抽风,整个机房网络都不通,严重影响工作效率。后面花了大量人力解决问题后没多久,竟然又再次出现问题了。看着纷乱复杂的网络接线,我们也只是“望洋兴叹”!因为参与了整个问题的处理过程(我不是网关,仅参与了排查和分析过程),不由得对问题产生了一些思考和想法:企业网络到底应该如何规划才能避免不出网络问题?

以下是我们机房的冰山一角:

请问我应该怎样做才能快速定位到问题在哪?

最后不得已,把所有的设备全部下架,所有接线全部重连!!!!!!!!!

一、事件回顾

某天中午,突然出现了机房网络访问不了的情况,办公环境访问测试环境断断续续,PING包时通时不通。因为是办公时间,都没有空处理,想着等一下就好了(我们经常出现断网的情况,大部分时候自己就好了),所以也没有管。直到晚上,网络依旧没有好,此时开始引起了网管的关注。

然后网管带着一票人(4、5个)去机房排查,但是情况就是上面那张图一样,如此杂乱无章的机房布线,网线也没有标记,应该从哪里开始排查起?完全不知道从何下手。最后在各个交换机上,拔网线,插网线,PING,历时一天,终于在第二天的晚上发现是一个虚拟机中有IP冲突了导致。虚拟机配置了我们的网关地址,导致中间有个交换机把包转发到虚拟机上了,但是它又没有路由,无法转发数据,所以整个网络就断了。

于是,我们直接把这个虚拟机删除了。但好景不长,一周之后,网络又出问题了。在历经第一次排查的种种困难后,我们直接放弃了。所有设备全部下架,所有网线全部断开,重新接线。整个网络改造差不多又历时一周。

也是在经过了这一连串的问题后才开始慢慢就思考,网络到底要如何规划才能尽量保证不出问题、少出问题?

虽然我不是网管,但我毕竟是通信工程出来的,也是一个合格的网络工程师!很有必要对问题进行总结。

二、常见网络问题

在工作中,最常遇到的网络问题有三种:

  1. 环路:在内网的二层环境中,网线首尾两端同时接到了一个交换机上。
  2. IP冲突:多台设备配置了相同的IP,导致交换机的ARP表经常变化,网络转发出现异常。
  3. MAC漂移:多出现在交换机和其他网络设备堆叠的环境中,因为很多设备堆叠后会使用虚拟MAC,也会导致交换机ARP表经常变化,数据转发出现异常。

其中前面2个网络问题是办公环境中比较常见的,虽然很简单但是很容易被忽视。第3个问题一般出现在网络出口,涉及到多个产品之间的联动才会出现,例如核心交换机和防火墙同时堆叠才可能出现,场景复杂且十分难查,这里先不讨论。

2.1 环路

环路还有一个名字是广播风暴,当一个二层交换机中有两个端口同时被一根网线连接时,就会出现环路的现象。

因为二层交换机只有数据转发的功能,只知道转发数据,所以数据从一个网口出去又从另外一个网口进来的时候,是无法识别到的。它只知道又有数据过来了,继续转发,一直循环就产生了环路。

这个逻辑其实很简单,相信很多人都会觉得这个情况不可能出现,因为不会有人傻到会这么接线。但实际场景中,这个是非常容易出现的,正因为很简单,所以很容易被忽视了。

环路场景

一般企业网络都是内网环境,一部门通过一个交换机连接起来:

这样内部的用户就可以很方便的互相访问,也可以上网,正常情况下是没有问题的。但是突然有一天,又来了一个设备,需要在内网中使用,于是就串进来了,网络拓扑变成了下面的样子:

现在使用是没有问题的,也可以正常上网。这时小组B的成员(一个萌新)也想使用这个小路由器,因为不清楚上层的网络部署,只知道它上面有个交换机。而他自己的电脑也只有一个网口,所以最简单的办法,把新加的小路由器接到的小组B的交换机上。此时的网络拓扑:

这个时候,网络还是没有问题的。然后因为某种原因,萌新把路由器从路由改成网桥。现在问题就出现了,网络中就产生环路了,因为网桥就相当于一根网线,现在整体的拓扑就变成了:

三个交换机之间就出现了环路!

这种场景在我们办公室环境中经常出现,因为我们的产品一个网关产品,经常会用到网桥模式,基本上1-2天网络就会环路一次。

2.2 IP冲突

以小组C为例,小组C下有两台PC,两者的IP都配置了1.1.1.1:

当收到一个去往1.1.1.1的数据包后,交换机就会发送一个arp报文到整个内网,内网PC收到后判断如果是自己的IP地址就返回MAC地址。

此时PC1收到了ARP请求,给交换机回复自己的MAC地址,交换机收到后,记录下“IP-MAC-端口”三者之间的关系,并写到自己的端口表中:

IP地址    MAC地址             端口
------------------------------
1.1.1.1  aa:aa:aa:aa:aa:aa  1

然后把包发到PC1上。但是问题是,PC2也配置了相同的地址,它也给交换机回了一个arp数据,此时交换机就认为数据发生了变化,将自己的端口表变成:

IP地址    MAC地址             端口
------------------------------
1.1.1.1  bb:bb:bb:bb:bb:bb  2

然后后面去往1.1.1.1的数据包就通过端口2发往了PC2,PC1无法接收到数据包,两者就交替出现了无法访问网络的情况。

有些时候,恶意用户会恶意构造出这种情况,以窃取其他用户的数据包,这种行为我们通常称为“ARP欺骗”。

如果只是内网PC地址冲突,只是会导致这两台PC无法正常上网。但是如果是上面我们出现事故场景中的网关地址冲突,就会导致整个内网区域的所有用户无法上网。

三、解决方案

3.1 环路的解决方案

  1. 内网用户多的区域,尽量使用跨三层网络部署。特别是网络相关工作比较多的小组,如果需要频繁接入设备等,直接三层隔离。不要使用二层环境。
  2. 内网环境内慎用网桥和交换机,不清楚网络部署的情况下不要轻易私接交换机,网桥设备确认不会导致网络有问题再接入网络。

3.2 IP冲突的解决方案

  1. IP冲突实际上需要解决的是ARP欺骗问题,有效解决ARP欺骗的办法只有一个:使用静态ARP。现在大多数的网络设备都有静态ARP的功能,就是为了防止ARP欺骗。
  2. 内网PC需要注意不要配相同的IP地址了,一般配置IP地址或者系统启动后,都会发送一个免费ARP到网络环境中,如果有IP冲突,免费ARP就能检测到冲突,系统会给与一个提示,说IP地址冲突了,此时就要注意调整IP地址。

四、规范网络环境的重要性

实际上,最有效的办法就是合理规范网络,不要乱接、私接网线。只有接线规范,有条理,网络是很少出问题的。

同时,机房的管理也很重要,网线必须要有固定走线,不能这里接一根,那里接一根,出问题了也会导致排查十分困难。

网管必须要肩负起自己的作用,对没有报备就接入网络的设备必须强制禁止,保证所有的网络接入都是合理的。

TIME-WAIT状态是TCP四次挥手中的状态,在我的认知中,它是客户端socket的状态。但是最近遇到了个问题是:服务端上某个处于监听状态的socket有很多连接都处于这个状态。

当然在某些特定的场景下,服务端出现大量TIME-WAIT状态的socket状态是合理的,例如爬虫服务器,它要主动发起大量的连接去爬取其他网站上的数据,它们在这个场景中都属于客户端socket,爬完数据主动关闭连接了,所以会导致出现大量TIME-WAIT状态。但是我这个并非一个客户端socket,它是执行了listen的:

图中的9090端口是我监听的端口,只列出来了前10个TIME-WAIT状态的连接,和它一样的socket有接近16w个:

这让我百思不得其解,讲道理这不应该是客户端socket才会有的状态吗?为什么都已经是监听状态的socket还会出现这种状态?为了确认我的记忆没有错误,我又回顾了一次四次挥手的过程:

image.png

  1. 客户端发起关闭socket,此时发送一个FIN数据包到服务端。
  2. 服务端收到客户端的关闭请求后,回一个ACK表示确认收到了关闭请求。
  3. 服务端在一段时间后决定也关闭socket,于是发送一个FIN到客户端。
  4. 客户端收到服务端的关闭请求后,回复一个ACK表示收到了关闭请求。

几乎所有计算机网络相关书籍上对4次挥手的描述都是这样的,TIME-WAIT应该是出现在客户端socket一侧的。。为什么处于监听状态的socket会有呢?它不应该是LAST-ACK状态吗?

这个问题困扰了我大半天,实在是捉摸不透。也是晚上睡觉前突然想起才醒悟过来,发现这个问题原因。

实际上问题原因很简单,TIME-WAIT状态并非是客户端独有的状态,而是主动发起关闭连接方都会拥有的状态。即使是服务端、处于监听状态的socket,只要它向另一端发起了关闭请求,那么它就会产生这个状态。我们的服务端程序中,因为特定业务逻辑的关系,会经常性的主动断开连接,因此就导致了出现大量TIME-WAIT状态的socket。

为了验证这个观点,使用socket编写了两个小程序,一个作为服务端监听8080端口,一个作为客户端去连接服务端。当客户端连接上来后,服务端主动close掉,然后再观察连接确实处于TIME-WAIT状态。

总结:TIME-WAIT状态是客户端socket的状态这个观点实际上是一个思维误区,因为不管是从大学中、工作中聊到的四次挥手都是客户端主动断开连接的,导致我们的思维惯性就认为TIME-WAIT是属于客户端的状态!而实际上这个观点是错误的。

一、证书和CA

HTTPS证书的颁发和验证一共包含以下几个角色:

  1. 顶级CA:最顶级的证书颁发机构,可以签发中间CA/。
  2. 中间CA:也是受信任的证书颁发机构,它由根CA签发,中间证书可以有很多级,中间CA也能再签发中间CA。
  3. 终端证书:由CA签发出来的证书。

三者的关系为:

顶级CA机构一般不直接参数证书颁发,因为顶级CA就一个公私钥,泄密后影响太大。因此一般都是通过中间CA来颁发证书,中间CA可以有多层,中间CA也可以自己再签发中间CA,他们都是等效的。实际上的正式的使用场景也是这样,中间CA会根据加密算法或其他因素再衍生出多个中间CA。以百度的证书,它的证书就是由一个中间CA颁发:

生成证书时,用户只要把自己的公私钥和身份信息(如域名信息等)提交给CA机构就行,CA机构对用户信息加密生成证书给到用户,用户把证书和私钥部署到服务端就能开启HTTPS了。

证书的表现形式

证书有多种表现形式,常用的为pemder格式,两者的区别在于前者是以ascii码表示的证书,后者是二进制形式。pem格式的证书是以下形式:

-----BEGIN CERTIFICATE-----
MIIEozCCBAygAwIBAgIJAIkKM/OEESv3MA0GCSqGSIb3DQEBBQUAMIHnMQswCQYD
...
otgUgl+vsfMW5hy8607ppPM7YWTMUV36N6mVAOGPtntf8HdlbH7MLr+PiAjBspkw
HGWHw5+FYqoBWPALLEi3d7LGHnF/qJchkjttwqakSS0u+sWQIqYD
-----END CERTIFICATE-----

两种证书在各操作系统下都是能被直接打开的(windows需要修改后缀为crt),效果也都一样,可以使用windows的证书管理或者openssl命令转换两种证书格式。

二、客户端校验CA

证书部署到服务器后,客户端请求到证书,会根据证书信息找到对应的根证书签发机构(CA),如果CA受信任,则认为证书可靠,证书中的公钥也可靠,可以建立加密连接。而如果CA不受信任,浏览器就会拦截请求,提示连接不安全,需要用户确认后才建立连接。如chrome浏览器就会弹出以下弹框信息等用户确认后才建立连接:

默认情况下,操作系统都会保存一份受信任根证书列表,这样在访问网站的时候很方便就能确认当前证书的颁发者是不是受信任CA了。windows系统可以在运行中输入certmgr.msc来查看:

ubuntu在:

 /etc/ssl/certs/ca-certificates.crt

centos在:

/etc/pki/tls/certs/ca-bundle.crt

OCSP校验

当本地信任库无法校验证书时,例如证书链不完整时,需要使用OCSP方式来校验证书的有效性,它实际上是一种在线校验证书机制,通过OCSP协议去请求服务端证书来检验CA。

三、部署HTTPS证书

nginx中配置HTTPS证书的方法:

server {
    ssl on; # 开启ssl
    ssl_certificate cert.pem; # 证书路径
    ssl_certificate_key key.pem; # 私钥路径
}

高版本的nginx配置中,取消了ssl on指令,改为了以下形式:

server {
    listen 443 ssl;
    ssl_certificate cert.pem;
    ssl_certificate_key key.pem;
}

证书中如果包含了CA,证书的格式应该为:

用户证书
中间证书
根证书

一、HTTP代理

HTTP代理的实现很简单,和普通的HTTP请求差别不大,只要在请求行的uri路径中加上实际请求的host就可以了:

代理服务器收到这个请求后,会主动请求uri中的host地址(不是请求头部的Host字段中的值),然后去掉这部分构造成一个普通的HTTP请求发送到服务端。服务端响应后的数据也直接发回到客户端。

过程图解:

长连接的场景

当连接是长连接的时候,后面的请求可以不用加上http://ifeng.com,直接和普通的GET请求一样就可以了。

二、HTTPS代理

HTTPS相较于HTTP代理不同的是:HTTPS代理是加密的,代理无法从HTTP请求行中获取到host。因此,执行HTTPS代理前,客户端要先主动发起一个CONNECT请求,表示要连接到的服务端。然后代理程序和服务端建立连接,把后续所有的客户端数据包都发往服务器。

以下是一个HTTPS代理请求的数据包:

过程分解:

  1. 客户端发起CONNECT连接,明确告诉代理程序,帮我连接www.ifeng.com443端口。
  2. 服务端发起对www.ifeng.com:443的连接,成功后给客户端返回HTTP 200
  3. 客户端开始建立HTTPS连接,执行Clinet-Hello过程。

图解:

整个代理过程中,除了多了个CONNECT流程以外,后面所有的请求交互过程和普通HTTPS连接是一样的。

一、socks5协议

socks5协议是一款广泛使用的代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。

代理的工作流程图为:

二、socks5协议交互过程

2.1 基于TCP的客户端连接过程

第一步,客户端向代理服务器发送代理请求,其中包含了代理的版本和认证方式:

                   +----+----------+----------+
                   |VER | NMETHODS | METHODS  |
                   +----+----------+----------+
                   | 1  |    1     | 1 to 255 |
                   +----+----------+----------+

如果是socks5代理,第一个字段VER的值是0x05,表明是socks代理的第5个版本。

第二个字段NMETHODS表示支持的认证方式,第三个字段是一个数组,包含了支持的认证方式列表:

  • 0x00: 不需要认证
  • 0x01: GSSAPI认证
  • 0x02: 用户名和密码方式认证
  • 0x03: IANA认证
  • 0x80-0xfe: 保留的认证方式
  • 0xff: 不支持任何认证方式

服务端收到客户端的代理请求后,选择双方都支持的加密方式回复给客户端:

                         +----+--------+
                         |VER | METHOD |
                         +----+--------+
                         | 1  |   1    |
                         +----+--------+

此时客户端收到服务端的响应请求后,双方握手完成,开始进行协议交互。

数据包分析

客户端开启socks代理,使用浏览器访问网页。抓包软件抓到客户端的认证请求:

服务端的认证响应请求:

2.2 请求

握手完成后,客户端要把需要执行的操作指令发给客户端,表明自己要执行代理的请求。请求帧格式:

        +----+-----+-------+------+----------+----------+
        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+

各字段含义:

  • VER: 代理版本信息
  • CMD: 代理指令

    • 0x01: connect指令,tcp代理时使用。
    • 0x02: bind,很少使用,类似FTP协议中主动连接场景,服务端后服务端会主动连接到客户端。
    • 0x03: udp代理时使用。
  • RSV: 保留字段
  • ATYP: 地址类型

    • 0x01: IPv4地址类型
    • 0x03: unix域socket类型代理
    • 0x04: IPv6地址类型
  • DST.ADDR: 需要连接的目的地址
  • DST.PORT: 需要连接的目的端口

数据包分析

以下是一个去往console.cloud.tencent.com:443的socks5代理请求:

2.3 响应

客户端发完上面的请求连接后,服务端会发起连接到DST.ADDR:DST.PORT,然后返回响应到客户端,响应格式:

        +----+-----+-------+------+----------+----------+
        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+

其中VER/RSV/ATYP的含义和上面相同,其他字段的意思:

  • REP: 请求响应

    • 0x00: 成功
    • 0x01-0x08: 失败
    • 0x09-0xff: 未使用
  • BND.ADDR: 连接到的远程地址
  • BND.PORT: 连接到的远程端口

数据包分析

以下是上面代理请求的响应数据包:

2.4 代理通信

当连接建立后,客户端就可以和正常一样访问服务端通信了,此时通信的数据除了目的地址是发往代理程序以外,所有内容都是和普通连接一模一样。对代理程序而言,后面所有收到的来自客户端的数据都会原样转发到服务读端。

例如代理的HTTPS请求连接,实际上发送的数据和普通HTTPS交互过程一模一样:

中间的Socks Protocol栏是wireshark根据连接上下文自动解析出来的,实际上数据包中并没有这一栏。

三、流程图

socks5通信的交互流程:

四、参考

RFC1928 - SOCKS Protocol Version 5

RFC1929 - Username/Password Authentication for SOCKS V5

一、问题现象

客户报障,说使用了我们的网关设备后网络经常中断。具体的情形是用我们设备做代理上网后,流量图显示流量每隔两分钟就会直线下跌一次,同时就伴随着内网用户断网。

经过排查后发现问题原因是设备负载太高了导致,出问题时候的负载趋势图为:

从图形来看,负载是每1-2分钟就会上升一次,而且上升得特别明显,最高到达了五十多。但设备只是一个4核的设备,这么高的负载肯定是无法承受了。可以断定断网是负载高导致的。

于是继续分析mpstat中对CPU的采样,看看是什么原因导致的设备负载高:

linux 2.6.30-os (localhost) 02/27/20 _x86_64_    (4 CPU)

Average:     CPU    %usr   %nice    %sys   %soft  %idle
Average:     all   17.50    4.75   18.00   29.75  27.75
Average:       0   17.00    5.00   16.00    4.00  57.00
Average:       1   17.17    0.00   18.18   60.61   0.00
Average:       2   16.16   13.13   16.16    1.01  53.54
Average:       3   20.00    0.00   21.00   54.00   1.00

从cpu抽样数据可以看到,出问题的时候,有2个CPU都已经跑满了,其中占用最高的是soft,两个核占用都超过了一半。soft是系统软中断调用,是内核层面的原因导致,需要进一步排查内核中的问题。

内核问题一般回打印到messages或者dmesg,我们的设备是都保存到了一个dmesg文件中,因此下一步思路就是分析dmesg了。果然,查看dmesg就看到了大量的错误信息:

TCP: time wait bucket table overflow
...
Out of socket memory
...
TCP: too many of orphaned sockets

经过查找资料后,整理出来三个错误日志的意思:

  • TCP: too many of orphaned sockets: 太多的孤儿socket
  • Out of socket memory: socket占用的内存超出
  • TCP: time wait bucket table overflow: 保存TIME-WAIT状态socket的哈希表槽溢出

对比错误日志产生的时间和系统负载增加的时间发现两者都能对上,负载高的时候打印日志更频繁,负载低的时候几乎没有什么日志。这就说明导致负载高的原因和这三条日志有关了,下一步思路是分析为什么要打印这些日志了。

二、分析孤儿socket

2.1 什么是孤儿socket

说到孤儿socket的原因是Out of socket memory日志,说实话在遇到这个问题之前我并不知道有孤儿socket的存在,也是在网上查这个日志信息才知道的。产生这个日志的原因有两个:

  1. 孤儿socket太多,超出系统阈值,阈值可通过cat /proc/sys/net/ipv4/tcp_max_orphans查看。
  2. TCP缓冲区超出系统阈值,缓冲区的大小可通过cat /proc/sys/net/ipv4/tcp_mem查看。

通过另外两个日志信息基本可以判断是第一种情况了——孤儿socket太多导致的。那么孤儿socket到底是什么呢?

大部分的资料都是这么描述孤儿socket的:孤儿socket是已经没有和文件句柄绑定、但是仍然存在于内核没有被释放的socket。

这个说法相当官方,并且笼统,虽然说的并没有错误。实际用接地气的话来描述的话就是那些已经执行了close()但是状态还没有到达CLOSED状态的socket。

回忆一下TCP四次挥手的过程:

客户端执行close()函数来关闭socket,此时会发送FIN包到服务端,客户端socket的状态是FIN-WAIT-1,等到服务端ACK后状态变成FIN-WAIT-2。然后服务端也执行close(),此时socket就变成了TIME-WAIT状态,等到2MSL时间过去后才变成CLOSED。孤儿socket指的是状态在FIN-WAIT-1到TIME-WAIT之间的socket,他们已经准备释放了,但是还没有达到完全释放的条件。

2.2 分析孤儿socket状态

在确定了问题原因是孤儿socket导致的之后,排查的思路就是确定孤儿socket的状态了。

查看系统支持的最大孤儿socket数量:

查看当前socket使用状态:

# 方法1
cat /proc/net/sockstat
# 方法2
ss -s

可以看到当前环境中孤儿socket有65506个,距离超出系统负载就差一点点了。而closed和timewait状态的socket更是有10w+个之多,这种情况下内核确实承受了它这种配置不该承受的压力。

此时,下一步的排查思路就变成了为什么会有这么多socket无法完全释放了。

三、问题原因分析

当问题定位孤儿socket后,网上所有的教程都是通过放大tcp_max_orphans参数来解决,实际上是否真正能解决问题呢?调大之后会不会导致孤儿socket继续增加,是否会导致系统运行异常,这些都是有待确认的。

因为是线上环境,为了避免调整导致网络环境出现更大的异常,所以没有直接按照教程把值往上调整一倍,而是增加了5%左右。调整完成后,实际上并没有太大的效果,孤儿socket还是一直增加到了上限,dmesg也是一直打印日志,因此可以认为这种方法不可取,再往大调只会导致情况更加恶化。具体的原因还得具体再分析。

3.1 统计连接数

根据上面ss命令的结果能看到,系统处于关闭状态(执行了close()或者shutdown()之后)的socket数量很多,总量加起来接近30w个,因此首要的问题是如何定位到是什么程序产生的socket。

通过ss命令统计出所有的连接状态:

ss -ant >ss.txt 

因为连接数量很多,统计需要很长时间,所以定位到文件避免后面需要多次执行命令浪费时间。

拿回来后,分别统计出各个socket状态的数量:

imagea0fafcb19f6453e8.png

其中,最多的是FIN-WAIT-1和TIME-WAIT状态,总共有差不多17w个。它们一个是主动关闭socket的状态,一个是被动关闭socket的状态,都属于客户端socket的状态,所以问题应该出现在我们作为客户端主动发起的连接上身上。

在知晓了这一点之后,要做的应该是统计出连接的五元组信息,哪个IP、哪个端口最多,然后通过lsof命令定位到具体的程序。但是结合业务逻辑来分析,很容易就想到是我们设备上的代理程序,根本无需再统计这些信息。

因为我们设备是作为代理上网,代理程序势必要作为客户端去连接服务端,只有他才可能产生这么大规模的连接数。下一步排查的思路应该就是查它了,为什么它会产生这么多无法释放的socket。

3.2 抓包

从上面统计的状态来看,FIN-WAIT-1和TIME-WAIT状态都是处于我们已经做完了自己的事情,等待服务端响应时候的状态,我们自身出问题的可能性较小,多半是服务端除了问题导致的,所以再往下的排查思路应该是对端。

要确定是否是对端的问题,抓包就可以了,抓取wan口所有的TCP数据包,抓100w个:

tcpdump -i eth2 tcp -nnv -c 1000000 -w e2.pcap

拿到数据包后,第一步,统计所有FIN状态的数据包,在过滤器中输入:

tcp.flags.fin eq 1

为什么要统计FIN状态的数据包?

因为大量的socket都处于被关闭之后的状态,所以分析的重点就是FIN过程(即四次挥手)中那些连接。

统计出来,FIN包一共有45w个,占了所有包的45%:

这明显是一个不合理的数字,正常网络环境中不可能出现这么大比例的FIN包。随机找到一个连接追踪流分析:

左边被打马赛克的是设备的IP地址,右边的是服务端地址。服务端地址实际上是微信的服务器地址,通过应用特征分析属于微信下载小视频的流量。

很明显能看到,设备发送了FIN出去之后,并没有收到预期中的服务端回复,一直在重传等待服务端回复。直到差不多14秒之后才收到了服务端的回复信息。

到这里,就可以得到初步结论了,问题原因应该是下面二者之一:

  1. 微信服务器出了问题,回复数据包太慢了。
  2. 出口网络环境存在问题,运营商线路不稳定。

四、解决方案

解决FIN-WAIT-1和TIME-WAIT状态的方案百度有很多,基本上都是说调整fin超时时间或者其他内核参数来解决。

调整fin超时时间是调整下面这个内核参数:

理论上说这个方案是有效的,并且同时还调整了以下参数:

# 允许重用处于TIME-WAIT状态的socket
net.ipv4.tcp_tw_reuse = 1
# 开启快速回收socket
net.ipv4.tcp_tw_recycle = 1

然而实际上,调整之后,即使超时时间只有1秒,孤儿socket还是溢出了,因为内网的用户流量实在太大,即使只占用1秒对设备而言也是无法承受的。

下一步的思路只能是协调运营商和腾讯去排查了,看看为什么网络状态会如此之差!

是道德的沦丧,还是钱没给够?我猜是后者!

五、参考

The "Out of socket memory" error

一、问题描述

在HTTPS连接被中间人代理后(一般出现在公共场所,例如公共WIFI或者需要ssl解密的场景),第一次访问网站会弹出HSTS错误:

HSTS是一个很简单的访问安全策略,通过在HTTP头部中增加字段告诉客户端HTTPS连接的相关信息,客户端(浏览器)通过这些信息校验连接是否可信。

因此,出现这个错误的原因是因为服务端的HTTPS证书被劫持了、而服务端又开启了HSTS策略导致的。完美的解决方案只有一种,就是避免网站被劫持。临时的解决方案可以通过修改浏览器配置来完成。

二、解决办法

在浏览器(chrome)输入:

chrome://net-internals/#hsts

拉到最下面,输入刚刚访问的网站域名,点击delete