英文:
TCP SYN RAW socket receive packet error 10022
问题
我正在将我的程序从Unix移植到Windows,并且在使用recvfrom()
函数接收数据包时遇到问题。直到现在,我还没有使用过Winsock。根据手册,我认为我已经做了一切。
此外,根据手册的描述,TCP RAW套接字不需要使用bind()
进行绑定,但仍然无法正常工作。
#define NESCA
int
ncread(const char* dest_ip, int recv_timeout_ms, unsigned char **buffer, bool debug,
int dest_port, int source_port, bool packet_trace)
{
/* 创建一个结构体并将目标IP传递给它。 */
struct in_addr dest; dest.s_addr = inet_addr(dest_ip);
/* 时间缓冲区。 */
unsigned char *read_buffer = *buffer;
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
wprintf(L"WSAStartup failed with error %d\n", iResult);
return -1;
}
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sock == -1) {return SOCKET_ERROR;}
#ifdef NESCA
/* 与下面的条目类似的函数 */
int result = set_socket_timeout(sock, recv_timeout_ms, 1, 1);
#else
struct timeval timeout;
timeout.tv_sec = recv_timeout_ms / 1000;
timeout.tv_usec = (recv_timeout_ms % 1000) * 1000;
int result = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
#endif
if (result < 0)
{
std::cout << "timeout infinity\n\n";
/* 超时。*/
fuck_fd(sock);
return -1;
}
/* 用于比较IP地址。*/
struct sockaddr saddr;
int saddr_size = sizeof(saddr);
auto start_time = std::chrono::steady_clock::now();
/* 无限循环,接受系统发送的任何数据包。*/
for (;;)
{
/* 将数据包接收到缓冲区。*/
int data_size = recvfrom(sock, (char*)read_buffer, READ_BUFFER_SIZE, 0, (sockaddr*)&saddr,
if (data_size == -1)
{
/* 错误:10022 */
wprintf(L"recvfrom failed with error %d\n", WSAGetLastError());
fuck_fd(sock);
return READ_ERROR;
}
/* 获取接收到的数据包的IP头。*/
struct ip_header *iph = (struct ip_header*)read_buffer;
unsigned short iphdrlen = (iph->ihl) * 4;
if (iphdrlen < 20)
{
fuck_fd(sock);
return IP_HEADER_LEN_ERROR;
}
/* 从中获取发送者的IP地址。*/
struct sockaddr_in source;
memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = iph->saddr;
/* 比较IP地址,以查看是否与目标IP匹配。
* 这是为了丢弃其他数据包。*/
if (source.sin_addr.s_addr != dest.s_addr)
{
if (debug) {std::cout << "Got the wrong package.\n";}
/* 无窗口超时可能在这里起作用。*/
auto current_time = std::chrono::steady_clock::now();
auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - start_time).count();
if (elapsed_time >= recv_timeout_ms)
{
std::cout << "timeout infinity\n\n";
fuck_fd(sock);
return INFINITY_TIMEOUT_EXITED;
}
/* 如果不是正确的数据包,继续等待,直到超时,或直到正确的数据包到达。*/
continue;
}
else
{
#ifdef NESCA
if (packet_trace)
{
struct in_addr addr;
addr.s_addr = iph->saddr;
std::string source_ip = inet_ntoa(addr);
addr.s_addr = iph->daddr;
std::string dest_ip = inet_ntoa(addr);
struct tcp_header *tcph = (struct tcp_header*)(buffer+ iphdrlen);
unsigned short id = ntohs(iph->id);
unsigned int seq = ntohl(tcph->seq);
unsigned int iplen = ntohs(iph->tot_len);
packet_trace1.lock();
np3.nlog_packet_trace("RCVD", "TCP", source_ip, dest_ip, dest_port, source_port, "", iph->ttl, id, tcph->window, seq, iplen);
packet_trace1.unlock();
}
#endif
/* 如果是正确的数据包。
* 使用它来填充缓冲区。*/
*buffer = read_buffer;
fuck_fd(sock);
return SUCCESS_READ;
}
}
/* 噢。*/
fuck_fd(sock);
return READ_ERROR;
}
关闭套接字函数:
int fuck_fd(int fd)
{
#ifdef _WIN32
WSACleanup();
closesocket(fd);
#else
close(fd);
#endif
}
英文:
I'm porting my program from Unix to Windows, and I have a problem with the recvfrom()
function to accept a packet. I haven't written anything with Winsock until now. I think I've done everything, according to the manual.
Also, as I read from the manual, a TCP RAW socket does not need binding via bind()
, but it still doesn't work.
#define NESCA
int
ncread(const char* dest_ip, int recv_timeout_ms, unsigned char **buffer, bool debug,
int dest_port, int source_port, bool packet_trace)
{
/*Create a structure and pass the recipient's IP into it.*/
struct in_addr dest; dest.s_addr = inet_addr(dest_ip);
/*Time buffer.*/
unsigned char *read_buffer = *buffer;
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
wprintf(L"WSAStartup failed with error %d\n", iResult);
return -1;
}
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sock == -1) {return SOCKET_ERROR;}
#ifdef NESCA
/*Function similar to the entry below*/
int result = set_socket_timeout(sock, recv_timeout_ms, 1, 1);
#else
struct timeval timeout;
timeout.tv_sec = recv_timeout_ms / 1000;
timeout.tv_usec = (recv_timeout_ms % 1000) * 1000;
int result = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
#endif
if (result < 0)
{
std::cout << "timeout infinity\n\n";
/*Timeout.*/
fuck_fd(sock);
return -1;
}
/*To compare the IPs.*/
struct sockaddr saddr;
int saddr_size = sizeof(saddr);
auto start_time = std::chrono::steady_clock::now();
/*An endless loop, on accepting any packets from the system at all.*/
for (;;)
{
/*Receive packet to buffer.*/
int data_size = recvfrom(sock, (char*)read_buffer, READ_BUFFER_SIZE, 0, (sockaddr*)&saddr,
if (data_size == -1)
{
/*ERROR: 10022*/
wprintf(L"recvfrom failed with error %d\n", WSAGetLastError());
fuck_fd(sock);
return READ_ERROR;
}
/*Obtaining the IP header of the received packet.*/
struct ip_header *iph = (struct ip_header*)read_buffer;
unsigned short iphdrlen = (iph->ihl) * 4;
if (iphdrlen < 20)
{
fuck_fd(sock);
return IP_HEADER_LEN_ERROR;
}
/*Retrieve the sender's IP from it.*/
struct sockaddr_in source;
memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = iph->saddr;
/*Compare the IP to see if it matches the one we sent it to.
* This is to discard other packets.*/
if (source.sin_addr.s_addr != dest.s_addr)
{
if (debug) {std::cout << "Got the wrong package.\n";}
/*A windowless for timeout might work here.*/
auto current_time = std::chrono::steady_clock::now();
auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - start_time).count();
if (elapsed_time >= recv_timeout_ms)
{
std::cout << "timeout infinity\n\n";
fuck_fd(sock);
return INFINITY_TIMEOUT_EXITED;
}
/*If it's the wrong one, it catches again until it times out,
* on a windowless loop. Or until the right packet arrives.*/
continue;
}
else
{
#ifdef NESCA
if (packet_trace)
{
struct in_addr addr;
addr.s_addr = iph->saddr;
std::string source_ip = inet_ntoa(addr);
addr.s_addr = iph->daddr;
std::string dest_ip = inet_ntoa(addr);
struct tcp_header *tcph = (struct tcp_header*)(buffer+ iphdrlen);
unsigned short id = ntohs(iph->id);
unsigned int seq = ntohl(tcph->seq);
unsigned int iplen = ntohs(iph->tot_len);
packet_trace1.lock();
np3.nlog_packet_trace("RCVD", "TCP", source_ip, dest_ip, dest_port, source_port, "", iph->ttl, id, tcph->window, seq, iplen);
packet_trace1.unlock();
}
#endif
/*If it's the right packet.
* Fill the buffer with it.*/
*buffer = read_buffer;
fuck_fd(sock);
return SUCCESS_READ;
}
}
/*oh.*/
fuck_fd(sock);
return READ_ERROR;
}
Close sock function:
int fuck_fd(int fd)
{
#ifdef _WIN32
WSACleanup();
closesocket(fd);
#else
close(fd);
#endif
答案1
得分: 0
错误10022是WSAEINVAL
:
> | 返回代码/值 | 描述 |
> | ----------------- | ----------- |
> | WSAEINVAL<br>10022 | 无效的参数。<br>提供了一些无效的参数(例如,为setsockopt函数指定了无效的级别)。 在某些情况下,它还指的是套接字的当前状态 - 例如,在不处于监听状态的套接字上调用accept。 |
根据Microsoft的recvfrom()
文档:
> 如果没有发生错误,recvfrom将返回接收到的字节数。如果连接已经正常关闭,返回值为零。否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError来检索特定的错误代码。
>
> | 错误代码 | 含义 |
> | ---------- | ------- |
> | WSAEINVAL | 套接字未绑定bind,或者指定了未知的标志,或者为启用了SO_OOBINLINE的套接字指定了MSG_OOB,或者(仅适用于字节流式套接字)len为零或负数。 |
我们可以排除标志,因为您没有使用任何标志。
而且,我们可能可以排除len
参数,假设READ_BUFFER_SIZE
是一个正值,并且您的buffer
确实指向至少READ_BUFFER_SIZE
字节大小的有效内存块,并且READ_BUFFER_SIZE
足够大以处理您正在接收的数据。
我们可以排除bind()
,根据Microsoft的TCP/IP原始套接字文档:
> 在Windows 7、Windows Vista、带有Service Pack 2(SP2)的Windows XP和带有Service Pack 3(SP3)的Windows XP上,以几种方式限制了使用原始套接字发送流量的能力:
>
> ...
>
> - 不允许使用原始套接字为IPPROTO_TCP协议调用bind函数。
>
> 注意
> 对于其他协议(例如IPPROTO_IP、IPPROTO_UDP或IPPROTO_SCTP),允许使用原始套接字调用bind
函数。
那么,还剩下什么呢? recvfrom()
抱怨无效参数,而在此代码中唯一的未知参数是 fromlen
参数,因为您发布的代码没有显示您为该参数传递了什么。 recvfrom()
接受6个参数,但提供的代码只显示传递了5个值。您为 fromlen
参数传递了什么? 您应该传递您的 saddr_size
变量的地址。 请注意,每次调用 recvfrom()
时,您应该重置该变量的值,但您只在首次声明它时设置了它的值。
无论如何,知道Microsoft对使用TCP与原始套接字施加了各种限制,您应该考虑改用 IPPROTO_RAW
或 IPPROTO_IP
,然后过滤掉您不感兴趣的非TCP流量。
顺便说一句:在WSACleanup()
之后不应该调用closesocket()
,您颠倒了这两个调用。而且,如果socket()
失败,此代码不应该调用WSAStartup()
/WSACleanup()
,它们只属于程序启动/关闭,比如在您的main()
/WinMain()
函数中。
英文:
Error 10022 is WSAEINVAL
:
> | Return code/value | Description |
> | ----------------- | ----------- |
> | WSAEINVAL<br>10022 | Invalid argument.<br>Some invalid argument was supplied (for example, specifying an invalid level to the setsockopt function). In some instances, it also refers to the current state of the socket — for instance, calling accept on a socket that is not listening. |
Per Microsoft's recvfrom()
documentation:
> If no error occurs, recvfrom returns the number of bytes received. If the connection has been gracefully closed, the return value is zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.
>
> | Error code | Meaning |
> | ---------- | ------- |
> | WSAEINVAL | The socket has not been bound with bind, or an unknown flag was specified, or MSG_OOB was specified for a socket with SO_OOBINLINE enabled, or (for byte stream-style sockets only) len was zero or negative. |
We can rule out the flags, since you are not using any.
And, we can probably rule out the len
parameter, assuming READ_BUFFER_SIZE
is a positive value, and that your buffer
really does point to a valid memory block of at least READ_BUFFER_SIZE
bytes in size, and that READ_BUFFER_SIZE
is large enough to handle the data you are receiving.
And, we can rule out bind()
, per Microsoft's TCP/IP raw sockets documentation:
> On Windows 7, Windows Vista, Windows XP with Service Pack 2 (SP2), and Windows XP with Service Pack 3 (SP3), the ability to send traffic over raw sockets has been restricted in several ways:
>
> ...
>
> - A call to the bind function with a raw socket for the IPPROTO_TCP protocol is not allowed.
>
> Note
> The bind
function with a raw socket is allowed for other protocols (IPPROTO_IP, IPPROTO_UDP, or IPPROTO_SCTP, for example).
So, what does that leave? recvfrom()
is complaining about an invalid parameter, and the only unknown parameter in this code is the fromlen
parameter, since the code you have posted is not showing what you are passing in for that parameter. recvfrom()
takes 6 parameters, but the code provided is showing only 5 values being passed in. What are you passing in for the fromlen
parameter? You should be passing in the address of your saddr_size
variable. Do note that you should be resetting that variable's value every time you call recvfrom()
, but you are only setting its value when you first declare it.
In any case, knowing that Microsoft imposes various restrictions on using TCP with RAW sockets, you should consider using IPPROTO_RAW
or IPPROTO_IP
instead, and then just filter out any non-TCP traffic you are not interested in.
On a side note: you should not be calling closesocket()
after WSACleanup()
, you have those two calls reversed. And, you are not calling WSACleanup()
if socket()
fails. This code should not be calling WSAStartup()
/WSACleanup()
to begin with, they belong at program startup/shutdown only, such as in your main()
/WinMain()
function.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论