GETADDRINFO(3)

名前
getaddrinfo, freeaddrinfo, gai_strerror - ネットワークのアドレスとサービスを変換する

書式
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);

void freeaddrinfo(struct addrinfo *res);

const char *gai_strerror(int errcode);

    glibc 向けの機能検査マクロの要件 (feature_test_macros(7) 参照):

getaddrinfo(), freeaddrinfo(), gai_strerror():
    Since glibc 2.22: _POSIX_C_SOURCE >= 200112L
    Glibc 2.21 and earlier: _POSIX_C_SOURCE

説明
getaddrinfo() は、(インターネットのホストとサービスを識別する) node と service を渡すと、一つ以上の addrinfo 構造体を返す。それぞれの addrinfo 構造体には、 bind(2) や connect(2) を呼び出す際に指定できるインターネットアドレスが格納されている。 getaddrinfo() 関数は、 gethostbyname(3) と getservbyname(3) の機能をまとめて一つのインターフェースにしたものであるが、 これらの関数と違い、 getaddrinfo() はリエントラントであり、 getaddrinfo() を使うことでプログラムは IPv4 と IPv6 の違いに関する依存関係を なくすことができる。

getaddrinfo() が用いる addrinfo 構造体は以下のフィールドを含む。

    struct addrinfo {
        int              ai_flags;
        int              ai_family;
        int              ai_socktype;
        int              ai_protocol;
        socklen_t        ai_addrlen;
        struct sockaddr *ai_addr;
        char            *ai_canonname;
        struct addrinfo *ai_next;
    };

hints 引数は addrinfo 構造体を指し示し、この構造体を用いて res が指すリストに入れて返すソケットアドレス構造体を選択するための基準を指定する。 hints が NULL でない場合、 hints は addrinfo 構造体を指し示し、その構造体のフィールド ai_family, ai_socktype, ai_protocol で getaddrinfo() が返すソケットアドレス集合に対する基準を指定する。

ai_family
    このフィールドは返されるアドレスの希望のアドレスファミリーを指定する。 このフィールドに指定できる有効な値としては AF_INET と AF_INET6 がある。 また、値 AF_UNSPEC を指定すると、 getaddrinfo() は node と service で使用できるいずれかのアドレスファミリー (例えば IPv4 か IPv6) の ソケットアドレスを返すことを求められる。

ai_socktype
    このフィールドは推奨のソケット型 (例えば SOCK_STREAM や SOCK_DGRAM) を指定する。 このフィールドに 0 を指定すると、任意のソケット型のソケットアドレスを getaddrinfo() が返してよいことを意味する。

ai_protocol
    このフィールドは返されるソケットアドレスのプロトコルを指定する。 このフィールドに 0 を指定すると、任意のプロトコルののソケットアドレスを getaddrinfo() が返してよいことを意味する。

ai_flags
    このフィールドは、追加のオプション (下記) を指定する。 複数のフラグを指定する際には、それらのビット単位の OR をとって指定する。

hints が指し示す構造体の他のすべてのフィールドには 0 かヌルポインターを適切に入れなければならない。

hints に NULL を指定するのは、 ai_socktype と ai_protocol に 0 を、 ai_family に AF_UNSPEC を、 ai_flags に (AI_V4MAPPED | AI_ADDRCONFIG) を設定するのと等価である (POSIX では ai_flags には別のデフォルト値が規定されている; 「備考」を参照)。 node には、数値形式のネットワークアドレス (IPv4 の場合は inet_aton(3) でサポートされているドット区切りの数字による表記、 IPv6 の場合は inet_pton(3) でサポートされている 16 進数の文字列形式) もしくは ネットワークホスト名を指定する。 ネットワークホスト名を指定した場合には、そのネットワークアドレスが検索され、 名前解決が行なわれる。 hints.ai_flags に AI_NUMERICHOST フラグが含まれている場合は、 node は数値形式のネットワークアドレスでなければならない。 AI_NUMERICHOST フラグを使うと、時間の掛かる可能性のあるネットワークホストアドレスの検索は すべて抑制される。

hints.ai_flags に AI_PASSIVE フラグが指定され、かつ node が NULL の場合、 返されるソケットアドレスは コネクションを accept(2) するためのソケットを bind(2) するのに適したものとなる。 返されるソケットアドレスには「ワイルドカードアドレス」 (IPv4 アドレスの場合は INADDR_ANY、 IPv6 アドレスの場合は IN6ADDR_ANY_INIT) が入る。 ワイルドカードアドレスは、任意のホストのネットワークアドレスで接続を 受け付けようとするアプリケーション (通常はサーバー) で用いられる。 node が NULL でない場合、 AI_PASSIVE フラグは無視される。

hints.ai_flags に AI_PASSIVE フラグがセットされていない場合、 返されるソケットアドレスは connect(2), sendto(2), sendmsg(2) での使用に適したものとなる。 node が NULL の場合、ネットワークアドレスにはループバックインターフェイスの アドレス (IPv4 アドレスの場合は INADDR_LOOPBACK IPv6 アドレスの場合は IN6ADDR_LOOPBACK_INIT)が設定される。 これは同じホスト上で動作している接続相手と通信するような アプリケーションで用いられる。

service により、返される各アドレス構造体のポート番号が決まる。 この引数がサービス名 (services(5) 参照) の場合、対応するポート番号に翻訳される。 この引数には 10 進数も指定することができ、 この場合にはバイナリへの変換だけが行われる。 service が NULL の場合、返されるソケットアドレスのポート番号は 初期化されないままとなる。 hints.ai_flags に AI_NUMERICSERV が指定され、かつ service が NULL でない場合、 service は数値のポート番号を含む文字列を指し示さなければならない。 このフラグは、名前解決サービスが不要であることが分かっている場合に、 サービスの起動を抑制するために用いられる。

node と service のどちらかは NULL にしてよいが、両方同時に NULL にしてはならない。

getaddrinfo() 関数は、 addrinfo 構造体のメモリー確保を行い、 addrinfo 構造体のリンクリストを初期化し、 res にリストの先頭へのポインターを入れて返す。 このとき、各構造体のネットワークアドレスは node と service に一致し、 hints で課されたすべての制限を満たすものとなる。 リンクリストの要素は ai_next フィールドにより連結される。

リンクリストの addrinfo 構造体は複数個になることもあり、その理由はいくつかある。 ネットワークホストがマルチホームである、 複数のプロトコルでアクセスできる (例えば AF_INET と AF_INET6 の両方) 、 複数のソケット種別で同じサービスが利用できる (例えば、ひとつが SOCK_STREM アドレスで、もうひとつが SOCK_DGRAM アドレスである)、がある。 通常は、アプリケーションは返された順序でアドレスを試すべきである。 getaddrinfo() の中で使用される並べ替え関数は RFC 3484 で定義されている。 特殊なシステムでは、 /etc/gai.conf を編集することで、この順序を微調整することができる (/etc/gai.conf は glibc 2.5 以降で利用できる)。

hints.ai_flags に AI_CANONNAME フラグが含まれている場合、返されるリストの最初の addrinfo 構造体の ai_canonname フィールドはホストの公式な名前を指すように設定される。

返される各々の addrinfo 構造体の残りのフィールドは以下のように初期化される。

*   ai_family, ai_socktype, ai_protocol フィールドはソケット生成パラメーターを返す (これらのフィールドの意味は socket(2) の同じ名前の引数と同じである)。 例えば、 ai_family は AF_INET や AF_INET6 を返し、 ai_socktype は SOCK_DGRAM や SOCK_STREAM を返し、 ai_protocol はそのソケットのプロトコルを返す。

*   ai_addr フィールドにはソケットアドレスへのポインターが書き込まれ、 ai_addrlen フィールドにはソケットアドレスの長さがバイト単位で書き込まれる。

hints.ai_flags が AI_ADDRCONFIG を含む場合、 res が指すリストには、ローカルシステムに最低一つの IPv4 アドレスが設定されている場合のみ IPv4 アドレスが返され、 ローカルシステムに最低一つの IPv6 アドレスが設定されている場合にのみ IPv6 アドレスが返される。 なお、この場合には、ループバックアドレスは有効に設定されたアドレスとはみなされない。 このフラグは、例えば、IPv4 だけのシステムで、 getaddrinfo() が必ず IPv6 ソケットアドレスを返さないことを保証するのに役立つ。 IPv4 だけのシステムでは、IPv6 アドレスは connect(2) や bind(2) で必ず失敗することになる。

hints.ai_flags に AI_V4MAPPED が指定されていて、 hints.ai_family に AF_INET6 が指定され、 マッチする IPv6 アドレスが見つからなかった場合、 res が指すリストには IPv4-mapped IPv6 アドレスが返される。 hints.ai_flags に AI_V4MAPPED と AI_ALL の両方が指定されている場合、 res が指すリストには IPv6 アドレスと IPv4-mapped IPv6 アドレスの 両方が返される。 AI_V4MAPPED が指定されていない場合、 AI_ALL は無視される。

freeaddrinfo() 関数は、 リンクリスト res に対して動的に割り当てられたメモリーを解放する。

  国際化ドメイン名のための getaddrinfo() の拡張
glibc 2.3.4 から、 getaddrinfo() は入出力するホスト名を透過的に国際化ドメイン名 (IDN) 形式 (RFC 3490 の Internationalizing Domain Names in Applications (IDNA) を参照のこと) と変換することを選択的に認めるように拡張されている。 4 つの新しいフラグが定義されている:

AI_IDN
    このフラグが指定されると、 node で与えられたノード名は必要があれば IDN 形式に変換される。 ソース符号化形式は現在のロケールのものである。

    入力名に非 ASCII 文字が含まれている場合、 IDN 符号化形式が使われる。 非 ASCII 文字が含まれている(ピリオドで区切られる)部分ノード名は、 名前解決機能に渡される前に ASCII 互換符号化形式 (ACE) を使って 符号化される。

AI_CANONIDN
    AI_CANONNAME が指定されている場合、 getaddrinfo() は名前の検索に成功した後、 返された addrinfo 構造体に対応するノードの正規名を返す。 返り値は名前解決機能から返された値の正確なコピーである。

    AI_CANONIDN 名前が ACE で符号化されている場合、一つまたは複数の名前の構成要素の先頭に xn-- を含んでいる。 これらの構成要素を読み込み可能な形に変換するために、 AI_CANONNAME と共に AI_CANONIDN フラグを渡すことも出来る。 返される文字列は現在のロケールの符号化形式で符号化されている。

AI_IDN_ALLOW_UNASSIGNED, AI_IDN_USE_STD3_ASCII_RULES
    これらのフラグをセットすると、IDNA 処理で使用されるフラグ IDNA_ALLOW_UNASSIGNED (未割り当ての Unicode のコードポイントを許容) と IDNA_USE_STD3_ASCII_RULES (出力が STD3 準拠のホスト名かをチェックする) がそれぞれ有効になる。

返り値
getaddrinfo() は成功すると 0 を返し、失敗すると以下の非 0 のエラーコードのいずれかを返す。

EAI_ADDRFAMILY
    指定されたネットワークホストには、 要求されたアドレスファミリーのネットワークアドレスがない。

EAI_AGAIN
    ネームサーバーから一時的な失敗 (temporary failure) を意味する返事が返された。後でもう一度試してみよ。

EAI_BADFLAGS
    hints.ai_flags のフラグに不正なフラグが含まれている。または、 hints.ai_flags に AI_CANONNAME が含まれていて、かつ name が NULL であった。

EAI_FAIL
    ネームサーバーから恒久的な失敗 (permanent failure) を意味する返事が返された。

EAI_FAMILY
    要求されたアドレスファミリーがサポートされていない。

EAI_MEMORY
    メモリーが足りない。

EAI_NODATA
    指定されたネットワークホストは存在するが、 ネットワークアドレスがひとつも定義されていない。

EAI_NONAME
    node と service のどちらかが不明、または node と service の両方が NULL だった場合、または AI_NUMERICSERV が hints.ai_flags に指定されていて、 hints.ai_flags と service が数値のポート番号の文字列でない。

EAI_SERVICE
    要求されたサービスは、要求されたソケットタイプでは利用できない。 他のソケットタイプでなら利用可能かもしれない。 このエラーが発生する例としては、 service が "shell" (ストリームソケットでのみ利用できるサービス) で、 hints.ai_protocol に IPPROTO_UDP が指定されたり、 hints.ai_socktype に SOCK_DGRAM が指定されたりした場合がある。 また、 service が NULL 以外で、 hints.ai_socktype に SOCK_RAW (サービスの考え方をサポートしていないソケット種別) が指定された場合にも、このエラーが発生する。

EAI_SOCKTYPE
    要求されたソケットタイプがサポートされていない。 このエラーが発生する例としては、 hints.ai_socktype と hints.ai_protocol が矛盾している場合 (例えば hints.ai_socktype が SOCK_DGRAM で hints.ai_protocol が IPPROTO_TCP) がある。

EAI_SYSTEM
    その他のシステムエラー。詳しくは errno を調べること。

gai_strerror() 関数を用いると、これらのエラーコードを人間に可読な文字列に変換できるので、 エラー報告に適するだろう。

ファイル
/etc/gai.conf

属性
この節で使用されている用語の説明は attributes(7) を参照のこと。
Interface | Attribute | Value
getaddrinfo() | Thread safety | MT-Safe env locale
freeaddrinfo(), gai_strerror() | Thread safety | MT-Safe

準拠
POSIX.1-2001, POSIX.1-2008. getaddrinfo() 関数は RFC 2553 に記載されている。

注意
getaddrinfo() は、IPv6 scope-ID を指定するために address%scope-id 記法をサポートしている。

AI_ADDRCONFIG, AI_ALL, AI_V4MAPPED は glibc 2.3.3 以降で利用可能である。 AI_NUMERICSERV は glibc 2.3.4 以降で利用可能である。

POSIX.1 によると、 hints に NULL が指定された場合、 ai_flags を 0 とみなすべきとされている。 GNU C ライブラリでは、この場合に、代わりに ai_flags を (AI_V4MAPPED | AI_ADDRCONFIG) とみなすようになっている。 この値の方が標準規格の改善になると考えられているからである。

例
以下のプログラムは、 getaddrinfo(), gai_strerror(), freeaddrinfo(), getnameinfo(3) の使い方を示したものである。 プログラムは UDP データグラムの echo サーバとクライアントである。

  サーバのプログラム
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netdb.h>

    #define BUF_SIZE 500

    int
    main(int argc, char *argv[])
    {
        struct addrinfo hints;
        struct addrinfo *result, *rp;
        int sfd, s;
        struct sockaddr_storage peer_addr;
        socklen_t peer_addr_len;
        ssize_t nread;
        char buf[BUF_SIZE];

        if (argc != 2) {
            fprintf(stderr, "Usage: %s port\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
        hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
        hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
        hints.ai_protocol = 0;          /* Any protocol */
        hints.ai_canonname = NULL;
        hints.ai_addr = NULL;
        hints.ai_next = NULL;

        s = getaddrinfo(NULL, argv[1], &hints, &result);
        if (s != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
            exit(EXIT_FAILURE);
        }

        /* getaddrinfo() returns a list of address structures.
           Try each address until we successfully bind(2).
           If socket(2) (or bind(2)) fails, we (close the socket
           and) try the next address. */

        for (rp = result; rp != NULL; rp = rp->ai_next) {
            sfd = socket(rp->ai_family, rp->ai_socktype,
                    rp->ai_protocol);
            if (sfd == -1)
                continue;

            if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
                break;                  /* Success */

            close(sfd);
        }

        freeaddrinfo(result);           /* No longer needed */

        if (rp == NULL) {               /* No address succeeded */
            fprintf(stderr, "Could not bind\n");
            exit(EXIT_FAILURE);
        }

        /* Read datagrams and echo them back to sender */

        for (;;) {
            peer_addr_len = sizeof(peer_addr);
            nread = recvfrom(sfd, buf, BUF_SIZE, 0,
                    (struct sockaddr *) &peer_addr, &peer_addr_len);
            if (nread == -1)
                continue;               /* Ignore failed request */

            char host[NI_MAXHOST], service[NI_MAXSERV];

            s = getnameinfo((struct sockaddr *) &peer_addr,
                            peer_addr_len, host, NI_MAXHOST,
                            service, NI_MAXSERV, NI_NUMERICSERV);
            if (s == 0)
                printf("Received %zd bytes from %s:%s\n",
                        nread, host, service);
            else
                fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

            if (sendto(sfd, buf, nread, 0,
                        (struct sockaddr *) &peer_addr,
                        peer_addr_len) != nread)
                fprintf(stderr, "Error sending response\n");
        }
    }

  クライアントのプログラム
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>

    #define BUF_SIZE 500

    int
    main(int argc, char *argv[])
    {
        struct addrinfo hints;
        struct addrinfo *result, *rp;
        int sfd, s;
        size_t len;
        ssize_t nread;
        char buf[BUF_SIZE];

        if (argc < 3) {
            fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        /* Obtain address(es) matching host/port */

        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
        hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
        hints.ai_flags = 0;
        hints.ai_protocol = 0;          /* Any protocol */

        s = getaddrinfo(argv[1], argv[2], &hints, &result);
        if (s != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
            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) {
            sfd = socket(rp->ai_family, rp->ai_socktype,
                         rp->ai_protocol);
            if (sfd == -1)
                continue;

            if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
                break;                  /* Success */

            close(sfd);
        }

        freeaddrinfo(result);           /* No longer needed */

        if (rp == NULL) {               /* No address succeeded */
            fprintf(stderr, "Could not connect\n");
            exit(EXIT_FAILURE);
        }

        /* Send remaining command-line arguments as separate
           datagrams, and read responses from server */

        for (int j = 3; j < argc; j++) {
            len = strlen(argv[j]) + 1;
                    /* +1 for terminating null byte */

            if (len > BUF_SIZE) {
                fprintf(stderr,
                        "Ignoring long message in argument %d\n", j);
                continue;
            }

            if (write(sfd, argv[j], len) != len) {
                fprintf(stderr, "partial/failed write\n");
                exit(EXIT_FAILURE);
            }

            nread = read(sfd, buf, BUF_SIZE);
            if (nread == -1) {
                perror("read");
                exit(EXIT_FAILURE);
            }

            printf("Received %zd bytes: %s\n", nread, buf);
        }

        exit(EXIT_SUCCESS);
    }

関連項目
getaddrinfo_a(3), gethostbyname(3), getnameinfo(3), inet(3), gai.conf(5), hostname(7), ip(7)

この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.10 の一部である。プロジェクトの説明とバグ報告に関する情報は https://www.kernel.org/doc/man-pages/ に書かれている。