你们好,我是轩辕。html
前几天,我在读者群里提了一个问题:nginx
这一下,你们总算中止了灌水(这群人都不用上班的,每天划水摸鱼),开始讨论起这个问题来。git
有的说经过User-Agent能够看,我直接给了一个狗头。github
而后发现不对劲,改口说能够经过HTTP响应的Server字段看,好比看到像这种的,那确定Windows无疑了。算法
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Fri, 23 Aug 2019 01:02:08 GMT
Accept-Ranges: bytes
ETag: "e65855634e59d51:0"
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Fri, 23 Jul 2021 06:02:38 GMT
Content-Length: 1375
复制代码
还有的说能够经过URL路径来判断,若是大小写敏感就是Linux,不敏感就是Windows。编程
因而我进一步提升了难度,若是连Web服务也没有,只有一个TCP Server呢?服务器
这时又有人说:能够经过ping这个IP,查看ICMP报文中的TTL值,若是是xxx就是xx系统,若是是yyy就是yy系统···(不过有些状况下也不是太准确)markdown
今天想跟你们探讨的是另一种方法,这个方法的思路来源于前几天被删掉的那篇文章。就是日本网络环境下访问不了极客时间的问题,当时抓包看到的状况是这个图的样子:网络
看到了服务器后面在不断的尝试重发了吗?当时我就想到了一个问题:tcp
服务器到底会重传好屡次呢?
众所周知,TCP是一种面向链接的、可靠的、基于字节流的传输层通讯协议。
其中,可靠性的一个重要体现就是它的超时重传机制。
TCP的通讯中有一个确认机制,我发给你了数据,你得告诉我你收到没,这样双方才能继续通讯下去,这个确认机制是经过序列号SEQ和确认号ACK来实现的。
简单来讲,当发送方给接收方发送了一个报文,而接收方在规定的时间里没有给出应答,那发送方将认为有必要重发。
那具体最多重发多少次呢?关于这一点,RFC中关于TCP的文档并未明确规定出来,只是给了一些在总超时时间上的参考,这就致使不一样的操做系统在实现这一机制的时候可能会有一些差别。因而我进一步想到了另外一个问题:
会不会不一样操做系统重传次数不同,这样就能经过这一点来判断操做系统了呢?
而后我翻看了《TCP/IP详解·卷1》,试图在里面寻找答案,果真,这本神书历来没有让我失望过:
这一段说了个什么事情呢?大意是说RFC标准中建议有两个参数R1和R2来控制重传的次数,Linux中,这俩参数能够这样看:
cat /proc/sys/net/ipv4/tcp_retries1
cat /proc/sys/net/ipv4/tcp_retries2
复制代码
tcp_retries1
默认值是3,tcp_retries2
默认值是15。
但须要特别注意的是,并非最多重传3次或者15次,Linux内部有一套算法,这两个值是算法中很是重要的参数,而不是重传次数自己。具体的重传次数还与RTO
有关系,具体的算法有兴趣的朋友能够看看这篇文章:聊一聊重传次数(perthcharles.github.io/2015/09/07/…)
整体来讲,在Linux上重传的次数不是一个固定值,而是不一样的链接根据tcp_retries2
和RTO
计算出来的一个动态值,不固定。
而在Windows上,也有一个变量来控制重传次数,能够在注册表中设定它:
键值路径:
HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
键值名:
TcpMaxDataRetransmissions
默认值:5
复制代码
我手里有一份Windows XP的源码,在实现协议栈的驱动tcpip.sys
的部分中,也印证了这个信息:
不过就目前的信息来看,因为Linux的重传次数是不固定的,还无法用这个重传次数来判断操做系统。
就在我想要放弃的时候,我再一次品读《TCP/IP详解·卷1》中的那段话,发现另外一个信息:TCP的重传在创建链接阶段和数据传输阶段是不同的!
上面说到的重传次数限制,是针对的是TCP链接已经创建完成,在数据传输过程当中发生超时重传后的重传次数状况描述。
而在TCP创建链接的过程当中,也就是三次握手的过程当中,发生超时重传,它的次数限定是有另一套约定的。
Linux:
在Linux中,另外还有两个参数来限定创建链接阶段的重传次数:
cat /proc/sys/net/ipv4/tcp_syn_retries
cat /proc/sys/net/ipv4/tcp_synack_retries
复制代码
tcp_syn_retries
限定做为客户端的时候发起TCP链接,最多重传SYN的次数,Linux3.10中默认是6,Linux2.6中是5。
tcp_synack_retries
限定做为服务端的时候收到SYN后,最多重传SYN+ACK的次数,默认是5
重点来关注这个tcp_synack_retries
,它指的就是TCP的三次握手中,服务端回复了第二次握手包,但客户端一直没发来第三次握手包时,服务端会重发的次数。
咱们知道正常状况下,TCP的三次握手是这个样子的:
但若是客户端不给服务端发起第三个包,那服务端就会重发它的第二次握手包,状况就会变成下面这样:
因此,这个tcp_synack_retries
实际上规定的就是上面这种状况下,服务端会重传SYN+ACK的次数。
为了进一步验证,我使用Python写了一段代码,用来手动发送TCP报文,里面使用的发包库是scapy,这个我以前写过一篇文章介绍它:面向监狱编程,就靠它了!。
下面的这段代码,我向目标IP的指定端口只发送了一个SYN包,:
def tcp_syn_test(ip, port):
# 第一次握手,发送SYN包
# 请求端口和初始序列号随机生成
# 使用sr1发送而不用send发送,由于sr1会接收返回的内容
ans = sr1(IP(dst=ip) / TCP(dport=port, sport=RandShort(), seq=RandInt(), flags='S'), verbose=False)
复制代码
用上面这段代码,向一台Linux的服务器发送,抓包来看一下:
实际验证,服务器确实重传了5次SYN+ACK报文。
一台服务器说明不了问题,我又多找了几个,结果都是5次。
再来看一下Linux的源码中关于这个次数的定义:
接下来看一下Windows上的状况。
前面说过,在注册表HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
目录下有一个叫TcpMaxDataRetransmissions
的参数能够用来控制数据重传次数,不过那是限定的数据传输阶段的重传次数。
根据MSDN
上的介绍,除了这个参数,还有另外一个参数用来限制上面SYN+ACK重传的次数,它就是TcpMaxConnectResponseRetransmissions
。
并且有趣的是,和Linux上的默认值不同,Windows上的默认值是2。
这就有意思了,经过这一点,就能把Windows和Linux区分开来。
我赶忙用虚拟机中的XP上跑了一个nginx,测试了一下:
果真是2次,随后我又换了一个Windows Server 2008,依旧是2次。
为了进一步验证,我经过注册表把这个值设定成了4:
再来试一下:
重传次数果真变成了4次了。
接下来在手中的Windows XP源码中去印证这个信息:
果真,不论是从实验仍是从源码中都获得了同一个结论:
Linux上,SYN+ACK默认重传5次。
Windows上,SYN+ACK默认重传2次。
若是一个IP开启了基于TCP的服务,不论是不是HTTP服务,均可以经过向其发送SYN包,观察其回应来判断对方是一个Linux操做系统仍是一个Windows操做系统。
固然,这种方法的局限性仍是挺大的。
首先,本文只介绍了一些默认的状况,但TCP的重传次数是能够更改的,若是网络管理员更改了这个数值,判断的结果就不许确了。
其次,对于有些网络服务器开启了防DDoS功能,测试发现,其根本不会重传SYN+ACK包,好比我用百度的IP测试就获得了这样的结果。
最后,没有测试其余操做系统上的状况,好比Unix和MAC OSX,为何呢?
所以,文中介绍的这种方法只能做为一种辅助手段,仅供参考,你们能顺便了解一些关于TCP重传的知识也是颇有意义的。
好了,以上就是今天的分享了,写做不易,你们看完给个三连支持呀~