《网络排查案例课》
《网络排查案例课》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
案例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 值一般就不准。
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的好坏也会影响连接数。
《网络排查案例课》04 | 挥手:Nginx日志报connection reset by peer是怎么回事?
突破应用层日志和网络报文的鸿沟:时间吻合;RST 行为吻合;URL 路径吻合。 -------------------------------------------------------------- 在应用层和网络层之间搭建桥梁 做网络排查的第一个要点:把应用层的信息,“翻译”成传输层和网络层的信息。 应用现象跟网络现象之间的鸿沟:你可能看得懂应用层的日志,但是不知道网络上具体发生了什么。 工具提示跟协议理解之间的鸿沟:你看得懂 Wireshark、tcpdump 这类工具的输出信息的含义,但就是无法真正地把它们跟你对协议的理解对应起来。
案例 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 的问题。
案例 2:一个 FIN 就完成了 TCP 挥手?"ack搭车" ========================================================================================== TCP 的挥手是任意一端都可以主动发起的。也就是说,挥手的发起权并不固定给客户端或者服务端。 仅仅只是展示了一个案例,看起来只有一个FIN 实际上另一个FIN是在POST请求中一起发出的。。。 同时因为ack搭车的关系,这个挥手只有3个报文 实际上 TCP 挥手可能不是表面上的四次报文,因为并包也就是 Piggybacking 的存在,它可能看起来是三次。
《网络排查案例课》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。
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
《网络排查案例课》06 | 定位防火墙(二):网络层的精确打击
IP 包的归宿;wireshark分析细节之TTL值 ============================================================================================ IP 包的归宿,无非以下几种: 网络包最终达到目的地; 进入路由黑洞并被丢弃; 因为网络设备问题被中途丢弃; 持续被路由转发并 TTL 减 1,直到 TTL 为 0 而被丢弃。 包乱序是一种相对来说比较普遍的现象,除了防火墙,还有网络设备、主机本身都可能引起乱序。 ------------------------------------------------------------------------ 不同的操作系统其初始 TTL 值不同,一般来说 Windows 是 128,Linux 是 64 内网同一个连接中的报文,其 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 给客户端,终止这条连接。
案例 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
《网络排查案例课》23 | 路径排查:没有网络设备权限要如何做排查?
ECMP概念;ECMP 的路径选择策略 ============================================================================== ECMP 全称是 equal-cost multi-path,它可以让路由器同时使用多条链路,这样就使得通往同一个网段的带宽,变成了原先的好几倍。 ECMP 的路径选择策略 比较常见的哈希算法是基于报文的五元组,也就是源 IP、目的 IP、源端口、目的端口、协议,这样就可以确定 TCP 流。 有了哈希转发机制,即使有一个节点出现问题,它所影响的就只是分配到这个节点上的 TCP 流,而没有分配到这个节点的 TCP 流就不会被影响到,这就起到了风险“隔离”的效果。 ECMP 用的哈希算法多数是基于五元组的哈希,那么imcp的路径就只有一条(源目IP不变,协议不变,源目端口无;N/A也是可以做哈希的嘛。。)
案例: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 #正常 确实出现了预期的正常和稳定复现故障
《网络排查案例课》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 } }
内核的 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获取重传报文数量
网络设备对报文的转发和回复;icmp限速;丢包率的可接受范围 ========================================================================================== 网络设备(交换机和路由器)在“转发”和 “回复”这两个任务上的工作机制是不同的: 对于“转发”,大部分工作是卸载到数据面的硬件芯片完成的,这也是实现高速转发的底层基础。 对于“回复”,因为需要自己生成一个 ICMP 响应报文,那就需要动用自己的 CPU 资源了,速度就会慢一些。 ------------------------------------------------------------------------------------------- 不少网络设备对自己的 ICMP 响应报文设置了限速(rate limit),这也会加剧这种中间节点丢包的情况。 -------------------------------------------------------------------------------------------- 丢包多少算严重? 实际上,公网丢包率在 1% 左右是一个可以接受的范围。如果明显超过 1%,比如达到了 5% 以上,那对应用的影响就会比较明显了,此时应该通过节点修复或者链路调整,来解决丢包的问题,把丢包率控制在 1% 左右,最好是 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的传输层方法:方法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的网络层方法;小结 ====================================================================================================== 透传真实源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 也可能无法处理返回报文,所以你需要评估自己的需求后再决定是否采用这一方案。