我使用的是一个做一些网络工作的库,当客户端连接时,该库提供了一个“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记录。

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

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

最佳答案

您不能使用sizeof(*hostaddr),因为hostaddr是通用的sockaddr*指针。不同的地址族使用不同的sockaddr_...类型,它们的大小与sockaddr本身并不完全相同。 getnameinfo()需要根据其地址族了解sockaddr_...实际指向的hostaddr结构的真实大小。

根据Linux getnameinfo() documentation:


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

最好的解决方案是使 call 者传递正确的大小:

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, ...);
    ...
}

07-24 14:01