Windows 11下recv()未在ICMP端口不可达时返回SOCKET_ERROR咨询
问题解答:Windows 11下UDP套接字未在收到ICMP端口不可达时返回SOCKET_ERROR
平台
Windows 11
需求
基于RFC 1122-3.2.2.1「目标不可达」规范编写UDP ping工具,逻辑与Linux下traceroute的UDP模式一致,流程如下:
- 向目标主机发送UDP数据包
- 目标主机接收UDP数据包(但目标端口未提供服务)
- 目标主机返回ICMP端口不可达数据包(类型3,代码3)
- 接收来自目标主机的ICMP端口不可达数据包
问题
根据Winsock文档中recv()的说明,收到ICMP端口不可达包时,recv()应返回SOCKET_ERROR,但实际运行中recv()一直阻塞,未触发该返回值。
验证
通过WireShark确认ICMP端口不可达数据包已到达本地主机。
伪代码
int main(void) { SOCKET sk; struct sockaddr_in dst_sockaddr; char buf[1280]; int ret; /* 指定目标地址 */ dst_sockaddr.sin_family = AF_INET; dst_sockaddr.sin_port = MY_DST_PORT; // 非服务端口 dst_sockaddr.sin_addr.S_un.S_addr = MY_DST_IP; /* 创建套接字 */ sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); connect(sk, (struct sockaddr *)&dst_sockaddr, sizeof(struct sockaddr_in)); // 用于调用recv() /* 发送数据 */ send(sk, "hello", 5, 0); /* 接收数据 */ ret = recv(sk, buf, sizeof(buf), 0); // 此处阻塞!! printf("recv() returns %d\n", ret); closesocket(sk); return 0; }
相关问题参考
- x/net/icmp: Windows下ICMP监听工作异常,该问题针对原始套接字,与UDP套接字无关。
- Windows UDP套接字:recvfrom()返回错误10054,其中提到的
SIO_UDP_CONNRESET选项曾标注仅适用于Windows XP,但实际在后续版本中仍可配置。
疑问
如何让recv()在收到ICMP端口不可达数据包时返回SOCKET_ERROR?是否遗漏了套接字配置细节或需要启用特定IO控制?
解决方案
Windows 11的Winsock默认不会将ICMP端口不可达错误映射到UDP套接字的recv()调用中,需通过WSAIoctl()手动配置SIO_UDP_CONNRESET选项,强制将这类错误暴露给应用层。
具体修改步骤
在创建UDP套接字后、调用connect()前,添加以下代码配置套接字选项:
BOOL bDisableReset = FALSE; DWORD dwBytesReturned = 0; // 配置SIO_UDP_CONNRESET选项,禁用默认的忽略ICMP错误行为 WSAIoctl(sk, SIO_UDP_CONNRESET, &bDisableReset, sizeof(bDisableReset), NULL, 0, &dwBytesReturned, NULL, NULL);
原理说明
默认情况下,Windows的UDP套接字会静默忽略ICMP端口不可达错误,导致recv()持续阻塞。通过将SIO_UDP_CONNRESET设置为FALSE,Winsock会将这类ICMP错误转化为套接字级错误,此时recv()会返回SOCKET_ERROR,调用WSAGetLastError()可获取错误码WSAECONNRESET(10054),符合预期行为。
修改后的完整代码示例
#include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") #define MY_DST_PORT htons(33434) // 示例非服务端口 #define MY_DST_IP inet_addr("8.8.8.8") // 示例目标IP int main(void) { WSADATA wsaData; SOCKET sk; struct sockaddr_in dst_sockaddr; char buf[1280]; int ret; // 初始化Winsock WSAStartup(MAKEWORD(2, 2), &wsaData); /* 指定目标地址 */ dst_sockaddr.sin_family = AF_INET; dst_sockaddr.sin_port = MY_DST_PORT; dst_sockaddr.sin_addr.S_un.S_addr = MY_DST_IP; /* 创建套接字 */ sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // 配置SIO_UDP_CONNRESET选项 BOOL bDisableReset = FALSE; DWORD dwBytesReturned = 0; WSAIoctl(sk, SIO_UDP_CONNRESET, &bDisableReset, sizeof(bDisableReset), NULL, 0, &dwBytesReturned, NULL, NULL); connect(sk, (struct sockaddr *)&dst_sockaddr, sizeof(struct sockaddr_in)); /* 发送数据 */ send(sk, "hello", 5, 0); /* 接收数据 */ ret = recv(sk, buf, sizeof(buf), 0); if (ret == SOCKET_ERROR) { printf("recv() returns SOCKET_ERROR, error code: %d\n", WSAGetLastError()); } else { printf("recv() returns %d\n", ret); } closesocket(sk); WSACleanup(); return 0; }
内容的提问来源于stack exchange,提问作者Defn_Lee
相关产品推荐
相关产品推荐

