Linux系统中如何让监听UDP的recvmsg调用返回小于0的失败值
如何让监听UDP的
recvmsg返回小于0的失败值 约束条件
- 无法在目标系统上运行代码,仅能向该系统发送UDP/IP数据包。
已尝试的方案及结果
此前参考@Ben Voigt提出的方案,尝试发送ICMP Port Unreachable报文触发ECONNRESET/ECONNREFUSED错误,分两步测试:
- 首先发送普通UDP报文触发目标系统调用
recvmsg,测试代码如下:
from scapy.all import * send(IP(dst=dst_ip)/UDP(sport=src_port,dport=dst_port))
运行后recvmsg被正常调用,未返回-1错误值。
2. 随后直接向目标端口发送构造的ICMP Port Unreachable报文,期望触发recvmsg调用失败,代码如下:
from scapy.all import * send(IP(dst=dst_ip)/ICMP(type=3,code=3)/IP(dst=dst_ip)/UDP(sport=src_port,dport=dst_port))
测试后该操作未触发recvmsg被调用。
失败原因分析
测试未生效有两个核心原因:
- 构造的ICMP报文不符合内核匹配规则
ICMP端口不可达报文需要严格匹配协议规范才会被内核关联到对应UDP socket,当前构造的报文存在明显错误:- 嵌套IP头地址方向错误:ICMP错误会路由给原始报文的发送方,因此嵌套的IP头源地址必须是目标主机IP,目的地址为原始报文的接收地址,当前代码中嵌套IP头目的地址写为目标主机自身IP,完全不符合格式要求。
- 嵌套UDP头的校验和、长度字段未按规范计算,内核校验失败后会直接丢弃报文,不会向上层递交。
- 目标UDP socket默认不接收异步ICMP错误
仅执行bind()后调用recvmsg()的UDP监听socket,默认不会处理任何异步ICMP错误。只有满足以下两个条件之一,匹配的ICMP错误才会触发recvmsg返回-1:- 该socket主动调用过
connect()绑定了固定对端地址 - 该socket开启了
IP_RECVERR套接字选项
如果目标程序未做上述配置,无论发送何种ICMP报文,都不会触发recvmsg返回错误。
- 该socket主动调用过
可行方案说明
如果确认目标UDP socket已经调用过connect()绑定对端,可按以下规则构造ICMP报文触发错误:
- 外层IP头:源地址填写socket绑定的对端IP,目的地址填写目标主机IP
- ICMP头:type=3(目的不可达)、code=3(端口不可达),正确计算校验和
- 嵌套IP头:完全匹配目标socket发往对端的报文特征:源IP为目标主机IP、目的IP为对端IP、协议号为17(UDP)
- 嵌套UDP头:源端口为目标监听端口、目的端口为对端端口,长度、校验和与原始发出的UDP报文前8字节一致
如果目标socket既未调用connect()也未开启IP_RECVERR,仅靠外部发包无法让recvmsg返回-1:所有畸形UDP包(校验和错误、长度非法、重叠分片、带非法IP选项)都会被内核协议栈直接丢弃,不会触发socket层错误;而socket本身的运行错误(接收超时、缓冲区异常、socket被关闭等)都需要本地权限操作才能触发,外部发包无法干预。
内容的提问来源于stack exchange,提问作者Kokomelom
相关产品推荐
相关产品推荐

