《网络排查案例课》

《网络排查案例课》01 | 网络模型和工具:网络为什么要分层?

七层模型;四层 / 五层模型;五元组;四元组
===============================================
OSI 的七层模型,和 TCP/IP 的四层 / 五层模型
五元组:传输协议类型、源 IP、源端口、目的 IP、目的端口
四元组:源 IP、源端口、目的 IP、目的端口
七层模型;四层 / 五层模型;五元组;四元组

《网络排查案例课》02 | 抓包分析技术初探:你会用tcpdump和Wireshark吗?

《网络排查案例课》03 | 握手:TCP连接都是用TCP协议沟通的吗?

案例1:TCP 连接都是用 TCP 协议沟通的吗?(不是的,比如server端未监听某端口,那么会使用icmp type 3 返回端口不可达信息)
-----------------------------------------------------------------------
案例1:TCP 连接都是用 TCP 协议沟通的吗?(不是的,比如server端未监听某端口,那么会使用icmp type 3 返回端口不可达信息)
比如server端未监听某端口,那么会使用icmp type 3 返回端口不可达信息

server端要拒绝连接的话,会以下两种情形:
    1.静默丢包;
        客户端将会不明真相:a.静默丢包;b.去向丢包;c.回向丢包
    2.明确拒绝


$ sudo sysctl net.ipv4.tcp_syn_retries
net.ipv4.tcp_syn_retries = 6


Iptables -I INPUT -p tcp --dport 80 -j REJECT     #实验配置的这条规则
-A INPUT -p tcp -m tcp --dport 80 -j REJECT --reject-with icmp-port-unreachable     #自动补上了–reject-with icmp-port-unreachable
-A INPUT -p tcp -m tcp --dport 80 -j REJECT -–reject-with tcp-reset     #可以手动修改为tcp rst
案例1:TCP 连接都是用 TCP 协议沟通的吗?(不是的,比如server端未监听某端口,那么会使用icmp type 3 返回端口不可达信息)
案例2:Windows 服务器加域报 RPC service unavailable?;案例3:发送的数据还能超过接收窗口?
================================================================================
案例2:Windows 服务器加域报 RPC service unavailable?

使用netstat -antp 在客户端进行查看,发现客户端卡在SYN_SENT 状态,最终确认了使因为防火墙未放行端口,过滤了报文。

-----------------------------------------------------------------------
案例3:发送的数据还能超过接收窗口?

问题:wireshark中,Redis 服务告诉客户端它的接收窗口是 190 字节,但是客户端居然会发送 308 字节,大大超出了接收窗口
根因:抓包没有抓到3次握手的协商报文,wireshark无法对报文的窗口进行正确的解析

    TCP Options 的 Window Scale 字段只出现在TCP3次握手的协商阶段,它表示原始 Window 值的左移位数,最高可以左移 14 位。
    根因在于这次抓包没有抓到tcp协商过程,所以wireshark认为协商的窗口大写为65535字节;而实际的窗口大小应该是服务端通告的窗口190字节* Window Scale数值

    在分析抓包文件时,要注意是否连接的握手包被抓取到,没有握手包,这个 Window 值一般就不准。
案例2:Windows 服务器加域报 RPC service unavailable?;案例3:发送的数据还能超过接收窗口?
UDP 也有握手?(nc探测UDP端口,无响应则认为succeeded,该结果是不可信的!!!);服务器端的最大连接数
====================================================================================================================================
UDP 也有握手?
有些同学会有这个误解,可能是跟 nc 这个命令有关。
$ nc -v -w 2 47.94.129.219 22
Connection to 47.94.129.219 22 port [tcp/ssh] succeeded!
victor@victorebpf:~$ nc -v -w 2 47.94.129.219 -u 22
Connection to 47.94.129.219 22 port [udp/*] succeeded!      #UDP测试,显示successded
抓包发现UDP只有发包,那么nc怎么回显示succeeded呢?
    可能只是因为对端没有回复 ICMP port unreachable

当你下次用 nc 探测 UDP 端口,不通的结果是可信的,而能通(succeeded)的结果并不准确,只能作为参考!!!!!!

------------------------------------------------------------------------------------------------------------------------------------
服务器端最多65535个连接,确实是个误区,其实这个跟很多都有关系的,比如服务器端的CPU、内存、fd数以及连接的情况,fd数是前提。
一个连接会牵扯到服务端的接收缓冲区(net.ipv4.tcp_rmem)以及发送缓冲区(net.ipv4.tcp_wmem),一个空的TCP连接会消耗3.3KB左右的内存,如果发数据的话,一个连接占用的内存会更大。
所以理论上4GB的机器理论上支持的空TCP连接可以达到100W个。
此外数据经过内核协议栈的处理需要CPU,所以CPU的好坏也会影响连接数。
UDP 也有握手?(nc探测UDP端口,无响应则认为succeeded,该结果是不可信的!!!);服务器端的最大连接数

《网络排查案例课》04 | 挥手:Nginx日志报connection reset by peer是怎么回事?

突破应用层日志和网络报文的鸿沟:时间吻合;RST 行为吻合;URL 路径吻合。
--------------------------------------------------------------
在应用层和网络层之间搭建桥梁
    做网络排查的第一个要点:把应用层的信息,“翻译”成传输层和网络层的信息。

应用现象跟网络现象之间的鸿沟:你可能看得懂应用层的日志,但是不知道网络上具体发生了什么。
工具提示跟协议理解之间的鸿沟:你看得懂 Wireshark、tcpdump 这类工具的输出信息的含义,但就是无法真正地把它们跟你对协议的理解对应起来。
突破应用层日志和网络报文的鸿沟:时间吻合;RST 行为吻合;URL 路径吻合。
案例 1:connection reset by peer?;附赠nginx日志解读
===================================================================================================================
案例背景:
    客户反馈,他们的 Nginx 服务器上遇到了很多 connection reset by peer 的报错。
    客户的应用是一个普通的 Web 服务,架设在 Nginx 上,而他们的另外一组机器是作为客户端,去调用这个 Nginx 上面的 Web 服务。
    客户端---nginx---服务端

根因:client的POST,nginx已经回复HTTP 200;但client还是发送了RST 重置连接;问题回到了client侧,进一步排查的话,需要查看client侧的源码了

排查及分析思路:
    1.在客户端上进行抓包;
    2.使用wireshark进行RST报文的过滤,发现了大量的RST报文 "ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1"
        任意选取了一个RST报文,发现该RST报文是次握手的第3个报文,所以实际上该tcp连接还未成功建立,那么应用层就还不知道该连接的存在,不会有该连接的日志了
        结论:该RST报文和nginx日志"connection reset by peer"无关
    3.继续更新wireshark过滤规则,锁定目标RST报文 "frame.time >="dec 01, 2015 15:49:48" and frame.time <="dec 01, 2015 15:49:49" and ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1)"
        应用层日志和网络报文是存在鸿沟的,没有直接的关系,但是可以通过以下的方式进行判断
            锁定 TCP 流和某条日志的对应关系,主要三点原因:时间吻合;RST 行为吻合;URL 路径吻合。
    4.追踪该TCP流,发现client的POST,nginx已经回复HTTP 200;但client还是发送了RST 重置连接;问题回到了client侧,进一步排查的话,需要查看client侧的源码了

--------------------------------------------------------------------------------------
nginx日志
2015/12/01 15:49:48 [info] 20521#0: *55077498 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/weixin/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/weixin/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:54 [info] 20523#0: *55077722 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:54 [info] 20523#0: *55077710 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:58 [info] 20522#0: *55077946 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"
2015/12/01 15:49:58 [info] 20522#0: *55077965 recv() failed (104: Connection reset by peer) while sending to client, client: 10.255.252.31, server: manager.example.com, request: "POST /WebPageAlipay/app/notify_url.htm HTTP/1.1", upstream: "http:/10.4.36.207:8080/WebPageAlipay/app/notify_url.htm", host: "manager.example.com"

recv() failed:这里的 recv() 是一个系统调用,也就是 Linux 网络编程接口。它的作用呢,看字面就很容易理解,就是用来接收数据的。
104:这个数字也是跟系统调用有关的,它就是 recv() 调用出现异常时的一个状态码,这是操作系统给出的。在 Linux 系统里,104 对应的是 ECONNRESET,也正是一个 TCP 连接被 RST 报文异常关闭的情况。
upstream:在 Nginx 等反向代理软件的术语里,upstream 是指后端的服务器。
    客户端把请求发到 Nginx,Nginx 会把请求转发到 upstream,等后者回复 HTTP 响应后,Nginx 把这个响应回复给客户端。


在网络运维的视角上,我们更关注网络报文的流向,因为 HTTP 报文是从外部进来的,那么我们认为其上游(upstream)是客户端;
但是在应用的视角上,更关注的是数据的流向,一般来说 HTTP 数据是从内部往外发送的,那么在这种视角下,数据的上游(upstream)就是后端服务器了。
    Nginx、Envoy 都属于应用网关,所以在它们的术语里,upstream 指的是后端环节。这里没有对错之分,你只要知道并且遵照这个约定就好了。

--------------------------------------------------------------------------------------
2.使用wireshark进行RST报文的过滤,发现了大量的RST报文 "ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1"
    任意选取了一个RST报文,发现该RST报文是次握手的第3个报文,所以实际上该tcp连接还未成功建立,那么应用层就还不知道该连接的存在,不会有该连接的日志了
    结论:该RST报文和nginx日志"connection reset by peer"无关


    客户端抓包
    ip.addr eq my_ip:过滤出源IP或者目的IP为my_ip的报文
    ip.src eq my_ip:过滤出源IP为my_ip的报文
    ip.dst eq my_ip:过滤出目的IP为my_ip的报文

    tcp.flags.reset eq 1

    ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1
        在 Wirershark 窗口的右下角,就有符合过滤条件的RST报文个数,这里有 9122 个,占所有报文的 4%


    我们就要先了解应用程序是怎么跟内核的 TCP 协议栈交互的。一般来说,客户端发起连接,依次调用的是这几个系统调用:
        socket()
        connect()
    而服务端监听端口并提供服务,那么要依次调用的就是以下几个系统调用:
        socket()
        bind()
        listen()
        accept()


    抓包现象:client在3次握手的第3个包,直接回复了TCP RST,ACK

        服务端的用户空间程序要使用 TCP 连接,首先要获得上面最后一个接口,也就是 accept() 调用的返回。
        而 accept() 调用能成功返回的前提呢,是正常完成三次握手。
        这次失败的握手,也不会转化为一次有效的连接了,所以 Nginx 都不知道还存在过这么一次失败的握手。

        当然,在客户端日志里,是可以记录到这次握手失败的。这是因为,客户端是 TCP 连接的发起方,它调用 connect(),而 connect() 失败的话,其 ECONNRESET 返回码,还是可以通知给应用程序的。

        所以 3次握手的 RST,不是我们要找的那种“在连接建立后发生的 RST”。

--------------------------------------------------------------------------
3.更新wireshark过滤规则,锁定目标RST报文 "frame.time >="dec 01, 2015 15:49:48" and frame.time <="dec 01, 2015 15:49:49" and ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1)"


frame.time >="dec 01, 2015 15:49:48" and frame.time <="dec 01, 2015 15:49:49" and ip.addr eq 10.255.252.31 and tcp.flags.reset eq 1 and !(tcp.seq eq 1 or tcp.ack eq 1)


应用层日志和网络报文是存在鸿沟的,没有直接的关系,但是可以通过以下的方式进行判断
    锁定 TCP 流和某条日志的对应关系,主要三点原因:时间吻合;RST 行为吻合;URL 路径吻合。

--------------------------------------------------------------------------
4.追踪该TCP流,发现client的POST,nginx已经回复HTTP 200;但client还是发送了RST 重置连接;问题回到了client侧,进一步排查的话,需要查看client侧的源码了

然后根据该RST报文找到了TCP会话,发现实际上nginx实际已经响应client的post请求,并回复了http 200;
    奇怪的是client却发送RST报文结束该连接



本案例通过规则最终锁定了与nginx错误日志匹配的数据包,追踪该TCP流,发现client的POST,nginx已经回复HTTP 200;但client还是发送了RST 重置连接;问题回到了client侧,进一步排查的话,需要查看client侧的源码了
避免这种 reset,需要客户端代码进行修复
    客户端用 RST 来断开连接并不妥当,需要从代码上找原因。比如客户端在 Receive Buffer 里还有数据未被读取的情况下,就调用了 close()。
        网络不稳定,或者防火墙来几个 RST,也都有可能导致类似的 connection reset by peer 的问题。
案例 1:connection reset by peer?;附赠nginx日志解读
案例 2:一个 FIN 就完成了 TCP 挥手?"ack搭车"
==========================================================================================
TCP 的挥手是任意一端都可以主动发起的。也就是说,挥手的发起权并不固定给客户端或者服务端。

仅仅只是展示了一个案例,看起来只有一个FIN
实际上另一个FIN是在POST请求中一起发出的。。。

同时因为ack搭车的关系,这个挥手只有3个报文

实际上 TCP 挥手可能不是表面上的四次报文,因为并包也就是 Piggybacking 的存在,它可能看起来是三次。
案例 2:一个 FIN 就完成了 TCP 挥手?"ack搭车"

 《网络排查案例课》05 | 定位防火墙(一):传输层的对比分析

案例:应用超时,最终报错(防火墙bug)
===================================================================================
背景:
    当时 eBay 内部的一个应用 A 访问应用 B 的时候,经常耗时过长,甚至有时候事务无法在限定时间内完成,就导致报错。
    而且我们发现,问题都是在访问 B 的 HTTPS 时发生的,访问 B 的 HTTP 就一切正常。

根因:
    在客户端和服务端之间,各有一道防火墙,两者之间设立有隧道;
    因为软件 Bug 的问题,这个隧道在大包的封包拆包的过程中,很容易发生乱序。
    疑问:乱序为什么就导致报错?可能是因为超时吧,触发了超时重传,但是没有在限定时间内完成,所以就导致报错了

分析过程:
    通过通过在client的抓包分析发现存在乱序,同时还存在tcp重传的现象,根据重传时间和报文可以判断,该重传是tcp超时重传
    通过通过在client和server端同时抓包分析发现,server端不存在乱序,也确认了tcp超时重传
    client和server端之间存在两台防火墙,最终通过升级防火墙解决了该问题

    说的挺复杂,应用报错的应用层面根因是因为TCP超时导致的。。。


为什么 HTTP 事务没有被影响,只有 HTTPS 被影响?
    在这个案例里,HTTP 确实一直没有被影响到。因为从抓包来看,这个场景的 HTTP 的 TCP 载荷,其实远没有达到一个 MSS 的大小。
    TCP 载荷只有两三百字节,远小于 MSS 的 1460 字节。

    一般有隧道的场景下,主机的 MTU 都需要改小以适配隧道需求。
        如果网络没有启用 Jumbo Frame,那这个 1520 字节的报文,就会被路由器 / 防火墙拆分为 2 个报文。
        而到了接收端,又得把这两个报文合并起来。这一拆一合,出问题的概率就大大增加了。

    事实上,在大包情况下,这个隧道引发的是两种不同的开销:
        IPIP 本身的隧道头的封包和拆包;
        IP 层因为超过 MTU 而引发的报文分片和合片。

    因为 HTTPS 是基于 TLS 加密的,TLS 握手阶段的多个 TCP 段(segment)就都撑满了 MSS(也就是前面分析的 1、2、3 的数据包),于是就触发了防火墙隧道的 Bug。
案例:应用超时,最终报错(防火墙bug)
telnet 挂起
===========================================================
关于 telnet 挂起。这里说的“挂起”,是指没有进一步的反应了,
    究其原因,就是 telnet 发送了 SYN 后没有收到 SYN+ACK。我们在第 21 讲提到了系统调用,那么对于 telnet 这样的用户空间程序来说,它要发起 TCP 连接,就必须调用 connect() 这个系统调用,后者会负责发出 SYN。但是 SYN 发出去后,对端没有回复 SYN+ACK,这就导致 connect() 阻塞,telnet 程序也只好等在那里,表象上就是挂起了。

strace telnet www.baidu.com 500
strace 停留在 connect() 系统调用这里了:
    ......
    socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
    setsockopt(3, SOL_IP, IP_TOS, [16], 4)  = 0
    connect(3, {sa_family=AF_INET, sin_port=htons(500), sin_addr=inet_addr("45.113.192.102")}, 16
telnet 挂起

 《网络排查案例课》06 | 定位防火墙(二):网络层的精确打击

IP 包的归宿;wireshark分析细节之TTL值
============================================================================================
IP 包的归宿,无非以下几种:
    网络包最终达到目的地;
    进入路由黑洞并被丢弃;
    因为网络设备问题被中途丢弃;
    持续被路由转发并 TTL 减 1,直到 TTL 为 0 而被丢弃。
    
包乱序是一种相对来说比较普遍的现象,除了防火墙,还有网络设备、主机本身都可能引起乱序。

------------------------------------------------------------------------
不同的操作系统其初始 TTL 值不同,一般来说 Windows 是 128,Linux 是 64
内网同一个连接中的报文,其 TTL 值一般不会变化。
IP 包的归宿;wireshark分析细节之TTL值
案例 1:Web 站点访问被 reset(通过RST报文的TTL值判断出RST报文是由防火墙发出的!)
============================================================================================
ip.addr eq 253.61.239.103 and tcp.flags.reset eq 1

从抓包的流分析可以看到,3次握手是ok的,但是发送get请求,server端就回复RST报文
    同时可以稳定触发该现象,但仅限于部分区域;其他区域访问server是正常的


哦哦哦,牛批
    本故障的现象为3次握手ok,但是GET请求,却收到了RST报文
        通过观察TTL,3次握手的ACK,SYS报文TTL为59,但是RST报文的TTL却是64!!!!!
        由此可以判断该RST报文是防火墙设备发出的!
        
根因:防火墙上对二楼有线网络有一条可疑的策略,跟其他线路不同。这条策略的出发点是:
        每个网络协议规定了协议数据格式以及标准端口号,所以协议数据跟端口号不匹配的话,就可以认为是“有害”流量。
        因为 HTTP 协议标准端口是 TCP 80,但是我们这个 Web 站点是 3001 端口的,被防火墙认为不一致,所以就拒掉了。
        防火墙application-default 就是说,端口需要跟协议匹配。要不然就会被禁止,也就是回复 RST 给客户端,终止这条连接。
案例 1:Web 站点访问被 reset(通过RST报文的TTL值判断出RST报文是由防火墙发出的!)
案例 2:访问 LDAPS 服务报 connection reset by peer
============================================================================================
本案例在client和server两端同时进行抓包
    client端抓包分析
        3次握手正常,发送TLS clinet hello后,server端回复RST
    server端抓包分析
        3次握手正常,但client端直接发送了RST
通过分析TTL,发现client和server两端收到的RST都是由防火墙设备发出的



实验
iptables -I FORWARD -p tcp -m tcp --tcp-flags SYN SYN -j REJECT --reject-with tcp-reset
案例 2:访问 LDAPS 服务报 connection reset by peer

 《网络排查案例课》23 | 路径排查:没有网络设备权限要如何做排查?

ECMP概念;ECMP 的路径选择策略
==============================================================================
ECMP 全称是 equal-cost multi-path,它可以让路由器同时使用多条链路,这样就使得通往同一个网段的带宽,变成了原先的好几倍。

ECMP 的路径选择策略
    比较常见的哈希算法是基于报文的五元组,也就是源 IP、目的 IP、源端口、目的端口、协议,这样就可以确定 TCP 流。
        有了哈希转发机制,即使有一个节点出现问题,它所影响的就只是分配到这个节点上的 TCP 流,而没有分配到这个节点的 TCP 流就不会被影响到,这就起到了风险“隔离”的效果。
    ECMP 用的哈希算法多数是基于五元组的哈希,那么imcp的路径就只有一条(源目IP不变,协议不变,源目端口无;N/A也是可以做哈希的嘛。。)
ECMP概念;ECMP 的路径选择策略
案例:TCP 连接时常失败(ECMP路径中某段链路存在问题;NC命令指定源port稳定复现了故障想象)
=================================================================================================
现象:
    我们的一个内部客户团队报告了一个 TCP 连接失常的问题。这是一个 MySQL 的服务,它有两台服务器,都在同一个 LB VIP 的后面。
    这个团队发现,从他们的客户端到这个 LB VIP 的 TCP 连接,时常有失败的情况发生,于是我们介入排查。

初步分析:
    排除了MySQL 主机问题的可能:我们从客户端直接去连接 MySQL 主机,重试了很多次,TCP 连接都可以正常建立。


问题复现:
    选了一台客户端,用 nc 命令对 LB VIP 发起了多次 TCP 连接
        差不多每四次里就有一次的连接请求会超时失败,确实是有问题。
        root@client:~# while true;do nc -zv 10.111.1.111 3306;sleep 1;done
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        nc: connect to 10.111.1.111 port 3306 (tcp) failed: Connection timed out
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        nc: connect to 10.111.1.111 port 3306 (tcp) failed: Connection timed out
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
        nc: connect to 10.111.1.111 port 3306 (tcp) failed: Connection timed out
        Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!


进一步分析:
    客户端tcpdump,显示客户端连续发送了 4 个 SYN,但都没有收到 SYN+ACK。
    LB tcpdump,显示 在 LB 这里既收到了客户端发来的全部 4 个 SYN,自己也发出了 4 个 SYN+ACK,可是并没有收到任何一个[.],也就是握手的第三个包。

    问题的全貌:客户端发出了 SYN,LB 回复了 SYN+ACK,但是却没到达客户端。显然,SYN+ACK 是在网络上丢失了。

    长 ping 测试。结果却发现,长 ping 是 100% 成功的,一点丢包都没有。
        因为交换机使用哈希,对icmp的哈希值没有变化,所以选择同一条路径(源目IP不变,协议不变,源目端口无;N/A也是可以做哈希的嘛。。)

    所以根据以上的数据,做出推测:客户端到LB是正常的,但是LB到client的某条链路存在异常,稳定丢包

落实证据:
    nc命令指定源端口进行测试
    nc -p 30000 -w 5 -vz 10.111.1.111 3306
        
    nc -p 30000 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30001 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30002 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30003 -w 5 -vz 10.111.1.111 3306   #不正常,可以稳定重现
    nc -p 30004 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30005 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30006 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30007 -w 5 -vz 10.111.1.111 3306   #不正常,可以稳定重现
    nc -p 30008 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30009 -w 5 -vz 10.111.1.111 3306   #正常
    nc -p 30010 -w 5 -vz 10.111.1.111 3306   #正常

    确实出现了预期的正常和稳定复现故障
案例:TCP 连接时常失败(ECMP路径中某段链路存在问题;NC命令指定源port稳定复现了故障想象)

 《网络排查案例课》24 | 丢包:如何确定丢包的存在及其程度?

探测类工具:ping、mtr、traceroute、nc、telnet 等。  见总结《linux探测类工具》

统计类工具netstat、ss、netstat
==========================================================================
统计类工具包括 netstat、ss,这些工具都是在一端读取自己的历史统计值,而并不发送出探测报文。比如,我们可以通过 netstat -s 命令,看到很详细的传输层、网络层、数据链路层等的质量状况,包括报文丢失、重传、重置(RST)等情况的统计。

    # netstat -s
    Ip:
        Forwarding: 1
        41406 total packets received
        0 forwarded
        0 incoming packets discarded
        41406 incoming packets delivered
        30976 requests sent out
    Icmp:
        16 ICMP messages received
        0 input ICMP message failed
        ICMP input histogram:
            echo replies: 16
        16 ICMP messages sent
        0 ICMP messages failed
        ICMP output histogram:
            echo requests: 16
    IcmpMsg:
            InType0: 16
            OutType8: 16
    Tcp:
        18 active connection openings
        0 passive connection openings
        0 failed connection attempts
        1 connection resets received
        2 connections established
        41353 segments received
        30924 segments sent out
        0 segments retransmitted        #如果它一直在增长,那一般意味着网络上存在丢包
        0 bad segments received
        0 resets sent
    Udp:
        37 packets received
        0 packets to unknown port received
        0 packet receive errors
        37 packets sent
        0 receive buffer errors
        0 send buffer errors
    ......

netstat 主要是通过 /proc 文件系统收集信息的,而 ss 主要通过 netlink 内核接口获取数据(有些信息也依然要从 /proc 中读取),这个 netlink 接口的效率要比 /proc 接口更高,所以 ss 能更快地返回数据。
ss 能获得的信息要比 netstat 更丰富。比如,ss 就可以获取到 socket option 的信息,但 netstat 就做不到。
-----------------------------------------------------------------------------------
$ ss -eit

[root@centos7 ~]# ss -eti
State       Recv-Q Send-Q                                             Local Address:Port                                                              Peer Address:Port
ESTAB       0      0                                                  172.17.22.136:ssh                                                              42.120.72.125:30251                 timer:(keepalive,119min,0) ino:99465085 sk:ffff8d623ac68f80 <->
         sack cubic wscale:8,7 rto:235 rtt:34.789/14.791 ato:40 mss:1460 rcvmss:1168 advmss:1460 cwnd:10 bytes_acked:6973 bytes_received:4388 segs_out:52 segs_in:77 send 3.4Mbps lastsnd:286 lastrcv:2 lastack:2 pacing_rate 6.7Mbps rcv_space:29200
    
    #cubic:这条 TCP 连接用的拥塞控制算法是 cubic。
    #wscale:8,7:这条 TCP 连接的两端的 Window Scale 分别是 8 和 7。
    #cwnd:10:这条 TCP 连接的拥塞窗口为 10 个 MSS。

-----------------------------------------------------------------------------------
nstat 这个工具。它跟 ss 一样,也是 iproute2 工具包里面的。nstat 的一个特点是,如果不加参数,它每次运行时输出的数值,是从上一次被执行以来这些计数器的变化值。
首次运行 nstat 时,输出的就是全部计数器的值。第二次运行时,就只是发生变化的数值了
    [root@centos7 ~]# nstat
    #kernel
    IpInReceives                    85                 0.0
    IpInDelivers                    85                 0.0
    IpOutRequests                   74                 0.0
    TcpActiveOpens                  32                 0.0
    TcpAttemptFails                 31                 0.0
    TcpInSegs                       83                 0.0
    TcpOutSegs                      82                 0.0
    TcpOutRsts                      31                 0.0
    UdpInDatagrams                  2                  0.0
    UdpOutDatagrams                 2                  0.0
    TcpExtTCPHPHits                 2                  0.0
    TcpExtTCPPureAcks               13                 0.0
    TcpExtTCPHPAcks                 3                  0.0
    TcpExtTCPOrigDataSent           16                 0.0
    IpExtInOctets                   4563               0.0
    IpExtOutOctets                  14535              0.0
    IpExtInNoECTPkts                85                 0.0

    nstat -a    #查看全部数值

    [root@centos7 ~]# nstat --json      #将结果输出为json格式
    [root@centos7 ~]# nstat --json |jq
    {
      "kernel": {
        "IpInReceives": 162,
        "IpInDelivers": 162,
        "IpOutRequests": 157,
        "TcpActiveOpens": 74,
        "TcpAttemptFails": 74,
        "TcpInSegs": 162,
        "TcpOutSegs": 158,
        "TcpOutRsts": 74,
        "TcpExtTCPHPHits": 1,
        "TcpExtTCPPureAcks": 3,
        "TcpExtTCPHPAcks": 5,
        "TcpExtTCPOrigDataSent": 10,
        "IpExtInOctets": 8296,
        "IpExtOutOctets": 11236,
        "IpExtInNoECTPkts": 162
      }
    }
统计类工具netstat、ss、netstat
内核的 SNMP 计数器;快速探测 PMTU 的方法;统计丢包率
===========================================================================================
内核根据RFC1213的标准,定义了 IP、ICMP、TCP、UDP 等协议相关的SNMP 计数器。
在每次报文发送、接收、重传等行为发生时,都会往相应的计数器中更新数值。
具体来说,这些计数器的定义在内核代码的 include/linux/snmp.h 文件中。

所以说,这些数值都不是 netstat、nstat 这些命令去“生成”的,而是内核早就准备好这些数据了,工具去完成读取就好了。

----------------------------------------------------
快速探测 PMTU 的方法
    ping www.baidu.com -s 1472  
    加上 IP 头部的 20 字节和 ICMP 头部的 8 字节

---------------------------------------------------------
统计丢包率
    长 ping
    既然 ping 也未必准,那不如直接一边跑应用一边抓包,然后对抓包文件进行分析,从而得出丢包率。
        1.使用wireshark专家信息进行数据包统计
        2.capinfos 命令分析pcap文件
            $ capinfos viaLB.pcap   #用 capinfos 命令获取总的报文数
            $ tshark -n -q -r viaLB.pcap -z "io,stat,0,tcp.analysis.retransmission" #tshark获取重传报文数量
            
内核的 SNMP 计数器;快速探测 PMTU 的方法;统计丢包率
网络设备对报文的转发和回复;icmp限速;丢包率的可接受范围
==========================================================================================
网络设备(交换机和路由器)在“转发”和 “回复”这两个任务上的工作机制是不同的:
    对于“转发”,大部分工作是卸载到数据面的硬件芯片完成的,这也是实现高速转发的底层基础。
    对于“回复”,因为需要自己生成一个 ICMP 响应报文,那就需要动用自己的 CPU 资源了,速度就会慢一些。

-------------------------------------------------------------------------------------------
不少网络设备对自己的 ICMP 响应报文设置了限速(rate limit),这也会加剧这种中间节点丢包的情况。

--------------------------------------------------------------------------------------------
丢包多少算严重?
    实际上,公网丢包率在 1% 左右是一个可以接受的范围。如果明显超过 1%,比如达到了 5% 以上,那对应用的影响就会比较明显了,此时应该通过节点修复或者链路调整,来解决丢包的问题,把丢包率控制在 1% 左右,最好是 1% 以下。
    内网网络会比公网稳定很多。一般来说,一个正常的内网也有万分之一左右的丢包率。如果明显超过了这个比率,比如达到了千分之一的话,尽管依然比公网丢包率低一个数量级,但也需要认真对待并解决。
网络设备对报文的转发和回复;icmp限速;丢包率的可接受范围(公网1%;内网万一)

 《网络排查案例课》不定期加餐(一) | 八仙过海,各显神通:透传真实源IP的各种方法

透传真实源IP的场景;透传真实源IP的应用层方法(HTTP header X-Forwarded-For)
===================================================================================================================
在互联网世界里,真实源 IP 作为一个比较关键的信息,在很多场合里都会被服务端程序使用到。比如以下这几个场景:
    1.安全控制:服务端程序根据源 IP 进行验证,比如查看其是否在白名单中。使用 IP 验证,再结合 TLS 层面和应用层面的安全机制,就形成了连续几道安全门,可以说是越发坚固了。
    2.进行日志记录:记下这个事务是从哪个源 IP 发起的,方便后期的问题排查和分析,乃至进行用户行为的大数据分析。比如根据源 IP 所在城市的用户的消费特点,制定针对性的商业策略。
    3.进行客户个性化展现:根据源 IP 的地理位置的不同,展现出不同的页面。以 eBay 为例,如果判断到访问的源 IP 来自中国,那就给你展现一个海淘页面,而且还会根据中国客户的特点,贴心地给你推荐流行爆款。

-------------------------------------------------------------------------------------------------------------------
透传真实源IP的应用层方法(HTTP header X-Forwarded-For)

header,用来传递真实源 IP,它就是 X-Forwarded-For


X-Forwarded-For 的形式跟其他 HTTP header 一样,也是 key: value 的形式。key 是 X-Forwarded-For 这个字符串,value 是一个 IP 或者用逗号分隔开的多个 IP,也就是下面这样:
    X-Forwarded-For: ip1,ip2,ip3
    多个IP:因为一个 HTTP 请求,可能会被多个 HTTP 代理等系统转发,每一级代理都可能会把上一个代理的 IP,附加到这个 X-Forwarded-For 头部的值里面。最左边的 IP 就是真实源 IP,后面跟着的多个 IP 就是依次经过的各个代理或者 LB 的 IP。


X-Forwarded-For的不足:
    1.源 IP 信息的伪造问题
        这也是它最大的问题,因为这个头部本身没有任何安全保障机制,攻击者完全可以任意构造 X-Forwarded-For 信息来欺骗服务端。

    2.重复的 X-Forwarded-For 头部
        HTTP 协议本身并不严格要求 header 是唯一的,所以有些情况下,HTTP 请求可能会携带两个或者更多的 X-Forwarded-For 头部。
        造成这个现象的原因是,某些代理或者 LB 并不是严格按照协议规定的,把 IP 附加到已有的 X-Forwarded-For 头部,而是自己另起一个 X-Forwarded-For 头部,那么这样就导致了重复的 X-Forwarded-For。

    3.不能解决 HTTP 和邮件协议以外的真实源 IP 获取的需求
        很多应用并不是基于 HTTP 协议工作的,比如数据库、FTP、syslog 等等,这些场景也需要“获取真实源 IP”这个功能。但是前面说的 X-Forwarded-For,只能为 HTTP/ 邮件协议所用,
透传真实源IP的场景;透传真实源IP的应用层方法(HTTP header X-Forwarded-For)
透传真实源IP的传输层方法:方法1:TOA 和 TCP Options;方法2:Proxy Protocol;方法3:NetScaler 的 TCP IP header
====================================================================================================================
透传真实源IP的传输层方法
方法1:TOA 和 TCP Options
    TOA 全称是 TCP Option Address,它是利用 TCP Options 的字段来承载真实源 IP 信息,这个是目前比较常见的第四层方案。
    这并非是 TCP 标准所支持的,所以需要通信双方都进行改造。也就是:
        对于发送方来说,需要有能力把真实源 IP 插入到 TCP Options 里面。
        对于接收方来说,需要有能力把 TCP Options 里面的 IP 地址读取出来。

    TOA 采用的 kind 是 254,长度为 6 个字节(用于 IPv4)。
    TOA 源码中 toa_data 的数据结构:
        opcode(op-kind)是 1 个字节,
        opsize(op-length)是 1 个字节,
        端口(客户端的)是 2 个字节,
        ip 地址是 4 个字节,也就是 TOA 传递了真实源 IP 和真实源端口的信息。

    TOA 具体的工作原理:
        TOA 模块 hook 了内核网络中的结构体 inet_stream_ops 的 inet_getname 函数,替换成了自定义函数。
        这个自定义函数会判断 TCP header 中是否有 op-kind 为 254 的部分。如果有,就取出其中的 IP 和端口值,作为返回值。
        
        这样的话,当来自用户空间的程序调用 getpeername() 这个系统调用时,拿到的 IP 就不再是 IP 报文的源 IP,而是 TCP Options 里面携带的真实源 IP 了。
        比如服务器加载 TOA 后(当然 LB 也要支持 TOA),那么在 access log 里面的 remote IP 一列,就会是真实源 IP;而不加载 TOA 模块的话,就只是 LB 的 SNAT IP 了。

方法2:Proxy Protocol
    实现原理:
        客户端在 TCP 握手完成之后,在应用层数据发送之前,插入一个包,这个包的 payload 就是真实源 IP。
            也就是说,在三次握手后,第四个包不是应用层请求,而是一个包含了真实源 IP 信息的 TCP 包,这样应用层请求会延后一个包,从第五个包开始。
        服务端也需要支持 Proxy Protocol,以此来识别三次握手后的这个额外的数据包,提取出真实源 IP。

    目前,除了 HAProxy 以外,其实也有不少软件已经支持了 Proxy Protocol,比如 Nginx,以及各大公有云的服务,比如 AWS(亚马逊云)和 GCP(谷歌云)。
    进行抓包分析的话,我们可以直接在 Wireshark 下方的报文详情里看到它的文本格式的内容:
        PROXY TCP 10.0.2.2 10.0.2.15 51866 80
            10.0.2.2 就是真实源 IP,10.0.2.15 是 VIP,51866 是真实源端口,80 是 VIP 端口。
    
    默认的 HAProxy 和 Nginx 配置都是不启用 Proxy Protocol 的,所以需要额外进行这些配置。
        如果中间 LB(这个例子里是 HAProxy)启用了 Proxy Protocol,而后端服务器(这个例子里是 Nginx)没启用,那么客户端会收到 HTTP 400 bad request。
        究其原因,是因为不启用 Proxy Protocol 的 Nginx,会认为握手后的第一个包并没有遵循 HTTP 协议规范,所以给出了 HTTP 400 的报错回复。

方法3:NetScaler 的 TCP IP header
    这是 Citrix(也就是 NetScaler 的厂商)提供的自家的方案。它的原理跟 Proxy Protocol 是类似的,也是在握手之后,立即发送一个包含真实源 IP 信息的 TCP 包,而差别仅仅在于数据格式不同。
    这种算是私有协议了,支持场景会比 Proxy Protocol 更少一些,所以需要服务端开发人员对此进行代码改造,来让应用程序能够识别这个包里面的信息。
透传真实源IP的传输层方法:方法1:TOA 和 TCP Options;方法2:Proxy Protocol;方法3:NetScaler 的 TCP IP header
透传真实源IP的网络层方法;小结
======================================================================================================
透传真实源IP的网络层方法
    比如利用 IPIP 这样的隧道技术。简单来说,就是用“三角模式”来实现直接的源 IP 信息的透传。
    它的实现原理,跟前面介绍的几个就有比较明显的区别:
        传输层和应用层:把真实源 IP 当做 header 的一部分,传输到后端。
        网络层:直接把真实源 IP 传输到后端。
    这种模式里,客户端地址(CIP)是被服务端直接可见的,看起来貌似最为直接,也不需要任何应用层和传输层的改造。
    这种方式的缺点:
        1.配置繁琐,扩展性不佳:IPIP 隧道(或者其他隧道技术)需要在 LB 和服务端都进行配置,VIP 也需要在服务端上配置。
        2.LB 无法处理回包:因为回包不再经过 LB,那么对应用回复的处理就无从实现了,比如对 HTTP Response 的改写,就没办法在 LB 环节做了。如果需要有这些逻辑,那么我们要把这部分逻辑回撤到服务器本身来处理。




小结
    应用层透传真实源 IP 的方法,是利用 X-Forwarded-For 这个头部,把真实源 IP 传递给后端服务器。这个场景对 HTTP 应用有效,但是对其他应用就不行了
    
    针对传输层主要是有三种方法:
        扩展 SYN 报文的 TCP Options,让它携带真实源 IP 信息。这个需要对中间的 LB 和后端服务器都进行小幅的配置改造。
        利用 Proxy Protocol。这是一个逐步被各种反向代理和 HTTP Server 软件接纳的方案,可以在不改动代码或者内核配置的情况下,只修改反向代理和 HTTP Server 软件的配置就能做到。
        利用特定厂商的方案,如果你用的也是 NetScaler,可以利用它的相关特性来实现 TCP 层面的真实源 IP 透传。不过这也需要你修改应用代码来读取这个信息。

    在网络层,我们可以用隧道 +DSR 模式的方法,让真实源 IP 直接跟服务端“对话”。这个方案的配置稍多,另外 LB 也可能无法处理返回报文,所以你需要评估自己的需求后再决定是否采用这一方案。
透传真实源IP的网络层方法;小结

 

posted @ 2022-08-15 11:18  雲淡風輕333  阅读(652)  评论(0编辑  收藏  举报