IPv4、IPv6 の両方の環境で動作するプログラムを書くために、主役となる2つの API があります。 getaddrinfoとgetnameinfoです。 以下に 2つの API の簡単な説明を示しますが、詳細は man(1) などで確認してください。
ノード名、サービス名をアドレス情報に変換するための getaddrinfo(3) です。
この API は gethostbyname(3) および getservbyname(3) と似たような機能を持っており、アドレスファミリから独立した方法で アドレス情報を取得することができます。
int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
アドレス情報をノード名、サービス名に変換するための getnameinfo(3) です。
ちょうど getaddrinfo(3) と逆の処理を受け持ちます。この API は gethostbyaddr(3) および getservbyport(3) と似た機能を持ち、やはり、アドレスファミリから独立した方法でノード名、サービス名を取得することができます。
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags);
/* tes.c */ #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> int main(int argc, char *argv[]) { char *nodename; /* ノード名 */ char *servname; /* サービス名 */ struct addrinfo hints; /* getaddrinfo() への指示用 */ struct addrinfo *res = NULL; /* getaddrinfo() からの返り値を受ける */ struct addrinfo *adrinf; /* getnameinfo() からの返り値を受け取るバッファ */ char hbuf[NI_MAXHOST]; /* ノード名 */ char sbuf[NI_MAXSERV]; /* サービス名 */ int rc; if (argc != 3) { fprintf(stderr, "usage %s nodename servname\n", argv[0]); return -1; } nodename = argv[1]; servname = argv[2]; /* まずアドレス情報を得る */ memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; rc = getaddrinfo(nodename, servname, &hints, &res); if (rc != 0) { fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(rc)); return -1; } /* 得られたアドレス情報すべてに対して処理を行う */ for (adrinf = res; adrinf != NULL; adrinf = adrinf->ai_next) { /* 得られたアドレス情報を文字列表現に戻す */ rc = getnameinfo((struct sockaddr *) adrinf->ai_addr, adrinf->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); /* アドレス形式を得る */ /* 0); */ /* できる限り FQDN を得る */ if (rc != 0) { fprintf(stderr, "getnameinfo(): %s\n", gai_strerror(rc)); continue; } printf("---------------\n"); printf("hbuf:[%s]\n", hbuf); printf("sbuf:[%s]\n", sbuf); } /* アドレス情報を解放する */ if (res != NULL) freeaddrinfo(res); return 0; }
さきほど紹介したコードをみれば自明のことかもしれませんが、上記コードのなかではアドレス情報を格納するために、 struct addrinfo を用いています。
今まで IPv4 で用いていた struct sockaddr_in や、IPv6 のための struct sockaddr_in6 を使う代わりに、struct addrinfo を用いることでアドレスファミリに依存せずに アドレス情報を保持することができるようになります。
struct addrinfo のメンバをみてみましょう(以下は getaddrinfo(3) のマニュアルから引用しました)。
struct addrinfo { int ai_flags; /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */ int ai_family; /* PF_xxx */ int ai_socktype; /* SOCK_xxx */ int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ size_t ai_addrlen; /* length of ai_addr */ char *ai_canonname; /* canonical name for nodename */ struct sockaddr *ai_addr; /* binary address */ struct addrinfo *ai_next; /* next structure in linked list */ };
ai_family というメンバがあります。getaddrinfo(3) でアドレス情報を取得すると、 ai_family には取得したアドレス情報のアドレスファミリが設定されます。そのほかにも、ai_socktype には ソケットタイプ、ai_protocol にはプロトコル、ai_addrlen にはアドレス情報の長さが設定されます。
今までのプログラミングでは socket(2) や bind(2) を呼び出す際に、 AF_INET や IPPROTO_TCP といった定数をハードコーディングしていました。また、アドレス情報の長さも sizeof(struct sockaddr_in) のようにして、直接指定していることが多かったと思います。
しかし、struct addrinfo のメンバにはこれらの API に必要な値が保持されているため、ハードコーディングする必要は なくなります。socket(2) や bind(2) には、getaddrinfo(3) で得た struct addrinfo のメンバの値をそのまま渡すだけで良くなりました。 例えば、以下のようになります。
struct addrinfo *adrinf; int sock, rc; /* アドレス情報を得る */ rc = getaddrinfo(..., &adrinf); ... /* ソケットを作成する */ sock = socket(adrinf->ai_family, adrinf->ai_socktype, adrinf->ai_protocol); ... /* ソケットにアドレス情報を関連付ける */ rc = bind(sock, adrinf->ai_addr, adrinf->ai_addrlen);
最初に紹介した TCP サーバ(sv4.c)の例と比べてみてください。最初の例では socket(2) の引数には AF_INET がハードコーディングされていましたし、bind(2) の引数にも sockaddr_in 型の変数のサイズを sizeof() で直接渡していました。 しかし、上記の方法ならば、定数 AF_INET を書く必要もありませんし、アドレス長も sizeof() で取る必要はありません。 struct addrinfo のメンバの値をそのまま渡すだけで済んでいます。
上で紹介した struct addrinfo と別に、もうひとつ構造体を紹介しておく必要があります。その構造体とは、 struct sockaddr_storage です。
この構造体は、accept(2) や recvfrom(2) を呼び出した際に、 リモートホストのアドレス情報を受け取るために使用できます。この構造体はどんな種類のアドレス情報に対しても十分に 大きいことが保障されていますので、安心してアドレス情報を受け取ることができます。
struct sockaddr_storage で受け取ったアドレス情報は struct addrinfo と同じように getnameinfo(3) に渡すことで、 ノード名、サービス名を得ることができます。例えば、以下のようになります。
struct sockaddr_storage peer; /* クライアントのアドレス情報を格納する */ socklen_t len; /* クライアントのアドレス情報の長さを格納する */ int svsock; int sock; char hbuf[NI_MAXHOST]; /* ノード名 */ char sbuf[NI_MAXSERV]; /* サービス名 */ int rc; /* サーバソケットをセットアップする */ svsock = socket(...); ... /* * クライアントからのコネクト要求を受け入れると共に、 * クライアントのアドレス情報を取得する。 */ len = sizeof(peer); sock = accept(svsock, (struct sockaddr *) &peer, &len); if (sock < 0) { perror("accept()"); ... } /* クライアントのノード名とサービス名を得る */ rc = getnameinfo((struct sockaddr *) &peer, len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 0); ...