shell脚本探测UDP端口状态

UDP 端口探测及shell重定向

需求背景

需要检测服务的某个UDP的端口是否正常。

分析

UDP是一种无状态,无连接的协议,这一知识点牢记我心,所以第一反应就是这探测没办法做了。只能从其他层面想办法,比如说服务增加一个状态检查的rest接口。通过检查rest接口的状态来判断对应的UDP端口的状态。

直到我终于了解到,原来,不仅仅是TCP,当UDP端口未开启监听时,操作系统也会发送ICMP端口不可达报文。ICMP并不是UDP的一部分, 所以这和UDP是无连接,无状态的协议这一说法并不矛盾。之前真是一知半解啊。

方案

总共两步:

  1. ping对应IP检测对应IP是否正常。
    约束:对应节点不能关闭ping响应。防火墙不能过滤ICMP 请求和响应报文
  2. 发送一个内容为空的UDP报文:
    1)如果收到了ICMP端口不可达的回复。则认为端口关闭。
    2)如果未收到ICMP端口不可达报文。则有两种可能:
    I)端口正常。不回复。
    约束:UDP端口对应的程序要能够处理报文内容为空的报文。
    II) 防火墙过滤掉了ICMP端口不可达报文
    约束:
    操作系统不能过滤ICMP端口不可达报文
    本身处于同一个局域网,报文传输过程中应该是不会经过防火墙的。

备注:
由于和待检测的节点处于同一个局域网,所以以上约束都是可以实施的。
由于UDP协议本身的不可靠,所以,可以考虑探测多次,只要有一次不成功则认为不成功。

实现

python

有人提供了一个基于python的解决方案:

  1. 启动抓包程序。
  2. 发送内容为空UDP报文。
  3. 判断一定时间内是否抓到对应的ICMP端口不可达报文。
    此方案通过抓包的方式实现,显然不够优雅。直觉告诉我,显然应该有更优雅的解决方案。
    更重要的是,交付的版本层面上来说,由于涉及到python2和python3的切换,以及如果项目中使用python脚本之后,就需要增加对应的安全扫描之类的工作。总体来说,如果使用python脚本,会增大版本层面的工作量。

java

前段时间,在看这篇文章的时候,看到了这样一段话:

这个ICMP错误消息虽然不属于telnet引发的TCP流,但它跟该TCP流却是RELATED的,而RELATED的流会继承原始流的conntrack结构体表项,这就是问题的根本,如果缺失了这个细节,就会带来错误的判断。

结合现在遇到的问题,我想到,我们在使用tcp的socket连接时,接收到的connection refused信息就是由于操作系统接收到了ICMP端口不可达报文。所以说,这个TCP流收发的ICMP报文,被操作系统送到了同一个socket。

而我们在使用UDP的时候,也是使用的socket连接的,那么UDP是不是也是同样的情况呢?
查找了一番,果然如此。只不过UDP稍微特殊一点:

  1. 需要明确调用bind()函数
  2. 并且发送报文的时候不会直接抛出错误,而是在接收报文的时候返回PortUnreachableException

这个方案显然是比python脚本抓包要优雅一些了。但是:

  1. 还需先检测IP是否能ping通,java代码显然不太方便。
  2. 等待可能需要设置一定的超时时间,所以可能需要新开线程去处理。
  3. java代码产生的结果最终需要传递给keepalived, 比较麻烦。

shell脚本

既然java的socket能够搞定这个问题,那么shell脚本的socket能不能搞定这个问题呢?如果能够搞定的话,shell脚本应该就是一个比较优雅的解决方案了。

shell提供了一种建立TCP/UDP连接的方法:
/dev/udp/host/port
/dev/tcp/host/port

所以,直接重定向当前shell的一个文件描述符到对应的ip/端口:
exec 8<>/dev/udp/10.0.2.15/12345
就相当于建立一个UDP socket。
发送报文:
echo "" >&8
抓包结果:

00:48:18.291124 IP 10.0.2.15.40371 > 10.0.2.15.12345: UDP, length 1
00:48:18.291145 IP 10.0.2.15 > 10.0.2.15: ICMP 10.0.2.15 udp port 12345 unreachable, length 37

由于UDP的无连接性,命令返回的结果依然为成功。

从对应的文件描述符中读取状态:

root@debian2:~# cat <&8
cat: -: Connection refused

当然,直接往流中再次写入数据,也会得到同样的错误:

root@debian2:~# exec 8<>/dev/udp/10.0.2.15/12345
root@debian2:~# echo "" >&8
root@debian2:~# echo "" >&8
-bash: echo: write error: Connection refused

使用完毕之后,关闭对应的流:
exec 8>&-

参考:
bash shell 连接socket
Bash One-Liners Explained, Part III: All about redirections

猜你喜欢

转载自blog.csdn.net/qq_31567335/article/details/86568254