Introduction

Documentation Home  Reference  Class Hierarchy  Index  Help


This introduction is addressed to programmers who wish to write their own distributed applications in Java using the DistinctÒ ONC RPC/XDR Toolkit for JavaÔ. The sections in this programming guide provide an overview of the following topics.

This document assumes that you are already familiar with Java, the basic concepts of RPC client/server computing and ONC RPC and XDR in particular. It also assumes a familiarity with the basics of the C binding of ONC RPC. If you wish to write Java ONC RPC applications and are not yet familiar with these concepts you should do further reading before using the toolkit.

You may need to refer to the reference part of this manual to understand parts of the examples given in this programmer’s guide.

This toolkit may be used to help you out in several different scenarios. Here are some:

In all these scenarios Distinct ONC RPC/XDR for Java will certainly help you to write portable, high-quality Java code in a minimum amount of time.

This guide discusses how to write client server applications using the Distinct ONC RPC/XDR Toolkit for Java. It first explains how to write an ONC RPC client and then illustrates how a server is built in Java. It then explains how to pass RPCs through Internet firewalls and then describes some other concepts the understanding of which are fundamental to using this toolkit.

What comes with the Distinct ONC RPC/XDR for Java Toolkit?

The Distinct ONC RPC/XDR Toolkit for Java is a set of tools and libraries that enables you to write pure Java ONC RPC clients and servers. It consists of:

Building a Client with Distinct ONC RPC/XDR for Java

In this section we will illustrate how to use this toolkit to build a Java client. For our example we will use the scenario of an application programmer who wants to write a Java-based front end for an existing ONC RPC service. This front end is an RPC client that may be a stand-alone application or a Java Applet for use inside a Web browser. Both types of clients can be built using the Distinct ONC RPC/XDR Toolkit for Java.

When writing an ONC RPC application the first thing to do is to write an XDR interface definition file. This file describes the data types and the signature of your interface. Distinct ONC RPC/XDR for Java understands the XDR language as described in RFC 1832 (XDR: External Data Representation Standard). By common programming convention XDR files have a .x extension.

The XDR Interface Definition Language File

For the remainder of this guide we will work with the XDR interface definition language (IDL) file demo.x as shown below. It defines a simple service that returns a sequence of consecutive lines from a text:

%/* ***************************************
% * Distinct ONC RPC/XDR for Java Example *
% ****************************************/

struct request {
    int from;
    int to;
};

struct result {
    int number;
    string line<>;
    struct result *next;
};

typedef result *res_list;

program DEMO_SERVER {
    version DEMO_VERSION {
        res_list get_line(request) = 1;
    } = 1;
} = 0x20000023;
 

This IDL file has 5 sections:

  1. A comment with leading '%' characters.
  2. A struct request type definition.
  3. A struct result type definition.
  4. A typedef res_list type definition.
  5. A program definition for the server interface DEMO_SERVER. The program definition contains only one procedure (function get_line()). It is a limitation of ONC RPC that each procedure may have only one input parameter and one return value. But as these parameters may be of arbitrary complex type (e.g. a structure), the restriction is only a syntactical one. In our example the input type of get_line() (named request) contains two integers specifying a range of lines (named from and to). The output type res_list is a pointer to a linked list of result structures. Each element of this list describes one line of the result (line number and content string). In this example it is important to define the typedef result because with Jrpcgen a procedure can have only plain type names in its signature (e.g. result *get_line(request) is not allowed). But since you can use any level of typedefs this is not really a semantic restriction.

The Server Implementation in C

Although the functionality of the service might be obvious at this point, we show here its implementation in C to clarify any questions you may have. In this simple version the lines of text that can be returned are hard coded and there is no error processing done (e.g. if a client requests a non-existing range of lines).

#include <stdio.h>
#include <rpc/rpc.h>
#include "demo.h"

char *text[] = {
    "This is the first line of text",
    "This is the second line of text",
    "This is the third line.",
    "This is the 4th line",
    "This is the 5th line",
    "This is the 6th line.",
    "This is the 7th line.",
    "This is the 8th line."
};

res_list *get_line_1(request *req)
{
    static res_list rl;
    result *p, *q;
    result **n;
    int c;

    /* free any dynamically allocated memory from previous requests */
    p = rl;
    while (p != NULL) {
        q = p;
        p = p->next;
        free(q);
    }

    /* the handling of the new request starts here*/
    rl = NULL;

    /* n points to the pointer where the next list element is inserted */
    n = &rl;
    for (c = req->from; c < req->to; c++) {
        /* allocate the next element of the list */
        *n = malloc(sizeof(result));

        /* assign the line number */
        (*n)->number = c;

        /* assign the line (string) */
        (*n)->line = text[c];
        n = &((*n)->next);
        *n = NULL;
    }
    return &rl;
}

To build the server binary with just the demo.x IDL file and the demo_server.c file above, run the standard ONC RPC rpcgen (for C) on demo.x and compile and link the generated files demo_svc.c, demo_xdr.c, demo.h, and demo_server.c into one native binary.

Once you have built the above server and have it up and running with the ONC RPC portmapper, you can proceed to build a Java stand-alone client that calls this server.

The Java Client

The following pages will take you through the steps needed to build the Java client. The first thing to do is to run the Jrpcgen provided with the Distinct ONC RPC/XDR for Java Toolkit on the XDR file. The Jrpcgen application translates XDR files into Java stubs. For a complete description of the Jrpcgen command please refer to the reference portion of this manual.

You run Jrpcgen on demo.x by typing:

>java Jrpcgen demo.x

Jrpcgen V4.0, Copyright 1997 - 2001 by Distinct Corporation
demo.x
writing: request.java
writing: result.java
writing: res_list.java
writing: demo.java

The execution of jrpcgen creates four Java classes, and subsequently four files, that implement the client stub for calling the demo service described: There is one file per type definition for request.java, result.java, and res_list.java, and one main stub file called demo.java.

The Main Stub

We will now take a look at the demo.java main stub file that has been generated.

/* **************************************
* Distinct ONC RPC/XDR for Java Example *
****************************************/

import com.distinct.rpc.*;
import java.io.IOException;
import java.net.InetAddress;

/**
*This class was automatically generated by Jrpcgen from the RPC/XDR file "demo.x" <br>.
* It defines the client interface to a server implementing the "demo" interface.
*/

public class demo extends JRPCClient {

/** Program ID of the interface*/
public static final int DEMO_SERVER = 0x20000023;

/** Interface Version */
public static final int DEMO_VERSION = 1;

/**
* Creates and connects an RPC client for a server that implements the "demo" interface.
* Calls the remote Portmapper in order to get the server.port
* @param host The host on which the server resides.
* @param stream true for a TCP connection, false for UDP.
* @exception RPCError When the calls fail for any reason.
*/

public demo(InetAddress host, boolean stream) throws RPCError {
    super(host, DEMO_SERVER, DEMO_VERSION, stream);
}

/**
* Creates and connects an RPC client for a server that implements the "demo" interface.
* The client is connected to a server with a known port. (No interaction with a portmapper)
* @param host The host on which the server resides.
* @param port The port on which the server listens.
* @param stream True for a TCP connection, false for UDP.
* @exception RPCError When the calls fail for any reason.
*/

public demo(InetAddress host, int port, boolean stream) throws RPCError {
    super(host, DEMO_SERVER, DEMO_VERSION, port, stream);
}

/**
* Creates an RPC client for a server that implements the "demo" interface.
* It initializes it with an externally created protocol client object.
* @param protocol The protocol object that implements the client connection.
*/

public demo(ClientGeneric protocol) {
    super(protocol);
}

public static final int get_line = 1;

/**
* Stub method that invokes the server function "get_line" (version 1).
*/

public res_list get_line_1(request arg) throws RPCError, IOException {
    res_list retval = new res_list();
    GetClient().Call(get_line, arg, retval);
    return retval;
}
};

An instance of this demo stub class represents a client to the demo server. It maintains the complete necessary connection status. Without going into all details we can easily see that this class consists of a number of constants, three constructors, and a public method get_line_1() that has the same signature as get_line() in the XDR file. You can also see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the Java source generated, without the leading % characters. This is because Jrpcgen generates comments that are understood by the javadoc auto documentation tool.

Like all main client-stub classes in Distinct ONC RPC/XDR for Java, the demo class is derived from the base class JRPCClient. This class provides the framework for calling RPC servers using the various possible protocols. Similar to the C binding of ONC RPC, the constants are the program's number and version as well as an ordinal number for each procedure. Of the three constructors the first (public demo(InetAddress host, boolean stream) throws RPCError) is probably the most commonly used one. We will use it in our application example. Finally, the get_line_1() method is what we have to invoke when we want to interact with the server. The _1 extension results from the fact that this is the implementation of version 1 of this RPC program. As with the C binding the version number from the XDR file is always appended with an underscore in front of it.

The Type Definition Files

We now take a look into the result.java file, which is one of the type definition files that were generated.

/****************************************
* Distinct ONC RPC/XDR for Java Example *
****************************************/

import com.distinct.rpc.*;

/**
* This class was automatically generated by Jrpcgen from the RPC/XDR file demo.x.<br>
*result: was struct
*/

public class result implements XDRType {
    public int number;
    public String line;
    public result next;

/**
* Encodes an object of class result in compliance to RFC 1832 (XDR).
* @param xdrs The XDR output stream.
*/

public void xdr_encode(XDRStream xdrs)
{
    xdrs.xdr_encode_int(number);
    xdrs.xdr_encode_string(line);
    xdrs.xdr_encode_boolean(next != null);
    if (next != null)
        next.xdr_encode(xdrs);
    return;
}

/**
* Decodes an object of class result in compliance to RFC 1832 (XDR).
* @param xdrs The XDR input stream.
* @exception RPCError When the call fails for any reason.
*/

public void xdr_decode(XDRStream xdrs) throws RPCError
{
    number = xdrs.xdr_decode_int();
    line = xdrs.xdr_decode_string();
    next = null;
    if (xdrs.xdr_decode_boolean()) {
        next = new result();
        next.xdr_decode(xdrs);
    }
    return;
}
};

Once again you can see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the Java file generated. This happens for all files generated. The data members of the result class mirror the C like definition of the struct result in the XDR file.

The two encoding/decoding methods use the methods of the XDRStream class to encode/decode the data members. The linked-list structure of the result type is mapped in an XDR-typical manner to a recursive invocation guided by a boolean value that indicates a NULL pointer value (and thus, the end of the list).

The XDR to Java Type Mapping

In most cases the mapping of XDR types to Java types and classes is quite obvious. For an overview of the complete mapping please have a look at the following table:
 

XDR Type

Java Type

(unsigned) int

int

(unsigned) long

int

(unsigned) short

short

(unsigned) char

char

(unsigned) hyper

long

float

float

double

double

bool

boolean

string

String

opaque

byte array

fixed length array

Java array

variable length array

Java array

optional data (pointer like *x)

Reference to an object of class x. If x is a basic type a special wrapper class XDRx that implements XDRType is used instead.

enum x

Class x implements XDRType with member variable int value and one constant int per enum constant.

struct x

Class x implements XDRType with member variables for each struct member.

union x

Class x implements XDRType with member variables for each union member including discriminant. No overlaying of members is supported (neither for type conversion nor for saving space).

typedef x y

Class x implements XDRType with member variable value of the redefined type y.

The most notable difficulty in the Java mapping is the handling of typedefs. As Java has no notion of type name aliasing, the only viable solution that preserves the introduced names is the definition of a new class. In the Java mapping of typedef this new class has always only one data member, called value, that is a variable of the redefined type (for an illustration please refer to the generated file res_list.java).

Like all classes that are created by Jrpcgen from type definitions, result implements the XDRType interface. This interface defines the two methods xdr_encode() and xdr_decode() that are used by the stub implementation for marshalling and unmarshalling the parameter into and from an XDRStream object. Jrpcgen generates the necessary code to implement these methods for the given XDR type.

Typically, if you are using the RPC functionality of the Distinct ONC RPC/XDR for Java Toolkit, you will not have to bother with these methods. As with the C binding, the stub implementation hides all details of their usage. You just have to construct objects of the defined input parameter class and pass them to the stub method (in our case e.g. of class request to demo.get_line()) and you will receive a newly created reply object from the stub (in our case of class res_list). However, if you use the Distinct ONC RPC/XDR Toolkit just for encoding/decoding XDR data (e.g. from a file) you will have to call these methods directly in conjunction with an appropriate XDRStream object.

The other two type definition classes, request.java and res_list.java, are very similar and have exactly the same structure.

The Client Application

Now we are ready to write our first Distinct ONC RPC/XDR for Java application. Below we show the listing of a simple stand-alone Java application that uses the generated stubs to make some invocations of the demo server:

import java.net.*;

public class testapp {
    static public void main(String[] args) {
        demo client;    // this is the RPC client object
        request req = new request();
        try {
            client = new demo(
                InetAddress.getByName(args[0]),    // the host
                true);    // use TCP

            for (int i = 0; i < 8; i++)
                for (int j = i + 1; j < 8; j++) {
                    req.from = i;
                    req.to = j;
                    System.out.println("from " + i + " to " + j);

                    res_list rl = client.get_line_1(req);
                    result res = rl.value;
                    while (res != null) {
                        System.out.println(res.number+":"+res.line);
                        res = res.next;
                    }
                }
                client.CloseClient();
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
        ...
    }
}

Here we take a step by step look at this application:

  1. First we import java.net.* since we need at least the InetAddress class to talk to the server.
  2. In this example we do not import the generated demo stub class, as we assume it is in the same package (in this case the default package). Of course, if this is not the case, we have to import it as well.
  3. We then define a very simple class testapp with just the static main() method. main() interprets the first string parameter from the command line as the name of the host where the demo server is running.
  4. Now we declare an object of class demo. This is the client object we use every time we call the demo server.
  5. Next we open an exception context. Most of the Distinct ONC RPC/XDR for Java methods throw either a RPCError or an IOException. Please note that every time this occurs, the status of the used client object is undefined. If this happens, you should reestablish the connection with a new client object if you want to call the server again. In our sample program we just catch every exception and print the message on the screen.
  6. Now, we establish the connection to the server by calling the simplest constructor of the demo client class. Parameters are the hostname of the server (from the command line) and a boolean that indicates whether we want to set up a TCP or a UDP connection. A value of true means TCP.
  7. If this call does not throw an exception we are connected. Depending on whether the addressed host really has a ONC RPC portmapper and a demo server running, it may take several seconds before the success or the failure of this call are reported.
  8. Once we are connected we call the server several times in a loop. We initialize the input object req, invoke the server with res_list rl = client.get_line_1(req); and process the output object rl. Please note the handling of the value member of rl that represents the original type (* result) of the typedef res_list. The invocation of client.get_line_1() may again result in an asynchronous exception of type RPCError or IOException if any problem occurs in the RPC runtime (typically, when the server becomes unreachable).
  9. Finally, we close the connection to the server and free all resources with client.CloseClient();.

Here we are. This is our first RPC client written in Java. Now we only have to compile the stubs and our testapp.java with the Java compiler.

If you have compiled the files and you run the program, the output will look like this:

>java testapp localhost

from 0 to 1
0:This is the first line of text
from 0 to 2
0:This is the first line of text
1:This is the second line of text
from 0 to 3
0:This is the first line of text
1:This is the second line of text
2:This is the third line.
from 0 to 4
0:This is the first line of text
1:This is the second line of text
2:This is the third line.
3:This is the 4th line
from 0 to 5
...

If the output looks like this:

>java testapp localhost

Server not available.

This means that our program has caught an exception because we have most probably forgotten to start the demo server (in this case on localhost, our local machine).

Inside an Applet

In principle, you can use the Distinct ONC RPC/XDR for Java classes and stubs generated by Jrpcgen inside an applet as described for the stand-alone program above. But, please note that by default all Java-enabled browsers do not allow network connections to hosts other than the one an applet was loaded from. In order to connect your applet to arbitrary RPC servers (or to run an RPC server) inside a browser you have to undertake additional action to work around this restriction:

Unfortunately both methods are currently highly browser specific. Please look into your browser's manuals for further information or have a look at the security features of Netscape Communicator, HotJava, or Microsoft Internet Explorer 4.0. In particular you should look for the terms applet security, signed code (JAR files, CAB files), and certificates.

If both methods are not viable for your application or you have to connect to an RPC server that is protected by a firewall you might want to use the HTTP tunneling protocol that is described below in the section "Connecting Through a Firewall". With this unique feature of the Distinct ONC RPC/XDR for Java Toolkit you can easily separate the Web server and other application or database servers. You should be aware that you could expect performance degradation compared to the direct connection as described earlier.

Building a Server with Distinct ONC RPC/XDR for Java

The Java Server

This section illustrates how to build a Java server. To build a Java server, again we have to run Jrpcgen from the Distinct ONC RPC/XDR Toolkit for Java on the XDR file. This time you run Jrpcgen on demo.x with the -S option, telling it that it has to create the server stub as well. Thus, you type:

>java Jrpcgen -S demo.x

Jrpcgen V4.0, Copyright 1997 - 2001 by Distinct Corporation
demo.x
writing: request.java
writing: result.java
writing: res_list.java
writing: demo.java
writing: demoServer.java

The execution of jrpcgen creates the same four Java classes as before (even if we do not need the client part demo.java this time) plus the additional server stub class named demoServer.java.

The Main Stub

We will now take a look into the demoServer.java main stub file that was generated.

/* **************************************
* Distinct ONC RPC/XDR for Java Example *
****************************************/

import com.distinct.rpc.*;

/**
* <b>DEMO_SERVER Server Stub</b>
* This class was automatically generated by Jrpcgen from the RPC/XDR file "demo.x".<br>
* demo: was interface DEMO_SERVER
*/

abstract public class demoServer extends JRPCServer {

/** Interface Program */
public static final int DEMO_SERVER = 0x20000023;

/** Interface Version */
public static final int DEMO_VERSION = 1;

public static final int get_line = 1;

/**
* Constructor that creates the RPC server that implements the "demo" interface.
* @exception RPCError When the calls fail for any reason.
*/

public demoServer() throws RPCError {
    super(DEMO_SERVER, DEMO_VERSION, true);
    UnregisterServer();
    RegisterServer(StartUDP(0), false);
    RegisterServer(StartTCP(0), true);
}

/**
* Constructor that creates the RPC server that implements the "demo" interface at a fixed port.
* @param port The port on which the server listens.
* @exception RPCError When the calls fails for any reason.
*/

public demoServer(int port) throws RPCError {
    super(DEMO_SERVER, DEMO_VERSION, true);
    UnregisterServer();
    RegisterServer(StartUDP(port), false);
    RegisterServer(StartTCP(port), true);
}

/**
* Dispatcher Routine that interprets the call requests.
* @param proc The index of the requested function.
* @param xin read the input parameter from this XDR stream.
* @param xout write the return parameter to this XDR stream.
* @return true, if the function with the index proc can be served, false otherwise.
*/

synchronized public boolean DoCall(int proc, XDRStream xin, XDRStream xout) {
    try {
        switch (proc) {
            case 0: {
                return true;
            }

            case get_line: {
                request arg = new request();
                arg.xdr_decode(xin);
                res_list ret = get_line_1(arg);
                ret.xdr_encode(xout);
                return true;
            }

            default:
                return false;
         }
    }
    catch (Exception e) {
        return false;
    }
}

/*
* Overwrite these abstract server methods for implementing the server's functionality.
*/

abstract public res_list get_line_1(request arg);
};

This stub class demoServer represents the frame for a server that implements the DEMO_SERVER interface described in demo.java. Similar to the client stub we see, this class consists of a number of constants, two constructors, and an abstract method get_line_1() that has the same signature as get_line() in the XDR file. You can also see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the generated Java source without the leading % characters. This is because Jrpcgen generates comments that are understood by the javadoc auto documentation tool.

Like all server stub classes in Distinct ONC RPC/XDR for Java, the class demoServer is derived from the base class JRPCServer. This class provides the framework for implementing RPC servers using the various possible protocols. Again, the constants are the program's number and version and ordinal numbers for the remote procedure. The method DoCall() is the core of the server. It implements dispatching of the remotely called procedures depending on the given ordinal number and it calls marshalling and unmarshalling of the parameters. Finally, the abstract get_line_1() method is the procedure we have to implement for our demo server by overriding it in a derived class.

Synchronization

In Distinct ONC RPC/XDR for Java DoCall() and, consequently, all implemented remote procedures can be called concurrently by different server threads. In the Distinct ONC RPC/XDR for Java implementation of an ONC RPC server, each TCP client is served by its own thread. One additional thread handles all UDP requests.

The Server Application

Now we are ready to write our first RPC server in Java. Below is the listing of a simple Java application that uses the generated stub to implement the demo server:

import com.distinct.rpc.*;

public class MyServer extends demoServer {

static String text[] = {
    "This is the first line of text",
    "This is the second line of text",
    "This is the third line.",
    "This is the 4th line",
    "This is the 5th line",
    "This is the 6th line.",
    "This is the 7th line.",
    "This is the 8th line."
};

static public void main(String[] args) {
    try {
        new MyServer ();
    }
    catch (RPCError e) {
        System.out.println(e.getMessage());
    }
}

public MyServer() throws RPCError {
    super();
}

// here we override and implement the real remote procedure
public res_list get_line_1(request arg) {
    res_list rl = new res_list();
    result n = new result();

    for (int c = arg.from; c < arg.to; c++) {
       // allocate the next element in the list
        n.next = new result();
        n = n.next;
        if (c == arg.from)
            rl.value = n;

        // assign the line number
        n.number = c;

        // assign the line (string)
        n.line = text[c];
    }
    return rl;
}
}

This Java code implements the same simple server functionality as the C version shown earlier. It defines a new class named MyServer that is derived from the demoServer class and that finally implements the remote procedure get_line_1() in a straightforward manner. Compared to the implementation in C there are two differences worth noting:

If you now compile this program and run it as a stand alone Java program together with the RPC client as shown above you will see that our first Java ONC RPC server behaves exactly like the initial C version.

The ONC Portmapper

If you are not using fixed server ports (see next section) you will need an instance of the ONC Portmapper running on each machine that hosts ONC RPC servers. It is required to bind RPC clients to servers. Basically, the Portmapper is a name server. Server ports of ONC RPC servers are chosen randomly. It is the responsibility of the Portmapper to tell the clients the port number of a server with a given program/version number. The Portmapper itself is a RPC server that listens on a well-known port (111) for these requests. Typically, the Portmapper is running as a demon in the background and it is usually provided by the standard ONC RPC infrastructure that is part of e.g. any network connected UNIX host (e.g. NFS relies on ONC RPC). However, if you are starting your RPC server on a machine that does not provide this infrastructure by default (e.g. all flavors of MS Windows) you have to make sure that a Portmapper is present to enable clients to connect to your new server.

The Distinct ONC RPC/XDR for Java Toolkit provides its own implementation of the Portmapper. This pure Java implementation of the Portmapper implements the portmap interface version 2. As the Portmapper uses a fixed port number there can be only one Portmapper instance per node. Therefore, any server that uses the Jrpcgen generated stub will first check, whether the system already provides a Portmapper, and if not, it will start its own Portmapper. Please note, that all ONC RPC servers that are subsequently started on that node will now also use this instance of the Portmapper. This means, if you shut down the Distinct ONC RPC/XDR for Java server that started the Portmapper, you will also loose the ability to bind new clients to the other, still running servers, as the portmapper that stores the binding information is gone. Connections that were established before the Portmapper went down are not affected, as the binding is established only once during the connection setup. You can avoid this by starting the portmapper manually before starting the server.

To check whether a Portmapper is really running you can use the standard ONC RPC rpcinfo tool with the -p <hostname> option. This should enumerate the servers registered with the portmapper of this host. If this tool reports an error or does not show the servers you were expecting, there is obviously a problem with the Portmapper.

If you have to disable the Portmapper autostart feature of Jrpcgen generated server stubs for some reason, you currently have to edit the xxxServer class. Simply change the third parameter of the invocation super(DEMO_SERVER, DEMO_VERSION, true); in the constructors to false and the server will not try to start a Portmapper.

Connecting Through Firewalls

Most RPC applications are designed for Intranets, and although RPC can be made to run over the Internet, the original specifications were not really designed for this. On the Internet connections over arbitrary ports (like those established by the RPC protocol) are often blocked by firewalls. Please ask your firewall administrator for the details of your network security.

Using Fixed Server Ports

Distinct ONC RPC/XDR for Java allows you to build servers and clients with well-known (fixed) server ports. Depending on the configuration of your firewalls this may be sufficient for you to access certain servers over the Internet.

The fact that RPC server ports are not known in advance makes it difficult to configure a firewall accordingly. When this turns out to be a problem it might help to be able to configure the used ports statically. Distinct ONC RPC/XDR for Java supports this additional feature for Java clients and servers. If you use fixed ports you will not need to run the portmapper application.

To connect a client using a fixed port you only have to make a small change to the initialization sequence. You have to use another constructor of the client class. In our example the demo class can be created using the following code:

client = new demo(
    InetAddress.getByName(args[0]), // the host
    Integer.parseInt(args[1]), // the port
    true); // use TCP

Now, the second command line parameter can be used to specify the known server port of the demo server.

Using HTTP Tunneling

Distinct ONC RPC/XDR for Java comes with a powerful mechanism that wraps RPC requests completely with standard HTTP transactions. The server side CGI script (RPCGw) together with a special protocol client (ClientHTTP) allows for transparent tunneling of the RPC protocol in HTTP. This mechanism allows executing arbitrary RPCs on the Internet where firewalls are blocking all protocols, such as RPC, that do not use fixed ports. It also allows you to call RPC servers that are not located on the same host as the web server but elsewhere in your LAN, from a Java applet. With this feature you can easily separate the Web server and other application or database servers on different machines.

The RPCGw CGI Script

RPCGw translates and executes RPCs that are encapsulated in HTTP requests. It runs as an add on to the Web server at the server site. It uses the standard CGI interface for communication with the Web server. RPCGw receives the RPC request encoded in an HTTP POST request from the ClientHTTP object on its standard input stream via the CGI interface. It decodes the request parameter (host, program number, version number, procedure number, and protocol type (TCP/UDP)), optionally checks an access control list, creates an RPC connection, and forwards the request to the RPC server via the standard RPC protocol. It then waits for the reply and sends the return parameters encapsulated in an HTTP reply (of mime type application/octet stream) via the Web server back to the original requestor. RPCGw is generic in the sense that it does not know about the server interfaces and the parameter types. It simply forwards all requests (if permitted by the access control list). This means, there is no need to adapt RPCGw to a specific RPC server interface; it will work for any interface.

RPCGw is provided in source code as it is a C program and as it should run on any common Web server architecture, it is hard to provide all the different binary formats. With the fairly portable sources and the simple Makefile you should be able to install it on any server architecture.

To install RPCGw you should first compile it for your server architecture. Please take a look into the Makefile provided. You have to customize the compile options for selecting the security policy (none or access control list) and the logging features (on/off and log filename). For compilation of RPCGw you will need the standard ONC RPC library (for all types of UNIX usually included in the standard distribution, for Win32 please check out the Distinct ONC RPC/XDR Toolkit for C developers). After compilation, copy the executable into the script directory of your Web server.

Access Control

In order to preserve the integrity and the privacy of your LAN RPCGw uses a sophisticated access control list that allows you to specify exactly which procedure of which RPC program/version number can be executed on which host via which protocol. In addition it allows the logging of all RPC activities to a local file. If you have chosen to work with the access control list (highly recommended) use a text editor to create a file named rpcgw.cfg in the same directory. This file holds the access control list. It will be read by RPCGw each time it is invoked. Each line in this file has five entries delimited by white spaces (e.g. tabs): hostname (symbolic name), program(decimal number), version(decimal number), protocol(tcp or udp), procedure(decimal number). Each entry is exactly one string or the * wildcard character. Thus, a simple rpcgw.cfg file might look like this:

island 77 1 tcp 1
* 79 1 * *

Line one of the above example for an access control list allows procedure number 1 in an RPC service with program number 77, version number 1 on host named island via the TCP protocol to be called. Line two allows any procedure in an RPC service with program number 79, version number 1 on any host via TCP or UDP to be called. In general, '*' matches any parameter provided by the remote client. Other regular expressions are not recognized. In order not to harm the security of your local network you should be highly restrictive when creating the RPCGw access control list and allow only access to those services, hosts and procedures that are really required by the application and that cannot be abused by an unauthorized person. In general, please contact your local security manager before installing RPCGw on a Web server with high security priorities or that is visible to the outside world.

Building a Client that uses RPCGw

To create an RPC client object in a Java applet that calls the RPC server via HTTP and RPCGw use the following sequence in your code. First you create a ClientHTTP protocol object that knows how to access the server via RPCGw, and then you initialize the RPC client object (of class demo in our example) with this protocol object.

The following code sequence is nearly identical to the previous client example. Only the client initialization is changed (the gray part) to use a ClientHTTP protocol object instead of standard TCP or UDP. The sample code given here assumes that the code is part of an applet (otherwise getDocumentBase().getHost() would be undefined) but this is not required. You may use HTTP tunneling also in stand-alone applications. Also, please note, that the URL of the RPCGw script (in our case /cgi-bin/RPCGw) may depend on the configuration of your Web server.
    ...
    demo client; // this is the RPC client object
    request req = new request();

    try {
        // Construct the HTTP protocol object
        ClientHTTP Protocol = new ClientHTTP(getDocumentBase().getHost(), // my Web-server
            "/cgi-bin/rpcgw", // the URL of the RPCGw program
            "island", // host of the RPC server
            demo.DEMO_SERVER, // the program number
            demo.DEMO_VERSION, // the version number
            true); // use TCP ("false" for UDP)

         // Initialize the RPC client object
        client = new demo(Protocol);
        for (int i = 0; i < 8; i++)
            for (int j = i + 1; j < 8; j++) {
                req.from = i;
                req.to = j;
                System.out.println("from " + i + " to " + j);
                res_list rl = client.get_line_1(req);
                result res = rl.value;
                while (res != null) {
                    System.out.println(res.number+":"+res.line);
                    res = res.next;
                }
            }
        client.CloseClient();
    }
    catch (Exception e) {
        System.out.println(e.getMessage());
    }
    ...

How To

This section discusses how to do certain things in the Distinct ONC RPC/XDR for Java environment. It includes information on how to authenticate, how to broadcast RPCs, how to run RPCs in batch mode and discusses server data flow, using XDR streams and error and timeout handling.

Authentication

By default, Distinct ONC RPC/XDR for Java uses NULL authentication. However, this default can be changed at the client object by using setCredential() and setVerifier(). In the other direction the returned verifier from the server can be obtained after each call with getReturnedVerifier(). Please note that Distinct ONC RPC/XDR for Java (like the C binding) provides the data structures for transferring authentication data and the methods for setting, retrieving and checking this data. It does not provide protection against forged authentication data. Any security and access control features have to be implemented by your application. Below is a code sample that creates a Unix authentication (sometimes also called System authentication) and sets it as the new credential of this client. From now on each request will be sent with this authentication.

import java.net.*;

public class testapp {
    static public void main(String[] args) {
        demo client; // this is the RPC client object
        request req = new request();
        try {
            client = new demo(
                InetAddress.getByName(args[0]), true);

                // a new Unix authentication is created (UID = GID = 0)
                client.GetClient().setCredential(new AuthUnix(
                    (int)(System.currentTimeMillis()/1000),
                    "myhostname", 0, 0, new int[0]);

                 for (int i = 0; i < 8; i++)
                    for (int j = i + 1; j < 8; j++) {
                        req.from = i;
                        req.to = j;
                        System.out.println("from " + i + " to " + j);
                        res_list rl = client.get_line_1(req);
                        result res = rl.value;
                        while (res != null) {
                        System.out.println(res.number+":"+res.line);
                        res = res.next;
                    }

                // Here it is assumed that the server returned an authentication
                // (typically of type short) that is now used for any subsequent calls
                client.GetClient().setCredential(client.GetClient().GetReturnedVerifier());
            }
        }
        ...
    }
}

The class com.distinct.rpc.JRPCServer provides the DoAuth() method for handling authentication at the server side. Override this method if you want your server to check authentication (the default is no check). DoAuth() receives the credential and verifier provided by the client as well as the call identifier and returns the verifier that should be sent back with the reply to the calling client (simply return null for NULL authentication). It throws a com.distinct.rpc.RPCAuthError if it wants to signal an unsuccessful authentication. The following code fragment illustrates how DoAuth() can be used in the server code.

    ...
    synchronized public Auth DoAuth(int proc, Auth cred, Auth verf)throws RPCError {
        if (cred.getFlavor() != JRPC.kAUTH_UNIX)
            throw new RPCAuthError(JRPC.kAUTH_TOOWEAK);

        // convert it to the correct type
        AuthUnix unix_cred = new AuthUnix(cred);
        if (unix_cred.getUid() != 0)
            throw new RPCAuthError(JRPC.kAUTH_BADCRED);

        // use the same authenticator as returned verifier
        // usually you would use some shortcut here
        return unix_cred;
    }
    ...

This example implementation of DoAuth() checks for each call to see if it has a credential of type AuthUnix and if so if the user id is 0. If either condition is not fulfilled it throws a com.distinct.rpc.RPCAuthError exception in order to terminate any further processing of this call. The remote procedure itself is not called at all in this case.

If you need to use other types of authentication (e.g Kerberos style) you can implement it easily by deriving a new authentication class from the com.distinct.rpc.Auth base type.

Secure RPC

As mentioned in the Authentication section above, new authentication types can be derived from the basic com.distinct.rpc.Auth type. Distinct ONC RPC/XDR for Java provides a standard implementation for advanced security with authentication of type AUTH_DES. The AUTH_DES authentication relies on Sun's Java Cryptography Extensions (JCE) version 1.2. Please refer to the vendors instructions on how to install and configure the cryptography package for your location and environment.

Please note that using the Sun JCE 1.2 package requires the Java Runtime Environment (JRE) version 1.2 (also known as Java 2). In all other cases, Distinct ONC RPC/XDR for Java can be used with any JRE version 1.1 or higher.

Secure RPC Client

The following example illustrates the required steps to set up a connection to a Secure RPC server from a Java program.

import java.net.*;

public class testapp {
    static public void main(String[] args) {
        demo client; // this is the RPC client object
        try {
            // Get the key database
            PublicKeyDB DB = new PublicKeyDB(new File("/etc/publickey"));

            // Authenticate yourself by entering the password
            if (!DB.doLogin(false))
                System.exit(-1);

            // Create the authenticator
            AuthDes a = new AuthDes(DB.getNetname(),
                DB.getPassword(),
                "unix.wied@home",
                DB,
                1800);

            // Construct the Secure RPC protocol object
            ClientSecureRPC Protocol = new ClientSecureRPC(
                InetAddress.getByName("localhost"),
                demo.DEMO_SERVER,
                demo.DEMO_VERSION,
                true, // use TCP
                a); // the authenticator

            // Initialize the RPC client object
            client  = new demo(Protocol);
            request req = new request();
            for (int i = 0; i 8; i++)
                for (int j = i + 1; j 8; j++) {
                    req.from = i;
                    req.to = j;
                    System.out.println("from " + i + " to " + j);
                    res_list rl = client.get_line_1(req);
                    result res = rl.value;
                    while (res != null) {
                        System.out.println(res.number+":"+res.line);
                        res = res.next;
                    }
                }
        }
        catch (Exception e) { // an RPC error happened !
            printStr(e.getMessage());
    }
}

This is our standard example again. Initialization of the client happens in five steps.

  1. We create a “PublicKeyDB” object. This object provides the interface to a key database. It offers methods to retrieve the public and private keys for a known netname. A “PublicKeyDB” object can be constructed from a file, an URL , or a NIS (Yellow Pages) server (see below). The content of the file or the URL must have the format of the UNIX "publickey" file (one entry per line, line format "usernetname public_key:encrypted_private_key"), e.g.:

        nobody 7f4bbd1ce1f69a593d5c39758773a33d0f4db3baad7e3c53:c4f50767351d6168f65300 e81cb6c934ea5034f0859ffbd165445ada89de8beb
        dummy 436a4d3789b6e9781c70b1eeac40a51f81a9fed65bfec2ab:4c37bc3d9605ea2be26ea92 39bd54f9ad81ffaa6b447dc987b5b226cb8e3ecd7


Usually each “publickey” database contains at least one entry for the pseudo-user “nobody” (something like a guest account) that always requires just an empty password to login.

  1. We have to authenticate ourselves by entering our netname and our password. Of course, we can hardcode this information in the program, but in that case most of the security enhancements will be lost. Therefore, typically, you will enter this information interactively. In order to simplify this procedure Java RPC offers a convenience dialog, that asks for a netname and a password. It is opened by invoking the “doLogin()” method of the “PublicKeyDB” object. The dialog is part of the “PublicKeyDB” class, as it offers the information of this database about the possible netnames in a choice. Here you can simply select your name without the need to type this long string each time. If you don’t like this feature (it helps somewhat in guessing correct netname/password combinations and sometime the list of possible passwords can be rather long) you can disable it by setting the boolean parameter of “doLogin()” to true and you will get a simple text-field for entering the netname. Once you have entered the login information here you can retrieve it by calling the “getNetname()” and “getPassword()” methods of “PublicKeyDB”. Please note, that you cannot exit a Java program that once has used one class from the “java.awt” package (like one that uses this login dialog) by simply returning from the main method. It will not die as there are some system threads still alive that were used to server the events from the graphical user interface. Please use “System.exit()” to terminate these programs reliably. Of course you are also free to choose (and implement) any other command line interaction or graphical dialog to get the login information.
  2. Now we have to create the authenticator (construction of an “AuthDes” object). The parameters for this constructor are: the netname of the user and his/her password (as discussed in the previous section), the netname of the authority that runs the server we want to connect to (usually known and hardcoded in the application), the “PublicKeyDB” from step 1, and a protocol specific time window that specifies after which time a credential expires. This constructor will fail and throw an “RPCAuthError” if the entered password is incorrect or the key belonging the given netname cannot be found.
  3. Now we can construct the protocol object. This object handles the Secure RPC protocol, which requires some additional actions compared to the standard RPC protocol. The constructor of this object of class “ClientSecureRPC” takes five parameters: the Internet address, the number and the version of the server, a boolean specifying whether TCP or UDP should be used, as well as the authenticator object.
  4. Finally, we can create the client object (in our case of class “demo”) by initializing it with the just created protocol object. From here on we can use this client-object without even knowing, that underlying protocol uses AUTH_DES. If anything in the authentication protocol fails, the client object will throw an “RPCAuthError” (a subclass of “RPCError”).

Secure RPC Server

In the server handling of secure RPC is even simpler. You just have to create a “AuthDesServerDB” in quite the same way as we did it in the previous section with the “AuthDes” authenticator and to invoke it in the server’s “doAuth()” method to check whether the incoming authenticators are valid. This is illustrated in the sample code below.

    ...
    public MyServer() throws RPCError, IOException {
        super();
        PublicKeyDB DB = new PublicKeyDB(new File("/etc/publickey"));
        if (!DB.doLogin(false))
            System.exit(-1);
        adb = new AuthDesServerDB(DB.getNetname(), DB.getPassword(), DB);
    }
    ...

    synchronized public Auth DoAuth(int proc, Auth cred, Auth verf) throws RPCAuthError {
        AuthDes a = adb.checkAuth(cred, verf);
        SetClientData(a);