You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 12:13:11