R P C - Zawołania Zdalnych Procedur

Autor: Zbigniew Lisiecki
wersja 1.4, 21 Styczeń 2004


The following text was created after a two weeks course in Winter 1993 in the ISP-DATA company in Munich/Germany. For the original text see german version. The exercises has been realized with SUN's ONC on SPARC-Station2 with SunOS 4.1/ Solaris.

Programming exapmles has been developed with a support of SUN's Programming Guide Rev. A 27.03.1990. To keep the examples more clear I omitted the ussual error handling, which should be added while trying to run them.

I kindly ask the reader to excuse my sometimes poor english. The autor will be thankful for every feedback about errors or any proposals on better presentation of the material.

The COPYRIGHT owns the autor Zbigniew Lisiecki. It is allowed to make copies without changing.

                                 - 1 -




                                   Contents



          Kap.     Title                                           Page

          1.       Application distribution in a network             4
          1.1        The RPC-Mechanismus                             4
          1.2        The history of RPC Versions                     7
          1.3        Stateless and statefull servers                 8

          2.       XDR - eXternal Data Representation                9
          2.1        The priciple of XDR                             9
          2.2        XDR-Streams                                    11
          2.2.1        Standard IO streams                          13
          2.2.2        Memory streams                               15
          2.2.3        Record streams                               15
          2.2.4        XDR macros for stream manipulation           18
          2.3        XDR filter                                     19
          2.3.1        Primitiv filters                             19
          2.3.2        Composite filters                            22
          2.3.3        Custom filters                               27
          2.3.4        Filter examples                              29
          2.4        The XDR protocol specification                 32
          2.4.1        XDR data types                               32
          2.4.2        XDR syntax                                   39

          3        The Portmapper Network Service                   41
          3.1        The functional principle                       41
          3.2        Program numbers                                44
          3.3        The Port mapper protocol                       46

          4.       The RPC programming I                            48
          4.1        High-level RPC                                 48
          4.2        Middle-level RPC                               49
          4.3        Low-level RPC programming                      51

          5.       The RPC programming II advanced methods          56
          5.1        Debugging with raw RPC                         56
          5.2        How a server dispatcher is built               57
          5.3        A server with different versions               58
          5.4        An example of a TCP application                60
          5.5        Technics of asynchron communication            62
          5.5.1        Broadcasting                                 62
          5.5.2        Batching                                     66
          5.5.3        Callback procedures                          68
          5.6        Security mechanisms                            71
          5.6.1        UNIX specific identification                 73
          5.6.2        Diffie-Hellman/DES authentication            75
          5.7        inetd daemon usage                             77
          5.8        The RPC error library                          78

          6        The RPC protocol                                 80
          6.1        RPC messages                                   80
          6.2        The protocol of security mechanisms            83
          6.2.1        security level one and two                   83
          6.2.2        security level three                         84

          7.       The RPC generator: rpcgen                        87
          7.1        Generating C programs                          87
          7.2        Advanced example                               90
          7.3        rpcgen options                                 91
          7.4        Dispatcher tables                              92
          7.5        The language definition                        93

          8.3      Exercises                                        98
          8.4      Solutions                                       100

          9.       Appendix                                        102
          9.1        Backus-Naur notation                          102
          9.2        Bibliography                                  103
          9.3        Dictionary                                    105



                                 - 3 -


1.  Application distribution in a network


1.1.  The RPC mechanism


     RPC (Remote Procedure Call) is a programming technic, which allows to
send data from one machine to another machine, where a procedure will be
called with this data as parameters. Result data will be send back to a
calling program on a local machine. RPC uses as a paradigma a client-server
model: local calling client machine uses services on a remote machine.




 ......................                    .................................
 .                    .                    .                               .
 .    computer A      .                    .        computer  B            .
 .                    .                    .                               .
 .  client programm   .                    .       server daemon           .
 .                    .                    .                               .
 . activ | RPC call   .                    .                               .
 .       V            .                    .                               .
 .        ----------------------------------->   initialising              .
 .       .            .  data transport    .   | of a service              .
 .       .            .                    .   |                           .
 .       .            .                    .   | call of service routine   .
 .                    .                    .   V                           .
 .     program        .                    .   ---------->                 .
 .    is waiting      .                    .              | service        .
 .                    .                    .              | routine        .
 .       .            .                    .      answere V                .
 .       .            .                    .    <----------                .
 .                    .                    .   |                           .
 .   return from a    .                    .   |  service termination      .
 .   RPC call         .                    .   V                           .
 .        <------------------------------------                            .
 .       |            .  data transport    .                               .
 .       | programm   .                    .                               .
 .       | contiuation.                    .                               .
 .       V            .                    .                               .
 ......................                    .................................


        Figure 1


     The objects of a service are programs and CPU ressources. Calling and
result data must be transported between computers. Inside a process image
in computer A they have a specific representation (e.g inteeger big endian,
or IEEE float. To make them available to a process in a computer B they must




                                 - 4 -

be transformed to a representation which is specific to computer B (e.g.
little endian). This will be done by first transforming them into a "network
data format", send them and then transforming them back to representation
in a remote computer after arrival there. Applying this method we obviously
need 2*n converting routines compared with n*(n-1)/2 when only one conversion
between computer A and B take place. n is the number of supported data formats.
A "network data format" is called eXternal Data Representation (XDR).


                                ---------------
              format A  ---->   | XDR-Filter  |
                                ---------------
                                       |
                                       |
                                 data transport
                                       |
                                       |
                                       V
                                ---------------
                                | XDR-Filter  | ---->   Format b
                                ---------------
            Figure 2



     RPC offers services, which make features of a network connection
transparent (unseen). This way applications can be developed, which distribute
their programs or data in a network (distributed applications). A program can
for example call a routine rdate(), which gives a date on a remote computer,
without bothering about any network communication on this level.

     The RPC programming interface consists of function calls, hiding all
network communication and format conversions. rdate() can as an example
be built of these functions. Many well known services, like NIS, REX, NFS,
NLM are as a fact constructed with RPC. RPC itself calls network protocols
like UDP, TCP, SPX/IPX.


     According to a OSI model RPC/XDR lies between a transport layer and
an application layer.



                                 - 5 -

--------------------------   ISO/OSI terminology      ------------------------
| client                 |                            |              server  |
| process  ------------- |                            | ------------ process |
|          | client    | |                            | | server   |         |
|          | routine   | |       Application Layer    | | routine  |         |
|          ------------- |                            | ------------         |
| local       |    ^     |                            |      |    ^          |
| procedur    V    |     |                            |      V    |          |
| call   --------------- |                            | ---------------      |
|        | client stub | |   RPC                      | | server stub |      |
|        | XDR filter  | |   XDR: Presentation Layer  | | XDR filter  |      |
|        --------------- |                            | ---------------      |
|             |    ^     |       Session Layer        |      |    ^          |
------------- | -- | -----                            ------ | -- | ----------
              |    |                                         |    |
  system call |    |                                         |    |
              |    |        Transport Layer e.g. TCP         |    |
------------- | -- | -----                            ------ | -- | ----------
| local       V    |     |                            |      V    |          |
| kernel --------------- |                            |                      |
|        | network     | |     Network Layer e.g. IP  |    remote            |
|        |   routines  | |                            |     kernel           |
|        --------------- |                            |                      |
------------- | -- ^ -----                            ------ | -- ^ ----------
              |    |                                         |    |
              |    | interrupt                               |    |
              V    |                                         V    |
------------------------------                        ------------------------
| network card e.g. ethernet |   Datalink Layer       |                      |
------------------------------                        ------------------------
              V    |                                         |    |
              |    -------<-----------------------------------    |
              |                  Physical Layer                   |
              ---------------------------------------->------------

    Figure 3



     As far as wee regard a UNIX environment, RPC can be used to communication
between two processes in a one machine. Nevertheless the RPC mechanism was
implemented in other operating systems: System/370 VM, MSV, VMS, MS-DOS.


                                 - 6 -


1.2.  A history of RPC versions


     RPC was developed in 70-ties. First versions appeared at Xerox. One of
them, a "Courier" was introduced as a product 1981. The SUN's version, in
ONC (Open Network Computing) came into beeing 1985 and served for the same
developer to build NFS, which soon became a de facto industry standard. To
spread it's RPC/XDR interface SUN distributes it's version freely with a
source code. As a library RPC/XDR is supported on most UNIX systems.


Simmilar functionalities are implemented in:

  1. Network Computing System (NCS), developed at Apollo/Hewlett Packard with the network wide data format, Network Data Representa- tion (NDR),

  2. RPCTOOL of Netwise Company

  3. Distributet Computing Environment (DCE) from OSF/1.



This text references the SUN version.




                                 - 7 -



1.3   Stateless and statefull servers


     The RPC interface can be used to set in a client an application layer
protocol, which corresponds with program's internal logic. E.g. the protocol
can state that the client searches data in the server with his first call
and transmit them with the second one. The server's answere will then depend
on the clients previous calls e.g. if they took place. Server's state is
in this case in reference to his answere surely not the same as after the
first call. If the server breaks down between two client's calls it must
be put in the apropriate state after booting. The client should not be
touched by server's breaks down. Such servers we call statefull servers. As
the oposite stateless servers answere on same requests always in the same way:
servers state is not maintained.


As an example we look at SUN's NFS implementation. It supports stateless
servers: Every file open() on a remote filesystem is kept in the client
until it is passed with write(), or read() in one transaction. Sever's
break down between open() and read() poses no problems. States are local.
Problematic states don't propagate in the network.


These expressions are not only bound to RPC, but to a general client/server
model. They allow adequate description of problems in client/server
environment.







                                 - 8 -


2.  XDR - eXternal Data Representation



2.1.  The principle of XDR


     With transmitting of procedure parameters, or call results with RPC
their number's information content and a possible complex data structure
must be preserved. We need to consider following points:


  1. Different CPUs use different number formats like big-, little-endian, 1's, or 2's complement, IEEE-floats, or BCD (binary coded decimal).

  2. The same type can have in different computer systems diffentent number of bytes: e.g. 16, or 32 bit integer.

  3. Different languages may have different representation of data types, like strings with a null at the end and strings with a string length in ther first byte.

  4. There are certain alignmets of data characteristic for different processors. They lead to address shifts, or holes in struct frameworks.

  5. Data transmitted between computers can build in the memory complex structures with pointers. During transmittion a topological reference to the memory addresses will be lost. But data must be serialized to a byte stream and deserialized back.



     The SUN's portable data representation eXternal Data Representation (XDR)
will help these problems.













                                 - 9 -

                   serialising                deserialising
                       |                           |
                       V                           V

-------------      ----------                  ----------      -------------
|           | ---> |  XDR - |                  |  XDR - | ---> |           |
|           |      | Filter1| ---------------> | Filter1|      |           |
|           |      ----------                  ----------      |           |
| App1 e.g. |                                                  | App2 e.g. |
|           |                  data transfer                   |           |
|  Client   |                                                  |  Server   |
|           |      ----------                  ----------      |           |
|           |      |  XDR - | <--------------- |  XDR - |      |           |
|           | <--- | Filter2|                  | Filter2| <--- |           |
-------------      ----------                  ----------      -------------

                       ^                           ^
                       |                           |
                 deserialising                serialising

 Figure 4


     The XDR Library supplies functions, which solve these Problems
automaticaly. They are divided in two groups:


  1. Functions, which generate and manipulate data streams (see 2.1)

  2. Conversion filters, which convert data and put them on data streams



     XDR functions are applied in C programs, but not neccessary under UNIX.
Especially UNIX I/O system is not presumed. As an example XDR under MS-DOS
allow data exchange between UNIX and DOS preserving numerical data values.
XDR plays a role not only for RPC. Some calculation results can be for
example stored in a machine independant way and lated used on another
computer.


     The local syntactic data description will be here C language. With XDR
conversion we obtain a XDR form, described by XDR language with it's so
called tranfere syntax, or it's abstract encoding, the so called abstract
syntax. As an example let us take an abstract syntax type "boolean". It's
representation in the transfere syntax can be values 0, or 1 and it's
representation in one computer values 'Y', and 'N', or in the second computer
values 'J' and 'N'. A pair (abstract syntax, transfere syntax) is sometimes
called "Presentation Context", in our example (bool, {0,1}),





                                 - 10 -



2.2.  XDR-Streams


     The operation of encoding and decoding of data (serialising/
     deserialising) uses data streams realised as:

  1. standard i/o, that is with help of FILEs from ,

  2. direct with a memory buffers (memory streams),

  3. or with a general mechanism, which details must be specified later (record streams)



     Beside of those given streams it is possible to define own custom streams
by writting own functions additional to XDR library. All statements about
an applied stream are kept in a struct XDR. It is defined in :



        enum xdr_op {
            XDR_ENCODE = 0,
            XDR_DECODE = 1,
            XDR_FREE   = 2
        };

        typedef struct {
            enum xdr_op  x_op;
            struct xdr_ops {
                bool_t   (*x_getlong)();
                bool_t   (*x_putlong)();
                bool_t   (*x_getbytes)();
                bool_t   (*x_putbytes)();
                u_int    (*x_getpostn)();
                bool_t   (*x_setpostn)();
                caddr_t  (*x_inline)();
                void     (*x_destrpy)();
            }              *x_ops;
            caddr_t        x_public;
            caddr_t        x_private;
            caddr_t        x_base;
            int            x_handy;
        } XDR;

This struct is the only data interface to a stream. The ponter to this struct will be given as a parameter to almost all functions from the XDR library. Procedures, which task can be formulated independant on applied stream need not to bother about stream specific issues. The components of XDR struct have the following significance, which is however often unimportant to an application programmer, if he don't want to define his own new streams: - 11 - x_op the current operation applied to a stream: encoding, or decoding. It is specified here, because applied filters are often independant on it. x_getlong writting, or reading of one long int from a stream, or to a x_putlong stream. These functions make the actual conversion (e.g from little to big endian). x_getbytes writting, or reading of byte sequences to, or from a stream x_putbytes They return TRUE or FALSE values back. x_getpostn macros for access operations x_setpostn x_destroy x_inline takes as a parameter (XDR *) and the number of bytes, returns back the address in a stream internal buffer. x_public XDR users data; they should not be used in a stream implementation. x_private data privat relativ to a stream implementation x_base privat too; it will be used for information about position in a stream. x_handy privat too; auxilary ---------------------------------- | | | --------- conversion with | ---------- | | user | XDR filter | | output | | | data | -----------> | UDP-packets --------->| data | | | | | / | | | | | X X X X / ---------- | --------- ----------------------- | / | -----------> back conversion | / | XDR stream with the same | user process / ------------------------ XDR filter | space / / | / / | | / / | | / / | | XDR- / | | handle | | | | | ---------------------------------- Figure 5 - 12 - Valid operations, described with a XDR struct are:


	enum xdr_op {
		XDR_ENCODE,    /* Serialisierung             */
		XDR_DECODE,    /* Deserialisierung           */
		XDR_FREE       /* befreit den Speicherplatz  */
	};

- 13 - 2.2.1. Standard IO-Streams Standard IO-Streams use for data transport IO mechanism, like it is defined in . Data will be converted (that means serialised) from their local format to the XDR format and will be written to a FILE. Back conversion means reading from a FILE. A standard IO stream will be created with the following call:


	void    xdrstdio_create (handle, file, op)
	XDR         *handle;
	FILE        *file;
	enum xdr_op op;

Before this call a memory area "handle" must already exist in user space as seen in figure 5. Simmilar a parameter "file" must point to an opened file. With xdrstdio_create() call all these components will be bound together and put in a struct "handle" for later use. The parameter "op" can be set to XDR_ENCODE, XDR_DECODE or XDR_FREE according to it's declaration. With it the choice will be made for which operation the stream described in "handle" will be used. "op" can be switched later too. In the case of XDR_FREE the IO stream buffer wil be flushed with fflush(), but the file will not be closed. fclose() must be explicitely called at the end. With it we have the opportunity to use one file for many subsequent IO streams. With standard IO streams one can store data in XDR format in a file and use them later for following processing with another program. - 14 - 2.2.2. Memory streams In opposite to standard IO streams user data stored with memory streams stay in user process space. They can be converted back by the same process, or read from a shared memory by another process.


	void    xdrmem_create (handle, addr, len, op)
	XDR          *handle;
	char         *addr;
	u_int        len;
	enum xdr_op  op;

The call parameters are analogous to xdrstdio_create(). Parameter "addr" is a pointer to a storage area, len it\s size in bytes. 2.2.3. Record Streams The third kind of XDR stream is a general one. Standard IO streams and memory streams are it's special cases.


	void xdrrec_create (handle, sendsize, recvsize,
						iohandle, readproc, writeproc)
	XDR     *handle;
	u_int   sendsize, recvsize;
	char    *iohandle;
	int     (*readproc)(), (*writeproc)();

Like in standard IO stream record stream maintains it's own buffers, which sizes are "sendsize" of output buffer and "recvsize" for input. In case of zero the system creates default values. Two parameters readproc, and writeproc are pointers to functions, which perform the actual IO operations. As an example these can be put to system calls read(2) and write(2) from clib. They can be nevertheles written by a user himself. After creation of a stream they will be called by XDR system with following parameters:


	int     readproc/writeproc (iohandle, buf, len)
	char    *iohandle,
	        *buf;
	int     len;

- 15 - The parameter iohandle will not be used in xdrrec_create(). It will be passed to readproc or writeproc and can be used there. In the case of our example with read(2) and write(2) iohandle must be set to a file descriptor. Also like read(2) and write(2) readproc and writeproc return the number of converted bytes. -------------------------------------- | | | ------------- conversion with | | | user data | XDR filter | | | | -----------> | e.g. UDP packets | | | | | | | X X X X | ------------- -------------------------------- | / | writeproc() --> | / | | user prozess / -------------------------------- | space / / <-- readproc() | / / | | / / | | / / | transport medium | XDR- / | with a XDR stream | handle | | | | | -------------------------------------- Figure 6 The transport mechanism will start a XDR filter for every data record. It's direction and all other significant variables are controlled by XDR handle. As an opposite to xdrstdio_create() and xdrmem_create() with xdrrec_create () the field handle->x_op is not set. It must be switched to:


	handle->x_op = XDR_ENCODE oder XDR_DECODE;

explicit by the user. Records will be written sequentialy on the stream with the lenght of each fragment put at the beginning: - 16 - ------------------------- | 0 | length 1 | data 1 | fragment 1 | 0 | length 2 | data 2 | fragment 2 | 0 | . | . | . ein Record | 0 | . | . | . | 0 | . | . | . | 0 | . | . | . | 0 | . | . | . | 1 | . | . | . ------------------------- ^ ^ | | | 31 Bits for the lenght of data fragment | 1 Bit: 1 for the last fragment, else 0 Figure 7 Three additional functions make it possible to explicit divide the stream into records:


	bool_t  xdrrec_endofrecord (handle, flushnow)
	XDR     *handle;
	bool_t  flushnow;

	bool_t  xdrrec_skiprecord (handle)
	XDR     *handle;

	bool_t  xdrrec_eof (handle)
	XDR     *handle;

xdrrec_endofrecord() set the end of a record with marking current fragment to the last one. With flushnow == TRUE writeproc() will be called and with flushnow == FALSE the system will wait until the buffer is full. xdrrec_skiprecord() will be used to skip the current record while reading the stream. The function xdrrec_eof() returns TRUE if there is no more data in the stream to be read. - 17 - 2.2.4. XDR Macros for stream manipulation In there are some predefined macros for all three stream types:


	u_int   xdr_getpos (handle)
	XDR     *handle;

	bool_t  xdr_setpos (handle, pos)
	XDR     *handle;
	u_int   pos;

	void    xdr_destroy (handle)
	XDR     *handle;

xdr_getpos() and xdr_setpos() return the position in the stream, or set it. With the return value of xdr_getpos() one can get the number of bytes put til now on the stream. The third function xdr_destroy() must be called by the user to explicitly close the stream created with xdr..._create(). Allocated space will be also freed. - 18 - 2.3. XDR Filter In the previous chapter we described functions and macros to initialise and manipulate the mechanism of XDR conversion. This mechanism can be set in motion with filter functions. All filters will be used to decode, or encode for handle->x_op = XDR_ENCODE or XDR_DECODE respectively. This feature make it possible to use the same function to encode and to decode of every object. 2.3.1. Primitiv Filters In the XDR library there exist following so called primitiv filter functions:


	bool_t  xdr_char (handle, cp)
	XDR     *handle;
	char    *cp;

	bool_t  xdr_u_char (handle, ucp)
	XDR             *handle;
	unsigned char   *ucp;

	bool_t  xdr_int (handle, ip)
	XDR     *handle;
	int     *ip;

	bool_t  xdr_u_int (handle, uip)
	XDR        *handle;
	unsigned   *uip;

	bool_t  xdr_long (handle, lp)
	XDR     *handle;
	long    *lp;

	bool_t  xdr_u_long (handle, ulp)
	XDR     *handle;
	u_long  *ulp;

	bool_t  xdr_short (handle, sp)
	XDR     *handle;
	short   *sp;

	bool_t  xdr_u_short (handle, usp)
	XDR     *handle;
	u_short *usp;

	bool_t  xdr_float (handle, fp)
	XDR     *handle;
	float   *fp;

	bool_t  xdr_double (handle, dp)
	XDR     *handle;
	double  *dp;

	bool_t  xdr_enum (handle, ep)
	XDR     *handle;
	enum_t  *ep;

	bool_t  xdr_bool (handle, bp)
	XDR     *handle;
	bool_t  *bp;

First parameter is a pointer to the existing stream. The second is the address where the data for the conversion are put or from where they are get. This kind of synopsis supports the usage of filters in both directions: encode and decode. The return value says if the conversion succeded. The primitiv filter xdr_enum presumes that enum is realised in the sender and receiver with int. But this is standard C since Kernighan & Ritchi.


	#define enum_t  int;

xdr_bool is a special case of xdr_enum for:


	enum bool_t     { TRUE = 1, FALSE = 0 };

Additional there exists in XDR library the function:


	bool_t  xdr_void ();

which will be used when no data should be converted. It is neccessary for example for discriminated unions or linked lists. Beside the described functions XDR library supplies in-line macros, which spare the function call but make no error handling possible.


	long    IXDR_GET_LONG (buf)
	long    *buf;

	bool_t  IXDR_GET_BOOL (buf)
	long    *buf;

	type  IXDR_GET_ENUM (buf, type)
	long    *buf;
	type  type;

	u_long  IXDR_GET_U_LONG (buf)
	long    *buf;

	short   IXDR_GET_SHORT (buf)
	long    *buf;

	u_short IXDR_GET_U_SHORT (buf)
	long    *buf;

Respectively there are ..._PUT_... macros. To use both (GET or PUT) one must first get the buffer address with:


	long    *xdr_inline (handle, len)
	XDR     *handle;
	int     len;
An example of macros usage with standard IO streams follows:


	#include        
	#define N       10

     ...

	FILE    *fp, *fopen();
	XDR     handle;
	long    *buf;
	int     i, count;
	short   array[N];

	fp = fopen (filename, "w");
	xdrstdio_create (&handle, fp, XDR_ENCODE);
	count = BYTES_PER_XDR_UNIT * N;
	buf = inline (&handle, count);
	for (i = 0; i < N; i++)
		array[i] = IXDR_GET_SHORT (buf);
	xdr_destroy (&handle);
	fclose (fp);
- 21 - 2.3.2. Composite filters In addition to primitive filters XDR library supplies composite filters. They allow the conversion of complex data structures. There are filters for: 1. strings, 2. byte-arrays, 3. opaque data, 4. arrays, 5. fixed size arrays 6. discriminated unions 7. pointers Like primitiv filters they can also be used in custom filters.


	bool_t  xdr_string (handle, sp, maxlen)
	XDR     *handle;
	char    **sp;
	u_int   maxlen;

maxlen can be set protocol dependant to a constant, e.g. 256. The second parameter is a double pointer. This is neccessary to cover the case of decoding: The caller may not know the current lenght of the result string, so he will supplie big enough buffer, or set *sp = NULL and let the buffer be allocated by XDR system itself. The caller gives only the address of a memory cell where xdr_string() stors the address of string data. In both encode and decode case an additional variable p is neccessary:


	char    buf[MAXLEN], *p;

	p = buf;
	xdr_string (handle, &p, maxlen)

---------------- | | | V -----|-------------------------------------------------- linear memory | p | | the string buf, i.e. string data | ----- ------------------------------------ ^ | address of p Figure 8 - 22 - The string buffer will be allocated only when *p == NULL, i.e. when p point to no valid buffer. In the other case the user must asure his buffer is not to small. In the case of XDR_FREE free(p) will be called if p!=NULL. Remark: The address operator & can in C be put only before a valid memory object. It makes no sense to write &&x. This method with a double pointer is not neccessary if maxlen is set, but it is the only efficient solution (also for shorter strings). xdr_wrapstring() is a "wrapper" around xdr_string():


	xdr_wrapstring(hand, strp)

ist equivalent to


	xdr_string(hand, strp, MAXUNSIGNED)

MAXUNSIGNED is the maximal unsigned integer. It is surely bad if it's bigger in encoding than in decoding system, but this problems arise only for encoding of very long strings. A practical limit is UDP packet size. Byte array is different from a string in that it is not ended with null, and null can be inside a byte array.


	bool_t  xdr_bytes (handle, sp, lp, maxlen)
	XDR     *handle;
	char    **sp;
	u_int   *lp;
	u_int   maxlen;

The filter xdr_bytes() has relativ to xdr_string() one additional argument: lp. It points to a place where the array lenght is stored. Like in the case of xdr_string() memory will be allocated by XDR system if *sp == NULL. - 23 - If the number of bytes to convert is known a faster routine xdr_opaque() can be used.


	bool_t  xdr_opaque (handle, sp, lp)
	XDR     *handle;
	char    *sp;
	u_int   lp;

xdr_bytes() can be regarded as a special case of an array filter.


	bool_t  xdr_array (handle, sp, lp, maxlen, esize, xdr_element)
	XDR     *handle;
	char    **sp;
	u_int   *lp;
	u_int   maxlen;
	u_int   esize;
	bool_t  (*xdr_element)();

esize is the size of one element in byte (e.g. calculated with sizeof()), and (*xdr_element)() is a pointer to XDR filter for one element. If one knows the number of bytes to convert one can use instead of xdr_array() faster routine xdr_vector() (like xdr_opaque() instead of xdr_bytes()).


	bool_t  xdr_vector (handle, sp, lp, esize, xdr_element)
	XDR     *handle;
	char    *sp;
	u_int   lp;
	u_int   esize;
	bool_t  (*xdr_element)();
In place of unions XDR uses discriminated unions. The discriminator, which at runtime decides which union arm will be followed, must be known before transmission. A special procedure must be specified for every union arm, which transforms this arm. This can be put like the following: Every union arm is mapped to a pair (value, proc):


	struct xdr_discrim {
		enum     value;
		bool_t   (*proc)();
	};
- 24 - value must equal discr for every union arm and (*proc)() is a procedure, which transforms this arm. Then xdr_union():


	bool_t  xdr_union (handle, discr, unp, arms, defaultarm)
	XDR                 *handle;
	enum_t              *discr;
	char                *unp;
	struct xdr_discrim  *arms;
	bool_t              (*defaultarm)();
has following parameters:
  1. discr, with the runtime information about which arm will be transformed first,

  2. unp, a pointer to the union data,

  3. arms, a vector of struct xdr_discrim with data for every arm. This vector must end with NULL.

  4. defaultarm can be a NULL pointer. Otherwise it is a filter called, if discr equals to no value in an arm.


We give an example for xdr_union() usage:



	enum utype      { INTEGER = 1, STRING = 2, MYTYPE = 3 };
	struct dunion {
		enum utype         discr;    /* der Diskriminator    */
		union {
			int            ival;
			char           *pval;
			struct mytype  mval;
		} uval;
	};

	struct xdr_discrim dunion_arms[4] = {
		{ INTEGER,       xdr_int },
		{ STRING,        xdr_wrapstring },
		{ MYTYPE,        xdr_myfilter },
		{ _dontcare_,    NULL }
	}

	bool_t xdr_dunion (handel, utp)
	XDR            *handle;
	struct dunion  *utp;
	{
		return (xdr_union (handle, &utp->utype, &utp->uval,
				dunion_arms, NULL));
	}
- 25 - Obviously xdr_myfilter() for tranformation of MYTYPEs must be written and supplied by the user. It is a good programming habit to put in the first line explicitely: INTEGER = 1, etc. for the compiler not to set it in a different way. Often it is the case, that a data object with pointers must be transformed. This is especially, when the pointer itself is an element of another data object. This task can be managed with xdr_reference().


	bool_t   xdr_reference (handle, pp, size, proc)
	XDR      *handle;
	char     **pp;
	int      *size;
	bool_t  (*proc)();

pp is a pointer to an object to be tranformed, size it's size (use sizeof() here), proc is a filter for the tranformation of an object. We give an example:


	struct pgn {
        char             *name;
        struct mytype    *mval;
	};

	#define MYSIZE  sizeof(struct mytype)

	bool_t xdr_pgn (handle, sp)
	XDR             *handle;
	struct pgn      *sp;
	{
        return (xdr_string    (handle, &sp->name, NLEN)  &&
                xdr_reference (handle, &sp->mval, MYSIZE,
                               xdr_mytype));
	}

xdr_reference() can be used only if the named pointer is not NULL. Otherwise we must use xdr_pointer(). The synopsis is the same. - 26 - 2.3.3. Custom filter The fact that all XDR filters have a pointer to a XDR handle as a first argument and bool_t as a return value can be used to built own filters. Already existing filters from XDR library and own filters can be used as elements. E.g. the conversion of the following struct:


	struct my_struct {
		int     i;
		char    c;
		short   s;
	};

can be performed with the following so called "custom filter":


	bool_t my_filter (handle, data)
	XDR                *handle;
	struct my_struct   *data;
	{
		return (xdr_int   (handle, &data->i)    &&
				xdr_char  (handle, &data->c)    &&
				xdr_short (handle, &data->s));
	}

The custom filter makes a conversion of my_struct one component after another. In the case of error the conversion is broken up and a FALSE value is returned. Otherwise TRUE will be returned. Just for this reason XDR developers have chosen opposite to C habit:


	#define TRUE    1
	#define FALSE   0

The feature of primitiv filters, that they convert in any direction (encode, decode, but also XDR_FREE) transfers on custom filters. Also alignment problems does not pose any problems, if only primitiv filter work correctly. The second parameter in the filter call can have different meaning, as we will see in an example later. The memory space neccessary for buffering can be unknown before the conversion takes place. In many cases a filter must allocate memory. Following filters may malloc memory space: - 27 -


	xdr_array(),
	xdr_bytes(),
	xdr_pointer(),
	xdr_reference(),
	xdr_string(),
	xdr_vector(),
	xdr_wrapstring()

To free this memory after usage xdr_free() can be aplied:


	void xdr_free (proc, obj)
	xdr_proc_t      proc;
	char            *obj;

The first argument is a pointer to a filter, which malloced memory e.g. xdr_string, and the second is the address of a pointer to a generated object:


	char    *ptr = NULL;

	xdr_string (&handle, &ptr, MAXSIZE);
	xdr_free (xdr_string, &ptr);

The feature to malloc, or to free the memory passes to custom filters. If malloc() is explicitly called in a custom filter also free() should be called for handle->x_op == XDR_FREE. - 28 - 2.3.4. Filter examples The following example shows how a XDR filter can be used to store numerical experiment data in a system independant way. Data can be read with the same procedure on another computer. Reading follows with -i and writting on a file with -o.


        #include   
        #include   
        #define    MAXNAME      128
        #define    MAXX        1024
        #define    DATLEN       27

        struct edata {
            char    *autor;
            char    date[DATLEN];
            int     n;
            float   *x;
        };

        bool_t filter (handle, d)
        XDR            *handle;
        struct e_data  *d;
        {
            register int    i;
            char            *da = d->date;

            if (!xdr_string (handle, &d->autor, MAXNAME) ||
                !xdr_string (handle, &da, DATLEN)        ||
                !xdr_int    (handle, &d->n)              ||
                d->n > MAXX)
                return (FALSE);
            for (i = 0; i < d->n; i++)
                if (!xdr_float (handle, &d->x[i])
                    return (FALSE);
            return (TRUE);
        }


        main (argc, argv)
        int     argc;
        char    *argv[];
        {
            struct e_data    d;
            float            *malloc ();
            XDR              hand;
            FILE             *fp, *fopen();
            int              i;
            char             *ctime();

            if (argc < 3) {
                printf ("usage: %s -o | -i file\n", argv[0]);
                exit (1);
            }
            d.x = malloc (MAXX * sizeof (float));
            if (argv[1][1] == 'i') {    /* input data    */
                fp = fopen (argv[2], "r");
                xdrstdio_create (&hand, fp, XDR_DECODE);
                d.autor = NULL;
            }
            else {
                fp = fopen (argv[2], "w");
                xdrstdio_create (&hand, fp, XDR_ENCODE);
                strcpy (d.date, ctime (time (0)));
                d.autor = "zbyszek";
                d.n     = MAXX;
                for (i = 0; i < MAXX; i++)
                    d.x[i] = i / 3;
            }
            printf ("filter = %d\n", filter (&hand, &d));
        }

In the "-o" case we first generate data (else block in main()). The proper read- or write- operation takes place in filter call (last line). Please, notice the diffeerence between autor and a date variable handling. The help variable


	char    *da = d->date;

is neccessary. The xdr_string() call needs the addresse of the addresse of the string. In the second example a record stream is used. The create call from the first example must be changed to:


	xdrrec_create (&hand, 0, 0, fp, rdata, wdata);

Before we must set the operation in x_op:


	hand.x_op = XDR_DECODE oder XDR_ENCODE;

- 30 - Two functions rdata() and wdata() are wrapper around fread() and fwrite():


        rdata (fp, buf, n)
        FILE    *fp;
        char    *buf;
        int     n;
        {
            if (n = fread (buf, 1, n, fp))
                return (n);
            else
                return (-1);
        }

        wdata (fp, buf, n)
        FILE    *fp;
        char    *buf;
        int     n;
        {
            if (n = fwrite (buf, 1, n, fp))
                return (n);
            else
                return (-1);
        }
Any two functions can be put here, which give access to whichever transport medium set in the fourth parameter of a "create" call and passed to these functions. Additional two lines at the end of our filter make sense here:


	if (hand->x_op == XDR_ENCODE)   xdr_endofrecord (hand, TRUE);
	if (hand->x_op == XDR_DECODE)   xdr_skiprecord (hand);

- 31 - 2.4. The XDR protocol specification 2.4.1. XDR data types This short description follows RFC1014 from IETF. We give here the byte order each data type is transmitted and a syntax of the XDR language. The XDR language is simmilar to C, therefor we give here mainly differences. All data types are multiples of 4 byte (32 bit). This asures correct alignment on most hardware platforms. (Exception: CRAY - 8 byte alignment). Fields will be padded with nulls up to 4 byte (especially strings and opaque). Transmission order is byte 0, byte 1, ..., i.e. big endian. The transmission of one byte in a way characteristic for the transport medium will be presumed. XDR says nothing about how one byte is composed of single bits. It will be specified on other layers mostly data link layer if the most significant bit comes first. integer are 4 byte, 2 complement


                    (MSB)                (LSB)
                    +------+------+------+------+
                    |Byte 0|Byte 1|Byte 2|Byte 3|
                    +------+------+------+------+
Unsigned integer is an integer without sign. Enum is built of integer like in C. Boolean is enum { FALSE = 0, TRUE = 1 }. It is declaired as:


        bool        identifier;

Hyper integer and unsigned hyper integer are extensions of integer and and unsigned integer up to 8 byte:


        (MSB)                                            (LSB)
        +------+------+------+------+------+------+------+------+
        |Byte 0|Byte 1|Byte 2|Byte 3|Byte 4|Byte 5|Byte 6|Byte 7|
        +------+------+------+------+------+------+------+------+

- 32 - Float: is a 4 byte long IEEE single precision float with fields: S: sign bit, 0 - positiv, 1 - negativ E: exponent with basis 2, 8 bit long V: bias of an exponent: 127 bit F: mantisse with basis 2, 32 bits (-1)**S * 2**(E-V) * 1.F ** is power operator


                    +------+------+------+------+
                    |Byte 0|Byte 1|Byte 2|Byte 3|
                    S|   E  |          F        |
                    +------+------+------+------+
                    1|<- 8->|<------23 Bit----->|
As the transmission of one byte is managed in the link layer, S in the above figure describes only the most significant bit of null-th byte and not a bit, which will be first transmitted. Double is 8 byte longer float:


        +------+------+------+------+------+------+------+------+
        |Byte 0|Byte 1|Byte 2|Byte 3|Byte 4|Byte 5|Byte 6|Byte 7|
        S|    E   |                    F                        |
        +------+------+------+------+------+------+------+------+
        1|<--11-->|<-----------------52 Bit-------------------->|

Signed Zero, Signed Infinity (Overflow), or Denormalized (underflow) correspond to IEEE convention, NaN (not a number) will not be used. Fixed-length opaque: +------+------+ ... +------+ ... +------+ ... +------+ |Byte 0|Byte 1| ... |Byte n-1 0 | 0 | ... | 0 | +------+------+ ... +------+ ... +------+ ... +------+ |<---------n Byte---------->| null padded | Variable length opaque are encodded as unsigned integer followed with fixed length opaque. The syntax is:


        opaque identifier;

or


        opaque identifier<>;

- 33 - m describes the maximal length, e.g. for UDP m=8192. If not specified m equels 2**32 - 1. Strings are in analogy to opaque unsigned integers followed with string data in ascending order byte0, byte1, byte2, ... and null padded up to multiple of four byte. The syntax is:


        string identifier;

or


        string identifier<>;

Fixed-length array:


        +-------------+-------------+ ...  +-------------+
        | Element 0   | Element 1   | ...  | Element n-1 |
        +-------------+-------------+ ...  +-------------+

Elements are multiples of 4 byte. They can be of either length, which is neccessary if the element type is variable lenght, like a string. Variable-length array is an integer followed by fixed length array. The Syntax is:


        type-name identifier;

or


        type-name identifier<>;

Struct is in analogy to array a sequence of struct elements in the order like in the definition. Also here, like anywhere in XDR, elements are multiples of 4 byte. Unions (Varianten) branch into several arms, which describe different types of which only one is valid at runtime. During conversion in a XDR stream type valid at runtime must be marked with a discriminant. There are no unions without discriminant. The discriminant will be transmitted first as an unsigned integer, followed by valid arms one after another with a type mark (unsigned integer) on the beginning of each. The discriminant of a whole union must corespond to one of given types. The syntax is: - 34 -


        union switch (discriminant-declaration) {
        case discriminant-value-A;
                arm-declaration-A;
        case discriminant-value-B;
                arm-declaration-B;
                .
                .
                .
        default :
                default-declaration;
        } identifier;

The default arm is optional. The void type has no data. A constant is an integer:


		const        identifier = n;

"typedef" declairs no data, but like in C language provide new types for declarations. There exists another notation equivalent to typedef, but prefered in XDR. Instead of


	typedef enum {
        FALSE = 0,
        TRUE  = 1
	} bool;

we write:


	enum bool {
		FALSE = 0,
		TRUE  = 1
	};

In XDR language pointers like:


        type-name        *identifier;

have another meaning as in C. They are called optional data. The above declaration is equivalent to a union:


        union switch (bool opt) {
        case TRUE:
                type-name        element;
        case FALSE:
                void;
        } identifier;

- 35 - This is also equivalent to:

        type-name        identifier<1>;
The four byte long bool opt will be interpreted as the array length (see also arrays). Til now there are no bit fields in XDR. - 36 - These are XDR data types listed together:
XDR-Datentyp C-Datentyp Decode/Encode-Filter
int int xdr_int
unsigned int unsigned int, uint xdr_uint
enum enum xdr_enum
bool int, bool_t xdr_bool
long long xdr_long
unsigned long unsigned long, ulong xdr_u_long
float float xdr_float
double double xdr_double
opaque identifier[n] char identifier[n] xdr_opaque
opaque identifier
opaque identifier<>
struct {
	uint identifier_len;
	char *identifier_val;
} identifier;
				
xdr_bytes
string identifier char *identifier xdr_string
string identifier<>    
typename identifier[n] typename identifier[n] xdr_vector
typename identifier
typename identifier<>
struct {
	uint     identifier_len;
	typename *identifier_val;
} identifier;
				 
xdr_array
struct {
	component-delar-A;
	component-delar-B;
	...
} identifier;
				
same as XDR custom filter
union switch (discr) {
	case discr-value-A:
		arm-A;
	case discr-value-B:
		arm-B;
	...
	default:
		default-decl;
} identifier;
				
struct identifier {
	int discr;
	union {
		arm-A;
		arm-B
	}
};
				
custom filter
void void xdr_void
constant int xdr_int
typename *identifier;
struct identifier {
	int  discr;
	union {
		typename element;
		void
	}
}
				
custom filter
- 38 - 2.4.2. The XDR syntax In the syntax description Backus-Naur notation will be used (see appendix).


                declaration:
                        type-specifier       identifier
                        | type-specifier     identifier "[" value "]"
                        | type-specifier     identifier "<" value ">"
                        | "opaque"           identifier "<" value ">"
                        | "string"           identifier "<" value ">"
                        | type-specifier "*" identifier
                        | "void"

                value:
                        constant
                        | identifier

                type-specifier:
                          [ "unsigned" ] "int"
                        | [ "unsigned" ] "hyper"
                        | "float"
                        | "double"
                        | "bool"
                        | enum-type-spec
                        | struct-type-spec
                        | union-type-spec
                        | identifier

                enum-type-spec:
                        "enum" enum-body

                enum-body:
                        "{"
                        (     identifier "=" value )
                        ( "," identifier "=" value )*
                        "}"

                struct-type-spec:
                        "struct" struct-body

                struct-body:
                        "{"
                        ( declaration ";" )
                        ( declaration ";" )*
                        "}"

                union-type-spec:
                        "union" union-body


                union-body:
                        "switch" "(" declaration ")" "{"
                        ( "case" value ":" declaration ";" )
                        ( "case" value ":" declaration ";" )*
                        [ "default"    ":" declaration ";" ]
                        "}"

                constant-def:
                        "const" identifier "=" constant ";"

                type-def:
                        "typedef" declaration ";"
                        | "enum"   identifier enum-body   ";"
                        | "struct" identifier struct-body ";"
                        | "union"  identifier union-body  ";"

                definition:
                        type-def
                        | constant-def

                specification:
                        defintion *

                keyword:
                        "bool"    | "case"   | "const"    |
                        "default" | "double" | "enum"     |
                        "float"   | "hyper"  | "opaque"   |
                        "string"  | "struct" | "switch"   |
                        "typedef" | "union"  | "unsigned" |
                        "void"

                identifier:
                        letter
                        | identifier letter
                        | identifier digit
                        | identifier \"_\"
                        must not be a keyword

                letter:
                        upper and lower case are same

                comment:
                        "/*" comment-text "*/"

- 40 - 3. The port mapper network service 3.1 The functional principle As an opposite to remote command execution (Remote Command Execution - RCX) like rsh, rcp or rlogin, it is not neccessary for RPC call to start a new remote process. RPC call connects with a already running daemon, which is waiting for a request. We call this process a server. One server can wait for requests of more clients. It is nevertheless possible, that one server computer runs more daemon service processes, which hold more service procedures ready to be called. Remote services will be addressed by program number, version number and procedure number. In one server process there may exist more versions of one service procedure, which allows to manage a developemenet in a network. To address a service the client must know the server computer, program number, version number and procedure number. Network transport protocols like TCP provide in addition to a network address and a host address also port numbers: transport address = network address + host address + port number | | ------------------------------ | e.g. IP address Figure 9 A port is a queue with two semaphores: one for input and one for output. The transport way is described by a pair (sender transport address, receiver transport address). With the port number one can address a server process, which is waiting for a request on this port. It won't have any sense to address a service process by it's process number, because this number can change after a process is killed and restarted. A constant mapping between a service and a port number is nevertheless also not good. One has to consider other port users beside RPC. A flexible mapping between services and port numbers is maintained by a so called port mapper. - 41 - (program number, procedure version) <--> (protocol, port number) We call this mapping the binding. In RPC service procedures are addressed not by a name, but by a triple (program number, version, procedure number). At loading time known procedure names can be removed from compiled object. ------------------------------------ | program PROGNR | | | | ---------------------------- | | | version VERSNR | | | | | <- | ------> port number | | -------------------- | | | | | procedure PROCNR | | | | | -------------------- | | | | . | | | | . | | | ---------------------------- | | . | | . | | . | | --------------------------- | | | version VERSNR_2 | | | | . | | | | . | | | --------------------------- | ------------------------------------ Figure 10 The port mapper is a daemon process in server computer. It will be started once e.g. after operating system boot. Every new server process must register it's service at port mapper and get a port number at which it will wait for requests. Port numbers can differ between systems, or runtime situation. The port mapper itself however is waiting always at the same port (number 111). All new requests first connect to this port and get there the port number of the required service routine, which they use for the following communication. The details of this mechanism are called port mapper protocol. The procedure number will not be registered, but it will be passed to remote procedure. - 42 - ------------------ ------------------------- | | | | | | ----------- | | client | | port | server | | | | a | | | | ----------- | | | | | | ------------ | ----------- ---------- | | | | -------------------------> | port | ---> | port | | | | client- | <------------------------- | 111 | <--- | mapper | | | | | ----------- ----------- ---------- | | | program | <-------- | | ^ | | | | | | | ----------- | | | ------------ | | | | port | registering | | | | | | b | of a service | | | | | ----------- | | | | | | | | | | | | | ----------- ----------- | | | | | | port | ---> | server | | | | | ---------------> | c | | program | | | | ------------------ | | <--- | | | | | ----------- ----------- | | | | | ------------------ ------------------------- Figure 11 Port mapper service is statefull. After a second start of a port mapper all server processes must be registered once more. The current state of a port mapper can be seen with rpcinfo(8c). - 43 - 3.2. The program numbers SUN allows for a user program numbers between 0x 20 000 000 and 0x 3f fff fff. All other numbers are reserved (see appendix). With this numbers server processes register their services at port mapper. Clients use these numbers to reference services (well known numbers). This list is not complete (see also /etc/rpc). It will be extended by SUN. To record own entry reference SUN.
0 - 1fffffff Definiert von SUN
100000 PMAPPROG Portmapper
100001 RSTATPROG remote stats
100002 RUSERSPROG remote users
100003 NFSPROG NFS
100004 YPPROG NIS
100005 MOUNTPROG mount daemon
100006 DBXPROG remote dbx
100007 YPBINDPROG NIS Binder
100008 WALLPROG shutdown msg
100009 YPPASSWDPROG yppasswd server
100010 ETHERSTATPROG ether stats
100011 RQUOTAPROG disk quotas
100012 SPRAYPROG spray packets
100013 IBM3270PROG IBM 3270 mapper
100014 IBMRJEPROG RJE mapper
100015 SELNSVCPROG selection service
100016 RDATABASEPROG remote database access
100017 REXECPROG remote execution
100018 ALICEPROG Alice Office Automation
100019 SCHEDPROG scheduling service
100020 LOCKPROG local lock manager
100021 NETLOCKPROG network lock manager
100022 X25PROG x.25 inr protocol
100023 STATMON1PROG status monitor 1
100024 STATMON2PROG status monitor 2
100025 SELNLIBPROG selection library
100026 BOOTPARAMPROG boot parameters service
100027 MAZEPROG mazewars game
100028 YPUPDATEPROG NIS update
100029 KEYSERVERPROG key server
100030 SECURECMDPROG secure login
100031 NETFWDIPROG nfs net forwarder init
100032 NETFWDTPROG nfs net forwarder trans
100033 SUNLINKMAP_PROG sunlink MAP
100034 NETMONPROG network monitor
100035 DBASEPROG lightweight database
100036 PWDAUTHPROG password authorisation
100037 TFSPROG translucent file svc
100038 NSEPPROG nse server
100039 NSE_ACTIVATE_PROG nse activate daemon
100043 SHOWHFD showfh
150001 PCNFSDPROG pc password authorisation
200000 PYRAMIDLOCKINGPROG Pyramid locking
200001 PYRAMIDSYS5 Pyramid Sys5
200002 CADDS_IMAGE CV cadds_image
300001 ADT_RFLOCKPROG ADT file locking
20000000 - 3fffffff steht fuer freie Benutzung
40000000 - 5fffffff voruebergehend besetzt
60000000 - 7fffffff reserviert
80000000 - 9fffffff reserviert
a0000000 - bfffffff reserviert
c0000000 - dfffffff reserviert
e0000000 - ffffffff reserviert
- 45 - 3.3. The port mapper protocol The port mapper protocol is written in a RPC language.


        const PMAP_PORT = 111           /* portmapper port number */


        /*
        ** A mapping of (program, version, protocoll) to port number
        */
        struct mapping {
            unsigned int prog;
            unsigned int vers;
            unsigned int prot;
            unsigned int port;
        };


        /*
        ** Supported values for port field
        */
        const IPPROTO_TCP =  6;
        const IPPROTO_UDP = 17;


        /*
        ** A list of mappings
        */
        struct *pmaplist {
            mapping  map;
            pmaplist *next;
        };


        /*
        ** Arguments to callit()
        */
        struct call_args {
            unsigned int prog;
            unsigned int vers;
            unsigned int proc;
            opaque args<>;
        };


        /*
        ** Results of callit()
        */
        struct call_result {
            unsigned int port;
            opaque res<>;
        };


        /*
        ** Portmapper procedures
        */
        program PMAP_PROG {
            version PMAP_VERS {
                void        PMAPPROOC_NULL    (void)      = 0;
                bool        PMAPPROOC_SET     (mapping)   = 1;
                bool        PMAPPROOC_UNSET   (mapping)   = 2;
                uint        PMAPPROOC_GETPORT (mapping)   = 3;
                pmaplist    PMAPPROOC_DUMP    (void)      = 4;
                call_result PMAPPROOC_CALLIT  (call_args) = 5;
            } = 2;
        } = 100000;

Procedure descriptions: PMAPPROOC_NULL: no action, for test only PMAPPROOC_SET: registering PMAPPROOC_UNSET: takes the registering back PMAPPROOC_GETPORT: returns port number of (program, version, pro- torcol), or 0 if not registered PMAPPROOC_DUMP: returns the list of all registered tripels (program, version, protocol). PMAPPROOC_CALLIT: calls referenced service and send results back to the client (with UDP), if service run without errors. It will be used for broadcasting. - 47 - 4. RPC programming I The RPC programming interface is divided into three layers: 1. High-, 2. Middle- und 3. Low-Level. 4.1. High-level RPC High-level RPC: For this layer the operating system, the hardware and the used network is completely transparent. The programmer calls a C routine, which hide the network handling completely: e.g. rnusers() to count users logged in on a remote computer. Other well known routines are:
rusers returns information about users on a remote computer
havedisk test if a remote computer has a hard disc
rstat returns information about system load
rwall write a message to all users on a remote computer
yppasswd start a password update in Network Information Service

     Routines from a high level RPC are put together in librpcsvc.a as
object modules ready to use. Please, reference system manual for a complete
list.


     For the sake of simplicity some autors don't even mention this layer
and divide the programming interface in two layers. Here we stick to SUN's
terminology of three layers.




                                 - 48 -




4.2.  Middle level RPC


     The programming interface to a second RPC layer consists of three
library functions: registerrpc(), svc_run() and callrpc(): Their
synopsis is put into appendix. registerrpc() is used to register a
service routine at the port mapper on server side. With svc_run()
the infinite loop is started, which accepts and answeres clients requests.
callrpc() realises the proper remote call on client side. An example,
analog to rnuser() reads on server side as follows:




        #include 
        #include 
        #include 

        extern unsigned long     *nuser();

        main ()
        {
            registerrpc (RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
                nuser, xdr_void, xdr_long);
            svc_run ();
            fprintf (stderr,
                "Server: main loop broke down, server died\n");
            exit (1);
        }

This program must be started on server side as a process waiting for client's calls. A remote procedure nuser() is registered with parameters: program, version and procedure numbers. In addition two filters xdr_void and xdr_long are supplied, which will convert call data in each direction: from client to server and back from server to client. Then the server process enters infinite loop and wait for requests. The remote procedure nuser() exists in a standard library. The client counterpart looks like the following: - 49 -


        #include 
        #include 
        #include 

        main (argc, argv)
        int     argc;
        char    *argv[];
        {
            unsigned long   n;
            int             stat = -1;

            if (argc > 1) {
                if (stat = callrpc (argv[1],
                    RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
                    xdr_void, 0, xdr_long, &n))
                    fprintf (stderr, "%s callrpc error %d\n",
                                         argv[0],stat);
                printf ("%d users on %s\n", n, argv[1]);
            }
            exit (0);
        }

The four last parameters of callrpc are input/output filter and data. For data fields pointers are used to be compatible with passing data back from a procedure call. Input/output filter are functions, to which later in the call two parameters are passed. Here xdr_long() is possible with:


        xdr_long (handle, l)
        XDR     *handle;
        long    *l;

but xdr_string, which uses three parameters not xdr_string() must be wrapped around with a \"wrapper\" - another procedure, which prepares parameters and then calls xdr_string(); All details of a network transport are hidden in these three procedures: registerrpc(), svc_run() and callrpc(). In middle level RPC UPD is used. Therefore input/output data can not be bigger than packet size of 8K. See also UDPMSGSIZE in . - 50 - 4.3. Low level RPC Middle level RPC routines hide details of RPC communication. However it might be neccessary just to influence these details. This can practically occur in three cases:
  1. One needs to omit UDP packet size limit and aply TCP instead, or other features of communication channel should be influenced.

  2. One would like RPC functions to allocate memory for the transfered data. (The way was shown in a chapter about XDR.)

  3. One would like to asure, that only chosen users are allowed to call services and one desires users authentication.



     In those cases middle level routines must be decomposited in low
level routines. This decomposition is shown in the following scheme:




                                - 51 -


                      ---------------------------------
                      |          middle level         |
                      ---------------------------------
                      |  client side  | server side   |
------------          ---------------------------------          ------------
|          | -------> |  callrpc()    | registerrpc() | <------- |          |
|          |          |               | svc_run()     |          |          |
| client   |          ---------------------------------          | server   |
| program  |              |                     |                | program  |
|          |              |                     |                |          |
|          |              V                     V                |          |
|          |      -----------------------------------------      |          |
|          |      |              low level                |      |          |
|          |      -----------------------------------------      |          |
|          | ---> | client side       | server side       | <--- |          |
------------      -----------------------------------------      ------------
     |            | clnt_create()     |                   |           |
     |            | clntudp_create()  | svcudp_create()   |           |
     |            | clnttcp_create()  | svctcp_create()   |           |
     |            | clntraw_create()  | svcraw_create()   |           |
     |            | clnt_destroy()    | svc_destroy()     |           |
     |            | clnt_freeres()    | svc_freeargs()    |           |
     |            | clnt_call()       |                   |           |
     |            | clnt_control()    |                   |           |
     |            |                   | svc_register()    |           |
     V            |                   | svc_unregister()  |           V
------------      |                   | svc_sendreply()   |      ------------
|          | <--- |                   | svc_getreqset()   | ---> |          |
|   XDR    |      -----------------------------------------      |   XDR    |
| library  |               |                  |                  | library  |
|          |               |                  |                  |          |
------------               V                  V                  ------------
                    -------------------------------------
                    |  transport library of a network   |
                    -------------------------------------

Figure 12


     The synopsis of low level RPC routines is put to the appendix.


     The following listing shows, how a rnuser server program from our
former example can be realized with low level routines:












                                - 52 -


        #include 
        #include 
        #include 
        #include 

        main ()
        {
            SVCXPRT   *transp;
            int       nuser();

            transp = svcudp_create (RPC_ANYSOCK);
            pmap_unset (RUSERSPROG, RUSERSVERS);
            svc_register (transp, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
                nuser, IPPROTO_UDP);
            svc_run ();
            fprintf (stderr, "main loop with dispatcher broke down\n");
            svc_unregister (RUSERSPROG, RUSERSVERS);
            svc_destroy (transp);
        }

        nuser (req, transp)
        struct svc_req   *req;
        SVCXPRT          *transp;
        {
            unsigned long    n, clnt_data;

            switch (req->rq_proc)
            case NULLPROC:
                svc_sendreply (transp, xdr_void, 0);
                return;
            case RUSERPROC_NUM:
                svc_getargs (transp, xdr_u_int, &clnt_data);
                /*
                ** count user number in n-variable here
                */
                svc_sendreply (transp, xdr_u_long, &n);
                return ();
            default :
                svcerr_noproc (transp);
                return ();
            }
        }

The interface to transport layer is enclosed in two places in the server side in structure SVCXPRT in in the client side in structure CLIENT in Originaly it was implemented with Berkeley sockets, but it may be exchanged with another protocols. - 53 - The argument in svcudp_create(), PRC_ANYSOCK causes a new socket to be created. A number of an already existing socket can be put here. If the address is bound to the socket port numbers in svcudp_create() and clntudp_create() must harmonize. pmap_unset() make the previous port mapper registering with RUSERSPROG and RUSERSVERS numbers unvalid. In this case two last lines in main() are actually not neccessary. Notice, that unlike in registerrpc() no procedure number and no XDR filter are neccessary in svc_register(). The procedure number will be passed to a dispatcher nuser(), which was included in registerrpc() to follow the last branching. Both switch cases in nuser() NULLPROC and default are covered by middle level routine registerrpc(). It is recomended not to forget the case of NULLPROC. A client can test with it, if the server is alive. With the line svc_getargs() client call data (clnt_data) are accessed. The corresponding client stub reads as follows: - 54 -


        #include 
        #include 
        #include 
        #include 
        #include 

        main (argc, argv)
        int    argc;
        char    *argv[];
        {
            struct hostent       *hp;
            struct timeval       t1, t2;
            struct sockaddr_in   s_addr;
            int                  sock = RPC_ANYSOCK;
            register CLIENT      *cp;
            unsigned long        n;

            if (argc != 2) {
                fprintf (stderr, "usage: %s hostname\n", argv[0]);
                exit (1);
            }
            t1.tv_sec         = 3;
            t1.tv_usec        = 0;
            hp                = gethostbyname (argv[1]);
            bcopy (hp->h_addr, (caddr_t)&s_addr.sin_addr, hp->h_length);
            s_addr.sin_family = AF_INET;
            s_addr.sin_port   = 0;
            cp = clntudp_create (&s_addr, RUSERPROG, RUSERVERS, t1, &sock);

            t2.tv_sec         = 20;
            t2.tv_usec        = 0;
            clnt_call (cp, RUSERSPROG_NUM, xdr_void, 0, xdr_u_long, &n, t2);

            printf ("%d users on %s\n", n, argv[1]);
            clnt_destroy (cp);
        }

This piece of code shows approximately the internal construction of a middle level routine callrpc(). We can see where UDP can be exchanged by TCP (caution: clnttcp_create has some different arguments than clntudp_create()). Naturally the transport mechanism must be the same on client and on server side. We also mention a general routine clnt_create(), which takes \"udp\", or \"tcp\" as a last argument. t1 and t2 are timeouts in seconds between single communication trials and together. The setings can be tuned later on with clnt_control(). Only if sin_port is set to zero the remote port mapper finds the propriate port the service is waiting at. - 55 - 5. RPC programming II / advanced methods 5.1. Debugging with raw RPC Raw RPC is a test environment for low level RPC. It consists of a single propgram both for client and server stub. All network communication is omitted. An example follows:


        #include 
        #include 
        #include 

        main ()
        {
            CLIENT          *cp;
            SVCXPRT         *sp;
            int             ni, no;
            struct timeval  timeout = {0, 0};

            sp = svcraw_create ();
            svc_register (svc, 200000, 1, server, 0);
            cp = clntraw_create (200000, 1);
            clnt_call (cp, 1, xdr_int, &ni, xdr_int, &no, timeout);
            printf ("input %d output %d\n", ni, no);
        }

        server (req, transp)
        struct svc_req  *req;
        SVCXPRT         *transp;
        {
            int    n;

            svc_getargs (transp, xdr_int, &n);    /* get client data n     */
            n++;
            svc_sendreply (transp, xdr_int, &n);  /* pass n to the client  */
        }

The test environment allows to extend client and server stubs and remote procedure and to test their functioning without a network. After a test one can exchange ...raw_create() with ..._create() and split a program into client and a server. All routines are built in the way, that allows debugging with standard tools, like debugger. - 56 - 5.2. How a server dispatcher is built We decomposed into simpler parts two middle level routines registerrpc() and callrpc(). But sometimes the need may arise to decompose svc_run(). This can be the case if the server have to wait also for other events than RPC requst call. The complete code for a standard dispatcher is like the following one:


        void    svc_run ()
        {
            fd_set    readfds;
            int       dtbsz = getdtablesize ();

            for (;;) {
                readfds = svc_fdset;
                switch (select (dtbsz, &readfds, NULL, NULL, NULL)) {
                case -1:
                    if (errno != EBADF)
                        continue;
                    perror ("select");
                    return;
                case 0
                    continue;
                default:
                    svc_getreqset (&readfds);
                }
            }
        }

Here we see an infinite loop waiting for requests with select() and a branch to a service routine with svc_getreqset(). svc_getreqset() calls the proper service routine and send the result data back to a client. In this loop a user can put his own code. In most cases it will be waiting for another events. This can be maintained by expanding readfs mask. svc_fdset is a global read only descriptor bit mask from RPC library. For the server after getting client's request to be soon ready for a next request, he must charge with a response a child process. For the dispatcher not to send back to the client empty result data a fork() must be called in the dispatcher. - 57 - 5.3. Server with different versions If the remote procedure has more versions, which is often the case in the developement, it may be functional to support all these versions in the same dispatcher - wrapper, like the following one:


        main ()            /* Server */
        {
            .
            .
            int     proc();
            .
            .
            svc_register (transp, PROGNR, VERS_SHORT, proc, IPPROTO_TCP);
            svc_register (transp, PROGNR, VERS_LONG,  proc, IPPROTO_TCP);
            .
            .
            svc_run ();
        }

        proc (req, transp)            /* wrapper */
        struct svc_req   *req;
        SVCXPRT          *transp;
        {
            extern short   proc1();   /* procedures differ in ret value */
            extern long    proc2();
            static short   n1;
            static long    n2;

            switch (req->rq_proc {
            case NULLPROC:
                .
            case PROCNR:
                switch (req->rq_vers {
                case VERS_SHORT:
                    n1 = proc1 (req, transp);
                    svc_sendreply (transp, xdr_short, &n1);
                    break;
                case VERS_LONG:
                    n2 = proc2 (req, transp);
                    svc_sendreply (transp, xdr_long,  &n2);
                    break;
                }
            default:
                svcerr_noproc (transp);
            }
        }

- 58 - If this is not so, the client can get a supported version from the error answeres like this:


        main ()                             /* Client */
        {
            struct rpc_err    err;
            short             s;
            long              l;
            .
            .
            cl = clnt_create (host, PROGNR, VERS2, "udp");
            switch (clnt_call (cl, PROCNR, xdr_void, NULL, xdr_long,
                               &l, timeout)) {

            case RPC_SUCCESS:               /* version ok.         */
                printf ("%d\n", l);
                break;

            case RPC_PROGVERSMISMATCH:      /* try former version  */
                clnt_geterr (cl, &err);
                if (err.re_vers.high < VERS2) {
                    clnt_destroy (cl);
                    cl = clnt_create (host, PROGNR, VERS1, "udp");
                    clnt_call (cl, PROCNR, xdr_void, NULL, xdr_short,
                               &s, timeout);
                    printf ("%d\n", s);
                }
                break;
            }
        }

- 59 - 5.4 An example of a TCP application The following example follows SUN's RPC usage. It shows a typical TCP application. The stdin will be read on client side, transported to the server and there written on stdout by a remote procedure. The task of a read/write operation takes over a non-typical XDR filter xdr_rcp(). This filter is not symetrical in respect to ENCODE and DECODE.


        #include 
        #include 
        #include 
        #include 
        #include "rcp.h"        /* with PROG-, VERS-, PROC */

        main (argc, argv)       /* client side */
        int   argc;
        char  *argv[];
        {
            int                 xdr_f();
            int                 socket = RPC_ANYSOCK
            struct sockaddr_in  server;
            struct hostent      *hp;
            struct timeval      tio;
            CLIENT              *cl;

            hp = gethostbyname (argv[1]);
            bcopy (hp->h_addr, (caddr_t)&server.sin_addr), hp->h_length);
            server.sin_family = AF_INET;
            server.sin_port   = 0;
            cp = clnttcp_create (&server, PROG, VERS, &socket,
                BUFSIZ, BUFSIZ);
            tio.tv_sec  = 20;
            tio.tv_usec = 0;
            clnt_call (cl, PROC, xdr_f, stdin, xdr_void, 0, tio);
            clnt_destroy (cl);
        }

- 60 -


        #include 
        #include 
        #include "rcp.h"

        main ()                 /* server side */
        {
            SVCXPRT     *transp;
            int         rpc();

            transp = svctcp_create (RPC_ANYSOCK, BUFSIZ, BUFSIZ);
            pmap_unset (PROG, VERS);
            svc_register (transp, PROG, VERS, rpc, IPPROTO_TCP);
            svc_run ();
        }


        rcp (req, transp)                 /* remote procedure */
        struct svc_req      *req;
        SVCXPRT             *transp;
        {
            extern int    xdr_f();

            switch (req->rq_proc) {
            case NULLPROC :
                svc_sendreply (transp, xdr_void, 0);
                break;
            case RPCPROC :
                svc_getargs (transp, xdr_f, stdout);
                svc_sendreply (transp, xdr_void, 0);
                break;
            default :
                svcerr_noproc (transp);
                break;
            }
        }

- 61 -


        #include 
        #include 

        xdr_rcp (xdr, fp)                 /* XDR-Filter */
        XDR    *xdr;
        FILE   *fp;
        {
            unsigned long    size = 0;
            char             buf[BUFSIZ], *p = buf;

            if (xdr->x_op == XDR_FREE)
                return (1);
            while (1) {
                if (xdr->x_op == XDR_ENCODE)    /* in client */
                    size = fread (p, sizeof (char), BUFSIZ, fp);
                if (!xdr_bytes (xdr, &p, &size, BUFSIZ))
                    return (0);
                if (size == 0)
                    return (1);
                if (xdr->x_op == XDR_DECODE)    /* in server */
                    fwrite (p, sizeof (char), size, fp);
            }
        }

In client a line buffering can be switched on with setbuf(), if you'd like to send every line separately to the server. - 62 - 5.5. Technics of asynchron communication 5.5.1. Broadcasting With broadcast we understand a situation, where a client calls more servers with one call. Naturally he must expect more answeres. A thread can maintain only one TPC connection. Therefore connection oriented transport must be excluded. As broadcasting is mostly used, when a client doesn't know which server provide which services, the RPC call automaticely filters erroneous answeres (e.g. wrong version number) on client side and the client won't receive them. The broadcast call request size is limited to a maximum transfer unit (MTU). For ethernet MTU is 1.5 kbyte, minus RPC header is 1.4 kbyte. Answers are limited to a packet size (now 8.8 kbyte). How a broadcast call spreads depends on the network. For ethernet all hosts in LAN are receivers. Further distribution depends on routers. The request packets are transmitted to the server only once. The second transmission, with which a client connects to the correct port, got from the port mapper, is not neccessary. The port mapper itself must call a server routine with a callit() function, collect call results and transmit them back to the waiting client. ------------------ ------------------------- | client | | server | | | | | | ------------ | ----------- ---------- | | | | -------------------------> | port | ---> | port- | | | | client | | ------- | 111 | <--- | mapper | | | | | | | ----------- ---------- | | | programm | | callit() | | | | | | | | ----------- ----------- | | ------------ | ------> | port | ---> | server | | | | | a | <--- | program | | | | ----------- ----------- | | | | | ------------------ ------------------------- Figure 13 Broadcasting is switched on with a single call clnt_broadcast() instaed of clnt_call(). With it clients request are tranmitted and servers answere - 63 - received. The synopsis looks like this:


        enum clnt_stat    clnt_broadcast (prognum, versnum, procnum
                                          outproc, outdata, inproc, indata,
                                          eachresult)
        u_long     prognum,
                   versnum,
                   procnum;
        xdrproc_t  outproc;
        caddr_t    outdata;
        xdrproc_t  inproc;
        caddr_t    indata;
        bool_t     (*eachresult)();

prognum, versnum and procnum are ussual identifications of a remote procedure, outdata / indata - call and result data and outproc / inproc the corresponding XDR filters. For every receipt of an answere clnt_broadcast() calls a function eachresult() with following parameters:


        bool_t     eachresult (resultp, raddr)
        caddr_t                 resultp;
        struct sockaddr_in      raddr;

This function must be supplied by a user. It has to be built, so that clnt_broadcast() can recognise with a return value (bool_t) if it has to wait for more answeres (FALSE), or not (TRUE). In the TRUE case clnt_broadcast returns back with RPC_SUCCESS. After a special number of FALSE results of eachresult() the broadcast routine repeats call requests in the interval of 4, 6, 8, 10, 12 and 14 seconds. This make sense for unrealiabel transport like UDP. After 54 seconds the routine returns back with RPC_TIMEOUT. If eachresult() is a null pointer broadcast is send, but no answeres are waited for. resultp is a pointer to result data after their XDR conversion with inproc(), raddr is the address of a corresponding server. Broadcast RPC uses as standard AUTH_UNIX security mechanism, and one cannot switch it off. An example follows, with which a client can find out the correct server in LAN, which provides the remote procedure searched for. - 64 -


        #include 
        #include 
        #include 

        static char     host[HOSTNAME_SIZE+1];

        char      *findserver ()
        {
            int   reply();

            host[0] = 0;    /* clean previous result  */
            if (clnt_broadcast (PROGNR, VERSNR, NULLPROC,
                xdr_void, NULL, XDR_void, NULL, reply) == RPC_SUCCESS)
                return (host);
            return (NULL);
        }

        reply (data, server)
        void                  *data;
        struct sockaddr_in    *server;
        {
            struct hostent   *hostentp;

            if (hostentp = gethostbyaddr (&server->sin_addr.s_addr,
                          sizeof (server->sin_addr.s_addr), AF_INET)) {
                strncpy (host, hostentp->h_name, HOSTNAME_SIZE);
                return (1);
            }
            return (0);
        }

- 65 - 5.5.2. Batching With batching we understand a situation, in which a client send a series of orders (\"in batch\") without waiting for results. Naturally the server does not transmit partial results. With it a scheme of figure 1 is invalidated: The client is waisting no time and can take possible results later on. During requests are worked out a client can even send another call to the same server. Requests are put together in a TCP buffer and transmitted to server with a reliabel transport (TCP). The last action follows ussually with one write(). The net load is minimalised this way. The flushing of a TCP buffer can be achieved with one \"remote\" call without batching. To switch on batching a client must satisfy following conditions:
  1. TCP must be chosen for transport.

  2. XDR filter for results must be NULL.

  3. timeout must be null.



     Such situations, when a client doesn't wait for results (2. & 3.)
are also without batching (without TCP) possible, e.g. if a client
send unreliable update messages to the server and is not interested
in receiving answers. Such situations we call nonblocking RPC.

Here is an example for batching:





                                - 66 -



        #include 
        #define  NULL   ((char *)0)
        #define  BSIZE  256

        main (argc, argv)
        int     argc;
        char    *argv[];
        {
            struct timeval      timeout;
            register CLIENT     *cl;
            char                buf[BSIZE], *s = buf;

            cl = clnt_create (argv[1], PROGNR, VERSNR, "tcp");
            timeout.tv_sec  = 0;
            timeout.tv_usec = 0;
            while (scanf ("%s", s) != EOF)
                clnt_call (cl, PROCNR, xdr_wrapstring, &s, NULL, NULL,
                timeout);

            /* flush the TCP-pipeline now */
            timeout.tv_sec  = 20;
            clnt_call (cl, NULLPROC, xdr_void, NULL, xdr_void, NULL,
                timeout);
            clnt_destroy (cl);
        }

- 67 - 5.5.3. Callback procedures In the opposite to our client-server model it may sometimes be neccessary for the server to play a more activ role and to send requests (callbacks) on his own to the hitherto client. A hitherto client must nevertheless register a procedure and wait for a call. An example for such a situation is a remote running program using local window system. The user input takes place local throught the window system. This triggers an action in a remote server, which transmits no passive answere, but he actively decide which window transformations must take place locally and calls them with a callback call. In the following example a callback routine must be registred. gettransient() choses the correct program number from a transient area between 0x400000000 and 0x5fffffff.


        gettransient (protocol, vers, port)
        int     protocol;
        u_long  vers;
        u_short port;
        {
            static u_long       prognum = 0x40000000;

            while (!pmap_set (prognum++, vers, protocol, port))
                continue;
            return (prognum - 1);
        }

prognum is static, to start every new search from a defined begin. pmap_set() returns false, if a registering was erronous. - 68 -


        #include 
        #include 
        #include "example.h"

        callback (req, transp)
        struct svc_req  *req;
        SVCXPRT         *transp;
        {
            if (req->rq_proc == 1)
                fprintf (stderr, "got callback\n");
            svc_sendreply (transp, xdr_void, 0);
            return (0);
        }

        main ()             /* client */
        {
            int     prognum;
            char    hostname[256];
            SVCXPRT *xprt;

            gethostbytname (host, sizeof (hostname));
            svcudp_create (RPC_ANYSOCK));
            prognum = gettransient (IPPROTO_UDP, 1, xprt->xp_port);
            svc_register (xprt, prognum, 1, callback, 0);
            callrpc (hostname, EXAMPLEPROG, EXAMPLEVERS, CALLBACK,
                xdr_int, &prognum, xdr_void, 0);
            svc_run ();
        }

The corresponding server looks like this: - 69 -


        #include 
        #include 
        #include 
        #include "example.h"

        int     pnum = -1;
        char    hostname[256];

        char    *getnewprog (pnump)
        int     *pnump;
        {
            pnum = *(int *)pnump;
            return (NULL);
        }

        docallback ()
        {
            if (pnum == -1) {
                signal (SIGALRM, docallback);
                return (0);
            }
            if (callrpc (hostname, pnum, 1, 1,
                    xdr_void, 0, xdr_void, 0) != RPC_SUCCESS)
                fprintf (stderr, "server error\n");
        }

        main ()             /* server */
        {
            gethostbytname (host, sizeof (hostname));
            registerrpc (EXAMPLEPROG, EXAMPLEVERS, CALLBACK,
                getnewprog, xdr_int, xdr_void);
            signal (SIGALRM, docallback);
            alarm (10);
            svc_run ();
        }

An example is runable in one computer only, but splitting the code into two computers makes no difficulty. The callback mechanism imply a danger of a deadlock. - 70 - 5.6. Security mechanisms RPC offers three levels of a shure identification and verification that this identification is authentic (authentication). 1. no special security mechanisms 2. UNIX specific identification 3. Diffie-Hellman/DES authentification The programming interface to security mechanisms consists of two fields in a service request struct: one for general information about algorithms used (req_cred) and one for algorithm specific data (rq_clntcred).


        /* RPC service request */
        struct svc_req {
                u_long              rq_prog;
                u_long              rq_vers;
                u_long              rq_proc;
                struct opaque_auth  rq_cred;
                caddt_t             rq_clntcred;   /* read only */
        };

with


        struct opaque_auth {
            enum_t  oa_flavor;      /* style of credentials       */
            caddr_t oa_base;        /* address of more auth stuff */
            u_int   oa_length;      /* length of oa_base field    */

The type of a second field (caddt_t) is intentionally a general pointer, because depending of the algorithm addresses of different structures are passed here: in the level 2 identifications (credential) and in level 3 verifier. Beside that, there exist a field in a CLIENT struct: cl_auth. Obviously to a complete interface definition we must specify how these fields are used. In the first security level (default) oa_flavour in rq_cred will automaticly be set to AUTH_NULL and cl_auth with authnone_create(): - 71 -


        struct svc_req  req;
        CLIENT          cl;

        req.eq_cred->oa_flavor = AUTH_NONE;
        cl.cl_auth             = authnone_create();

Nevertheless also in this level the call request will be identified according to program number, version number and procedure number, as we can see in the struct svc_req. Exact values can be seen in a chapter about RPC message protocol. In the second security level (UNIX specific identification) rq_clntcred field carries UNIX specific informations like hostname, user id, group id, etc. This provides only additional identification. There is no security, that they are not simulated. In spite of using field names with \"auth\" there is no authentication test here. Only at the third security level every message is tested, if the identification is authentic. For this purpose the method of Diffie- -Hellman is used. The encryption algorithm used is DES (Data Encryption Standard), which is well known from password encryption in UNIX. The second advantage of this security level consist in a operating system independant user identification. It works also without UNIX, like e.g. with MS-DOS, where a notion of a user does not exist at all. Additional names, so called net names are provided, which identifie every communication peer on every computer for all networks. The simplicity of this interface allows almost whatever extensions of higher security levels with other user implemented tests and encodigs. Their informations can be carried by extendable fields rq_cred- and rq_clntcred. Additional it is always possible to implement security mechanisms in higher than RPC layers. This often makes sense. - 72 - 5.6.1. UNIX specific identification The second security level, a UNIX specific identification will be switched on in client with setting of variable cl_auth after a creation of a client handle.


	CLIENT  *clnt;
	.
	clnt = clntudp_create (address, prognum, versnum, waittime, sockptr);
	clnt->cl_auth = authunix_create_default ();

The function authunix_create_default() set automaticly: 1. the oa_flavor to AUTH_UNIX and 2. rq_clntcred to a pointer to the filled struct authunix_perms:


	struct authunix_perms {
		u_long  aup_time;           /* credential creation time      */
		char    *aup_machname;      /* client's host name            */
		int     aup_uid;            /* client's UNIX effective uid   */
		int     aup_gid;            /* client's current group id     */
		u_int   aup_len;            /* element length of aup_gids    */
		int     *aup_gids;          /* array of groups user is in    */

The usage of these fields, which are passed to remote server's dispatcher can be seen in the followin example: remote services should be limited to two users with uid=104 and uid=105: - 73 -


        nuser (req, transp)
        struct svc_req  *req;
        SVCXPRT         *transp;
        {
            struct authunix_parms   *up;
            static int              n;

            if (req->rq_proc == NULLPROC) {
                /*
                ** customary no auth-check for NULLPROC
                */
                svc_send_reply (transp, xdr_void, 0);
                return;
            }
            switch (req->rq_cred.oa_flavor) {
            case AUTH_UNIX:
                up = (struct authunix_parms *) req->rq_clnt_cred;
                break;
            case AUTH_NULL:
            default:
                /*
                ** only weak error if flavor type not appropriate
                */
                svcerr_weakauth (transp);
                return;
            }


            switch (req->rq_proc) {
            case USERSPROG_NUM:
                if (up->aup_uid != 104 ||
                    up->aup_uid != 105) {
                    svcerr_systemerr (transp); /* access denied - hard error */
                    return;
                }
                /* calculate n */
                .
                .
                svc_send_reply (transp, xdr_u_long, &n);
                return;
            default:
                svcerr_noproc (transp);
                return;
            }
        }

After the deployment of these fields the user have to destroy them and free allocated memory with:


        auth_destroy (clnt->cl_auth);

- 74 - 5.6.2. Diffie-Hellman/DES authentication In a LAN UNIX specific identification gives in most cases sufficent security, because simulating of another peer won't eliminate him from the communication. This is different, if the traffic goes through a gateway: correct packets can be captured and wrong packets send instaed of them. Beside this problem of \"write\" permition, a \"read\" permition can be managed by encryption on higher protocol layers. The Diffie-Hellman method provides a security, that the packets come from a named sender (netname). The prerequisite is NIS with publickey.byname and netid.byname database. The public key must be encrypted with DES encryption kit. DES encryption kit usage is limited to USA (state 1990). The third security level is switched on with:


	CLIENT  *clnt;
	 .
	clnt = clntudp_create (address, prognum, versnum, waittime, sockptr);
	clnt->cl_auth = authdes_create (netname, timecred, syncaddr, deskeyp);

netname is a name of server process, which one can get with:


        char *netname[MAXNETNAMELEN+1];
         .
        user2netname (netname, getuid (), domain);
or
        host2netname (netname, rhostname, domain);

timecred is time in seconds, how long a sent message is valid. syncaddr is a socket address, with which the time synchronisation is maintained. It can be set to NULL. Set to the server address it causes, that a server time without synchronisation is used. deskeyp is an optional address of DES key used. A client initialised in this manner transmits in rq_clntcred field DES characteristic data of a type struct authdes_cred. In the server they can be used in the following way: - 75 -


        #include 
        #include 

        nuser (req, transp)
        struct svc_req  *req;
        SVCXPRT         *transp;
        {
            struct authdes_cred     *dc;
            int                     uid, gid, gidlen, gidlist[10];

            .
            .
            switch (req->rq_cred.oa_flavor) {
            case AUTH_DES:
                dc = (struct authdes_cred *) req->rq_clnt_cred;
                /*
                ** get user and group for this netname
                */
                if (! netname2user (dc->adc_fullname.name,
                                    &uid, &gid, &gidlen, gidlist)) {
                    fprintf (stderr, "unknown user: %s\n",
                             dc->adc_fullname.name);
                    svcerr_systemerr (transp);
                    return;
                }
                break;
            case AUTH_NULL:
            default:
                svcerr_weakauth (transp);
                return;
            }
            .
            .
        }

- 76 - 5.7. inetd daemon usage Like the most daemon processes RPC server can be started by an Internet Service Daemon inetd(8). inetd spawns processes put in /etc/inetd.conf only when their services are needed. The entrys in config file are:


	p_name/version dgram  rpc/udp wait/nowait user server args
	p_name/version stream rpc/tcp wait/nowait user server args

version can be a version range, like 1-3. Using inetd one has to consider following two points: 1. The server must pass the supervision to inetd explicitely with exit() and end with it the indefinite loop svc_run(). 2. Call parameters of routines for transport creation must be changed to:


        transp = svcudp_create (0);
        transp = svctcp_create (0, 0, 0);
        transp = svcfd_create  (0, 0, 0);
        svc_register (transp, PROGNUM, VERSION, service, 0);

- 77 - 5.8. RPC error library RPC provides a set of error functions. They are called by other RPC routines, or can be used by a RPC user. In the first case names and call parameters of these functions can be used to replace them with own written code. This is acomplished by placeing users code bevore RPC library in a loader call line. Following functions are called in server stub: They take as a parameter a pointer to a SVCXPRT struct and return back void. As an exeption svcerr_auth() needs two parameters. Most of them inform also a client: svcerr_auth () security mechanism violated (second parameter of type enum auth_stat) svcerr_decode () cannot decode transmitted parameters svcerr_noproc () there is no procedure with this number svcerr_noprog () a program with this number was not registered svcerr_progvers () a program with this version was not registered svcerr_systemerr () system error, but not a protocol error svcerr_weakaout () security mechanism not violated, but security level too small Following functions are called in client stub: void clnt_pcreateerror (str) client creation error (on stderr) char *clnt_spcreateerror (str) \" , message only with return value void clnt_perrno (stat) stderr output with a variable stat char *clnt_sperrno (stat) \" , message only with return value void clnt_perror (handle, str) message about erroneous clnt_call char *clnt_sperror (handle, str) \", message only with return value Parameters are of the following tape:


            char               *str;
            enum clnt_stat     stat;
            CLIENT             *handle;

- 78 - The variable clnt_stat (result of a RPC call) can take following values: Their meaning can be recognised from the name. Exact description one can find in manual pages.


        RPC_SUCCESS
        RPC_CANTENCODEARGS
        RPC_CANTDECODERES
        RPC_CANTSEND
        RPC_CANTRECV
        RPC_TIMEDOUT
        RPC_VERSMISMATCH        Version der ganzen RPC-Bibliothek
        RPC_AUTHERROR
        RPC_PROGUNAVAIL
        RPC_PROGVERSMISMATCH
        RPC_PROCUNAVAIL
        RPC_CANTDECODEARGS
        RPC_SYSTEMERROR
        RPC_UNKNOWNHOST
        RPC_UNKNOWNPROTO
        RPC_PMAPFAILURE
        RPC_PROGNOTREGISTERED
        RPC_FAILED

- 79 - 6. The RPC protocol 6.1. RPC messages The RPC communication between client and server occurs with passing of messages, or PDUs (Protocol Data Units). The client sends a call message and the server a reply message. These messages entail data encoded in XDR format. The message exchange between instances follows fixed rules. The set of these rules is called a RPC protocol. RPC/XDR implementation uses services of lower communication layers. In UNIX systems there are in most cases TCP,UDP/IP with their interfaces Berkeley sockets and TLI. Sticking to ISO/OSI layers model XDR can be positioned roughly in the sixth presentation layer and RPC in the seventh application layer. The following part of this chapter is mainly commented passage from RFC1050. - 82 - 6.2. The protocol of security mechanisms Regarding protocol the interface to security mechanisms consists of two fields in a service request message: one for identification (client credential) and one for authentication (client verifier). Reply message has only one field - a server verifier. The identification is not neccessary here.


        struct call_body {
            .
            .
            opaque_auth   cred;    /* Identification */
            opaque_auth   verf;    /* Verification   */
        };

with:


        enum auth_flavor {
            AUTH_NULL  = 0;
            AUTH_UNIX  = 1;
            AUTH_SHORT = 2;
            AUTH_DES   = 3;
        };
        struct opaque_auth {
            auth_flavor     flavor;
            opaque          body<400>;
        };

6.2.1. Security level one and two In the first security level client credential (cred), client verifier und server verifier (verf) are set to flavor = AUTH_NULL. In the second security level (UNIX identification) flavor is set to AUTH_UNIX. opaque body has the following structure:


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

The server verifier (verf) is set to AUTH_NULL (no authentication test). - 83 - 6.2.2. The protocol of a third security level (Diffie-Hellman/DES)


        /*
        * Der Client kann aus Effizienzgruenden ausser in der ersten
        * Message einen Spitznamen (Nickname) des Servers benutzen.
        * Er wird vom Server vorgeschlagen.
        */
        enum authdes_namekind {
            ADN_FULLNAME = 0;
            ADN_NICKNAME = 0;
        };

        typedef opaque des_block[8];
        const MAXNETNAMELEN = 255;

        /*
        * Konversationsschluessel ist verschluesselt mit DES
        * Zeitfenster ist die Zeitspanne fuer die Gueltigkeit
        * der folgenden Messages
        */
        struct authdes_fullname {
            string        name; /* Name des Clients        */
            des_block     key;                 /* Konversationsschluessel */
            unsigned int  window;              /* Zeitfenster             */
        };

        /*
        * Nickname ist meistens der Index in der Tabelle
        * des Clients, die der Server fuehrt.
        */
        union authdes_cred switch (authdes_namekind adc_namekind) {
        case ADN_FULLNAME:
            authdes_fullname adc_fullname;
        case ADN_NICKNAME:
            unsigned int     adc_nickname;
        };

        struct timestamp {
            unsigned int  seconds;   /* seit 1.1.1970 */
            unsigned int useconds;   /* Mikrosekunden */
        };

        /*
        * Die Komponenten sind verschluesselt mit DES, mode ECB
        */
        struct authdes_verf_clnt {
            timestamp     adv_timestamp;
            unsigned int  adv_winverf;    /* window verifier */
        };
        /*
        * Bei der ersten Transaktion wird stattdessen
        * folgende Struktur gebaut und als eine Einheit
        * mit DES, mode CBC verschluesselt:
        */
        struct authdes_verf_clnt_first {
            timestamp     adv_timestamp;            /* 1   DES Block */
            unsigned int  w = adc_fullname.window;  /* 1/2 DES Block */
            unsigned int  adv_winverf;              /* 1/2 DES Block */
        };

        /*
        * Die Komponenten sind verschluesselt mit DES, mode ECB
        */
        struct authdes_verf_svr {
            timestamp     adv_timeverf;
            unsigned int  adv_nickname;
        };

Before the first transaction the client choses the conversation key (with random number generator), computes common key ckey and encrypted with it key, i.e. calculates ckey(key). The encryption of x with a key k is descripted k(x), win is a time window, t are times. The flow of communication shows the following figure:


        Client                                                  Server

             Credential: ckey(key), key(win)   ---->
             Verifier:   key(t1), key(win+1)   ---->

                             <----  Verifier:   key(t1-1), nickname

             Credential: nickname              ---->
             Verifier:   key(t2)               ---->

                             <----  Verifier:   key(t2-1), nickname

                                 .
                                 .
                                 .
                                 .

Figure 14 - 85 - The method, with which a client can as the only network peer calculate the common key ckey from the chosen conversation key and the public key of a server was first described by Diffie & Hellman. It can be illustrated with the following consideration: Communication partners chose their privat keys A and B. For constant BASE and MODULUS they compute public keys:


                PA = (BASE ** A) mod MODULUS
                PB = (BASE ** B) mod MODULUS

double star means exponentiation. BASE and MODULUS are:


    	const BASE    =  3;
    	const MODULUS = "d4a0ba0250b6fd2ec626e7efd637df76c716e22d094"

For RPC setings in your system see Now only two communication partners in the whole network can caluculate common key C. It will be computed with:


                C  :=  (PB ** A) mod MODULUS

or

                C  :=  (PA ** B) mod MODULUS

This fact comes from the equation:


           (PB ** A) mod MODULUS   =   (PA ** B) mod MODULUS

which can be seen while droping modulo operation for simplicity:


                    PB     ** A    =         PA     ** B
               (BASE ** B) ** A    =    (BASE ** A) ** B
                BASE ** (B * A)    =     BASE ** (A * B)    .

- 86 - 7. The RPC generator - rpcgen 7.1. Program generation With XDR/RPC programming files must be edited in the way, which can be made automaticly. rpcgen generates C code files from a more general description held in a special language (RPCL), simmilar to C language. The following diagram shows which files are generated from which. RPCL files have the extension \".x\" . --------------- --------------- | transf.data | | XDR filter | -- | structure | --------> | | | | definitions | rpcgen | data_xdr.c | ------ | data.x | --------------- | | --------------- ------------- | | . | remote | | | . | procedure | -------| . | proc.c | | | -------------- . ------------- | | | server | . --------------- | |------> | executable | . ------> | server stub | | | cc | | . | | file_svc.c | -----| -------------- --------------- | --------------- | | | remote proc.| --- ---------- | | | interface | --------> | header | ----------- | definition | --- | file.h | ------| | file.x | | ---------- | --------------- | --------------- | ------> | client stub | -| -------------- rpcgen | file_clnt.c | | | client | --------------- |----------> | executable | ------------ | cc | | | client | | -------------- | program | ----- | client.c | ------------ Figure 15 There are two sources in RPCL for rpcgen: data.x and file.x. They can be put together in one file, which is signed on a diagram with dots. data.x contains struct definitions for parameter passing for remote procedure. From these definitions XDR filters are generated. If primitive filters are enough, we can spare this part. file.x contains interface definitions for a remote procedure. From this client and server stubs are generated. In addition an include file is generated with #define constants. - 87 - The remote procedure and a client program, which calls it must be supplied by a user. C files created this way are compiled and linked together to a client and a server program. It's often a good policy to call rpcgen in a makefile, to edit only rpcgen sources and do not change C files. Some additional details are explained in examples:


        /* msg.x: remote procedure interface definition */
        program MESSAGEPROG {
            version MESSAGEVERS {
                int PRINTMESSAGE (string) = 1;
            } = 1;
        } = 0x20000099;

The above text in a file msg.x can be treated as an interface description for rpcgen input. With it ussual data about program number, procedure version and procedure number are given. In our example: correspondingly 0x20000099, 1 und 1. Also types for input parameter (string) and return value (int) are given. The name of a remote procedure PRINTMESSAGE will be set in a C file to printmessage_1(). A version number (\"_1\") is apended. The convension to use capital letters in a .x file makes things easier. The procedure must be called in client with it's name printmessage_1(). The following client program must be supplied by a user:


        #include "msg.h"

        main ()
        {
            CLIENT  *cl;
            int     *result;
            char    *text = "hello";
            .
            .
            cl = clnt_create (servername, MESSAGEPROG, MESSAGEVERS, "tcp");
            .
            .
            result = printmessage_1 (&text, cl);
            .
            .
        }

- 88 - We refernce some points here:
  1. The client handle cl must be explicitely created (clnt_create call).

  2. printmessage_1 needs cl as a second parameter.

  3. printmessage_1 takes as a parameter only a pointer to data and it returns also a pointer to results.



     The last point is a consequence of a XDR rule, which says: filters
get as parameters not data itself, but pointers where data are stored,
or where they have to be written. The receiver cannot yet know runtime
data sizes.


     Together with a client stub clnt_msg.c generated by rpcgen and an
include file msg.h a client can be compiled an loaded. For the server
we need the remote procedure itself. It can be put like the following:



        #include "msg.h"

        int    *printmessage_1 (text)
        char   **text
        {
                static int   result;
            .
            .
            .
            return (&result);
        }

The variable result is static, because it must be valid also after the procedure printmessage_1() is run. Not the client, which can be in another computer, but the server stub needs a valid address, where the result data (int) are stored. If printmesage_1() returns no data (return (NULL)) nothing will be sent to the client. Void procedures, which return no data are in RPC declaired as void pointers: ((void *)&variable is not neccesarily NULL). Naturally the server stub sends a confirmation of a completed task, but without result data. With a C code for printmessage_1() one can create a server stub now. - 89 - 7.2. An advanced example In our advanced example parameter type and result data type has no primitive XDR filter. Therefore one must specify them in rpcgen source file:


        const   MAXLEN = 256;
        typedef string  msgtext;

        struct msglist {
            msgtext text;
            msglist *next;
        };

        /* result-typ   */
        union result switch (int errno) {
        case 0:
            msglist *list;
        default:
            void;
        }

        /* remote procedure interface defintion */
        program MESSAGEPROG {
            version MESSAGEVERS {
                result GETMESSAGE (void) = 1;
            } = 1;
        } = 0x20000098;

Notice, that there are no keywords \"struct\", nor \"union\" before type names in printmessage prototype line. rpcgen actually exchanges unions to structs and append needed typedefs. As rpcgen result we get now a file msg_xdr.c with XDR filters. The remote procedure must be changed in the following way now: - 90 -


        #include "msg.h"
        #define MSGSZ   sizeof(struct msglist)

        result    *getmessage_1 (void)
        {
            static result            r = { 0 };
            register struct msglist  *l = r->list, **lp = &l;
            register msgtext         t;

            if (!r.errno)           /* free space from previous call */
                xdr_free (xdr_msglist, l);
            while (t = readtext ()) {
                l = *lp = (struct msglist *)malloc (MSGSZ);
                l->text = t;
                lp      = &l->next;
            }
            lp = NULL;
            if (!t) r.errno = errno;
            return (&r);
        }

The nontrivial message list construction can be managed also without a variable lp. It only spares address calculations. 7.3. rpcgen options Beside a generated C code rpcgen supports five following #define constants:


                        RPC_HDR
                        RPC_XDR
                        RPC_SVC
                        RPC_CLNT
                        RPC_TBL

With the line #ifdef RPC_... im x-source one can pass C code from .x file only to server stub, or only to client. % sign on the line begin in .x file has for rpcgen the meaning of a comment. Text following this sign will be copied to output files. This feature can be used to omit editing C sources. #define variables can be switch on in rpcgen command line like in cc command line: e.g.


        rpcgen -DDEBUG proto.x

With \"-s\" option one can generate only server side. With \"-I\" generated code will support inetd (see also 5.7). With \"-K number\" one can change the default two minutes wait time before exit to inetd. For other switches see rpcgen manual page. - 91 - 7.4. Dispatcher tables The \"-T\" rpcgen option generates dispatcher tables in \".i\" file. The table entry has the following structure:


	struct rpcgen_table {
		char        *(*proc)();  /* Service Routine                       */
		xdrproc_t   xdr_arg;     /* Zeiger auf den Input des XDR-Filters  */
		unsigned    len_arg;     /* Laenge in Bytes des Input Argumentes   */
		xdrproc_t   xdr_res;     /* Zeiger auf den Output des XDR-Filters */
		unsigned    len_res;     /* Laenge in Bytes des Output Argumentes  */
	};

With such tables one can generate a server stub for several service routines. To prevent not resolved loader references entrys are initialized with RPCGEN_ACTION. With:


        #define RPCGEN_ACTION(routine)  0

or


        #define RPCGEN_ACTION(routine)  routine

one can use the same source code in client and in server. - 92 - 7.5. The language definition The RPC language (RPCL) is an extension of XDR language (see also XDR protocol 2.4) with three elements:


        program-def:
                "program" identifier "{"
                        version-def
                        version-def *
                "}" "=" constant ";"

        version-def:
                "version" identifier "{"
                        procedure-def
                        procedure-def *
                "}" "=" constant ";"

        procedure-def:
                type-specifier identifier "(" type-specifier ")"
                "=" constant ";"

and additional keywords: \"program\" and \"version\". 8.3. Questions, problems
  1. Give a list of informations neccessary for a RPC call. Is it possible to omit some points ?
  2. Why NFS provides only stateless service ? What does it mean for a NFS user ?
  3. Why do wee need XDR conversions to a network format and from a network format ? Is one conversion not enought ?
  4. Why does a server need an infinite loop ?
  5. Why do we need a port mapper ?
  6. Why is it neccessary to register a remote procedure every time a server start ?
  7. Why do more versions of a remote procedure have to coexist in one server ?
  8. Develope programs using two versions of a remote procedure.
  9. Explain why do we use XDR handle.
  10. Can we construct memory streams with record streams, or other way round ?
  11. Does it make sense a call xdr_string(&handle, &&stringdaten, ...) ?
  12. Write a routine, which returns two strings with variabel length.
  13. Which feature of XDR filter make is possible for one filter to convert in both directions ?
  14. What is a wrapper ?
  15. For RPC the network is not perfectly transparent. Which differences makes the exchange of UDP with TCP in low level RPC code ?
  16. Built in error handling in all examples in this text.
  17. Write a server program, which spawns a child for every client's request and charge the child with a service. - 98 -
  18. What happens, if in the previous problem a port won't be free at time.
  19. Why does batching use a reliable transport (TCP) ?
  20. Is it neccessary for a client not to have other tasks while waiting for the results of a RPC call ?
  21. Write a program, which print on servers console lines of text written by a user on another computer.
  22. Develope from examples a program for remote directory query rls (remote ls)
  23. Compare the protocol interface for security mechanisms, struct call_body with the programming interface, struct svc_req. Do the fields cred and verf correspond to variables rq_cred and rq_clntcred ?
  24. Is it possible with Diffie-Hellman method to communicate through a public channel with a unknown partner in the way that nobody can decode the messages ?


8.4.  Answers


    1. the name (address) of a server,
    2. the program- version- and procedure number
    3. the semantic of a service, input/output types of a remote routine
  1. Servers break down and boot doesn't put a client in an indefinite state. Services are completely available, or not available at all.
  2. It's easier to make two conversions, than to administer many conversion types as pairs (source, destination).
  3. To service more requests one after another.
  4. Without a port mapper the caller could not know at which port the server is waiting. The port number can change after servers restart.
  5. After restart server processes are possibly waiting at another ports.
  6. This can be neccessary for the developement of network software.
  7. Record streams are more general. Memory streams and standard IO streams can be constructed with record streams.
  8. One can take the address only of a real existing object, not of a number.
  9. Double character pointers are neccessary. The caller must tell for the procedure, where to put addresses of strings. With one string one could use a return value.
  10. The conversion direction is not managed in symetrical filters. - 100 -
  11. A wrapper is a routine, which prepares parameters and then calls a proper service routine.
    1. UDP packet size is limited to 8k.
    2. TCP cannot be used for broadcasting and UDP cannot be used for batching
    3. Becsause of buffering there are differences between TCP and UDP regarding timing.
    4. UDP is not reliable. RPC doesn't take the responsibility for retransmission.
  12. Call fork() in dispatcher not in the remote procedure.
  13. A port is a queue with two semaphores.
  14. Requests are sent together.
  15. No, there is the possibility of callbacks (see 5.5.3)
  16. Use ther first example in the chapter about rpcgen.
  17. No. With UNIX identification rq_cred and rq_clntcred are transmitted in two subsequent messages in a field cred. The field verf will not be used.
  18. Yes. With a public key one describes how to encode comming messages. Once encoded, only the owner of a secret key can decode them.



9.  Appendix



9.1.  Backus-Naur notation


    1. Characters: |, (, ), [, ], ,, * have special meaning.

    2. Closing symbols are quoted strings.

    3. Not closing symbols are strings of characters without special
       meaning.

    4. Alternativ entrys are divided with |.

    5. Optional entrys are embraced with [].

    6. Entrys are grouped with ().

    7. A star after an entry means 0 or more repetitions.



9.  Bibliography


  1. RFC1014 von ARPA Network Information Center enthaelt XDR Protokoll Beschreibung
  2. RFC1050, RFC1057 RPC Protocoll Specification
  3. New Directions in Cryptography, Diffie & Hellman, IEEE Transactions on Information Theory IT-22, November 1976
  4. Data Encryption Standard, National Bureau of Standards, Federal Informa- tion Processing Standards Publication 46, January 15, 1977
  5. Time Server, K. Herrenstein, RFC 738, October 1977
  6. Die Mathematik der Verschluesselungssysteme, Martin E. Hellman, (Hellmann fehlerhaft !) Spektrum der Wissenschaft, Oktober 1979, oder Scientific American, August 1979
  7. User Datagram Protocol, J.Postel, RFC 768, Information Science Institute, August 1980
  8. Transmission Control Protocol - DARPA Internet Program Protocol Specifi- cation, J.Postel, RFC 793, Information Science Institute, September 1981
  9. Courier: The Remote Procedure Call Protocol, XEROX Corporation, XSIS 038112, December 1981
  10. Distributed Deadlock Detection Algorithm, R.Obermarck, ACM Transactions on Database Systems 7:2, 187-208, 1982
  11. Implementing Remote Procedure Calls, Birrell, Andrew D & Nelson, Bruce Jay, XEROX CSL-83-7, October 1983
  12. Implementing Remote Procedure Calls, A.d.Birrell & B.J.Nelson, ACM Transactions on Computer Systems 2:1, 39-59, 1984
  13. Assigned Numbers, J.Reynolds & J.Postel, RFC 923 October 1984
  14. Sun External Data Representation Specification, B.Lyon, Sun Microsystems, Inc., Mountain View (Calif.), 1984
  15. Sun Remote Proceduire Call Specification, B.Lyon, Sun Microsystems, Inc., Mountain View (Calif.), 1984
  16. ANSI/IEEE Standard 754-1985, IEEE Standard for Binary Floating-Point Arithmetic, Institute for Electrical and Electronisc Engineers, August 1985
  17. VMTP: Versatile Message Transaction Protocol, D.Cheriton, Stanford University, Januar 1987
  18. Sun Microsystems: Network Programming, Mountain View (Clif.) Sun Microsystems, 1988
  19. Sun Microsystems: SunOs Reference Manual, Mountain View (Clif.) Sun Microsystems, 1988
  20. Sun Microsystems: Security Features Guide, Mountain View (Clif.) Sun Microsystems, 1988
  21. Internetworking with TCP/IP - Priciples, Protocols and Architectures, Douglas E. Comer, David L. Stevens, Band I und III, Eaglewood Cliffs New Jersey, Prentice Hall 1988
  22. Open Systems Standards - OSI Remote and Reliable Operations, H.Folts, IEEE Network 3:3 1989
  23. Sun Remote Procedure Call Implementation Made Transport Independant, V.Samar & C.McManis, Sun Microsystems, Mountain View (Calif.), December 1989
  24. The Design and Implementation of the 4.3BSD UNIX Operating System, S.J.Leffler et.al., Reading (Mass.), Addison-Wesley, 1990
  25. Network Programming Guide von Sun Microsystems, Revision A, March 1990
  26. UNIX Network Programming, W.R.Stevens, Prentice Hall, 1990
  27. ASN.1 Abstract Syntax Notation One, Walter Gora, Reinhard Speyerer, 1990, DATACOM Verlag
  28. Power Programming with RPC, John Bloomer, O'Reilly & Associates, Inc, 1991
  29. The Art of Distributet Applications, J.R. Corbin, Springer-Verlag, 1991
  30. Kryptologie, Albrecht Beutelspacher, Vieweg-Verlag, 1991
  31. Objektboerse - CORBA, Torsten Beyer, iX Heise Verlag 2.1993
  32. Bedeutungsschwanger: Datenbeschreibung mit Abstract Syntax Notation One (ASN 1), Hans Georg Baumguertel, iX Heise Verlag 3.1993, S.118 ff
  33. Entwicklung von Kommunkationsanwendungen mit ONC XDR Teil I und II, Peter Mandl, iX Heise Verlag 4,5.1993
  34. Kommunikation ueber alles - Tooltalk Einfuehrung, Michael Busch, iX 5.1993
9.2 Online documentation: http://www12.w3.org/History/1992/nfs_dxcern_mirror/rpc/doc/Introduction/Abstract.html http://docs.sun.com/ - 104 - 11. Dictionary
Begriff description
API Application Programmers Interface
ASN.1 Abstract Syntax Notation One
asynchron without timing
synchron with given timing e.g. time measure
Big Endian byte order in words: most significant byte first
Little Endian least significant byte first
Client/Server peers in network: requests comming from the client are served by the server
Courier first RPC version (Xerox)
DCE Distributed Computing Environment: OSF specification RPC, Nameserver and autorising services
DES Data Encryption Standard (Patent IBM)
local reachable without network
well known number information known before a proper communication
hostaddresse address of a computer in a network
IEEE Institute for Electrical and Electronic Engineers
IEEE-Float ein Format for die Kodierung der Fliesskommazahlen
IP Internet Protokoll
ISO International Standard Organisation
MTU maximum transfer unit (Datalink Layer)
NCS Network Computing System, developed at Apollo/Hewlett Packard
NDR data format of NCS
NIS Network Information System
NFS Network File System
ONC Open Network Computing: Sun's RPC
OSF Open Software Foundation
OSF/1 UNIX based operating system of OSF
OSI Open Systems Interconnection
PDU Protocol Data Unit
port mapper daemon process in server, which manages mapping of services to port numbers
POSIX Portable Operating Systems Environment Committee
program number number, which port mapper uses to identify the service
RCX Remote Command Execution
RFC Request for Comment
ROSE Remote Operation Service Element: RPC of OSI
RPC Remote Procedure Call
RPCL RPC language: extension of XDR language
rpcinfo(8c) shows programs registered at the port mapper
REX remote execution
sockets Berkeleys interface to transport layer
RPCTOOL a product of Netwise Company
transaktion more statement put together, which have to be exacuted in one step
TCP Transport Control Protocol
TLI Transport Layer Interface
UDP User Datagram Protocol
XDR eXternal Data Representation


Copyright © 2000, Z.Lisiecki