Previous section.
Protocols for Interworking: XNFS, Version 3W
Copyright © 1998 The Open Group
Remote Procedure Calls: Protocol Specification
This chapter specifies a protocol that is used by many implementors of
XNFS.
It is derived from a document designated RFC 1057 by the ARPA
Network Information Centre (see
References to RFCs
).
Introduction
This chapter specifies a message protocol used in implementing
a Remote Procedure Call (RPC) package.
The message protocol is
specified with the External Data Representation (XDR) language (see
XDR Protocol Specification
).
It is assumed that the reader is familiar
with XDR and no attempt is made to justify it or its uses.
The paper by Birrell and Nelson, Implementing Remote Procedure Calls
is recommended as an excellent background to, and justification of, RPC.
Terminology
This chapter discusses servers, services, programs, procedures,
clients and versions.
- network server
-
A piece of software where network services are implemented.
- network service
-
A collection of one or more remote programs.
- remote program
-
Implements one or more remote procedures; the procedures, their
parameters and results are documented in the specific program's
protocol specification.
- network clients
-
Pieces of software that initiate remote procedure calls to services.
A network server may support more than one version of a
remote program in order to be forward compatible with changing
protocols.
For example, a network file service may be composed of two
programs: one program may deal with high-level applications such
as file system access control and locking; the other may deal with
low-level file I/O and have procedures like
read and write.
A client machine of the network file service would call the
procedures associated with the two programs of the service on
behalf of a user on the client machine.
The RPC Model
The remote procedure call model is similar to the local procedure
call model.
In the local case, the caller places arguments to a
procedure in some well-specified location (such as a stack).
It then transfers control to the procedure, and
eventually gains back control.
At that point, the results of the
procedure are extracted from the well-specified location, and the
caller continues execution.
In the remote case, one thread of control
logically winds through two processes; one is the caller's
process, the other is a server's process.
That is, the caller
process sends a call message to the server process and waits
(blocks) for a reply message.
The call message contains the
procedure's parameters, among other things.
The reply message contains the procedure's results, among other things.
Once the reply message is received, the results of the procedure are
extracted, and the caller's execution is resumed.
On the server side, a process is dormant awaiting the arrival of a
call message.
When one arrives, the server process extracts the
procedure's parameters, computes the results, sends a reply
message, and then awaits the next call message.
Note that in this model, only one of the two processes is active at
any given time.
However, this model is only given as an example.
The RPC protocol makes no restrictions on the concurrency model
implemented, and others are possible.
For example, an
implementation may choose to have RPC calls asynchronous, so
that the client may do useful work while waiting for the reply from
the server.
Another possibility is to have the server create a
task to process an incoming request, so that the server can be free
to receive other requests.
Transports and Semantics
The RPC protocol is independent of transport protocols.
That is, RPC does not care how a message is passed from one process to
another.
The protocol deals only with specification and
interpretation of messages.
It is important to point out that RPC does not try to implement any
kind of reliability, and that the application must be aware of the
type of transport protocol underneath RPC.
If it knows it is
running on top of a reliable transport such as TCP/IP, as described
in Transmission Control Protocol - DARPA Internet Program Protocol Specification, then most of the work is already done for it.
If, however, it is running on top of an unreliable transport such as
UDP/IP, as described in User Datagram Protocol, it
must implement its own retransmission and time-out policy as the RPC
layer does not provide this service.
Because of transport independence, the RPC protocol does not attach
specific semantics to the remote procedures or their execution.
Semantics can be inferred from (but should be explicitly specified
by) the underlying transport protocol.
For example, consider RPC
running on top of an unreliable transport such as UDP/IP.
If an application retransmits RPC messages after short time-outs, the
only thing it can infer if it receives no reply is that the
procedure was executed zero or more times.
If it does receive a reply, then it can infer that the procedure
was executed at least once.
A server may wish to remember previously granted requests from a
client and not regrant them in order to ensure some degree of
execute-at-most-once semantics.
A server can do this by taking
advantage of the transaction ID that is packaged with every RPC
request.
The main use of this transaction is by the client RPC
layer in matching replies to requests.
However, a client
application may choose to reuse its previous transaction ID when
retransmitting a request.
The server application, knowing this
fact, may choose to remember this ID after granting a request and
not re-grant requests with the same ID in order to achieve some
degree of execute-at-most-once semantics.
The server is not
allowed to examine this ID in any other way except as a test for
equality.
However, if using a reliable transport such as TCP/IP,
the application can infer from a reply message that the procedure
was executed exactly once, but if it receives no reply message, it
cannot assume the remote procedure was not executed.
Note that
even if a connection-oriented protocol like TCP is used, an
application still needs time-outs and reconnection to handle server
crashes.
There are other possibilities for transports besides datagram or
connection-oriented protocols.
For example, a request-reply
protocol such as VMTP[2] is perhaps the most natural transport for RPC.
Note that
although RPC is currently implemented on top of both TCP/IP
and UDP/IP transports,
the XNFS specification defines only the use of connectionless protocols;
therefore, RPC over connection-oriented protocols will not be discussed
further in this specification.
Binding and Rendezvous Independence
The act of binding a client to a service is not part of the remote
procedure call specification.
This important and necessary
function is left up to some higher-level software.
(The software may use RPC itself, see
Introduction to Port Mapper Program Protocol
.)
Implementors should think of the RPC protocol as the
jump-subroutine instruction (JSR) of a network; the loader
(binder) makes JSR useful, and the loader itself uses JSR to
accomplish its task.
Likewise, the network makes RPC useful, using RPC to accomplish this task.
RPC Protocol Requirements
The RPC protocol must provide for the following:
-
Unique specification of a procedure to be called
-
Provisions for matching response messages to request messages
-
Provisions for authenticating the caller to service and vice versa
Besides these requirements, RPC has features that detect the following:
-
RPC protocol mismatches
-
Remote program protocol version mismatches
-
Protocol errors (such as incorrect specification of a procedure's parameters)
-
Reasons why remote authentication failed
-
Any other reasons why the desired procedure was not called
Programs and Procedures
The RPC call message has three unsigned fields: remote program number,
remote program version number and remote procedure number.
The three fields uniquely identify the procedure to be called.
Program numbers are administered by some central authority.
(Currently Sun Microsystems, Inc. is responsible for administering
program numbers.)
Once an implementor has a program number, the remote program can be
implemented; the first implementation would most likely have
the version number of 1.
Because most new protocols evolve into
better, stable and mature protocols, a version field of the call
message identifies which version of the protocol the caller is
using.
Version numbers make speaking old and new protocols through
the same server process possible.
The procedure number identifies the procedure to be called.
These numbers are documented in the specific program's protocol
specification.
For example, a file service's protocol
specification may state that its procedure number 5 is read
and procedure number 12 is write.
Just as remote program protocols may change over several versions,
the actual RPC message protocol could also change.
Therefore, the
call message also contains the RPC version number, which is always
2 for the version of RPC described here.
The reply message to a request message has enough information to
distinguish the following error conditions:
-
The remote implementation of RPC does not speak protocol version 2;
the lowest and highest supported RPC version numbers are returned
-
The remote program is not available on the remote system
-
The remote program does not support the requested version
number; the lowest and highest supported remote program version
numbers are returned
-
The requested procedure number does not exist
(this is usually a caller side protocol or programming error)
-
The parameters to the remote procedure appear to be uninterpretable
from the server's point of view
(again, this is usually caused by
a disagreement about the protocol between client and service)
Authentication
The RPC protocol provides the fields necessary for a client to
identify itself to a service and vice versa.
Security and access
control mechanisms can be built on top of the message authentication.
Several different authentication protocols can be supported.
A field in the RPC header indicates which protocol is being used.
More information on specific authentication protocols can be found in
Authentication Protocols
.
The RPC Message Protocol
This section defines the RPC message protocol in the XDR data
description language.
The message is defined in a top-down style.
-
-
enum msg_type {
CALL = 0,
REPLY = 1
};
/*
* A reply to a call message can take on two forms:
* the message was either accepted or rejected.
*/
enum reply_stat {
MSG_ACCEPTED = 0,
MSG_DENIED = 1
};
/*
* Given that a call message was accepted, the following is the
* status of an attempt to call a remote procedure.
*/
enum accept_stat {
SUCCESS = 0, /* RPC executed successfully */
PROG_UNAVAIL = 1, /* remote hasn't exported program */
PROG_MISMATCH = 2, /* remote can't support version number */
PROC_UNAVAIL = 3, /* program can't support procedure */
GARBAGE_ARGS = 4 /* procedure can't decode params */
};
/*
* Reasons why a call message was rejected:
*/
enum reject_stat {
RPC_MISMATCH = 0, /* RPC version number is not 2 */
AUTH_ERROR = 1 /* remote can't authenticate caller */
};
/*
* Why authentication failed:
*/
enum auth_stat {
AUTH_BADCRED = 1, /* bad credentials */
AUTH_REJECTEDCRED = 2, /* client must begin new session */
AUTH_BADVERF = 3, /* bad verifier */
AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */
AUTH_TOOWEAK = 5 /* rejected for security reasons */
};
-
-
/*
* The RPC message:
* All messages start with a transaction identifier, xid,
* followed by a two-armed discriminated union. The union's
* discriminant is a msg_type which switches to one of
* the two types of the message. The xid of a
* REPLY message always matches that of the initiating
* CALL message. N.B.: The xid field may be
* used by clients to match reply messages with call messages,
* or by servers detecting retransmissions; the service side
* cannot treat this xid as any type of sequence number.
*/
struct rpc_msg {
unsigned int xid;
union switch (msg_type mtype) {
case CALL:
call_body bgcolor="#FFFFFF" cbody;
case REPLY:
reply_body bgcolor="#FFFFFF" rbody;
} body bgcolor="#FFFFFF";
};
/*
* Body of an RPC request call:
* In version 2 of the RPC protocol specification, rpcvers
* must be equal to 2. The fields prog, vers and
* proc specify the remote program, its version number,
* and the procedure within the remote program to be called.
* After these fields are two authentication parameters:
* cred (authentication credentials) and verf
* (authentication verifier). The two authentication parameters
* are followed by the parameters to the remote procedure,
* which are specified by the specific program protocol.
*/
struct call_body bgcolor="#FFFFFF" {
unsigned int rpcvers; /* must be equal to two (2) */
unsigned int prog;
unsigned int vers;
unsigned int proc;
opaque_auth cred;
opaque_auth verf;
/* procedure specific parameters start here */
};
/*
* Body of an RPC reply:
* The call message was either accepted or rejected.
*/
-
-
union reply_body bgcolor="#FFFFFF" switch (reply_stat stat) {
case MSG_ACCEPTED:
accepted_reply areply;
case MSG_DENIED:
rejected_reply rreply;
} reply;
/*
* Reply to an RPC request that was accepted by the server:
* there could be an error even though the request was accepted.
* The first field is an authentication verifier that the server
* generates in order to validate itself to the caller. It is
* followed by a union whose discriminant is an enum
* accept_stat. The SUCCESS arm of the union is
* protocol-specific. The PROG_UNAVAIL, PROC_UNAVAIL
* and GARBAGE_ARGS arms of the union are void. The
* PROG_MISMATCH arm specifies the lowest and highest
* version numbers of the remote program supported by the server.
*/
struct accepted_reply {
opaque_auth verf;
union switch (accept_stat stat) {
case SUCCESS:
opaque results[0];
/* procedure-specific results start here */
case PROG_MISMATCH:
struct {
unsigned int low;
unsigned int high;
} mismatch_info;
default:
/*
* Void. Cases include PROG_UNAVAIL,
* PROC_UNAVAIL and GARBAGE_ARGS.
*/
void;
} reply_data;
};
/*
* Reply to an RPC request that was rejected by the server.
* The request can be rejected for two reasons: either the
* server is not running a compatible version of the RPC
* protocol (RPC_MISMATCH), or the server refuses to
* authenticate the caller (AUTH_ERROR). In case of an RPC
* version mismatch, the server returns the lowest and highest
* supported RPC version numbers. In case of refused
* authentication, failure status is returned.
*/
-
-
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;
};
Authentication Protocols
As previously stated, authentication parameters are opaque, but
open-ended to the rest of the RPC protocol.
This section defines four "flavours" of authentication.
Other implementations are free to invent new authentication types, with
the same rules of flavour number assignment as there are for program
number assignment.
Provisions for authentication of caller to service and vice versa
are provided as a part of the RPC protocol.
The call message has two authentication fields: the credentials and verifier.
The reply message has one authentication field, the response verifier.
The RPC protocol specification defines all three fields to be the
following opaque type:
-
-
enum auth_flavor {
AUTH_NULL = 0,
AUTH_UNIX = 1,
AUTH_DES = 3,
AUTH_KERB = 4,
/* and more to be defined */
};
struct opaque_auth {
auth_flavor flavor;
opaque body bgcolor="#FFFFFF"<400>;
};
In other words, any opaque_auth structure is an auth_flavor
enumeration followed by a sequence of bytes which are opaque to the RPC
protocol implementation.
The interpretation and semantics of the data contained within the
authentication fields is specified by individual, independent
authentication protocol specifications.
If authentication parameters are rejected, the response message
will contain information stating why they were rejected.
Null Authentication
Often calls must be made where the caller does not know who he is,
or the server does not care who the caller is.
In this case, the flavour value (the discriminant of the
opaque_auth's union) of the
RPC message's credentials, verifier and response verifier is AUTH_NULL.
The bytes of the opaque_auth's body bgcolor="#FFFFFF" are undefined.
It is recommended that the opaque length is zero.
UNIX Authentication
The caller of a remote procedure may wish to identify himself as he
is identified on a UNIX system.
The value of the credential's discriminant of an RPC call message is
AUTH_UNIX.
The bytes of the credential's opaque body bgcolor="#FFFFFF" encode the following structure:
-
-
struct auth_unix {
unsigned int stamp;
string machinename<255>;
unsigned int uid;
unsigned int gid;
unsigned int gids<16>;
};
The stamp is an arbitrary ID which the caller machine may generate.
The machinename is the name of the caller's machine (like "krypton").
Implementations and applications must be able to handle machine names
as 8-bit transparent data (allowing use of arbitrary character set
encodings).
For maximum portability and interworking,
it is recommended that applications and users define machine
names containing only the characters of the Portable Filename
Character Set defined in ISO/IEC 9945-1:1990.
The uid is the caller's effective user ID.
The gid is the caller's effective group ID.
The gids is a counted array of groups which contain the
caller as a member (supplementary groups).
An entry in the gids
array whose value is 0xffffffff should be ignored.
Although the supplementary group list gids
is part of the XNFS specification
and is supported by (PC)NFS clients, it is only optional in the X/Open (PC)NFS Specification.
It is unspecified whether a server uses all the entries in the
gids array or only [NGROUPS_MAX] entries, which may be zero.
[NGROUPS_MAX] is defined in the X/Open Commands and Utilities Specification (see reference XCU).
The verifier accompanying the credentials (the verf field in
call_body bgcolor="#FFFFFF" and accept_reply) should be of
AUTH_NULL (defined above).
DES and Kerberos Authentication
The
AUTH_DES
flavour is defined in RFC 1057.
It provides DES-encrypted authentication parameters based on a
network-wide name, with session keys exchanged via a public key scheme.
The
AUTH_KERB
flavour
provides DES encrypted authentication parameters based on a
network-wide name with session keys exchanged via Kerberos, Version 4
secret keys.
The
AUTH_DES
and
AUTH_KERB
styles of authentication are based
on a network-wide name.
They provide greater security
through the use of DES encryption and public keys in the
case of
AUTH_DES,
and DES encryption and Kerberos secret
keys (and tickets) in the
AUTH_KERB
case.
The server and client must agree on the identity of a
particular name on the network, but the name to identity
mapping is more operating system independent than the
uid
and
gid
mapping in
AUTH_UNIX.
Also, because the
authentication parameters are encrypted, a malicious user
must know another user's network password or private key to
masquerade as that user.
Similarly, the server returns a
verifier that is also encrypted so that masquerading as a
server requires knowing a network password.
The RPC Language
Just as it was necessary to describe the XDR data types in a formal
language, it is also necessary to describe the procedures that
operate on these XDR data types in a formal language.
The RPC Language is used for this purpose.
It is an extension to the XDR language.
The following example is used to describe the essence of the language.
The RPC Language Specification
The RPC language is identical to the XDR language, except for the
added definition of a program-def described below.
-
-
program-def:
"program" identifier "{"
version-def
version-def *
"}" "=" constant ";"
version-def:
"version" identifier "{"
procedure-def
procedure-def *
"}" "=" constant ";"
procedure-def:
type-specifier identifier "(" type-specifier ")"
"=" constant ";"
An Example Service Described in the RPC Language
Here is an example of the specification of a simple ping
program.
-
-
/*
* Simple ping program
*/
program PING_PROG {
/* Latest and greatest version */
version PING_VERS_PINGBACK {
void PINGPROC_NULL(void) = 0;
/*
* Ping the caller, return the round-trip time (in
* microseconds). Returns -1 if the operation timed out.
*/
int PINGPROC_PINGBACK(void) = 1;
} = 2;
/* Original version */
version PING_VERS_ORIG {
void PINGPROC_NULL(void) = 0;
} = 1;
} = 1;
const PING_VERS = 2; /* latest version */
The first version described is PING_VERS_PINGBACK with two procedures,
PINGPROC_NULL and PINGPROC_PINGBACK.
PINGPROC_NULL
takes no arguments and returns no results, but it is useful for
computing round-trip times from the client to the server and back again.
By convention, procedure 0 of any RPC protocol should have
the same semantics, and never require any kind of authentication.
The second procedure is used for the client to have the server do a
reverse ping operation back to the client, and it returns the
amount of time (in microseconds) that the operation used.
The next version, PING_VERS_ORIG,
is the original version of the protocol and it does not contain
PINGPROC_PINGBACK procedure.
It is useful
for compatibility with old client programs, and as this program
matures it may be dropped from the protocol entirely.
Syntax Notes
-
The keywords program and version
are added and cannot be used as identifiers
-
A version name cannot occur more than once within the scope of
a program definition; nor can a version number occur more than once
within the scope of a program definition
-
A procedure name cannot occur more than once within the scope
of a version definition; nor can a procedure number occur more than
once within the scope of version definition
-
Program identifiers are in the same name space as constant and
type identifiers
-
Only unsigned constants can be assigned to programs, versions
and procedures
Why not acquire a nicely bound hard copy?
Click here to return to the publication details or order a copy
of this publication.