のぴぴのメモ

自分用のLinuxとかの技術メモ

C言語/glibc getaddrinfo()を使ってホスト名からIPアドレスに変換するサンプル

C言語の標準ライブラリで、接続先のホストの名前解決をしてIPアドレス情報を取得するには、従来はgethostbynameの利用が一般的でした。しかし現在はIPv6に対応するため、getaddrinfo()を利用するべきとされています。*1

f:id:nopipi:20201116013457p:plain
getaddrinfo()の処理概要

ここでは、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の設定に従います。

f:id:nopipi:20201116013457p:plain
getaddrinfo()の処理概要

処理概要

  1. アプリーケーションから、ホスト名の名前解決でCライブラリのgetaddrinfo()を呼び出します
  2. getaddrinfo()は、/etc/nsswitch.confを参照し解決順序を取得します
  3. nsswitch.confは一般にhostsファイルから参照する設定のため、最初に/etc/hostsの内容を参照し、該当するホスト名がないか確認します
  4. hotsファイルにない場合はDNSクエリーを発行してホスト名の解決を図ります
    1. DNSクエリーの発行先のDNSサーバ情報を/etc/resolve.confから取得します
    2. DNSでUPD処理をするためsocketを準備し、クエリーを発行し応答を受信します

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: