Previous Next Contents

7   Services

Much of the functionality of PLAN comes from its services. While the initial release of PLAN offered only a smattering of services, the current version provides a rich library from which to choose. Services may be divided into two categories: the core services, which are present on all Active Routers, and additional service packages. Each service package consists of one or more service routines made available to PLAN programs.

As with all functions called from PLAN, service functions may take multiple arguments (and not a single argument which may be a tuple, as in ML); this is presented below as a comma-separated list of types. And, like all PLAN functions, services always return a value; a service will return unit in the case that the output has no meaning.

Because service routines are not guaranteed to be ubiquitious, the PLAN programmer must declare the types he expects the services to take in his PLAN program using service declarations. These may be present at the top-level and have the form svc id : argtypes -> resulttype. Types may be polymorphic; type variables are specified with a ' followed by a letter. Here are some example declarations:
svc defaultRoute : host -> host * dev
svc fragment : ('a chunk,int) -> unit chunk list
svc getRB : void -> int
The first names the defaultRoute function, which takes a single argument of type host and returns a pair of type host × dev. The second names the fragment function which takes two arguments, of types a chunk and int, returning a list of unit chunks. Finally, getRB takes no parameters and returns a value of type int.

7.1   Core Services

These are the services which the programmer may expect to be present at every active router. As such, service declarations for these services need not be supplied for typechecking purposes.

7.2   Service Packages

There are ten service packages provided with the current PLAN distribution: install, port, resident, RIP, arp, flow, dns, auth, frag, reliable, and csum. The latter three may be used to implement traditional protocol layering in PLAN. They are described below.

7.2.1   install

This package provides routines that allow service packages to be dynamically installed. It is however only available in a specialized version of PLAN, available in the distribution as an executable called plan_loader. For more on this, refer to [2].

7.2.2   port

This package provides a means for PLAN programs to interact with host applications. The package makes use of the PLAN ports, which have PLAN type port. A PLAN port is a duplex connection between an active router and a host application. The active router side of the PLAN port always receives PLAN packets, while the application side receives output in a number of formats, depending on the service used. When a PLAN packet is first injected into the network onto a particular node, it is associated with its injection PLAN port on that node. This PLAN port is referred to as the implicit PLAN port of a packet.

The module in the source distribution which implements PLAN ports is planport.ml. Here is its signature:
type plan_port
exception ClosedByPeer
exception PortOpFailed
exception PortClosed
exception NoPacket

(* client side ports *)
val open_port : int -> plan_port
val close_port : plan_port -> unit
val get_packet : plan_port -> string
val try_get_packet : plan_port -> string option
val timed_get_packet : plan_port -> float -> string
val send_packet : plan_port -> string -> unit

(* server port (only one) *)
val get_local_port : int -> plan_port
By default, PLAN ports are implemented as Unix Domain sockets -- that is, each PLAN port names a Unix Domain connection between pland (the active node daemon) and a host application. Associated with each PLAN port is a port number, and the socket opened is called ``/tmp/sware''^port-num. Alternatively, TCP may be used as the underlying connection mechansim; this is done by compiling with the Camlp4 option -DPLANPORT_TCP. In this case, the PLAN port number refers to a TCP port number.3

PLAN ports may be created implicitly or explicitly. Each Active Router has a server thread listening on a PLAN port whose number is specified when the router is started. Essentially this thread consists of a loop which calls get_local_port on the user-specified port number; this routine waits for and accepts client connections. Once a connection is established, another thread is forked which repeatedly calls get_packet to obtain client packets and then queues them for processing. The router tracks the connection in a table and creates a PLAN port to name it. Packets that arrive via this connection will have this PLAN port as their implicit PLAN port. The implicit PLAN port can be obtained within a PLAN program with the PLAN routine getImplicitPort.

A PLAN port may be created explicitly within an executing PLAN program by the routine openPort (which is a wrapper for the planport.ml routine, open_port. This routine attempts to connect to a server application listening on the specified port number, and if successful returns the PLAN port associated with the connection.

PLAN Ports are only valid on the router which created the PLAN port in the first place. In other words, if PLAN port P lives on router R, but a PLAN program attempts to send data through it while executing on router S, the operation will fail and raise an exception.

Note that PLAN port names are not stored in any explicit way on the routers. Therefore, a PLAN program that wishes to use a PLAN port must retain the value of type port which names the PLAN port. This serves to prevent one PLAN application from using the PLAN ports of another. Note that PLAN programs may use the resident data facility to store PLAN port values on the routers (see Section 7.2.3 for more on resident data).

A Unix Domain (or TCP/IP) socket connection named by a PLAN port is closed whenever a PLAN program explicitly closes it via closePort, or when the peer terminates the connection.

7.2.3   resident

Resident data is data that remains on the router after the PLAN program that created it has terminated. This feature was added to PLAN to enable programs to easily perform network traversals, such as depth first search (DFS) and breadth first search (BFS). To perform these searches, it is necessary to have some way of marking nodes so they are not revisited, as the network topology may be cyclic. Rather than providing a special-purpose service to enable node marking, we opted to provide the more general service of resident data. An example of the use of resident data to perform a DFS is given in the example program rout_tests/traceNet.plan.

Resident data is stored in a table on the router which is indexed by a string which names the data, and a session key. This key serves to differentiate data stored by different instances of the same PLAN application.

One hazard of allowing resident data is that PLAN programs may (maliciously or inadvertently) leave large chunks of data on a router, consuming memory even after the PLAN program has terminated. To combat this problem, each piece of resident data has a timeout associated with it. This timeout is set by the user when the data is created, but may not exceed 5 minutes (in other words, users may be ``nice'' and opt to release their data before the 5 minute limit). In the future, we hope to implement a more semantically-appealing way of cleaning up leftover resident data, such as a form of distributed garbage collection. We also anticipate that eventually the use of this service will require authentication.

7.2.4   RIP

This implements a distributed routing protocol based on the standard RIP protocol. The following are included :

7.2.5   arp

This package contains services needed to implement address resolution. This package is based on the standard Address Resolution Protocol (ARP).

7.2.6   flow

In some applications, it is reasonable to route packets along a route different from the one determined by the default, hop-count based routing protocol. Since hop count metrics tend to approximate latency, applications with higher bandwidth requirements might choose to accept longer latencies via longer paths in order to achieve higher bandwidth. Such an example is described in [5].

This package provides services to help set up and use such alternate routes, called ``flows''. Associated with a flow is the notion of a ``metric,'' which may be calculated differently for different applications. Each node maintains a value called the ``toll'' which might be used in determining the metric for a flow. A toll of 1 at each node, with the metric being the sum of all toll values on a route reduces to shortest path routing. Several other strategies can be explored.

A flow is identified using a PLAN key (see Section 7.2.3), which must have been generated at the destination.

7.2.7   dns

This package implements domain-name resolution. Mappings are provided by a static table, generated from a file specified at router startup time.

7.2.8   frag

This package provides a set of services used to fragment PLAN programs into smaller pieces and then reconstitute them at a remote destination. This service is needed because most physical networks have a maximum deliverable packet size, called the Maximum Transferable Unit, or MTU. Consider the following PLAN program:
svc defaultRoute : host -> host * dev

fun sendem (c:unit chunk,x:int*host) =
  (OnRemote(c,snd x,fst x,defaultRoute);x)

svc fragment : ('a chunk,int) -> unit chunk list
svc length : 'a list -> int
svc getRB : void -> int

fun fragment_deliver (dest, path_mtu, c:unit chunk) =
  let val cs = fragment(c,path_mtu)
      val l = length(ds) in
   (foldr(sendem,cs,(getRB()/l,dest)); ())
  end
The purpose of the PLAN function fragment_deliver is to deliver the chunk c to node dest and evaluate it there. The problem is that the chunk c, when marshalled and encapsulated in a PLAN packet, may be too large to send across one of the links on the path to dest. Therefore, fragment_deliver first fragments that chunk into n smaller, properly-sized chunks via the call to the fragment service. Each of these chunks consists of a call to the service reassemble, which takes four arguments: a fragment of the original chunk, the sequence number of the fragment (numbered 0 to n-1), a boolean set to true for the last fragment and false otherwise, and finally a value of type key which is uniquely generated and set to the same for each fragment. Next, each fragment is sent via the call to sendem from foldr. When each fragment arrives at the destination, it calls reassemble, which stores each original chunk fragment until all have arrived, at which point it reassembles the original chunk and evaluates it.

Notes: The following services are included:

7.2.9   reliable

This package provides a set of services used in conjunction with the RetransOnRemote primitive to ensure reliable delivery of PLAN data. Consider the following PLAN program:
svc generateKey : void -> key
svc sendAck : ('a chunk,key,int) -> 'a chunk
svc defaultRoute : host -> host * dev
svc fragment : ('a chunk,int) -> unit chunk list
svc getMTU : dev -> int
svc fragment_overhead : int

fun reliable_deliver(dest, c:unit chunk) =
  let val seq = generateKey()
      val d = sendAck(c,seq,10)
      val p = defaultRoute(dest)
      val ds = fragment(d,getMTU(snd p)-fragment_overhead) in
    RetransOnRemote(ds,seq,3,dest,20,defaultRoute)
  end
The purpose of the PLAN function reliable_deliver is to reliably deliver the chunk c to node dest and evaluate it there. The program first generates a key to associate with the packets to send; this can be thought of as sequence number which identifies the chunk. More specifically, this key will be used in a call to reliableAck on the source to halt further retransmissions of the packets containing c. Next, the call to sendAck creates a new chunk d which ``encapsulates'' c with some additional code to be executed on the destination (the encapsulating code will be shown below); part of the action of d is to send a packet to the source to invoke reliableAck. Next, d is fragmented into a list of chunks ds using the fragmentation service (see Section 7.2.8), using the outgoing link MTU for size information. Finally, this list of chunks is sent using RetransOnRemote. The list of chunks will be resent at most 3 times (third argument), and each chunk will receive 20 resource bound (fifth argument).

Suppose that the list ds consists of two chunks. When the second chunk arrives at the destination, the two chunks will be reconstituted into the chunk d which is then evaluated; the code for d (created by sendAck), is as follows:
svc reliableAck : key -> unit
svc defaultRoute : host -> host * dev
svc eval : 'a chunk -> 'a
svc arrived : key -> bool
fun send_ack(c:unit chunk,source,seq,rb) =
  (OnRemote(|reliableAck|(seq),
            source,rb,defaultRoute);
   if arrived(seq) then
     ()
   else
     (eval(c);()))
Here, the arguments c, seq, and 10 which were given to sendAck are bound here to c, seq, and rb, respectively (the source variable is filled in automatically by sendAck). First, a packet is sent back to the source to invoke reliableAck; this tells the sender to quit resending. Next, the arrived service is invoked with the given key to make sure this isn't a duplicate packet (and if not, it registers the key). If not, the original chunk c is finally evaluated (note that the returned value is discarded to satisfy the typechecker).

Some things to note: The following services are included:

7.2.10   csum

This package provides a encapsulation-based checksum service. For more details on workings of encapsulation, see Sections 7.2.9 and 7.2.8. The following services are included:

7.2.11   authproto

Each of these services is described in more detail in the PLAN Security Guide.

7.2.12   security

Each of these services is described in more detail in the PLAN Security Guide.

7.3   Services available at injection

Not all services are available in the arguments to the initial invocation at the point where a PLAN packet is injected into a PLAN network. The services available at the injection point include all the core services, plus the services in the dns package and the following new service:

7.4   Services available at the REPL level

Finally, the plan tool (explained in the tutorial) also limits the availability of services. The ones available are all the core services, plus the services in the csum, frag and reliable modules.


Previous Next Contents