v06i090: Sun RPC Source (rpc2), Part02/11
sources-request at mirror.UUCP
sources-request at mirror.UUCP
Wed Aug 6 09:46:35 AEST 1986
Submitted by: sun!ferne!marks (Mark Stein)
Mod.sources: Volume 6, Issue 90
Archive-name: rpc2/Part02
[ All I have done is verify that the shar files unpack correctly. -r$ ]
Sun RPC source (part 2 of 11). This software package contains code
and documentation for Revision 3.0 of the Sun Remote Procedure Call
library. In addition, a beta version of the XDR/RPC protocol compiler
is included. Comments about this latest release may be mailed to
sun!rpc or rpc at sun.com.
Sun RPC is a product of Sun Microsystems, Inc. and is provided for
unrestricted use provided that this legend is included on all tape
media and as a part of the software program in whole or part. Users
may copy or modify Sun RPC without charge, but are not authorized to
license or distribute it to anyone else except as part of a product or
program developed by the user.
- - - - - - - - - C U T - H E R E - - - - - - - - - - - - - - - - - -
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
# rpc/doc/rpc.prog.p1
# This archive created: Mon Jul 14 16:54:58 1986
export PATH; PATH=/bin:/usr/bin:$PATH
for d in rpc rpc/doc rpc/rpclib rpc/tools rpc/toys rpc/rpclib/profiled rpc/rpcgen rpc/rpcgen/test
do
if test ! -d $d
then
echo "shar: Making directory $d"
mkdir $d
chmod 755 $d
fi
done
echo shar: "extracting 'rpc/doc/rpc.prog.p1'" '(49365 characters)'
if test -f 'rpc/doc/rpc.prog.p1'
then
echo shar: "will not over-write existing file 'rpc/doc/rpc.prog.p1'"
else
sed 's/^X//' << \SHAR_EOF > 'rpc/doc/rpc.prog.p1'
X.PL RIGHT
X.TL
XRemote Procedure Call
X.br
XProgramming Guide
X.bp
X.NH
XIntroduction
X.LP
XThis document is intended for programmers
Xwho wish to write network applications
Xusing remote procedure calls (explained below),
Xthus avoiding low-level system primitives based on sockets.
XThe reader must be familiar with the C programming language,
Xand should have a working knowledge of network theory.
X.LP
XPrograms that communicate over a network
Xneed a paradigm for communication.
XA low-level mechanism might
Xsend a signal on the arrival of incoming packets,
Xcausing a network signal handler to execute.
XA high-level mechanism would be the Ada
X.LW rendezvous .
XThe method used at Sun is the
XRemote Procedure Call (RPC) paradigm,
Xin which a client communicates with a server.
XIn this process,
Xthe client first calls a procedure to send a data packet to the server.
XWhen the packet arrives, the server calls a dispatch routine,
Xperforms whatever service is requested, sends back the reply,
Xand the procedure call returns to the client.
X.NH 2
XLayers of RPC
X.LP
XThe RPC interface is divided into three layers.
XThe highest layer is totally transparent to the programmer.
XTo illustrate,
Xat this level a program can contain a call to
X.LW rnusers() ,
Xwhich returns the number of users on a remote machine.
XYou don't have to be aware that RPC is being used,
Xsince you simply make the call in a program,
Xjust as you would call
X.LW malloc() .
X.LP
XAt the middle layer, the routines
X.LW registerrpc()
Xand
X.LW callrpc()
Xare used to make RPC calls:
X.LW registerrpc()
Xobtains a unique system-wide number, while
X.LW callrpc()
Xexecutes a remote procedure call.
XThe
X.LW rnusers()
Xcall is implemented using these two routines.
XThe middle-layer routines are designed for most common applications,
Xand shield the user from knowing about sockets.
X.LP
XThe lowest layer is for more sophisticated applications,
Xsuch as altering the defaults of the routines.
XAt this layer, you can explicitly manipulate
Xsockets that transmit RPC messages.
XThis level should be avoided if possible.
X.LP
XSection 2 of this manual illustrates use of the highest two layers
Xwhile Section 3 presents the low-level interface.
XSection 4 of the manual discusses miscellaneous topics.
XThe final section summarizes
Xall the entry points into the RPC system.
X.LP
XAlthough this document only discusses the interface to C,
Xremote procedure calls can be made from any language.
XEven though this document discusses RPC
Xwhen it is used to communicate
Xbetween processes on different machines,
Xit works just as well for communication
Xbetween different processes on the same machine.
X.NH 2
XThe RPC Paradigm
X.LP
XHere is a diagram of the RPC paradigm:
X.LP
X.PL FULL
X.PS
XL1: arrow down 1i "client " rjust "program " rjust
XL2: line right 1.5i "\fLcallrpc()\fP" "function"
Xmove up 1.5i; line dotted down 6i; move up 4.5i
Xarrow right 1i
XL3: arrow down 1i "execute " rjust "request " rjust
XL4: arrow right 1.5i "call" "service"
XL5: arrow down 1i " service" ljust " executes" ljust
XL6: arrow left 1.5i "\fLreturn\fP" "answer"
XL7: arrow down 1i "request " rjust "completed " rjust
XL8: line left 1i
Xarrow left 1.5i "\fLreturn\fP" "reply"
XL9: arrow down 1i "program " rjust "continues " rjust
Xline dashed down from L2 to L9
Xline dashed down from L4 to L7
Xline dashed up 1i from L3 "service " rjust "daemon " rjust
Xarrow dashed down 1i from L8
Xmove right 1i from L3
Xbox invis "Machine B"
Xmove left 1.2i from L2; move down
Xbox invis "Machine A"
X.PE
X.FN "Network Communication with the Remote Procedure Call"
X.PL RIGHT
X.bp
X.NH
XHigher Layers of RPC
X.NH 2
XHighest Layer
X.LP
XImagine you're writing a program that needs to know
Xhow many users are logged into a remote machine.
XYou can do this by calling the library routine
X.LW rnusers() ,
Xas illustrated below:
X.BS
X.LS
X#include <stdio.h>
X.sp.5
Xmain(argc, argv)
X int argc;
X char **argv;
X{
X unsigned num;
X.sp.5
X if (argc < 2) {
X fprintf(stderr, "usage: rnusers hostname\en");
X exit(1);
X }
X if ((num = rnusers(argv[1])) < 0) {
X fprintf(stderr, "error: rnusers\en");
X exit(-1);
X }
X printf("%d users on %s\en", num, argv[1]);
X exit(0);
X}
X.Lf
X.BE
XRPC library routines such as
X.LW rnusers()
Xare in the RPC services library
X.LW librpcsvc.a .
XThus, the program above should be compiled with
X.BS
X.LS
X% cc \fIprogram\fP.c -lrpcsvc
X.Lf
X.BE
XThis routine, and other RPC library routines,
Xare documented in section 3R of the
X.I "System Interface Manual for the Sun Workstation" .
XHere is a table of RPC service library routines
Xavailable to the C programmer:
X.TN "RPC Service Library Routines"
X.TS
Xbox;
XcfBI s
Xc c
XlfL l.
X.sp.5
X\s+2RPC Service Library Routines\s-2
X.sp.5
X_
X\fIroutine description\fP
X_
Xrnusers() return number of users on remote machine
Xrusers() return information about users on remote machine
Xhavedisk() determine if remote machine has disk
Xrstat() get performance data from remote kernel
Xrwall() write to specified remote machines
Xgetmaster() get name of YP master
Xgetrpcport() get RPC port number
Xyppasswd() update user password in yellow pages
X.TE
X.LP
XThe other RPC services \(em
X.LW ether ,
X.LW mount ,
X.LW rquota ,
Xand
X.LW spray
X\(em are not available to the C programmer as library routines.
XThey do, however,
Xhave RPC program numbers so they can be invoked with
X.LW callrpc() ,
Xwhich will be discussed in the next section.
X.bp
X.NH 2
XIntermediate Layer
X.LP
XThe simplest interface, which explicitly makes RPC
Xcalls, uses the functions
X.LW callrpc()
Xand
X.LW registerrpc() .
XUsing this method, another way to get the number of remote users is:
X.BS
X.LS
X#include <stdio.h>
X#include <rpcsvc/rusers.h>
X.sp.5
Xmain(argc, argv)
X int argc;
X char **argv;
X{
X unsigned long nusers;
X.sp.5
X if (argc < 2) {
X fprintf(stderr, "usage: nusers hostname\en");
X exit(-1);
X }
X if (callrpc(argv[1],
X RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
X xdr_void, 0, xdr_u_long, &nusers) != 0) {
X fprintf(stderr, "error: callrpc\en");
X exit(1);
X }
X printf("%d users on %s\en", nusers, argv[1]);
X exit(0);
X}
X.Lf
X.BE
XA program number, version number, and procedure number
Xdefines each RPC procedure.
XThe program number defines a group
Xof related remote procedures, each of which has a different
Xprocedure number.
XEach program also has a version number,
Xso when a minor change is made to a remote service
X(adding a new procedure, for example),
Xa new program number doesn't have to be assigned.
XWhen you want to call a procedure to
Xfind the number of remote users, you look up the appropriate
Xprogram, version and procedure numbers
Xin a manual, similar to when you look up the name of memory
Xallocator when you want to allocate memory.
X.LP
XThe simplest routine in the RPC library
Xused to make remote procedure calls is
X.LW callrpc() .
XIt has eight parameters.
XThe first is the name of the remote machine.
XThe next three parameters
Xare the program, version, and procedure numbers.
XThe following two parameters
Xdefine the argument of the RPC call, and the final two parameters
Xare for the return value of the call.
XIf it completes successfully,
X.LW callrpc()
Xreturns zero, but nonzero otherwise.
XThe exact meaning of the return codes is found in
X.LW <rpc/clnt.h> ,
Xand is in fact an
X.LW "enum clnt_stat"
Xcast into an integer.
X.LP
XSince data types may be represented differently on different machines,
X.LW callrpc()
Xneeds both the type of the RPC argument, as well as
Xa pointer to the argument itself (and similarly for the result). For
X.LW RUSERSPROC_NUM ,
Xthe return value is an
X.LW "unsigned long" ,
Xso
X.LW callrpc()
Xhas
X.LW xdr_u_long
Xas its first return parameter, which says
Xthat the result is of type
X.LW "unsigned long" ,
Xand
X.LW &nusers
Xas its second return parameter,
Xwhich is a pointer to where the long result will be placed. Since
X.LW RUSERSPROC_NUM
Xtakes no argument, the argument parameter of
X.LW callrpc()
Xis
X.LW xdr_void .
X.LP
XAfter trying several times to deliver a message, if
X.LW callrpc()
Xgets no answer, it returns with an error code.
XThe delivery mechanism is UDP,
Xwhich stands for User Datagram Protocol.
XMethods for adjusting the number of retries
Xor for using a different protocol require you to use the lower
Xlayer of the RPC library, discussed later in this document.
XThe remote server procedure
Xcorresponding to the above might look like this:
X.BS
X.LS
Xchar *
Xnuser(indata)
X char *indata;
X{
X static int nusers;
X.sp.5
X /*
X * code here to compute the number of users
X * and place result in variable nusers
X */
X return((char *)&nusers);
X}
X.Lf
X.BE
X.LP
XIt takes one argument, which is a pointer to the input
Xof the remote procedure call (ignored in our example),
Xand it returns a pointer to the result.
XIn the current version of C,
Xcharacter pointers are the generic pointers,
Xso both the input argument and the return value are cast to
X.LW "char *" .
X.LP
XNormally, a server registers all of the RPC calls it plans
Xto handle, and then goes into an infinite loop waiting to service requests.
XIn this example, there is only a single procedure
Xto register, so the main body of the server would look like this:
X.BS
X.LS
X#include <stdio.h>
X#include <rpcsvc/rusers.h>
X.sp.5
Xchar *nuser();
X.sp.5
Xmain()
X{
X registerrpc(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
X nuser, xdr_void, xdr_u_long);
X svc_run(); /* never returns */
X fprintf(stderr, "Error: svc_run returned!\en");
X exit(1);
X}
X.Lf
X.BE
X.LP
XThe
X.LW registerrpc()
Xroutine establishes what C procedure
Xcorresponds to each RPC procedure number.
XThe first three parameters,
X.LW RUSERPROG ,
X.LW RUSERSVERS ,
Xand
X.LW RUSERSPROC_NUM
Xare the program, version, and procedure numbers
Xof the remote procedure to be registered;
X.LW nuser()
Xis the name of the C procedure implementing it;
Xand
X.LW xdr_void
Xand
X.LW xdr_u_long
Xare the types of the input to and output from the procedure.
X.LP
XOnly the UDP transport mechanism can use
X.LW registerrpc() ;
Xthus, it is always safe in conjunction with calls generated by
X.LW callrpc() .
X.LP
XWarning: the UDP transport mechanism can only deal with
Xarguments and results less than 8K bytes in length.
X.NH 2
XAssigning Program Numbers
X.LP
XProgram numbers are assigned in groups of 0x20000000 (536870912)
Xaccording to the following chart:
X.BS
X.LS
X 0 - 1fffffff defined by sun
X20000000 - 3fffffff defined by user
X40000000 - 5fffffff transient
X60000000 - 7fffffff reserved
X80000000 - 9fffffff reserved
Xa0000000 - bfffffff reserved
Xc0000000 - dfffffff reserved
Xe0000000 - ffffffff reserved
X.Lf
X.BE
XSun Microsystems administers the first group of numbers,
Xwhich should be identical for all Sun customers.
XIf a customer develops an application that might be of general interest,
Xthat application should be given an assigned number in the first range.
XThe second group of numbers is reserved for specific customer applications.
XThis range is intended primarily for debugging new programs.
XThe third group is reserved for applications that
Xgenerate program numbers dynamically.
XThe final groups are reserved for future use, and should not be used.
X.LP
XTo register a protocol specification,
Xsend a request by network mail to
X.LW sun!rpc ,
Xor write to:
X.DS
XRPC Administrator
XSun Microsystems
X2550 Garcia Ave.
XMountain View, CA 94043
X.DE
XPlease include a complete protocol specification,
Xsimilar to those in this manual for NFS and YP.
XYou will be given a unique program number in return.
X.NH 2
XPassing Arbitrary Data Types
X.LP
XIn the previous example, the RPC call passes a single
X.LW "unsigned long" .
XRPC can handle arbitrary data structures, regardless of
Xdifferent machines' byte orders or structure layout conventions,
Xby always converting them to a network standard called
X.I "eXternal Data Representation"
X(XDR) before
Xsending them over the wire.
XThe process of converting from a particular machine representation
Xto XDR format is called
X.I serializing ,
Xand the reverse process is called
X.I deserializing .
XThe type field parameters of
X.LW callrpc()
Xand
X.LW registerrpc()
Xcan be a built-in procedure like
X.LW xdr_u_long()
Xin the previous example, or a user supplied one.
XXDR has these built-in type routines:
X.BS
X.LS
Xxdr_int() xdr_u_int() xdr_enum()
Xxdr_long() xdr_u_long() xdr_bool()
Xxdr_short() xdr_u_short() xdr_string()
X.Lf
X.BE
XAs an example of a user-defined type routine,
Xif you wanted to send the structure
X.BS
X.LS
Xstruct simple {
X int a;
X short b;
X} simple;
X.Lf
X.BE
Xthen you would call
X.LW callrpc()
Xas
X.BS
X.LS
Xcallrpc(hostname, PROGNUM, VERSNUM, PROCNUM,
X xdr_simple, &simple ...);
X.Lf
X.BE
Xwhere
X.LW xdr_simple()
Xis written as:
X.BS
X.LS
X#include <rpc/rpc.h>
X.sp.5
Xxdr_simple(xdrsp, simplep)
X XDR *xdrsp;
X struct simple *simplep;
X{
X if (!xdr_int(xdrsp, &simplep->a))
X return (0);
X if (!xdr_short(xdrsp, &simplep->b))
X return (0);
X return (1);
X}
X.Lf
X.BE
X.LP
XAn XDR routine returns nonzero (true in the sense of C)
Xif it completes successfully, and zero otherwise.
XA complete description of XDR is in the
X.I "XDR Protocol Specification" ,
Xso this section only gives a few examples of XDR implementation.
X.LP
XIn addition to the built-in primitives,
Xthere are also the prefabricated building blocks:
X.BS
X.LS
Xxdr_array() xdr_bytes()
Xxdr_reference() xdr_union()
X.Lf
X.BE
XTo send a variable array of integers,
Xyou might package them up as a structure like this
X.BS
X.LS
Xstruct varintarr {
X int *data;
X int arrlnth;
X} arr;
X.Lf
X.BE
Xand make an RPC call such as
X.BS
X.LS
Xcallrpc(hostname, PROGNUM, VERSNUM, PROCNUM,
X xdr_varintarr, &arr...);
X.Lf
X.BE
Xwith
X.LW xdr_varintarr()
Xdefined as:
X.BS
X.LS
Xxdr_varintarr(xdrsp, arrp)
X XDR *xdrsp;
X struct varintarr *arrp;
X{
X xdr_array(xdrsp, &arrp->data, &arrp->arrlnth, MAXLEN,
X sizeof(int), xdr_int);
X}
X.Lf
X.BE
XThis routine takes as parameters the XDR handle,
Xa pointer to the array, a pointer to the size of the array,
Xthe maximum allowable array size,
Xthe size of each array element,
Xand an XDR routine for handling each array element.
X.LP
XIf the size of the array is known in advance, then
Xthe following could also be used to send
Xout an array of length
X.LW SIZE :
X.BS
X.LS
Xint intarr[SIZE];
X.sp.5
Xxdr_intarr(xdrsp, intarr)
X XDR *xdrsp;
X int intarr[];
X{
X int i;
X.sp.5
X for (i = 0; i < SIZE; i++) {
X if (!xdr_int(xdrsp, &intarr[i]))
X return (0);
X }
X return (1);
X}
X.Lf
X.BE
X.LP
XXDR always converts quantities to 4-byte multiples when deserializing.
XThus, if either of the examples above involved characters
Xinstead of integers, each character would occupy 32 bits.
XThat is the reason for the XDR routine
X.LW xdr_bytes() ,
Xwhich is like
X.LW xdr_array()
Xexcept that it packs characters;
X.LW xdr_bytes()
Xhas four parameters, similar to the first four parameters of
X.LW xdr_array() .
XFor null-terminated strings, there is also the
X.LW xdr_string()
Xroutine, which is the same as
X.LW xdr_bytes()
Xwithout the length parameter.
XOn serializing it gets the string length from
X.LW strlen() ,
Xand on deserializing it creates a null-terminated string.
X.LP
XHere is a final example that calls the previously written
X.LW xdr_simple()
Xas well as the built-in functions
X.LW xdr_string()
Xand
X.LW xdr_reference() ,
Xwhich chases pointers:
X.BS
X.LS
Xstruct finalexample {
X char *string;
X struct simple *simplep;
X} finalexample;
X.sp.5
Xxdr_finalexample(xdrsp, finalp)
X XDR *xdrsp;
X struct finalexample *finalp;
X{
X int i;
X.sp.5
X if (!xdr_string(xdrsp, &finalp->string, MAXSTRLEN))
X return (0);
X if (!xdr_reference(xdrsp, &finalp->simplep,
X sizeof(struct simple), xdr_simple);
X return (0);
X return (1);
X}
X.Lf
X.BE
X.bp
X.NH
XLowest Layer of RPC
X.LP
XIn the examples given so far,
XRPC takes care of many details automatically for you.
XIn this section, we'll show you how you can change the defaults
Xby using lower layers of the RPC library.
XIt is assumed that you are familiar with sockets
Xand the system calls for dealing with them.
XIf not, consult the
X.I "IPC Primer" .
X.LP
XThere are several occasions when you may need to use lower layers of RPC.
XFirst, you may need to use TCP.
XThe higher layer uses UDP,
Xwhich restricts RPC calls to 8K bytes of data.
XUsing TCP permits calls to send long streams of data.
XFor an example, see section 5.2 below.
XSecond, you may want to allocate and free memory
Xwhile serializing or deserializing with XDR routines.
XThere is no call at the higher level to let you free memory explicitly.
XFor more explanation, see section 3.2 below.
XThird, you may need to perform authentication
Xon either the client or server side,
Xby supplying credentials or verifying them.
XSee the explanation in section 4.4 below.
X.NH 2
XMore on the Server Side
X.LP
XThe server for the
X.LW nusers
Xprogram shown below does the same thing as the one using
X.LW registerrpc()
Xabove, but is written using a lower layer of the RPC package:
X.BS
X.LS no
X#include <stdio.h>
X#include <rpc/rpc.h>
X#include <rpcsvc/rusers.h>
X.sp.5
Xmain()
X{
X SVCXPRT *transp;
X int nuser();
X.sp.5
X transp = svcudp_create(RPC_ANYSOCK);
X if (transp == NULL){
X fprintf(stderr, "can't create an RPC server\en");
X exit(1);
X }
X pmap_unset(RUSERSPROG, RUSERSVERS);
X if (!svc_register(transp, RUSERSPROG, RUSERSVERS,
X nuser, IPPROTO_UDP)) {
X fprintf(stderr, "can't register RUSER service\en");
X exit(1);
X }
X svc_run(); /* never returns */
X fprintf(stderr, "should never reach this point\en");
X}
X.sp.5
Xnuser(rqstp, tranp)
X struct svc_req *rqstp;
X SVCXPRT *transp;
X{
X unsigned long nusers;
X.sp.5
X switch (rqstp->rq_proc) {
X case NULLPROC:
X if (!svc_sendreply(transp, xdr_void, 0)) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X case RUSERSPROC_NUM:
X /*
X * code here to compute the number of users
X * and put in variable nusers
X */
X if (!svc_sendreply(transp, xdr_u_long, &nusers) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X default:
X svcerr_noproc(transp);
X return;
X }
X}
X.Lf
X.BE
X.LP
XFirst, the server gets a transport handle, which is used
Xfor sending out RPC messages.
X.LW registerrpc()
Xuses
X.LW svcudp_create()
Xto get a UDP handle.
XIf you require a reliable protocol, call
X.LW svctcp_create()
Xinstead.
XIf the argument to
X.LW svcudp_create()
Xis
X.LW RPC_ANYSOCK ,
Xthe RPC library creates a socket
Xon which to send out RPC calls.
XOtherwise,
X.LW svcudp_create()
Xexpects its argument to be a valid socket number.
XIf you specify your own socket, it can be bound or unbound.
XIf it is bound to a port by the user, the port numbers of
X.LW svcudp_create()
Xand
X.LW clntudp_create()
X(the low-level client routine) must match.
X.LP
XWhen the user specifies
X.LW RPC_ANYSOCK
Xfor a socket or gives an unbound socket,
Xthe system determines port numbers in the following way:
Xwhen a server starts up,
Xit advertises to a port mapper demon on its local machine,
Xwhich picks a port number for the RPC procedure
Xif the socket specified to
X.LW svcudp_create()
Xisn't already bound.
XWhen the
X.LW clntudp_create()
Xcall is made with an unbound socket,
Xthe system queries the port mapper on
Xthe machine to which the call is being made,
Xand gets the appropriate port number.
XIf the port mapper is not running
Xor has no port corresponding to the RPC call,
Xthe RPC call fails.
XUsers can make RPC calls
Xto the port mapper themselves.
XThe appropriate procedure
Xnumbers are in the include file
X.LW <rpc/pmap_prot.h> .
X.LP
XAfter creating an
X.LW SVCXPRT ,
Xthe next step is to call
X.LW pmap_unset()
Xso that if the
X.LW nusers
Xserver crashed earlier,
Xany previous trace of it is erased before restarting.
XMore precisely,
X.LW pmap_unset()
Xerases the entry for
X.LW RUSERSPROG
Xfrom the port mapper's tables.
X.LP
XFinally, we associate the program number for
X.LW nusers
Xwith the procedure
X.LW nuser() .
XThe final argument to
X.LW svc_register()
Xis normally the protocol being used,
Xwhich, in this case, is
X.LW IPPROTO_UDP .
XNotice that unlike
X.LW registerrpc() ,
Xthere are no XDR routines involved
Xin the registration process.
XAlso, registration is done on the program,
Xrather than procedure, level.
X.LP
XThe user routine
X.LW nuser()
Xmust call and dispatch the appropriate XDR routines
Xbased on the procedure number.
XNote that
Xtwo things are handled by
X.LW nuser()
Xthat
X.LW registerrpc()
Xhandles automatically.
XThe first is that procedure
X.LW NULLPROC
X(currently zero) returns with no arguments.
XThis can be used as a simple test
Xfor detecting if a remote program is running.
XSecond, there is a check for invalid procedure numbers.
XIf one is detected,
X.LW svcerr_noproc()
Xis called to handle the error.
X.LP
XThe user service routine serializes the results and returns
Xthem to the RPC caller via
X.LW svc_sendreply() .
XIts first parameter is the
X.LW SVCXPRT
Xhandle, the second is the XDR routine,
Xand the third is a pointer to the data to be returned.
XNot illustrated above is how a server
Xhandles an RPC program that passes data.
XAs an example, we can add a procedure
X.LW RUSERSPROC_BOOL ,
Xwhich has an argument
X.LW nusers ,
Xand returns
X.LW TRUE
Xor
X.LW FALSE
Xdepending on whether there are nusers logged on.
XIt would look like this:
X.BS
X.LS
Xcase RUSERSPROC_BOOL: {
X int bool;
X unsigned nuserquery;
X.sp.5
X if (!svc_getargs(transp, xdr_u_int, &nuserquery) {
X svcerr_decode(transp);
X return;
X }
X /*
X * code to set nusers = number of users
X */
X if (nuserquery == nusers)
X bool = TRUE;
X else
X bool = FALSE;
X if (!svc_sendreply(transp, xdr_bool, &bool){
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X}
X.Lf
X.BE
X.LP
XThe relevant routine is
X.LW svc_getargs() ,
Xwhich takes an
X.LW SVCXPRT
Xhandle, the XDR routine,
Xand a pointer to where the input is to be placed as arguments.
X.NH 2
XMemory Allocation with XDR
X.LP
XXDR routines not only do input and output,
Xthey also do memory allocation.
XThis is why the second parameter of
X.LW xdr_array()
Xis a pointer to an array, rather than the array itself.
XIf it is
X.LW NULL ,
Xthen
X.LW xdr_array()
Xallocates space for the array and returns a pointer to it,
Xputting the size of the array in the third argument.
XAs an example, consider the following XDR routine
X.LW xdr_chararr1() ,
Xwhich deals with a fixed array of bytes with length
X.LW SIZE :
X.BS
X.LS
Xxdr_chararr1(xdrsp, chararr)
X XDR *xdrsp;
X char chararr[];
X{
X char *p;
X int len;
X.sp.5
X p = chararr;
X len = SIZE;
X return (xdr_bytes(xdrsp, &p, &len, SIZE));
X}
X.Lf
X.BE
XIt might be called from a server like this,
X.BS
X.LS
Xchar chararr[SIZE];
X.sp.5
Xsvc_getargs(transp, xdr_chararr1, chararr);
X.Lf
X.BE
Xwhere
X.LW chararr
Xhas already allocated space.
XIf you want XDR to do the allocation,
Xyou would have to rewrite this routine in the following way:
X.BS
X.LS
Xxdr_chararr2(xdrsp, chararrp)
X XDR *xdrsp;
X char **chararrp;
X{
X int len;
X.sp.5
X len = SIZE;
X return (xdr_bytes(xdrsp, charrarrp, &len, SIZE));
X}
X.Lf
X.BE
XThen the RPC call might look like this:
X.BS
X.LS
Xchar *arrptr;
X.sp.5
Xarrptr = NULL;
Xsvc_getargs(transp, xdr_chararr2, &arrptr);
X/*
X * use the result here
X */
Xsvc_freeargs(transp, xdr_chararr2, &arrptr);
X.Lf
X.BE
XAfter using the character array, it can be freed with
X.LW svc_freeargs() .
XIn the routine
X.LW xdr_finalexample()
Xgiven earlier, if
X.LW finalp->string
Xwas
X.LW NULL
Xin the call
X.BS
X.LS
Xsvc_getargs(transp, xdr_finalexample, &finalp);
X.Lf
X.BE
Xthen
X.BS
X.LS
Xsvc_freeargs(xdrsp, xdr_finalexample, &finalp);
X.Lf
X.BE
Xfrees the array allocated to hold
X.LW finalp->string ;
Xotherwise, it frees nothing.
XThe same is true for
X.LW finalp->simplep .
X.LP
XTo summarize, each XDR routine is responsible
Xfor serializing, deserializing, and allocating memory.
XWhen an XDR routine is called from
X.LW callrpc() ,
Xthe serializing part is used.
XWhen called from
X.LW svc_getargs() ,
Xthe deserializer is used.
XAnd when called from
X.LW svc_freeargs() ,
Xthe memory deallocator is used.
XWhen building simple examples like those in this section,
Xa user doesn't have to worry about the three modes.
XThe XDR reference manual has examples of more
Xsophisticated XDR routines that
Xdetermine which of the three modes they are in
Xto function correctly.
X.NH 2
XThe Calling Side
X.LP
XWhen you use
X.LW callrpc() ,
Xyou have no control over the RPC delivery
Xmechanism or the socket used to transport the data.
XTo illustrate the layer of RPC that lets you adjust these
Xparameters, consider the following code to call the
X.LW nusers
Xservice:
X.BS
X.LS no
X#include <stdio.h>
X#include <rpc/rpc.h>
X#include <rpcsvc/rusers.h>
X#include <sys/socket.h>
X#include <sys/time.h>
X#include <netdb.h>
X.sp.5
Xmain(argc, argv)
X int argc;
X char **argv;
X{
X struct hostent *hp;
X struct timeval pertry_timeout, total_timeout;
X struct sockaddr_in server_addr;
X int addrlen, sock = RPC_ANYSOCK;
X register CLIENT *client;
X enum clnt_stat clnt_stat;
X unsigned long nusers;
X.sp.5
X if (argc < 2) {
X fprintf(stderr, "usage: nusers hostname\en");
X exit(-1);
X }
X if ((hp = gethostbyname(argv[1])) == NULL) {
X fprintf(stderr, "can't get addr for %s\en",argv[1]);
X exit(-1);
X }
X pertry_timeout.tv_sec = 3;
X pertry_timeout.tv_usec = 0;
X addrlen = sizeof(struct sockaddr_in);
X bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr,
X hp->h_length);
X server_addr.sin_family = AF_INET;
X server_addr.sin_port = 0;
X if ((client = clntudp_create(&server_addr, RUSERSPROG,
X RUSERSVERS, pertry_timeout, &sock)) == NULL) {
X clnt_pcreateerror("clntudp_create");
X exit(-1);
X }
X total_timeout.tv_sec = 20;
X total_timeout.tv_usec = 0;
X clnt_stat = clnt_call(client, RUSERSPROC_NUM, xdr_void,
X 0, xdr_u_long, &nusers, total_timeout);
X if (clnt_stat != RPC_SUCCESS) {
X clnt_perror(client, "rpc");
X exit(-1);
X }
X clnt_destroy(client);
X}
X.Lf
X.BE
XThe low-level version of
X.LW callrpc()
Xis
X.LW clnt_call() ,
Xwhich takes a
X.LW CLIENT
Xpointer rather than a host name. The parameters to
X.LW clnt_call()
Xare a
X.LW CLIENT
Xpointer, the procedure number,
Xthe XDR routine for serializing the argument,
Xa pointer to the argument,
Xthe XDR routine for deserializing the return value,
Xa pointer to where the return value will be placed,
Xand the time in seconds to wait for a reply.
X.LP
XThe
X.LW CLIENT
Xpointer is encoded with the transport mechanism.
X.LW callrpc()
Xuses UDP, thus it calls
X.LW clntudp_create()
Xto get a
X.LW CLIENT
Xpointer. To get TCP (Transport Control Protocol), you would use
X.LW clnttcp_create() .
X.LP
XThe parameters to
X.LW clntudp_create()
Xare the server address, the length of the server address,
Xthe program number, the version number,
Xa timeout value (between tries), and a pointer to a socket.
XThe final argument to
X.LW clnt_call()
Xis the total time to wait for a response.
XThus, the number of tries is the
X.LW clnt_call()
Xtimeout divided by the
X.LW clntudp_create()
Xtimeout.
X.LP
XThere is one thing to note when using the
X.LW clnt_destroy()
Xcall.
XIt deallocates any space associated with the
X.LW CLIENT
Xhandle, but it does not close the socket associated with it,
Xwhich was passed as an argument to
X.LW clntudp_create() .
XThe reason is that if
Xthere are multiple client handles using the same socket,
Xthen it is possible to close one handle
Xwithout destroying the socket that other handles
Xare using.
X.LP
XTo make a stream connection, the call to
X.LW clntudp_create()
Xis replaced with a call to
X.LW clnttcp_create() .
X.BS
X.LS
Xclnttcp_create(&server_addr, prognum, versnum, &socket,
X inputsize, outputsize);
X.Lf
X.BE
XThere is no timeout argument; instead, the receive and send buffer
Xsizes must be specified. When the
X.LW clnttcp_create()
Xcall is made, a TCP connection is established.
XAll RPC calls using that
X.LW CLIENT
Xhandle would use this connection.
XThe server side of an RPC call using TCP has
X.LW svcudp_create()
Xreplaced by
X.LW svctcp_create() .
X.bp
X.NH
XOther RPC Features
X.LP
XThis section discusses some other aspects of RPC
Xthat are occasionally useful.
X.NH 2
XSelect on the Server Side
X.LP
XSuppose a process is processing RPC requests
Xwhile performing some other activity.
XIf the other activity involves periodically updating a data structure,
Xthe process can set an alarm signal before calling
X.LW svc_run() .
XBut if the other activity
Xinvolves waiting on a a file descriptor, the
X.LW svc_run()
Xcall won't work.
XThe code for
X.LW svc_run()
Xis as follows:
X.BS
X.LS
Xvoid
Xsvc_run()
X{
X int readfds;
X.sp.5
X for (;;) {
X readfds = svc_fds;
X switch (select(32, &readfds, NULL, NULL, NULL)) {
X.sp.5
X case -1:
X if (errno == EINTR)
X continue;
X perror("rstat: select");
X return;
X case 0:
X break;
X default:
X svc_getreq(readfds);
X }
X }
X}
X.Lf
X.BE
X.LP
XYou can bypass
X.LW svc_run()
Xand call
X.LW svc_getreq()
Xyourself.
XAll you need to know are the file descriptors
Xof the socket(s) associated with the programs you are waiting on.
XThus you can have your own
X.LW select()
Xthat waits on both the RPC socket,
Xand your own descriptors.
X.NH 2
XBroadcast RPC
X.LP
XThe
X.I portmapper
Xis a daemon that converts RPC program numbers
Xinto DARPA protocol port numbers; see
X.LW portmap (8).
XYou can't do broadcast RPC without the portmapper,
X.LW pmap ,
Xin conjunction with standard RPC protocols.
XHere are the main differences between
Xbroadcast RPC and normal RPC calls:
X.IP 1.
XNormal RPC expects one answer, whereas
Xbroadcast RPC expects many answers
X(one or more answer from each responding machine).
X.IP 2.
XBroadcast RPC can only be supported by packet-oriented (connectionless)
Xtransport protocols like UPD/IP.
X.IP 3.
XThe implementation of broadcast RPC
Xtreats all unsuccessful responses as garbage by filtering them out.
XThus, if there is a version mismatch between the
Xbroadcaster and a remote service,
Xthe user of broadcast RPC never knows.
X.IP 4.
XAll broadcast messages are sent to the portmap port.
XThus, only services that register themselves with their portmapper
Xare accessible via the broadcast RPC mechanism.
X.NH 3
XBroadcast RPC Synopsis
X.LP
X.BS
X.LS
X#include <rpc/pmap_clnt.h>
X.sp.5
Xenum clnt_stat clnt_stat;
X.sp.5
Xclnt_stat =
Xclnt_broadcast(prog, vers, proc, xargs, argsp, xresults,
X resultsp, eachresult)
Xu_long prog; /* program number */
Xu_long vers; /* version number */
Xu_long proc; /* procedure number */
Xxdrproc_t xargs; /* xdr routine for args */
Xcaddr_t argsp; /* pointer to args */
Xxdrproc_t xresults; /* xdr routine for results */
Xcaddr_t resultsp; /* pointer to results */
Xbool_t (*eachresult)(); /* call with each result gotten */
X.Lf
X.BE
XThe procedure
X.LW eachresult()
Xis called each time a valid result is obtained.
XIt returns a boolean that indicates
Xwhether or not the client wants more responses.
X.BS
X.LS
Xbool_t done;
X.sp.5
Xdone =
Xeachresult(resultsp, raddr)
Xcaddr_t resultsp;
Xstruct sockaddr_in *raddr; /* addr of responding machine */
X.Lf
X.BE
XIf
X.LW done
Xis
X.LW TRUE ,
Xthen broadcasting stops and
X.LW clnt_broadcast()
Xreturns successfully.
XOtherwise, the routine waits for another response.
XThe request is rebroadcast
Xafter a few seconds of waiting.
XIf no responses come back,
Xthe routine returns with
X.LW RPC_TIMEDOUT .
XTo interpret
X.LW clnt_stat
Xerrors, feed the error code to
X.LW clnt_perrno() .
X.NH 2
XBatching
X.LP
XThe RPC architecture is designed so that clients send a call message,
Xand wait for servers to reply that the call succeeded.
XThis implies that clients do not compute
Xwhile servers are processing a call.
XThis is inefficient if the client does not want or need
Xan acknowledgement for every message sent.
XIt is possible for clients to continue computing
Xwhile waiting for a response,
Xusing RPC batch facilities.
X.LP
XRPC messages can be placed in a ``pipeline'' of calls
Xto a desired server; this is called batching.
XBatching assumes that:
X1) each RPC call in the pipeline requires no response from the server,
Xand the server does not send a response message; and
X2) the pipeline of calls is transported on a reliable
Xbyte stream transport such as TCP/IP.
XSince the server does not respond to every call,
Xthe client can generate new calls in parallel
Xwith the server executing previous calls.
XFurthermore, the TCP/IP implementation can buffer up
Xmany call messages, and send them to the server in one
X.LW write()
Xsystem call. This overlapped execution
Xgreatly decreases the interprocess communication overhead of
Xthe client and server processes,
Xand the total elapsed time of a series of calls.
X.LP
XSince the batched calls are buffered,
Xthe client should eventually do a legitimate call
Xin order to flush the pipeline.
X.LP
XA contrived example of batching follows.
XAssume a string rendering service (like a window system)
Xhas two similar calls: one renders a string and returns void results,
Xwhile the other renders a string and remains silent.
XThe service (using the TCP/IP transport) may look like:
X.BS
X.LS no
X#include <stdio.h>
X#include <rpc/rpc.h>
X#include <rpcsvc/windows.h>
X.sp.5
Xvoid windowdispatch();
X.sp.5
Xmain()
X{
X SVCXPRT *transp;
X.sp.5
X transp = svctcp_create(RPC_ANYSOCK, 0, 0);
X if (transp == NULL){
X fprintf(stderr, "can't create an RPC server\en");
X exit(1);
X }
X pmap_unset(WINDOWPROG, WINDOWVERS);
X if (!svc_register(transp, WINDOWPROG, WINDOWVERS,
X windowdispatch, IPPROTO_TCP)) {
X fprintf(stderr, "can't register WINDOW service\en");
X exit(1);
X }
X svc_run(); /* never returns */
X fprintf(stderr, "should never reach this point\en");
X}
X.sp.5
Xvoid
Xwindowdispatch(rqstp, transp)
X struct svc_req *rqstp;
X SVCXPRT *transp;
X{
X char *s = NULL;
X.sp.5
X switch (rqstp->rq_proc) {
X case NULLPROC:
X if (!svc_sendreply(transp, xdr_void, 0)) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X case RENDERSTRING:
X if (!svc_getargs(transp, xdr_wrapstring, &s)) {
X fprintf(stderr, "can't decode arguments\en");
X /*
X * tell caller he screwed up
X */
X svcerr_decode(transp);
X break;
X }
X /*
X * call here to render the string s
X */
X if (!svc_sendreply(transp, xdr_void, NULL)) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X break;
X case RENDERSTRING_BATCHED:
X if (!svc_getargs(transp, xdr_wrapstring, &s)) {
X fprintf(stderr, "can't decode arguments\en");
X /*
X * we are silent in the face of protocol errors
X */
X break;
X }
X /*
X * call here to render string s, but send no reply!
X */
X break;
X default:
X svcerr_noproc(transp);
X return;
X }
X /*
X * now free string allocated while decoding arguments
X */
X svc_freeargs(transp, xdr_wrapstring, &s);
X}
X.Lf
X.BE
XOf course the service could have one procedure
Xthat takes the string and a boolean
Xto indicate whether or not the procedure should respond.
X.LP
XIn order for a client to take advantage of batching,
Xthe client must perform RPC calls on a TCP-based transport
Xand the actual calls must have the following attributes:
X1) the result's XDR routine must be zero
X.LW NULL ), (
Xand 2) the RPC call's timeout must be zero.
X.LP
XHere is an example of a client that uses batching
Xto render a bunch of strings;
Xthe batching is flushed when the client gets a null string:
X.BS
X.LS no
X#include <stdio.h>
X#include <rpc/rpc.h>
X#include <rpcsvc/windows.h>
X#include <sys/socket.h>
X#include <sys/time.h>
X#include <netdb.h>
X.sp.5
Xmain(argc, argv)
X int argc;
X char **argv;
X{
X struct hostent *hp;
X struct timeval pertry_timeout, total_timeout;
X struct sockaddr_in server_addr;
X int addrlen, sock = RPC_ANYSOCK;
X register CLIENT *client;
X enum clnt_stat clnt_stat;
X char buf[1000], *s = buf;
X.sp.5
X /* initial as in example 3.3
X */
X if ((client = clnttcp_create(&server_addr,
X WINDOWPROG, WINDOWVERS, &sock, 0, 0)) == NULL) {
X perror("clnttcp_create");
X exit(-1);
X }
X total_timeout.tv_sec = 0;
X total_timeout.tv_usec = 0;
X while (scanf("%s", s) != EOF) {
X clnt_stat = clnt_call(client, RENDERSTRING_BATCHED,
X xdr_wrapstring, &s, NULL, NULL, total_timeout);
X if (clnt_stat != RPC_SUCCESS) {
X clnt_perror(client, "batched rpc");
X exit(-1);
X }
X }
X /* now flush the pipeline
X */
X total_timeout.tv_sec = 20;
X clnt_stat = clnt_call(client, NULLPROC, xdr_void, NULL,
X xdr_void, NULL, total_timeout);
X if (clnt_stat != RPC_SUCCESS) {
X clnt_perror(client, "rpc");
X exit(-1);
X }
X clnt_destroy(client);
X}
X.Lf
X.BE
XSince the server sends no message,
Xthe clients cannot be notified of any of the failures that may occur.
XTherefore, clients are on their own when it comes to handling errors.
X.LP
XThe above example was completed to render
Xall of the (2000) lines in the file
X.I /etc/termcap .
XThe rendering service did nothing but throw the lines away.
XThe example was run in the following four configurations:
X1) machine to itself, regular RPC;
X2) machine to itself, batched RPC;
X3) machine to another, regular RPC; and
X4) machine to another, batched RPC.
XThe results are as follows:
X1) 50 seconds;
X2) 16 seconds;
X3) 52 seconds;
X4) 10 seconds.
XRunning
X.LW fscanf()
Xon
X.I /etc/termcap
Xonly requires six seconds.
XThese timings show the advantage of protocols
Xthat allow for overlapped execution,
Xthough these protocols are often hard to design.
X.NH 2
XAuthentication
X.LP
XIn the examples presented so far,
Xthe caller never identified itself to the server,
Xand the server never required an ID from the caller.
XClearly, some network services, such as a network filesystem,
Xrequire stronger security than what has been presented so far.
X.LP
XIn reality, every RPC call is authenticated by
Xthe RPC package on the server, and similarly,
Xthe RPC client package generates and sends authentication parameters.
XJust as different transports (TCP/IP or UDP/IP)
Xcan be used when creating RPC clients and servers,
Xdifferent forms of authentication can be associated with RPC clients;
Xthe default authentication type used as a default is type
X.I none .
X.LP
XThe authentication subsystem of the RPC package is open ended.
XThat is, numerous types of authentication are easy to support.
XHowever, this section deals only with
X.I unix
Xtype authentication, which besides
X.I none
Xis the only supported type.
X.NH 3
XThe Client Side
X.LP
XWhen a caller creates a new RPC client handle as in:
X.BS
X.LS
Xclnt = clntudp_create(address, prognum, versnum,
X wait, sockp)
X.Lf
X.BE
Xthe appropriate transport instance defaults
Xthe associate authentication handle to be
X.BS
X.LS
Xclnt->cl_auth = authnone_create();
X.Lf
X.BE
XThe RPC client can choose to use
X.I unix
Xstyle authentication by setting
X.LW clnt->cl_auth
Xafter creating the RPC client handle:
X.BS
X.LS
Xclnt->cl_auth = authunix_create_default();
X.Lf
X.BE
XThis causes each RPC call associated with
X.LW clnt
Xto carry with it the following authentication credentials structure:
X.BS
X.LS
X/*
X * Unix style credentials.
X */
Xstruct authunix_parms {
X u_long aup_time; /* credentials creation time */
X char *aup_machname; /* host name where client is */
X int aup_uid; /* client's UNIX effective uid */
X int aup_gid; /* client's current group id */
X u_int aup_len; /* element length of aup_gids */
X int *aup_gids; /* array of groups user is in */
X};
X.Lf
X.BE
XThese fields are set by
X.LW authunix_create_default()
Xby invoking the appropriate system calls.
XSince the RPC user created this new style of authentication,
Xthe user is responsible for destroying it with:
X.BS
X.LS
Xauth_destroy(clnt->cl_auth);
X.Lf
X.BE
XThis should be done in all cases, to conserve memory.
X.NH 3
XThe Server Side
X.LP
XService implementors have a harder time dealing with authentication issues
Xsince the RPC package passes the service dispatch routine a request
Xthat has an arbitrary authentication style associated with it.
XConsider the fields of a request handle passed to a service dispatch routine:
X.BS
X.LS
X/*
X * An RPC Service request
X */
Xstruct svc_req {
X u_long rq_prog; /* service program number */
X u_long rq_vers; /* service protocol vers num */
X u_long rq_proc; /* desired procedure number */
X struct opaque_auth
X rq_cred; /* raw credentials from wire */
X caddr_t rq_clntcred; /* credentials (read only) */
X};
X.Lf
X.BE
XThe
X.LW rq_cred
Xis mostly opaque, except for one field of interest:
Xthe style of authentication credentials:
X.BS
X.LS
X/*
X * Authentication info. Mostly opaque to the programmer.
X */
Xstruct opaque_auth {
X enum_t oa_flavor; /* style of credentials */
X caddr_t oa_base; /* address of more auth stuff */
X u_int oa_length; /* not to exceed MAX_AUTH_BYTES */
X};
X.Lf
X.BE
XThe RPC package guarantees the following
Xto the service dispatch routine:
X.IP 1.
XThat the request's
X.LW rq_cred
Xis well formed. Thus the service implementor may inspect the request's
X.LW rq_cred.oa_flavor
Xto determine which style of authentication the caller used.
XThe service implementor may also wish to inspect the other fields of
X.LW rq_cred
Xif the style is not one of the styles supported by the RPC package.
X.IP 2.
XThat the request's
X.LW rq_clntcred
Xfield is either
X.LW NULL
Xor points to a well formed structure
Xthat corresponds to a supported style of authentication credentials.
XRemember that only
X.I unix
Xstyle is currently supported, so (currently)
X.LW rq_clntcred
Xcould be cast to a pointer to an
X.LW authunix_parms
Xstructure. If
X.LW rq_clntcred
Xis
X.LW NULL ,
Xthe service implementor may wish to inspect the other (opaque) fileds of
X.LW rq_cred
Xin case the service knows about a new type of authentication
Xthat the RPC package does not know about.
X.LP
XOur remote users service example can be extended so that
Xit computes results for all users except UID 16:
X.BS
X.LS no
Xnuser(rqstp, tranp)
X struct svc_req *rqstp;
X SVCXPRT *transp;
X{
X struct authunix_parms *unix_cred;
X int uid;
X unsigned long nusers;
X.sp.5
X /*
X * we don't care about authentication for null proc
X */
X if (rqstp->rq_proc == NULLPROC) {
X if (!svc_sendreply(transp, xdr_void, 0)) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X }
X /*
X * now get the uid
X */
X switch (rqstp->rq_cred.oa_flavor) {
X case AUTH_UNIX:
X unix_cred = (struct authunix_parms *)rqstp->rq_clntcred;
X uid = unix_cred->aup_uid;
X break;
X case AUTH_NULL:
X default:
X svcerr_weakauth(transp);
X return;
X }
X switch (rqstp->rq_proc) {
X case RUSERSPROC_NUM:
X /*
X * make sure caller is allowed to call this proc
X */
X if (uid == 16) {
X svcerr_systemerr(transp);
X return;
X }
X /*
X * code here to compute the number of users
X * and put in variable nusers
X */
X if (!svc_sendreply(transp, xdr_u_long, &nusers) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X default:
X svcerr_noproc(transp);
X return;
X }
X}
X.Lf
X.BE
XA few things should be noted here.
XFirst, it is customary not to check
Xthe authentication parameters associated with the
X.LW NULLPROC
X(procedure number zero).
XSecond, if the authentication parameter's type is not suitable
Xfor your service, you should call
X.LW svcerr_weakauth() .
XAnd finally, the service protocol itself should return status
Xfor access denied; in the case of our example, the protocol
Xdoes not have such a status, so we call the service primitive
X.LW svcerr_systemerr()
Xinstead.
X.LP
XThe last point underscores the relation between
Xthe RPC authentication package and the services;
XRPC deals only with authentication and not with
Xindividual services' access control.
XThe services themselves must implement their own access control policies
Xand reflect these policies as return statuses in their protocols.
X.NH 2
XUsing Inetd
X.LP
XAn RPC server can be started from
X.LW inetd .
XThe only difference
Xfrom the usual code is that
X.LW svcudp_create()
Xshould be called as
X.BS
X.LS
Xtransp = svcudp_create(0);
X.Lf
X.BE
Xsince
X.LW inet
Xpasses a socket as file descriptor 0.
XAlso,
X.LW svc_register()
Xshould be called as
X.BS
X.LS
Xsvc_register(transp, PROGNUM, VERSNUM, service, 0);
X.Lf
X.BE
Xwith the final flag as 0,
Xsince the program would already be registered by
X.LW inetd .
XRemember that if you want to exit
Xfrom the server process and return control to
X.LW inet ,
Xyou need to explicitly exit, since
X.LW svc_run()
Xnever returns.
X.LP
XThe format of entries in /etc/servers for RPC services is
X.BS
X.LS
Xrpc udp \fIserver \0program \0version\fP
X.Lf
X.BE
Xwhere
X.I server
Xis the C code implementing the server,
Xand
X.I program
Xand
X.I version
Xare the program and version numbers of the service.
XThe key word
X.LW udp
Xcan be replaced by
X.LW tcp
Xfor TCP-based RPC services.
X.LP
XIf the same program handles multiple versions,
Xthen the version number can be a range,
Xas in this example:
X.BS
X.LS
Xrpc udp /usr/etc/rstatd 100001 1-2
X.Lf
X.BE
X.bp
X.NH
XMore Examples
X.NH 2
XVersions
X.LP
XBy convention, the first version number of program
X.LW PROG
Xis
X.LW PROGVERS_ORIG
Xand the most recent version is
X.LW PROGVERS .
XSuppose there is a new version of the
X.LW user
Xprogram that returns an
X.LW "unsigned short"
Xrather than a
X.LW long .
XIf we name this version
X.LW RUSERSVERS_SHORT ,
Xthen a server that wants to support both versions
Xwould do a double register.
X.BS
X.LS
Xif (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG,
X nuser, IPPROTO_TCP)) {
X fprintf(stderr, "can't register RUSER service\en");
X exit(1);
X}
Xif (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT,
X nuser, IPPROTO_TCP)) {
X fprintf(stderr, "can't register RUSER service\en");
X exit(1);
X}
X.Lf
X.BE
XBoth versions can be handled by the same C procedure:
X.BS
X.LS no
Xnuser(rqstp, tranp)
X struct svc_req *rqstp;
X SVCXPRT *transp;
X{
X unsigned long nusers;
X unsigned short nusers2
X.sp.5
X switch (rqstp->rq_proc) {
X case NULLPROC:
X if (!svc_sendreply(transp, xdr_void, 0)) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X case RUSERSPROC_NUM:
X /*
X * code here to compute the number of users
X * and put in variable nusers
X */
X nusers2 = nusers;
X if (rqstp->rq_vers != RUSERSVERS_ORIG)
X return;
X if (!svc_sendreply(transp, xdr_u_long, &nusers) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X } else
X if (!svc_sendreply(transp, xdr_u_short, &nusers2) {
X fprintf(stderr, "can't reply to RPC call\en");
X exit(1);
X }
X return;
X default:
X svcerr_noproc(transp);
X return;
X }
X}
X.Lf
X.BE
X.NH 2
XTCP
X.LP
XHere is an example that is essentially
X.LW rcp .
XThe initiator of the RPC
X.LW snd()
Xcall takes its standard input and sends it to the server
X.LW rcv() ,
Xwhich prints it on standard output.
XThe RPC call uses TCP.
XThis also illustrates an XDR procedure that behaves differently
Xon serialization than on deserialization.
X.BS
X.LS no
X/*
X * The xdr routine:
X * on decode, read from wire, write onto fp
X * on encode, read from fp, write onto wire
X */
X#include <stdio.h>
X#include <rpc/rpc.h>
X.sp.5
Xxdr_rcp(xdrs, fp)
X XDR *xdrs;
X FILE *fp;
X{
X unsigned long size;
X char buf[BUFSIZ], *p;
X.sp.5
X if (xdrs->x_op == XDR_FREE)/* nothing to free */
X return 1;
X while (1) {
X if (xdrs->x_op == XDR_ENCODE) {
X if ((size = fread(buf, sizeof(char), BUFSIZ,
X fp)) == 0 && ferror(fp)) {
X fprintf(stderr, "can't fread\en");
X exit(1);
X }
X }
X p = buf;
X if (!xdr_bytes(xdrs, &p, &size, BUFSIZ))
X return 0;
X if (size == 0)
X return 1;
X if (xdrs->x_op == XDR_DECODE) {
X if (fwrite(buf, sizeof(char), size,
X fp) != size) {
X fprintf(stderr, "can't fwrite\en");
X exit(1);
X }
X }
X }
X}
X.sp.5
X/*
X * The sender routines
X */
X#include <stdio.h>
X#include <netdb.h>
X#include <rpc/rpc.h>
X#include <sys/socket.h>
X#include <sys/time.h>
X.sp.5
Xmain(argc, argv)
X int argc;
X char **argv;
X{
X int err;
X.sp.5
X if (argc < 2) {
X fprintf(stderr, "usage: %s servername\en", argv[0]);
X exit(-1);
X }
X if ((err = callrpctcp(argv[1], RCPPROG, RCPPROC_FP,
X RCPVERS, xdr_rcp, stdin, xdr_void, 0) != 0)) {
X clnt_perrno(err);
X fprintf(stderr, "can't make RPC call\en");
X exit(1);
X }
X}
X.sp.5
Xcallrpctcp(host, prognum, procnum, versnum,
X inproc, in, outproc, out)
X char *host, *in, *out;
X xdrproc_t inproc, outproc;
X{
X struct sockaddr_in server_addr;
X int socket = RPC_ANYSOCK;
X enum clnt_stat clnt_stat;
X struct hostent *hp;
X register CLIENT *client;
X struct timeval total_timeout;
X.sp.5
X if ((hp = gethostbyname(host)) == NULL) {
X fprintf(stderr, "can't get addr for '%s'\en", host);
X exit(-1);
X }
X bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr,
X hp->h_length);
X server_addr.sin_family = AF_INET;
X server_addr.sin_port = 0;
X if ((client = clnttcp_create(&server_addr, prognum,
X versnum, &socket, BUFSIZ, BUFSIZ)) == NULL) {
X perror("rpctcp_create");
X exit(-1);
X }
X total_timeout.tv_sec = 20;
X total_timeout.tv_usec = 0;
X clnt_stat = clnt_call(client, procnum,
X inproc, in, outproc, out, total_timeout);
X clnt_destroy(client)
X return (int)clnt_stat;
X}
X.sp.5
X/*
X * The receiving routines
X */
X#include <stdio.h>
X#include <rpc/rpc.h>
X.sp.5
Xmain()
X{
X register SVCXPRT *transp;
X.sp.5
X if ((transp = svctcp_create(RPC_ANYSOCK,
X BUFSIZ, BUFSIZ)) == NULL) {
X fprintf("svctcp_create: error\en");
X exit(1);
X }
X pmap_unset(RCPPROG, RCPVERS);
X if (!svc_register(transp,
X RCPPROG, RCPVERS, rcp_service, IPPROTO_TCP)) {
X fprintf(stderr, "svc_register: error\en");
X exit(1);
X }
X svc_run(); /* never returns */
X fprintf(stderr, "svc_run should never return\en");
X}
X.sp.5
Xrcp_service(rqstp, transp)
X register struct svc_req *rqstp;
X register SVCXPRT *transp;
X{
X switch (rqstp->rq_proc) {
X case NULLPROC:
X if (svc_sendreply(transp, xdr_void, 0) == 0) {
X fprintf(stderr, "err: rcp_service");
X exit(1);
X }
X return;
X case RCPPROC_FP:
X if (!svc_getargs(transp, xdr_rcp, stdout)) {
X svcerr_decode(transp);
X return;
X }
X if (!svc_sendreply(transp, xdr_void, 0)) {
X fprintf(stderr, "can't reply\en");
X return;
X }
X exit(0);
X default:
X svcerr_noproc(transp);
X return;
X }
X}
X.Lf
X.BE
X.NH 2
XCallback Procedures
X.LP
XOccasionally, it is useful to have a server become a client,
Xand make an RPC call back the process which is its client.
XAn example is remote debugging,
Xwhere the client is a window system program,
Xand the server is a debugger running on the remote machine.
XMost of the time,
Xthe user clicks a mouse button at the debugging window,
Xwhich converts this to a debugger command,
Xand then makes an RPC call to the server
X(where the debugger is actually running),
Xtelling it to execute that command.
XHowever, when the debugger hits a breakpoint, the roles are reversed,
Xand the debugger wants to make an rpc call to the window program,
Xso that it can inform the user that a breakpoint has been reached.
SHAR_EOF
if test 49365 -ne "`wc -c < 'rpc/doc/rpc.prog.p1'`"
then
echo shar: "error transmitting 'rpc/doc/rpc.prog.p1'" '(should have been 49365 characters)'
fi
chmod 444 'rpc/doc/rpc.prog.p1'
fi
exit 0
# End of shell archive
More information about the Mod.sources
mailing list