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);
...