Windows LSP Socks5代理:WSPSendTo获调用方端口及UDP转发问题
解答:Windows LSP中WSPSendTo获取调用方端口及UDP数据转发问题
作为常年折腾Windows LSP和代理开发的老鸟,我来帮你拆解这两个问题:
一、在WSPSendTo中获取调用方(本地应用)的UDP端口
你要的调用方端口,其实就是当前socket绑定的本地UDP端口。在WSPSendTo的钩子函数里,你可以通过WSPGetSockName来获取这个信息,具体步骤如下:
- 拿到WSPSendTo的第一个参数
SOCKET s(就是应用使用的UDP套接字句柄); - 准备一个
sockaddr_in结构和长度变量; - 调用下层的
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。我把完整的修正流程和关键细节给你梳理清楚:
完整流程修正
拦截应用的WSPSendTo调用:
- 用上面的方法获取本地socket的端口和地址;
- 通过已建立的TCP连接向Socks5代理发送UDP关联请求(按照Socks5协议,UDP关联需要先发送
0x03命令); - 接收代理返回的关联信息(包括代理的UDP监听端口);
- 创建一个专用的UDP接收socket(proxy_recv_sock),用来接收代理转发回来的远端数据;
- 维护映射表:记录
(本地socket句柄, 目标远端地址) → proxy_recv_sock的对应关系,同时缓存应用的接收上下文(比如如果是异步调用,要保存lpOverlapped和lpCompletionRoutine)。
发送数据到代理:
- 按照Socks5 UDP包格式封装数据(头部:
0x00 0x00 0x00+ 地址类型 + 目标地址 + 目标端口 + 原始数据); - 用
proxy_recv_sock调用WSPSendTo把封装后的数据发送到代理的UDP端口。
- 按照Socks5 UDP包格式封装数据(头部:
接收代理返回的数据:
- 在
proxy_recv_sock上调用WSPRecvFrom等待数据(可以用异步IO或者线程轮询,推荐异步避免阻塞); - 收到数据后,解析Socks5 UDP头部,提取出原始的远端地址(就是应用最初发送的目标地址);
- 用
(本地socket句柄, 远端地址)作为键,在映射表中找到对应的本地应用socket。
- 在
转发数据给调用方(核心步骤):
- 如果应用是同步调用WSPRecvFrom:你需要把解析后的数据填充到应用的
WSABUF缓冲区中,设置lpNumberOfBytesRecvd,然后返回成功; - 如果应用是异步调用(用了
lpOverlapped或完成例程):你需要调用WSPSetEvent触发重叠结构的事件,或者直接调用应用提供的lpCompletionRoutine,把数据传递回去。
- 如果应用是同步调用WSPRecvFrom:你需要把解析后的数据填充到应用的
关键注意事项
- 映射表的生命周期:当本地应用关闭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
相关产品推荐
相关产品推荐

