gai_strerror(3) ネットワークのアドレスとサービスを変換する

Other Alias

getaddrinfo, freeaddrinfo

書式

#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():

_POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

説明

getaddrinfo() は、(インターネットのホストとサービスを識別する) nodeservice を渡すと、一つ以上の 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 でない場合、 hintsaddrinfo 構造体を指し示し、その構造体のフィールド ai_family, ai_socktype, ai_protocolgetaddrinfo() が返すソケットアドレス集合に対する基準を指定する。

ai_family
このフィールドは返されるアドレスの希望のアドレスファミリーを指定する。 このフィールドに指定できる有効な値としては AF_INETAF_INET6 がある。 また、値 AF_UNSPEC を指定すると、 getaddrinfo() は nodeservice で使用できるいずれかのアドレスファミリー (例えば IPv4 か IPv6) の ソケットアドレスを返すことを求められる。
ai_socktype
このフィールドは推奨のソケット型 (例えば SOCK_STREAMSOCK_DGRAM) を指定する。 このフィールドに 0 を指定すると、任意のソケット型のソケットアドレスを getaddrinfo() が返してよいことを意味する。
ai_protocol
このフィールドは返されるソケットアドレスのプロトコルを指定する。 このフィールドに 0 を指定すると、任意のプロトコルののソケットアドレスを getaddrinfo() が返してよいことを意味する。
ai_flags
このフィールドは、追加のオプション (下記) を指定する。 複数のフラグを指定する際には、それらのビット単位の OR をとって指定する。

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

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

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

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

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

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

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

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

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

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

*
ai_family, ai_socktype, ai_protocol フィールドはソケット生成パラメータを返す (これらのフィールドの意味は socket(2) の同じ名前の引き数と同じである)。 例えば、 ai_familyAF_INETAF_INET6 を返し、 ai_socktypeSOCK_DGRAMSOCK_STREAM を返し、 ai_protocol はそのソケットのプロトコルを返す。
*
ai_addr フィールドにはソケットアドレスへのポインタが書き込まれ、 ai_addrlen フィールドにはソケットアドレスの長さがバイト単位で書き込まれる。

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

hints.ai_flagsAI_V4MAPPED が指定されていて、 hints.ai_familyAF_INET6 が指定され、 マッチする IPv6 アドレスが見つからなかった場合、 res が指すリストには IPv4-mapped IPv6 アドレスが返される。 hints.ai_flagsAI_V4MAPPEDAI_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_flagsAI_CANONNAME が含まれていて、かつ name が NULL であった。
EAI_FAIL
ネームサーバーから恒久的な失敗 (permanent failure) を意味する返事が返された。
EAI_FAMILY
要求されたアドレスファミリーがサポートされていない。
EAI_MEMORY
メモリが足りない。
EAI_NODATA
指定されたネットワークホストは存在するが、 ネットワークアドレスがひとつも定義されていない。
EAI_NONAME
nodeservice のどちらかが不明、または nodeservice の両方が NULL だった場合、または AI_NUMERICSERVhints.ai_flags に指定されていて、 hints.ai_flagsservice が数値のポート番号の文字列でない。
EAI_SERVICE
要求されたサービスは、要求されたソケットタイプでは利用できない。 他のソケットタイプでなら利用可能かもしれない。 このエラーが発生する例としては、 service が "shell" (ストリーム・ソケットでのみ利用できるサービス) で、 hints.ai_protocolIPPROTO_UDP が指定されたり、 hints.ai_socktypeSOCK_DGRAM が指定されたりした場合がある。 また、 service が NULL 以外で、 hints.ai_socktypeSOCK_RAW (サービスの考え方をサポートしていないソケット種別) が指定された場合にも、このエラーが発生する。
EAI_SOCKTYPE
要求されたソケットタイプがサポートされていない。 このエラーが発生する例としては、 hints.ai_socktypehints.ai_protocol が矛盾している場合 (例えば hints.ai_socktypeSOCK_DGRAMhints.ai_protocolIPPROTO_TCP) がある。
EAI_SYSTEM
その他のシステムエラー。詳しくは errno を調べること。

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

ファイル

/etc/gai.conf

準拠

POSIX.1-2001. 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-2001 によると、 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(struct addrinfo));
    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);
    }
    if (rp == NULL) {               /* No address succeeded */
        fprintf(stderr, "Could not bind\n");
        exit(EXIT_FAILURE);
    }
    freeaddrinfo(result);           /* No longer needed */
    /* Read datagrams and echo them back to sender */
    for (;;) {
        peer_addr_len = sizeof(struct sockaddr_storage);
        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, j;
    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(struct addrinfo));
    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);
    }
    if (rp == NULL) {               /* No address succeeded */
        fprintf(stderr, "Could not connect\n");
        exit(EXIT_FAILURE);
    }
    freeaddrinfo(result);           /* No longer needed */
    /* Send remaining command-line arguments as separate
       datagrams, and read responses from server */
    for (j = 3; j < argc; j++) {
        len = strlen(argv[j]) + 1;
                /* +1 for terminating null byte */
        if (len + 1 > 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);
}

この文書について

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