Search code examples
c++csocketsnetwork-programmingwinsock

How to retrieve IP and port from a `sockaddr_storage` instance?


How can I retrieve IP and port from a sockaddr_storage instance with getnameinfo() (ref)?

When I used inet_ntop, it worked like normal, but when I replaced it with the getnameinfo function, Windows returned an error:

Code 10047: An address incompatible with the requested protocol was used.

#ifdef USE_IPV6
int fd = socket(AF_INET6, SOCK_DGRAM, 0);
#else
int fd = socket(AF_INET, SOCK_DGRAM, 0);
#endif

sockaddr_storage address;
int length = sizeof address;

char buffer[1];
recvfrom(fd, buffer, sizeof buffer, 0, (struct sockaddr *) &address, &length);

// Error
char ip[NI_MAXHOST];
char port[NI_MAXSERV];
int rc = getnameinfo((struct sockaddr *) &address, length, ip, sizeof ip, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV);
if (rc) WSAGetLastError(); // Error Code = 10047

// Works
#ifdef USE_IPV6
struct sockaddr_in6 *sa = (struct sockaddr_in6 *) &address;
inet_ntop(AF_INET6, &sa->sin6_addr, ip, INET6_ADDRSTRLEN);
uint16_t port_ = ntohs(sa->sin6_port);
#else
struct sockaddr_in *sa = (struct sockaddr_in *) &address;
inet_ntop(AF_INET, &sa->sin_addr, ip, INET_ADDRSTRLEN);
uint16_t port_ = ntohs(sa->sin_port);
#endif

Solution

  • I took a look at your issue and was able to reproduce the error you were having.

    Reproducible example:

    #define WIN32_LEAN_AND_MEAN
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    
    #include <cstdint>
    #include <cstdlib>
    #include <iostream>
    #include <windows.h>
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    
    int main(int, char **) {
    
      WORD wVersionRequested = MAKEWORD(2, 2);
      WSADATA wsaData;
      WSAStartup(wVersionRequested, &wsaData);
    
    #ifdef USE_IPV6
      int fd = socket(AF_INET6, SOCK_DGRAM, 0);
    #else
      SOCKET fd = socket(AF_INET, SOCK_DGRAM, 0);
    #endif
    
      sockaddr_storage address;
      int length = sizeof(sockaddr_storage);
    
      char buffer[1];
      recvfrom(fd, buffer, sizeof buffer, 0, (struct sockaddr*)&address, &length);
    
      // Error
      char ip[NI_MAXHOST];
      char port[NI_MAXSERV];
      int rc = getnameinfo((struct sockaddr*)&address,
                           length,
                           ip,
                           sizeof(ip),
                           port,
                           sizeof(port),
                           NI_NUMERICHOST | NI_NUMERICSERV);
      if (rc)
        WSAGetLastError(); // Error Code = 10047
    
      std::cout << "IP: " << ip << std::endl;
      std::cout << "Port: " << port << std::endl;
    
    // Works
    #ifdef USE_IPV6
      struct sockaddr_in6* sa = (struct sockaddr_in6*)&address;
      inet_ntop(AF_INET6, &sa->sin6_addr, ip, INET6_ADDRSTRLEN);
      uint16_t port_ = ntohs(sa->sin6_port);
    #else
      struct sockaddr_in* sa = (struct sockaddr_in*)&address;
      inet_ntop(AF_INET, &sa->sin_addr, ip, INET_ADDRSTRLEN);
      uint16_t port_ = ntohs(sa->sin_port);
    #endif
    }
    

    The issue lies in the fact that you aren't setting the ss_family field of the sockaddr_storage structure.

    To fix this, you need to specify the ss_family value in the sockaddr_storage as follows:

    sockaddr_storage address;
    address.ss_family = AF_INET;
    

    Once you do this, you should not see error 10047 anymore and you should be able to print the contents of the ip and port buffers and see the respective information.

    References: