本文へジャンプ

IPv6 Socket APIプログラミング

RFC 2553→RFC 3493で規定される基本ソケットAPIプログラミング。C/C++から使用します。

Socket API

UNIX系OSでは、IPv6ネットワークプログラミングAPIであるSocket APを用いてプログラミングを行います。最近の大半のOSではIPv6がサポートされています。


ソケットAPIはIPv4の頃からの拡張としてIPv6対応しているので、従来同様のプログラミングもできます。しかし、本ページでは、参考文献[IPv6ネットワークプログラミング]で推奨しているアドレスファミリ独立な新しいプログラミングを使用します。アドレスファミリ独立なプログラムなら、IPv4とIPv6のデュアルスタックに対応することができます。

やってはいけない旧プログラミング
アドレスファミリ独立なプログラムにおいて、やってはいけないことを列挙します。
  • AF_INET6、AF_INETをコード中で使用
  • gethostbyname2を使用
  • inet_addr、inet_aton、inet_lnaof、inet_makeaddr、inet_netof、inet_network、inet_ntoa、inet_ntop、inet_pton、addr_ntoa、network、getservbyport、gethostbyname、gethostbyaddr、getservbynameの使用
  • struct in_addrおよびin_port_tだけを関数の間で受け渡す、あるいはメモリへ格納する
  • sockaddrのsa_familyメンバーに対するswitch処理
アドレスファミリ独立なソケットプログラミング

バイナリアドレスの格納

IPv4アドレス/IPv6アドレスのバイナリ形式を格納する領域の確保には、構造体struct sockaddr_storageを使用します。
バイナリアドレス情報をポインタで受け渡す際は、型struct sockaddr *を使用します。
Solarisでの定義例
struct sockaddr {
    sa_family_t    sa_family;    // アドレスファミリ
    char           sa_data[14];  // 14バイト以内のプロトコルアドレス
};

struct sockaddr_storage {
    sa_family_t    sa_family;
    char           _ss_pad1[_SS_PAD1SIZE];
    sockaddr_maxalign_t  _ss_align;
    char           _ss_pad2[_SS_PAD2SIZE];
};
 実際にソケットのためのアドレス情報を保持する構造体は、IPv4用が struct sockaddr_in でIPv6用が struct sockaddr_in6 となります。このアドレス構造体を struct sockaddr *にキャストするということは、先頭のアドレスファミリを読むことにあります。つまり、アドレスの情報を受け渡すときは、特定のプロトコル(IPv4とかIPv6とか)に依存しない情報 = struct sockaddr* の型で記述し、受けた側は最初のフィールドであるsa_familyを読み、その値に応じて本来の型にキャストし直す(IPv6なら struct sockaddr_in6 )という使い方となります。
 C++になじみがあれば、アドレスを抽象化した基底クラス sockaddr と、それを派生した具体アドレスクラス sockaddr_in と sockaddr_in6 があり、将来は新たなアドレス型が追加できるようになっていると考えると理解しやすいかもしれません。

 長々と書き連ねましたが、アドレスを扱うときはこのsockaddr系ではなく、次に説明する addrinfo を使うことが多いかもしれません。
 

テキスト表現のアドレス(FQDN名またはアドレス文字列)をアドレス情報(addrinfo)へ変換

関数getaddrinfo を使用します。ホスト名/アドレス文字列をsockaddrを含むアドレス情報に変換します。または、ポート名からポート番号へ変換します。
int getaddrinfo(
    const char* hostname,
    const char* servname,
    const struct addrinfo* hints,
    struct addrinfo** res
);
hostnameかservnameのどちらかはNULLでないことが必要です。hintsの条件にマッチしたaddrinfoのリストがresにセットされます。
  • DNS検索を避けるなら、hints->ai_flagsにAI_NUMERICHOSTを指定します。
  • hostnameにNULLを指定すると、ループバックアドレス(IPv4: 127.0.0.1、IPv6: ::1)となります。
  • hostnameにNULLを指定し、かつAI_PASSIVEを指定すると、ワイルドカードアドレスとなります。
  • スレッドセーフな関数です。
  • getaddrinfoが返却するすべての情報は動的にアロケートされているので、解放する必要があります。リスト構造をたどってfreeaddrinfo関数を使って解放します。
struct addrinfoの構造は以下のようになります。
struct addrinfo {
    int     ai_flags;         // AI.PASSIVE, AI_CANONNAME, AI_NUMERICHOST
    int     ai_family:        // PF_XX
    int     ai_socktype;      // SOCK_XX
    int     ai_protocol;      // IPv4とIPv6に応じて0またはIPPROTO_XX
    size_t  ai_addrlen;       // ai_addr長
    char*   ai_canonname;     // ノード名の正規名
    struct sockaddr* ai_addr; // バイナリアドレス
    struct addrinfo* ai_next; // バイナリアドレスの次の構造体
};
使用するには以下のヘッダーファイルをincludeします。
#include <sys/socket.h>
#include <netdb.h>
サーバ側での使用例
サーバ側で受け入れ(accept)用ソケットを作成する際は、ポート番号を指定し、ワイルドカードアドレスとしてアドレス情報を作成します。
char* port = "12345";      // ポート番号文字列
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;    // TCPソケット作成
hints.ai_flags = AI_PASSIVE;        // ワイルドカードアドレス

struct addrinfo* res0;
int error = getaddrinfo(NULL, port, &hints, &res0);
if (error) {
    fprintf(stderr, "%s: %s\n", port, gai_strerror(error));
    exit(1);
}
gai_strerrorは、getaddrinfo関数のエラー返却値を文字列に変換する関数です。

ソケットの生成

ソケットの生成は、アドレス情報を渡してsocket関数で生成します。
int sockfd = socket(res0->ai_family, res0->ai_socktype, res0->ai_protocol);
if (sockfd <= 0) {
}
getaddrinfoで取得できるアドレス情報は、リスト形式となっており、複数のアドレスが一般的です。その場合、アドレス一つ一つに bind してLISTEN状態とするのが本来です。

マルチキャスト・ソケット・プログラミング
マルチキャストを使ったソケット・プログラミングでは、UDP同様SOCK_DGRAMによる送受信となります。

参考文献

書籍

  • 「IPv6ネットワークプログラミング」(荻野純一郎 著、小川彩子 訳、ASCII出版、2003年2月14日)
    2009.3.29現在日本語唯一のIPv6ソケットプログラミング本です。
  • "UNIX Network Programming Volume 1, 3rd Edition"(W.Richard Stevens, Bill Fenner and Andrew M.Rudoff 著、Addison Wesley、2004)
    IPv4、IPv6プロトコル独立なソケットプログラミングについて詳細に説明している。残念ながら洋書。日本語訳されているのは第2版で今ではかなり古い内容。