When using the function getsockopt(...)
with the level SOL_SOCKET
and option SO_BSP_STATE
, I am receiving the WSA error code WSAEFAULT
, which states the following:
"One of the
optval
or theoptlen
parameters is not a valid part of the user address space, or theoptlen
parameter is too small."
However, I was passing in a correctly sized, user-mode buffer:
/* ... */
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;
}
/* ... */
According to the socket option SO_BSP_STATE
documentation 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, states the following requirements:
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."
After doing some research, I stumbled upon some test code from WineHQ that was passing in more memory than sizeof(CSADDR_INFO)
when calling getsockopt(...)
(see lines 1305 and 1641):
union _csspace
{
CSADDR_INFO cs;
char space[128];
} csinfoA, csinfoB;
It also looks like the project ReacOS also references this same exact code (see reference). Even though this is a union
, because sizeof(CSADDR_INFO)
is always less than 128
, csinfoA
will always be 128
bytes in size.
Therefore, this got me wondering how many bytes the socket option SO_BSP_STATE
actually requires when calling getsockopt(...)
. I created the following complete example (via Visual Studio 2019 / C++17) that illustrates that in fact SO_BSP_STATE
requires a buffer more than sizeof(CSADDR_INFO)
, which is in direct contrast to the Microsoft published documentation:
/**
* @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;
}
(If you change the EXTRA_SPACE
define to equal 0
or 1
, then you will see the issue I am outlining.)
Due to the default structure alignment and pointer size change when compiling for either X86 or X64 in Visual Studio 2019, the required extra space beyond the CSADDR_INFO
structure can vary:
sizeof(CSADDR_INFO) + 29
sizeof(CSADDR_INFO) + 25
This is completely arbitrary as shown and if you don't add this arbitrary padding, then getsockopt(...)
will fail. This makes me call into question if the data I am getting back is even correct. This looks like there might be a missing footnote in the published documentation, however, I very well could be misunderstanding something (very likely this).
My Question(s):
SO_BSP_STATE
actually requires (i.e. a structure, etc.)? Because, it is clearly not sizeof(CSADDR_INFO)
as documented.EXTRA_SPACE
is set to 0
, in order for getsockopt(...)
to be successful?I think what's happening here is as follows:
CSADDR_INFO
is defined like so:typedef struct _CSADDR_INFO {
SOCKET_ADDRESS LocalAddr;
SOCKET_ADDRESS RemoteAddr;
INT iSocketType;
INT iProtocol;
} CSADDR_INFO;
Specifically, it contains two SOCKET_ADDRESS
structures.
SOCKET_ADDRESS
is defined like so:typedef struct _SOCKET_ADDRESS {
LPSOCKADDR lpSockaddr;
INT iSockaddrLength;
} SOCKET_ADDRESS;
lpSockaddr
of the SOCKET_ADDRESS
structure is a pointer to a SOCK_ADDR
structure, and the length of that varies by address family (ipv4 vs ipv6, for example).It follows that getsockopt
needs somewhere to store these SOCK_ADDR
structures and that's where your 'blob' of extra data comes in - they're in there, pointed to by the two SOCKET_ADDRESS
structures. It further follows that the worst-case scenario for the size of this extra data may be more than you are allowing, since, if they are ipv6 addresses, they will be longer than if they are ipv4 addressses.
Of course, the documentation should spell all of this out, but, as is sometimes the case, the authors probably didn't understand how things work. You might like to raise a bug report.