套接字选项 SO_BSP_STATE 因 WSAEFAULT 而失败
Socket option SO_BSP_STATE fails with WSAEFAULT
当使用具有级别 SOL_SOCKET
和选项 SO_BSP_STATE
的函数 getsockopt(...)
时,我收到 WSA 错误代码 WSAEFAULT
,其中说明如下:
"One of the optval
or the optlen
parameters is not a valid part of the user address space, or the optlen
parameter is too small."
但是,我传入了一个大小正确的用户模式缓冲区:
/* ... */
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
文档 under the remarks section of the getsockopt(...)
function, the return value is of type CSADDR_INFO
. Furthermore, the Microsoft documentation page for the SO_BSP_STATE socket option,规定了以下要求:
optval
:
"[...] This parameter should point to buffer equal to or larger than the size of a CSADDR_INFO
structure."
optlen
:
"[...] This size must be equal to or larger than the size of a CSADDR_INFO
structure."
在做了一些研究之后,我偶然发现了一些来自 WineHQ 的测试代码,在调用 getsockopt(...)
时传递的内存比 sizeof(CSADDR_INFO)
多(参见第 1305 and 1641 行):
union _csspace
{
CSADDR_INFO cs;
char space[128];
} csinfoA, csinfoB;
ReacOS 项目似乎也引用了相同的代码 (see reference)。即使这是 union
,因为 sizeof(CSADDR_INFO)
总是小于 128
,csinfoA
的大小总是 128
字节。
因此,这让我想知道调用 getsockopt(...)
时套接字选项 SO_BSP_STATE
实际需要多少字节。我创建了以下完整示例(通过 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
结构之外所需的额外 space 可能会有所不同:
X86 需要 - Space:
sizeof(CSADDR_INFO) + 29
X64 需要 - Space:
sizeof(CSADDR_INFO) + 25
如图所示,这完全是任意的,如果您不添加这个任意填充,那么 getsockopt(...)
将会失败。这让我怀疑我得到的数据是否正确。看起来发布的文档中可能缺少脚注,但是,我很可能误解了一些东西(很可能是这个)。
我的问题:
- 什么与
SO_BSP_STATE
实际需要的缓冲区大小相关(即结构等)?因为,显然 不是 sizeof(CSADDR_INFO)
所记录的。
- Microsoft 文档是否不正确 (reference)?如果 不是 ,我上面的代码示例中发现了什么问题,如果
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;
SOCKET_ADDRESS
结构的lpSockaddr
是指向SOCK_ADDR
结构的指针,的长度 因地址系列而异(例如 ipv4 与 ipv6)。
因此 getsockopt
需要在某个地方存储这些 SOCK_ADDR
结构,这就是您的 'blob' 额外数据的来源 - 它们就在那里,由两个指向SOCKET_ADDRESS
结构。进一步得出,对于这些额外数据的大小,最坏的情况可能会超过您所允许的,因为如果它们是 ipv6 地址,它们将比如果它们是 ipv4 地址更长。
当然,文档应该把所有这些都拼写出来,但是,有时是这样,作者可能不明白事情是如何运作的。你可能喜欢 raise a bug report.
当使用具有级别 SOL_SOCKET
和选项 SO_BSP_STATE
的函数 getsockopt(...)
时,我收到 WSA 错误代码 WSAEFAULT
,其中说明如下:
"One of the
optval
or theoptlen
parameters is not a valid part of the user address space, or theoptlen
parameter is too small."
但是,我传入了一个大小正确的用户模式缓冲区:
/* ... */
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
文档 under the remarks section of the getsockopt(...)
function, the return value is of type CSADDR_INFO
. Furthermore, the Microsoft documentation page for the SO_BSP_STATE socket option,规定了以下要求:
optval
:
"[...] This parameter should point to buffer equal to or larger than the size of a
CSADDR_INFO
structure."
optlen
:
"[...] This size must be equal to or larger than the size of a
CSADDR_INFO
structure."
在做了一些研究之后,我偶然发现了一些来自 WineHQ 的测试代码,在调用 getsockopt(...)
时传递的内存比 sizeof(CSADDR_INFO)
多(参见第 1305 and 1641 行):
union _csspace
{
CSADDR_INFO cs;
char space[128];
} csinfoA, csinfoB;
ReacOS 项目似乎也引用了相同的代码 (see reference)。即使这是 union
,因为 sizeof(CSADDR_INFO)
总是小于 128
,csinfoA
的大小总是 128
字节。
因此,这让我想知道调用 getsockopt(...)
时套接字选项 SO_BSP_STATE
实际需要多少字节。我创建了以下完整示例(通过 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
结构之外所需的额外 space 可能会有所不同:
-
X86 需要
- Space:
sizeof(CSADDR_INFO) + 29
X64 需要 - Space:
sizeof(CSADDR_INFO) + 25
如图所示,这完全是任意的,如果您不添加这个任意填充,那么 getsockopt(...)
将会失败。这让我怀疑我得到的数据是否正确。看起来发布的文档中可能缺少脚注,但是,我很可能误解了一些东西(很可能是这个)。
我的问题:
- 什么与
SO_BSP_STATE
实际需要的缓冲区大小相关(即结构等)?因为,显然 不是sizeof(CSADDR_INFO)
所记录的。 - Microsoft 文档是否不正确 (reference)?如果 不是 ,我上面的代码示例中发现了什么问题,如果
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;
SOCKET_ADDRESS
结构的lpSockaddr
是指向SOCK_ADDR
结构的指针,的长度 因地址系列而异(例如 ipv4 与 ipv6)。
因此 getsockopt
需要在某个地方存储这些 SOCK_ADDR
结构,这就是您的 'blob' 额外数据的来源 - 它们就在那里,由两个指向SOCKET_ADDRESS
结构。进一步得出,对于这些额外数据的大小,最坏的情况可能会超过您所允许的,因为如果它们是 ipv6 地址,它们将比如果它们是 ipv4 地址更长。
当然,文档应该把所有这些都拼写出来,但是,有时是这样,作者可能不明白事情是如何运作的。你可能喜欢 raise a bug report.