如何在Ruby中查看UDP消息对应的ICMP错误报文?
Ruby 非root权限下捕获ICMP错误实现Traceroute
针对你遇到的recvmsg(Socket::MSG_ERRQUEUE)阻塞但Wireshark能抓到ICMP包的问题,可以从以下几个方向排查和修复:
1. 确认套接字错误接收选项的正确配置
确保针对IPv4/IPv6设置了对应的错误接收选项:
- IPv4配置:
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_RECVERR, 1) - IPv6配置:
sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_RECVERR, 1)
注意必须在对应协议层级设置,不能混用IPPROTO_IP和IPV6_RECVERR。
2. 给套接字添加超时,避免永久阻塞
默认recvmsg是阻塞模式,即使有ICMP包,也可能因为内核队列的问题导致无法立即读取。给套接字设置接收超时:
# 设置1秒接收超时,格式为[秒, 微秒]的数组,打包成long类型 timeout = [1, 0] sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeout.pack("l_2"))
之后调用recvmsg时,超时会抛出Errno::EAGAIN或Errno::EWOULDBLOCK,可以捕获并处理。
3. 正确解析错误队列中的控制信息
ICMP错误报文会被封装在控制信息(cmsg)中,需要解析IP_RECVERR类型的控制数据:
begin # 接收数据,指定MSG_ERRQUEUE标志 msg, flags, _, control = sock.recvmsg(1024, Socket::MSG_ERRQUEUE) control&.each do |cmsg| # 筛选IPv4的ICMP错误控制信息 next unless cmsg.cmsg_level == Socket::IPPROTO_IP && cmsg.cmsg_type == Socket::IP_RECVERR # 解析sock_extended_err结构体(Linux下) err_info = cmsg.data.unpack("i!i!i!i!a16a16") # 提取来源IP地址 src_ip = Socket.unpack_sockaddr_in(err_info[4])[1] puts "第一跳IP: #{src_ip}" end rescue Errno::EAGAIN, Errno::EWOULDBLOCK # 超时,未收到错误报文 end
IPv6的解析逻辑类似,只需将IPPROTO_IP换成IPPROTO_IPV6,对应IPV6_RECVERR类型。
4. 确保UDP发送包的目标端口有效
发送的UDP包需要指向一个大概率未开放的端口(比如traceroute常用的33434-33534区间),这样中间路由才会返回ICMP超时错误:
target = "8.8.8.8" port = 33434 sock.send("\x00" * 32, 0, Socket.sockaddr_in(port, target))
内容的提问来源于stack exchange,提问作者Marek Küthe
相关产品推荐
相关产品推荐

