R P C - Remote Procedure Calls
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:
- Network Computing System (NCS), developed at Apollo/Hewlett
Packard with the network wide data format, Network Data Representa-
tion (NDR),
- RPCTOOL of Netwise Company
- 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:
- Different CPUs use different number formats like big-, little-endian,
1's, or 2's complement, IEEE-floats, or BCD (binary coded decimal).
- The same type can have in different computer systems diffentent
number of bytes: e.g. 16, or 32 bit integer.
- 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.
- There are certain alignmets of data characteristic for different
processors. They lead to address shifts, or holes in struct frameworks.
- 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:
- Functions, which generate and manipulate data streams (see 2.1)
- 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:
- standard i/o, that is with help of FILEs from ,
- direct with a memory buffers (memory streams),
- 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:
- discr, with the runtime information about which arm will be
transformed first,
- unp, a pointer to the union data,
- arms, a vector of struct xdr_discrim with data for every arm.
This vector must end with NULL.
- 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:
- One needs to omit UDP packet size limit and aply TCP instead,
or other features of communication channel should be influenced.
- One would like RPC functions to allocate memory for the
transfered data. (The way was shown in a chapter about XDR.)
- 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:
- TCP must be chosen for transport.
- XDR filter for results must be NULL.
- 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:
- The client handle cl must be explicitely created
(clnt_create call).
- printmessage_1 needs cl as a second parameter.
- 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
- Give a list of informations neccessary for a RPC call. Is it possible
to omit some points ?
- Why NFS provides only stateless service ? What does it mean for a
NFS user ?
- Why do wee need XDR conversions to a network format and from a network
format ? Is one conversion not enought ?
- Why does a server need an infinite loop ?
- Why do we need a port mapper ?
- Why is it neccessary to register a remote procedure every time a
server start ?
- Why do more versions of a remote procedure have to coexist in one
server ?
- Develope programs using two versions of a remote procedure.
- Explain why do we use XDR handle.
- Can we construct memory streams with record streams, or other way
round ?
- Does it make sense a call xdr_string(&handle, &&stringdaten, ...) ?
- Write a routine, which returns two strings with variabel length.
- Which feature of XDR filter make is possible for one filter to
convert in both directions ?
- What is a wrapper ?
- For RPC the network is not perfectly transparent. Which differences
makes the exchange of UDP with TCP in low level RPC code ?
- Built in error handling in all examples in this text.
- Write a server program, which spawns a child for every client's
request and charge the child with a service.
- 98 -
- What happens, if in the previous problem a port won't be free at time.
- Why does batching use a reliable transport (TCP) ?
- Is it neccessary for a client not to have other tasks while
waiting for the results of a RPC call ?
- Write a program, which print on servers console lines of text
written by a user on another computer.
- Develope from examples a program for remote directory query
rls (remote ls)
- 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 ?
- 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
-
- the name (address) of a server,
- the program- version- and procedure number
- the semantic of a service, input/output types of a remote routine
- Servers break down and boot doesn't put a client in an indefinite state.
Services are completely available, or not available at all.
- It's easier to make two conversions, than to administer many
conversion types as pairs (source, destination).
- To service more requests one after another.
- Without a port mapper the caller could not know at which port the
server is waiting. The port number can change after servers restart.
- After restart server processes are possibly waiting at another ports.
- This can be neccessary for the developement of network software.
- Record streams are more general. Memory streams and standard IO
streams can be constructed with record streams.
- One can take the address only of a real existing object, not of a
number.
- 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.
- The conversion direction is not managed in symetrical filters.
- 100 -
- A wrapper is a routine, which prepares parameters and then calls
a proper service routine.
-
- UDP packet size is limited to 8k.
- TCP cannot be used for broadcasting and UDP cannot be used for
batching
- Becsause of buffering there are differences between TCP and UDP
regarding timing.
- UDP is not reliable. RPC doesn't take the responsibility for
retransmission.
- Call fork() in dispatcher not in the remote procedure.
- A port is a queue with two semaphores.
- Requests are sent together.
- No, there is the possibility of callbacks (see 5.5.3)
- Use ther first example in the chapter about rpcgen.
- 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.
- 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
- RFC1014 von ARPA Network Information Center enthaelt XDR Protokoll
Beschreibung
- RFC1050, RFC1057 RPC Protocoll Specification
- New Directions in Cryptography, Diffie & Hellman, IEEE Transactions on
Information Theory IT-22, November 1976
- Data Encryption Standard, National Bureau of Standards, Federal Informa-
tion Processing Standards Publication 46, January 15, 1977
- Time Server, K. Herrenstein, RFC 738, October 1977
- Die Mathematik der Verschluesselungssysteme, Martin E. Hellman,
(Hellmann fehlerhaft !) Spektrum der Wissenschaft, Oktober 1979, oder
Scientific American, August 1979
- User Datagram Protocol, J.Postel, RFC 768, Information Science Institute,
August 1980
- Transmission Control Protocol - DARPA Internet Program Protocol Specifi-
cation, J.Postel, RFC 793, Information Science Institute, September 1981
- Courier: The Remote Procedure Call Protocol, XEROX Corporation, XSIS
038112, December 1981
- Distributed Deadlock Detection Algorithm, R.Obermarck, ACM Transactions
on Database Systems 7:2, 187-208, 1982
- Implementing Remote Procedure Calls, Birrell, Andrew D & Nelson, Bruce
Jay, XEROX CSL-83-7, October 1983
- Implementing Remote Procedure Calls, A.d.Birrell & B.J.Nelson, ACM
Transactions on Computer Systems 2:1, 39-59, 1984
- Assigned Numbers, J.Reynolds & J.Postel, RFC 923 October 1984
- Sun External Data Representation Specification, B.Lyon, Sun Microsystems,
Inc., Mountain View (Calif.), 1984
- Sun Remote Proceduire Call Specification, B.Lyon, Sun Microsystems,
Inc., Mountain View (Calif.), 1984
- ANSI/IEEE Standard 754-1985, IEEE Standard for Binary Floating-Point
Arithmetic, Institute for Electrical and Electronisc Engineers, August
1985
- VMTP: Versatile Message Transaction Protocol, D.Cheriton, Stanford
University, Januar 1987
- Sun Microsystems: Network Programming, Mountain View (Clif.) Sun
Microsystems, 1988
- Sun Microsystems: SunOs Reference Manual, Mountain View (Clif.) Sun
Microsystems, 1988
- Sun Microsystems: Security Features Guide, Mountain View (Clif.) Sun
Microsystems, 1988
- 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
- Open Systems Standards - OSI Remote and Reliable Operations, H.Folts,
IEEE Network 3:3 1989
- Sun Remote Procedure Call Implementation Made Transport Independant,
V.Samar & C.McManis, Sun Microsystems, Mountain View (Calif.),
December 1989
- The Design and Implementation of the 4.3BSD UNIX Operating System,
S.J.Leffler et.al., Reading (Mass.), Addison-Wesley, 1990
- Network Programming Guide von Sun Microsystems, Revision A, March 1990
- UNIX Network Programming, W.R.Stevens, Prentice Hall, 1990
- ASN.1 Abstract Syntax Notation One, Walter Gora, Reinhard Speyerer,
1990, DATACOM Verlag
- Power Programming with RPC, John Bloomer, O'Reilly & Associates, Inc,
1991
- The Art of Distributet Applications, J.R. Corbin, Springer-Verlag, 1991
- Kryptologie, Albrecht Beutelspacher, Vieweg-Verlag, 1991
- Objektboerse - CORBA, Torsten Beyer, iX Heise Verlag 2.1993
- Bedeutungsschwanger: Datenbeschreibung mit Abstract Syntax Notation One
(ASN 1), Hans Georg Baumguertel, iX Heise Verlag 3.1993, S.118 ff
- Entwicklung von Kommunkationsanwendungen mit ONC XDR Teil I und II,
Peter Mandl, iX Heise Verlag 4,5.1993
- 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
|
|