c++ - 套接字选项 SO_BSP_STATE 失败并显示 WSAEFAULT
问题描述
当使用getsockopt(...)
带有 levelSOL_SOCKET
和 option的函数时SO_BSP_STATE
,我收到 WSA 错误代码WSAEFAULT
,其中说明如下:
"或参数之一不是用户地址空间的有效部分,或参数太小。
optval
optlen
optlen
"
但是,我传入了一个大小正确的用户模式缓冲区:
/* ... */
HRESULT Result = E_UNEXPECTED;
CSADDR_INFO Info = { 0 }; // Placed on the stack.
int InfoSize = sizeof (CSADDR_INFO); // The size of the input buffer to `getsockopt()`.
// Get the local address information from the raw `SOCKET`.
if (getsockopt (this->WsaSocket,
SOL_SOCKET,
SO_BSP_STATE,
reinterpret_cast <char *> (&Info),
&InfoSize) == SOCKET_ERROR)
{
Result = HRESULT_FROM_WIN32 (WSAGetLastError ());
}
else
{
Result = S_OK;
}
/* ... */
根据函数备注部分下的套接字选项SO_BSP_STATE
文档,返回值为 type 。此外, SO_BSP_STATE 套接字选项的 Microsoft 文档页面规定了以下要求:getsockopt(...)
CSADDR_INFO
optval
“[...] 此参数应指向等于或大于
CSADDR_INFO
结构大小的缓冲区。”
optlen
:
“[...] 这个大小必须等于或大于
CSADDR_INFO
结构的大小。”
在做了一些研究之后,我偶然发现了来自 WineHQ 的一些测试代码,它传递的内存比sizeof(CSADDR_INFO)
调用时更多getsockopt(...)
(参见第1305和1641行):
union _csspace
{
CSADDR_INFO cs;
char space[128];
} csinfoA, csinfoB;
看起来 ReacOS 项目也引用了同样的代码(参见参考资料)。即使这是 a union
,因为sizeof(CSADDR_INFO)
总是小于128
,大小csinfoA
总是128
字节。
因此,这让我想知道套接字选项SO_BSP_STATE
在调用getsockopt(...)
. 我创建了以下完整示例(通过 Visual Studio 2019 / C++17),它说明实际上SO_BSP_STATE
需要一个超过 的缓冲区sizeof(CSADDR_INFO)
,这与 Microsoft 发布的文档形成鲜明对比:
/**
* @note This example was created and compiled in Visual Studio 2019.
*/
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
/**
* @brief The number of bytes to increase the @ref CSADDR_INFO_PLUS_EXTRA_SPACE structure by.
* @note Alignment and pointer size changes when compiling for Intel x86 versus Intel x64.
* The extra bytes required therefore vary.
*/
#if defined(_M_X64) || defined(__amd64__)
#define EXTRA_SPACE (25u) // Required extra space when compiling for X64
#else
#define EXTRA_SPACE (29u) // Required extra space when compiling for X86
#endif
/**
* @brief A structure to add extra space passed the `CSADDR_INFO` structure.
*/
typedef struct _CSADDR_INFO_PLUS_EXTRA_SPACE
{
/**
* @brief The standard structure to store Windows Sockets address information.
*/
CSADDR_INFO Info;
/**
* @brief A blob of extra space.
*/
char Extra [EXTRA_SPACE];
} CSADDR_INFO_PLUS_EXTRA_SPACE;
/**
* @brief The main entry function for this console application for demonstrating an issue with `SO_BSP_STATE`.
*/
int main (void)
{
HRESULT Result = S_OK; // The execution result of this console application.
SOCKET RawSocket = { 0 }; // The raw WSA socket index variable the references the socket's memory.
WSADATA WindowsSocketsApiDetails = { 0 }; // The WSA implementation details about the current WSA DLL.
CSADDR_INFO_PLUS_EXTRA_SPACE Info = { 0 }; // The structure `CSADDR_INFO` plus an extra blob of memory.
int InfoSize = sizeof (CSADDR_INFO_PLUS_EXTRA_SPACE);
std::cout << "Main Entry!" << std::endl;
// Request for the latest Windows Sockets API (WSA) (a.k.a. Winsock) DLL available on this system.
if (WSAStartup (MAKEWORD(2,2),
&WindowsSocketsApiDetails) != 0)
{
Result = HRESULT_FROM_WIN32 (WSAGetLastError ());
}
// Create a blank TCP socket using IPv4.
if ((RawSocket = WSASocketW (AF_INET,
SOCK_STREAM,
IPPROTO_TCP,
nullptr,
0,
0)) == INVALID_SOCKET)
{
Result = HRESULT_FROM_WIN32 (WSAGetLastError ());
}
else
{
// Get the local address information from the raw `SOCKET`.
if (getsockopt (RawSocket,
SOL_SOCKET,
SO_BSP_STATE,
reinterpret_cast <char *> (&Info),
&InfoSize) == SOCKET_ERROR)
{
std::cout << "Failed obtained the socket's state information!" << std::endl;
Result = HRESULT_FROM_WIN32 (WSAGetLastError ());
}
else
{
std::cout << "Successfully obtained the socket's state information!" << std::endl;
Result = S_OK;
}
}
// Clean up the entire Windows Sockets API (WSA) environment and release the DLL resource.
if (WSACleanup () != 0)
{
Result = HRESULT_FROM_WIN32 (WSAGetLastError ());
}
std::cout << "Exit Code: 0x" << std::hex << Result << std::endl;
return Result;
}
(如果您将EXTRA_SPACE
定义更改为等于0
或1
,那么您将看到我概述的问题。)
由于在 Visual Studio 2019 中为 X86 或 X64 编译时默认结构对齐和指针大小发生变化,结构之外所需的额外空间CSADDR_INFO
可能会有所不同:
- X86 所需空间:
sizeof(CSADDR_INFO) + 29
- X64 所需空间:
sizeof(CSADDR_INFO) + 25
如图所示,这是完全任意的,如果您不添加此任意填充,getsockopt(...)
则将失败。这让我质疑我返回的数据是否正确。这看起来在已发布的文档中可能缺少脚注,但是,我很可能会误解某些东西(很可能是这个)。
我的问题:
- 什么与
SO_BSP_STATE
实际需要的缓冲区大小(即结构等)相关联?因为,它显然没有sizeof(CSADDR_INFO)
记录在案。 - Microsoft 文档是否不正确(参考)?如果没有,在我上面的代码示例中发现了什么问题,如果
EXTRA_SPACE
设置为0
,为了getsockopt(...)
成功?
解决方案
我认为这里发生的情况如下:
CSADDR_INFO
定义如下:
typedef struct _CSADDR_INFO {
SOCKET_ADDRESS LocalAddr;
SOCKET_ADDRESS RemoteAddr;
INT iSocketType;
INT iProtocol;
} CSADDR_INFO;
具体来说,它包含两个SOCKET_ADDRESS
结构。
SOCKET_ADDRESS
定义如下:
typedef struct _SOCKET_ADDRESS {
LPSOCKADDR lpSockaddr;
INT iSockaddrLength;
} SOCKET_ADDRESS;
- 结构的 是指向结构的指针
lpSockaddr
,其长度因地址族而异(例如,ipv4 与 ipv6)。SOCKET_ADDRESS
SOCK_ADDR
因此,getsockopt
需要在某个地方存储这些结构,这就是你的额外数据“blob”的来源——它们就在那里,由两个结构SOCK_ADDR
指向。SOCKET_ADDRESS
这进一步表明,这种额外数据大小的最坏情况可能超出您的允许范围,因为如果它们是 ipv6 地址,它们将比它们是 ipv4 地址更长。
当然,文档应该说明所有这些,但是,有时,作者可能不理解事情是如何工作的。您可能想提出错误报告。
推荐阅读
- javascript - 导航菜单不关闭
- wordpress - 从帖子标题中删除网站名称?
- java - 在java中插入带有准备好的语句的多行字符串
- javascript - 如何使条在d3中具有不同的颜色?
- javascript - 接收 TypeError ... 不是函数
- r - 如何在 r 中创建一个随机增加的矩阵?
- twitter - 英国的 Twitter Profile Geo Enrichment
- c# - C# SQL: System.InvalidOperationException: '已经有一个打开的 DataReader 与此命令关联,必须先关闭。'
- python - 在pytorch中使用预训练对象检测器时如何获得边界框所有类的置信度分数
- javascript - AWS 中是否有全局变量的真正替代品?