【问题标题】:Isn't struct sockadr_in supposed to work for both IPv4 and IPv6?struct sockadr_in 不应该适用于 IPv4 和 IPv6 吗?
【发布时间】:2012-10-20 20:44:38
【问题描述】:

特别是 sin_addr 似乎位于 IPv4 和 IPv6 套接字寻址的不同内存位置。这会导致奇怪:

#include <stdio.h>                                                                               
#include <netinet/in.h>                                                                          

int main(int argc, char ** argv) {                                                               
  struct sockaddr_in sa;                                                                         
  printf("sin_addr in sockaddr_in  = %p\n", &sa.sin_addr);                                       
  printf("sin_addr in sockaddr_in6 = %p\n", &((struct sockaddr_in6*)&sa)->sin6_addr);            
};

输出:

sin_addr in sockaddr_in  = 0x7fffa26102b4
sin_addr in sockaddr_in6 = 0x7fffa26102b8

为什么这两个值不一样?

由于 this 指向相同的数据(要连接的地址),因此应该位于相同的地址。否则,您应该如何使用您不知道 IPv4 或 IPv6 的 sockaddr_in 调用 inet_ntop ?

【问题讨论】:

    标签: c linux sockets


    【解决方案1】:

    为什么这两个值不一样?

    sockaddr_insockaddr_in6 是用于不同地址族(分别为 IPv4 和 IPv6)的不同结构。它们不需要以任何方式相互兼容,除了一个 - 第一个字段必须是一个 16 位整数来保存地址系列。 sockaddr_in 始终将该字段设置为 AF_INETsockaddr_in6 始终将该字段设置为 AF_INET6。通过以这种方式标准化 family 字段,任何基于 sockaddr 的 API 都可以访问该字段并知道如何根据需要解释其余的结构数据。这也是为什么基于sockaddr的API通常也有一个int大小值作为输入/输出,因为sockaddr_insockaddr_in6是不同的字节大小,所以API需要能够验证大小您传递的任何缓冲区。

    由于 this 指向相同的数据(要连接的地址),因此应该位于相同的地址。

    不,不应该。结构中地址字段的位置特定于结构所属的地址族的类型。不要求sockaddr_insockaddr_in6 将它们的地址存储在完全相同的偏移量处。

    否则,您应该如何使用您不知道是 IPv4 还是 IPv6 的 sockaddr_in 调用 inet_ntop ?

    sockaddr_in 只能与 IPv4 一起使用,不能与其他任何东西一起使用,sockaddr_in6 只能与 IPv6 一起使用,不能与其他任何东西一起使用。如果你有一个sockaddr_in,那么你隐含地知道你有一个 IPv4 地址,如果你有一个sockaddr_in6,那么你隐含地知道你有一个 IPv6 地址。您必须将该信息指定给inet_ntop(),以便它知道如何解释您传递给它的数据:

    struct sockaddr_in sa;
    inet_ntop(AF_INET, &(sa.sin_addr), ...);
    

    .

    struct sockaddr_in6 sa;
    inet_ntop(AF_INET6, &(sa.sin6_addr), ...);
    

    为了帮助您编写与家庭无关的代码,您应该尽可能直接使用sockaddr_storage 而不是sockaddr_insockaddr_in6sockaddr_storage 的大小足以容纳 sockaddr_insockaddr_in6 结构。由于两个结构都以相同的偏移量和大小定义了一个族字段,sockaddr_storage 可以与任何对sockaddr* 指针(connect()accept()bind()getsockname()getpeername())进行操作的 API 一起使用等)。

    但是,inet_ntop() 不属于该类别,因此您在使用inet_ntop() 时必须手动拆分sockaddr_storage,例如:

    struct sockaddr_storage sa;
    
    switch (sa.ss_family)
    {
    case AF_INET:
        inet_ntop(AF_INET, &(((sockaddr_in*)&sa)->sin_addr), ...);
        break;
    case AF_INET6:
        inet_ntop(AF_INET6, &(((sockaddr_in6*)&sa)->sin6_addr), ...);
        break;
    }
    

    【讨论】:

    • 实际上可以使用 sockaddr_in6 进行 IPv4 通信,方法是将 setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY) 设置为 false,并使用 IPv4 映射的 IPv6 地址指定 IPv4 主机(例如 ::ffff: 192.168.0.1 而不是 ::192.168.0.1)。这很好,因为有了这个应用程序可以与 IPv4 和 IPv6 客户端通信,而无需到处使用 switch/case 语句。
    • IPv4 映射地址在技术上仍然是 IPv6 地址,因此它们仍然必须通过 sockaddr_in6in6_addr,而不是 sockaddr_inin_addr。除此之外,IPv6 映射地址仅在支持双栈套接字的操作系统版本上可用。并非所有操作系统都支持这一点。例如,Windows XP 支持 IPv6,但不支持双栈套接字。这是在 Windows Vista 中添加的。
    【解决方案2】:

    不,对于 ipv6 你需要使用

    in6_addr // is used to store the 128-bit network address
    

    sockaddr_in6
    

    详情可参考here

    要编写支持双栈的代码,即 ipv4 和 6,请使用 this

    【讨论】:

    • 这并不能真正回答问题。使用这个泛型结构 sockaddr 很容易将其传递给连接甚至绑定,为什么我不能将泛型结构传递给同时支持 IPv4 和 IPv6 的函数?
    • 您的问题的答案是否定的,正如我在开头所写的那样。没有通用功能,因为不同的 ip 地址类型意味着不同的 ip 协议栈和一直到物理层和数据包/路由格式的处理。如果没有函数可以通用地对其进行操作,那么传递任何通用的东西是没有任何意义的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多