Go to main content

ONC+ RPC Developer's Guide

Exit Print View

Updated: March 2019
 
 

rpcgen Programming Techniques

This section suggests some common RPC and rpcgen programming techniques.

Table 10  RPC Programming Techniques
Technique
Description
Network type
rpcgen can produce server code for specific transport types.
Define statements
You can define C-preprocessing symbols on rpcgen command lines.
Broadcast calls
Servers need not send error replies to broadcast calls.
Debugging applications
Debug as normal function calls, then change to a distributed application.
Port monitor support
Port monitors can "listen" on behalf of RPC servers.
Dispatch tables
Programs can access server dispatch tables.
Time-out changes
You can change client default time-out periods.
Authentication
Clients can authenticate themselves to servers; the appropriate servers can examine client authentication information.

Network Types/Transport Selection

rpcgen takes optional arguments that enable a programmer to specify desired network types or specific network identifiers. For details of network selection, see Oracle Solaris 11.3 Programming Interfaces Guide.

The –s flag creates a server that responds to requests on the specified type of transport. For example, the command 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.


Note - Be careful using servers created by rpcgen with the –n flag. Network identifiers are host specific, so the resulting server might not run as expected on other hosts.

Command-Line Define Statements

You can define C-preprocessing symbols and assign values to them from the command line. Command-line define statements can be used to generate conditional debugging code when the DEBUG symbol is defined. For example:

$ rpcgen -DDEBUG proto.x

Server Response to Broadcast Calls

When a procedure has been called through broadcast RPC and cannot provide a useful response, the server should send no reply to the client, thus reducing 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 return and sends no reply.

The following code example is a procedure that replies only if it reaches an NFS server.

Example 23  NFS Server Response to Broadcast Calls
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 then RPC sends a reply */
	return( (void *) &notnull );
}

A procedure must return a non-NULL pointer when it is appropriate for RPC library routines to send a reply.

In the example, if the procedure reply_if_nfsserver() is defined to return nonvoid values, the return value &notnull should point to a static variable.

Port Monitor Support

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.

Services might wait for a specified interval after completing a service request, in case another request follows. If no call arrives 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.


Note - 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, the server should exit immediately on completion.

By default, services created using rpcgen wait for 120 seconds after servicing a request before exiting. You can change the interval with the –K flag. In the following example, the server waits for 20 seconds before exiting. To create a server that exits immediately, you can use zero value for the interval period.

$ rpcgen -K 20 proto.x
$ rpcgen -K 0 proto.x

To create a server that never exits, the value is –K -1.

Time-out Changes

After sending a request to the server, a client program waits for a default period (25 seconds) to receive a reply. You can change this timeout by using the clnt_control() routine. See Standard Interfaces for additional uses of the clnt_control() routine. See also the rpc(3NSL) man page. When considering time-out periods, be sure to allow the minimum amount of time required for "round-trip" communications over the network. The following code example illustrates the use of clnt_control().

Example 24  clnt_control Routine
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);

Client Authentication

The client create routines do not have any facilities for client authentication. Some clients might have to authenticate themselves to the server.

The following example illustrates one of the least secure authentication methods in common use. See Authentication of RPC Clients for information about more secure authentication techniques.

Example 25  AUTH_SYS Authentication Program
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.

The following example is for a server that checks client authentication data. It is modified from printmessage_1() in rpcgen Tutorial. The code allows only superusers to print a message to the console.

Example 26  printmsg_1 for Superuser
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. */
}

Dispatch Tables

Sometimes programs should have access to the dispatch tables used by the RPC package. For example, the server dispatch routine might check authorization and then invoke the service routine. Or, a client library might handle 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". You can invoke rpcgen with the –t option to build only the header file. You cannot invoke rpcgen 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.

The find_proc() routine shows an example of how to locate a procedure in the dispatch tables.

Example 27  Using a Dispatch Table
struct rpcgen_table *
find_proc(proc) 
   rpcproc_t 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).

Using this technique, 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

Use the following define when writing the server.

#define RPCGEN_ACTION(routine)routine

64-Bit Considerations for rpcgen

In Example 27, Using a Dispatch Table proc is declared as type rpcproc_t. Formerly, RPC programs, versions, procedures, and ports were declared to be of type u_long. On a 32-bit system, a u_long is a 4-byte quantity (as is an int). On a 64-bit system, a u_long is an 8-byte quantity. The data types rpcprog_t, rpcvers_t, rpc_proc_t, and rpcport_t should be used whenever possible in declaring RPC programs, versions, procedures, and ports in place of both u_long and long. These newer types provide backwards compatibility with 32-bit systems. They are guaranteed to be 4-byte quantities no matter which system rpcgen is run on. While rpcgen programs using u_long versions of programs, versions, and procedures can still run, they have different consequences on 32-bit and 64-bit systems. For that reason, replace them with the appropriate newer data types. In fact, avoid using long and u_long whenever possible.

Source files created with rpcgen containing XDR routines use different inline macros depending on whether the code is to run on a 32-bit or 64-bit system. Specifically, the source files will use the IXDR_GET_INT32() and IXDR_PUT_INT32() macros instead of IXDR_GETLONG() and IXDR_PUTLONG(). For example, if the rpcgen source file foo.x contains the following code, the resulting foo_xdr.c file ensures that the correct inline macro is used.

struct foo {
        char      c;
        int       i1;
        int       i2;
        int       i3;
        long      l;
        short     s;
};
#if defined(_LP64) || defined(_KERNEL)
	register int *buf; 

#else   
	register long *buf; 

#endif
  . . . 
 #if defined(_LP64) || defined(_KERNEL)

 	IXDR_PUT_INT32(buf, objp->i1);                         

	IXDR_PUT_INT32(buf, objp->i2);                         
	IXDR_PUT_INT32(buf, objp->i3);                         

	IXDR_PUT_INT32(buf, objp->l);                         

	IXDR_PUT_SHORT(buf, objp->s); 
#else                         

 
	IXDR_PUT_LONG(buf, objp->i1);                         

	IXDR_PUT_LONG(buf, objp->i2);                         

	IXDR_PUT_LONG(buf, objp->i3);                         
	IXDR_PUT_LONG(buf, objp->l);                         

	IXDR_PUT_SHORT(buf, objp->s); 
#endif

The code declares buf to be either int or long, depending on whether the system is 64-bit or 32-bit.

Currently, data types transported by using RPC are limited in size to 4-byte quantities (32 bits). The 8-byte long is provided to enable applications to make maximum use of 64-bit architecture. However, programmers should avoid using longs, and functions that use longs, such as x_putlong(), in favor of ints whenever possible. As noted previously, RPC programs, versions, procedures, and ports have their own dedicated types. xdr_long() fails if the data value is not between INT32_MIN and INT32_MAX. Also, the data could be truncated if inline macros such as IXDR_GET_LONG() and IXDR_PUT_LONG() are used. The same concerns apply to u_long variables. See also the xdr_long(3NSL) man page.

IPv6 Considerations for rpcgen

Only TI-RPC supports IPv6 transport. If an application is intended to run over IPv6, now or in the future, you must not use the backward compatibility switch. The selection of IPv4 or IPv6 is determined by the respective order of associated entries in /etc/netconfig.

Debugging Applications

To 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(3NSL) man page) and the authentication routines. Do not link with libnsl.

Link the procedures from the previous example by using the command:

$ 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. For details, see Testing Programs Using Low-Level Raw RPC for details.

    Two kinds of errors 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 this problem are:

  • The procedure is not available

  • The remote server is not responding

  • The remote server is unable to decode the arguments.

In Example 26, printmsg_1 for Superuser, 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 26, printmsg_1 for Superuser, an error can be returned by opendir(). The handling of these errors is application specific and is the responsibility of the programmer.

Note that you will be unable to link the client and server programs to each other if you are using the –C option, because of the –_svc suffix added to the server-side routines.