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

如何不绑定端口使用WSASendTo/WSASendMsg发送指定端口UDP数据报

Windows下从不绑定的指定端口发送UDP数据报(WSASendTo实现)

要实现从不绑定的指定端口(即使该端口已被其他套接字占用)发送UDP数据报,核心思路是直接构造包含自定义源端口的IP和UDP头部,通过原始套接字发送,绕过系统自动处理的端口绑定逻辑。以下是具体实现步骤和代码示例:

关键前提

  • 程序必须以管理员权限运行(Windows下创建原始套接字需要该权限)。
  • 需要自行构造IP头部和UDP头部,包括计算正确的校验和。

实现步骤

  1. 初始化Winsock:调用WSAStartup初始化套接字环境。
  2. 创建原始套接字:创建类型为SOCK_RAW、协议为IPPROTO_UDP的套接字,用于手动构造IP和UDP头部。
  3. 启用IP_HDRINCL选项:设置套接字选项IP_HDRINCL,告知系统我们将自行构造IP头部,而非由系统自动添加。
  4. 构造IP和UDP头部:手动填写IP头部的源IP、目标IP等字段,UDP头部的源端口(指定的目标端口)、目标端口等字段,并计算IP头部校验和与UDP校验和。
  5. 发送数据包:使用WSASendTo发送构造好的完整数据包(IP头部 + UDP头部 + 数据内容)。

代码示例

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

// IP头部结构体
typedef struct ip_hdr {
    unsigned char  ip_hl:4, ip_v:4;       // 版本和头部长度
    unsigned char  ip_tos;                // 服务类型
    unsigned short ip_len;                // 总长度
    unsigned short ip_id;                 // 标识
    unsigned short ip_off;                // 分片偏移
    unsigned char  ip_ttl;                // 生存时间
    unsigned char  ip_p;                  // 协议
    unsigned short ip_sum;                // 校验和
    struct in_addr ip_src, ip_dst;        // 源IP和目标IP
} IP_HDR;

// UDP头部结构体
typedef struct udp_hdr {
    unsigned short uh_sport;              // 源端口
    unsigned short uh_dport;              // 目标端口
    unsigned short uh_ulen;               // UDP长度
    unsigned short uh_sum;                // 校验和
} UDP_HDR;

// 计算校验和的辅助函数
unsigned short checksum(unsigned short *buf, int len) {
    unsigned long sum = 0;
    while (len > 1) {
        sum += *buf++;
        len -= 2;
    }
    if (len == 1) {
        sum += *(unsigned char*)buf;
    }
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}

int main() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed\n");
        return 1;
    }

    // 创建原始套接字
    SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if (sock == INVALID_SOCKET) {
        printf("socket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // 设置IP_HDRINCL选项,自行构造IP头部
    BOOL optval = TRUE;
    if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&optval, sizeof(optval)) == SOCKET_ERROR) {
        printf("setsockopt IP_HDRINCL failed: %d\n", WSAGetLastError());
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // 构造目标地址
    sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(8080);  // 目标端口
    inet_pton(AF_INET, "192.168.1.100", &dest_addr.sin_addr);  // 目标IP

    // 构造IP头部
    IP_HDR ip_hdr;
    memset(&ip_hdr, 0, sizeof(ip_hdr));
    ip_hdr.ip_v = 4;
    ip_hdr.ip_hl = sizeof(IP_HDR) / 4;
    ip_hdr.ip_tos = 0;
    ip_hdr.ip_len = htons(sizeof(IP_HDR) + sizeof(UDP_HDR) + 10);  // 总长度:IP头+UDP头+数据长度(这里数据长度10)
    ip_hdr.ip_id = htons(12345);
    ip_hdr.ip_off = 0;
    ip_hdr.ip_ttl = 64;
    ip_hdr.ip_p = IPPROTO_UDP;
    inet_pton(AF_INET, "192.168.1.50", &ip_hdr.ip_src);  // 源IP
    ip_hdr.ip_dst = dest_addr.sin_addr;
    ip_hdr.ip_sum = checksum((unsigned short*)&ip_hdr, sizeof(IP_HDR));

    // 构造UDP头部
    UDP_HDR udp_hdr;
    memset(&udp_hdr, 0, sizeof(udp_hdr));
    udp_hdr.uh_sport = htons(9999);  // 指定的源端口(即使已被占用)
    udp_hdr.uh_dport = htons(8080);
    udp_hdr.uh_ulen = htons(sizeof(UDP_HDR) + 10);  // UDP长度:UDP头+数据长度

    // 构造UDP伪头部(用于计算UDP校验和)
    struct {
        struct in_addr src_ip;
        struct in_addr dst_ip;
        unsigned char zero;
        unsigned char proto;
        unsigned short udp_len;
    } udp_pseudo_hdr;
    udp_pseudo_hdr.src_ip = ip_hdr.ip_src;
    udp_pseudo_hdr.dst_ip = ip_hdr.ip_dst;
    udp_pseudo_hdr.zero = 0;
    udp_pseudo_hdr.proto = IPPROTO_UDP;
    udp_pseudo_hdr.udp_len = udp_hdr.uh_ulen;

    // 拼接伪头部、UDP头部和数据,计算校验和
    char udp_checksum_buf[sizeof(udp_pseudo_hdr) + sizeof(UDP_HDR) + 10];
    memcpy(udp_checksum_buf, &udp_pseudo_hdr, sizeof(udp_pseudo_hdr));
    memcpy(udp_checksum_buf + sizeof(udp_pseudo_hdr), &udp_hdr, sizeof(UDP_HDR));
    const char* data = "testdata12";  // 10字节数据
    memcpy(udp_checksum_buf + sizeof(udp_pseudo_hdr) + sizeof(UDP_HDR), data, 10);
    udp_hdr.uh_sum = checksum((unsigned short*)udp_checksum_buf, sizeof(udp_checksum_buf));

    // 拼接完整数据包:IP头 + UDP头 + 数据
    char packet[sizeof(IP_HDR) + sizeof(UDP_HDR) + 10];
    memcpy(packet, &ip_hdr, sizeof(IP_HDR));
    memcpy(packet + sizeof(IP_HDR), &udp_hdr, sizeof(UDP_HDR));
    memcpy(packet + sizeof(IP_HDR) + sizeof(UDP_HDR), data, 10);

    // 发送数据包
    int send_len = WSASendTo(sock, (LPWSABUF)&packet, 1, NULL, 0, (SOCKADDR*)&dest_addr, sizeof(dest_addr), NULL, NULL);
    if (send_len == SOCKET_ERROR) {
        printf("WSASendTo failed: %d\n", WSAGetLastError());
    } else {
        printf("Packet sent successfully\n");
    }

    closesocket(sock);
    WSACleanup();
    return 0;
}

注意事项

  • 校验和计算:IP头部和UDP头部的校验和必须准确,否则数据包会被网络设备或目标主机丢弃。UDP校验和计算需要借助伪头部,包含源IP、目标IP、协议类型和UDP长度。
  • 权限问题:Windows下创建SOCK_RAW套接字必须以管理员身份运行程序,否则会创建失败。
  • 网络兼容性:部分防火墙或网络设备可能拦截原始套接字发送的数据包,需要确保网络环境允许此类流量。
  • 端口冲突风险:这种方式绕过了系统的端口绑定机制,可能导致端口复用引发的网络异常,仅在特殊场景下使用。

内容的提问来源于stack exchange,提问作者Vega4

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 19:20:23