C言語の標準ライブラリで、接続先のホストの名前解決をしてIPアドレス情報を取得するには、従来はgethostbynameの利用が一般的でした。しかし現在はIPv6に対応するため、getaddrinfo()を利用するべきとされています。*1
ここでは、getaddrinfo()による名前解決とIPアドレスに解決した情報の参照方法のサンプルコードを以下に記載します。またおまけで、getaddrinfo()を実行した時の挙動をざっくり説明します。
サンプルコード
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #define BUFF_SIZE 1024 int main(int argc, char **argv) { char *hostname; char *service; struct addrinfo hints, *res0, *res; int ret; char ipandport[BUFF_SIZE], buf[BUFF_SIZE]; int port; /* set hostname and service from arguments */ if( argc < 2 ){ fprintf(stderr, "error: illigal arguments.\nsample_getaddrinfo HOSTNAME [SERVICE]\n"); return(EXIT_FAILURE); }else{ /* set hostname */ hostname = (char *)malloc(sizeof(char) * BUFF_SIZE); strncpy(hostname, *(argv+1), BUFF_SIZE); /* set port, if service is specified */ if( argc > 2){ service = (char *)malloc(sizeof(char) * BUFF_SIZE); strncpy(service, *(argv+2), BUFF_SIZE); }else{ service = NULL; } } printf("hostname=%s, service=%s\n", hostname, service); /* main dish!! */ memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = PF_UNSPEC; if ((ret = getaddrinfo(hostname, service, &hints, &res0)) != 0) { perror("error at getaddrinfo():"); return(EXIT_FAILURE); } /* convert ai_addr from binary to string */ switch(res0->ai_family){ case AF_INET: inet_ntop(res0->ai_family, &((struct sockaddr_in *)res0->ai_addr)->sin_addr, buf, BUFF_SIZE); port = ntohs( ((struct sockaddr_in *)res0->ai_addr)->sin_port ); break; case AF_INET6: inet_ntop(res0->ai_family, &((struct sockaddr_in6 *)res0->ai_addr)->sin6_addr, buf, BUFF_SIZE); port = ntohs( ((struct sockaddr_in6 *)res0->ai_addr)->sin6_port ); break; default: fprintf(stderr, "ai_family is neither AF_INET nor AF_INET6\n"); break; } sprintf(ipandport, "ip=%s, port=%d", buf, port); /* print addrinfo */ printf("addrinfo {\n"); printf(" int ai_flags = %2d \n", res0->ai_flags); printf(" int ai_family = %2d (AF_INET=2, AF_INET6=10, AF_UNSPEC=0)\n", res0->ai_family); printf(" int ai_socktype = %2d (SOCK_STREAM=1, SOCK_DGRAM=2)\n", res0->ai_socktype); printf(" int ai_protocol = %2d\n", res0->ai_protocol); printf(" socklen_t ai_addrlen = %2d\n", res0->ai_addrlen); printf(" struct sockaddr *ai_addr = %s\n", ipandport ); printf(" char *ai_canonname= %s\n", res0->ai_canonname); printf(" struct addrinfo *ai_next = %p\n", res0->ai_next); printf("}\n"); return(EXIT_SUCCESS); }
使い方
ビルド
上記サンプルコードのファイル名を、sample_getaddrinfo.c
とした場合の実行手順です。
gcc -o sample_getaddrinfo sample_getaddrinfo.c
実行例
./sample_getaddrinfo google.com hostname=google.com, service=(null) addrinfo { int ai_flags = 0 int ai_family = 2 (AF_INET=2, AF_INET6=10, AF_UNSPEC=0) int ai_socktype = 1 (SOCK_STREAM=1, SOCK_DGRAM=2) int ai_protocol = 6 socklen_t ai_addrlen = 16 struct sockaddr *ai_addr = ip=172.217.25.78, port=0 char *ai_canonname= (null) struct addrinfo *ai_next = 0x1b3faf0 }
補足
getaddrinfo()の挙動
大まかな挙動ですが、アプリケーションからcライブラリのgetaddrinfo()を呼び出すと、/etc/nsswitch.conf
に設定された順序にしたがってhostsファイルやDNSクエリー発行でホスト名のIPアドレス変換をします。この時DNSクエリーの発行先は、/etc/resolve.conf
の設定に従います。
処理概要
getaddrinfo()実行時のstrace
getaddrinfo()挙動の参考としてstraceによるサンプルコード実行時のシステムコールのトレースを貼り付けます。
$ strace -F -tt ./sample_getaddrinfo google.com strace: option -F is deprecated, please use -f instead 15:08:38.895790 execve("./sample_getaddrinfo", ["./sample_getaddrinfo", "google.com"], 0x7fff89a8f6b8 /* 22 vars */) = 0 <中略> <-----------ここからgetaddrinfo()を呼び出した中の処理になります <中略> 15:08:38.901343 open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3 ↓ <<<nsswitch.confで解決優先順位を確認 15:08:38.901539 fstat(3, {st_mode=S_IFREG|0644, st_size=1713, ...}) = 0 15:08:38.901680 read(3, "#\n# /etc/nsswitch.conf\n#\n# An ex"..., 4096) = 1713 <中略> ↓ <<< /etc/hostsファイルを読み込み確認 15:08:38.902267 open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3 15:08:38.906723 fstat(3, {st_mode=S_IFREG|0644, st_size=126, ...}) = 0 15:08:38.907003 read(3, "127.0.0.1 localhost localhost."..., 4096) = 126 <中略> ↓ <<<resolv.confを読み込みでDNSクエリー発行先を取得 15:08:38.902946 open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3 15:08:38.903064 fstat(3, {st_mode=S_IFREG|0644, st_size=129, ...}) = 0 15:08:38.903266 read(3, "options timeout:2 attempts:5\n; g"..., 4096) = 129 <中略> ↓ <<< UDPによるDNSサーバへの問い合わせ(問い合わせ先 = 10.1.0.2) 15:08:38.911102 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3 <== ここから、DNS(UDP 53port)でDNSにクエリーを発行し応答を受信する処理になります 15:08:38.911335 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.1.0.2")}, 16) = 0 15:08:38.911535 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}]) 15:08:38.911694 sendmmsg(3, [{msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="8\276\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\1\0\1", iov_len=28}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=28}, {msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\276\316\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\34\0\1", iov_len=28}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=28}], 2, MSG_NOSIGNAL) = 2 15:08:38.912113 poll([{fd=3, events=POLLIN}], 1, 2000) = 1 ([{fd=3, revents=POLLIN}]) 15:08:38.912375 ioctl(3, FIONREAD, [44]) = 0 15:08:38.912558 recvfrom(3, "8\276\201\200\0\1\0\1\0\0\0\0\6google\3com\0\0\1\0\1\300\f\0\1"..., 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.1.0.2")}, [28->16]) = 44 15:08:38.912858 poll([{fd=3, events=POLLIN}], 1, 1998) = 1 ([{fd=3, revents=POLLIN}]) 15:08:38.913024 ioctl(3, FIONREAD, [56]) = 0 15:08:38.916333 recvfrom(3, "\276\316\201\200\0\1\0\1\0\0\0\0\6google\3com\0\0\34\0\1\300\f\0\34"..., 65536, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.1.0.2")}, [28->16]) = 56 15:08:38.916608 close(3) = 0 <中略> <-----------ここまでgetaddrinfo()の処理 <中略> 15:08:38.924689 write(1, "addrinfo {\n", 11addrinfo { <== 結果出力 ) = 11 15:08:38.924772 write(1, " int ai_flags "..., 39 int ai_flags = 0 ) = 39 15: