the following code does not work on macOS anymore if IPv6 or some virtual interfaces are available.
i got always the error getnameinfo() failed: Unknown error (ai_family not supported)
any idea whats wrong with this? i only need a correct network interface with ipv4 and internet.
The problem first appeared with 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;
}
I know that there was already another similar question, but I don't really understand the answer
CodePudding user response:
The problem is occurring because IPV4 and IPV6 have different sizes. Consider the following two lines of your code
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);
the sizeof operation will work fine for your code if the address familyis AF_INET, but for IPV6 aka AF_INET6 you have to use sizeof(sockaddr_in6);, they both have different size (4 and 6 bytes respectively).
what you can do is the following -
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;
}
then use the length on you call to getnameinfo.
EDIT
@leo_poldX, It seems because of if (ifa->ifa_addr->sa_family != AF_INET) continue;, the rest of code should not execute for IPV6. Can you check if it is correct?
CodePudding user response:
i got always the error getnameinfo() failed: Unknown error (ai_family not supported)
Based on the origin of that message in your code, it seems clear that it arises from a case where
s2 = getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in), subnet, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
fails with error code EAI_FAMILY, even though the immediately preceding
s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
succeeded. Based on the specific error code, the failure probably arises from ifa->ifa_netmask->ai_family being set to a value different from AF_INET, either
- a different, known family whose addresses require a larger aaddress structure, or
- an invalid / unknown family.
I can imagine ways in which either one might arise, but either way, I would characterize it as a bug for getifaddrs() to return any entry in which the address and netmask were drawn from different address families.
Plausible mitigations depend on the specific nature of the problem. For example,
- if the overall entry is simply invalid then you should detect that and skip it.
- if the netmask data are in the form of an IPv4 address, but the system has failed to set the family [correctly] then you could try to detect that case and correct it before calling
getnameinfo(). - if the netmask is in the form of an IPv6 address, then you could detect that and read it as an IPv6 address, and figure out where to go from there. It might be that the result is an IPv4 address encoded as an IPv6 address, in which case you could extract the former from the latter.
- if you can do without the subnet mask, then could just dummy it up in this case, or perhaps even remove it from the method result altogether.
