付録 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 メッセージを再送した場合、その手続きは 0 回以上実行されたと推論されます。応答が返された場合は、手続きが少なくとも一度は実行されたことが推測できます。
サーバーは、一度だけ実行というセマンティクスをある程度実現するため、以前にクライアントから受け取った要求を記憶しておいて、同じ要求を再受信しないようにする場合があります。その場合サーバーは、RPC 要求に必ず含まれているトランザクション ID を使用します。トランザクション ID は、主として RPC クライアントが、応答と要求との対応を調べるために使用します。クライアントアプリケーションでは、要求を再送信するときに以前のトランザクション ID を再使用することができます。サーバーアプリケーションでもこのことを確認していれば、要求を受信したときはトランザクション ID を記憶しておいて、同じ ID を持つ要求は再受信しないことができます。サーバーでは、以前と同じ要求かどうか調べるため以外の目的でトランザクション ID を使用することはできません。
一方、TCP のような信頼性の高いトランスポートを使用する場合、アプリケーションは、応答メッセージが返されれば、手続きは正確に 1 回実行されたと推論できます。応答メッセージが受け取られないと、アプリケーションがリモートプロシージャは実行されなかったと推論することはできません。 TCP のような接続型プロトコルを使用する場合も、サーバーのクラッシュに対応するために、アプリケーションでタイムアウトと再接続確立の操作が必要なことに注意してください。
クライアントとサービスの結合は、リモートプロシージャ呼び出しの仕様の一部ではありません。結合という重要で不可欠な機能は、より上位レベルのソフトウェアで行います。ソフトウェアは RPC 自体を使用できます。 rpcbind プロトコル を参照してください。
そのようなソフトウェアを開発する場合は、RPC プロトコルをネットワーク間のジャンプ - サブルーチン命令 (JSR 命令) と考えます。ローダーは、JSR 命令を実行可能にするために、ローダー自身も JSR 命令を使用します。同様に、ネットワークは RPC を実行可能にするためにネットワーク自身も RPC を使用します。
RPC プロトコルには、サービスに対してクライアントが自分自身を証明するため、またはその反対方向の証明のためのフィールドが用意されています。セキュリティやアクセス制御の機能は、メッセージ認証の上に成り立っており、何種類かの認証プロトコルをサポートできます。どのプロトコルを使用するかは、RPC ヘッダーの 1 フィールドで指定します。認証プロトコルについての詳細は、レコードマーク標準 の節を参照してください。
RPC 呼び出しメッセージには、呼び出される手続きを一意に識別する次の 3 つの符号なしフィールドがあります。
リモートプログラム番号
リモートプログラムのバージョン番
リモートプロシージャ番号
プログラム番号は、プログラム番号の登録にあるように、中央の 1 人の管理者が決定します。
プログラムを最初に作成したときは、バージョン番号は通常 1 になります。新規プロトコルは通常、品質と安定性がより高い、成熟したプロトコルへと進化します。このため、コールメッセージのバージョンフィールドで、呼び出し側が使用するプロトコルのバージョンを指定します。バージョン番号を使用することにより、これまで使用していたプロトコルと新規プロトコルとが同じサーバープロセスで使用可能になります。
手続き番号では、どの手続きを呼び出すかを指定します。手続き番号は、各プログラムのプロトコル仕様に記されています。たとえば、ファイルサービスのプロトコル仕様には、手続き番号 5 を読み取り、手続き番号 12 を書き込みなどと記されます。
リモートプログラムのプロトコルがバージョンが変わるたびに変更されるように、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 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, 901 San Antonio Road, Palo Alto, CA 94043
その際には rpcgen で生成した、プロトコルを記述する「.x」ファイルも同封してください。一意に識別できるプログラム番号を返送します。
標準 RPC サービスの RPC プログラム番号とプロトコル仕様は、/usr/include/rpcsvc のインクルードファイルに入っています。ただし、これらのサービスは登録されているサービスのほんの一部分にすぎません。
本来、RPC プロトコルはリモートプロシージャ呼び出しを目的に作成されています。すなわち、各呼び出しメッセージがそれぞれ 1 つの応答メッセージに一致します。ところが、プロトコル自体はメッセージ引き渡しプロトコルなので、RPC 以外のプロトコルで対応できます。RPC パッケージでサポートされている RPC 以外のプロトコルとしては、バッチとブロードキャストがあります。
バッチを使用すると、クライアントは任意の大きさのコールメッセージシーケンスをサーバーに送信できます。一般にバッチでは、トランスポートとして TCP のような信頼性の高いバイトストリームプロトコルを使用します。バッチを使用すると、クライアントはサーバーからの応答を待たず、サーバーもバッチ要求に対しては応答しません。バッチ呼び出しシーケンスを終了するには、通常、非バッチの RPC 呼び出しを行なってパイプラインをフラッシュします。このときは肯定応答が返されます。詳細については、バッチ処理 を参照してください。
ブロードキャスト RPC では、クライアントがブロードキャストパケットをネットワークに送信し、それに対する数多くの応答を待ちます。ブロードキャスト RPC では、トランスポートに UDP のような非接続型のパケットベースプロトコルを使用します。ブロードキャストプロトコルをサポートするサーバーは、要求を正しく処理できたときだけ応答を返し、エラーが起これば応答は返しません。ブロードキャスト RPC では rpcbind サービスを使用してそのセマンティクスを達成します。詳細については、 ブロードキャスト RPC と rpcbind プロトコル を参照してください。
この節では、RPC メッセージプロトコルを、XDR データ記述言語を使用して説明します。メッセージは、次の例のように、トップダウン方式で定義されます。
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 値は AUTH_NONE にします。flavor 値は opaque_auth 共用体の要素識別子です。AUTH_NONE 認証 flavor を使用する場合、その body の長さは 0 にします。
これは 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>;
};
呼び出し側のマシンで生成できる任意の ID
呼び出し側マシンの名前
呼び出し側の実効ユーザー ID
呼び出し側の実行グループ ID
呼び出し側がメンバーであるグループの可変長配列
資格に伴うベリファイアの 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 タイプの元の資格を試すこともできます。

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] 暗号化手法です。この暗号化方式については Diffie-Hellman の暗号化手法 で詳しく説明します。
この検証方法が正しく機能するためには、クライアントとサーバーで時刻が一致していなければなりません。ネットワークの時刻同期が保証できないときは、会話を始める前にクライアントの方でサーバーと時刻を合わせることができます。rpcbind の提供する手続き RPCBPROC_GETTIME を使用すれば、現在時刻を取り出すことができます。
サーバーはクライアントのタイムスタンプが正当なものかどうか判定します。2 番目以降のすべてのトランザクションに対して、サーバーは次の 2 つの項目をチェックします。
タイムスタンプが、同じクライアントからの以前のものより大きい値になっている
タイムスタンプが失効していない。サーバーの時刻が、クライアントのタイムスタンプにクライアントウィンドウと呼ばれる値を加えた時刻より後ならば、タイムスタンプは失効している。ウィンドウの値は、最初のトランザクションのときにクライアントが暗号化してサーバーに引き渡す。このウィンドウは資格の寿命とみなすことができる。
最初のトランザクションでは、サーバーはタイムスタンプが失効していないことを確認します。さらに検査を行うため、クライアントは最初のトランザクション内へウィンドウベリファイアと呼ばれる暗号化した値を送ります。このベリファイアはウィンドウの値より 1 だけ小さい値に、それ以外の場合はサーバーは資格を拒否します。
クライアントはサーバーから返されたベリファイアが正当なものかどうか調べなければなりません。サーバーは、クライアントから受信したタイムスタンプから 1 秒少ない値を暗号化してクライアントに送り返します。クライアントへこれ以外の値が返された場合、ベリファイアは拒否されます。
最初のトランザクションの後、サーバーの AUTH_DES 認証サブシステムは、クライアントへのベリファイアの中に整数のニックネームを返します。以降のトランザクションにおいて、クライアントはネットワーク名、暗号化された DES 鍵、およびウィンドウを毎回渡す代わりに、このニックネームを使用します。ニックネームは、サーバーが各クライアントのネットワーク名、復号化された DES 鍵、ウィンドウを保存しているテーブルのインデックスのようなものですが、クライアントからは隠されたデータとしてしか使用できません。
クライアントとサーバーのクロックは最初は同期していても、やがて同期が取れなくなることがあります。その場合、クライアント側の RPC サブシステムは RPC_AUTHERROR を受け取るので、その時点で再び同期を取る必要があります。
クライアントとサーバーの時刻が同期していてもクライアントが RPC_AUTHERROR を受け取ることがあります。サーバーのニックネームテーブルのサイズは限られており、必要に応じてエントリをフラッシュできます。その場合、クライアントは元の資格を再送信しなければなりません。サーバーはクライアントに新たなニックネームを割り当てます。もしもサーバーがクラッシュすると、ニックネームテーブル全体が失われ、全クライアントが元の資格を再送信しなければなりません。
次の例で資格を説明します。
/* * 資格は 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; /* クライアントの新しいニックネーム */};
この暗号化手法では、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 つの手続きをエクスポートします。
KSETKCRED は、一次名、インスタンス、領域が指定されると、暗号化されたチケットおよび DES セッション鍵を返す
KGETUCRED は UNIX 固有です。一次名がサーバーにもわかるユーザー名にマップされると想定し、ユーザーの 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 呼び出しを行います。
チケットとセッション鍵を取得するためのローカル kerbd への KSETKCRED
フルネーム資格とベリファイアを使用する、サーバーの NFS サービスへの NFSPROC_GETATTR。 サーバーは、呼び出しを受信し、ローカルの kerbd へ KGETKCRED 呼び出しを行なってクライアントのチケットを検査します。
サーバーの kerbd と Kerberos ライブラリは、チケットの暗号を解除し、主体名および DES セッション鍵を他のデータの中に返します。サーバーは、チケットがまだ有効であることをチェックし、セッション鍵を使用して資格、ベリファイアの DES の暗号化された部分を複号化し、ベリファイアが有効であることを検査します。
この時に返される可能性のある Kerberos 認証エラーは、次のとおりです。
ベリファイアが無効な場合は AUTH_BADCRED が返されます。これは、資格内に記述されている win とベリファイア内の win +1 が一致しない場合や、タイムスタンプがウィンドウ範囲外の場合です。
再送信が検出されると AUTH_REJECTEDCRED が返されます。
ベリファイアが誤伝送されるとAUTH_BADVERF が返されます。
エラーを受信しない場合、サーバーはクライアントの識別情報をキャッシュに書き込み、NFS 回答に返されるニックネームである小さい整数を割り当てます。その時サーバーは、クライアントがサーバーと同じ領域かどうかをチェックします。クライアントがサーバーと同じ領域の場合、サーバーは、KGETUCRED をローカルの kerbd に呼び出して、主体名を UNIX の資格に変換します。以前の名前が変換できない場合は、ユーザーは匿名と指定されます。サーバーは、ファイルシステムのエクスポート情報に対するこれらの資格を検査します。次の場合について検討します。
KGETUCRED 呼び出しが失敗し、匿名の要求が受け入れられた場合、匿名のユーザーに UNIX 資格が割り当てられます。
KGETUCRED 呼び出しが失敗し、匿名の要求が受け入れられない場合、NFS 呼び出しは失敗し、AUTH_TOOWEAK が返されます。
KGETUCRED 呼び出しが成功する場合は、資格が割り当てられ、その後にルートのアクセス権のチェックも含む、正常な保護検査が行われます。
次に、サーバーが、ニックネームおよびサーバーのベリファイアを組み込んで NFS 回答を送信します。クライアントは回答を受信し、ベリファイアの複号化および妥当性検査を行い、これからの呼び出しのためにニックネームを格納します。クライアントがサーバーに 2 番目の NFS 呼び出しを行うと、先にサーバーに書込まれた呼び出しが繰り返されます。クライアントのカーネルが、以前に記述されたニックネーム資格およびベリファイアを使用して、サーバーの NFS サービスに NFSPROC_STATVFS 呼び出しを行います。サーバーは呼び出しを受信し、ニックネームの妥当性検査を行います。これが範囲外であれば、エラー AUTH_BADCRED を返します。サーバーは、獲得したばかりのセッション鍵を使用して、ベリファイアの DES の暗号化された部分を複号化し、ベリファイアの妥当性検査を行います。
この時に返される可能性のある Kerberos 認証エラーは、次のとおりです。
タイムスタンプが無効で、やり直しが検出されるか、タイムスタンプがウィンドウの範囲外の場合は、AUTH_REJECTEDVERF を返す
エラーが受信されない場合、サーバーは、ニックネームを使用して、呼び出し側の UNIX 資格を検出します。それから、サーバーはファイルシステムのエクスポート情報に対するこれらの資格を検査し、ニックネームおよびサーバーのベリファイアを組み込んだ NFS 回答を送信します。クライアントは回答を受信し、ベリファイアの複号化および妥当性検査を行い、これからの呼び出しのためにニックネームを格納します。最後に、クライアントの NFS マウントシステムコールが返り、要求が終了します。
次の AUTH_KERB の例は AUTH_DES と多くの点で似ていることが次のコーディング例からわかります。両者の違いに注意してください。
#define AUTH_KERB 4 /* * 資格は 2 種類ある。1 つはクライアントが * Kerberos チケット (以前に暗号化された) を送信する際の資格で、 * もう 1 つはサーバーがクライアントに与えた * ニックネーム (単純な符号なし整数)。 * クライアントはサーバーとの最初のトランザクションではそのフルネームを * 使用する必要がある。その際、サーバーはクライアントへニックネームを返す。 * クライアントはそれ以降のサーバーとのすべてのトランザクションで * このニックネームを使用できる (チケットが期限切れとなるまで)。 * ニックネームは使用しなくてもかまいませんが、パフォーマンス上の理由から * 使用するほうが望ましい。 */ enum authkerb_namekind { AKN_FULLNAME = 0, AKN_NICKNAME = 1 }; /* * フルネームには暗号化されたサービスチケットと * ウィンドウが含まれる。ウィンドウは資格の有効期限。 * ベリファイアのタイムスタンプに記載された時刻に * ウィンドウの時間を加えた時刻が過ぎると、サーバーは要求を期限切れとし、 * 許可しない。要求を再送信しないように、最初のトランザクションを除き、 * サーバーは前回よりも大きな値のタイムスタンプを要求する。 * 最初のトランザクションでは、サーバーはタイムスタンプを * チェックする代わりに、ウィンドウベリファイアがウィンドウより 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 言語についての例を示します。
次のコーディング例は、シンプルな 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 手続きは含まれません。これは古いクライアントプログラムと互換性を持たせるために便利です。
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 言語は、次のテーブルを記述する定義が追加されている点を除けば 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 言語では、
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;
整数の定数を使用する場合は、XDR シンボリック定数を使用できます。たとえば、配列サイズの指定に使用します。
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 以外は入力と同じです。この 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;
};
この共用体は次のようにコンパイルされます。
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
C 形式モードでは、rpcgen への 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 以外何も記述しません。 void 宣言は次の 2 箇所でのみ行います。 リモートプロシージャの引数または戻り値として、共用体の定義とプログラムの定義 (引数が渡されないなどで使用される) です。たとえば、引数は渡されません。
rpcbind は RPC のプログラム番号とバージョン番号を汎用アドレスにマップし、リモートプログラムの動的結合を可能にします。
rpcbind はそれをサポートしているトランスポートのよく知られたアドレスに結合しています。他のプログラムは、動的に割り当てられたアドレスを rpcbind で登録します。rpcbind は、それらのアドレスを一般に使用できるようにします。汎用アドレスとは、トランスポートに依存したアドレスで、文字列で表現されています。汎用アドレスは、各トランスポートのアドレス管理者が定義します。
rpcbind はブロードキャスト RPC にも利用できます。RPC プログラムでは、マシンが異なる場合、アドレスも異なるため、これらのプログラムすべてに直接ブロードキャスト通信を行うことは不可能です。ところが、rpcbind のアドレスはわかっています。 そのため、特定のプログラムへブロードキャスト通信を行うには、クライアントは送信先マシン上にある rpcbind プロセスへメッセージを送信します。 rpcbind はブロードキャストメッセージを取り出し、クライアントが指定したローカルサービスを呼び出します。rpcbind はローカルサービスからの応答を取り出すと、それをクライアントに送信します。
次のコーディング例は、RPC 言語の rpcbind プロトコル仕様を示します。
/*
* rpcb_prot.x
* rpc 言語で記述した RPCBIND プロトコル
*/
/*
* (プログラム、バージョン、ネットワーク 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; /* 直接的に呼び出すか、間接的に呼び出すか */
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 のバージョン 1 つに対して
* 1 つの rpcb_stat 構造体が返される。
*/
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 サーバーはこの手続きの要求を
* ループバックトランスポートのみで受け付ける。成功の場合は真を、
* 失敗の場合は偽を返す。
*/
bool
RPCBPROC_SET(rpcb) = 1;
/*
* [r_prog, r_vers, r_owner, r_netid] の組み合わせを登録解除。
* vers がゼロの場合、すべてのバージョンが登録解除される。
* セキュリティー上の理由から、rpcbind サーバーは
* この手続きの要求をループバックトランスポート
* のみで受け付ける。成功の場合は真を、失敗の場合は偽を返す。
*/
bool
RPCBPROC_UNSET(rpcb) = 2;
/*
* [r_prog, r_vers, r_netid] の組み合わせが登録されている
* 汎用アドレスを返す。r_addr を指定すると、
* r_addr へマージされた汎用アドレスを返す。
* r_owner は無視する。失敗の場合は偽を返す。
*/
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;
/*
* 注: RPCBPROC_BCAST は CALLIT と同じ機能を持つ。
* 新しい名前の目的は RPCBPROC_BCAST はブロードキャスト 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、汎用アドレスのリストです。この手続きを呼び出すときは、データグラムトランスポートではなくストリームトランスポートを使用します。これは、大量のデータが返されるのを回避するためです。
この手続きを使用すると、汎用アドレスがわからなくても同一マシン上にあるリモートプロシージャを呼び出すことができます。 RPCBPROC_CALLIT は、rpcbind の汎用アドレス経由での任意のリモートプログラムへのブロードキャスト通信をサポートします。
パラメータ prog、vers、proc、args_ptr にはそれぞれプログラム番号、バージョン番号、手続き番号、リモートプロシージャへの引数を指定します
この手続きは正常終了の場合は応答しますが、異常終了の場合は一切応答しません。
この手続きからは、リモートプログラムの汎用アドレスと、リモートプロシージャからの戻り値が返されます。
この手続きは、自分のマシンのローカル時刻を、1970 年 1 月 1 日午前 0 時からの秒数で返します。
この手続きは、汎用アドレスをトランスポート (netbuf) アドレスに変換します。RPCBPROC_UADDR2TADDR は uaddr2taddr() と同じ機能を持ちます。 マニュアルページ netdir(3NSL) を参照してください。名前 - アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。
この手続きは、トランスポート (netbuf) アドレスを汎用アドレスに変換します。RPCBPROC_TADDR2UADDR は 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 の要求だけはループバックトランスポートからでないと受け入れません。