アドレスファミリに依存しないソケットプログラミング

IPv4、IPv6 の両方の環境で動作するプログラムを書くために、主役となる2つの API があります。 getaddrinfoとgetnameinfoです。 以下に 2つの API の簡単な説明を示しますが、詳細は man(1) などで確認してください。

getaddrinfo

ノード名、サービス名をアドレス情報に変換するための getaddrinfo(3) です。
この API は gethostbyname(3) および getservbyname(3) と似たような機能を持っており、アドレスファミリから独立した方法で アドレス情報を取得することができます。

int getaddrinfo(const char *nodename, const char *servname,
                const struct addrinfo *hints, struct addrinfo **res);
  • <IN> nodename:
    ノード名を渡します。例えば、"192.168.1.155" や "super-www-sv" などを渡せます。
  • <IN> servname:
    サービス名を渡します。例えば、"21" や "ftp" などを渡せます。
  • <IN> hints:
    取得するアドレス情報の種類などを指定します。
  • <OUT> res:
    取得されたアドレス情報へのポインタが返されます。ポインタが不要になったら、freeaddrinfo(3) で解放しなければなりません。
  • <RETURN>
    成功時は 0 です。失敗時は gai_strerror(3) に返り値を渡してエラー文字列を取得できます。

getnameinfo

アドレス情報をノード名、サービス名に変換するための 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);
  • <IN> sa:
    アドレス情報へのポインタを渡します。
  • <IN> salen:
    アドレス情報の長さを渡します。
  • <OUT> host:
    取得されたノード名を格納するバッファを渡します。
  • <IN> hostlen:
    host のバッファ長を渡します。
  • <OUT> serv:
    取得されたサービス名を格納するバッファを渡します。
  • <IN> servlen:
    serv のバッファ長を渡します。
  • <IN> flags:
    ノード名、サービス名の取得方法などを指定します。
  • <RETURN>
    成功時は 0 です。失敗時は gai_strerror(3) に返り値を渡してエラー文字列を取得できます。

Sample

/* 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;
}

アドレス情報を格納する構造体(addrinfo)

さきほど紹介したコードをみれば自明のことかもしれませんが、上記コードのなかではアドレス情報を格納するために、 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 のメンバの値をそのまま渡すだけで済んでいます。

アドレス情報を格納する構造体(sockaddr_storage)

上で紹介した 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);
      ...

NIC一覧

パケットキャプチャ

bookmark

タグ:

+ タグ編集
  • タグ:
最終更新:2011年04月07日 09:18