套接字选项 SO_BSP_STATE 因 WSAEFAULT

如何解决套接字选项 SO_BSP_STATE 因 WSAEFAULT

使用具有级别 SOL_SOCKET 和选项 SO_BSP_STATE 的函数 getsockopt(...) 时,我收到 WSA 错误代码 WSAEFAULT,其中说明如下:

"optvaloptlen 参数之一不是用户地址空间的有效部分,或者 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(...) 多(参见行 13051641) :

union _csspace
{
    CSADDR_INFO cs;
    char space[128];
} csinfoA,csinfoB;

看起来 ReacOS 项目也引用了相同的代码 (see reference)。即使这是一个 union,因为 sizeof(CSADDR_INFO) 总是小于 128csinfoA 的大小总是 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 定义更改为等于 01,那么您将看到我概述的问题。

由于在 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(...) 成功,我上面的代码示例中发现了什么问题?

解决方法

我认为这里发生的事情如下:

  1. CSADDR_INFO 的定义如下:
typedef struct _CSADDR_INFO {
  SOCKET_ADDRESS LocalAddr;
  SOCKET_ADDRESS RemoteAddr;
  INT            iSocketType;
  INT            iProtocol;
} CSADDR_INFO;

具体来说,它包含两个 SOCKET_ADDRESS 结构。

  1. SOCKET_ADDRESS 的定义如下:
typedef struct _SOCKET_ADDRESS {
  LPSOCKADDR lpSockaddr;
  INT        iSockaddrLength;
} SOCKET_ADDRESS;
  1. lpSockaddr 结构的 SOCKET_ADDRESS 是一个指向 SOCK_ADDR 结构的指针那个的长度因地址族(例如 ipv4 与 ipv6)。

因此 getsockopt 需要某个地方来存储这些 SOCK_ADDR 结构,这就是您的额外数据“blob”所在的地方 - 它们就在那里,由两个 {{1} } 结构。进一步推断,这些额外数据大小的最坏情况可能会超出您的允许范围,因为如果它们是 ipv6 地址,它们将比它们是 ipv4 地址时更长。

当然,文档应该说明所有这些,但是,有时情况下,作者可能不了解事情是如何运作的。您可能喜欢raise a bug report

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-