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

Windows LSP Socks5代理:WSPSendTo获调用方端口及UDP转发问题

解答:Windows LSP中WSPSendTo获取调用方端口及UDP数据转发问题

作为常年折腾Windows LSP和代理开发的老鸟,我来帮你拆解这两个问题:


一、在WSPSendTo中获取调用方(本地应用)的UDP端口

你要的调用方端口,其实就是当前socket绑定的本地UDP端口。在WSPSendTo的钩子函数里,你可以通过WSPGetSockName来获取这个信息,具体步骤如下:

  1. 拿到WSPSendTo的第一个参数SOCKET s(就是应用使用的UDP套接字句柄);
  2. 准备一个sockaddr_in结构和长度变量;
  3. 调用下层的NextProcTable.lpWSPGetSockName来获取本地地址和端口。

代码示例大概是这样:

int WSPAPI MyWSPSendTo(
    SOCKET s,
    LPWSABUF lpBuffers,
    DWORD dwBufferCount,
    LPDWORD lpNumberOfBytesSent,
    DWORD dwFlags,
    const struct sockaddr *lpTo,
    int iToLen,
    LPWSAOVERLAPPED lpOverlapped,
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
    LPWSATHREADID lpThreadId,
    LPINT lpErrno
) {
    // 获取调用方(本地应用)的UDP端口
    sockaddr_in local_addr = {0};
    int addr_len = sizeof(local_addr);
    if (NextProcTable.lpWSPGetSockName(s, (sockaddr*)&local_addr, &addr_len, lpErrno) == SOCKET_ERROR) {
        // 处理错误
        return SOCKET_ERROR;
    }
    u_short local_port = ntohs(local_addr.sin_port); // 这就是调用方的本地端口

    // 你的其他逻辑...

    return NextProcTable.lpWSPSendTo(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo, iToLen, lpOverlapped, lpCompletionRoutine, lpThreadId, lpErrno);
}

这里要注意:如果应用的socket还没绑定(UDP可以不用绑定直接发送,系统会自动分配临时端口),WSPGetSockName也能返回系统自动分配的端口,完全没问题。


二、UDP数据向调用方转发的实现方案

你当前的流程卡在最后一步,核心原因是UDP是无连接协议,你需要维护会话映射关系,才能知道代理返回的数据该交给哪个本地应用socket。我把完整的修正流程和关键细节给你梳理清楚:

完整流程修正

  1. 拦截应用的WSPSendTo调用:

    • 用上面的方法获取本地socket的端口和地址;
    • 通过已建立的TCP连接向Socks5代理发送UDP关联请求(按照Socks5协议,UDP关联需要先发送0x03命令);
    • 接收代理返回的关联信息(包括代理的UDP监听端口);
    • 创建一个专用的UDP接收socket(proxy_recv_sock),用来接收代理转发回来的远端数据;
    • 维护映射表:记录(本地socket句柄, 目标远端地址) → proxy_recv_sock的对应关系,同时缓存应用的接收上下文(比如如果是异步调用,要保存lpOverlapped和lpCompletionRoutine)。
  2. 发送数据到代理:

    • 按照Socks5 UDP包格式封装数据(头部:0x00 0x00 0x00 + 地址类型 + 目标地址 + 目标端口 + 原始数据);
    • 用proxy_recv_sock调用WSPSendTo把封装后的数据发送到代理的UDP端口。
  3. 接收代理返回的数据:

    • 在proxy_recv_sock上调用WSPRecvFrom等待数据(可以用异步IO或者线程轮询,推荐异步避免阻塞);
    • 收到数据后,解析Socks5 UDP头部,提取出原始的远端地址(就是应用最初发送的目标地址);
    • 用(本地socket句柄, 远端地址)作为键,在映射表中找到对应的本地应用socket。
  4. 转发数据给调用方(核心步骤):

    • 如果应用是同步调用WSPRecvFrom:你需要把解析后的数据填充到应用的WSABUF缓冲区中,设置lpNumberOfBytesRecvd,然后返回成功;
    • 如果应用是异步调用(用了lpOverlapped或完成例程):你需要调用WSPSetEvent触发重叠结构的事件,或者直接调用应用提供的lpCompletionRoutine,把数据传递回去。

关键注意事项

  • 映射表的生命周期:当本地应用关闭socket时,要及时清理对应的映射条目,避免内存泄漏;
  • Socks5 UDP包解析:一定要严格按照协议格式解析,头部的三个0x00是保留字段,然后是地址类型(0x01是IPv4,0x03是域名,0x04是IPv6),接着是地址长度(域名的话是第一个字节表示长度),然后是端口(大端序);
  • 异步IO的处理:在LSP中处理异步UDP时,一定要正确传递上下文,避免把不同应用的数据搞混;
  • 线程安全:映射表要加锁,因为LSP可能在多线程环境下被调用。

举个简化的转发代码片段:

// 假设已经解析出远端地址remote_addr,本地socket是local_sock,数据是data_buf
// 应用已经调用过MyWSPRecvFrom,我们缓存了它的接收上下文
RecvContext* ctx = FindRecvContext(local_sock, &remote_addr);
if (ctx) {
    // 把数据拷贝到应用的缓冲区
    memcpy(ctx->lpBuffers->buf, data_buf, data_len);
    ctx->lpNumberOfBytesRecvd = data_len;
    
    if (ctx->lpCompletionRoutine) {
        // 调用应用的完成例程
        ctx->lpCompletionRoutine(0, data_len, ctx->lpOverlapped, 0);
    } else {
        // 设置重叠事件
        SetEvent(ctx->lpOverlapped->hEvent);
    }
    // 释放上下文资源
    free(ctx);
}

内容的提问来源于stack exchange,提问作者just do IT-Nike

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:16:21