Using an example from the manual I am trying to write a simple program that gets a list of IPv4 address of a host:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
struct addrinfo hints;
struct addrinfo *result, *rp;
/* Obtain address(es) matching host/port */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* IPv4 */
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
hints.ai_protocol = 0; /* Any protocol */
int res = getaddrinfo("google.com", NULL, &hints, &result);
if (res != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully connect(2).
If socket(2) (or connect(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) {
puts(rp->ai_addr->sa_data);
}
if (rp == NULL) {
fprintf(stderr, "No address succeeded\n");
exit(EXIT_FAILURE);
}
return 0;
}
I was expecting to get a list of IP addresses from the for loop. But, unfortunately, the program outputs empty lines.
What is wrong?
CodePudding user response:
The getaddrinfo()
functions returns a linked list of struct addrinfo
. Each entry has a struct_sockaddr *ai_addr
member, which is a generic structure for representing network addresses. You need to cast that to a protocol specific structure -- in this case, struct sockaddr_in
, which looks like this:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
In struct sockaddr_in
, the ip address is represented as a 32-bit integer; to convert that into the common dotted-octet format, we need to use the inet_ntoa
function, making your code look like this:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
struct addrinfo hints;
struct addrinfo *result, *rp;
/* Obtain address(es) matching host/port */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* IPv4 */
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
hints.ai_protocol = 0; /* Any protocol */
int res = getaddrinfo("google.com", NULL, &hints, &result);
if (res != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully connect(2).
If socket(2) (or connect(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) {
printf("%s\n", inet_ntoa(((struct sockaddr_in*)rp->ai_addr)->sin_addr));
}
return 0;
}
Note that I've removed the if (rp == NULL)
bit from the end, because after the for
loop rp
is always equal to NULL
(that's the exit condition of the loop).
Running the above code produces:
142.250.80.78
To make the code present both ipv4 and ipv6 addresses you would use inet_ntop
instead of inet_ntoa
; that would look like:
char dst[1024];
for (rp = result; rp != NULL; rp = rp->ai_next) {
if (rp->ai_family == AF_INET) {
if (NULL != inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, dst, sizeof(dst))) {
printf("ipv4: %s\n", dst);
} else {
perror("inet_ntop");
}
} else if (rp->ai_family == AF_INET6) {
if (NULL != inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, dst, sizeof(dst))) {
printf("ipv6: %s\n", dst);
} else {
perror("inet_ntop");
}
} else {
printf("wtf? %d", rp->ai_family);
}
}
The above code would output:
ipv6: 2607:f8b0:4006:81f::200e
ipv4: 142.250.80.110