付録 B では、RPC パッケージで使用しているメッセージプロトコルの仕様を説明します。メッセージプロトコルは XDR 言語を使用して示します。XDR についての詳細は、 付録 C 「XDR プロトコル仕様」 を参照してください。
呼び出される手続きの仕様が一意です。
要求メッセージに対する応答メッセージを提供します。
呼び出し側からサーバーへ、およびサーバーから呼び出し側への認証を提供します。さらに、RPC パッケージには、以下の現象を検出する機能があります。
RPC プロトコルの不一致
遠隔プログラムプロトコルのバージョンの不一致
プロトコルのエラー (手続きのパラメータの仕様が間違っているなど)
遠隔認証が失敗した原因
ネットワーク・ファイル・サービスが 2 つのプログラムで構成されていると考えてください。1 つのプログラムは、ファイルシステムへのアクセス制御やロックなど高レベルのアプリケーションを扱います。もう 1 つのプログラムは、低いレベルの入出力ファイルを扱い、「読込み」や「書込み」などの手続があります。ネットワークファイルサービスのクライアントマシンは、クライアントマシンのユーザーのために 2 つのプログラムに関連する手続きを呼び出します。クライアントサーバーモデルでは、遠隔手続き呼び出しは、サービスを呼び出す場合に使用します。
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 バージョン番号の最大値と最小値が返される
遠隔システム上で指定した遠隔プログラムが使用できない
遠隔プログラムは要求されているバージョン番号をサポートしていない。サポートしている遠隔プログラムバージョン番号の最大値と最小値が返される
要求されている手続き番号が存在しない。これは、呼び出し側のプロトコルエラーかプログラミングエラーであることが多い
サーバー側から見ると、遠隔手続きへの引数に誤りがある。このエラーもまた、クライアントとサービスの間のプロトコルの不一致による場合が多い
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 |
ユーザーが定義 |
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 プロトコルは遠隔手続き呼び出しを目的に作成されています。すなわち、各呼び出しメッセージがそれぞれ 1 つの応答メッセージに一致します。ところが、プロトコル自体はメッセージ引き渡しプロトコルなので、RPC 以外のプロトコルで対応できます。RPC パッケージでサポートされている RPC 以外のプロトコルとしては、バッチとブロードキャストがあります。
バッチを使用すると、クライアントは任意の大きさの呼び出しメッセージシーケンスをサーバーに送信できます。一般にバッチでは、トランスポートとして TCP のような信頼性の高いバイトストリームプロトコルを使用します。バッチを使用すると、クライアントはサーバーからの応答を待たず、サーバーもバッチ要求に対しては応答しません。バッチ呼び出しシーケンスを終了するには、通常、非バッチの RPC 呼び出しを行なってパイプラインをフラッシュします。このときは肯定応答が返されます。詳細については、「バッチ処理」の節を参照してください。
ブロードキャスト RPC では、クライアントがブロードキャストパケットをネットワークに送信し、それに対する数多くの応答を待ちます。ブロードキャスト RPC では、トランスポートに UDP のような非接続型のパケットベースプロトコルを使用します。ブロードキャストプロトコルをサポートするサーバーは、要求を正しく処理できたときだけ応答を返し、エラーが起これば応答は返しません。ブロードキャスト RPC では rpcbind サービスを使用してそのセマンティクスを達成します。詳細については、 「ブロードキャスト RPC」、および 「rpcbind プロトコル」 の節を参照してください。
この節では、RPC メッセージプロトコルを、XDR データ記述言語を使用して説明します。メッセージは、例 B-1で示すようにトップダウン形式で定義します。
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 つの値が符号化されています。
レコード内の最終フラグメントかどうかを示すブール値 (ビット値が 1 の場合は最終フラグメント)
フラグメントデータの長さ (バイト数) を示す 31 ビットの符号なしバイナリ値。最終フラグメントを示すブール値はヘッダーの最上位ビットに入り、データ長は下位 31 ビットに入る。このレコード仕様は、XDR 標準形式には含まれない
認証パラメータは内容が隠されていますが、以降の RPC プロトコルで自由に解釈できます。この節では、既に定義されているタイプの認証について説明します。別のサイトでは自由に新たな認証タイプを作成し、プログラム番号割り当て規則と同様の認証タイプ番号割り当て規則に従って、認証タイプ番号を割り当てることができます。認証タイプ番号は Sun で保守、管理しています。認証番号の割り当てを希望されるユーザーは、RPC プログラム番号を割り当てるときと同様に、「プログラム番号の登録」に示すように、Sun の RPC 管理者に連絡してください。
呼び出し側は自身を証明せず、また、サーバー側も呼び出し側が誰でもかまわないという呼び出しもあります。その場合は RPC メッセージの認証証明、ベリファイア、応答ベリファイアの flavor (opaque_auth 共用体の要素識別子) は AUTH_NONE にします。AUTH_NONE タイプの認証を使用するときは、body フィールドの長さをゼロにします。
これは、AUTH_UNIX として知られる以前に説明した認証 flavor と同じです。遠隔手続きを呼び出す側では、従来の UNIX のプロセス許可認証を使用して自分自身を証明する場合があります。そのような RPC 呼び出しメッセージでは、opaque_auth の flavor は AUTH_SYSとなります。body には、次に示す構造体が符号化されます。
struct auth_sysparms { unsigned int stamp; string machinename<255>; uid_t uid; gid_t gid; gid_t gids<10>; };
stamp は、呼び出し側のマシンで生成できる任意の ID
machinename は、呼び出し側のマシン名
uid 呼び出し側の実効ユーザー ID
gid は、呼び出し側の実効グループ
gids は、呼び出し側がメンバーであるグループの可変長配列
認証証明に伴うベリファイアの flavor は AUTH_NONE でなければなりません。
AUTH_SYSタイプの認証を使用するときは、サーバーからの応答メッセージに入っている応答ベリファイアの flavor は AUTH_NONE か AUTH_SHORT のどちらかです。
AUTH_SHORT の場合、応答ベリファイアの文字列には short_hand_verf 構造体が符号化されています。この隠された構造体を、元の AUTH_SYS 認証証明の代わりにサーバーに渡すことができます。
サーバー側では、隠された short_hand_verf 構造体 (AUTH_SHORT タイプの応答ベリファイアによって返される) を呼び出し側の元の認証証明にマップするキャッシュを保存します。呼び出し側は、新たな認証証明を使用してネットワークの帯域幅とサーバーの CPU サイクルを保存できます。
サーバー側では、隠された short_hand_verf 構造体をいつでもフラッシュできます。そうすると、遠隔手続き呼び出しメッセージは認証エラーにより拒絶されます。エラー原因は AUTH_REJECTEDCRED になります。この場合、呼び出し側では AUTH_SYS タイプの元の認証証明を試すこともできます。 図 B-1 を参照してください。
AUTH_SYS タイプの認証には次のような問題があります。
異なるオペレーティングシステムのマシンが同じネットワークに接続している場合に、呼び出し側の ID が一意に決まるとは限らない
ベリファイアがないため簡単に認証証明をごまかすことができます。AUTH_DES タイプの認証はこの 2 つの問題を解決するための方法
最初の問題を解決するには、呼び出し側をオペレーティングシステム固有の整数ではなく単純文字列で指定します。この文字列のことを、呼び出し側の netname (ネットワーク名) といいます。サーバーでは、呼び出し側の識別のためにだけ呼び出し側の名前を使用します。したがって、名前ドメイン内では呼び出し側を一意に識別できるようなネットワーク名を設定しなければなりません。
遠隔サーバーを呼び出す各ユーザーに一意のネットワーク名を生成するのは、それぞれのオペレーティングシステムで実現されている AUTH_DES 認証機能の責任です。オペレーティングシステムでは既に、システムのローカルユーザーを識別しています。通常はこれを単純に拡張してネットワーク名とします。たとえば、ユーザー ID を 515 とすると、「UNIX.515@sun.com」いうネットワーク名を割り当てることができます。このネットワーク名は、確実に一意の名前にするために 3 つの要素で構成されています。後ろから見ると、インターネットには sun.com という名前ドメインは 1 つしかありません。その名前ドメイン内では、ID が 515 の UNIX ユーザーは 1 人しかいません。ただし、同一の名前空間にある別のオペレーティングシステム (たとえば VMS) 上のユーザーが偶然同じユーザー ID を持つことはあります。そのような 2 人のユーザーを区別するために、オペレーティングシステム名を追加します。そうすると、一方のユーザーは「UNIX.515@sun.com」となり、他方のユーザーは「VMS.515@sun.com」となります。
最初のフィールドは実際にはオペレーティングシステム名とは別の命名方法で指定します。現在、その命名方法とオペレーティングシステム名とがほぼ 1 対 1 に対応しているだけです。命名方法の標準が確立すれば、最初のフィールドにはオペレーティングシステム名ではなくその標準規約に従った名前を入れます。
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 を受け取ることがあります。その原因は、サーバーのニックネームテーブルの大きさには制限があり、足りなくなるとエントリが失われることがあるためです。その場合、クライアントは元の認証証明を再送信しなければなりません。サーバーはそれに新たなニックネームを割り当てます。もしもサーバーがクラッシュすると、ニックネームテーブル全体が失われ、全クライアントが元の認証証明を再送信しなければなりません。
この暗号化手法では、2 つの定数 PROOT と HEXMODULUSを使用します。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 の S 実装に使用されたカーネルは、Kerberos のコードをオペレーティングシステムのカーネルへコンパイルしないで、kerbd という代理の RPC デーモンを使用します。このデーモンは、以下の 3 つの手続きをエクスポートします。詳細については、kerbd(1M) のマニュアルページを参照してください。
KGETKCRED
KSETKCRED
UNIX 固有の KGETUCRED
KGETUCRED は、主な名前がサーバーにもわかるユーザー名にマップされると想定し、ユーザーの ID、グループ ID、およびグループリストを返す
Kerberos の内容を的確に説明するには、現在 Kerberos を実装しているサービスであるネットワークファイルシステム (NFS) を例として使用するのが良いでしょう。サーバー s の NFS サービスは、nfs.s という周知の主体名を持つとします。クライアント c の特権ユーザーは、root という一次名と、インスタンス c をもっているとします。AUTH_DES の場合とは異なり、ユーザーのチケット発行用のチケットの期限が切れた場合は、kinit() を再び呼び出さなければならないことに注意してください。Kerberos マウントの NFS サービスは、新しいチケット発行用のチケットを獲得するまで成功しません。
この節全体を通して、AUTH_KERB を使用した NFS マウント要求について説明します。マウント要求は、ルートで実行されるので、ユーザーの識別情報は、root.c.になります。
クライアント c は、マウントするディレクトリのファイルハンドルを獲得するために、サーバー s に MOUNTPROC_MOUNT 要求を実行します。クライアントのマウントプログラムは、ファイルハンドル、mountflavor、時間同期アドレス、サーバーの既知の主体名である nfs.s を、クライアントのカーネルに渡して、NFS マウントシステムコールを実行します。次に、クライアントのカーネルが時間同期ホストでサーバーに接続し、クライアント/ サーバー間の時間差を取得します。
クライアントのカーネルは、次の RPC 呼び出しを行います。(1) チケットおよびセッションキーを獲得するためにのローカルの kerbd への KSETKCRED 呼び出し。(2) フルネームの資格およびベリファイアを使用した、サーバーの NFS サービスへの NFSPROC_GETATTR 呼び出し。サーバーは、呼び出しを受信し、ローカルの kerbd へ KGETKCRED 呼び出しを行なってクライアントのチケットを検査します。
サーバーの kerbd と Kerberos ライブラリは、チケットの暗号を解除し、主体名および DES セッション鍵を他のデータの中に返します。サーバーは、チケットがまだ有効であることをチェックし、セッション鍵を使用して資格、ベリファイアの DES の暗号化された部分を複号化し、ベリファイアが有効であることを検査します。
この時に返される可能性のある Kerberos 認証エラーは、下記のとおりです。
ベリファイアが無効な場合 (資格にある暗号が解除された win と、ベリファイアでの win +1 は、一致しない)、またはタイムスタンプがウィンドウの範囲外の場合は、AUTH_BADCRED が返される
エラーを受信しない場合、サーバーはクライアントの識別情報をキャッシュに書き込み、NFS 回答に返されるニックネーム (小さい整数) を割り当てります。その時サーバーは、クライアントがサーバーと同じ領域かどうかをチェックします。クライアントがサーバーと同じ領域の場合、サーバーは、KGETUCRED をローカルの kerbd に呼び出して、主体名を UNIX の資格に変換します。変換できない場合、ユーザーは匿名であるとマークされます。サーバーは、ファイルシステムのエクスポート情報に対するこれらの資格を検査します。次の 3 つのケースを考えてください。
KGETUCRED 呼び出しが失敗し、匿名の要求が受け入れられた場合、匿名のユーザーに UNIX 資格が割り当てられます。
KGETUCRED 呼び出しが失敗し、匿名の要求が受け入れられない場合、NFS 呼び出しは失敗し、AUTH_TOOWEAK が返されます。
KGETUCRED 呼び出しが成功する場合は、資格が割り当てられ、その後にルートのアクセス権のチェックも含む、正常な保護検査が行われます。
次に、サーバーが、ニックネームおよびサーバーのベリファイアを組み込んで NFS 回答を送信します。クライアントは回答を受信し、ベリファイアの複号化と妥当性検査を行い、今後の呼び出しのためにニックネームを格納します。クライアントがサーバーに 2 番目の NFS 呼び出しを行うと、先にサーバーに書込まれた呼び出しが繰り返されます。クライアントのカーネルが、以前に記述されたニックネーム資格およびベリファイアを使用して、サーバーの NFS サービスに NFSPROC_STATVFS 呼び出しを行います。サーバーは呼び出しを受信し、ニックネームの妥当性検査を行います。これが範囲外であれば、エラー AUTH_BADCRED を返します。サーバーは、獲得したばかりのセッション鍵を使用して、ベリファイアの DES の暗号化された部分を複号化し、ベリファイアの妥当性検査を行います。
この時に返される可能性のある Kerberos 認証エラーは、次のとおりです。
タイムスタンプが無効で、やり直しが検出されるか、タイムスタンプがウィンドウの範囲外の場合は、AUTH_REJECTEDVERF を返す
エラーが受信されない場合、サーバーは、ニックネームを使用して、呼び出し側の UNIX 資格を検出します。それから、サーバーはファイルシステムのエクスポート情報に対するこれらの資格を検査し、ニックネームおよびサーバーのベリファイアを組み込んだ NFS 回答を送信します。クライアントは回答を受信し、ベリファイアの複号化および妥当性検査を行い、これからの呼び出しのためにニックネームを格納します。最後に、クライアントのNFS マウントシステムコールが返り、要求が終了します。
例 B-3 (AUTH_KERB) は、例 B-2 で示された AUTH_DES と似ています。両者の違いに注意してください。
#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; /* クライアントの新しいニックネーム */ }; |
XDR データ型を形式言語で記述する必要があるのと同様に、これらの XDR データ型に対して作用する手続きも、形式言語で記述する必要があります。XDR 言語の拡張版である RPC 言語は、XDR 言語を形式言語で記述するための言語です。次に、RPC 言語についての例を示します。
例 B-4 は、単純な 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 は、古いクライアントプログラムと互換性を持たせる場合に便利ですが、このプログラムが完成すると、プロトコルから完全に削除されることがあります。
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 |
program、version のキーワードが追加されますが、識別子としては使用できない
バージョン名およびバージョン番号は、プログラム定義範囲内で一度しか指定できない
手続き名および手続き番号は、バージョン定義内で一度しか指定できまない
プログラム識別子は、定数および型識別子と同じ名前空間にある
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
次の例では定数 DOZEN を 12 に定義します。
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
simple-declaration: type-ident variable-ident
次に例を示します。
colortype color; --> colortype color;
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
を除いて型名前と同じです。
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
この節では、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;
void 宣言では、変数を指定できません。宣言は、void だけで、void 宣言が使用されるのは、共用体およびプログラム定義 (遠隔手続きの引数または結果として、引数が引き渡されなかったなどで使用される) の 2 ヶ所だけです。
rpcbind は RPC のプログラム番号とバージョン番号を汎用アドレスにマップし、遠隔プログラムの動的結合を可能にします。
rpcbind はそれをサポートしているトランスポートのよく知られたアドレスに結合しています。他のプログラムは、動的に割り当てられたアドレスを rpcbind で登録します。rpcbind は、それらのアドレスを一般に使用できるようにします。汎用アドレスとは、トランスポートに依存したアドレスで、文字列で表現されています。汎用アドレスは、各トランスポートのアドレス管理者が定義します。
rpcbind はブロードキャスト RPC にも利用できます。RPC プログラムは異なるマシン上で異なるアドレスを持っているため、これらすべてのプログラムに直接ブロードキャストする方法はありません。ところが、rpcbind のアドレスはわかっているため、クライアントが特定のプログラムにブロードキャストするには、送信先マシン上の rpcbind プロセスにメッセージを送ります。rpcbind はブロードキャストメッセージを取り出し、クライアントが指定したローカルサービスを呼び出します。rpcbind はローカルサービスから応答を受け取ると、それをクライアントに渡します。
/* * 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 にアクセスするには、使用するトランスポートごとに割り当てられているアドレスを使用します。たとえば TCP/IP と UDP/IP の場合は、ポート番号 111 が割り当てられています。各トランスポートには、このようによく知られているアドレスがあります。以下には、rpcbind がサポートしている各手続きを説明します。
この手続きは何もしない手続きです。習慣的にどのプログラムでも、手続き 0 は引数も戻り値もない手続きとします。
マシン上でプログラムが初めて使用可能になるときは、そのマシンで実行されている rpcbind に自分自身を登録します。登録時にプログラムが渡すのは、プログラム番号 prog、バージョン番号 vers、ネットワーク ID netid、サービス要求を待つ汎用アドレス uaddr です。
この手続きは、プログラムのマッピングに成功すれば TRUE、失敗すれば FALSE のブール値を返します。指定された (prog、 vers、 netid) の組み合わせで既にマップされたものがあれば、新たなマップは行いません。
netid と uaddr はどちらも NULL にはできません。また、netid には、呼び出しを行うマシン上のネットワーク ID が正しく指定されていなければなりません。
プログラムが使用できなくなった場合は、同一マシン上の rpcbind で自分自身を登録解除する必要があります。
この手続きの引数と戻り値は、RPCBPROC_SET と同じです。(prog、vers、netid) の組み合わせと uaddr のマッピングが削除されます。
netid が NULL の場合は、(prog、vers、*) 組み合わせとそれに対応する汎用アドレスのマッピングがすべて削除されます。サービスの登録解除は、サービスの所有者かスーパーユーザーだけが実行できます。
プログラム番号 prog、バージョン番号 vers、ネットワークID netid を指定してこの手続きを呼び出すと、そのプログラムが呼び出し要求を待っている汎用アドレスが返されます。
引数の netid フィールドは無視され、要求が到着するトランスポートの netid から取り出します。
この手続きは、rpcbind データベースの全エントリのリストを返します。
この手続きには引数がなく、戻り値は、プログラム、バージョン、ネットワーク ID、汎用アドレスのリストです。この手続きを呼び出すときは、データグラムトランスポートではなくストリームトランスポートを使用します。これは、大量のデータが返されるのを回避するためです。
この手続きを使用すると、汎用アドレスがわからなくても同一マシン上にある遠隔手続きを呼び出すことができます。この手続きの目的は、rpcbind の汎用アドレスを通して任意の遠隔プログラムにブロードキャストできるようにすることです。
パラメータ prog、vers、proc、args_ptr にはそれぞれプログラム番号、バージョン番号、手続き番号、遠隔手続きへの引数を指定します
この手続きは正常終了の場合は応答しますが、異常終了の場合は一切応答しません。
この手続きからは、遠隔プログラムの汎用アドレスと、遠隔手続きからの戻り値が返されます。
この手続きは、自分のマシンのローカル時刻を、1970 年 1 月 1 日午前 0 時からの秒数で返します。
この手続きは、汎用アドレスをトランスポート (netbuf) アドレスに変換します。この手続きは、uaddr2taddr() (netdir(3NSL) のマニュアルページを参照) と同じ機能を持ちます。名前 - アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。
この手続きは、トランスポート (netbuf) アドレスを汎用アドレスに変換します。この手続きは、taddr2uaddr() (netdir(3NSL) のマニュアルページを参照) と同じ機能を持ちます。名前 - アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。
rpcbind のバージョン 4 では、これまでに説明した手続きのほかに、次に示す手続きが追加されています。
この手続きは、バージョン 3 の RPCBPROC_CALLIT 手続きと同じです。新たな名前を付けたのは、この手続きはブロードキャスト RPC だけに使用することを示すためです。これに対して、次のテキストで定義する RPCBPROC_INDIRECT は、間接 RPC 呼び出しだけに使用します。
この手続きは、RPCBPROC_GETADDR に似ています。異なる点は、rpcb 構造体の r_vers フィールドで目的のバージョンを指定できることです。そのバージョンが登録されていない場合、アドレスは返されません。
この手続きは、RPCBPROC_CALLIT に似ていますが、エラーが起こった場合 (たとえば、呼び出すプログラムがシステムに登録されていない場合) にエラー情報を返す点が異なります。この手続きはブロードキャスト RPC には使用できません。間接 RPC 呼び出しだけに使用します。
この手続きは、指定された rpcb エントリのアドレスリストを返します。クライアントはそのリストを使用して、サーバーと通信するための代替トランスポートを調べることができます。
この手続きは、rpcbind サーバーのアクティビティに関する統計情報を返します。統計情報には、サーバーが受信した要求の種類と回数が示されます。
RPCBPROC_SET と RPCBPROC_UNSET 以外の手続きはすべて、rpcbind が実行されているマシンとは別のマシン上のクライアントから呼び出すことができます。rpcbind は、RPCPROC_SET と RPCBPROC_UNSET の要求だけはループバックトランスポートからでないと受け入れません。
この付録、リファレンスで説明した技術内容やアーキテクチャに関する参考文献には、次のものがあります。
『Implementing Remote Procedure Calls』 Birrel, Andrew D.& Nelson, Bruce Jay 著、XEROX 社 CSL-83-7、1983 年 10 月
『VMTP: Versatile Message Transaction Protocol, Preliminary Version 0.3』 Cheriton, D. 著、Stanford 大学、1987 年 1 月
『New Direction in Cryptography, IEEE Transactions on Information Theory 』 Diffie & Hellman 著、Transactions on Information Theory IT-22、1976 年 11 月
『Time Server, RFC 738』Harrenstien, K. 著、Information Sciences Institute、1977 年 10 月
『Data Encryption Standard』 National Bureau of Standards、Federal Information Processing Standards Publication Publication 46、1977 年 1 月
『Transmission Control Protocol - DARPA Internet Program Protocol Specification, RFC 793』Postel, J. 著、Information Sciences Institute、1981 年 9 月
『User Datagram Protocol, RFC 768』Postel, J. 著、Information Sciences Institute、1980 年 8 月