Remote procedure calls are a high-level communication paradigm that allows programmers to write network applications using procedure calls that hide the details of the underlying network. RPC implements a client/server system without requiring that callers be aware of the underlying network.
This chapter introduces the RPC programming interface, which enables an application to make procedure calls to remote machines using architecture-independent mechanisms. This portability is achieved by using eXternal Data Representation (XDR) data-encoding to resolve byte-ordering differences and the port mapper program to locate and invoke a requested procedure.
Topics in this chapter include:
an overview of remote procedure calls, including the RPC model, the RPC protocol, and RPC message authentication
the XDR standard
the layers of RPC
the rpcgen protocol compiler
assigning RPC program numbers
the port mapper programs
Programs that communicate over a network need a paradigm for communication. For example, a low-level mechanism might send a signal when incoming packets arrive, causing a network signal handler to execute. With the remote procedure call paradigm, a client makes a procedure call to send a data packet to the server. When the packet arrives, the server calls a dispatch routine, performs whatever service is requested, sends back the reply, and the procedure call returns to the client.
In this context, a server is a machine where some number of network services are implemented. A service is a collection of one or more remote programs. A 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 are pieces of software that initiate remote procedure calls to services. A server may support more than one version of a remote program in order to be forward-compatible with changing protocols.
The remote procedure call model is similar to the local procedure call model. With the local model, the caller places arguments to a procedure in a well-specified location (such as a result register) and transfers control to the procedure. When the caller eventually regains control, it extracts the results of the procedure from the well-specified location and continues execution.
The remote procedure call model operates in a similar fashion, except control winds through two processes: the caller's process and a server's process. That is, the caller process sends a message to the server process and waits (blocks) for a reply message. The call message contains the procedure's parameters (among other things), and the reply message contains the procedure's results (among other things). When the reply message returns, the caller extracts the results of the procedure and resumes execution.
On the server side, a process is dormant as it waits for the arrival of a call message. When a reply arrives, the server process extracts the procedure's parameters, computes the results, sends a reply message, and then waits for the arrival of the next call message.
Note that in the remote procedure call model, only one of the two processes is active at any given time. However, this scenario is given only as an example. The RPC protocol (see “RPC Transports and Semantics”) makes no restrictions on concurrency, and other scenarios are possible. For example, an implementation may choose to have asynchronous RPC calls, so 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 the server can be free to receive other requests.
Figure 4-1 illustrates the remote procedure call model.
The RPC package is implemented using the RPC protocol, a message protocol specified using XDR language. 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 is concerned only with the specification and interpretation of messages.
RPC does not try to implement reliability; the application must be aware of the type of transport protocol underneath RPC. If the application knows it's running on top of a reliable transport (such as TCP/IP), most of the work is already done. If the application is running on top of an unreliable transport (such as UDP/IP), however, it must implement its own retransmission and timeout policy, because the RPC layer does not provide this service.
To ensure 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 what happens when RPC runs on top of an unreliable transport. If an application retransmits RPC messages after short timeouts and receives no reply, all it can infer is that the procedure was executed zero or more times. If it receives a reply, it can infer that the procedure was executed at least once.
A server may wish to ensure some degree of execute-at-most-once semantics and remember previously granted requests from a client and not grant them again. A server can do this by taking advantage of the transaction ID that is packaged with every RPC request.
The transaction ID is used primarily by the client RPC layer to match 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 regrant 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.
On the other hand, if the application uses a reliable transport, it can infer from a reply message that the procedure was executed exactly once. If it receives no reply message, however, it cannot assume the remote procedure was not executed. Note that even with a connection-oriented protocol such as TCP, an application still needs timeouts and reconnection to handle server crashes.
Additional transport possibilities exist for datagram- or connection–oriented protocols. On IRIX, RPC is currently implemented on top of both TCP/IP and UDP/IP transports.
The act of binding a client to a service is not part of the RPC specification. This important and necessary function is left up to some higher-level software. (The software may use RPC itself; see “Port Mapper Program Protocol” in Appendix A for more information.)
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 its task.
The RPC protocol provides the fields necessary for a client to identify itself to a service and vice versa. Authentication and identity-based access control mechanisms, on the other hand, are not provided and must be added to provide security.
Identification is the means to present or assert an identity that is recognizable to the receiver; it provides no proof that the identity is valid. In UNIX, typing a user name at the login prompt is identification, as is putting a UID and a GID into an AUTH_UNIX credential for RPC.
Authentication provides the actions to verify the truth of the asserted identity. To continue with the examples above, the password is used by the UNIX login program to verify the user's identity, and the AUTH_DES or AUTH_KERB protocols (not provided with Silicon Graphics' RPC) provide authentication in RPC. The action the password program performs is to compare what the user types to the encrypted copy that resides on the system. AUTH_DES and AUTH_KERB use encryption-based user authentication.
Silicon Graphics' RPC message authentication mechanism, which consists of the AUTH_UNIX and AUTH_NONE protocols, does not provide authentication, as defined above. When these protocols are used, the application must provide the authentication. An example of how to extend RPC to include an authentication procedure, as provided in the rlogin program, is shown in “Server-side Authentication” in Chapter 6.
Once the client is identified and verified, access control can be implemented. Access control is the mechanism that provides permission to allow the requests made by the user to be granted, based upon the user's authentic identity. Access control is not provided in RPC and must be supplied by the application.
Several different authentication protocols are supported. A field in the RPC header indicates which protocol is being used.
RPC assumes the existence of XDR, a set of library routines that allow a C programmer to describe arbitrary data structures in a machine-independent fashion. XDR is useful for transferring data between diverse computer architectures and has been used to communicate data between such diverse machines as the IRIS, Sun, VAX, IBM PC, and Cray computers.
XDR enables RPC to handle arbitrary data structures, regardless of a machine's byte order or structure layout conventions, by converting the data structures to XDR before sending them over the wire. Any program running on any machine can use XDR to create portable data by translating its local representation into the XDR representation; similarly, any program running on any machine can read portable data by translating the XDR standard representations into its local equivalents. This process of converting from a particular machine representation to XDR format is called serializing, and the reverse process is called deserializing (see Chapter 8, “XDR Programming Notes”, for details).
XDR uses the XDR language to describe data formats (see Chapter 7, “XDR and RPC Language Structure”). Protocols such as Sun RPC and NFS use XDR to describe their data format.
The XDR language lets you describe intricate data formats in a concise manner. The alternative—using graphical representations (an informal language)—quickly becomes incomprehensible when faced with complexity. The XDR language is similar to the C language, but it is not a programming language and can only be used to describe data.
XDR fits into the ISO presentation layer and is roughly analogous in purpose to X.409, ISO Abstract Syntax Notation. The major difference is that XDR uses implicit typing, while X.409 uses explicit typing.
This section provides a brief overview of the RPC layers. For programming details about each layer, see Chapter 6, “RPC Programming Guide”.
RPC is divided into three layers: the highest layer, the middle layer, and the lowest layer.
The highest layer of RPC is transparent to the operating system, the machine, and the network upon which it is run. It's probably best to think of this level as a way of using RPC, rather than as a part of “RPC proper.” Programmers who write RPC routines should (almost) always make this layer available to others by using a simple C front end that entirely hides the networking.
For example, at this level, a program can make a call to the C routine rnusers(), which returns the number of users on a remote machine. Users are not explicitly aware of using RPC—they simply call a procedure, just as they would call malloc().
The middle layer of RPC is really RPC proper and consists of routines used for most applications. In the middle layer, the user simply makes remote procedure calls to routines on other machines, without considering details about the socket interface, the UNIX system, or other low-level implementation mechanisms. For example, the middle layer allows RPC to pass the “hello world” test.
RPC calls are made with the registerrpc(), callrpc(), and svc_run() routines. registerrpc() and callrpc() are the most fundamental: registerrpc() obtains a unique system-wide procedure-identification number, and callrpc() actually executes a remote procedure call. In the middle layer, a call to rnusers() is implemented by using these two routines.
|Note: The middle layer of RPC is rarely used in serious programming due to its inflexibility (simplicity). It does not allow timeout specifications or the choice of transport; it does not allow UNIX process control or flexibility in the case of errors; and it does not support multiple methods of call authentication. Although programmers rarely need all of these controls, one or two are sometimes necessary.|
In the lowest layer of RPC, the programmer has control over the hidden details and can write more-sophisticated applications that alter the defaults of the routines. At this layer, programmers can explicitly manipulate sockets used for transmitting RPC messages.
Programs written at this level are most efficient, but efficiency is rarely an issue, because RPC clients and servers rarely generate heavy network loads; if possible, this level should be avoided.
|Note: This guide only describes the interface to C, but you can make remote procedure calls from any language. And, although this guide describes RPC when it is used to communicate between processes on different machines, it works just as well for communication between different processes on the same machine.|
Programming applications that use RPC can be difficult, especially when you are writing XDR routines that convert procedure arguments and results into their network format and vice versa. The rpcgen compiler helps automate the process of writing RPC applications. Using rpcgen decreases development time that would otherwise be spent coding and debugging low-level routines. With rpcgen, the compiler does most of the dirty work; the programmer need only debug the main features of the application, rather than spend time debugging network interface code.
rpcgen accepts remote program interface definitions written in the RPC language (see Chapter 7, “XDR and RPC Language Structure”, for more information) and produces C language output for RPC programs. This output consists of a stub version of the client routines, a server skeleton, XDR filter routines for parameters and results, a header file that contains common definitions, and ANSI C prototyped stub routines.
You can compile and link rpcgen's output files using standard techniques. Then after writing the server procedures, you can link the server procedures with the server skeletons to produce an executable server program.
To use a remote program, the programmer writes an ordinary main program that makes local procedure calls to the client skeletons. Linking the main program with the skeletons creates an executable program.
Like other compilers, rpcgen provides an escape hatch that lets programmers mix low-level code with high-level code. In speed-critical applications, handwritten routines can be linked with the rpcgen output without any difficulty. In addition, rpcgen output can be used as a starting point; you can rewrite the code as necessary.
For details about rpcgen, see Chapter 5, “Programming with rpcgen”.
the remote program's RPC version number
the remote procedure number
the remote program number
The version field of the call message identifies which version of the RPC protocol the caller is using. 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 it possible for old and new protocols to communicate through the same server process.
The procedure number identifies the procedure to be called. This number is 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() (see “Remote Programs and Procedures” in Appendix A for more information).
Program numbers are administered by a central authority (such as Sun Microsystems). Once you have a program number, you can implement your remote program. Table 4-1 lists some of the currently registered programs.
local lock manager
network lock manager
status monitor 1
status monitor 2
boot parameters service
RPC program numbers are assigned in groups of 0x20000000 (536870912) according to the categories in Table 4-2.
0x0 - 0x1fffffff
Defined by Sun
0x20000000 - 0x3fffffff
Defined by user
0x40000000 - 0x5fffffff
0x60000000 - 0x7fffffff
0x80000000 - 0x9fffffff
0xa0000000 - 0xbfffffff
0xc0000000 - 0xdfffffff
0xe0000000 - 0xffffffff
Sun Microsystems administers the first group of numbers. The second group is reserved for specific customer applications; this range is intended primarily for debugging new programs. The third group is reserved for applications that generate program numbers dynamically. The final groups are reserved for future use and should not be used.
To register a protocol specification, write to:
2550 Garcia Avenue
Mountain View, CA 94043
Make sure to include a compilable rpcgen “.x” file (see Chapter 5, “Programming with rpcgen”) describing your protocol. In return, you will be given a unique program number.
You can find the RPC program numbers and protocol specifications of standard Sun RPC services in the include files in /usr/include/rpcsvc. These services, however, constitute only a small subset of those that have been registered.
The port mappers (see portmap(1M) or rpcbind(1M)) are servers that convert RPC program numbers into universal addresses (IP port numbers). Either portmap or rpcbind must be running in order to make RPC calls. If rpcbind is installed, it runs by default.
When an RPC server is started, it tells the port mapper the port number it is listening to and what RPC program numbers it is prepared to serve. When a client wants to make an RPC call to a given program number, it first checks the port mapper on the server machine to determine the port number where RPC packets should be sent.