GETADDRINFO(3) | Linux Programmer's Manual | 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);
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() が返すソケットアドレス集合に対する基準を指定する。
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 構造体の残りのフィールドは以下のように初期化される。
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 に対して動的に割り当てられたメモリーを解放する。
glibc 2.3.4 から、 getaddrinfo() は入出力するホスト名を透過的に国際化ドメイン名 (IDN) 形式 (RFC 3490 の Internationalizing Domain Names in Applications (IDNA) を参照のこと) と変換することを選択的に認めるように拡張されている。 4 つの新しいフラグが定義されている:
getaddrinfo() は成功すると 0 を返し、失敗すると以下の非 0 のエラーコードのいずれかを返す。
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/ に書かれている。
2020-11-01 | GNU |