C/C++ getnameinfo ai_family 仅在 macOS 上不受支持

C/C++ getnameinfo ai_family not supported only on macOS

如果 IPv6 或某些虚拟接口可用,以下代码将不再适用于 macOS。

我总是得到错误 getnameinfo() failed: Unknown error (ai_family not supported)

知道这有什么问题吗?我只需要一个正确的 ipv4 和 internet 网络接口。

问题首先出现在 macOS Sierra 上。


#include "jni.h"
#include "bla_nativeclasses_JNISubNetMask.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

static jobjectArray make_row(JNIEnv *env, jsize count, const char* elements[])
{
    jclass stringClass = (*env)->FindClass(env, "java/lang/String");
    jobjectArray row = (*env)->NewObjectArray(env, count, stringClass, 0);
    jsize i;

    for (i = 0; i < count; ++i) {
        (*env)->SetObjectArrayElement(env, row, i, (*env)->NewStringUTF(env, elements[i]));
    }
    return row;
}


JNIEXPORT jobjectArray JNICALL Java_bla_JNISubNetMask_getSubNetMask(JNIEnv *env, jobject jobj){
    struct ifaddrs *ifaddr, *ifa;
    int family, s ,s2;
    int i = 0;
    int count = 0;
    char host[NI_MAXHOST];
    char subnet[NI_MAXHOST];
    char *tmp = NULL;

    const char* net[1000];
    if (getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        exit(EXIT_FAILURE);
    }

    /* Walk through linked list, maintaining head pointer so we can free list later */
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL)
           continue;

        if (ifa->ifa_addr->sa_family != AF_INET)
            continue;

        s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
        s2 = getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in), subnet, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);

        if (s != 0 || s2 != 0) {
            printf("getnameinfo() failed: %s (%s)\n", gai_strerror(s), gai_strerror(s2));
            exit(EXIT_FAILURE);
        }

        tmp = (char *)malloc(100*sizeof(char));
        strcpy (tmp,ifa->ifa_name);
        net[i++] = tmp;
        tmp = (char *)malloc(100*sizeof(char));
        strcpy (tmp,host);
        net[i++] = tmp;
        tmp = (char *)malloc(100*sizeof(char));
        strcpy (tmp,subnet);
        net[i++] = tmp;
    }

    freeifaddrs(ifaddr);
    count = i;
    jobjectArray jnet = make_row(env, count, net);
    return jnet;
}

我知道已经有另一个,但我不太明白答案

出现此问题是因为 IPV4 和 IPV6 的大小不同。考虑以下两行代码

s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
s2 = getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in), subnet, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);

如果地址族是 AF_INETsizeof 操作将适用于您的代码,但对于 IPV6 又名 AF_INET6 你必须使用 sizeof(sockaddr_in6);,它们都有不同的大小(分别为 4 和 6 字节) .

你可以做的是以下-

    int len;
    switch (fa->ifa_addr->sa_family) {
        case AF_INET:
            len = sizeof(sockaddr_in);
            break;
        case AF_INET6:
            len= sizeof(sockaddr_in6);
            break;
        default:
            std::cerr << "Unknown Address family\n";
            break;
    }

然后在调用 getnameinfo 时使用长度。

编辑

@leo_poldX, 好像是因为if (ifa->ifa_addr->sa_family != AF_INET) 继续;,其余代码不应针对 IPV6 执行。你能检查一下是否正确吗?

i got always the error getnameinfo() failed: Unknown error (ai_family not supported)

根据您代码中该消息的来源,很明显它是由以下情况引起的

        s2 = getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in), subnet, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);

失败,错误代码为 EAI_FAMILY,即使前面的

        s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);

成功。根据具体的错误代码,失败可能是由于 ifa->ifa_netmask->ai_family 被设置为不同于 AF_INET 的值,或者

  • 一个不同的已知家族,其地址需要更大的地址结构,或者
  • 一个无效/未知的家庭。

我可以想象其中任何一种可能出现的方式,但无论哪种方式,我都将其描述为 getifaddrs() 到 return 任何从中提取地址和网络掩码的条目的错误不同的地址系列。

合理的缓解措施取决于问题的具体性质。例如,

  • 如果整个条目完全无效,那么您应该检测到并跳过它。
  • 如果网络掩码数据采用 IPv4 地址形式,但系统未能[正确]设置系列,那么您可以尝试检测这种情况并在调用 getnameinfo() 之前更正它。
  • 如果网络掩码是 IPv6 地址的形式,那么您可以检测到它并将其读取为 IPv6 地址,然后找出从那里去的地方。结果可能是编码为 IPv6 地址的 IPv4 地址,在这种情况下,您可以从后者中提取前者。
  • 如果您可以不用子网掩码,那么在这种情况下您可以将其虚拟化,或者甚至可以将其从方法结果中完全删除。