This section suggests some common RPC and rpcgen programming techniques. Each topic is covered in its own subsection.
Clients may authenticate themselves to servers; interested servers can examine client authentication information.
rpcgen takes optional arguments that allow a programmer to specify desired network types or specific network identifiers. (For details of network selection, see Transport Interfaces Programming Guide).
The -s flag creates a server that responds to requests on the specified type of transport. For example, the invocation
rpcgen -s datagram_n prot.x
writes a server to standard output that responds to any of the connectionless transports specified in the NETPATH
environment variable (or in /etc/netconfig, if NETPATH
is not defined). A command line can contain multiple -s flags and their network types.
Similarly, the -n flag creates a server that responds only to requests from the transport specified by a single network identifier.
Be careful using servers created by rpcgen with the -n flag. Network identifiers are host specific, so the resulting server may not run as expected on other hosts.
You can define C-preprocessing symbols and assign values to them from the command line. Command line define statements can, for example, be used to generate conditional debugging code when the DEBUG symbol is defined. For example:
$ rpcgen -DDEBUG proto.x
When a procedure has been called through broadcast RPC and cannot provide a useful response, the server should send no reply to the client. This reduces network traffic. To prevent the server from replying, a remote procedure can return NULL as its result. The server code generated by rpcgen detects this and sends no reply.
Example 3-23is a procedure that replies only if it is an NFS server.
void * reply_if_nfsserver() { char notnull; /*only here so we can *use its address */ if( access( "/etc/dfs/sharetab", F_OK ) < 0 ) { /* prevent RPC from replying */ return( (void *) NULL ); } /* assign notnull a non-null value * so RPC will send a reply */ return( (void *) ¬null ); }
A procedure must return a non-NULL pointer when it wants RPC library routines to send a reply.
In Example 3-23, if the procedure reply_if_nfsserver() is defined to return non void values, the return value (¬null) should point to a static variable.
Port monitors such as inetd and listen can monitor network addresses for specified RPC services. When a request arrives for a particular service, the port monitor spawns a server process. After the call has been serviced, the server can exit. This technique conserves system resources. The main server function generated by rpcgen allows invocation by inetd. See "Using inetd ""for details.
It may be useful for services to wait for a specified interval after satisfying a service request, in case another request follows. If there is no call in the specified time, the server exits, and some port monitors, like inetd, continue to monitor for the server. If a later request for the service occurs, the port monitor gives the request to a waiting server process (if any), rather than spawning a new process.
When monitoring for a server, some port monitors, like listen(), always spawn a new process in response to a service request. If a server is used with such a monitor, it should exit immediately on completion.
By default, services created using rpcgen wait for 120 seconds after servicing a request before exiting. The programmer can change the interval with the -K flag. In this example,
$ rpcgen -K 20 proto.x
the server waits for 20 seconds before exiting. To create a server that exits immediately, zero value can be used for the interval period:
$ rpcgen -K 0 proto.x.
To create a server that never exits, the value is -K -1.
After sending a request to the server, a client program waits for a default period (25 seconds) to receive a reply. This time-out may be changed using the clnt_control() routine. See "Standard Interfaces ", for additional uses of the clnt_control() routine. See also the rpc(3N) manpage. When considering time-out periods, be sure to allow the minimum amount of time required for "round-trip" communications over the network. Example 3-24 illustrates the use of clnt_control().
struct timeval tv; CLIENT *clnt; clnt = clnt_create( "somehost", SOMEPROG, SOMEVERS, "visible" ); if (clnt == (CLIENT *)NULL) exit(1); tv.tv_sec = 60; /* change time-out to * 60 seconds */ tv.tv_usec = 0; clnt_control(clnt, CLSET_TIMEOUT, &tv);
The client create routines do not have any facilities for client authentication. Some clients may have to authenticate themselves to the server.
The following example illustrates one of the least secure authentication methods in common use. See "Authentication ", for information on the more secure DES authentication technique.
CLIENT *clnt; clnt = clnt_create( "somehost", SOMEPROG, SOMEVERS, "visible" ); if (clnt != (CLIENT *)NULL) { /* To set AUTH_SYS style authentication */ clnt->cl_auth = authsys_createdefault(); }
Authentication information is important to servers that have to achieve some level of security. This extra information is supplied to the server as a second argument.
Example 3-26is a server that checks client authentication data. It is modified from printmessage_1() in "An rpcgen Tutorial" and only allows superusers to print a message to the console.
int * printmessage_1(msg, req) char **msg; struct svc_req *req; { static int result; /* Must be static */ FILE *f; struct authsys_parms *aup; aup = (struct authsys_parms *)req->rq_clntcred; if (aup->aup_uid != 0) { result = 0; return (&result) } /* Same code as before. */ }
It is sometimes useful for programs to have access to dispatch tables used by the RPC package. For example, the server dispatch routine may check authorization and then invoke the service routine; or a client library may deal with the details of storage management and XDR data conversion.
When invoked with the -T option, rpcgen generates RPC dispatch tables for each program defined in the protocol description file, proto.x, in the file proto_tbl.i. The suffix.i stands for "index." rpcgen may be invoked with the -t option to build only the header file. rpcgen cannot be invoked in C-style mode (-N) with either the -T or -t flag.
Each entry in the dispatch table is a struct
rpcgen_table, defined in the header file proto.h as follows:
struct rpcgen_table { char *(*proc)(); xdrproc_t xdr_arg; unsigned len_arg; xdrproc_t xdr_res; xdrproc_t len_res };
where:
proc is a pointer to the service routine
xdr_arg is a pointer to the input (argument) xdr routine
len_arg is the length in bytes of the input argument
xdr_res is a pointer to the output (result) xdr routine
len_res is the length in bytes of the output result
The table, named dirprog_1_table for the dir.x example, is indexed by procedure number. The variable dirprog_1_nproc contains the number of entries in the table.
An example of how to locate a procedure in the dispatch tables is shown by the routine find_proc():
struct rpcgen_table * find_proc(proc) u_long proc; { if (proc >= dirprog_1_nproc) /* error */ else return (&dirprog_1_table[proc]); }
Each entry in the dispatch table contains a pointer to the corresponding service routine. However, that service routine is usually not defined in the client code. To avoid generating unresolved external references, and to require only one source file for the dispatch table, the rpcgen service routine initializer is RPCGEN_ACTION(proc_ver).
This way, the same dispatch table can be included in both the client and the server. Use the following define statement when compiling the client:
#define RPCGEN_ACTION(routine) 0
And use the following define when writing the server:
#define RPCGEN_ACTION(routine)routine
You can simplify the testing and debugging process. First test the client program and the server procedure in a single process by linking them with each other rather than with the client and server skeletons. Comment out calls to the client create RPC library routines (see the rpc_clnt_create(3N) manpage) and the authentication routines. Do not link with libnsl.
Link the procedures from previous example by:
cc rls.c dir_clnt.c dir_proc.c -o rls
With the RPC and XDR functions commented out, the procedure calls execute as ordinary local function calls, and the program is debugged with a local debugger such as dbxtool. When the program works, the client program is linked to the client skeleton produced by rpcgen and the server procedures are linked to the server skeleton produced by rpcgen.
You can also use the Raw RPC mode to test the XDR routines. See "Testing Programs Using Low-level Raw RPC " for details.
There are two kinds of errors that can happen in an RPC call. The first kind of error is caused by a problem with the mechanism of the remote procedure calls. Examples of these are (1) the procedure is not available, (2) the remote server is not responding, and (3) the remote server is unable to decode the arguments. In Example 3-26, an RPC error happens if result is NULL. The reason for the failure can be displayed by using clnt_perror(), or an error string can be returned through clnt_sperror().
The second type of error is caused by the server itself. In Example 3-26, an error can be returned by opendir(). The handling of these errors is application specific and is the responsibility of the programmer.
Note that the mechanism illustrated by the paragraphs above does not function with the -C option because of the _svc suffix added to the server-side routines.