#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(3) 関数が作ることのできるのは IPv4 アドレス構造体だけではない。 IPv6 のサポートされているシステムなら IPv6 のソケットアドレス構造体も生成できる。 これらのソケットアドレス構造体は、 クライアントやサーバーでソケットを準備する際に、 bind(2) や connect(2) から直接利用できる。
この関数が用いる addrinfo 構造体には以下のメンバーが含まれている。
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; };
getaddrinfo(3) は、 メモリを動的に割り当てて addrinfo 構造体のリンクリストを作成し、 そのリンクリストへのポインタを res に設定する。 各構造体は ai_next メンバーでリンクされる。 リンクリストの addrinfo 構造体は複数個になることもあり、その理由はいくつかある。 ネットワークホストがマルチホームの場合、 複数のソケットプロトコルで同じサービスが使える (ひとつが SOCK_STREM アドレスで、もうひとつが SOCK_DGRAM アドレスであるなど) 場合、がある。
ai_family, ai_socktype, ai_protocol メンバーは、それぞれ socket(2) システムコールの対応する引き数と同じ意味を持つ。 getaddrinfo(3) 関数は IPv4 と IPv6 のどちらかのアドレスファミリーのソケットアドレスを返す (ai_family が AF_INET または AF_INET6 のどちらかにセットされる)。
hints パラメータには、希望のソケットのタイプやプロトコル を指定する。 hints を NULL にすることは、どんなネットワークアドレスやプロトコルでもよいことを意味する。 このパラメータが NULL でないときは、 addrinfo 構造体へのポインタとなる。 この構造体の ai_familiy, ai_socktype, ai_protocol メンバーで、希望のソケットタイプなどを指定する。 ai_family に AF_UNSPEC を指定すると、任意のプロトコルファミリーを意味する (例えば IPv4 や IPv6)。 ai_socktype や ai_protocol に 0 を指定すると、 同様に任意のソケットタイプやプロトコルを受付けることを意味する。 ai_flags メンバーには追加オプションを指定する (定義は下のとおり)。 複数のフラグを指定するには、それらの論理 OR をとって指定すればよい。 hints パラメータのその他のメンバーには、 0 (またはヌル・ポインタ) を入れなければならない。
node パラメータと service パラメータのどちらかは NULL にしてよい (両方同時 NULL は不可)。 node には数値形式のネットワークアドレス (IPv4 ならドット区切りの 10 進形式、 IPv6 なら 16 進形式) か、あるいはネットワークホスト名を指定する。 後者の場合は、ネットワークアドレスが検索され、名前解決が行なわれる。 hints.ai_flags に AI_NUMERICHOST フラグが含まれている場合は、 node パラメータは数値的なネットワークアドレスでなければならない。 AI_NUMERICHOST フラグを使うと、 時間の掛かる可能性のあるすべてのネットワークホストアドレスの検索が抑制される。
getaddrinfo(3) 関数は addrinfo 構造体のリンクリストを生成するが、 それぞれのネットワークアドレスは hints パラメータで課されたすべての制限に従っている。 hints.ai_flags に AI_CANONNAME フラグが含まれている場合、これらの addrinfo 構造体の最初の ai_canonname フィールドにはホストの公式な名前を指すように設定される。 ai_family, ai_socktype, ai_protocol 各メンバーにはソケット生成時のパラメータが設定される。 ai_addr メンバーにはソケットアドレスへのポインタが書き込まれ、 ai_addrlen メンバーにはソケットアドレスの長さ (バイト単位) が書き込まれる。
node が NULL の場合、各ソケット構造体のネットワークアドレスは AI_PASSIVE フラグに基づいて初期化される (このフラグは hints.ai_flags に設定される)。 AI_PASSIVE フラグがセットされている場合、 各ソケット構造体のネットワークアドレスは設定されないままとなる (訳注: IPv4 の場合 INADDR_ANY に、IPv6 の場合 IN6ADDR_ANY_INIT に等しい)。 これはすべてのネットワークアドレスでクライアント接続を待ち受けるような サーバーアプリケーションで用いられる。 AI_PASSIVE フラグがセットされていない場合、 ネットワークアドレスは loopback インターフェースアドレスに設定される。 これは同じネットワークホスト上で動作しているサーバーに接続するような クライアントアプリケーションで用いられる。
hints.ai_flags が AI_ADDRCONFIG を含む場合、 result が指すリストには、 ローカルシステムに最低一つの IPv4 アドレスが設定されている場合は IPv4 アドレスが返され、 ローカルシステムに最低一つの IPv6 アドレスが設定されている場合は IPv6 アドレスが返される。
hint.ai_flags に AI_V4MAPPED が指定されていて、 hints.ai_family に AF_INET6 が指定され、 マッチする IPv6 アドレスが見つからなかった場合、 result が指すリストには IPv4-mapped IPv6 アドレスが返される。 hints.ai_family に AI_V4MAPPED と AI_ALL の両方が指定されている場合、 result が指すリストには IPv6 アドレスと IPv4-mapped IPv6 アドレスの 両方が返される。 AI_V4MAPPED が指定されていない場合、 AI_ALL は無視される。
service は、各ソケット構造体のネットワークアドレスのポート番号を設定する。 service が NULL の場合、ポート番号は初期化されない。 hints.ai_flags に AI_NUMERICSERV が指定され、 service が NULL でない場合、 service は数値のポート番号を含む文字列を指し示さなければならない。 このフラグは、名前解決サービスが不要であることが分かっている場合に、 サービスの起動を抑制するために用いられる。
freeaddrinfo(3) 関数は、 リンクリスト res に対して動的に割り当てられたメモリを解放する。
glibc 2.3.4 から、 getaddrinfo() は入出力するホスト名を透過的に国際化ドメイン名 (IDN) 形式 (RFC 3490 の Internationalizing Domain Names in Applications (IDNA) を参照のこと) と変換することを選択的に認めるように拡張されている。 4 つの新しいフラグが定義されている:
入力名に非 ASCII 文字が含まれている場合、 IDN 符号化形式が使われる。 非 ASCII 文字が含まれている(ピリオドで区切られる)部分ノード名は、 名前解決機能に渡される前に ASCII 互換符号化形式 (ACE) を使って 符号化される。
AI_CANONIDN 名前が ACE で符号化されている場合、一つまたは複数の名前の構成要素の先頭に xn-- を含んでいる。 これらの構成要素を読み込み可能な形に変換するために、 AI_CANONNAME と共に AI_CANONIDN フラグを渡すことも出来る。 返される文字列は現在のロケールの符号化形式で符号化されている。
gai_strerror(3) 関数を用いると、これらのエラーコードを人間に可読な文字列に変換できるので、 エラー報告に適するだろう。
サーバのプログラム:
#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 %ld bytes from %s:%s\n", (long) 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 %ld bytes: %s\n", (long) nread, buf); } exit(EXIT_SUCCESS); }