i41
i41

Reputation: 311

getaddrinfo Removes last character

int print_socket_info(int sock_fd, struct sockaddr_in *sin, short protocol){
    char dbg[INET_ADDRSTRLEN];
    char *famstr;
inet_ntop(protocol, &(sin->sin_addr), dbg, INET_ADDRSTRLEN);
printf("============ SOCKET INFORMATION =============\n");
printf("!** socket: %d\n", sock_fd);
printf("!** info->ai_addr: sockaddr_in(\n");
famstr = fam2str(sin->sin_family);
printf("!**     sin_family:    %s\n", famstr);
printf("!**     sin_port:      %d\n", ntohs(sin->sin_port));
printf("!**     sin_addr:      in_addr( s_addr : '%s' )\n", dbg);
printf("!**)\n");
printf("=============================================\n");
return 1;
}

char *fam2str(int fam){
switch (fam){
    case AF_INET:
        return "AF_INET";
    case AF_INET6:
        return "AF_INET6";
    case AF_UNSPEC:
        return "AF_UNSPEC";
    default:
        return "(UNKNOWN)";
    }
    return "(UNKNOWN)";
}

If I pass in hint.ai_addr (ignore the info->...that's part of a string) like so:

print_socket_info(sock, (struct sockaddr_in *)hint.ai_addr, protocol);

...then I get the following printed out...

============ SOCKET INFORMATION =============
!** socket: 3
!** info->ai_addr: sockaddr_in(
!**     sin_family:    AF_INET6
!**     sin_port:      8081
!**     sin_addr:      in_addr( s_addr : '::1' )
!**)
=============================================

... information is printed out correctly. Next I call the function:

res =  getaddrinfo(target_host, target_port, &hint, &info);

I get no error so far. Now, I loop through the linked list:

struct addrinfo *rp;
for (rp = info; rp != NULL; rp = rp->ai_next){
    printf("==> Another element.\n");
    print_socket_info(sock, (struct sockaddr_in *) rp->ai_addr, protocol);
}

... I get just one element printed out:

============ SOCKET INFORMATION =============
!** socket: 3
!** info->ai_addr: sockaddr_in(
!**     sin_family:    AF_INET6
!**     sin_port:      8081
!**     sin_addr:      in_addr( s_addr : '::' )
!**)
=============================================

...which is wreaking havoc with bind(), of course. Why was the address shortened?

Something else that's odd: If I pass in 127.0.0.1 and use AF_INET4 then the address is maintained throughout the entire program (I only get one result and bind still fails, though).

Any ideas? Thanks in advance.

Upvotes: 0

Views: 182

Answers (1)

Celada
Celada

Reputation: 22261

Your print_socket_info function is wrong. If takes a struct sockaddr_in * (IPv4 socket address structure) but it's meant to support both IPv4 and IPv6.

You must declare print socket_info to take a generic struct sockaddr * (socket address of any type). For good measure: rename the sin argument to sa to indicate that it's of the generic type, not struct sockaddr_in type. Then, inside the function, you check sin->sin_family to find out what the actual family is and continue by casting sin to either a struct sockaddr_in * or struct sockaddr_in6 as appropriate.

What's happening in your existing function is that you're just treating it as a struct sockaddr_in * throughout the whole function with the following results (at least on Linux):

  • Checking sin_family is OK because the family is guaranteed to be at the same offset into the structure for all types of sockaddrs, whether it is struct sockaddr_in or struct sockaddr_in6 or even struct sockaddr_un for UNIX domain sockets or the sockaddr structures for all the other obscure address families too.
  • You are lucky to get away with it when checking sin_port because sin_port in struct sockaddr_in happens to lie at the same offset in the structure as sin6_port in struct sockaddr_in6.
  • If doesn't work for sin_addr because for IPv4, sin_addr comes right after sin_port in the structure, but for IPv6, some other field is found at that location (namely, sin6_flowinfo). sin6_addr is somewhere else.

Another thing that's wrong with print_socket_info is that your string buffer only has enough space for an IPv4 address string because it is declared with length INET_ADDRSTRLEN which is too short for IPv6 (for IPv6 you need INET6_ADDRSTRLEN. And the function does not need to take a parameter protocol. This information is already embedded in the sockaddr.

int print_socket_info(int sock_fd, struct sockaddr *sa){
    char dbg[INET6_ADDRSTRLEN]; /* the larger of the two sizes */
    char *famstr;
    unsigned short port;

   switch (sa->sa_family):
        case AF_INET4:    
            inet_ntop(AF_INET4, &(((struct sockaddr_in *)sa)->sin_addr), dbg, sizeof(dbg));
            port = ((struct sockaddr_in *)sa)->sin_port;
            break;
        case AF_INET6:
            inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), dbg, sizeof(dbg));
            port = ((struct sockaddr_in6 *)sa)->sin6_port;
            break;
        default:
            strcpy(dst, "UNKNOWN");
            port = 0;
    }
    printf("============ SOCKET INFORMATION =============\n");
    printf("!** socket: %d\n", sock_fd);
    printf("!** info->ai_addr: sockaddr_in(\n");
    famstr = fam2str(sa->sa_family);
    printf("!**     sa_family:    %s\n", famstr);
    printf("!**     sin[6]_port:  %d\n", ntohs(port));
    printf("!**     sin[6]_addr:  in_addr( s_addr : '%s' )\n", dbg);
    printf("!**)\n");
    printf("=============================================\n");
    return 1;
}

Upvotes: 1

Related Questions