ONC+ 開発ガイド

付録 B RPC プロトコルおよび言語の仕様

付録 B では、RPC パッケージで使用しているメッセージプロトコルの仕様を説明します。メッセージプロトコルは XDR 言語を使用して示します。XDR についての詳細は、 付録 C 「XDR プロトコル仕様」 を参照してください。

プロトコルの概要

RPC プロトコルには、以下の機能があります。

ネットワーク・ファイル・サービスが 2 つのプログラムで構成されていると考えてください。1 つのプログラムは、ファイルシステムへのアクセス制御やロックなど高レベルのアプリケーションを扱います。もう 1 つのプログラムは、低いレベルの入出力ファイルを扱い、「読込み」や「書込み」などの手続があります。ネットワークファイルサービスのクライアントマシンは、クライアントマシンのユーザーのために 2 つのプログラムに関連する手続きを呼び出します。クライアントサーバーモデルでは、遠隔手続き呼び出しは、サービスを呼び出す場合に使用します。

RPC モデル

RPC モデルは、ローカル手続き呼び出しモデルに似ています。ローカル手続き呼び出しでは、呼び出し側が手続きへの引数を特定の記憶領域に書き込みます。次に呼び出し側が制御を手続きに渡しますが、最終的には再び制御が呼び出し側に戻ります。その時点で手続きからの戻り値は特定の記憶領域から取り出され、呼び出し側は処理を続行します。

RPC もこれと同様に実行されますが、1 つの制御スレッドが論理的に 2 つのプロセスにまたがって実行されます。2 つのプロセスとは、呼び出し側のプロセスとサーバープロセスです。概念的には、呼び出し側のプロセスがサーバープロセスに呼び出しメッセージを送り、その後応答メッセージが返されるまで待ちます (ブロックします)。呼び出しメッセージには、さまざまなデータとともに手続きへの引数が含まれています。応答メッセージには、さまざまなデータとともに手続きからの戻り値が含まれています。応答メッセージを受信すると、その中から戻り値が取り出され、呼び出し側の処理が再開します。

サーバー側では、プロセスは呼び出しメッセージが到着するまで休止しています。呼び出しメッセージが到着すると、サーバープロセスはその中から手続きへの引数を取り出し、戻り値を計算し、応答メッセージを送信して、次の呼び出しメッセージが来るのを待ちます。

この説明では、2 つのプロセスのうち同時にはどちらか一方だけがアクティブになることに注意してください。ただし、これ以外の呼び出し方法も可能です。RPC プロトコルでは、同時実行モデルも使用できます。たとえば、RPC 呼び出しを非同期モードで実行すれば、クライアントはサーバーからの応答を待つ間も作業を続けることができます。また、サーバーでは、到着した要求を処理するために新たなタスクを生成して、別の要求の受信も続けることができます。

トランスポートとセマンティクス

RPC プロトコルとトランスポートプロトコルとは互いに独立しています。すなわち、RPC では、メッセージがプロセス間で実際にどのように伝送されるかについては関知しません。RPC プロトコルで対象にしているのは、メッセージの仕様と解釈方法だけです。

RPC では、トランスポートの信頼性を保証していません。ですから、RPC で使用されるトランスポートプロトコルの型についての情報をアプリケーションに指定する必要があります。もし、RPC サービスが TCP のような信頼性の高いトランスポートを使用しているとわかっていれば、必要な作業はほとんどトランスポートで実行されています。反対に、RPC が UDP のような信頼性の低いトランスポート上で実行されている場合は、サービスの方で再転送やタイムアウトに対する処理を行わなければなりません。RPC ではそのようなサービスを提供しません。

RPC はトランスポート独立であるため、RPC プロトコルは、遠隔手続きやその実行に特定のセマンティクスを結び付けることができません。セマンティクスは、使用しているトランスポートプロトコルから推測されます。ただし、明示的に指定されなければなりません。たとえば、RPC が信頼性の低いトランスポート上で実行されている場合を考えてみます。アプリケーションが短時間のタイムアウト後に RPC メッセージを再転送する場合、応答が返されなければ手続きが 0 回以上実行されたことしか推測できません。応答が返された場合は、手続きが少なくとも一度は実行されたことが推測できます。

サーバーでは、一度だけ実行というセマンティクスをある程度実現するため、以前にクライアントから受け取った要求を記憶しておいて、同じ要求を再受信しないようにする場合があります。その場合サーバーは、RPC 要求に必ず含まれているトランザクション ID を利用します。トランザクション ID は、主として RPC クライアントが、応答と要求との対応を調べるために使用します。クライアントアプリケーションでは、要求を再送信するときに以前のトランザクション ID を再使用することができます。サーバーアプリケーションでもこのことを確認していれば、要求を受信したときはトランザクション ID を記憶しておいて、同じ ID を持つ要求は再受信しないことができます。サーバーでは、以前と同じ要求かどうか調べるため以外の目的でトランザクション ID を使用することはできません。

反対に TCP のような信頼性の高いトランスポートを使用している場合、応答メッセージが返されればアプリケーションは手続きが一度だけ実行されたと推測できます。ところが、応答メッセージが返されないからといって、遠隔手続きが一度も実行されなかったと推測することはできません。TCP のような接続型プロトコルを使用する場合も、サーバーのクラッシュに対応するために、アプリケーションでタイムアウトと再接続確立の操作が必要なことに注意してください。

結合と相互認識の独立性

クライアントとサービスの結合は、遠隔手続き呼び出しの仕様の一部ではありません。結合という重要で不可欠な機能は、より上位レベルのソフトウェアで行います。そのソフトウェアでも RPC を使用することがあります。rpcbind プロトコル」の節を参照してください。

そのようなソフトウェアを開発する場合は、RPC プロトコルをネットワーク間のジャンプ - サブルーチン命令 (JSR 命令) と考えます。ローダー (バインダ) は、JSR 命令を実行可能にするために、ローダー自身も JSR 命令を使用します。同様に、ネットワークは RPC を実行可能にするためにネットワーク自身も RPC を使用します。

RPC プロトコルには、サービスに対してクライアントが自分自身を証明するため、またはその反対方向の証明のためのフィールドが用意されています。セキュリティやアクセス制御の機能は、メッセージ認証の上に成り立っており、何種類かの認証プロトコルをサポートできます。どのプロトコルを使用するかは、RPC ヘッダーの 1 フィールドで指定します。認証プロトコルについての詳細は、「レコードマーク標準」の節を参照してください。

プログラムと手続き番号

RPC 呼び出しメッセージには、呼び出される手続きを一意に識別する次の 3 つの符号なしフィールドがあります。

プログラム番号は、「プログラム番号の登録」にあるように、中央の 1 人の管理者が決定します。

プログラムを最初に作成したときは、バージョン番号は通常 1 になります。プロトコルは次第に改善されて、安定し、よりよいプロトコルになるため、呼び出し側のプロセスでは、呼び出しメッセージのバージョンフィールドを使用してどのプロトコルバージョンを使用するかを指定できます。バージョン番号を使用することにより、これまで使用していたプロトコルと新規プロトコルとが同じサーバープロセスで「使用可能」になります。

手続き番号では、どの手続きを呼び出すかを指定します。手続き番号は、各プログラムのプロトコル仕様に記されています。たとえば、ファイルサービスのプロトコル仕様には、手続き番号 5 は read で、手続き番号 12 は write というように記されています。

遠隔プログラムのプロトコルがバージョンが変わるたびに変更されるように、RPC メッセージプロトコルも変わることがあります。したがって、呼び出しメッセージには RPC バージョン番号も入っています。ここで説明する RPC のバージョン番号は常に 2 です。

要求メッセージに対する応答メッセージには、次に示すエラー条件を識別できるような情報が入っています。

RPC プロトコルの一部として、呼び出し側からサービスへの認証、および、その反対方向の認証が提供されています。呼び出しメッセージには、認証証明とベリファイアという 2 つの認証フィールドがあります。応答メッセージには、応答ベリファイアという認証フィールドがあります。RPC プロトコル仕様では、この 3 つのフィールドはすべて次のような隠されたデータ型で定義されています。

enum auth_flavor {
  	AUTH_NONE = 0,
  	AUTH_SYS = 1,
  	AUTH_SHORT = 2,
  	AUTH_DES = 3,
 	AUTH_KERB = 4
  	/* その他のタイプも定義可能 */
  };
  struct opaque_auth {
   enum         auth_flavor;        /* 認証のタイプ */
  	caddr_t      oa_base;          /* その他の認証データのアドレス */
  	u_int        oa_length;        /* データ長は MAX_AUTH_BYTES 以下 */
  };

opaque_auth 構造体には、列挙型 auth_flavor に続いて、RPC プロトコルには隠された認証データが入ります。

認証フィールドに入っているデータの解釈とセマンティクスは、個々の独立した認証プロトコル仕様で定義します。さまざまな認証プロトコルについては、「レコードマーク標準」の節を参照してください。

認証パラメータが拒絶された場合は、応答メッセージの中に拒絶理由が返されます。

プログラム番号の割り当て

0x20000000 のグループのプログラム番号は 表 B-1 に示すように分散されます。

表 B-1 RPC プログラム番号

プログラム番号 

説明 

00000000 - 1fffffff 

ホストが定義 

20000000 - 3fffffff 

ユーザーが定義 

40000000 - 5fffffff

一時的 (カスタマ作成アプリケーションのために予約)

60000000 - 7fffffff 

予約 

80000000 - 9fffffff 

予約 

a0000000 - bfffffff 

予約 

c0000000 - dfffffff 

予約 

e0000000 - ffffffff 

予約 

最初のグループの番号は全カスタマで一致している必要があり、Sun で管理しています。一般に使用できるアプリケーションをカスタマが開発した場合は、そのアプリケーションに最初のグループの番号を割り当てなければなりません。

第 2 グループの番号は特定のカスタマアプリケーションのために予約されています。この範囲の番号は、主に新規プログラムのデバッグで使用します。

第 3 グループは、動的にプログラム番号を生成するアプリケーションのために予約されています。

最後のグループは将来のために予約されているので、使用しないでください。

プログラム番号の登録

プロトコル仕様を登録するには、email で rpc@sun.com に送信するか、次の住所に送ってください。

RPC Administrator
Sun Microsystems, Inc
901 San Antonio Road.
Palo Alto,CA 94043
U.S.A. 650-960-1300

その際には rpcgen で生成した、プロトコルを記述する「.x」ファイルも同封してください。一意に識別できるプログラム番号を返送します。

標準 RPC サービスの RPC プログラム番号とプロトコル仕様は、/usr/include/rpcsvc のインクルードファイルに入っています。ただし、これらのサービスは登録されているサービスのほんの一部分にすぎません。

RPC プロトコルのその他の使用方法

本来、RPC プロトコルは遠隔手続き呼び出しを目的に作成されています。すなわち、各呼び出しメッセージがそれぞれ 1 つの応答メッセージに一致します。ところが、プロトコル自体はメッセージ引き渡しプロトコルなので、RPC 以外のプロトコルで対応できます。RPC パッケージでサポートされている RPC 以外のプロトコルとしては、バッチとブロードキャストがあります。

バッチ

バッチを使用すると、クライアントは任意の大きさの呼び出しメッセージシーケンスをサーバーに送信できます。一般にバッチでは、トランスポートとして TCP のような信頼性の高いバイトストリームプロトコルを使用します。バッチを使用すると、クライアントはサーバーからの応答を待たず、サーバーもバッチ要求に対しては応答しません。バッチ呼び出しシーケンスを終了するには、通常、非バッチの RPC 呼び出しを行なってパイプラインをフラッシュします。このときは肯定応答が返されます。詳細については、「バッチ処理」の節を参照してください。

ブロードキャスト RPC

ブロードキャスト RPC では、クライアントがブロードキャストパケットをネットワークに送信し、それに対する数多くの応答を待ちます。ブロードキャスト RPC では、トランスポートに UDP のような非接続型のパケットベースプロトコルを使用します。ブロードキャストプロトコルをサポートするサーバーは、要求を正しく処理できたときだけ応答を返し、エラーが起これば応答は返しません。ブロードキャスト RPC では rpcbind サービスを使用してそのセマンティクスを達成します。詳細については、 「ブロードキャスト RPC」、および rpcbind プロトコル」 の節を参照してください。

RPC メッセージプロトコル

この節では、RPC メッセージプロトコルを、XDR データ記述言語を使用して説明します。メッセージは、例 B-1で示すようにトップダウン形式で定義します。


例 B-1 RPC メッセージプロトコル

enum msg_type {
 	CALL = 0,
 	REPLY = 1
 };
 
/*
 * 呼び出しメッセージに対する応答には、2 つの形式があります。メッセージが 
 * 受け入れられた場合と拒絶された場合のどちらかです。
 */
 enum reply_stat {
 	MSG_ACCEPTED = 0,
 	MSG_DENIED = 1
 };
 
/*
 * 呼び出しメッセージが受け入れられた場合、遠隔手続きを呼び出したときの 
 * ステータスが次のように示されます。
 */
enum accept_stat {
 	SUCCESS = 0,       /* RPC が正常に実行された */
 	PROG_UNAVAIL = 1,  /* 遠隔サービスにエクスポートされたプログラムがない */
 	PROG_MISMATCH = 2, /* 遠隔サービスがそのバージョン番号をサポートしていない */
 	PROC_UNAVAIL = 3,  /* プログラムがその手続きをサポートしていない */
 	GARBAGE_ARGS = 4   /* 手続きが引数を復号化できない */
};
 
/*
 * 呼び出しメッセージが拒絶された原因
 */
enum reject_stat {
 	RPC_MISMATCH = 0,  /* RPC のバージョン番号が 2 でない */
 	AUTH_ERROR = 1     /* 遠隔サービスで呼び出し側の認証エラー */
};
/*
 * 認証が失敗した原因
 */
enum auth_stat {
 	AUTH_BADCRED = 1,       /* 認証エラーの原因 */
 	AUTH_REJECTEDCRED = 2,  /* クライアントは新規セッションが必要 */
 	AUTH_BADVERF = 3,       /* ベリファイアのエラー */
 	AUTH_REJECTEDVERF = 4,  /* ベリファイアの失効または再使用 */
 	AUTH_TOOWEAK = 5        /* セキュリティによる拒絶 */
};
 
/*
 * RPC メッセージ:
 * どのメッセージもトランザクション ID xid と 
 * それに続く識別型共用体 (アームは 2 つ) で始まります。 
 * 共用体の要素識別子は msg_type で、2 つのメッセージタイプのうち
 * どちらのタイプのメッセージかを示します。REPLY メッセージの xid は、
 * 対応する CALL メッセージの xid に一致します。注意: xid フィールドは、
 * クライアント側で応答メッセージがどの呼び出しメッセージに対応するかを 
 * 調べるか、サーバー側で再送信かどうかを調べるためにだけ使用できます。 
 * サービス側では xid をなんらかのシーケンス番号として使用することはできません。
 */
struct rpc_msg {
 	unsigned int xid;
 	union switch (msg_type mtype) {
 		case CALL:
 			call_body cbody;
 		case REPLY:
 			reply_body rbody;
 	} body;
};
 
/*
 * RPC 要求呼び出しの本体:
 * RPC プロトコル仕様のバージョン 2 では、rpcvers は 2 でなければ 
 * なりません。prog、vers、proc の各フィールドにはそれぞれ、 
 * 遠隔プログラム、そのバージョン番号、遠隔プログラムに入っている 
 * 呼び出し対象の手続きを指定します。これらのフィールドに続いて 
 * 2 つの認証パラメータ cred (認証を証明するもの) と verf (認証を検証する
 * もの : 認証ベリファイア) があります。この 2 つの認証パラメータの後には、 
 * 遠隔手続きへの引数が入りますが、それらは特定プログラムの 
 * プロトコルで指定されます。
*/
struct call_body {
 	unsigned int rpcvers; /* この値は 2 でなければならない */
 	unsigned int prog;
 	unsigned int vers;
 	unsigned int proc;
 	opaque_auth cred;
 	opaque_auth verf;
 	/* ここからは手続きに固有の引数 */
 };
 
/*
 * RPC 要求への応答の本体:
 * 呼び出しメッセージは受け入れられたか拒絶されたかのどちらか
 */
union reply_body switch (reply_stat stat) {
 	case MSG_ACCEPTED:
 		accepted_reply areply;
 	case MSG_DENIED:
 		rejected_reply rreply;
} reply;
 
/*
 * RPC 要求がサーバーに受け入れられた場合の応答: 要求が受け入れられた場合も 
 * エラーはあり得ます。最初のフィールドはサーバーが呼び出し側に自分自身を 
 * 証明する認証ベリファイアです。次のフィールドは共用体で、 
 * 要素識別子は列挙型 accept_stat です。この共用体の SUCCESS アームは 
 * プロトコルによって異なります。
 * PROG_UNAVAIL、PROC_UNAVAIL、GARBAGE_ARGP の 
 * アームは void です。PROG_MISMATCH アームにはサーバーが 
 * サポートしている遠隔プログラムのバージョン番号の
 * 最大値と最小値が入ります。
 */
struct accepted_reply {
 	opaque_auth verf;
 	union switch (accept_stat stat) {
 		case SUCCESS:
 			opaque results[0];
 			/* ここからは手続き固有の戻り値 */
 		case PROG_MISMATCH:
 			struct {
 				unsigned int low;
 				unsigned int high;
 			} mismatch_info;
 		default:
 			/*
 			 * PROG_UNAVAIL、PROC_UNAVAIL、GARBAGE_ARGS
 		    * の場合は void
 			 */
 			void;
 	} reply_data;
 };
 
/*
 * RPC 要求がサーバーに拒絶された場合の応答:
 * 要求が拒絶されるのには 2 つの原因があります。互換性のあるバージョンの 
 * RPC プロトコルがサーバーで実行されていない場合 (RPC_MISMATCH) と、 
 * サーバーが呼び出し側の認証を拒否した場合 (AUTH_ERROR) です。 
 * RPC バージョンの不一致の場合は、サーバーがサポートしている RPC バージョンの 
 * 最大値と最小値が返されます。認証拒否の場合は、 
 * 異常終了ステータスが返されます。
 */
union rejected_reply switch (reject_stat stat) {
 	case RPC_MISMATCH:
 		struct {
 			unsigned int low;
 			unsigned int high;
 		} mismatch_info;
 	case AUTH_ERROR:
 		auth_stat stat;
};
 

レコードマーク標準

RPC メッセージが TCP のようなバイトストリーム型のトランスポートに渡されるとき、ユーザープロトコルエラーを検出し、できれば回復するために各メッセージの区切りを知る必要があります。これをレコードマーク (RM) といいます。1 つの RPC メッセージは 1 つの RM レコードに収められます。

レコードはいくつかのレコードフラグメントで構成されます。レコードフラグメントには、4 バイトのヘッダーに続いて 0(2**31) - 1 バイトのフラグメントデータが入っています。データには符号なしバイナリ数値が符号化され、バイト順序は XDR 整数と同様にネットワークのバイト順序に従います。

ヘッダーには次の 2 つの値が符号化されています。

認証プロトコル

認証パラメータは内容が隠されていますが、以降の RPC プロトコルで自由に解釈できます。この節では、既に定義されているタイプの認証について説明します。別のサイトでは自由に新たな認証タイプを作成し、プログラム番号割り当て規則と同様の認証タイプ番号割り当て規則に従って、認証タイプ番号を割り当てることができます。認証タイプ番号は Sun で保守、管理しています。認証番号の割り当てを希望されるユーザーは、RPC プログラム番号を割り当てるときと同様に、「プログラム番号の登録」に示すように、Sun の RPC 管理者に連絡してください。

AUTH_NONE

呼び出し側は自身を証明せず、また、サーバー側も呼び出し側が誰でもかまわないという呼び出しもあります。その場合は RPC メッセージの認証証明、ベリファイア、応答ベリファイアの flavor (opaque_auth 共用体の要素識別子) は AUTH_NONE にします。AUTH_NONE タイプの認証を使用するときは、body フィールドの長さをゼロにします。

AUTH_SYS

これは、AUTH_UNIX として知られる以前に説明した認証 flavor と同じです。遠隔手続きを呼び出す側では、従来の UNIX のプロセス許可認証を使用して自分自身を証明する場合があります。そのような RPC 呼び出しメッセージでは、opaque_authflavorAUTH_SYSとなります。body には、次に示す構造体が符号化されます。

struct auth_sysparms {
  	unsigned int  stamp;
  	string machinename<255>;
  	uid_t uid;
  	gid_t gid;
  	gid_t gids<10>;
 };

認証証明に伴うベリファイアの flavorAUTH_NONE でなければなりません。

AUTH_SHORT タイプのベリファイア

AUTH_SYSタイプの認証を使用するときは、サーバーからの応答メッセージに入っている応答ベリファイアの flavorAUTH_NONEAUTH_SHORT のどちらかです。

AUTH_SHORT の場合、応答ベリファイアの文字列には short_hand_verf 構造体が符号化されています。この隠された構造体を、元の AUTH_SYS 認証証明の代わりにサーバーに渡すことができます。

サーバー側では、隠された short_hand_verf 構造体 (AUTH_SHORT タイプの応答ベリファイアによって返される) を呼び出し側の元の認証証明にマップするキャッシュを保存します。呼び出し側は、新たな認証証明を使用してネットワークの帯域幅とサーバーの CPU サイクルを保存できます。

サーバー側では、隠された short_hand_verf 構造体をいつでもフラッシュできます。そうすると、遠隔手続き呼び出しメッセージは認証エラーにより拒絶されます。エラー原因は AUTH_REJECTEDCRED になります。この場合、呼び出し側では AUTH_SYS タイプの元の認証証明を試すこともできます。 図 B-1 を参照してください。

図 B-1 認証過程のマップ

Graphic

AUTH_DES タイプの認証

AUTH_SYS タイプの認証には次のような問題があります。

  1. 異なるオペレーティングシステムのマシンが同じネットワークに接続している場合に、呼び出し側の ID が一意に決まるとは限らない

  2. ベリファイアがないため簡単に認証証明をごまかすことができます。AUTH_DES タイプの認証はこの 2 つの問題を解決するための方法

最初の問題を解決するには、呼び出し側をオペレーティングシステム固有の整数ではなく単純文字列で指定します。この文字列のことを、呼び出し側の netname (ネットワーク名) といいます。サーバーでは、呼び出し側の識別のためにだけ呼び出し側の名前を使用します。したがって、名前ドメイン内では呼び出し側を一意に識別できるようなネットワーク名を設定しなければなりません。

遠隔サーバーを呼び出す各ユーザーに一意のネットワーク名を生成するのは、それぞれのオペレーティングシステムで実現されている AUTH_DES 認証機能の責任です。オペレーティングシステムでは既に、システムのローカルユーザーを識別しています。通常はこれを単純に拡張してネットワーク名とします。たとえば、ユーザー ID を 515 とすると、「UNIX.515@sun.com」いうネットワーク名を割り当てることができます。このネットワーク名は、確実に一意の名前にするために 3 つの要素で構成されています。後ろから見ると、インターネットには sun.com という名前ドメインは 1 つしかありません。その名前ドメイン内では、ID が 515UNIX ユーザーは 1 人しかいません。ただし、同一の名前空間にある別のオペレーティングシステム (たとえば VMS) 上のユーザーが偶然同じユーザー ID を持つことはあります。そのような 2 人のユーザーを区別するために、オペレーティングシステム名を追加します。そうすると、一方のユーザーは「UNIX.515@sun.com」となり、他方のユーザーは「VMS.515@sun.com」となります。

最初のフィールドは実際にはオペレーティングシステム名とは別の命名方法で指定します。現在、その命名方法とオペレーティングシステム名とがほぼ 1 対 1 に対応しているだけです。命名方法の標準が確立すれば、最初のフィールドにはオペレーティングシステム名ではなくその標準規約に従った名前を入れます。

AUTH_DES 認証のベリファイア

AUTH_SYS 認証とは違って、AUTH_DES 認証にはベリファイアがあり、サーバーはクライアントの認証証明が正しいかどうかを確認できます。また、その反対方向の認証確認もできます。ベリファイアの主な内容は暗号化されたタイムスタンプです。サーバーは暗号化されたタイムスタンプを解読し、もしそれが実際の時刻に近ければ、クライアントが正しく暗号化したものと考えられます。クライアントが正しくタイムスタンプを暗号化するには、RPC セッションの会話鍵を知っていなければなりません。会話鍵を知っているクライアントならば、本当のクライアントのはずです。

会話鍵は、クライアントが生成して最初の RPC 呼び出しでサーバーに通知する DES [5] 鍵です。会話鍵は、最初のトランザクションで公開鍵方式で暗号化されます。AUTH_DES 認証で使用する公開鍵方式は、192 ビット鍵を使用する Diffie-Hellman [3] 暗号化手法です。この暗号化方式については後で詳しく説明します。

この認証方法が正しく機能するためには、クライアントとサーバーで時刻が一致していなければなりません。ネットワークの時刻同期が保証できないときは、会話を始める前にクライアントの方でサーバーと時刻を合わせることができます。rpcbind の提供する手続き RPCBPROC_GETTIME を使用すれば、現在時刻を取り出すことができます。

サーバーはクライアントのタイムスタンプが正当なものかどうか判定します。2 番目以降のすべてのトランザクションに対して、サーバーは次の 2 つの項目をチェックします。

最初のトランザクションでは、サーバーはタイムスタンプが失効していないことを確認します。さらに、クライアントは最初のトランザクションで、ウィンドウベリファイアと呼ばれるウィンドウの値より 1 少ない値を暗号化して送信します。そうしないと、認証証明がサーバーに破棄されてしまいます。

クライアントはサーバーから返されたベリファイアが正当なものかどうか調べなければなりません。サーバーは、クライアントから受信したタイムスタンプから 1 秒少ない値を暗号化してクライアントに送り返します。クライアントはそれ以外の値を受け取った場合は、それを拒絶します。

ニックネームとクロック同期

最初のトランザクションの後で、サーバーの AUTH_DES 認証サブシステムからクライアントへのベリファイアの中に整数値のニックネームが返されます。クライアントは以降のトランザクションで、ネットワーク名、暗号化された DES 鍵、ウィンドウを毎回渡す代わりに、ニックネームを使用できます。ニックネームは、サーバーが各クライアントのネットワーク名、復号化された DES 鍵、ウィンドウを保存しているテーブルのインデックスのようなものですが、クライアントからは隠されたデータとしてしか使用できません。

クライアントとサーバーのクロックは最初は同期していても、やがて同期が取れなくなることがあります。その場合、クライアント側の RPC サブシステムは RPC_AUTHERROR を受け取るので、その時点で再び同期を取る必要があります。

クライアントとサーバーの時刻が同期していてもクライアントが RPC_AUTHERROR を受け取ることがあります。その原因は、サーバーのニックネームテーブルの大きさには制限があり、足りなくなるとエントリが失われることがあるためです。その場合、クライアントは元の認証証明を再送信しなければなりません。サーバーはそれに新たなニックネームを割り当てます。もしもサーバーがクラッシュすると、ニックネームテーブル全体が失われ、全クライアントが元の認証証明を再送信しなければなりません。

DES 認証プロトコル (XDR 言語で記述)


例 B-2 AUTH_DES 認証プロトコル

/*
 * 認証証明には 2 種類あります。1 つは完全なネットワーク名を使用する方法で、
 
 * もう 1 つはサーバーがクライアントに割り当てたニックネーム (符号なし整数) を
 * 使用する方法です。クライアントからサーバーへの初めてのトランザクションでは
 * 完全なネットワーク名を使用しなければなりません。それに対して、サーバーは
 * クライアントにニックネームを返します。クライアントはそれ以降、サーバーへの
 * トランザクションでニックネームを使用できます。必ずニックネームを
 * 使用しなければならないわけではありませんが、パフォーマンスから考えても、
 * ニックネームを使用する方がよいでしょう。
 */
enum authdes_namekind {
 	ADN_FULLNAME = 0,
 	ADN_NICKNAME = 1
 };
 
/*
 * 暗号化した DES データのための 64 ビットブロック
 */
 typedef opaque des_block[8];
 
/*
 * ユーザーのネットワーク名の最大長
 */
const MAXNETNAMELEN = 255;
 
/*
 * クライアントのネットワーク名、暗号化された会話鍵、
 * ウィンドウの含まれたフルネーム。
 * ウィンドウは認証証明の有効な時間を示します。
 * ベリファイアに示されたタイムスタンプにウィンドウを加えた時刻が
 * 過ぎている場合は、サーバーは要求を無効として許可しません。要求を
 * 再送信しないために、最初のトランザクション以外は、
 * タイムスタンプが以前の値より大きくないと、サーバーはそれを受け入れません。
 * 最初のトランザクションの場合は、サーバーはウィンドウベリファイアが
 * ウィンドウより 1 小さい値であることを確認します。
 */
struct authdes_fullname {
 	string name<MAXNETNAMELEN>; /* クライアント名 */
 	des_block key;              /* PK で暗号化された会話鍵 */
 	unsigned int window;        /* 暗号化されたウィンドウ */
};                             /* 注: PK は公開鍵の略 */
 
/*
 * 認証証明はフルネームかニックネーム
*/
unionauthdes_credswitch(authdes_namekindadc_namekind){
 	case ADN_FULLNAME:
 		authdes_fullname adc_fullname;
 	case ADN_NICKNAME:
 		unsigned int adc_nickname;
};
 
/*
 * タイムスタンプには、1970 年 1 月 1 日の午前 0 時からの秒数を符号化
 */
struct timestamp {
 	unsigned int seconds;      /* 秒数 */
 	unsigned int useconds;     /* マイクロ秒 */
};
 
/*
 * ベリファイア: クライアント側
 */
struct authdes_verf_clnt {
 	timestamp adv_timestamp;   /* 暗号化されたタイムスタンプ */
 	unsigned int adv_winverf;  /* 暗号化されたウィンドウベリファイア */
};
 
/*
 * ベリファイア: サーバー側
 * サーバーは、クライアントから渡されたタイムスタンプから 1 秒少ない値を 
 * 暗号化して返します。また、クライアントが以降のトランザクションで使用できる 
 * ニックネームを暗号化せずに渡します。
 */
struct authdes_verf_svr {
 	timestamp adv_timeverf;    /* 暗号化されたベリファイア */
 	unsigned int adv_nickname; /* クライアントの新しいニックネーム */
};
 

Diffie-Hellman の暗号化手法

この暗号化手法では、2 つの定数 PROOTHEXMODULUSを使用します。DES 認証プロトコルでは、この 2 つの定数として次の値を使用します。

const PROOT = 3;
const HEXMODULUS =	/* 16 進 */
 "d4a0ba0250b6fd2ec626e7efd637df76c716e22d0944b88b";

この暗号化手法は次の例で説明するとわかりやすいでしょう。ここに A と B という 2 人の人が互いに暗号化したメッセージを送信するとします。Aと B はそれぞれランダムに秘密鍵を生成し、この鍵は誰にも教えません。秘密鍵をそれぞれ SK(A) と SK(B) とします。また、2 人は公開ディレクトリにそれぞれ公開鍵を示します。公開鍵は次のように計算されます。

PK(A) = (PROOT ** SK(A)) mod HEXMODULUS
PK(B) = (PROOT ** SK(B)) mod HEXMODULUS

** という記号はべき乗を表します。

ここで A と B は、互いに秘密鍵を知らせ合うことなく 2 人の間の共通鍵 CK(A,B) を求めることができます。

A は次のように計算します。

CK(A, B) = (PK(B) ** SK(A)) mod HEXMODULUS

B は次のように計算します。

CK(A, B) = (PK(A) ** SK(B)) mod HEXMODULUS

上の 2 つの式から次の等式が得られます。

(PK(B)**SK(A)) mod HEXMODULUS = (PK(A)**SK(B)) mod HEXMODULUS

ここで、mod HEXMODULUS という部分を両辺から取り除いてモジュロ計算を省略し、プロセスを簡単にします。

PK(B) ** SK(A) = PK(A) ** SK(B)

次に、PK(B) を先に B が計算した値で置き換えます。PK(A) も同様に置き換えます。

((PROOT ** SK(B)) ** SK(A) = (PROOT ** SK(A)) ** SK(B)

この式は次のように書き換えられます。

PROOT ** (SK(A) * SK(B)) = PROOT ** (SK(A) * SK(B))

共通鍵 CK(A,B) は、プロトコルで使用されるタイムスタンプの暗号化には使用しません。共通鍵は会話鍵の暗号化にだけ使用し、タイムスタンプの暗号化には会話鍵を使用します。これは、共通鍵を使用する回数をできるだけ少なくして、共通鍵が破られないようにするためです。会話時間は比較的短いため、会話鍵の方が破られる心配がずっと少ないからです。

会話鍵は、56 ビットの DES 鍵を使用して暗号化します。共通鍵は 192 ビットなので、共通鍵から次のようにして 56 ビットを選択し、ビット数を減らします。共通鍵から中央の 8 バイトを選択し、各バイトの下位ビットにパリティを加えます。こうして、8 ビットのパリティの付いた 56 ビット鍵が生成されます。

AUTH_KERB 認証プロトコル

AUTH_KERB の S 実装に使用されたカーネルは、Kerberos のコードをオペレーティングシステムのカーネルへコンパイルしないで、kerbd という代理の RPC デーモンを使用します。このデーモンは、以下の 3 つの手続きをエクスポートします。詳細については、kerbd(1M) のマニュアルページを参照してください。

  1. KGETKCRED

    クライアントによって提供された認証プロトコルを検査するために、サーバー側の RPC が使用する

  2. KSETKCRED

    主体名、インスタンス、領域が指定されると、暗号化されたチケットおよび DES セッション鍵を返す

  3. UNIX 固有の KGETUCRED

    KGETUCRED は、主な名前がサーバーにもわかるユーザー名にマップされると想定し、ユーザーの ID、グループ ID、およびグループリストを返す

Kerberos の内容を的確に説明するには、現在 Kerberos を実装しているサービスであるネットワークファイルシステム (NFS) を例として使用するのが良いでしょう。サーバー s の NFS サービスは、nfs.s という周知の主体名を持つとします。クライアント c の特権ユーザーは、root という一次名と、インスタンス c をもっているとします。AUTH_DES の場合とは異なり、ユーザーのチケット発行用のチケットの期限が切れた場合は、kinit() を再び呼び出さなければならないことに注意してください。Kerberos マウントの NFS サービスは、新しいチケット発行用のチケットを獲得するまで成功しません。

NFS マウント例

この節全体を通して、AUTH_KERB を使用した NFS マウント要求について説明します。マウント要求は、ルートで実行されるので、ユーザーの識別情報は、root.c.になります。

クライアント c は、マウントするディレクトリのファイルハンドルを獲得するために、サーバー sMOUNTPROC_MOUNT 要求を実行します。クライアントのマウントプログラムは、ファイルハンドル、mountflavor、時間同期アドレス、サーバーの既知の主体名である nfs.s を、クライアントのカーネルに渡して、NFS マウントシステムコールを実行します。次に、クライアントのカーネルが時間同期ホストでサーバーに接続し、クライアント/ サーバー間の時間差を取得します。

クライアントのカーネルは、次の RPC 呼び出しを行います。(1) チケットおよびセッションキーを獲得するためにのローカルの kerbd への KSETKCRED 呼び出し。(2) フルネームの資格およびベリファイアを使用した、サーバーの NFS サービスへの NFSPROC_GETATTR 呼び出し。サーバーは、呼び出しを受信し、ローカルの kerbdKGETKCRED 呼び出しを行なってクライアントのチケットを検査します。

サーバーの kerbd と Kerberos ライブラリは、チケットの暗号を解除し、主体名および DES セッション鍵を他のデータの中に返します。サーバーは、チケットがまだ有効であることをチェックし、セッション鍵を使用して資格、ベリファイアの DES の暗号化された部分を複号化し、ベリファイアが有効であることを検査します。

この時に返される可能性のある Kerberos 認証エラーは、下記のとおりです。

エラーを受信しない場合、サーバーはクライアントの識別情報をキャッシュに書き込み、NFS 回答に返されるニックネーム (小さい整数) を割り当てります。その時サーバーは、クライアントがサーバーと同じ領域かどうかをチェックします。クライアントがサーバーと同じ領域の場合、サーバーは、KGETUCRED をローカルの kerbd に呼び出して、主体名を UNIX の資格に変換します。変換できない場合、ユーザーは匿名であるとマークされます。サーバーは、ファイルシステムのエクスポート情報に対するこれらの資格を検査します。次の 3 つのケースを考えてください。

  1. KGETUCRED 呼び出しが失敗し、匿名の要求が受け入れられた場合、匿名のユーザーに UNIX 資格が割り当てられます。

  2. KGETUCRED 呼び出しが失敗し、匿名の要求が受け入れられない場合、NFS 呼び出しは失敗し、AUTH_TOOWEAK が返されます。

  3. KGETUCRED 呼び出しが成功する場合は、資格が割り当てられ、その後にルートのアクセス権のチェックも含む、正常な保護検査が行われます。

次に、サーバーが、ニックネームおよびサーバーのベリファイアを組み込んで NFS 回答を送信します。クライアントは回答を受信し、ベリファイアの複号化と妥当性検査を行い、今後の呼び出しのためにニックネームを格納します。クライアントがサーバーに 2 番目の NFS 呼び出しを行うと、先にサーバーに書込まれた呼び出しが繰り返されます。クライアントのカーネルが、以前に記述されたニックネーム資格およびベリファイアを使用して、サーバーの NFS サービスに NFSPROC_STATVFS 呼び出しを行います。サーバーは呼び出しを受信し、ニックネームの妥当性検査を行います。これが範囲外であれば、エラー AUTH_BADCRED を返します。サーバーは、獲得したばかりのセッション鍵を使用して、ベリファイアの DES の暗号化された部分を複号化し、ベリファイアの妥当性検査を行います。

この時に返される可能性のある Kerberos 認証エラーは、次のとおりです。

エラーが受信されない場合、サーバーは、ニックネームを使用して、呼び出し側の UNIX 資格を検出します。それから、サーバーはファイルシステムのエクスポート情報に対するこれらの資格を検査し、ニックネームおよびサーバーのベリファイアを組み込んだ NFS 回答を送信します。クライアントは回答を受信し、ベリファイアの複号化および妥当性検査を行い、これからの呼び出しのためにニックネームを格納します。最後に、クライアントのNFS マウントシステムコールが返り、要求が終了します。

KERB 認証プロトコル (XDR 言語で記述)

例 B-3 (AUTH_KERB) は、例 B-2 で示された AUTH_DES と似ています。両者の違いに注意してください。


例 B-3 Kerb 認証プロトコル

#define AUTH_KERB 4
/*
 * 資格には 2 種類あります。1 つはクライアントが (前もって暗号化された)
 
 * Kerberos チケット送信する資格で、もう 1 つはクライアントがサーバーに指定され
 * た「ニックネーム」(符号なしの整数のみ) を使用する資格です。クライアントは、
 * サーバーへの初めてのトランザクションでは、フルネームを使用しなければなりませ
 * ん。それに対してサーバーはクライアントにニックネームを返します。クライアントは
 * それ以降、サーバーへのトランザクションでニックネームを使用できます。
 * (チケットの期限が切れるまで)。 	必ずニックネームを使用しなけれ
 * ばならないわけではありませんが、パフォーマンスから考えても、ニック
 * ネームを使用する方がよいでしょう。
 */
enum authkerb_namekind {
 	AKN_FULLNAME = 0,
 	AKN_NICKNAME = 1
 };
 
 
/*
 * フルネームには、暗号化されたサービスチケットと有効期限が含まれます。
 
 * 実際、この有効期限は、資格の有効期限と同じです。ベリファイアの
 * タイムスタンプに示されている時間に有効期限を加えた時間がすでに経過すると、
 * サーバーは要求を満了にして、もはや承諾しません。要求がやり直されないようにす
 * るため、サーバーは、はじめのトランザクション以外の場合に、タイムスタンプが以
 * 前のタイムスタンプより大きくないかどうかをチェックする必要があります。はじ
 * めのトランザクションでは、サーバーはウィンドウベリファイアがウィンドウより小
 * さいことを検査します。
 */
struct authkerb_fullname {
 	KTEXT_ST ticket;              /* Kerberos サービスチケット */
 	unsigned long window;        /* 暗号化されたウィンドウ */
};                             
/*
 * 資格はフルネームかニックネーム
 */
union authkerb_credswitch(authkerb_namekind akc_namekind){
 	case AKN_FULLNAME:
 		authkerb_fullname akc_fullname;
 	case AKN_NICKNAME:
 		unsigned long akc_nickname;
};
 
/*
 * タイムスタンプには、1970 年 1 月 1 日の午前 0 時からの秒数を符号化
 
 */
struct timestamp {
 	unsigned long seconds;      /* 秒 */
 	unsigned long useconds;     /* マイクロ秒 */
};
/*
 * ベリファイア:クライアント側
 
 */
struct authkerb_verf_clnt {
 	timestamp akv_timestamp;   /* 暗号化されたタイムスタンプ */
 	unsigned long akv_winverf;  /* 暗号化されたウィンドウベリファイア */
};
 
/*
ベリファイア:サーバー側
 * クライアントによりサーバーは、タイムスタンプ(暗号化)が与えられた。
 * また、サーバーは、クライアントがニックネームを今後の(暗号化されて
 * いない)トランザクションで使用するように指定します。
 */
struct authkerb_verf_svr {
 	timestamp akv_timeverf;    /* 暗号化されたベリファイア */
 	unsigned long akv_nickname; /* クライアントの新しいニックネーム */
};
 

RPC 言語の仕様

XDR データ型を形式言語で記述する必要があるのと同様に、これらの XDR データ型に対して作用する手続きも、形式言語で記述する必要があります。XDR 言語の拡張版である RPC 言語は、XDR 言語を形式言語で記述するための言語です。次に、RPC 言語についての例を示します。

RPC 言語で記述されたサービスの例

例 B-4 は、単純な ping プログラムの仕様を示します。


例 B-4 RPC 言語を使用した ping サービス

/*
 * 単純な ping プログラム
 */
program PING_PROG {
 	version PING_VERS_PINGBACK {
 		void		
 		PINGPROC_NULL(void) = 0;
 		/*
 	 * 呼び出し側の ping は、往復時間をミリ秒で返します。
 	 * オペレーションがタイムアウトの場合は、-1 を返します。
		 */
		int
 		PINGPROC_PINGBACK(void) = 1;
 		/* void - 上記は呼び出しへの引き数 */
 	} = 2;
/*
 * オリジナルのバージョン
 */
 	version PING_VERS_ORIG {
 		void
 		PINGPROC_NULL(void) = 0;
 	} = 1;
} = 200000;	
const PING_VERS = 2; /* 最新バージョン */

記述された最初のバージョンは、2 つの手続き、PINGPROC_NULL、および PINGPROC_PINGBACK が組み込まれた PING_VERS_PINGBACKです。

PINGPROC_NULL は引数を必要とせず、結果も返しませんが、クライアントとサーバー間の往復時間を計算するときになどに便利です。規則によると、RPC プログラムの手続き 0 はすべて同じセマンティクスを持つことになっているので、認証は必要ありません。

2 番目の手続きは、オペレーションにかかった合計時間を (マイクロ秒で) 返します。

次のバージョンである PING_VERS_ORIG は、プロトコルのオリジナルのバージョンで、PINGPROC_PINGBACK 手続きは含まれません。PING_VERS_ORIG は、古いクライアントプログラムと互換性を持たせる場合に便利ですが、このプログラムが完成すると、プロトコルから完全に削除されることがあります。

RPCL 構文

RPC 言語 (RPCL) は C に似ています。この節では、例を示しながら RPC 言語の構文を説明します。また、出力ヘッダーファイルで、RPC 型定義および XDR 型定義を C 型定義にコンパイルする方法についても説明します。

RPC 言語ファイルは次の一連の定義から構成されています。

   definition-list:
      definition;
      definition; definition-list 

定義には、6 つの型があります。

definition:
      enum-definition
 		const-definition
 		typedef-definition
 	 struct-definition
 		union-definition
 		program-definition  

定義は宣言と同じではありません。1 つまたは一連のデータ要素の型定義以外の定義によっては領域を割り当てることはできません。これは、変数は定義するだけでは十分でなく、宣言もする必要があることを意味しています。

RPC 言語は、表 B-2 で追加された定義以外は、XDR 言語と同じです。

表 B-2 RPC 言語定義

用語 

定義 

プログラム定義

program program-ident {version-list} = value

バージョンリスト

version;

version; version-list

バージョン

version version-ident {procedure-list} = value

手続きリスト

procedure;

procedure; procedure-list

手続き

type-ident procedure-ident (type-ident) = value

列挙法

RPC/XDR 列挙法の構文は、C 列挙法と同じです。

enum-definition:
   "enum" enum-ident "{"
 		enum-value-list
 	"}"
enum-value-list:
 	enum-value
 	enum-value "," enum-value-list
enum-value:
 	enum-value-ident
 	enum-value-ident "=" value   

次に、コンパイルされる XDR enum および C enum の例を示します。

enum colortype {               enum colortype {
 	RED = 0,                       RED = 0,
 	GREEN = 1,       -->           GREEN = 1,
 	BLUE = 2                       BLUE = 2,
};                             };
                               typedef enum colortype colortype;

定数

シンボリック定数は、整数の定数が使用されればどこでも使用できます。たとえば配列サイズ仕様で使用すると、次のようになります。

   const-definition:
   const const-ident = integer 

次の例では定数 DOZEN12 に定義します。

const DOZEN = 12; --> #define DOZEN 12 

型定義

XDR typedef の構文は、C typedef と同じです。

typedef-definition:
      typedef declaration  

この例では、最大 255 文字のファイル名の文字列を宣言するために使用する fname_type を定義します。

typedef string fname_type<255>; --> typedef char *fname_type; 

宣言

XDR には、4 種類の宣言があります。これらの宣言は、struct または typedef の中に記述する必要があります。単独では使用できません。

declaration: 
      simple-declaration
 		fixed-array-declaration
 		variable-array-declaration
 		pointer-declaration 

単純宣言

単純宣言は、C 単純宣言に似ています。

simple-declaration:
    	type-ident variable-ident 

次に例を示します。

   colortype color; --> colortype color;

固定長配列宣言

固定長配列宣言は、C 配列宣言に似ています。

fixed-array-declaration:
      type-ident variable-ident [value] 

次に例を示します。

colortype palette[8]; --> colortype palette[8];

変数宣言を型宣言と混同するプログラマがよくいます。rpcgen は、変数宣言をサポートしないことに注意してください。この例は、コンパイルされないプログラムです。

int data[10];
program P {
   version V {
      int PROC(data) = 1;
 	} = 1;
} = 0x200000;

上記の例は、変数宣言なのでコンパイルされません。

int data[10]

代わりに以下を使用します。

typedef int data[10];

または

struct data {int dummy [10]};

可変長配列宣言

可変長配列宣言は、C 構文とはまったく異なります。XDR 言語は、構文を使用しないで山括弧でくくります。

variable-array-declaration:
      type-ident variable-ident <value>
      type-ident variable-ident < > 

最大サイズは山括弧内で指定します。配列のサイズにこだわらない場合は、サイズを省略することができます。

int heights<12>; /* 最高 12 項目 */
int widths<>; /* 項目数に制限なし */

可変長配列が C 構文とまったく異なるので、これらの宣言はコンパイルされて struct 宣言になります。たとえば、heights 宣言はコンパイルされて struct になります。

struct {
   u_int heights_len;               /* # 配列の項目番号 */
 	int *heights_val;                /* 配列へのポインタ */
} heights;

配列の項目の番号は、_len 構成要素に、配列へのポインタは _val 構成要素に格納されます。各構成要素名のはじめの部分は、宣言された XDR 変数名 (heights) と同じです。

ポインタ宣言

XDR には、C とまったく同じポインタ宣言が作成されます。アドレスポインタはネットワーク上で送信されませんが。その代わり、XDR ポインタは、リストおよびツリーなどの再帰的なデータ型を送信するのに便利です。この型は、XDR 言語では「ポインタ」ではなく、「オプション・データ」と呼ばれます。

ポインタ宣言:
 	type-ident *variable-ident 

次に例を示します。

listitem *next; --> listitem *next;

構造体

RPC/XDR struct は C struct とほぼ同様に宣言されます。RPC/XDR struct の宣言は次のようになります。

struct-definition:
 	struct struct-ident "{"
      declaration-list
 	"}" 
declaration-list:
	 	  declaration ";"
 	  declaration ";" declaration-list

次の左の部分は二次元の座標の XDR 構造体の例で、右の部分はそれを C 言語にコンパイルした構造体です。

struct coord {                 struct coord {
 	int x;            -->           int x;
 	int y;                          int y;
};                             };
                               typedef struct coord coord;

出力は、出力の末端部で追加された typedef 以外は入力と同じです。これによって、項目を宣言する時に、struct coord のかわりに coord を使用することができます。

共用体

XDR 共用体は、識別された共用体なので、C の共用体とは似ていません。どちらかというと、Pascal 変数レコードに似ています。

union-definition:
	"union" union-ident "switch" "("simple declaration")" "{"
 		case-list
 	"}" 
case-list:
 	"case" value ":" declaration ";"
 	"case" value ":" declaration ";" case-list
 	"default" ":" declaration ";" 

以下は、「読み取りデータ」操作の結果として返された型の例です。エラーがない場合は、データのブロックが返されます。エラーがある場合は、何も返されません。

union read_result switch (int errno) {
 	case 0:
 		opaque data[1024];
 	default:
 		void;
 	};

C 言語にコンパイルされると次のようになります。

struct read_result {
 	int errno;
 	union {
 		char data[1024];
 	} read_result_u;
};
typedef struct read_result read_result;

出力 struct の共用体構成要素の名前は、接尾語 _u を除いて型名前と同じです。

プログラム

RPC プログラムは、次の構文を使用して宣言します

program-definition:
 	"program" program-ident "{"
 		version-list
 	"}" "=" value;
version-list:
 	version ";"
 	version ";" version-list
version:
 	"version" version-ident "{"
 		procedure-list
 	"}" "=" value;
procedure-list:
 	procedure ";"
 	procedure ";" procedure-list
procedure:
 	type-ident procedure-ident "(" type-ident ")" "=" value;     

-N オプションが指定されると、rpcgen は次の構文も認識できます。

手続き:
 	type-ident procedure-ident "(" type-ident-list ")" "=" value;
type-ident-list:
 	type-ident
 	type-ident "," type-ident-list 

次に例を示します。

/*
 * time.x: 時間を取得、または設定します。
 * 時間は、1970 年 1 月 1 日 0:00 から経過した秒数で表されます。
 */
program TIMEPROG {
   version TIMEVERS {
      unsigned int TIMEGET(void) = 1;
 		void TIMESET(unsigned) = 2;
 	} = 1;
} = 0x20000044;

void という引数の型は、引数が引き渡されないことを意味しています。

このファイルは、コンパイルされると、出力ヘッダーファイル内で以下の #define 文になります。

#define TIMEPROG 0x20000044
#define TIMEVERS 1
#define TIMEGET 1
#define TIMESET 2

RPC 言語規則の例外

RPC 言語規則には、例外があります。

C 形式 モード

この節では、rpcgen の C 形式モードの機能について説明します。これらの機能は、void 引数の引き渡しに関してかかわっています。値が void の場合、引数が引き渡される必要はありません。

ブール値

C には組み込み型のブール型はありません。ただし、RPC ライブラリは、TRUE または FALSE のうちいずれかの bool_t と呼ばれるブール値を使用します。XDR 言語で型 bool として宣言されたパラメータは、コンパイルされると、出力ヘッダーファイルで bool_t になります。

次に例を示します。

bool married; --> bool_t married;

文字列

C 言語は組み込み型の文字列型ではありませんが、代わりに null で終了する char * 規則を使用します。C では、文字列は通常 null で終了する単一配列であるとみなされます。

XDR 言語では、string キーワードを使用して文字列が宣言されて、出力ヘッダーファイルで char * 型にコンパイルされます。山括弧でくくられた最大サイズは、文字列で使用できる最大文字数を指定します (NULL文字をカウントしません)。 任意の文字列のサイズを表す場合は、最大サイズを省略することができます。

次に例を示します。

string name<32>;   --> char *name;
string longname<>; --> char *longname;

注 -

NULL 文字列は引き渡されません。ただし、0 長の文字列 (つまりターミネータだけ、または NULL バイト) は引き渡されます。


隠されたデータ

隠されたデータは、未入力のデータ、つまり任意のバイトのシーケンスを記述するために、XDR で使用されます。隠されたデータは、固定長または可変長配列として宣言できます。次に例を示します。

opaque diskblock[512]; --> char diskblock[512];
opaque filedata<1024>; --> struct {
                             u_int filedata_len;
                             char *filedata_val;
                         } filedata;

Voids

void 宣言では、変数を指定できません。宣言は、void だけで、void 宣言が使用されるのは、共用体およびプログラム定義 (遠隔手続きの引数または結果として、引数が引き渡されなかったなどで使用される) の 2 ヶ所だけです。

rpcbind プロトコル

rpcbind は RPC のプログラム番号とバージョン番号を汎用アドレスにマップし、遠隔プログラムの動的結合を可能にします。

rpcbind はそれをサポートしているトランスポートのよく知られたアドレスに結合しています。他のプログラムは、動的に割り当てられたアドレスを rpcbind で登録します。rpcbind は、それらのアドレスを一般に使用できるようにします。汎用アドレスとは、トランスポートに依存したアドレスで、文字列で表現されています。汎用アドレスは、各トランスポートのアドレス管理者が定義します。

rpcbind はブロードキャスト RPC にも利用できます。RPC プログラムは異なるマシン上で異なるアドレスを持っているため、これらすべてのプログラムに直接ブロードキャストする方法はありません。ところが、rpcbind のアドレスはわかっているため、クライアントが特定のプログラムにブロードキャストするには、送信先マシン上の rpcbind プロセスにメッセージを送ります。rpcbind はブロードキャストメッセージを取り出し、クライアントが指定したローカルサービスを呼び出します。rpcbind はローカルサービスから応答を受け取ると、それをクライアントに渡します。


例 B-5 rpcbind プロトコル仕様 (RPC 言語で記述)

/*
 * rpcb_prot.x
 * RPCBIND プロトコルを RPC 言語で記述 
 */
/*
 * (プログラム、バージョン、ネットワーク ID) を汎用アドレスにマップ
 */
struct rpcb {
 rpcproc-t r_prog;                /* プログラム番号 */
	rpcvers-t r_vers;               /* バージョン番号 */
	string r_netid<>;               /* ネットワーク ID */
	string r_addr<>;                /* 汎用アドレス */
	string r_owner<>;               /* サービスの所有者 */ };
/* マップリスト */
struct rpcblist {
 rpcb rpcb_map;
	struct rpcblist *rpcb_next;
};
 
/* 遠隔呼び出しの引数 */
struct rpcb_rmtcallargs {
	rpcprog_t prog;             /* プログラム番号 */
	rpcvers_t vers;             /* バージョン番号 */
	rpcproc_t proc;             /* 手続き番号 */
	opaque args<>;                  /* 引数 */
};
 
/* 遠隔呼び出しの戻り値 */
struct rpcb_rmtcallres {
	string addr<>;                  /* 遠隔汎用アドレス */
	opaque results<>;               /* 結果 */
};
 
/*
 * rpcb_entry には、特定トランスポート上のサービスアドレスと、
 * それに関連した netconfig 情報がマージされて入っています。
 * RPCBPROC_GETADDRLIST は、rpcb_entry のリストを返します。
 * r_nc_* フィールドで使用する値については、netconfig.h を参照してください。
 */
struct rpcb_entry {
	string          r_maddr<>;      /* マージされたサービスアドレス */
	string          r_nc_netid<>;   /* netid フィールド */
 unsigned int   r_nc_semantics; /* トランスポートのセマンティクス */
	string          r_nc_protofmly<>; /* プロトコルファミリ */
	string          r_nc_proto<>;   /* プロトコル名 */
};
 
/* サービスがサポートしているアドレスリスト */
struct rpcb_entry_list {
 rpcb_entry rpcb_entry_map;
	struct rpcb_entry_list *rpcb_entry_next;
};
 
typedef rpcb_entry_list *rpcb_entry_list_ptr;
 
/* rpcbind 統計情報 */
const rpcb_highproc_2 = RPCBPROC_CALLIT;
const rpcb_highproc_3 = RPCBPROC_TADDR2UADDR;
const rpcb_highproc_4 = RPCBPROC_GETSTAT;
const RPCBSTAT_HIGHPROC = 13;  /* rpcbind V4 の手続き数 + 1 */
const RPCBVERS_STAT = 3;  /* rpcbind V2,V3,V4 だけのために提供 */
const RPCBVERS_4_STAT = 2;
const RPCBVERS_3_STAT = 1;
const RPCBVERS_2_STAT = 0;
 
/* getport と getaddr の全状態のリンクリスト */
struct rpcbs_addrlist {
 rpcprog_t prog;
	rpcvers_t vers;
	int success;
 int failure;
	string netid<>;
	struct rpcbs_addrlist *next;
};
 
/* rmtcall の全状態のリンクリスト */
struct rpcbs_rmtcalllist {
	rpcprog_t prog;
 rpcvers_t vers;
	rpcproc_t proc;
 int success;
 int failure;
	int indirect;   /* callit か indirect かを示す */
 string netid<>;
 struct rpcbs_rmtcalllist *next;
};
 
typedef int rpcbs_proc[RPCBSTAT_HIGHPROC];
typedef rpcbs_addrlist *rpcbs_addrlist_ptr;
typedef rpcbs_rmtcalllist *rpcbs_rmtcalllist_ptr;
 
struct rpcb_stat {
	rpcbs_proc              info;
 int                     setinfo;
	int                     unsetinfo;
 rpcbs_addrlist_ptr      addrinfo;
	rpcbs_rmtcalllist_ptr   rmtinfo;
};
 
/*
 * 監視される rpcbind の各バージョンに対して rpcb_stat 構造体が
 * 1 つずつ返されます。
 */
typedef rpcb_stat rpcb_stat_byvers[RPCBVERS_STAT];
/* rpcbind 手続き */
program RPCBPROG {
	version RPCBVERS {
		void
		RPCBPROC_NULL(void) = 0;
 
		/*
		 * この手続きは、[r_prog, r_vers, r_addr, r_owner, r_netid] の
		 * 組み合わせを登録します。rpcbind サーバーは、セキュリティ上の理由
		 * から、この手続きへの要求をループバックトランスポートだけで受け入れ
 	 * ます。 正常終了では TRUE、異常終了では FALSE が返されます。
		 */
 	bool
		RPCBPROC_SET(rpcb) = 1;
 
	 /*
		 *この手続きは、[r_prog, r_vers, r_owner, r_netid] の
 
		 * 組み合わせの登録を解除します。
		 * vers がゼロの場合は、全バージョンを登録解除します。
 	 * この手続きへの要求は、ループバックトランスポートだけで受け入れます。
		 * 正常終了では TRUE、異常終了では FALSE が返されます。
		 */
		bool
	 RPCBPROC_UNSET(rpcb) = 2;
 
		/*
 	 * この手続きは、[r_prog, r_vers, r_netid] の組み合わせが登録さ
		 * れている汎用アドレスを返します。r_addr が指定されていれば、
		 * 汎用アドレスが r_addr にマージされて返されます。r_owner は無視
		 * します。 異常終了の場合は、FALSE が返されます。
 	 */
		string
		RPCBPROC_GETADDR(rpcb) = 3;
 
	 /* この手続きは、全マップのリストを返します。 */
 
		rpcblist
		RPCBPROC_DUMP(void) = 4;
 
	 /*
		 * この手続きは、遠隔マシン上の手続きを呼び出します。
		 * 手続きが登録されていない場合は何もしません。
		 * すなわち、エラー情報も返しません。
 	 */
		rpcb_rmtcallres
		RPCBPROC_CALLIT(rpcb_rmtcallargs) = 5;
 
		/*
		 * この手続きは、rpcbind サーバーシステムの時刻を返します。
		 */
		unsigned int
		RPCBPROC_GETTIME(void) = 6;
 
		struct netbuf
		RPCBPROC_UADDR2TADDR(string) = 7;
 
		string
		RPCBPROC_TADDR2UADDR(struct netbuf) = 8;
 
		} = 3;
		version RPCBVERS4 {
 	bool
		RPCBPROC_SET(rpcb) = 1;
 
	 bool
		RPCBPROC_UNSET(rpcb) = 2;
 
	 string
		RPCBPROC_GETADDR(rpcb) = 3;
 
		rpcblist_ptr
		RPCBPROC_DUMP(void) = 4;
 
		/*
		 * 注: RPCBROC_BCAST と CALLIT の機能は同じです。
		 * 新たな名前を付けた目的は、ブロードキャスト RPC にはこの手続きを
		 * 使用し、間接呼び出しには RPCBPROC_INDIRECT を
		 * 使用することを示すためです。
		 */
		rpcb_rmtcallres
		RPCBPROC_BCAST(rpcb_rmtcallargs) = RPCBPROC_CALLIT;
 
	 unsigned int
		RPCBPROC_GETTIME(void) = 6;
 
		struct netbuf
		RPCBPROC_UADDR2TADDR(string) = 7;
 
		string
		RPCBPROC_TADDR2UADDR(struct netbuf) = 8;
 
/*
	 * この手続きは、RPCBPROC_GETADDR と同じですが、指定された
 * バージョン番号がない場合はアドレスを返さない点が異なります。
 */
		string
		RPCBPROC_GETVERSADDR(rpcb) = 9;
 
		/*
		 * この手続きは、遠隔マシン上の手続きを呼び出します。
		 * 手続きが登録されていない場合は、エラー情報を
		 * 返します。
		 */
		rpcb_rmtcallres
		RPCBPROC_INDIRECT(rpcb_rmtcallargs) = 10;
 
		/*
		 * この手続きは、RPCBPROC_GETADDR と同じですが、(prog, vers) の
		 * 組み合わせで登録されているアドレスのリストを返す点が異なります。
		 */
		rpcb_entry_list_ptr
		RPCBPROC_GETADDRLIST(rpcb) = 11;
 
		/*
		 * この手続きは、rpcbind サーバーのアクティビティに関する統計情報を返します。
		 */
		rpcb_stat_byvers
		RPCBPROC_GETSTAT(void) = 12;
	} = 4;
} = 100000;
 

rpcbind の操作

rpcbind にアクセスするには、使用するトランスポートごとに割り当てられているアドレスを使用します。たとえば TCP/IP と UDP/IP の場合は、ポート番号 111 が割り当てられています。各トランスポートには、このようによく知られているアドレスがあります。以下には、rpcbind がサポートしている各手続きを説明します。

RPCBPROC_NULL

この手続きは何もしない手続きです。習慣的にどのプログラムでも、手続き 0 は引数も戻り値もない手続きとします。

RPCBPROC_SET

マシン上でプログラムが初めて使用可能になるときは、そのマシンで実行されている rpcbind に自分自身を登録します。登録時にプログラムが渡すのは、プログラム番号 prog、バージョン番号 vers、ネットワーク ID netid、サービス要求を待つ汎用アドレス uaddr です。

この手続きは、プログラムのマッピングに成功すれば TRUE、失敗すれば FALSE のブール値を返します。指定された (progversnetid) の組み合わせで既にマップされたものがあれば、新たなマップは行いません。

netiduaddr はどちらも NULL にはできません。また、netid には、呼び出しを行うマシン上のネットワーク ID が正しく指定されていなければなりません。

RPCBPROC_UNSET

プログラムが使用できなくなった場合は、同一マシン上の rpcbind で自分自身を登録解除する必要があります。

この手続きの引数と戻り値は、RPCBPROC_SET と同じです。(progversnetid) の組み合わせと uaddr のマッピングが削除されます。

netidNULL の場合は、(prog、vers、*) 組み合わせとそれに対応する汎用アドレスのマッピングがすべて削除されます。サービスの登録解除は、サービスの所有者かスーパーユーザーだけが実行できます。

RPCBPROC_GETADDR

プログラム番号 prog、バージョン番号 vers、ネットワークID netid を指定してこの手続きを呼び出すと、そのプログラムが呼び出し要求を待っている汎用アドレスが返されます。

引数の netid フィールドは無視され、要求が到着するトランスポートの netid から取り出します。

RPCBPROC_DUMP

この手続きは、rpcbind データベースの全エントリのリストを返します。

この手続きには引数がなく、戻り値は、プログラム、バージョン、ネットワーク ID、汎用アドレスのリストです。この手続きを呼び出すときは、データグラムトランスポートではなくストリームトランスポートを使用します。これは、大量のデータが返されるのを回避するためです。

RPCBPROC_CALLIT

この手続きを使用すると、汎用アドレスがわからなくても同一マシン上にある遠隔手続きを呼び出すことができます。この手続きの目的は、rpcbind の汎用アドレスを通して任意の遠隔プログラムにブロードキャストできるようにすることです。

パラメータ progversprocargs_ptr にはそれぞれプログラム番号、バージョン番号、手続き番号、遠隔手続きへの引数を指定します


注 -

この手続きは正常終了の場合は応答しますが、異常終了の場合は一切応答しません。


この手続きからは、遠隔プログラムの汎用アドレスと、遠隔手続きからの戻り値が返されます。

RPCBPROC_GETTIME

この手続きは、自分のマシンのローカル時刻を、1970 年 1 月 1 日午前 0 時からの秒数で返します。

RPCBPROC_UADDR2TADDR

この手続きは、汎用アドレスをトランスポート (netbuf) アドレスに変換します。この手続きは、uaddr2taddr() (netdir(3NSL) のマニュアルページを参照) と同じ機能を持ちます。名前 - アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。

RPCBPROC_TADDR2UADDR

この手続きは、トランスポート (netbuf) アドレスを汎用アドレスに変換します。この手続きは、taddr2uaddr() (netdir(3NSL) のマニュアルページを参照) と同じ機能を持ちます。名前 - アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。

rpcbind のバージョン 4

rpcbind のバージョン 4 では、これまでに説明した手続きのほかに、次に示す手続きが追加されています。

RPCBPROC_BCAST

この手続きは、バージョン 3 の RPCBPROC_CALLIT 手続きと同じです。新たな名前を付けたのは、この手続きはブロードキャスト RPC だけに使用することを示すためです。これに対して、次のテキストで定義する RPCBPROC_INDIRECT は、間接 RPC 呼び出しだけに使用します。

RPCBPROC_GETVERSADDR

この手続きは、RPCBPROC_GETADDR に似ています。異なる点は、rpcb 構造体の r_vers フィールドで目的のバージョンを指定できることです。そのバージョンが登録されていない場合、アドレスは返されません。

RPCBPROC_INDIRECT

この手続きは、RPCBPROC_CALLIT に似ていますが、エラーが起こった場合 (たとえば、呼び出すプログラムがシステムに登録されていない場合) にエラー情報を返す点が異なります。この手続きはブロードキャスト RPC には使用できません。間接 RPC 呼び出しだけに使用します。

RPCBPROC_GETADDRLIST

この手続きは、指定された rpcb エントリのアドレスリストを返します。クライアントはそのリストを使用して、サーバーと通信するための代替トランスポートを調べることができます。

RPCBPROC_GETSTAT

この手続きは、rpcbind サーバーのアクティビティに関する統計情報を返します。統計情報には、サーバーが受信した要求の種類と回数が示されます。


注 -

RPCBPROC_SETRPCBPROC_UNSET 以外の手続きはすべて、rpcbind が実行されているマシンとは別のマシン上のクライアントから呼び出すことができます。rpcbind は、RPCPROC_SETRPCBPROC_UNSET の要求だけはループバックトランスポートからでないと受け入れません。


参考文献

この付録、リファレンスで説明した技術内容やアーキテクチャに関する参考文献には、次のものがあります。

  1. 『Implementing Remote Procedure Calls』 Birrel, Andrew D.& Nelson, Bruce Jay 著、XEROX 社 CSL-83-7、1983 年 10 月

  2. 『VMTP: Versatile Message Transaction Protocol, Preliminary Version 0.3』 Cheriton, D. 著、Stanford 大学、1987 年 1 月

  3. 『New Direction in Cryptography, IEEE Transactions on Information Theory 』 Diffie & Hellman 著、Transactions on Information Theory IT-22、1976 年 11 月

  4. 『Time Server, RFC 738』Harrenstien, K. 著、Information Sciences Institute、1977 年 10 月

  5. 『Data Encryption Standard』 National Bureau of Standards、Federal Information Processing Standards Publication Publication 46、1977 年 1 月

  6. 『Transmission Control Protocol - DARPA Internet Program Protocol Specification, RFC 793』Postel, J. 著、Information Sciences Institute、1981 年 9 月

  7. 『User Datagram Protocol, RFC 768』Postel, J. 著、Information Sciences Institute、1980 年 8 月