ネットワークインタフェース

拡張機能

分散型アプリケーションを構築する場合、通常は、これまでに説明したメカニズムで十分対応できます。この節に挙げた機能は必要に応じて参照してください。

帯域外データ

ストリームソケットの抽象化には、帯域外データが含まれます。帯域外データは、接続されたストリームソケットペア間の論理的に独立した伝送チャネルです。帯域外データは、通常のデータとは無関係に配信されます。帯域外データ機能が使用される場合、一度に 1 つ以上の帯域外メッセージが確実に配信されなければなりません。このメッセージには 1 バイト以上のデータを含むことができ、いつでも 1 つ以上のメッセージの配信を保留できます。

帯域内シグナリングだけをサポートする (つまり、緊急のデータが通常のデータと共に順序どおり配信される) 通信プロトコルでは、メッセージは通常のデータストリームから抽出され、個別に格納されます。これにより、ユーザーは間に入るデータをバッファリングする必要にせまられることなく、緊急データを順番に受信するか順不同で受信するかを選択できます。

帯域外データは、MSG_PEEK を使用して先読みできます。ソケットにプロセスグループがある場合は、その存在がプロトコルに通知される時に SIGURG シグナルが生成されます。SIGIO に関する 「割り込み方式のソケット入出力」で説明しているように、プロセスは適切な fcntl(2) 呼び出しを使用して、プロセスグループまたはプロセス ID が SIGURG によって通知されるように設定できます。複数のソケットに配信待ちの帯域外データがある場合は、select(3C) 呼び出しを使用してそのようなデータ保留のあるソケットを確認できます。

帯域外データが送信された位置のデータストリームには、論理マークが置かれます。リモートログインアプリケーションとリモートシェルアプリケーションは、この機能を使用してクライアントプロセスとサーバープロセス間にシグナルを伝えます。シグナルが受信された時点で、データストリームのそのマークまでのデータがすべて破棄されます。

帯域外メッセージを送信するには、MSG_OOB フラグを send(3SOCKET) または sendto(3SOCKET) に適用します。帯域外メッセージを受信するには、順に取得する場合を除き MSG_OOBrecvfrom(3SOCKET) または recv(3SOCKET) に指定します (順に取得する場合は、MSG_OOB フラグは不要)。SIOCATMARK ioctl(2) は、読み取りポインタが現在、データストリーム内のマークを指しているかどうかを示します。

int yes;
ioctl(s, SIOCATMARK, &yes);

戻り時に yes1 の場合、次の読み取りはマークのあとのデータを返します。yes が 1 でない場合は、帯域外データが到着したと仮定して、次の読み取りは帯域外シグナルを送信する前にクライアントによって送信されたデータを提供します。割り込みまたは停止のシグナルを受ける場合に出力をフラッシュするリモートログインプロセス内のルーチンを、例 2-12 に示します。このコードは、マークまでの通常のデータを (これを破棄するために) 読み取り、続いて帯域外バイトを読み取ります。

プロセスは、初めにマークまでを読み取らずに、帯域外データの読み取りまたは先読みを行うことができます。これは、プロトコルが緊急の帯域内データを通常のデータと共に配信し、前もってその存在の通知だけを行う場合には困難になります (例: インターネットファミリ内のソケットストリームの提供に使用されるプロトコル、TCP)。このようなプロトコルでは、MSG_OOB フラグによって recv(3SOCKET) が行われた時点で帯域外バイトが到着していないことがあります。このような場合、呼び出しはエラー EWOULDBLOCK を返します。また、入力バッファー内に十分な帯域内データが存在し、バッファーが空になるまでは通常のフロー制御がピアによる緊急データの送信を防止する場合もあります。この場合プロセスは、待ち行列に入ったデータを十分に読み取ってからでないと緊急データを配信できません。


例 2-12 帯域外データの受信時における端末入出力のフラッシュ

#include <sys/ioctl.h>
#include <sys/file.h>
...
oob()
{
		int out = FWRITE;
		char waste[BUFSIZ];
		int mark = 0;
 
		/* ローカル端末出力をフラッシュする */
		ioctl(1, TIOCFLUSH, (char *) &out);
		while(1) {
			if (ioctl(rem, SIOCATMARK, &mark) == -1) {
				perror("ioctl");
				break;
			}
			if (mark)
				break;
			(void) read(rem, waste, sizeof waste);
		}
		if (recv(rem, &mark, 1, MSG_OOB) == -1) {
			perror("recv");
			...
		}
		...
}

ソケットストリーム内の緊急のインラインデータの位置を保持する機能もあります。この機能は、ソケットレベルのオプション SO_OOBINLINE として提供されています。使用方法については、getsockopt(3SOCKET) のマニュアルページを参照してください。このオプションを使用すると緊急データの位置 (マーク) は保持されますが、緊急データは MSG_OOB フラグなしで返される通常のデータストリーム内のマークの直後に続きます。複数の緊急指示を受信するとマークは移動しますが、帯域外データが消失することはありません。

非ブロックソケット

一部のアプリケーションは、ブロックしないソケットを必要とします。たとえば、すぐに完了できず (完了を待っている間に) プロセスを一時的に中断させてしまう要求は実行されません。エラーコードが返されます。ソケットが作成され、別のソケットに対する接続が確立されたあと、例 2-13 に示すように fcntl(2) 呼び出しを発行してこのソケットを非ブロックに設定できます。


例 2-13 非ブロックソケットの設定

#include <fcntl.h>
#include <sys/file.h>
...
int fileflags;
int s;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
...
if (fileflags = fcntl(s, F_GETFL, 0) == -1)
		perror("fcntl F_GETFL");
		exit(1);
}
if (fcntl(s, F_SETFL, fileflags | FNDELAY) == -1)
		perror("fcntl F_SETFL, FNDELAY");
		exit(1);
}
...

非ブロックソケットで入出力を行う場合は、オペレーションが正常にブロックする場合に発生する、errno.h 内のエラー EWOULDBLOCK をチェックしてください。accept(3SOCKET)connect(3SOCKET)send(3SOCKET)recv(3SOCKET)read(2)、および write(2) はすべて、EWOULDBLOCK を返すことができます。send(3SOCKET) のようなオペレーションを完全には実行できないが、ストリームソケットを使用する場合などに部分的な書き込みは可能という場合には、すぐに送信できるデータは処理され、実際に送信された量が関数の戻り値となります。

非同期ソケット入出力

複数の要求を同時に処理するアプリケーションでは、プロセス間の非同期通信を必要とします。非同期ソケットは、SOCK_STREAM タイプでなければなりません。ソケットを非同期にするには、例 2-14 に示すように fcntl(2) 呼び出しを発行します。


例 2-14 ソケットを非同期にする

#include <fcntl.h>
#include <sys/file.h>
...
int fileflags;
int s;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
...
if (fileflags = fcntl(s, F_GETFL ) == -1)
		perror("fcntl F_GETFL");
		exit(1);
}
if (fcntl(s, F_SETFL, fileflags | FNDELAY | FASYNC) == -1)
		perror("fcntl F_SETFL, FNDELAY | FASYNC");
		exit(1);
}
...

ソケットの初期化と接続が終わり、非同期に設定されると、ファイルを非同期で読み書きする場合のように通信が行われます。send(3SOCKET)write(2)recv(3SOCKET)read(2) は、データ転送を開始します。次の節で説明しているように、データ転送はシグナル方式の入出力ルーチンによって行われます。

割り込み方式のソケット入出力

SIGIO シグナルは、ソケット (実際には任意のファイル記述子) がデータ転送を終了した時点をプロセスに通知します。SIGIO を使用する手順は次のとおりです。

例 2-15 は、ソケットで要求が保留される場合にその保留要求に関する情報を特定のプロセスが受信する例を示しています。SIGURG のハンドラを追加すると、このコードは SIGURG シグナルを受信する目的でも使用できます。


例 2-15 入出力要求の非同期通知

#include <fcntl.h>
#include <sys/file.h>
 ...
signal(SIGIO, io_handler);
/* SIGIO または SIGURG シグナルを受信するプロセスを s に設定する。*/
if (fcntl(s, F_SETOWN, getpid()) < 0) {
		perror("fcntl F_SETOWN");
		exit(1);
}

シグナルとプロセスグループ ID

SIGURGSIGIO の場合、各ソケットにはプロセス番号とプロセスグループ ID があります。これらの値はゼロに初期化されますが、先の例で示しているように、F_SETOWN fcntl(2) を使用してあとから定義し直すことができます。fcntl(2) の 3 番目の引数が正の場合、ソケットのプロセス ID を設定します。fcntl(2) の 3 番目の引数が負の場合、ソケットのプロセスグループ ID を設定します。SIGURG シグナルと SIGIO シグナルの受信側として許可されるのは、呼び出し側のプロセスだけです。同様に、fcntl(2)F_GETOWN は、ソケットのプロセス番号を返します。

SIGURGSIGIO の受信は、ioctl(2) を使用してソケットをユーザーのプロセスグループに割り当てることによっても有効にできます。

/* oobdata はルーチンを処理する帯域外データ */
sigset(SIGURG, oobdata);
int pid = -getpid();
if (ioctl(client, SIOCSPGRP, (char *) &pid) < 0) {
		perror("ioctl: SIOCSPGRP");
}

サーバープロセスで便利なシグナルとして、ほかに SIGCHLD が挙げられます。このシグナルは、任意の子プロセスがその状態を変更した場合にプロセスに配信されます。通常、サーバーはこのシグナルを使用して、明示的に終了を待機せずに、あるいは終了ステータスを周期的にポーリングせずに、終了した子プロセスの「リープ (刈り取り)」を行います。たとえば、先に示したリモートログインサーバーループは、例 2-16 に示すように拡大できます。


例 2-16 SIGCHLD シグナル

int reaper();
...
sigset(SIGCHLD, reaper);
listen(f, 5);
while (1) {
		int g, len = sizeof from;
		g = accept(f, (struct sockaddr *) &from, &len);
		if (g < 0) {
			if (errno != EINTR)
				syslog(LOG_ERR, "rlogind: accept: %m");
			continue;
		}
		...
}
 
#include <wait.h>
 
reaper()
{
		int options;
		int error;
		siginfo_t info;
 
		options = WNOHANG | WEXITED;
		bzero((char *) &info, sizeof(info));
		error = waitid(P_ALL, 0, &info, options);
}

親サーバープロセスがその子のリープに失敗する場合、ゾンビプロセスが生じます。

特定のプロトコルの選択

socket(3SOCKET) 呼び出しの 3 番目の引数が 0 の場合、socket(3SOCKET) は要求されたタイプである戻されたソケットにデフォルトプロトコルを使用することを選択します。通常はデフォルトプロトコルで十分であり、ほかの選択肢はありません。raw ソケットを使用して低レベルのプロトコルやハードウェアインタフェースと直接通信を行う場合は、必要に応じてプロトコル引数で非多重化を設定してください。たとえば、インターネットファミリ内の raw ソケットを使用して IP 上に新しいプロトコルを実装できます。この場合、ソケットは指定されたプロトコルのパケットだけを受信します。特定のプロトコルを取得するには、プロトコルファミリで定義されているようにプロトコル番号を決定します。インターネットファミリの場合、「標準のルーチン」で説明しているライブラリルーチンの 1 つ (getprotobyname(3SOCKET) など) を使用してください。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
 ...
pp = getprotobyname("newtcp");
s = socket(AF_INET6, SOCK_STREAM, pp->p_proto);

このコードでは、ソケット s はストリームベースの接続を使用しますが、プロトコルタイプはデフォルトの tcp ではなく newtcp を使用します。

アドレスのバインド

TCP と UDP は、ローカル IP アドレスローカルポート番号外部 IP アドレス、 および外部ポート番号の 4 つを使用してアドレス指定を行います。TCP では、これらの 4 つの要素は一意でなければなりません。UDP にはこのような要求はありません。ホストは複数のネットワークに常駐でき、ユーザーは割り当てられているポート番号に直接アクセスできないため、ユーザープログラムがローカルアドレスとローカルポートに使用する適切な値を常に認識できるとは限りません。この問題を避けるため、アドレスの一部を指定せずにおき、必要時にシステムにこれらの部分を適切に割り当てることができます。これらの要素の各部は、ソケット API のさまざまな部分によって指定できます。

bind(3SOCKET)

ローカルアドレスまたはローカルポート (あるいはこの両方)

connect(3SOCKET)

外部アドレスと外部ポート

accept(3SOCKET) 呼び出しは、外部クライアントからの接続情報を検出します。そのため、この呼び出しによってローカルアドレスとローカルポートがシステムに指定され (accept(3SOCKET) の呼び出し側が何も指定しなくても)、外部アドレスと外部ポートが返されます。

listen(3SOCKET) を呼び出すと、ローカルポートが選択されます。ローカル情報を割り当てるために明示的な bind(3SOCKET) がなされていない場合に listen(3SOCKET) を呼び出すと、一時的なポート番号が割り当てられます。

特定のポートに常駐するが、どのローカルアドレスが選択されてもかまわないというサービスは、それ自体をそのポートに bind(3SOCKET) し、ローカルアドレスを指定しないままにしておくことができます。このためには、<netinet/in.h> 内の定数値を持つ変数 in6addr_any に設定します。ローカルポートを固定する必要がない場合、listen(3SOCKET) を呼び出すと、ポートが選択されます。アドレス in6addr_any またはポート番号 0 を指定することを、ワイルドカードの使用と呼びます (AF_INET の場合、in6addr_any ではなく INADDR_ANY を使用する)。

ワイルドカードアドレスは、インターネットファミリにおけるローカルアドレスのバインドを簡易化します。次のコード例は、特定のポート番号 MYPORT をソケットにバインドし、ローカルアドレスは指定しないままにします。


例 2-17 ソケットにポート番号をバインドする

#include <sys/types.h>
#include <netinet/in.h>
...
struct sockaddr_in6 sin;
...
		s = socket(AF_INET6, SOCK_STREAM, 0);
		bzero (&sin6, sizeof (sin6));
		sin.sin6_family = AF_INET6;
		sin.sin6_addr.s6_addr = in6addr_any;
		sin.sin6_port = htons(MYPORT);
		bind(s, (struct sockaddr *) &sin, sizeof sin);

ホスト上の各ネットワークインタフェースは、通常、一意の IP アドレスを持ちます。ワイルドカードローカルアドレスを持つソケットは、指定されたポート番号にあてたメッセージと、ホストに割り当てられた可能性のあるアドレスに送信されたメッセージを受信できます。たとえば、ホストにアドレス 128.32.0.4 と 10.0.0.78 を持つ 2 つのインタフェースがあり、ソケットが例 2-17 のようにバインドされている場合、プロセスは 128.32.0.4 または 10.0.0.78 にアドレス指定された接続要求を受け入れることができます。特定のネットワーク上のホストだけに接続を許可するには、サーバーは適切なネットワーク上のインタフェースのアドレスをバインドします。

同様に、ローカルポート番号は指定しないままにしておくことができます (0 を指定)。この場合、システムがポート番号を選択します。次に、特定のローカルアドレスをソケットにバインドし、ローカルポート番号を指定しないままにする例を示します。

bzero (&sin, sizeof (sin));
(void) inet_pton (AF_INET6, ":ffff:127.0.0.1", sin.sin6_addr.s6_addr);
sin.sin6_family = AF_INET6;
sin.sin6_port = htons(0);
bind(s, (struct sockaddr *) &sin, sizeof sin);

システムは、次の 2 つの基準でローカルポート番号を選択します。

クライアントのポート番号と IP アドレスは、accept(3SOCKET) (from の結果) または getpeername(3SOCKET) で見つけます。

ポート番号を選択するためにシステムが使用するアルゴリズムがアプリケーションに適さない場合もあります。これは、関連付けが 2 段階のプロセスで作成されるためです。たとえば、インターネットファイル転送プロトコルでは、データ接続は常に同じローカルポートから発生しなければならないと定めています。しかし、異なる外部ポートに接続することによって、関連付けの重複が避けられます。この場合、前のデータ接続のソケットが存在していると、システムは同じローカルアドレスとポート番号をソケットにバインドすることを許可しません。デフォルトのポート選択アルゴリズムを無効にするには、次に示すようにアドレスをバインドする前にオプション呼び出しを行う必要があります。

 ...
int on = 1;
...
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);
bind(s, (struct sockaddr *) &sin, sizeof sin);

この呼び出しを行うと、すでに使用されているローカルアドレスをバインドできます。同じローカルアドレスとローカルポートを持つほかのソケットが同じ外部アドレスと外部ポートを持たないことをシステムが接続時に検証するため、この呼び出しは一意性という条件に違反することはありません。関連付けがすでに存在する場合、エラー EADDRINUSE が返されます。

マルチキャストの使用

IP マルチキャストは、タイプが SOCK_DGRAM または SOCK_RAW であるソケット AF_INET6 または AF_INET およびインタフェースドライバがマルチキャストをサポートするサブネットワーク上でだけサポートされます。

IPv4 マルチキャストデータグラムの送信

マルチキャストデータグラムを送信するには、sendto(3SOCKET) 呼び出しで宛先アドレスとして 224.0.0.0 〜 239.255.255.255 の範囲の IP マルチキャストアドレスを指定します。

デフォルトでは、IP マルチキャストデータグラムは存続期間 (TTL) 1 で送信されます。この場合、単一のサブネットワーク外にデータグラムが転送されることはありません。ソケットオプション IP_MULTICAST_TTL を指定すると、後続のマルチキャストデータグラムの TTL を 0 〜 255 の範囲の任意の値に設定してマルチキャストの配信範囲を制御できます。

    u_char ttl;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,sizeof(ttl))

TTL 0 のマルチキャストデータグラムはいかなるサブネット上でも伝送されませんが、送信ホストが宛先グループに属しており、かつ送信側ソケットでマルチキャストループバックが無効になっていない場合は、ローカルに配信できます (下記を参照)。最初の配信先となるサブネットに 1 つ以上のマルチキャストルーターが接続されている場合は、1 を超える TTL を持つマルチキャストデータグラムを複数のサブネットに配信できます。意味のある配信範囲制御を提供するため、マルチキャストルーターは TTL に「しきい値」の概念をサポートします。この概念は、一定の TTL 未満のデータグラムが特定のサブネットを越えることを防止します。マルチキャストデータグラムの初期 TTL と、それらに対してしきい値が強制する規則を次に示します。

同じホストに制限される 

同じサブネットに制限される 

32 

同じサイトに制限される 

64 

同じ地域に制限される 

128 

同じ大陸に制限される 

255 

配信範囲内で制限されない 

「サイト」と「地域」は厳密には定義されません。サイトは、ローカルな事柄として、小さな管理単位にさらに分割できます。

アプリケーションは、上記の TTL 以外に初期 TTL を選択できます。たとえば、アプリケーションは、TTL シーケンス 0、1、2、4、8、16、32 を使用し、TTL 0 から開始して応答が得られるまでより大きな TTL のマルチキャスト照会を送ることによってネットワークリソースの「拡張リング検索」が行えます。

マルチキャストルーターは、224.0.0.0 〜 224.0.0.255 の範囲の宛先アドレスを持つマルチキャストデータグラムの転送を、TTL の値にかかわらず拒否します。この範囲のアドレスは、経路指定プロトコルとその他の低レベルトポロジの発見または保守プロトコル (ゲートウェイ発見、グループメンバーシップ報告など) の使用に予約されています。

ホストにマルチキャスト機能を持つ複数のインタフェースがある場合でも、各マルチキャスト伝送は単一のネットワークインタフェースから送信されます (ホストがマルチキャストルーターでもあり、TTL が 1 を超える場合には、発信インタフェース以外のインタフェースにマルチキャストを転送できる)。一定のソケットからの後続の伝送については、ソケットオプションを使用してデフォルトを無効にできます。

    struct in_addr addr;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr))
addr は、使用する発信インタフェースのローカル IP アドレスです。デフォルトインタフェースに戻すには、アドレス INADDR_ANY を指定します。インタフェースのローカル IP アドレスは、SIOCGIFCONF ioctl を使用して取得します。インタフェースがマルチキャストをサポートするかどうかを確認するには、SIOCGIFFLAGS ioctl を使用してインタフェースフラグを取り出し、IFF_MULTICAST フラグが設定されているかどうかをテストしてください。このオプションは、インターネットトポロジと明確な関係があるマルチキャストルーターなどのシステムサービスを主な対象としています。

(発信インタフェース上で) 送信ホスト自体が属しているグループにマルチキャストデータグラムが送信されると、ローカル配信の IP 層によってデータグラムのコピーがデフォルトでループバックされます。後続のデータグラムがループバックされるかどうかにかかわらず、別のソケットオプションが送信側に明示的な制御を与えます。

    u_char loop;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop))  
このコードの loop の値は、ループバックを無効にする場合は 0、ループバックを有効にする場合は 1 です。単一のホストにインスタンスを 1 つしか持たないアプリケーション (ルーターやメールデーモンなど) では、このオプションを使用するとアプリケーション自体の伝送を受信するオーバーヘッドが排除されるため、パフォーマンスが向上します。このオプションは、単一のホストに複数のインスタンスを持つアプリケーション (会議システムプログラムなど) や送信側が宛先グループに属さないアプリケーション (時間照会プログラムなど) には通常使用しないでください。

送信ホストが別のインタフェースの宛先グループに属している場合、1 を超える TTL で送信されたマルチキャストデータグラムは、他方のインタフェース上の送信ホストに配信できます。このような配信には、ループバック制御オプションは何の効果もありません。

IPv4 マルチキャストデータグラムの受信

ホストは、IP マルチキャストデータグラムを受信する前に、1 つ以上の IP マルチキャストグループのメンバーになる必要があります。プロセスは、次のソケットオプションを使用して、マルチキャストグループに加わるようにホストに求めることができます。

    struct ip_mreq mreq;
    setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) 
mreq は次の構造体です。
    struct ip_mreq {
        struct in_addr imr_multiaddr;   /* 加わるマルチキャストグループ */
        struct in_addr imr_interface;   /* 加わるインタフェース */
    }  
各メンバーシップは単一のインタフェースに関連付けられ、複数のインタフェース上の同じグループに加わることができます。imr_interfacein6addr_any になるように 指定してデフォルトのマルチキャストインタフェースを選択するか、あるいはホストのローカルアドレスの 1 つを指定して特定の (マルチキャスト機能を持った) インタフェースを選択してください。

メンバーシップを取り消すには、次のコードを使用します。

    struct ip_mreq mreq;
    setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq))  
mreq には、メンバーシップの追加に使用したのと同じ値が入ります。ソケットが閉じられるか、あるいはソケットを保持しているプロセスが停止される時には、ソケットに関連付けられたメンバーシップも取り消されます。特定のグループ内で複数のソケットがメンバーシップを要求でき、ホストは最後の要求が取り消されるまでそのグループのメンバーにとどまります。

ソケットのどれかがデータグラムの宛先グループ内でメンバーシップを要求した場合、カーネル IP 層が受信マルチキャストパケットを受け入れます。特定のソケットに対するマルチキャストデータグラムの配信は、単一キャストデータグラムの場合と同様に、宛先ポートと、ソケットに関連付けられたメンバーシップ (raw ソケットの場合はプロトコルタイプ) に基づいています。特定のポートに送信されたマルチキャストデータグラムを受信するには、ローカルアドレスを未指定のまま (INADDR_ANY などを指定) ローカルポートにバインドします。

次の指定が bind(3SOCKET) の前にあると、複数のプロセスを同じ SOCK_DGRAM UDP ポートにバインドできます。

    int one = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))  
この場合、共有ポートに向けられた各受信マルチキャストまたは受信ブロードキャスト UDP データグラムは、そのポートにバインドされているすべてのソケットに配信されます。下位互換性の理由から、これは単一キャストの受信データグラムには適用されません。データグラムの宛先ポートにバインドされているソケットの数にかかわらず、単一キャストデータグラムが複数のソケットに配信されることはありません。SOCK_RAW ソケットは、SO_REUSEADDR オプションがなくても単一の IP プロトコルタイプを共有できます。

マルチキャストに関連する新しいソケットオプションの説明は、<netinet/in.h> を参照してください。IP アドレスはすべて、ネットワークバイトオーダーで渡されます。

IPv6 マルチキャストデータグラムの送信

マルチキャストデータグラムを送信するには、sendto(3SOCKET) 呼び出しで宛先アドレスとして ff00::0/8 の範囲の IP マルチキャストアドレスを指定します。

デフォルトでは、IP マルチキャストデータグラムはホップ制限 1 で送信されます。この場合、単一のサブネットワーク外にデータグラムが転送されることはありません。ソケットオプション IPV6_MULTICAST_HOPS を指定すると、後続のマルチキャストデータグラムのホップ制限を 0 から 255 の範囲の任意の値に設定してマルチキャストの配信範囲を制御できます。

    uint_l;
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,sizeof(hops))

ホップ制限 0 のマルチキャストデータグラムはどのサブネットにも伝送されませんが、送信ホストが宛先グループに属しており、送信側ソケットでマルチキャストループバックが無効になっていない場合は、ローカルに配信できます (次を参照)。最初の配信先となるサブネットに 1 つ以上のマルチキャストルーターが接続されている場合は、1 を超えるホップ制限を持つデータグラムを複数のサブネットに配信できます。IPv4 マルチキャストアドレスと異なり、IPv6 マルチキャストアドレスには、アドレスの最初の部分にコード化された明示的な配信範囲情報が含まれます。定義されている配信範囲を次に示します (X は未指定)。

ffX1::0/16

ノード - ローカルな配信範囲 - 同じノードに制限される

ffX2::0/16

リンク - ローカルな配信範囲

ffX5::0/16

サイト - ローカルな配信範囲

ffX8::0/16

組織 - ローカルな配信範囲

ffXe::0/16

全世界的な配信範囲

アプリケーションは、マルチキャストアドレスの配信範囲とは個別に、異なるホップ制限値を使用できます。たとえば、アプリケーションは、ホップ制限シーケンス 0、1、2、4、8、16、32 を使用し、ホップ制限 0 から開始して応答が受信されるまでより大きなホップ制限のマルチキャスト照会を送信することにより、ネットワークリソースの「拡張リング検索」を実行する場合があります。

ホストにマルチキャスト機能を持つ複数のインタフェースがある場合でも、各マルチキャスト伝送は単一のネットワークインタフェースから送信されます (ホストがマルチキャストルーターでもあり、ホップ制限 が 1 を超える場合には、発信インタフェース以外のインタフェースにマルチキャストを転送できる)。一定のソケットからの後続の伝送については、ソケットオプションを使用してデフォルトを無効にできます。

    uint_t ifindex;

    ifindex = if_nametoindex )"hme3");
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex))
ifindex は、希望する発信インタフェースのインタフェースインデックスです。デフォルトインタフェースに戻すには、値 0 を指定します。

(発信インタフェース上で) 送信ホスト自体が属しているグループにマルチキャストデータグラムが送信されると、ローカル配信の IP 層によってデータグラムのコピーがデフォルトでループバックされます。後続のデータグラムがループバックされるかどうかにかかわらず、別のソケットオプションが送信側に明示的な制御を与えます。

    uint_t loop;
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop))  
loop の値は、ループバックを無効にする場合は 0、ループバックを有効にする場合は 1 です。単一のホストにインスタンスを 1 つしか持たないアプリケーション (ルーターやメールデーモンなど) では、このオプションを使用するとアプリケーション自体の伝送を受信するオーバーヘッドが排除されるため、パフォーマンスが向上します。このオプションは、単一のホストに複数のインスタンスを持つアプリケーション (会議システムプログラムなど) や送信側が宛先グループに属さないアプリケーション (時間照会プログラムなど) に通常は使用しないでください。

送信ホストが別のインタフェースの宛先グループに属している場合、1 以上のホップ制限で送信されたマルチキャストデータグラムは、他方のインタフェース上の送信ホストに配信できます。このような配信には、ループバック制御オプションは何の効果もありません。

IPv6 マルチキャストデータグラムの受信

ホストは、IP マルチキャストデータグラムを受信する前に、1 つ以上の IP マルチキャストグループのメンバーになる必要があります。プロセスは、次のソケットオプションを使用して、マルチキャストグループに加わるようにホストに求めることができます。

    struct ipv6_mreq mreq;
    setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) 
mreq は次の構造体です。
    struct ipv6_mreq {
        struct in6_addr ipv6mr_multiaddr;   /* IPv6 マルチキャストアドレス */
        unsigned int    ipv6mr_interface;   /* インタフェースインデックス */
    }  
各メンバーシップは単一のインタフェースに関連付けられ、複数のインタフェース上の同じグループに加わることができます。ipv6_interface0 になるように指定してデフォルトのマルチキャストインタフェースを選択するか、あるいはホストのインタフェースの 1 つのインタフェースインデックスを指定してその (マルチキャスト機能を持った) インタフェースを選択してください。

グループから抜けるには、次のコードを使用します。

    struct ipv6_mreq mreq;
    setsockopt(sock, IPPROTO_IPV6, IP_LEAVE_GROUP, &mreq, sizeof(mreq))  
mreq には、メンバーシップの追加に使用した同じ値が入ります。ソケットが閉じられるか、あるいはソケットを保持しているプロセスが停止する時には、ソケットに関連付けられたメンバーシップも取り消されます。特定のグループ内で複数のソケットがメンバーシップを要求でき、ホストは最後の要求が取り消されるまでそのグループのメンバーにとどまります。

ソケットのどれかがデータグラムの宛先グループ内でメンバーシップを要求した場合、カーネル IP 層が受信マルチキャストパケットを受け入れます。特定のソケットに対するマルチキャストデータグラムの配信は、単一キャストデータグラムの場合と同様に、宛先ポートと、ソケットに関連付けられたメンバーシップ (raw ソケットの場合はプロトコルタイプ) に基づいています。特定のポートに送信されたマルチキャストデータグラムを受信するには、ローカルアドレスを未指定のまま (INADDR_ANY などに指定) ローカルポートにバインドします。

次の指定が bind(3SOCKET) の前にあると、複数のプロセスを同じ SOCK_DGRAM UDP ポートにバインドできます。

    int one = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))  
この場合、共有ポートに向けられた各受信マルチキャスト UDP データグラムは、そのポートにバインドされているすべてのソケットに配信されます。下位互換性の理由から、これは単一キャストの受信データグラムには適用されません。データグラムの宛先ポートにバインドされているソケットの数にかかわらず、単一キャストデータグラムが複数のソケットに配信されることはありません。SOCK_RAW ソケットは、SO_REUSEADDR オプションがなくても単一の IP プロトコルタイプを共有できます。

マルチキャストに関連する新しいソケットオプションの説明は、<netinet/in.h> を参照してください。IP アドレスはすべて、ネットワークバイトオーダーで渡されます。

ゼロコピーとチェックサム負荷解除

SunOS 5.6 以降では、TCP/IP プロトコルスタックは、ゼロコピーと TCP チェックサム負荷解除という 2 つの新しい機能をサポートするように機能が拡張されています。


注 -

ゼロコピーとチェックサム負荷解除は互いに機能的には依存していませんが、最大のパフォーマンスを得るには連携して動作する必要があります。チェックサム負荷解除はネットワークインタフェースからのハードウェアサポートを必要とし、このハードウェアサポートがないとゼロコピーは有効になりません。


ゼロコピーを実行するためには VM ページの再マッピングを適用する前に、アプリケーションがページ型のバッファーを供給する必要があります。負荷が高い書き込み時コピーの失敗を避けるには、アプリケーションは伝送側に大きな循環バッファーを使用する必要があります。一般的なバッファー割り当ては 16 の 8K バッファーです。

ソケットオプション

送信バッファー領域または受信バッファー領域の変更などにより、setsockopt(3SOCKET)getsockopt(3SOCKET) を使用してソケットオプションのいくつかを設定、取得できます。呼び出しの一般的な書式を次に示します。

setsockopt(s, level, optname, optval, optlen);
および
getsockopt(s, level, optname, optval, optlen);

場合によっては (バッファーサイズを設定する場合など)、これらの指定がオペレーティングシステムに対する参考情報にしかならない場合があります。値を適切に調整する権限はオペレーティングシステムに与えられています。

表 2-4 に、これらの呼び出しの引数を示します。

表 2-4 setsockopt(3SOCKET)getsockopt(3SOCKET) の引数

引数 

説明 

s

オプションの適用先であるソケット 

level

sys/socket.h 内の 記号定数 SOL_SOCKET によって示されるプロトコルレベル (ソケットレベルなど) を指定する

optname

オプションを指定する、sys/socket.h で定義されている記号定数

optval

オプションの値を示す 

optlen

オプションの値の長さを示す 

getsockopt(3SOCKET) の場合、optlen は、初期状態では optval によって示される記憶領域のサイズに設定され、最終的に使用された記憶領域の長さに設定されます。

既存ソケットのタイプ (ストリームやデータグラムなど) を決定すると便利な場合があります。inetd(1M) によって呼び出されるプログラムは、SO_TYPE ソケットオプションと getsockopt(3SOCKET) 呼び出しを使用してこの決定を行うことができます。

#include <sys/types.h>
#include <sys/socket.h>
 
int type, size;
 
size = sizeof (int);
if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &type, &size) <0) {
 	...
}

getsockopt(3SOCKET) のあと、sys/socket.h で定義しているように、type は ソケットタイプの値に設定されます。データグラムソケットの場合、typeSOCK_DGRAM です。

inetd(1M) デーモン

システムに装備されているデーモンの 1 つが inetd(1M) です。システムの起動時に呼び出され、 /etc/inet/inetd.conf ファイルを読み、その内容に従いサービスを取得します。デーモンは /etc/inet/inetd.conf ファイルに記述されている各サービスごとに 1 つのソケットを作成し、各ソケットに適切なポート番号の割り当てを行います。inetd(1M) に関するより詳しい情報は、マニュアルページを参照してください。

inetd(1M) は各ソケットのポーリングを行い、そのソケットに対するサービスの接続要求待ちを行います。SOCK_STREAM タイプのソケットの場合、inetd(1M) は待機ソケットに対し accept(3SOCKET) を実行、新しいファイル記述子 0 および 1 (stdin および stdout) において、新しいソケットに対して fork(2)dup(2) を実行し、その他の開かれているファイル記述子を終了させ適切なサーバーに対し exec(2) を実行します。

inetd(1M) の主な利点は、使用されていないサービスがシステム資源を使用しない点にあります。また、接続の確立に関する処理の大部分を inetd(1M) が請け負う点も大きな利点の 1 つです。inetd(1M) によって開始されたサーバーのソケットは、ファイル記述子 0 および 1 においてクライアントに接続され、ただちに read(2)write(2)send(3SOCKET)、または recv(3SOCKET) の実行が可能です。サーバーは stdio の取り決めに従い、必要な場合に fflush(3C) を利用する限りバッファー入出力の使用が可能です。

getpeername(3SOCKET) はソケットに接続されたピア (処理) のアドレスを戻り値として返します。これは inetd(1M) で開始されたサーバーにおいて、有効な手段です。たとえば、インターネットアドレス (たとえば、クライアントの IPv6 アドレスとしては定型的なアドレス fec0::56:a00:20ff:fe7d:3dd2 など) のログを記録する場合、inetd(1M) を使用しているサーバーならば以下が使用可能です。

    struct sockaddr_storage name;
    int namelen = sizeof (name);
    char abuf[INET6_ADDRSTRLEN];
    struct in6_addr addr6;
    struct in_addr addr;

    if (getpeername(fd, (struct sockaddr *)&name, &namelen) == -1) {
        perror("getpeername");
        exit(1);
    } else {
        addr = ((struct sockaddr_in *)&name)->sin_addr;
        addr6 = ((struct sockaddr_in6 *)&name)->sin6_addr;
        if (name.ss_family == AF_INET) {
                (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf));
        } else if (name.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&addr6)) {
                /* これは IPv4 によりマップされた IPv6 アドレスです。*/
                IN6_MAPPED_TO_IN(&addr6, &addr);
                (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf));
        } else if (name.ss_family == AF_INET6) {
                (void) inet_ntop(AF_INET6, &addr6, abuf, sizeof (abuf));

        }
        syslog("Connection from %s¥n", abuf);
    }

ブロ−ドキャストとネットワーク構成の判定

ブロードキャストは IPv6 ではサポートされていません。サポートしているのは IPv4 のみです。

データグラムソケットにより送信されたメッセージは、接続されているネットワークのすべてのホストに届くようブロードキャストを行うことができます。システムのソフトウェア上でのブロードキャストのシミュレーションは行われないため、ネットワークがブロードキャストをサポートしていなければなりません。ブロードキャストはネットワーク上のすべてのホストにサービスを強制するため、ネットワークメッセージはネットワークに大きい負荷をかける場合があります。ブロードキャストは主に 2 つの目的に使用されます。アドレスが不明なローカルネットワーク上の資源の検索、またアクセス可能なすべての隣接ホストへ送られる情報のルーティングを行う場合に使用されます。

ブロードキャストメッセージの送信を行うには、インターネットデータグラムソケットを作成します。

s = socket(AF_INET, SOCK_DGRAM, 0);
ソケットのポート番号を割り当てます。
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(MYPORT);
bind(s, (struct sockaddr *) &sin, sizeof sin);

ネットワークのブロードキャストアドレス宛てに送信を行うことにより、データグラムは 1 つのネットワーク上のみでブロードキャストを行うことが可能です。また netinet/in.h 内で定義されている特別なアドレス INADDR_BROADCAST に送信することにより、接続されているすべてのネットワークに対し、データグラムのブロードキャストを行うことが可能です。

システムにはシステム上のネットワークインタフェースに関する情報の数 (IP アドレスおよびブロードキャストアドレスを含む) を判定するメカニズムが装備されています。SIOCGIFCONF ioctl(2) 呼び出しはホストのインタフェース構成を単一 ifconf 構造体において (戻り値として) 返します。この構造体にはホストが接続されている各ネットワークインタフェースによりサポートされる各アドレスファミリに対し個別の ifreq 構造体の配列が含まれます。例 2-18 ではこれらの構造体の net/if.h 内における定義を行っています。


例 2-18 net/if.h

struct ifreq {
#define IFNAMSIZ 16
char ifr_name[IFNAMSIZ]; /* たとえば名前が "en0" */
union {
		struct sockaddr ifru_addr;
		struct sockaddr ifru_dstaddr;
		char ifru_oname[IFNAMSIZ]; /* 名前の場合、その他 */
		struct sockaddr ifru_broadaddr;
		short ifru_flags;
		int ifru_metric;
		char ifru_data[1]; /* インタフェース依存データ */
		char ifru_enaddr[6];
} ifr_ifru;
#define ifr_addr ifr_ifru.ifru_addr
#define ifr_dstaddr ifr_ifru.ifru_dstaddr
#define ifr_oname ifr_ifru.ifru_oname
#define ifr_broadaddr ifr_ifru.ifru_broadaddr
#define ifr_flags ifr_ifru.ifru_flags
#define ifr_metric ifr_ifru.ifru_metric
#define ifr_data ifr_ifru.ifru_data
#define ifr_enaddr ifr_ifru.ifru_enaddr
};

インタフェース構成を取り出す呼び出しは以下の通りです。

/*
 * インタフェースの数を検索するため SIOCGIFNUM ioctl を実行。
 *
 * 発見されたインタフェースの数に相当する空間を割り当て。
 *
 * 割り当てられたバッファーに対し SIOCGIFCONF を実行。
 *
 */
if (ioctl(s, SIOCGIFNUM, (char *)&numifs) == -1) {
        numifs = MAXIFS;
}
bufsize = numifs * sizeof(struct ifreq);
reqbuf = (struct ifreq *)malloc(bufsize);
if (reqbuf == NULL) {
        fprintf(stderr, "out of memory¥n");
        exit(1);
}
ifc.ifc_buf = (caddr_t)&reqbuf[0];
ifc.ifc_len = bufsize;
if (ioctl(s, SIOCGIFCONF, (char *)&ifc) == -1) {
        perror("ioctl(SIOCGIFCONF)");
        exit(1);
}
...
}

この呼び出しの後、 buf にはホストが接続されている各ネットワークに対して個別の ifreq 構造体の配列が格納されます。これらの構造体の順序付けは、まずインタフェース名で、次にサポートするアドレスファミリによって行われます。 ifc.ifc_lenifreq 構造体に使用されるバイト数に合わせて設定されます。

各構造体は対応するネットワークの利用可能状況、ポイントツーポイント (point-to-point) またはブロードキャストなどを表示するインタフェースフラグのセットを持ちます。例 2-19SIOCGIFFLAGS ioctl(2)ifreq 構造体により指定されたインタフェースのフラグを返すコードの例です。


例 2-19 インタフェースフラグの取得

struct ifreq *ifr;
ifr = ifc.ifc_req;
for (n = ifc.ifc_len/sizeof (struct ifreq); --n >= 0; ifr++) {
   /*
    * 別の目的でアドレスファミリ用に使用されているインタフェースを
    * 使用しないよう注意する。
    */
   if (ifr->ifr_addr.sa_family != AF_INET)
      continue;
   if (ioctl(s, SIOCGIFFLAGS, (char *) ifr) < 0) {
      ...
   }
     if ((ifr->ifr_flags & IFF_UP) == 0 ||
      (ifr->ifr_flags & IFF_LOOPBACK) ||
      (ifr->ifr_flags & (IFF_BROADCAST | IFF_POINTOPOINT)) == 0)
      continue;
}

例 2-20 では SIOCGIFBRDADDR ioctl(2) によってインタフェースのブロードキャストが取得可能であることを表現しています。


例 2-20 インタフェースのブロードキャストアドレス

if (ioctl(s, SIOCGIFBRDADDR, (char *) ifr) < 0) {
		...
}
memcpy((char *) &dst, (char *) &ifr->ifr_broadaddr,
		sizeof ifr->ifr_broadaddr);

SIOCGIFBRDADDR ioctl(2) はポイントツーポイントインタフェースのアドレスを取得する目的にも使用できます。

インタフェースブロードキャストアドレスの取得が完了したら、sendto(3SOCKET) を使用してブロードキャストデータグラムの送信を行います。

sendto(s, buf, buflen, 0, (struct sockaddr *)&dst, sizeof dst);

ブロードキャストまたはポイントツーポイントアドレスをサポートするホストが接続されている各インタフェースに対し 1 つの sendto(3SOCKET) を使用します。