写点什么

C/S UDP 通信实践踩坑记录与对于 ICMP 的进一步认识

作者:这我可不懂
  • 2023-06-28
    福建
  • 本文字数:4566 字

    阅读完需:约 15 分钟

背景

最近有个业务场景需要服务端(简称 S)与客户端(简称 C)设计一套基于 UDP 的通信协议--要求尽可能快的前提下可容忍一定丢包率,得以比较深入地学习和了解 UDP 通信和实践,在开发调试期间先后碰到了 C 端 UDP 发包无响应、响应 Host Unreachable、响应 Port Unreachable、再次 C 端 UDP 发包无响应这四种错误情况,不同于以往连接调试成功后万事大吉不再细究,这次有了好奇心想刨根问底的弄清楚造成不同错误的原因与错误通知的原理,并最终进一步了解了 ICMP 这个熟悉又陌生的协议。

错误问题与原因分析

为了便于更清晰、方便的阐明问题,以下对问题的顺序和出现场景进行了艺术加工--和实际发生的情况并不一致,毕竟实际问题并不会讲道理的一个一个顺序给你出现,而是经常多个问题混在在一起形成所谓的 bug 渐欲迷人眼==!

C 端 UDP 发包无响应

sudo hping -2 -k -s 3000 -p 9999 test.demoabc.com -d 2 # hping参数含义:-2表示UDP模式, -s表示源端口固定3000, -p表示目的端口9999,  test.demoabc.com为目的主机, -d 2表示数据包payload为2字节HPING test.demoabc.com (en0 119.x.x.100): udp mode set, 28 headers + 2 data bytes^C--- test.demoabc.com hping statistic ---11 packets tramitted, 0 packets received, 100% packet lossround-trip min/avg/max = 0.0/0.0/0.0 ms
复制代码

如上,使用 hping 向 test.demoabc.com:9999 发送了 11 个 UDP 包,但是没有得到任何回应,S 上的监听进程也没有接收到这 11 个 UDP 包中的任意一个,如果说 UDP 本身不可靠会导致可能丢包的话,在网络链路质量正常的情况下这 11 个包理论上是不可能 100%丢包的。略一思考很快想到应该是由于防火墙没有开放 UDP 端口,于是防火墙既不会将 UDP 包转给后面正在监听 9999 端口的 S 进程,也不会给 C 端回包,而是直接丢弃处理。


解决方案:防火墙开放 UDP 对应端口即可。

Host Unreachable

防火墙放开端口后,自测联调 C、S 的发包、回包已经调通,于是交付客户端,结果客户端反馈 UDP 发包有问题,并且 ping 目标 host 会报 Host Unreachable,这就奇怪了,自测已经调通了,监听进程已经在运行且能够收到使用 hping 命令发包的 UDP 包了,客户端怎么就有问题呢?


仔细一看:嗯,C 端 host 写错了--之前还没有配置测试域名的时候,直接给了 C 端一个公网 ip 想快速测试,结果由于种种原因最终实际使用的是另外一台服务器,旧 IP 对应的服务器回收了,所以客户端 ping 会报 Host Unreachable。ping 命令大概是这么个效果:

 ping 119.x.x.90PING 119.x.x.90 (119.x.x.90) 56(84) bytes of data.From 192.168.0.105 icmp_seq=1 Destination Host UnreachableFrom 192.168.0.105 icmp_seq=2 Destination Host UnreachableFrom 192.168.0.105 icmp_seq=3 Destination Host UnreachableFrom 192.168.0.105 icmp_seq=4 Destination Host UnreachableFrom 192.168.0.105 icmp_seq=5 Destination Host Unreachable^C--- 119.x.x.90 ping statistics ---8 packets transmitted, 0 received, +5 errors, 100% packet loss, time 7167ms
复制代码

解决方案: 客户端更改为正确 host 请求即可。

Port Unreachable

解决了上面 ping 结果 Host Unreachable 的问题后,客户端表示 ping 是 OK 的,但是 UDP 通信还是会报 Port Unreachable 错误,真是怪事天天有,今天特别多,继续排查。


嗯,经过排查,客户端的 UDP 端口写错了,简单来说给客户端的连接地址是 test.demoabc.com:9999, 但是客户端实际使用的时候用的域名是 test.demoabc.com,但是端口却使用了默认的 HTTP 80 端口,TCP 的 80 端口确实起着 nginx 在监听着,但是 UDP 的 80 端口可是没有任何进程监听的,于是就会导致 Port Unreachable,大概类似于以下 hping 的请求

sudo hping -2 -k -s 3000  -p 80 test.demoabc.com -d 2HPING test.demoabc.com (en0 119.x.x.100): udp mode set, 28 headers + 2 data bytesICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.comICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.comICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.comICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.comICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.comICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com^C--- test.demoabc.com hping statistic ---6 packets tramitted, 6 packets received, 0% packet loss
复制代码

解决方案:客户端更改为正确 host+port 请求即可。

再次 C 端 UDP 发包无响应

解决了上面三个问题后,客户端、服务端 UDP 通信终于是调通了,可以继续快乐的开发后续逻辑了,结果某天客户端突然反馈客户端发包正常,但是收不到任何回包,重新自己用 hping 进行测试确实也是类似的结果,hping 结果如下:

sudo hping -2 -k -s 3000  -p 9999  test.demoabc.com -d 2HPING test.demoabc.com (en0 119.x.x.100): udp mode set, 28 headers + 2 data bytes^C--- test.demoabc.com hping statistic ---19 packets tramitted, 0 packets received, 100% packet lossround-trip min/avg/max = 0.0/0.0/0.0 ms
复制代码

看上去和刚开始防火墙导致的问题确实毫无区别,但是又 check 了防火墙规则确认并不存在问题,使用 tcpdump 抓包也确认收到了来自 C 端的 UDP 包,最后还在 server 代码中添加了 UDP 收包后直接打印原始内容的 log,也能够确认数据已经被交付到了监听进程,可是 C 端为什么收不到任何响应呢?


仔细思考 UDP 的原理,UDP 本身是无连接、不可靠的,它不像 TCP 那样协议保证每个发包都会保证送达,协议会保证有对应的 ack 回包--即便业务代码不给 C 端回包,协议本身也会保证有 ack 的回包,所以理论上如果 S 端收到了 C 端的 UDP 包,本身却不做任何回应的话,对于发包的 C 端来说其实并不能知道数据包是在发送途中默默丢失了、被目标防火墙拒收丢弃了还是最终被 S 收到了但未做任何回应。


前面已经确认了防火墙配置正确,tcpdump 抓包和业务 log 也验证了监听进程确实已经收到了 C 端数据包,那么问题就只可能出在 S 端给 C 端的回包逻辑上了,代码中为了测试对于 C 端的发包是有一次固定回包的,S 端固定 1s 间隔还会给 C 端发送心跳包,这在之前几天其实无论自测还是 C 端使用上都是正常的,结果现在突然就不 work 了。依据丰富的 bug 经验--推断应该是最近的业务代码改动出 bug 了--很合理的解释,仔细一查服务端近期并没有代码改动,但是代码中会有一些异常条件判断提前 return 的逻辑,在相应地方添加详细错误 log 后重新测试,终于真相大白--客户端使用的序列化协议错了,C 端最近一次改动序列化生成的二进制数据 S 端会解析出错,于是提前 return,不会走后面的回包逻辑,而固定心跳包机制未生效的原因也破解了--只有成功走到 S 回包流程的 C 端 ip/port 才会被加入 S 的活跃 C 端列表,才会发送心跳包,这里由于 C 端的数据全部错误提前返回了,未能加入活跃 C 端列表,也就不会发送心跳包了。而自己使用 hping 测试之前正常现在却是失败的原因也是由于刚开始并没有加这块解析错误提前 return 的逻辑,只是简单验证发包回包连通性,现在已经有这块解析校验 return 的逻辑的情况下使用 hping 当然也一样收不到回包了。


解决方案:C 端 fix 错误的序列化代码,S 端增加更全面、详细的错误 log 方便后续更快排查问题。

对于 ICMP 的进一步认识

到这一步 UDP 通信联调碰到的 4 个问题已经阐述完毕,似乎已经可以结束整篇 blog 了,但是好像标题中的 ICMP 到目前为止还没有丝毫提及的样子?


对于有一定网络基础、经验的小伙伴,其实应该都能意识到之前虽然一直没有提到 ICMP 的名字,但 ICMP 本身的使用其实已经被多次提及,从 ping 命令执行的响应 Host Unreachable,hping 命令和 C 端发 UDP 包得到的响应 Port Unreachable 这些其实都是依赖的 ICMP 协议。


但是对于网络基础较弱的小伙伴,这一点就并不那么明显了,包括自己在内其实之前从来没有把 Unreachable 这些报错响应和 ICMP 直接联系起来过--换句话说,课堂上学习过 ICMP,知道其全称是 Internet Control Message Protocol,也大概知道 ping 命令和 ICMP 有关系,但是更进一步:ICMP 到底是干啥的?什么场景下会有 ICMP 响应?为什么有时候响应是 Host Unreachable,有时候是 Port Unreacable,有时候又直接是 timeout 而没有任何响应呢?ICMP 是哪一层的协议?UDP、TCP 和 ICMP 又有什么关系?


更详细的 ICMP 介绍网上已经有很多的资料了,这里仅简单讲述一下自己对以上问题的理解--有错漏欢迎大家指正,想进一步了解的小伙伴推荐阅读小林 coding 的20 张图解: ping 的工作原理,图文并茂讲的非常之赞。

ICMP 到底是干啥的

顾名思义,ICMP 是用于控制报文传输的协议,主要分为两类:查询报文和差错报文。

什么场景下会有 ICMP 响应

可以先思考一个问题,当源主机发送一个 IP 包、UDP 包或者 TCP 包给目标主机时,如果在送达过程中出现了某些而被丢弃--比如目标主机关机了,源主机怎么能知道某个包被丢弃了/主机不可达呢?如果没有一种机制负责通知源主机的话,源主机可能只能傻等到 timeout 了,这就是 ICMP 的通知机制的一种使用场景,节点会在丢弃数据包的同时向源主机发送 ICMP 报文通知,明确告知其目标主机 unreachable。


非常常用的 ping 命令就是基于查询报文实现,查询报文可用于测试到目的主机链路是否可达,目的主机在收到查询报文时默认会回复一个响应报文给源主机--除非目的主机禁止了策略回送,在简单网络延迟测试、链路连通故障检测方面使用 ping 命令大家应该都已经十分熟悉了。


而差错报文类型就是在 IP 数据包送达目的主机过程中出错时,出错节点给源主机回送的具体差错信息,比如数据包到达了目的主机前一跳路由器,目的主机已关机,路由器无法送达数据包就会给告知源主机 Host Unreachable, 又比如源主机发送 UDP 包到目标主机的 9999 端口但是 9999 端口的监听进程挂了没起来,目标主机发现收到了 UDP 包但是却没有可交付的进程,就会告知源主机 Port Unreachable。

为什么有时候响应是 Host Unreachable,有时候是 Port Unreacable,有时候又直接是 timeout 而没有任何响应呢?

如果最终发现数据包无法送达只能丢弃的节点(可能是中间路由器、最终主机等)没有禁止对应 ICMP 消息响应,那就会给源主机发送 Unreachable 响应,简单来说如果直接是目的 host 都找不到无法交付会响应 Host Unreachable, 如果已经交付到了目标 host,但是目标 host 发现这是个传输层 TCP、UDP 包却无法找到对应 port 的接收进程, 响应就是 Port Unreachable。而如果节点禁止对应 ICMP 消息响应,那么节点只是简单丢弃无法送达的数据包,不会有响应操作,此时源主机等待超过一定时间也就只能判定 timeout 了。

ICMP 是哪一层的协议

ICMP 本身是网络层协议。

UDP、TCP 和 ICMP 又有什么关系

UDP、TCP 都是传输层协议,其和网络层 ICMP 并没有直接关系,但是当 UDP、TCP 数据包在送达目的主机的过程中出现问题--如 Host UnReachable、Port Unreachable、拒绝分片导致被丢弃时,节点会通过向源主机发送 ICMP 报文帮助源主机了解发送失败原因以进一步处理,其他还有提示数据发送方更优路径的 Redirect Message 和缓解拥堵的 Source Quench Message 等。

总结

亲自实践了基于 UDP 的网络编程可谓把之前以死记硬背为主的 UDP 知识进行了一番试炼与提纯,真正的加深了理解,同时对于看似熟悉实则陌生的 ICMP 协议有了直观得多、深入的多的学习。真正是:纸上得来终觉浅,绝知此事要躬行。与大家共勉。

转载请注明出处,原文地址: https://www.cnblogs.com/AcAc-t/p/udp_message_and_icmp.html

发布于: 刚刚阅读数: 4
用户头像

低代码技术追随者,为全民开发而努力 2023-02-15 加入

大家好,我是老王,专注于分享低代码图文知识,感兴趣的伙伴就请关注我吧!

评论

发布
暂无评论
C/S UDP通信实践踩坑记录与对于ICMP的进一步认识_这我可不懂_InfoQ写作社区