如何理解存储在 sockaddr_storage 中的 IP 的 IP 版本

Posted

技术标签:

【中文标题】如何理解存储在 sockaddr_storage 中的 IP 的 IP 版本【英文标题】:How to understand IP version of an IP stored in sockaddr_storage 【发布时间】:2021-08-23 05:05:14 【问题描述】:

我是 IPV6 的新手,我需要将我的项目从 IPV4 样式移植到双栈支持样式,但我遇到了困难。我需要了解存储在 sockaddr_storage 中的地址的 IP 版本,而无需检查其系列。

我编写了一个代码并尝试使用“inet_ntop”函数,但它将所有族相互转换而没有任何错误。例如,如果我尝试使用 AF_INET 进行转换,它会将“fe80::9110:403c:1f99:eacb”IP 转换为“0.0.0.0”,并且由于它是有效的 IPV4 地址,因此我的代码会执行错误的操作。

请检查我下面的代码;

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>

#include "ipv6_util.h"

#define SUCCESS (EXIT_SUCCESS)
#define FAIL    (EXIT_FAILURE)

#define UNUSED(__val__) (void)(__val__)

#define PORT            9999
#define CONN_COUNT      1
#define BUFFER_SIZE     1024
#define QUIT_MESSAGE    "quit"
#define ACK_MESSAGE     "ack"
#define IPV6_IP_1      "::1"
#define IPV4_IP_1      "127.0.0.1"
#define IPV4_IP_2      "192.168.2.254"
#define IPV4_IP_3      "100.100.100.100"
#define IPV6_IP_2      "fe80::9110:403c:1f99:eacb"


void ipv6_util_get_ipadd_ver_from_addr2(const struct sockaddr_storage *ipaddress)

    char address_buf[128] = 0;

    memset(address_buf, 0, 128);
    if (inet_ntop(AF_INET, &((struct sockaddr_in *)ipaddress)->sin_addr, address_buf, 128)) 
        printf("it is ipv4\n");
        printf("buff: %s  %d\n", address_buf, __LINE__);
        //return;
    

    memset(address_buf, 0, 128);
    if (inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ipaddress)->sin6_addr, address_buf, 128)) 
        printf("it is ipv6\n");
        printf("buff: %s  %d\n", address_buf, __LINE__);
        //return;
    



int main(void)


    struct sockaddr_storage dumm;
    memset(&dumm, 0, sizeof(struct sockaddr_storage));


    printf("\n");
    memset(&dumm, 0, sizeof(struct sockaddr_storage));
    if (inet_pton(AF_INET6, IPV6_IP_1, &((struct sockaddr_in6 *)&dumm)->sin6_addr) != 1) 
        printf("error %d\n", __LINE__);
    
    ipv6_util_get_ipadd_ver_from_addr2(&dumm);
    printf("\n\n");


    memset(&dumm, 0, sizeof(struct sockaddr_storage));
    if (inet_pton(AF_INET, IPV4_IP_1, &((struct sockaddr_in *)&dumm)->sin_addr) != 1) 
        printf("error %d\n", __LINE__);
    
    ipv6_util_get_ipadd_ver_from_addr2(&dumm);
    printf("\n\n");


    memset(&dumm, 0, sizeof(struct sockaddr_storage));
    if (inet_pton(AF_INET, IPV4_IP_2, &((struct sockaddr_in *)&dumm)->sin_addr) != 1) 
        printf("error %d\n", __LINE__);
    
    ipv6_util_get_ipadd_ver_from_addr2(&dumm);
    printf("\n\n");


    memset(&dumm, 0, sizeof(struct sockaddr_storage));
    if (inet_pton(AF_INET6, IPV6_IP_2, &((struct sockaddr_in6 *)&dumm)->sin6_addr) != 1) 
        printf("error %d\n", __LINE__);
    
    ipv6_util_get_ipadd_ver_from_addr2(&dumm);
    printf("\n\n");


    memset(&dumm, 0, sizeof(struct sockaddr_storage));
    if (inet_pton(AF_INET, IPV4_IP_3, &((struct sockaddr_in *)&dumm)->sin_addr) != 1) 
        printf("error %d\n", __LINE__);
    
    ipv6_util_get_ipadd_ver_from_addr2(&dumm);
    printf("\n\n");



    return SUCCESS;


     return 0;

对于这段代码,我得到了这个输出;

it is ipv4
buff: 0.0.0.0  39
it is ipv6
buff: ::1  46


it is ipv4
buff: 127.0.0.1  39
it is ipv6
buff: ::  46


it is ipv4
buff: 192.168.2.254  39
it is ipv6
buff: ::  46


it is ipv4
buff: 0.0.0.0  39
it is ipv6
buff: fe80::9110:403c:1f99:eacb  46


it is ipv4
buff: 100.100.100.100  39
it is ipv6
buff: ::  46

所以我找不到它的原始版本。我该怎么做?

感谢您的帮助。

【问题讨论】:

我怀疑这可能是 XY 问题。尽管这样做是有正当理由的,但用户空间代码需要解释 struct sockaddr 的内容有点不寻常。但是,如果您的程序确实有这样的需求,那么为什么您需要在不检查 sockaddr 的家庭的情况下这样做呢? 似乎这种方式更适合我尽可能少地更改源代码,但我从您的 cmets 了解到,这种方式不应该是一个用例。我得到了它。由于我的问题不是一个有效的问题,我真的很想知道为什么“inet_ntop”会这样,有什么办法可以做我想做的事,也许是一种解决方法? 【参考方案1】:

地址族正好在ss_familysa_family 成员中。

使用getaddrinfosockaddr 转换为人类可读的格式。

您不应该使用sockaddr_storage 作为函数的参数,而是使用sockaddr 并将sockaddr_storage 转换为sockaddr

【讨论】:

OP 确实说他想做他想做的事情“而不检查它的家庭”。不过,公平地说,这个要求听起来是人为的。 如果我没记错的话,一个IPV6地址有128位,我的意思是16个字节。但是,sockaddr 结构也是 16 字节长,但它还包括一些额外的字段,例如其中的家庭数据。那么,既然没有足够的空间容纳它,它怎么能覆盖 IPV6 地址呢?那时我很困惑。 sockaddr_storage 应该足够大以包含任何sockaddr 类型。因此,它是用作字节的 storage 的正确类型。但是,接受该类型作为函数的参数是非常不寻常的。我的意思是,例如看getaddrinfogetnameinfo。由于您传递的是指针,因此实际上并不重要,因为它基本上是一个内存地址。该函数的内部可以根据需要使用它。您可以使用switch (sa-&gt;sa_family)getnameinfo 就可以了,没有任何问题。【参考方案2】:

我按照Cheatah 的建议尝试了getnameinfo 函数,结果与我原来的帖子相同。我的意思是,我似乎必须在某些时候将 switch-case 与 sa_familiy 一起使用。

谢谢大家。

【讨论】:

以上是关于如何理解存储在 sockaddr_storage 中的 IP 的 IP 版本的主要内容,如果未能解决你的问题,请参考以下文章

从 sockaddr_storage 中提取 IP 地址和端口信息

从sockaddr_storage中提取IP地址和端口信息

将 IPv4/IPv6 地址和端口设置为 sockaddr_storage 结构

将 sockaddr_storage 转换为 inet_ntop 的 sockaddr_in

UB与不同类型的结构铸件? [复制]

如何理解 React 应用的数据存储?