Appendix A. RPC Protocol Specification

This chapter describes the RPC protocol, a message protocol that is specified with the XDR language and is used in implementing Sun's RPC package.

This chapter assumes you are familiar with both RPC and XDR, as described in this guide. It does not attempt to justify RPC or its uses. The casual user of RPC need not be familiar with the information in this chapter.

Topics in this chapter include:

RPC Protocol Requirements

The RPC protocol provides:

  • unique specification of a procedure to be called

  • provisions for matching response messages to request messages

  • provisions for authenticating the caller to the service and vice versa

Besides these requirements, features that detect the following are worth supporting because of protocol roll-over errors, implementation bugs, user error, and network administration:

  • RPC protocol mismatches

  • remote program protocol version mismatches

  • protocol errors (such as errors in specifying a procedure's parameters)

  • reasons why remote authentication failed

  • any other reasons why the desired procedure was not called

Remote Programs and Procedures

An RPC call message has three unsigned fields that uniquely identify the procedure to be called:

  • remote program number

  • remote program version number

  • remote procedure number

Program numbers are administered by some central authority (see “Assigning RPC Program Numbers” in Chapter 4 for details). Once you have a program number, you can implement your remote program.

The version field of the call message identifies the version of the RPC protocol being used by the caller. Because most new protocols evolve into better, stable, and mature protocols, a version field identifies which version of the protocol the caller is using. Version numbers make it possible to speak old and new protocols through the same server process.

The procedure number identifies the procedure to be called. Such 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 has the RPC version number in it; this field must be two (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 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 garbage from the server's point of view. (Again, this situation is caused by a disagreement about the protocol between client and service.)

Message Authentication

The RPC protocol provides the fields necessary for a client to identify itself to a service and vice versa. 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 as the following opaque type:

enum auth_flavor {
    AUTH_NULL    = 0,
    AUTH_UNIX    = 1,
    AUTH_SHORT   = 2
/* and more to be defined */ 
}; 
struct opaque_auth {
    auth_flavor flavor;
    opaque body<400>; 
}; 

In simple English, any opaque_auth structure is an auth_flavor enumeration followed by bytes that 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. (See “Authentication Protocols” for definitions of the various authentication protocols.)

If authentication parameters are rejected, the response message contains information stating why they were rejected.

Other Uses of the RPC Protocol

The intended use of the RPC protocol is for calling remote procedures. That is, each call message is matched with a response message. However, the protocol itself is a message passing protocol with which other (non-RPC) protocols can be implemented. Sun currently uses the RPC message protocol for the following two (non-RPC) protocols: batching (or pipelining) and broadcast RPC. These two protocols are discussed (but not defined) next.

Batching

Batching allows a client to send an arbitrarily large sequence of call messages to a server; batching uses reliable byte stream protocols (such as TCP/IP) for its transport. The client never waits for a reply from the server, and the server does not send replies to batch requests. A sequence of batch calls is usually terminated by a legitimate RPC in order to flush the pipeline (with positive acknowledgment).

Broadcast RPC

In broadcast RPC-based protocols, the client sends a broadcast packet to the network and waits for numerous replies. Broadcast RPC uses unreliable, packet-based protocols (such as UDP/IP) as its transport. Servers that support broadcast protocols respond only when the request is successfully processed, and they are silent in the face of errors. Broadcast RPC uses the Port Mapper RPC service to achieve its semantics. See “Port Mapper Program Protocol” for more information.

RPC Protocol Definition

This section defines the RPC protocol in the XDR data description language. The message is defined in a top-down style.

Note that this is an XDR specification, not C code:

enum msg_type {
    CALL = 0,
    REPLY = 1 
}; 
/* 
 * A reply to a call message can take 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 successfully executed */
    PROG_UNAVAIL   =1,  /* remote machine exports program */
    PROG_MISMATCH  =2,  /* remote can't support version num*/
    PROC_UNAVAIL   =3   /* prog can't support procedure */
    GARBAGE_ARGS   =4   /* remote can't figure out params */
}; 
/*
 * Reasons why a call message was rejected:
 */ 
enum reject_stat {
    RPC_MISMATCH = 0,   /* RPC version number not 2 */
    AUTH_ERROR   = 1    /* caller not authenticated on */
                        /* remote */    
};

/*
 * Why authentication failed:
 */ 
enum auth_stat {
  AUTH_BADCRED      = 1, /* bad credentials (seal broken) */
  AUTH_REJECTEDCRED = 2, /* have client begin new session */
  AUTH_BADVERF      = 3, /* bogus verifier (seal broken) */
  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. NB: The xid field is only used for clients
 * matching reply messages with call messages or for servers
 * detecting  retransmissions; the service side cannot treat
 * this ID as any type of sequence number.
 */ 
struct rpc_msg {
    unsigned int    xid;
    union switch (msg_type mtype) {
        case CALL:  
            call_body cbody;
        case REPLY: 
            reply_body rbody;
    } body; 
};

/*
 * 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, and the
 * procedure within the remote program to be called.  These
 * fields are followed by 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 {
    unsigned int rpcvers;  /* must be equal to 2 */
    unsigned int prog;
    unsigned int vers;
    unsigned int proc;
    opaque_auth cred;
    opaque_auth verf;
    /* procedure-specific parameters start here */ 
};

/*  
 *  Body of a reply to an RPC request.
 *  The call message was either accepted or rejected.
 */
union reply_body 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.
 * Note: there could be an error even though the request
 * was accepted.  The first field is an authentication
 * verifier which 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 that are
 * 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 because of two reasons: either
 * the server is not running a compatible version of the 
 * RPC protocol (RPC_MISMATCH), or the server refused to 
 * authenticate the caller (AUTH_ERROR).  In the case of
 * an RPC version mismatch, the server returns the lowest and 
 * highest supported RPC version numbers. In the case of 
 * refused authentication, the 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 some “flavors” of authentication in this implementation. Other sites are free to invent new authentication types, with the same rules of flavor number assignment as those for program number assignment.

Null Authentication

RPC calls are often made when the caller doesn't know its authentication parameters, and the server doesn't care. In this case, the auth_flavor value (the discriminant of the opaque_auth's union) of the RPC message's credentials, verifier, and response verifier is AUTH_NULL(0). The bytes of the opaque_auth's body are undefined. It is recommended that the opaque length be zero.

AUTH_UNIX Authentication

The caller of a remote procedure may want to identify itself as it is identified on a trusted UNIX system. The value of the credential's discriminant of an RPC call message is AUTH_UNIX (1). The bytes of the credential's opaque body 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 that the caller machine may generate. The machinename is the name of the caller's machine (such as krypton). The uid is the caller's effective user ID. The gid is the caller's effective group ID. The gid is a counted array of groups that contain the caller as a member. The verifier accompanying the credentials should be of AUTH_NULL (defined in the previous section).

The value of the discriminate of the “response verifier” received in the reply message from the server may be AUTH_NULL or AUTH_SHORT(2). In the case of AUTH_SHORT, the bytes of the response verifier's string encode an opaque structure. This new opaque structure may now be passed to the server instead of the original AUTH_UNIX flavor credentials. The server keeps a cache that maps shorthand opaque structures (passed back via an AUTH_SHORT style “response verifier”) to the original credentials of the caller. The caller can save network bandwidth and server CPU cycles by using the new credentials.

The server may flush the shorthand opaque structure at any time. If this happens, the remote procedure call message will be rejected due to an authentication error. The reason for the failure will be AUTH_REJECTEDCRED. At this point, the caller may want to try the original AUTH_UNIX style of credentials.


Note: In an open environment, extra checks should be performed against the source and identity of the originator before accepting the credential values.


Trusted UNIX Systems

Authentication is based on the premise that one multi-user UNIX system should be able to accept and rely upon the user and group identification information from a trusted source. The criteria for such trust between two systems are as follows:

  • Both systems are administered securely. This includes practices such as:

    • using passwords on all accounts, especially root.

    • ensuring all setuid-root programs and daemons that run as root are trustworthy, that is, they do not lie about their UID and are not easily fooled.

    • protecting system files (the kernel) through file system permissions.

  • Both systems share a common set of user UIDs and GIDs, such as is implemented with NIS.

Systems that adhere to the criteria in the first bulleted item above are considered equivalent, and are typically named in the file /etc/hosts.equiv.

As a result of following these criteria, a UNIX system believes the content of a credential is authentic if comes from a trusted and trustworthy source. A UNIX system attempts to assure itself that the credential has come from such a trusted source if:

  • The packet is not self-inconsistent about its source (the host name in the credential maps to the IP source address of the packet).

  • The packet bears a source address that maps into a list of trusted or equivalent hosts. Assuming that the list has been properly maintained, this assures the program that the source system is a UNIX system, with privileged ports.

  • The credential information (that is, the UIDs and GIDs) are all known to the local system. This attempts to catch information from hosts that do not have equivalent (common) sets of users' UIDs and GIDs.

  • The packet came from a privileged port on the source system. Given that the source system was a UNIX host, this implies that the packet came from a process running as root, which is trustworthy.

All of these actions constitute authentication, not access control. They attempt to answer the question “should we trust the identity information in this credential?” not the question “is the identity in this credential entitled to perform the requested RPC function?”

This entire premise of authentication based on trust of equivalent systems is dated. Personal computers and UNIX workstations that are individually administered are far less likely to be worthy of the level of trust suggested here than were the large multi-user systems that were kept in locked computer rooms, and whose root passwords were known only to a few trusted system administrators, so common years ago. Most typical modern UNIX workstation environments simply don't meet the criteria for equivalent systems any more.

Personal computers do not have root accounts, and no privileged ports. Any user on a personal computer can send packets from a port number that would be a privileged port if it were a UNIX system.

So, accepting an AUTH_UNIX RPC request from a system not known to be UNIX is risky.

Record Marking Standard

When RPC messages are passed on top of a byte stream protocol (such as TCP/IP), it is necessary, or at least desirable, to delimit one message from another in order to detect and possibly recover from user protocol errors. This is called record marking (RM). This implementation of RPC uses this RM/TCP/IP transport for passing RPC messages on TCP streams. One RPC message fits into one RM record.

A record is composed of one or more record fragments. A record fragment is a 4-byte header followed by 0 to 231-1 bytes of fragment data. The bytes encode an unsigned binary number; as with XDR integers, the byte order is from highest to lowest. The number encodes two values—a boolean, which indicates whether the fragment is the last fragment of the record (bit value 1 implies the fragment is the last fragment), and a 31-bit unsigned binary value, which is the length in bytes of the fragment's data. The boolean value is the highest order bit of the header; the length is the 31 low-order bits. (Note that this record specification is not in XDR standard form.)

Port Mapper Program Protocol

The port mapper program maps RPC program and version numbers to transport–specific port numbers, which enables dynamic binding of remote programs.

This mapping is desirable because the range of reserved port numbers is very small and the number of potential remote programs is very large. By running only the port mapper on a reserved port, the port numbers of other remote programs can be ascertained by querying the port mapper.

The port mapper also aids in broadcast RPC. A given RPC program will usually have different port number bindings on different machines, so there is no way to directly broadcast to all of these programs. The port mapper, however, does have a fixed port number. To broadcast to a given program, the client actually sends its message to the port mapper located at the broadcast address. Each port mapper that picks up the broadcast then calls the local service specified by the client. When the port mapper gets the reply from the local service, it sends the reply back to the client.

Port Mapper Protocol Specification

The following specifies the Port Mapper Protocol (in RPC language):

const PMAP_PORT = 111;      /* portmapper port number */ 

/*
 * A mapping of (program, version, protocol) to port number
 */ 
struct mapping {
    unsigned int prog;
    unsigned int vers;
    unsigned int prot;
    unsigned int port; 
}; 

/* 
 * Supported values for the "prot" field
 */ 
const IPPROTO_TCP = 6;      /* protocol number for TCP/IP */ 
const IPPROTO_UDP = 17;     /* protocol number for UDP/IP */ 

/*
 * A list of mappings
 */ 
struct *pmaplist {
    mapping map;
    pmaplist next; 
}; 

/*
 * Arguments to callit
 */ 
struct call_args {
    unsigned int prog;
    unsigned int vers;
    unsigned int proc;
    opaque args<>; 
};  

/*
 * Results of callit
 */ 
struct call_result {
    unsigned int port;
    opaque res<>; 
}; 

/*
 * Port mapper procedures
 */ 
program PMAP_PROG {
    version PMAP_VERS {
        void 
        PMAPPROC_NULL(void)         = 0;
        bool
        PMAPPROC_SET(mapping)       = 1;
        bool
        PMAPPROC_UNSET(mapping)     = 2;
        unsigned int
        PMAPPROC_GETPORT(mapping)   = 3;
        pmaplist
        PMAPPROC_DUMP(void)         = 4;
        call_result
        PMAPPROC_CALLIT(call_args)  = 5;
    } = 2; 
} = 100000; 

Port Mapper Operation

The port mapper program currently supports two protocols (UDP/IP and TCP/IP). The port mapper is contacted by talking to it on assigned port number 111 (sunrpc in /etc/services) on either of these protocols.

Table A-1 contains a description of each port mapper procedure.

Table A-1. Port Mapper Procedures

Procedure

Description

PMAPPROC_NULL

This procedure does not do any work. By convention, procedure zero of any protocol takes no parameters and returns no results.

PMAPPROC_SET

When a program first becomes available on a machine, it registers itself with the port mapper program on the same machine. The program passes its program number prog, version number vers, transport protocol number prot, and the port port on which it awaits a service request. The procedure returns a boolean response whose value is TRUE if the procedure successfully established the mapping, and FALSE otherwise. The procedure refuses to establish a mapping if one already exists for the tuple “(prog, vers, prot).”

PMAPPROC_UNSET

When a program becomes unavailable, it should unregister itself with the port mapper program on the same machine. The parameters and results have meanings identical to those of PMAPPROC_SET. The protocol and port number fields of the argument are ignored.

PMAPPROC_GETPORT

Given a program number prog, version number vers, and transport protocol number prot, this procedure returns the port number on which the program is awaiting call requests. A port value of zero means the program has not been registered. The port field of the argument is ignored.

PMAPPROC_DUMP

This procedure enumerates all entries in the port mapper's database. The procedure takes no parameters and returns a list of program, version, protocol, and port values.

PMAPPROC_CALLIT

This procedure allows a caller to call another remote procedure on the same machine without knowing the remote procedure's port number. It is intended for supporting broadcasts to arbitrary remote programs via the well-known port mapper's port. The parameters prog, vers, proc, and the bytes of args are the program number, version number, procedure number, and parameters of the remote procedure.

This procedure sends a response only if the procedure was successfully executed and is silent (no response) otherwise.

The port mapper communicates with the remote program using UDP/IP only.

The procedure returns the remote program's port number, and the bytes of results are the results of the remote procedure.