如何解决套接字选项 SO_BSP_STATE 因 WSAEFAULT
使用具有级别 SOL_SOCKET
和选项 SO_BSP_STATE
的函数 getsockopt(...)
时,我收到 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
函数的socket选项getsockopt(...)
文档under the remarks section,返回值是CSADDR_INFO
类型。此外,SO_BSP_STATE socket option 的 Microsoft 文档页面规定了以下要求:optval
:
"[...] 此参数应指向等于或大于 CSADDR_INFO
结构大小的缓冲区。"
optlen
:
“[...] 此大小必须等于或大于 CSADDR_INFO
结构的大小。”
在做了一些研究之后,我偶然发现了 WineHQ 的一些测试代码,这些代码在调用 sizeof(CSADDR_INFO)
时传递的内存比 getsockopt(...)
多(参见行 1305 和 1641) :
union _csspace
{
CSADDR_INFO cs;
char space[128];
} csinfoA,csinfoB;
看起来 ReacOS 项目也引用了相同的代码 (see reference)。即使这是一个 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)) == INVALID_SOCKET)
{
Result = HRESULT_FROM_WIN32 (WSAGetLastError ());
}
else
{
// Get the local address information from the raw `SOCKET`.
if (getsockopt (RawSocket,&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 文档是否不正确 (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;
-
lpSockaddr
结构的SOCKET_ADDRESS
是一个指向SOCK_ADDR
结构的指针,那个的长度因地址族(例如 ipv4 与 ipv6)。
因此 getsockopt
需要某个地方来存储这些 SOCK_ADDR
结构,这就是您的额外数据“blob”所在的地方 - 它们就在那里,由两个 {{1} } 结构。进一步推断,这些额外数据大小的最坏情况可能会超出您的允许范围,因为如果它们是 ipv6 地址,它们将比它们是 ipv4 地址时更长。
当然,文档应该说明所有这些,但是,有时情况下,作者可能不了解事情是如何运作的。您可能喜欢raise a bug report。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。