LEPTON   Documentation Download Videos About

How to interface an OppNet system with LEPTON

Overview

LEPTON's prime function is to simulate the mobility of nodes in an opportunistic networking (OppNet) system, while conducting transmissions between these nodes based on their relative positions.

Being an emulator rather than a simulator, LEPTON is meant to drive the communication between full-featured instances of an OppNet system, each instance determining the behavior of one node during the simulation. In the remainder of this document we will use the term System Node (SN) to refer to an instance of the OppNet system. A SN is typically composed of application code combined with communication middleware, which itself requires to run on top of a standard protocol stack.

General architecture

The whole emulation system in LEPTON is composed of System Nodes (SNs), and the network emulator. The emulator is itself structured in two main parts: a simulation engine and a software hub. The simulation engine determines if any pair of nodes should be considered as neighbors at any specific time of the experiment. The software hub plays approximately the same role as a switched hub in an Ethernet LAN: it forwards every message it receives from a SN to its destination, and to that destination only. Yet this forwarding only takes place if the simulation engine confirms that the source SN is currently a neighbor to the destination SN.

lepton-and-nodes.svg

Communication between SNs can rely on a variety of protocols, but many OppNet systems implement a neighbor discovery mechanism based on UDP multicast or broadcast, while the gossiping (i.e., the exchange of control and data messages) between neighbor nodes is based either on unicast UDP or TCP sessions.

LEPTON implements a generic hub that can process UDP-based beaconing traffic, and UDP or TCP-based gossiping traffic. Yet since all OppNet systems do not use the same message formats, a specific adapter must be developed for each type of OppNet system, so the hub can process the messages received from SNs. The development of this adapter is detailed below.

Interfacing an OppNet system with LEPTON: a step by step procedure

Interfacing an existing OppNet system with LEPTON requires:

  1. Making sure that all SNs can run in emulation mode.
  2. Making sure that traffic received by the hub can be processed.
  3. Making sure that LEPTON can start and stop SNs during an experiment.

Step 1: preparing the OppNet system for emulation

This step may involve modifying the OppNet system, so it can operate in emulation mode.

Step 2: developing an adapter for the hub

Developing a dedicated adapter capable of processing transmissions the hub will receive from SNs may require a little more work, but since most existing systems rely on UDP and TCP for their transmissions, Basically, LEPTON's software development kit defines two interfaces for which implementations must be provided:

Assume an OppNet system called XXX must be interfaced with LEPTON. This requires developing two Java classes (XXX_Adapter and XXX_Beacon), each class implementing one of the two above-mentioned interfaces.

Interface OppNetAdapter simply defines three methods, which will be invoked by the hub whenever needed:

Beacon processing

The methods defined in interface Beacon are meant to decode the payload of a UDP packet as a beacon and, conversely, to encode a beacon into a UDP packet. The encoding format of course depends on the kind of OppNet system considered, and that is why a specific implementation of interface Beacon is required for each system. Whatever the encoding format, once a beacon has been decoded the identity of its source should be known, as well as additional information such as the type(s) of gossiping protocol(s) admitted by this source (i.e., TCP, UDP, or both), and about the port number(s) the source is listening to. Further information, such as the beacon type or its destination (if specified), may be available as well. Accessors defined in Beacon should be implemented in XXX_Beacon so as to return such elements of information. Accessor getGossipingAddress(Beacon.GossipingType) should return an InetSocketAddress, indicating the address and port number the source is listening to, for the specified gossiping type (GossipingType is an enum type which takes only two values: TCP and UDP). If the address of the source is not specified in the beacon, then the address field of the InetSocketAddress should be a wildcard value (i.e., 0.0.0.0).

Example: assume a beacon received from a source contains indication that this source is listening to UDP port #8628 (with no indication of the address). Then once this beacon has been decoded an invocation of getGossipingAddress(GossipingType.UDP) should return InetSocketAddress(0.0.0.0, 8628). If the beacon specifies that the address of the source is 192.168.1.10, then the result should be InetSocketAddress(192.168.1.10, 8628). The same goes for TCP of course.

The core of the hub (defined in class casa.lepton.hub.Hub) is designed to rely on XXX_Beacon whenever a beacon received from an XXX instance must be processed. Basically, upon receiving a UDP packet that is assumed to contain a beacon, the hub invokes XXX_Adapter.getBeacon(...), and thus tries to decode the packet's payload as an XXX_Beacon. If this operation succeeds, the hub then processes the beacon, based on the information obtained via its accessors. Upon receiving a beacon from a SN for the first time, and depending of the results returned by getGossipingAddress() for both TCP and UDP, the hub may create a TCP proxy, a UDP proxy, or both types of proxies, depending on information found in the beacon. In any case, the beacon is modified with method setGossipingAddress(...), so the address and port numbers embedded in the beacon are now those of the proxies instead of those of the sender node. The beacon is then re-encoded as a UDP packet, and forwarded to any SN that is considered as a neighbor of the sender SN (information about neighborhood is provided by LEPTON's simulation engine). Instead of addressing gossiping traffic directly to the beacon's source, any SN will thus address this traffic to one of the proxies maintained by the hub. This proxy will in turn forward the traffic to the actual destination SN, but only after checking that the source and destination are considered as neighbors by the simulation engine at that specific time.

TCP and/or UDP proxies for gossiping traffic

As mentioned above, when a beacon indicates that a SN accepts gossiping traffic by UDP or TCP (or both), the hub creates proxy sockets accordingly, and makes sure (by modifying the beacon) that subsequent traffic will actually be sent to these proxy sockets rather than directly to the SN. A UDP proxy is thus created in the hub for any SN that accepts UDP traffic, and a TCP proxy is likewise created for any SN that accepts TCP traffic. Note that all proxies are created on dynamically assigned ports, so the port number a SN is listening to and the port number the corresponding proxy is listening to may be different.

Remember that a dedicated TCP_Proxy or UDP_Proxy (or both) is created by the hub for each SN from which it receives beacons. Thus when one of these proxies receives traffic from a source SN, there is no ambiguity about the identity of the destination SN: this is the SN this proxy is associated with. Yet there is a question about the identity of the source SN, and this identity must be discovered in order to decide if traffic forwarding should be allowed between both SNs, or if any gossiping data received from the source SN should be discarded. Taking this decision is the responsability of the simulation engine, but before solliciting the simulation engine the hub must determine the identity of the source SN.

Fortunately, in most OppNet systems the identity of the SN that is the source of a UDP packet or TCP session is usually embedded as meta-information in the data contained in the datagram or stream. If this is not the case, then the code of the OppNet system considered should be modified accordingly, as defined in Step 1.

Methods getSource(DatagramPacket) and getStreamHeader(InputStream) (both defined in interface OppNetAdapter) are meant to examine the payload of a UDP datagram, or the first bytes received from a TCP session, and extract the identify of the source of these data.

Consider again system XXX, assuming this system must be interfaced with LEPTON. If this system uses TCP for the gossiping between SNs, then method getStreamHeader(InputStream) must be implemented in class XXX_Adapter (which implements interface OppNetAdapter). This method should parse the first bytes received from the InputStream, until the identity of the source of this stream has been discovered. This method should then return a StreamHeader object (StreamHeader is a sub-class defined in OppNetAdapter), containing both the identity of the source and the sequence of bytes that has been read from the stream in order to discover this identity. When LEPTON is running, whenever a SN opens a TCP session with one of the TCP proxies maintained by the hub, this proxy invokes method getStreamHeader(), as defined in class XXX_Adapter, in order to determine the identity of the source of this session. It will then check with LEPTON's simulation engine that this source is allowed to connect to the destination (which is the SN the proxy has been created for), and if so the sequence of bytes will then be forwarded to that destination, as well as the rest of the traffic exchanged between both nodes within that TCP session.

If system XXX uses UDP for the gossiping between SNs, then method getSource(DatagramPacket) must be implemented in class XXX_Adapter. This method should parse a UDP datagram received from a SN, and return the identity of its source.

The UDP and/or TCP proxies maintained by the hub will automatically invoke XXX_Adapter.getStreamHeader(InputStream) or XXX_Adapter.getSource(DatagramPacket), in order to determine the identity of the source of any kind of traffic they receive.

If system XXX does not rely on both kinds of gossiping protocols, there is no need to implement both methods. One of the methods may be defined as doing nothing, since no proxy will ever invoke it.

Step 3: define the bash functions required to start and stop a SN

As explained above, LEPTON must be able to start and stop SNs while running an experiment. This is especially important when the scenario considered involves a population of SNs that varies over time, for in that case all SNs cannot be created at the same time at the beginning of the experiment.

The procedures for creating and terminating a SN depend on the OppNet system considered. These procedures must therefore be implemented as functions in a bash file, so they can be called by LEPTON whenever needed to either create or terminate a SN. Likewise, variables must be defined in that bash file so as to identify the adapter that will be required for the hub to process transmissions received from SNs.

In order to interface system XXX with LEPTON, a bash file named XXX_adapter.sh should be defined, based on the following model:

#!/bin/bash

oppnet_adapter_classname=TO_BE_DEFINED
oppnet_adapter_classpath=TO_BE_DEFINED
node_process_tag=TO_BE_DEFINED

#---------------------------------------------------------------------
#  Start an emulated node
#
#  Available variables
#  node_id        : id of the node
#  node_start_time: time when the node should start (EPOCH in ms, optional)
#  node_end_time  : time when the node should stop, (EPOCH in ms, optional)
#  node_seed      : seed value for the node's random generator
#  lepton_host    : name or address of the host that runs LEPTON
#  lepton_hub_port: TCP port number LEPTON's hub is listening to
#---------------------------------------------------------------------
start_node() {

# TO BE IMPLEMENTED (USING THE ABOVE-MENTIONED VARIABLES)
}

#---------------------------------------------------------------------
#  Stop an emulated node
#
#  node_id: id of the node
#---------------------------------------------------------------------
stop_node() {

# TO BE IMPLEMENTED
}

#---------------------------------------------------------------------
#  Exec command on an emulated node
#
#  node_id: the id of the node
#  $*     : command (with arguments) to be executed on that node
#---------------------------------------------------------------------
#exec_on_node() {
#
# TO BE IMPLEMENTED IF NEEDED (WHAT COMMANDS CAN BE EXECUTED ON A NODE
# DEPENDS ON THE OPPNET SYSTEM CONSIDERED)
#
#}

where:

Note that among the above-mentioned parameters, variables node_start_time, node_end_time and node_seed are meant to maximize the repeatability of experiments conducted with LEPTON. Indeed, when many SNs must be created at the same time during an experiment, the creation of these many SNs (i.e., loading code, opening files, etc.) can yield a transient congestion of the host platform. This phenomenon can notably be observed in scenarios where hundreds of SNs must be created simultaneously at the beginning of an experiment. In order to the reason why LEPTON can be configured so as to create SNs slightly in advance (typically, a few seconds), while passing each SN a parameter node_start_time that specifies when this SN should actually start running. The code of the SN is therefore expected to prepare for running (for example, open files and sockets), but to wait until the specified start time before doing anything else. The exact time for a node to terminate gracefully can likewise be specified by node_end_time. Finally, each SN can be passed a seed value to initialize any random generator it may use internally.

Using our new adapter with LEPTON

Once a new adapter XXX_adapter.sh has been developed, running LEPTON with this adapter should be straightforward:

bin/lepton.sh start oppnet_adapter=<full/path/to/XXX_Adapter.sh>

When started with option oppnet_adapter, LEPTON instantiates a hub, together with the specified adapter, based on the variables defined in XXX_Adapter.sh. Besides, if LEPTON is started with configuration parameters that imply that it must create SNs at runtime according to a set scenario, these SNs are created --and later deleted-- using the functions defined in XXX_Adapter.sh.