【问题标题】:C/C++ getnameinfo ai_family not supported不支持 C/C++ getnameinfo ai_family
【发布时间】:2016-06-30 20:21:56
【问题描述】:

我使用一个库来做一些网络工作,当客户端连接时,这个库提供了一个“struct sockaddr *”来保存客户端套接字。我只是想提取 IP 和端口,我目前这样做:

#include <sys/socket.h>
#include <netdb.h>

const std::string Client::prepareIPandPort(struct sockaddr *hostaddr) {
    assert(hostaddr != nullptr);

    std::string ipport;
    char clienthost[NI_MAXHOST];
    char clientport[NI_MAXSERV];
    int result = getnameinfo(hostaddr, sizeof(*hostaddr),
                             clienthost, sizeof(clienthost),
                             clientport, sizeof(clientport),
                             NI_NUMERICHOST | NI_NUMERICSERV);

    if (result != 0) {
        std::cerr << "Error: " << gai_strerror(result) << std::endl;
        ipport = "unknown";
    } else {
        switch (hostaddr->sa_family) {
            case AF_INET:
                ipport = std::string {clienthost} + ":"
                         + std::string {clientport};
                break;
            case AF_INET6:
                ipport = "[" + std::string {clienthost} + "]:"
                         + std::string {clientport};
                break;
            default:
                ipport = "unknown";
        }
    }

    return ipport;
}

在我的 Mac 上使用 IPv4 时,它可以工作。如果我在完全支持 IPv6 的 Gentoo Linux 服务器上使用这个应用程序,我只会得到:

错误:不支持 ai_family

连接的客户端有 AAAA 和 IP6 记录。

我添加了一些 couts 并打印了 hostaddr->sa_family,它返回 10,即 AF_INET6。

为什么这不起作用? :-)

【问题讨论】:

    标签: c++ c sockets networking


    【解决方案1】:

    您不能使用sizeof(*hostaddr),因为hostaddr 是一个通用的sockaddr* 指针。不同的地址族使用不同的sockaddr_... 类型,它们的大小并不都与sockaddr 本身相同。 getnameinfo() 需要知道 sockaddr_... 结构体的 true 大小,hostaddr 实际指向的结构体,基于其地址族。

    根据 Linux getnameinfo() documentation:

    EAI_FAMILY
    地址族无法识别,或指定族的地址长度无效

    sockaddr_in (IPv4) 的大小与sockaddr 相同,这就是为什么getnameinfo() 对 IPv4 “有效”。但是sockaddr_in6(IPv6)大于sockaddr,这就是getnameinfo()失败的原因。

    最好的解决方案是让调用者传入正确的大小:

    const std::string Client::prepareIPandPort(struct sockaddr *hostaddr, int hostaddrlen) {
        ...
        int result = getnameinfo(hostaddr, hostaddrlen, ...);
        ...
    }
    

    sockaddr_in ipv4host;
    ...
    client.prepareIPandPort((sockaddr*)&ipv4host, sizeof(ipv4host));
    

    sockaddr_in6 ipv6host;
    ...
    client.prepareIPandPort((sockaddr*)&ipv6host, sizeof(ipv6host));
    

    否则,你必须计算它:

    const std::string Client::prepareIPandPort(struct sockaddr *hostaddr) {
        ...
        int hostaddrlen;
        switch (hostaddr->sa_family) {
            case AF_INET:
                hostaddrlen = sizeof(sockaddr_in);
                break;
            case AF_INET6:
                hostaddrlen = sizeof(sockaddr_in6);
                break;
            default:
                std::cerr << "Error: " << gai_strerror(EAI_FAMILY) << std::endl;
                return "unknown";
        }
        int result = getnameinfo(hostaddr, hostaddrlen, ...);
        ...
    }
    

    【讨论】:

    • sockaddr 是否携带 IPv4 或 IPv6,有什么方法可以查出吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-20
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多