2016-06-30 4 views
0

Ich benutze eine Bibliothek, die einige Nework Zeug macht und wenn ein Client verbindet, bietet diese Bibliothek eine "struct sockaddr *", die den Client-Socket hält. Ich wollte einfach die IP und den Port extrahieren und ich tue es derzeit so aus:C/C++ Getnameinfo Ai_Family nicht unterstützt

#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; 
} 

Bei der Verwendung von IPv4 auf meinem Mac, es funktioniert. Wenn ich diese Anwendung auf meinem Gentoo Linux-Server mit voller Unterstützung IPv6 verwenden, erhalte ich nur:

Fehler: ai_family nicht unterstützt

Der Verbindungs ​​Client AAAA und IP6 Aufzeichnungen in Platz hat.

Ich fügte einige couts hinzu und druckte hostaddr-> sa_family, das 10 zurückgibt, das AF_INET6 ist.

Warum funktioniert das nicht? :-)

Antwort

1

Sie können sizeof(*hostaddr) nicht verwenden, da hostaddr ein generischer sockaddr* Zeiger ist. Verschiedene Adressfamilien verwenden unterschiedliche sockaddr_... Typen, die nicht alle die gleiche Größe wie sockaddr selbst haben. getnameinfo() muss wissen, die true Größe der sockaddr_... Struktur, die hostaddr zeigt tatsächlich auf, basierend auf seiner Adresse Familie.

Per Linux getnameinfo() documentation:

EAI_FAMILY
The address family was not recognized, or the address length was invalid for the specified family

sockaddr_in (IPv4) ist die gleiche Größe wie sockaddr, weshalb getnameinfo() für IPv4 "arbeiten". Aber (IPv6) ist größer als sockaddr, weshalb getnameinfo() fehlschlägt.

Die beste Lösung, um die Anrufer Pass in der richtigen Größe zu haben ist:

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)); 

Ansonsten haben Sie es berechnen:

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

Gibt es eine Möglichkeit herauszufinden, ob der Sockaddr IPv4 oder IPv6 trägt? –

+1

Entschuldigung. Na sicher. Ich überprüfe einfach sa_family –