Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38

Chapter 4


Remote Procedure Calls (RPC)

4.1 REMOTE PROCEDURE CALL GUIDE

This chapter is intended for programmers who want to write network applications using remote procedure calls (explained below), thus avoiding low-level system primitives based on sockets. The reader must be familiar with the C programming language and should have a working knowledge of network theory.

Programs that communicate over a network need a paradigm for communication. A low-level mechanism might send a signal on the arrival of incoming packets, causing a network signal handler to execute. A high-level mechanism would be the Ada rendezvous. The method used by the NFS is the Remote Procedure Call (RPC) paradigm, in which a client communicates with a server. In this process, the client first calls a procedure to send a data packet to the server. When the packet arrives, the server calls a dispatch routine, performs the service requested, sends back the reply, and the procedure call returns to the client.

The RPC interface is divided into three layers. The highest layer is totally transparent to the programmer. To illustrate, at this level a program can contain a call to rnusers(), which returns the number of users on a remote machine. The user need not be aware that RPC is being used, since the call is simply made in a program, just as malloc() would be called.

At the middle layer, the routines registerrpc() and callrpc() are used to make RPC calls: registerrpc() obtains a unique system-wide number, while callrpc() executes a remote procedure call. The rnusers() call is implemented using these two routines. The middle-layer routines are designed for most common applications and shield the user from needing to know about sockets.

The lowest layer is used for more sophisticated applications that may want to alter the defaults of the routines. At this layer, sockets used for transmitting RPC messages can be explicitly manipulated. This level should be avoided if possible.

Although this document only discusses the interface to C, remote procedure calls can be made from any language. Even though this document discusses 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.

Home


A diagram of the RPC paradigm is shown in Figure 4-1.

Figure 4-1 Network Communication with RPC

4.1.1 Introductory Examples

Some examples of RPC use follow.

Home


4.1.1.1 Highest layer

Consider a program that needs to know how many users are logged into a remote machine. This can be done by calling the library routine rnusers() as shown in the following example.

Example:


     #include <stdio.h>

     main(argc, argv)
          int argc;
          char **argv;
     {
          unsigned num;

          if     (argc < 2) {
                    fprintf(stderr, "usage: rnusers hostname\n");
                    exit(1);
          }
          if     ((num = rnusers(argv[1])) < 0) {
                    fprintf(stderr, "error: rnusers\n");
                    exit (-1);
          }
          printf("%d users on %s\n", num, argv[1]);
          exit(0);
     }

RPC library routines such as rusers() are in the RPC services library librpcsvc.a. Thus, the program above should be compiled with the following:


     $ cc program.c -lrpcsvc

NOTE

For rusers() to work, the rusers daemon must be running on the remote host.

4.1.1.2 INTERMEDIATE LAYER

The simplest interface, which explicitly makes RPC calls, uses the function callrpc() and registerrpc(). Using this method, another way to get the number of remote users is as follows.

Example:


    #includ     e      <stdio.h>
     #include     <rpcsvc/rusers.h>

     main(argc, argv)
               int argc;
               char **argv;
     {
               unsigned long nusers;

Home




          if     (argc < 2) {
                    fprintf(stderr, "usage: nusers hostname\n");
                    exit(1);
          }
          if     (callrpc(argv[1], RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
               xdr_void, 0, xdr_u_long, &nusers) != 0) {
                 fprintf(stderr, "error: callrpc\n");
                 exit(1);
          }
          printf("number of users on %s is %d\n", argv[1], nusers);
          exit(0);
}

A program number, version number, and procedure number defines each RPC procedure. The program number defines a group of related remote procedures, each of which has a different procedure number. Each program also has a version number, so when a minor change is made to a remote service (adding a new procedure, for example) a new program number does not have to be assigned.

When a procedure is to be called to find the number of remote users, the appropriate program, version and procedure numbers are looked up in a manual, in a similar manner to looking up the name of memory allocator when memory is to be allocated.

The simplest routine in the RPC library used to make remote procedure calls is callrpc(). It has eight parameters. The first is the name of the remote machine. The next three parameters are the program, version, and procedure numbers. The following two parameters define the argument of the RPC call, and the final two parameters are for the return value of the call. If it completes successfully, callrpc() returns zero, but nonzero otherwise. The exact meaning of the return codes is found in <rpc/clnt.h>, and is in fact an enum clnt_stat cast into an integer.

Since data types may be represented differently on different machines, callrpc() needs both the type of the RPC argument, as well as a pointer to the argument itself (and similarly for the result). For RUSERSPROC_NUM, the return value is an unsigned long, so callrpc() has xdr_u_long as its first return parameter, which says that the result is of type unsigned long, and &nusers as its second return parameter, which is a pointer to where the long result will be placed. Since RUSERSPROC_NUM takes no argument, the argument parameter of callrpc() is xdr_void.

After trying several times to deliver a message, if callrpc() gets no answer, it returns with an error code. The delivery mechanism is UDP, which stands for User Datagram Protocol. Methods for adjusting the number of retries or for using a different protocol require the use of the lower layer of the RPC library, discussed later in this document. The remote server procedure corresponding to the preceding might look like this:


     char *
     nuser(indata)
          char *indata;
     {
          static int nusers;

          /*
           * code here to compute the number of users
           * and place results in variable nusers
           */
          return ((char *)&nusers);
}

Home


It takes one argument, which is a pointer to the input of the remote procedure call (ignored in the preceding example) and it returns a pointer to the result. In the current version of C, character pointers are the generic pointers, so both the input argument and the return value are cast to char *.

Normally, a server registers all of the RPC calls it plans to handle, and then goes into an infinite loop waiting to service requests. In this example, there is only a single procedure to register, so the main body of the server looks like this:


     #include <stdio.h>
     #include <rpcsvc/rusers.h>

     char *nuser();

     main()
     {
               registerrpc(RUSERSPROG, RUSERSVERS, URSERSPROC_NUM, nuser,
                    xdr_void, scr_u_long);
               svc_run*( );               /* never returns */
               fprintf(stderr, "Error: svc_run returned!\n");
               exit(1);
     }

The registerrpc() routine establishes what C procedure corresponds to each RPC procedure number. The first three parameters, RUSERPROG, RUSERSVERS, and RUSERSPROC_NUM are the program, version, and procedure numbers of the remote procedure to be registered; nuser is the name of the C procedure implementing it; and xdr_void and xdr_u_long are the types of the input to and output from the procedure.

Only the UDP transport mechanism can use registerrpc(); thus, it is always safe in conjunction with calls generated by callrpc().

4.1.1.3 ASSIGNING PROGRAM NUMBERS

Program numbers are assigned in groups of 0x20000000 (536870912) according to the following chart:


     0                   --     1fffffff     defined by Sun Microsystems
     20000000     --     3fffffff     defined by user
     40000000     --     5fffffff     transient
     60000000     --     7fffffff     reserved
     80000000     --     9fffffff     reserved
     a0000000     --     bfffffff     reserved
     c0000000     --     dfffffff     reserved
     e0000000     --     ffffffff     reserved

Sun Microsystems administers the first group of numbers, and the intent is that they are identical across all systems and applications. If a customer develops an application that might be of general interest, that application should be given a number assigned by Sun from the first range. The second group of numbers 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.

Home


4.1.1.4 PASSING ARBITRARY DATA TYPES

In the previous example, the RPC call passes a single unsigned long. RPC can handle arbitrary data structures, regardless of different machines' byte orders or structure layout conventions, by always converting them to a network standard called eXternal Data Representation (XDR) before sending them over the wire. The process of converting from a particular machine representation to XDR format is called serializing, and the reverse process is called deserializing. The type field parameters of callrpc() and registerrpc() can be a built-in procedure like xdr_u_long() in the previous example, or a user-supplied one. XDR has these built-in type routines:



     xdr_int()        xdr_u_int()         xdr_enum()
     xdr_long()     xdr_u_long()       xdr_bool()
     xdr_short()    xdr_u_short()      xdr_string()

As an example of a user-defined type routine, if the user wants to send the following structure,


     struct simple {
          int a;
          short b;
     }   simple;

callrpc should be called as follows.


     callrpc(hostname, PROGNUM, VERSNUM, PRCCNUM, xdr_simple, &simple ...);

Where xdr_simple() is written as:


     #include <rpc/rpc.h>

     xdr_simple(xdrsp, simplep)
               XDR *xdrsp;
               struct simple *simplep;
     {
               if     (!xdr_int(xdrsp, &simplep-->a))
                         return(0);
               if     (!xdr_short(xdrsp, &simple-->b))
                         return(0);
               return(1);
     }

An XDR routine returns nonzero (true in the sense of C) if it completes successfully, and zero otherwise. A complete description of XDR is found in Chapter 5, eXternal Data Representation. This section only gives a few examples of XDR implementation.

In addition to the built-in primitives, there are also the prefabricated building blocks:


     xdr_array( )           xdr_bytes( )
     xdr_reference( )     xdr_union( )

Home


To send a variable array of integers, they might be packaged in a structure as follows.


     struct varintarr  {
          int *data;
>          int arrlnth;
>     } arr;

and make an RPC call such as


     callrpc(hostname, PROGNUM, VERSNUM, PROCNUM, xdr_varintarr. &arr ...);

with xdr_varintarr() defined as:


     xdr_varintarr(xdrsp, arrp)
                    XDR *xdrsp;
                    struct varintarr *arrp;
     {
                    xdr_array(xdrsp, &arrp-->data, &arrp-->arrinth, MAXIEN,
                              sizeof(int), xdr_int);
     }

This routine takes as parameters the XDR handle, a pointer to the array, a pointer to the size of the array, the maximum allowable array size, the size of each array element, and an XDR routine for handling each array element.

If the size of the array is known in advance, the following could also be used to send out an array of length SIZE:


     int intarr[SIZE];

     xdr_intarr(xdrsp, intarr)
                    XDR *xdrsp;
                    int intarr[];
     {
                    int i;

                    for (i =  0; i< SIZE; i++) {
                    if     (!xdr_int(xdrsp, &intarr[i]))
                              return(0);
                    }
                    return(1);
     }

XDR always converts quantities to 4-byte multiples when deserializing. Thus, if either of the previous examples involves characters instead of integers, each character occupies 32 bits. That is the reason for the XDR routine xdr_bytes(), which is like xdr_array() except that it packs characters. It has four parameters that are the same as the first four parameters of xdr_array(). For null-terminated strings, there is also the xdr_string() routine, which is the same as xdr_bytes() without the length parameter. On serializing it gets the string length from strlen(), and on deserializing it creates a null-terminated string.

The following example calls the previously written xdr_simple() as well as the built-in functions xdr_string() and xdr_reference(), which chases pointers:

Home



     struct      finalexample {
                    char *string;
                    struct simple *simplep;
     }     finalexample;

     xdr_finalexample *(xdrsp, finalp);
                    XDR *xdrsp;
                    struct finalexample *finalp;
     {
                    int i;
                    if     (!xdr_string(xdrsp, &finalp-->string, MAXSTRLEN))
                              return(0);
                    if     (!xdr_reference (xdrsp, &finalp-->simplep,
                              sizeof(struct simple), xdr_simple);
                              return(0);
                    return(1);
     }

4.1.2 Lower Layers of RPC

In the examples given so far, RPC takes care of many details automatically. This section shows how to change the defaults by using lower layers of the RPC library. The reader should be familiar with sockets and the system calls for dealing with them.

4.1.2.1 MORE ON THE SERVER SIDE

A number of assumptions are built into registerrpc(). One is that the UDP datagram protocol is being used. Another is that the user does not want to do anything unusual while deserializing, since the deserialization process happens automatically before the user's server routine is called. The server for the nusers program shown is written using a lower layer of the RPC package, which does not make these assumptions.


     #include <stdio.h>
     #include <rpc/rpc.h>
     #include <rpcsvc/rusers.h>

     int nuser();

     main()

     {

               SVCXPRT *transp;

               transp = svcudp_create(RPC_ANYSOCK);
               if     (transp = = NULL) {
                         fprintf(stderr, "couldn't create an RPC server\n");
                         exit(1);

               }

Home



              pmap_unset(RUSERSPROG, RUSERSVERS);
               if     (!svc_register(transp, RUSERPROG, RUSERSVERS, nuser,
                       IPPROTO_UDP)) {
                         fprintf(stderr, "couldn't register RUSER service\n");
                         exit(1);

               }
               svc_run();       /* never returns */
               fprintf(stderr, "should never reach this point\n");
     }

     nuser(rqstp, tranp)
               struct svc_req *rqstp;
               SVCXPRT *transp;
     {
               unsigned long nusers;

               switch (rqstp-->rq_proc) {
                         case NULLPROC:
                                   if     (!svc_sendreply(transp, xdr_void, 0)) {
                                             fprintf(stderr, "couldn't reply to RPC call\n:);
                                             exit(1);
                                   }
                                   return;
                         caseRUSERSPROC_NUM:
                                   /*
                                    *  code here to compute the number of users
                                    *  and put in variable nusers
                                    */
                                   if     (     !svc_sendreply(transp, xdr_u_long, &nusers) {
                                             fprintf(stderr, "couldn't reply to RPC call\n");
                                             exit(1);
                                   }
                                   return;
                         default:
                                   svcerr_noproc(transp);
                                   return;
               }
     }

First, the server gets a transport handle, which is used for sending out RPC messages. registerrpc() uses svcudp_create() to get a UDP handle. If a reliable protocol is required, svctcp_create() should be called instead. If the argument to svcudp_create() is RPC_ANYSOCK, the RPC library creates a socket on which to send out RPC calls. Otherwise, svcudp_create() expects its argument to be a valid socket number. If the user specifies his own socket, it can be bound or unbound. If it is bound to a port by the user, the port numbers of svcudp_create() and clntudp_create(), the low-level client routine, must match.

When the user specifies RPC_ANYSOCK for a socket or gives an unbound socket, the system determines port numbers in the following way. When a server starts up, it advertises to a port mapper demon on its local machine, which picks a port number for the RPC procedure if the socket specified to svcudp_create() is not already bound. When the clntudp_create() call is made with an unbound socket, the system queries the port mapper on the machine to which the call is being made, and gets the appropriate port number. If the port mapper is not running or has no port corresponding to the RPC call, the RPC call fails. Users can make RPC calls to the port mapper themselves. The appropriate procedure numbers are in the include file <rpc/pmap_prot.h>.

Home


After creating an SVCXPRT, the next step is to call pmap_unset(). This way, if the nusers server crashed earlier, any previous trace of it is erased before restarting. More precisely, pmap_unset() erases the entry for RUSERS from the port mapper's tables.

Finally, the program number for nusers is associated with the procedure nuser(). The final argument to svc_register() is normally the protocol being used which, in this case, is IPPROTO_UDP. Unlike registerrpc(), there are no XDR routines involved in the registration process. Registration is also done on the program, rather than procedure, level.

The user routine nuser() must call and dispatch the appropriate XDR routines based on the procedure number. nuser() handles two things that are handled automatically by registerrpc(). The first is that procedure NULLPROC (currently zero) returns with no arguments. This can be used as a simple test for detecting if a remote program is running. Second, there is a check for invalid procedure numbers. If one is detected, svcerr_noproc() is called to handle the error.

The user service routine serializes the results and returns them to the RPC caller via svc_sendreply(). Its first parameter is the SVCXPRT handle, the second is the XDR routine, and the third is a pointer to the data to be returned. How a server handles an RPC program that passes data is not illustrated above. As an example, a procedure, RUSERSPROC_BOOL, which has an argument nusers, and returns TRUE or FALSE depending on whether there are nusers logged on can be added. It would look like this:


     case RUSERPROC_BOOL: {
               int bool;
               unsigned nuserquary;

               if     (!svc_getargs(transp, xdr_u_int, &nurserquery) {
                         svcerr_decode(transp);
                         return;
               }
               /*
                *  code to set nusers = number of users
                */
               if     (nurserquery = = nusers)
                              bool = TRUE;
               else
                              bool = FALSE;
               if     (!svc_sendreply(transp, xdr_bool, &bool)) {
                         fprintf(stderr, "couldn't reply to RPC call\n");
                         exit(1);
               }
               return;
     }

The relevant routine is svc_getargs(), which takes as arguments an SVCXPRT handle, the XDR routine, and a pointer to where the input is to be placed.

4.1.2.2 MEMORY ALLOCATION WITH XDR

XDR routines not only perform input and output, they also allocate memory. This is why the second parameter of xdr_array() is a pointer to an array, rather than the array itself. If it is NULL, then xdr_array() allocates space for the array and returns a pointer to it, putting the size of the array in the third argument. For example, the following XDR routine xdr_chararr1() deals with a fixed array of bytes with length SIZE:

Home



     xdr_charrarr1(xdrsp, cararr)
                    XDR *xdrsp;
                    char chararr[];
     {
                    char *p;
                    int len;

                    p = chararr;
                    len = SIZE;
                    return(xdr_bytes(xdrsp, &p, &len, SIZE));
     }

It might be called from a server as follows,


     char chararr[SIZE];

     svc_getargs(transp, xdr_chararr1, chararr);

where chararr has already allocated space. If XDR was used to do the allocation, this routine would be rewritten in the following way:


     xdr_chararr2(xdrsp, chararrp)
               XDR *xdrsp;
               char **chararrp;
     {
               int len;

               len = SIZE;
               return(xdr_bytres(xdrsp, chararrp, &len, SIZE));
     }

The RPC call might then look like this:


     char *arrptr;

     arrptr = NULL;
     svc_getargs(transp, xdr_chararr2, &arrptr);
     /*
      *use the result here
      */
     svc_freeargs(transp, xdr_chararr2, &arrptr);

After using the character array, it can be freed with svc_freeargs(). In the routine xdr_finalexample() given earlier, if finalp-->string was NULL in the call


     svc_getargs(transp, xdr_finalexample, &finalp);
then

     svc_freeargs(transp, xdr_finalexample &finalp);

frees the array allocated to hold finalp-->string; otherwise, it frees nothing. The same is true for finalp-->simplep.

Home


Each XDR routine is responsible for serializing, deserializing, and allocating memory. When an XDR routine is called from callrpc(), the serializer is used. When called from svc_getargs(), the deserializer is used. When called from svc_freeargs(), the memory deallocator is used. When building simple examples like those in this section, a user doesn't have to worry about the three modes. The XDR reference manual has examples of more sophisticated XDR routines that determine which of the three modes they are in to function correctly.

4.1.2.3 THE CALLING SIDE

When callrpc is used, there is no control over the RPC delivery mechanism or the socket used to transport the data. To illustrate the layer of RPC that allows adjustment of these parameters, consider the following code to call the nusers service.


     #include <stdio.h>
     #include <rpc/rpc.h>
     #include <rpcsvc/rusers.h>
     #include <sys/socket.h>
     #include <sys/time.h>
     #include <netdb.h>

     main(argc,           argv)
                    int argc;
                    char **argv;
     {
                    struct hostent *hp;
                    struct timeval pertry_timeout, total-timeout;
                    struct sockaddr_in server_addr;
                    int addrlen, sock = PRC_ANYSOCK;
                    register CLIENT *client;
                    enum clnt_stat clnt_stat;
                    unsigned long nusers;

                    if     (argv < 2) {
                              fprintf(stderr, "usage: nusers hostname\n");
                              exit(-1);
                    }
                    if     ((hp = gethostbyname(argv[1])) = = NULL) {
                              fprintf(stderr, "cannot get addr for '%s'\n", argv[1]);
                              exit(-1);
                    }

                    pertry-timeout.tv_sec = 3;
                    pertry-timeout.tv_usec = 0;
                    addrlen = sizeof(struct sockaddr_in);
                    bcopy(hp-->h_addr, (caddr_t)&server_addr.sin_addr, hp-->h_length);
                    server_addr.sin_family = AF_INET;
                    server_addr.sin_port = 0;
                    if     ((client = clntudp_create(&server_addr, RUSERSPROG,
                            RUSERSVERS, pertry_timeout, &sock)) = = NULL) {
                              perror("clntudp_create");
                              exit(-1);
                    }

Home




                    total-timeout.tv_sec = 20;
                    total-timeout.tv_usec = 0;
                    clnt_stat = clnt_call(client, RUSERSPROC_NUM, xdr_void, 0,
                              xdr_u_long, &nusers, total-timeout);
                    if     (clnt_stat != PRC_SUCCESS) {
                              clnt_perror(client, "rpc");
                              exit(-1);
                    }
                    clnt_destroy(client);
     }

The low-level version of callrpc() is clnt_call(). It takes a CLIENT pointer rather than a host name. The parameters to clnt_call() are a CLIENT pointer, the procedure number, the XDR routine for serializing the argument, a pointer to the argument, the XDR routine for deserializing the return value, a pointer to where the return value will be placed, and the time in seconds to wait for a reply.

The CLIENT pointer is encoded with the transport mechanism. callrpc() uses UDP, thus it calls clntudp_create() to get a CLIENT pointer. To get Transport Control Protocol (TCP), use clnttcp_create().

The parameters to clntudp_create() are the server address, the length of the server address, the program number, the version number, a timeout value (between tries), and a pointer to a socket. The final argument to clnt_call() is the total time to wait for a response. Thus, the number of tries is the clnt_call() timeout divided by the clntudp_create() timeout.

One thing should be noted when using the clnt_destroy() call: it deallocates any space associated with the CLIENT handle, but it does not close the socket associated with it, which was passed as an argument to clntudp_create(). The reason is, if there are multiple client handles using the same socket, it is possible to close one handle without destroying the socket that other handles are using.

To make a stream connection, the call to clntudp_create() is replaced with a call to clnttcp_create().


     clnttcp_create(&server_addr, prognum, cersnum, &socket, inputsize,
          outputsize);

There is no timeout argument; instead, the receive and send buffer sizes must be specified. When the clnttcp_create() call is made, a TCP connection is established. All RPC calls using that CLIENT handle would use this connection. The server side of an RPC call using TCP has svcudp_create() replaced by svctcp_create().

4.1.3 Other RPC Features

This section discusses some other aspects of RPC.

4.1.3.1 SELECT ON THE SERVER SIDE

Suppose a process is processing RPC requests while performing some other activity. If the other activity involves periodically updating a data structure, the process can set an alarm signal before calling svc_run().

Home


However, if the other activity involves waiting for a file descriptor, the svc_run() call does not work. The code for svc_run() is as follows:


     void
     svc_run()
     {
                    int readfds;

                    for (;;) {
                              readfds = svc_fds;
                              switch     (select (32, &readfds, NULL, NULL, NULL)) {
                                             case -1:
                                                       if   (errno = = EINTR)
                                                            continue;
                                                       perror("rstat: select");
                                                       return;
                                             case 0:
                                                       break;
                                             default:
                                                       svc_getreq(readfds);
                              }
                    }
     }

svc_run() can be bypassed, and svc_getreq() called directly. To do this, the file descriptors of the socket(s) waiting for programs must be known. Users can write their own selects that wait for both the RPC socket, and their own descriptors.

4.1.3.2 BROADCAST RPC

The pmap and RPC protocols implement broadcast RPC. The main differences between broadcast RPC and normal RPC calls are as follows:

Home


Broadcast RPC synopsis is shown in the following example.

Example:


     #include <rpc/pmap_clnt.h>

     enum clnt_stat    clnt_stat;

     clnt_stat =
     clnt_broadcast(prog, vers, proc, xargs, argsp, xresults, resulrsp, eachresult)
     u_long               prog;                              /* program number */
     u_long               vers;                              /* version number */
     u_long               proc;                             /* procedure number */
     xdrproc_t          xargs;                           /* xdr routine for args */
     caddr_t              argsp;                          /* pointer to args */
     xdrproc_t          xresults;                       /* xdr routine for results */
     caddr_t             resultsp;                       /* pointer to results */
     bool_t  (*eachresult)();                         /* call with each result obtained */ 

The procedure eachresult() is called each time a valid result is obtained. It returns a boolean that indicates whether or not the client wants more responses.


     bool_t                                  done;

     done =
     eachresult(resultsp,           raddr)

     caddr_t                                resuctsp;
     struct sockaddr_in            *raddr;           /* address of machine that sent response */

If done is TRUE, the broadcasting stops and clnt_broadcast() returns successfully. Otherwise, the routine waits for another response. The request is rebroadcast after a few seconds of waiting. If no responses come back, the routine returns with RPC_TIMEDOUT. To interpret clnt_stat errors, feed the error code to clnt_perrno().

4.1.3.3 BATCHING

The RPC architecture is designed so that clients send a call message and wait for servers to reply that the call succeeded. This implies that clients do not compute while servers are processing a call. This is inefficient if the client does not want or need an acknowledgment for every message sent. Clients can continue to compute while waiting for a response, using RPC batch facilities.

RPC messages can be placed in a "pipeline" of calls to a desired server; this is called batching. Batching assumes that:

Since the server does not respond to every call, the client can generate new calls in parallel with the server executing previous calls. Furthermore, the TCP/IP implementation can buffer many call messages and send them to the server in one write system call. This overlapped execution greatly decreases the interprocess communication overhead of the client and server processes, and the total elapsed time of a series of calls.

Home


Since the batched calls are buffered, the client should eventually do a legitimate call to flush the pipeline.

For a client to take advantage of batching, the client must perform RPC calls on a TCP-based transport and the actual calls must have the following attributes:

Since the server sends no message, the clients cannot be notified of any of the failures that occur. Therefore, clients are on their own when it comes to handling errors.

4.1.3.4 AUTHENTICATION

In the examples presented so far, the caller never identified itself to the server, and the server never required an ID from the caller. Some network services, such as a network file system, require stronger security measures than those that were presented so far.

Every RPC call is actually authenticated by the RPC package on the server, and similarly, the RPC client package generates and sends authentication parameters. Just as different transports (TCP/IP or UDP/IP) can be used when creating RPC clients and servers, different forms of authentication can be associated with RPC clients. The default authentication type used as a default is type none.

The authentication subsystem of the RPC package is open ended. Numerous types of authentication are easy to support. However, this section deals only with UNIX-type authentication, which is the only supported type besides none.

Home


Home


The remote users service example can be extended so that it computes results for all users except UID 16, as shown in the following example.

Example:


     nuser(rqstp, transp)
               struct svc_req *rqstp;
               SVCXPRT *transp;
     {
               struct authunix_parms *unix_cred;
               int uid;
               unsigned long nusers;

               /*
                * we don't care about authentication for the null procedure
                */
               If     (rqstp-->rq_proc = = NULLPROC) {
                         if     (!svc_sendreply(transp, xdr_void, 0)) {
                                   fprintf(stderr, "couldn't reply to RPC call\n");
                                   exit(1);
                         }
                    return;
               }
               /*
                * now get the uid
                */
               switch     (rqstp-->rq_cred.oa_flavor) {
                         case AUTH_UNIX:
                                   unix_cred = (struct authunix_parms *)rqstp-->rq_clntcred;
                                   uid = unix_cred-->aup_uid;
                                   break;
                         caseAUTH_NULL:
                         default:
                                   svcerr_weakauth(transp);
                                   return;
               }
               switch     (rqstp-->rq_proc) {
                         case RUSERSPROC_NUM:

               /*
                * make sure the caller is allowed to call this procedure.
                */
               if     (uid = = 16) {
                    svcerr_systemerr(transp);
                    return;
               }
               /*
                * code here to compute the number of users
                * and put in variable nusers
                */
               if      (!svc_sendreply(transp, xdr_u_long, &nusers) {
                         fprintf(stderr, "couldn't reply to RPC call\n");
                         exit(1);
               }
               return;
          default:
               svcerr_noproc(transp);
               return;
          }
     }

Home


4.1.3.5 USING inetd

Some systems have a utility daemon named inetd that simplifies the administration of RPC servers by controlling them based on an input control file.

If a server is to be started by inetd, the only difference from the usual code is that svcudp_create() should be called as


     transp = svcudp_create(0);

since inet passes a socket as file descriptor 0. Also, svc_register() should be called as


     svc_register(PROGNUM, VERSNUM, service, transp, 0);

with the final flag as 0, since the program would already be registered by inetd. To exit from the server process and return control to inet, the user must explicitly exit, since svc_run() never returns.

4.1.4 More Examples

This section provides additional RPC examples.

4.1.4.1 VERSIONS

By convention, the first version number of program FOO is FOOVERS_ORIG and the most recent version is FOOVERS. Suppose there is a new version of the user program that returns an unsigned short rather than a long. If we name this version RUSERSVERS_SHORT, then a server that wants to support both versions would do a double register.

Home



     if     (!svc_register(transp, RUSERSPORG, RUSERSVERS_ORIG, nuser,
          IPPROTO_TCP)) {
               fprintf(stderr, "couldn't register RUSER service\n");
               exit(1);
     }
     if     (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT, nuser,
          IPPROTO_TCP)) {
               fprintf(stderr, "couldn't register RUSER service\n");
               exit(1);
     }
Both versions can be handled by the same C procedure. Example:

     nuser     (rqstp,      transp)
               struct svc_req *rqstp;
               SVCXPRT *transp;
     {
               unsigned long nusers;
               unsigned short nusers2;

               switch (rqstp-->rq _ proc) {
               caseNULLPROC:
                         if     (!svc_sendreply(transp, xdr_void, 0)) {
                                   fprintf(stderr, "couldn't reply to RPC call\n");
                                   exit(1);
                         }
                         return;
               caseRUSERSPROC_NUM:
                         /*
                          *code here to compute the number of users
                          *and put in variable nusers
                          */
                         nusers2 = nusers;
                         if     (rqstp-->rq_vers = = RUSERSVERS_ORIG) {
                              if     (!svc_sendreply(transp, xdr_u_long, &nusers) {
                                   fprintf(stderr, "couldn't reply to RPC call\n");
                                   exit(1);
                              }
                         }
                         else
                              if     (!svc_sendreply(transp, xdr_u_short, &nusers2) {
                                   fprintf(stderr, "couldn't reply to RPC call\n");
                                   exit(1);
                               }
                         return;
               default:
                         svcerr_noproc(transp);
                         return;
     }
}

Home


4.1.4.2 TCP

The following example is essentially rcp. The initiator of the RPC snd() call takes its standard input and sends it to the server rcv(), which prints it on standard output. The RPC call uses TCP. This also illustrates an XDR procedure that behaves differently on serialization than on deserialization.

Example:


     /*
     *The xdr routine:
     *
     *on decode, read from wire, write onto fp
     *on encode, read from fp, write onto wire
     */
     #include <stdio.h>
     #include <rpc/rpc.h>

     xdr_rcp(xdrs, fp)
                    XDR *xdrs;
                    FILE *fp;
     {
                    unsigned long size;
                    char buf[MAXCHUNK], *p;

                    if     (xdrs-->x_op = = XDR_FREE)      /* nothing to free */
                              return 1;
                    while (1) {
                              if     (xdrs-->x_op == XDR_ENCODE) {
                                        if     ((size = fread(buf, sizeof(char), MAXCHUNK, fp))
                                                  = = 0 && ferror(fp)) {
                                                   fprintf(stderr, "couldn't fread\n");
                                                  exit(1);
                                        }
                              }
                              p = buf;
                              if     (!xdr_bytes(xdrs, &p, &size, MAXCHUNK))
                                        return 0;
                              if      (size = = 0)
                                          return 1;
                              if      (xdrs-->x_op = = XDR_DECODE) {
                                         if   (fwrite(buf, sizeof(char), size, fp) != size {
                                              fprintf(stderr, "couldn't fwrite\n");
                                              exit(1);
                                        }
                              }
                     }
          }

Home



     /*
      *The sender routines
      */
     #include <stdio.h>
     #include <netdb.h>
     #include <rpc/rpc.h>
     #include <sys/socket.h>
     #include <sys/time.h>

     main(argc, argv)
               int argc;
               char **argv;
     {
          int err;

          if     (argc < 2) {
                    fprintf(stderr, "usage: %s server-name\n", argv[0]);
                    exit(-1);
          }
          if     ((err = callrpctcp(argv[1], RCPPROG, PCPPROC_FP, PCPVERS,
                      xdr_rcp, stdin, xdr_void, 0)) != 0) {
                    clnt_perrno(err);
                    fprintf(stderr, "couldn't make RPC call\n");
                    exit(1);
          }
}
callrpctcp(host, prognum, procnum, versnum, inproc, in, outproc, out)
          char *host, *in, *out;
          xdrproc_t inproc, outproc;
{
          struct sockaddr_in setver_addr;
          int socket = RPC_ANYSOCK;
          enum clnt_stat clnt_stat;
          struct hostent *hp;
          register CLIENT *client;
          struct timeval total_timeout;

          if     ((hp = gethostbyname(host)) = = NULL) {
                    fprintf(sterr, "cannot get addr for `%s'\n", host);
                    exit(-1);
          }
          bcopy(hp-->h_addr, (caddr_t)&server_addr.sin_addr, hp-->h_length);
          server_addr.sin_family = AF_INET;
          server_addr.sin_port = 0;
          if     ((client = clnttcp_create(&server_addr, prognum,
                 versnum, &socket, BUFSIZ, BUFSIZ)) = = NULL) {
               perror("rpctcp_create");
               exit(-1);
          }

Home



          total_timeout.tv_sec = 20;
          total_timeout.tv_usec = 0;
          clnt_stat = clnt_call(client, procnum, lnproc, ln,
                              output, out, total_timeout);
          clnt_destroy(client);
          return (int)clnt_stat;
     }

/*
 * The receiving routines
 */
#include <stdio.h>
#include <rpc/rpc.h>

main()
{
          register SVCXPRT *transp;

          if     ((transp = svctcp_create(RPC_ANYSOCK, 1024, 1024) = = NULL) {
                    fprintf(stderr, "svctcp_create: error\n");
                    exit(1);
          }
          pmap_unset(RCPPROG, RCPVERS);
          if     (!svc_register(transp, RCPPROG, RCPVERS, rcp_service, IPPRORO_TCP)) {
                    fprintf(stderr, "svc_register: erro\n");
                    exit(1);
          }
          svc_run();     /* never return */
          fprintf(stderr, "svc_run should never return\n");
}
rcp_service(rqstp, transp)
          register struct scv_req rqstp;
          register SVCXPRT *transp;
{
          switch (rqstp-->rq_proc) {
          case NULLPROC:
                    if     (svc_sendreply(transp, xdr_void, 0) = = 0) {
                               fprintf(stderr, "err: rcp_service\n");
                              exit(1);
                    }
                    return;
          case RCPPROC_FP:
               if     (!svc_getargs(transp, xdr_rcp, stdout)) {
                              svcerr_decode(transp);
                              return;
                }
               if     (!svc_sendreply(transp, xdr_void, 0)) {
                              fprintf(stderr, "cannot reply\n");
                              return;
               }
               exit(0);
     default:
               svcerr-noproc(transp);
               return;
     }
}

Home


4.1.4.3 CALLBACK PROCEDURES

Occasionally, it is useful to have a server become a client and make an RPC call back to the process that is its client. An example is remote debugging, where the client is a window system program, and the server is a debugger running on the remote machine. Most of the time, the user clicks a mouse button at the debugging window, which converts this to a debugger command, and then makes an RPC call to the server (where the debugger is actually running), telling it to execute that command. However, when the debugger hits a breakpoint, the roles are reversed, and the debugger wants to make an RPC call to the window program, so that it can inform the user that a breakpoint was reached.

To do an RPC callback, a program number on which to make the RPC call is needed. Since this is a dynamically generated program number, it should be in the transient range, 0x40000000 - 0x5FFFFFFF. The routine gettransient() returns a valid program number in the transient range and registers it with the portmapper. It only talks to the portmapper running on the same machine as the gettransient() routine itself. The call to pmap_set() is a test and set operation, in that it indivisibly tests whether a program number is already registered and if it is not, reserves it. On return, the sockp argument contains a socket that can be used as the argument to an svcudp_create() or svctcp_create() call.

Example:


     #include <stdio.h>
     #include <rpc/rpc.h>
     #include <sys/socket.h>

     gettransient(proto, vers, sockp)
               int *sockp;
     {
               static int prognum = 0x40000000;
               int s, len, socktype;
               struct sockaddr_in addr;

               switch (proto) {
                    case IPPROTO_UDP:
                                   socktype = SOCK_DGRAM;
                                   break;
                    caseIPPROTO_TCP:
                                   socktype = SOCK_STREAM;
                                   break;
                    default:
                                   fprintf(stderr, "unknown protocol type\n");
                                   return (0);
               }
               if      (*sockp = = RPC_ANYSOCK) {
                         if      ((s = socket(AF_INET, socktype, 0)) < 0) {
                                   perror("socket");
                                   return(0);
                         }
                         *sockp = s;
               }
               else
                         s = *sockp;
               addr.sin_sin_addr.s_addr = 0;
               addr.sin_family = AF_INET;

Home



               addr.sin_ port = 0;
               len = sizeof(addr);
               /*
                * may be already bound, so don't check for err
                */
               bind(s, &addr, len);
               if     (getsockname(s, &addr, &len) < 0) {
                         perror("getsockname");
                         return(0);
               }
               while(pmap_set(prognum++, vers, proto, addr.sin_port) = = 0)
                         continue;
               return(prognum-1);
     }

     /*
     * client
     */
     #include <stdio.h>
     #include <rpc/rpc.h>

     int callback();
     char hostname[256];

     main(argc, argv)
               char **argc;
     {
               int x, ans, s;
               SVCXPRT *xprt;

               gethostname(hostname, sizeof(hostname));
               s = RPC_ANYSOCK;
               x = gettransient(IPPROTO_UDP, 1, &s);
               fprintf(stderr, "client gets prognum %d\n", x);

               if      ((xprt = svcudp_create(s)) = = NULL) {
                         fprintf(stderr, "rpc_server: svcudp_create\n, x);
                         exit(1);
               }
               (void)svc_register(xprt, x, 1, callback, 0);

               ans = callrpc(hostname, EXAMPLEPROG, EXAMPLEPROC_CALLBACK,
                      EXAMPLEVERS, xdr_int, &x, xdr_void, 0);
               if     (ans != 0) {
                         fprintf(stderr, "call: ");
                         clnt_perrno(ans);
                    fprintf(stderr, "\n");
          }
          svc_run();
          fprintf(stderr, "Error: svc_run shouldn't have returned\n);
}

Home



     callback(rqstp, transp)
               register struct svc_req *rqstp;
               register SVCXPRT *transp;
     {
               switch (rqstp-->rq _ proc) {
                    case 0:
                                   if    (svc_sendreply(transp, xdr_void, 0) = = FALSE) {
                                        fprintf(stderr, "err: rusersd\n");
                                        exit(1);
                                   }
                                   exit(0);
                         case 1:
                                   if     (!svc_getargs(transp, xdr_void, 0)) {
                                        svcerr_decode(transp);
                                        exit(1);
                                   }
                                   fprintf(stderr, "client got callback\n");
                                   if     (svc_sendreply(transp, xdr_void, 0) = = FALSE) {
                                        fprintf(stderr, "err: rusersd\n");
                                        exit(1);
                                   }
               }
     }

The following pair of programs illustrate how to use the gettransient() routine. The client makes an RPC call to the server, passing it a transient program number. Then the client waits around to receive a callback from the server at that program number. The server registers the program EXAMPLEPROG, so that it can receive the RPC call informing it of the callback program number. Then, at some random time (on receiving an ALRM signal in this example), it sends a callback RPC call, using the program number it received earlier.


     /*
     *server
     */
     #include <stdio.h>
     #include <rpc/rpc.h>
     #include <sys/signal.h>

     char *getnewprog();
     char hostname[256];
     int docallback();
     int pnum;          /*program number for callback routine */

     main(argc, argv)
               char **argv;
{
               gethostname(hostname, sizeof(hostname));
               registerrpc(EXAMPLEPROG, EXAMPLEPROC_CALLBACK, EXAMPLEVERS,
                 getnewprog, xdr_int, xdr_void);
               fprintf(stderr, "server going into svc_run\n");
               alarm(10);
               signal(SIGALRM, docallback);
               svc_run();
               fprintf(stderr, "Error: svc_run shouldn't have returned\n");

Home



     }

     char *
               getnewprog(pnump)
                    char *pnump;
     {
               pnum = *(int *)pnump;
               return NULL;
     }
               docallback()
{
               int ans;
               ans = callrpc(hostname, pnum, 1, 1, xdr_void, 0, xdr_void, 0);
               if     (ans != 0) {
                         fprintf(stderr, "server: ");
                         clnt_perrno(ans);
                         fprintf(stderr, "\n");
               }
     }

4.2 REMOTE PROCEDURE CALL PROTOCOL SPECIFICATION

This section specifies a message protocol used in implementing the Remote Procedure Call (RPC) package. The message protocol is specified with the eXternal Data Representation (XDR) language.

This section assumes that the reader is familiar with both RPC and XDR. It does not attempt to justify RPC or its uses. Also, the casual RPC user does not need to be familiar with the information in this section.

Home


4.2.1 Requirements

The RPC protocol must provide for the following:

  1. Unique specification of a procedure to be called.

  2. Provisions for matching response messages to request messages.

  3. Provisions for authenticating the caller to service and vice versa.

Home


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

  1. RPC protocol mismatches.

  2. Remote program protocol version mismatches.

  3. Protocol errors (like mis-specification of a procedure's parameter).

  4. Reasons why remote authentication failed.

  5. Any other reasons why the desired procedure was not called.

4.2.1.1 REMOTE 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 a central authority (currently Sun Microsystems). Once an implementor has a program number, he can implement his remote program. The first implementation would most probably have the version number of 1. Because most new protocols evolve into better, more 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 can also change. Therefore, the call message also has the RPC version number in it; this field must be two (2).

The reply message to a request message has enough information to distinguish the following error conditions:

  1. The remote implementation of RPC does not speak protocol version 2. The lowest and highest supported RPC version numbers are returned.

  2. The remote program is not available on the remote system.

  3. The remote program does not support the requested version number. The lowest and highest supported remote program version numbers are returned.

  4. The requested procedure number does not exist (this is usually a caller side protocol or programming error).

  5. The parameters to the remote procedure appear to be garbage from the server's point of view. (Again, this is caused by a disagreement about the protocol between client and service.)

Home


4.2.1.2 AUTHENTICATION

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_SHORT                 = 2
                    /* and more to be defined  */
     };
     struct      opaque_auth {
                    union switch (enum auth_flavor) {
                              default: string auth_body<400>;
                    };
     };

Any opaque_auth structure is an auth_flavor enumeration followed by a counted string, whose bytes 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. Section 4.3 defines three authentication protocols.

If authentication parameters are rejected, the response message states why.

4.2.1.3 PROGRAM NUMBER ASSIGNMENT

Program numbers are given out in groups of 0x20000000 (536870912) according to the following chart:


                   0     --     1fffffff     defined by Sun
     20000000     --     3fffffff     defined by user
     40000000     --     5fffffff     transient
     60000000     --     7fffffff     reserved
     80000000     --     9fffffff     reserved
     a0000000     --     bfffffff     reserved
     c0000000     --     dfffffff     reserved
     e0000000     --     ffffffff     reserved

The first group is a range of numbers administered by Sun Microsystems, and should be identical for all RPC users. The second range is for applications peculiar to a particular user. This range is intended primarily for debugging new programs. When a user develops an application that might be of general interest, that application should be given an assigned number in the first range. The third group is for applications that generate program numbers dynamically. The final groups are reserved for future use, and should not be used.

4.2.2 Other Uses and Abuses of the RPC Protocol

This protocol is intended for calling remote procedures. 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. The RPC message protocol is currently used for two non-RPC protocols: batching (or pipelining) and broadcast RPC. These two protocols are discussed on the following page.

Home


4.2.3 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.

NOTE

This is an XDR specification, not C code.

Example:


     enum msg_type {
               CALL = 0,
               REPLY = 1
     };
     /*
      *A reply to a call message can take on two forms:
      *the massage 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,
                         /*remote procedure was successfully executed */
               PROG_UNAVAIL = 1,
                         /*remote machine exports the program number */
          PROG_MISMATCH = 2,
                         /*remote machine can't support version number */
          PROC_UNAVAIL = 3,
                         /*remote program doesn't know about procedure */
          GARBAGE_ARGS = 4
                         /*remote procedure can't figure out parameters */
     };
 

Home



     /*
      * Reasons why a call message was rejected:
      */
     enum reject_stat {
               RPC_MISMATCH = 0,
                         /*RPC version number was not two (2) */
               AUTH_ERROR = 1
                         /*caller not authenticated on remote machine */
     };

     enum reject_stat {
               RPC_MISMATCH = 0,
                         /*RPC version number was not two (2) */
               AUTH_ERROR = 1
                         /*caller not authenticated on remote machine */
     };

     /*
      *Why authentication failed:
      */
     enum auth_stat {
               AUTH_BADCRED = 1,
                         /*bogus credentials (seal broken) */
               AUTH_REJECTEDCRED = 2,
                         /*client should begin new session */
               AUTH_BADVERF = 3,
                         /*bogus verifier (seal broken) */
               AUTH_REJECTEDVERF = 4,
                         /*verifier expired or was replayed */
               AUTH_TOOWEAK = 5
                         /*rejected due to 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;
      *the service side cannot treat this id as any
      *type of sequence number.
      */
     struct rpc_msg {
               unsigned xid;
               union switch (enum msg_type) {
                         CALL: struct call_body;
                         REPLY: struct reply_body;
               };
     };

Home



     /*
      *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
      *authentication parameters are followed by
      *the parameters to the remote procedure;
      *these parameters are specified by the
      *specific program protocol.
      */
     struct call_body {
               unsinged rpcvers;               /*must be equal to two (2) */
               unsigned prog;
               unsigned vers;
               unsigned proc;
               struct opaque_auth cred;
               struct opaque-auth verf;
               /*procedure specific parameters start here */
     };
     /*
      *Body of a reply to an RPC request.
      *The call message was either accepted or rejected.
      */
     struct reply_body {
               union switch (enum reply_stat) {
                    MSG_ACCEPTED: struct accepted_reply;
                    MSG_DENIED: struct rejected_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.
      */

Home



     struct accepted_reply {
               struct opaque_auth verf;
               union switch (enum accept_stat) {
                         SUCCESS: struct {
                              /*
                               *  procedure-specific results start here
                               */
               };
               PROG_MISMATCH: struct {
                    unsigned low;
                    unsigned high;
               };
               default: struct {
                    /*
                     *void. Cases include PROG_UNAVAIL,
                     *PROC_UNAVAIL, and GARBAGE_ARGS.
                     */
               };
          };
     };
     /*
      *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.
      */
     struct rejected_reply {
          union switch (enum reject_stat) {
                    RPC_MISMATCH: struct {
                         unsigned low;
                         unsigned high;
                    };
                    AUTH_ERROR: ecnum auth_stat;
          };
     };

Home


4.3 AUTHENTICATION PARAMETER SPECIFICATION

Authentication parameters are opaque, but open-ended to the rest of the RPC protocol. This section defines some authentication types that are implemented and generally supported.

4.3.1 Null Authentication

Often calls must be made where the caller does not know who he is and the server does not 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 auth_body string are undefined. It is recommended that the string length be zero.

4.3.2 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(1). The bytes of the credential's string encode the following (XDR) structure:


     struct auth_unix {
          unsigned          stamp;
          string               machinename<255>;
          unsigned          uid;
          unsigned          gid;
          unsigned          gids<10>;
};

The stamp is an arbitrary id that the caller machine may generate. The machinename is the name of the caller's machine (like "krypton"). The uid is the caller's effective user id. The gid is the caller's effective group id. gids is a counted array of groups that contain the caller as a member. The verifier accompanying the credentials should be of AUTH_NULL (previously defined).

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 auth_opaque structure. This new auth_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 auth_opaque structures (passed back via a 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 auth_opaque structure at any time. If this happens, the remote procedure call message is rejected due to an authentication error. The reason for the failure is AUTH_REJECTEDCRED. At this point, the caller may wish to try the original AUTH_UNIX style of credentials.

4.4 RECORD MARKING STANDARD

When RPC messages are passed on top of a byte stream protocol (like 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 RM/TCP/IP transport is used for passing RPC messages on TCP streams. One RPC message fits into one RM record.

Home


A record is composed of one or more record fragments. A record fragment is a four-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 that 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 that 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.

This record specification is not in XDR standard form.

4.5 Port Mapper Program Protocol

The port mapper program maps RPC program and version numbers to UDP/IP or TCP/IP port numbers. This program makes dynamic binding of remote programs possible.

This 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.

4.5.1 The Port Mapper RPC Protocol

The protocol is specified by the XDR description language as shown in the following example.

Example:


     Port Mapper RPC Program Number: 100000
          Version Number: 1
          Supported Transports:
               UDP/IP on port 111
               RM/TCP/IP on port 111
     /*
      *Handy transport protocol numbers
      */
     #define IPPROTO_TCP     6
          /*protocol number used for rpc/rm/tcp/ip */
     #define IPPROTO_UDP    17
          /*protocol number used for rpc/udp/ip */
     /*Procedures */

     /*
      *Convention: procedure zero of any protocol takes no parameters
      *and returns no results.
      */
     0.PMAPPROC_NULL () returns ()
     /*
      *Procedure 1, setting a mapping:
      *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 service request. The
      *procedure returns success whose value is TRUE if the procedure
      *successfully established the mapping and FALSE otherwise. The
      *procedure will refuse to establish a mapping if one already exists
      *for the tuple [prog, vers, prot].

Home



      */
     1.PMAPPROC_SET (prog, vers, prot, port) returns (success)
          unsigned     prog;
          unsigned     vers;
          unsigned     prot;
          unsigned     port;
          boolean       success;
     /*
      *Procedure 2, Unsetting a mapping:
      *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.
      */
     2. PMAPPROC_UNSET (prog, vers, dummy1, dummy2) returns (success)
          unsigned     prog;
          unsigned     vers;
          unsigned     dummy1;     /*this value is always ignored */
          unsigned     dummy2;     /*this value is always ignored */
          boolean     success;
     /*
      *Procedure 3, looking-up a mapping:
      *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 zeros means that the program has not been registered.
      */
     3.PMAPPROC_GETPORT (prog, vers, prot, dummy) returns (port)
          unsigned     prog;
          unsigned     vers;
          unsigned     port;
          unsigned     dummy;     /*this value is always ignored */
          unsigned     port;          /*zero means the program is not registered */
     /*
      *Procedure 4, dumping the mappings:
      *This procedure enumerates all entries in the port mapper's data base.
      *The procedure takes no parameters and returns a "list" of
      *[program, version, prot, port] values.
      */
     4.PMAPPROC_DUMP () returns (maplist)
          struct     maplist {
                    union     switch (boolean) {
                              FALSE:      struct { /* void, end of list */ };
                              TRUE:      struct {
                                        unsigned      prog;
                                        unsigned      vers;
                                        unsigned      prot;
                                        unsigned      port;
                                        struct           maplist the_rest;
                    };
               };
          } maplist;

Home



     /*
      *     Procedure 5, indirect call routine:
      *     The procedures allows a caller to call another remote
      *     procedure on the same machine without knowing the remote
      *     procedure's port number. Its intended use is 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.
      *
      *     NB:
      *     1. This procedure only sends a response if the procedure was
      *     successfully executed and is silent (No response) otherwise.
      *     2. The port mapper communicates with the remote program via
      *     UDP/IP only.
      *
      *     The procedure returns the port number of the remote program and
      *     the bytes of results are the results of the remote procedure.
      */
     5.     PMAPPROC_CALLIT (prog, vers, proc, args) returns (port, results)
               unsigned     prog;
               unsigned     vers;
               unsigned     proc;
               string          args<>;
               unsigned     port;
               string          results<>;

Home

Contents Previous Chapter Next Chapter Index