[Ns-developers] Cooperative ns-3 simulation with third party software via JSON-RPC
Sébastien Vincent
vincent at clarinet.u-strasbg.fr
Fri Jan 9 09:09:40 PST 2009
Hello all,
Last summer at the University of Strasbourg, we began a new extension
for ns-3 which allows external applications to
interact with ns-3. Initially those functionalities were needed to
interconnect a mobility simulator with ns-3. The
goal is that a mobility simulator do all mobility stuff while ns-3
realizes network (L2, L3, transport and application)
stuff.
We decided that this ns-3 extension should be as generic as possible and
that any external applications could "connect" to ns-3 via
the network. So we decided to use JSON format [1] and its remote
procedure call version JSON-RPC [2]. We chose JSON
because it is more lightweight compared to XML (which is a very verbose
format). During our preliminary stuff on the
implementation, we tried to find libraries for JSON and JSON-RPC. We
focused our choice on json-cpp [3] for encoding
and decoding format but we couldn't find a C++ implementation for
JSON-RPC. So we have contributed to the OpenSource
community by releasing on sourceforge JsonRpc-Cpp [4], a low-level
easy-to-use implementation of JSON-RPC in C++.
The functionalities of our ns-3 extension are:
- Synchronization between external applications and ns-3;
- Get / Set properties (depending on the application);
- Request ns-3 application to send a packet;
- Notify external application when a node receives a packet.
The first external application which work with our augmented ns-3
version is LEMMA (Layered Mobility Model Architecture)
[6], a mobility simulator developed by Alexander Pelov at the University
of Strasbourg. There is a simple example that
can be run. See LEMMA README. We hope that the discussions following
this announcement would lead to a rapidly growing
list of such examples.
Our JSON-RPC ns-3 implementation is available at our mercurial
repository: https://svnet.u-strasbg.fr/hg/ns-3-jsonrpc/
Note that it lacks a few features and needs some polishing.
Let's talk about a little bit of these features and design.
1) JSON-RPC in ns-3
We use libjson-cpp and libjsonrpc-cpp to have JSON and JSON-RPC support
in ns-3.
In order to receive JSON-RPC request, ns-3 scenarios have to have an
JSON-RPC server instance. We retrieve JsonRpc-Cpp
server examples and adapt them in order to (for the moment) have only
one external application connected to ns-3 at a
time. These classes are Ns3RpcServer (abstract), Ns3TcpServer and
Ns3UdpServer. Note that these servers could be configured
to encode / decode messages with netstring format [5].
2) Synchronization
This is an important part for a collaborative work between ns-3 and
external applications. First of all, special ns-3
scenarios wait a connection from the external application. At this point
we can initialize some attributes, request that
ns-3 callback the external application at a certain time or even
dynamically build simple topology. When this phase is done,
the external applications can ask ns-3 to start the simulation by
sending a "run" RPC command. The external
application could be notified upon some events, at which point the
simulation is paused.
To deblock the simulation state, the external application has to send
the "run" command.
2) RPC agent
RPC agent registers RPC methods to the native RPC server. Thus these
methods can be called remotely. We provide two agents,
SynchronisationAgent and UpdateAgent. The first is dedicated to time and
synchronization management and the second is used
to get / set attributes in ns-3 object.
a) SynchronisationAgent
SynchronisationAgent is responsible for the synchronization of the
simulation engines (ns-3 and external application). Here is the list of
its RPC methods:
- run: run (or re-run after a pause) the simulation;
- callmeat: ask ns-3 to callback the external application at specified
time (absolute time);
- callmeafter: ask ns-3 to callback the external application after
specified time (relative time);
- stopat: ask ns-3 to stop the simulation (absolute time);
- stopafter: ask ns-3 to stop the simulation (relative time).
List of indications that could be sent to external applications (other
than response to RPC methods):
- endofsimulation: notification that ns-3 simulation is done;
- icallyou: callback the external application and put ns-3 in pause.
b) UpdateAgent
UpdateAgent takes care of get / set attributes in the simulation. It
can also be used to create a simple topology so
that external applications can build topology they want without
redefining the same topology in ns-3 scenarios.
List of RPC methods of UpdateAgent:
- getposition: Get position of a node (given in parameters);
- setposition: Set position of a node (given in parameters);
- buildsimulation: Build a simulation (create nodes, ...).
"buildsimulation" command should not be called after the simulation has
been started.
3) RPC application
RPC application is special application that can notify external program
that something happened (like packet reception).
It can also react to external application request, for example "send a
packet to someone" which will trigger a packet
emission.
For the first capability, it registers an RPC method to the native RPC
server. Method names depend on the application
name and the node ID (on which the application is installed). For
example an RPC application called RpcUdpEcho installed on
node ID 3 could have the following name to receive request to send a
packet: "udpecho.3.send". When receiving a packet, RPC
application sends a JSON-RPC indication to external application and
pauses the simulation.
With these features, RPC application must have a reference to
SynchronisationAgent (to pause the simulation) and to Ns3RpcServer
(to send the notification).
Such RPC application should have following RPC methods:
- nameofapp.<nodeid>.send: Trigger a packet emit.
list of of indications that such application should declare:
- packetreceived (with some extra information like source and
destination address, packet data, ...).
Like "packetreceived" event pause the simulation, every RPC application
must have a reference to the SynchronisationAgent.
We defined an abstract RpcApplication class which contains reference to
SynchronisationAgent and Ns3RpcServer. The abstract
method to implement is ::RpcSend().
To quickly have an application to test, we choosed to take the existing
rpc-udp application. We duplicated and adapted the code to make
it work. We all know that duplicating code is bad but we hope to find a
clean solution to reuse existing apps and add our
extension methods in a generic way.
Note: for the moment data attribute of JSON-RPC indication is not set
(it is replaced by "XXX"), see next section.
4) How it works
You can use the "scenario builder server" example (launch it via ./waf
--run rpc-simulation). With this program you can
create your scenario topology and nodes remotely! For the moment just
simple topology build is available, i.e. topology
without nodes between two distinct network (like a router).
All you have to do is to send JSON-RPC request to ns-3 to build your
topology and nodes (see example of such request in
the next section). After that, you can request ns-3 to call back your
application when a certain time is reached by using
the CallMeAt requests. When this time arrives, the ns-3 simulation is
paused and you can for example get/set some parameters
(position, direction, ...).
You can use a specific RpcApplication (like RpcUdpEchoServer or
RpcUdpEchoClient) which can provide application specific RPC
calls and notification mechanisms (like notify packets reception,
trigger packets emission).
Upon simulation termination, the rpc-simulation program does not exit.
In fact you can setup a new RPC simulation immediately.
Note that the program does not allow multiple client connection at the
same time, so a RPC simulation is owned by its remote
client.
Here are a complete and commented JSON-RPC exchanges of a collaborative
simulation. You can execute this example yourself
by launching ./waf --run rpc-simulation, connect via telnet or netcat
and type every "-->" commands (one by one) to see
progression of the simulation.
##
# Example of JSON-RPC interactions between ns-3 and external application.
#
# --> message sent from external application to ns-3
# <-- message sent from ns-3 to external application
# Get the list of RPC methods
--> {"id":0, "jsonrpc":"2.0", "method":"system.describe"}
<--
{"id":0,"jsonrpc":"2.0","result":{"buildsimulation":{"description":"Build
a simulation (topology, nodes,
...).","parameters":{"application":{"description":"Install the specified
application (may depend on a particular network stack).","type":"enum :
udpecho | null"},"mobility":{"description":"Wheter or not install
mobility
model.","type":"boolean"},"network_stack":{"description":"Install the
specified network stack.","type":"enum : ipv4 |
null"},"nodeid":{"description":"Node
identifier.","type":"integer"},"number":{"description":"Number of nodes
to create.","type":"integer"},"topology":{"description":"Topology
type.","type":"enum : csma | wifi | point-to-point"}},"returns":"success
| failure"},"callmeafter":{"description":"Schedule task at relative time
that will send data to external application and block (to deblock call
Run).","parameters":{"time":{"description":"Relative time (in
milliseconds).","type":"integer"}},"returns":"success"},"callmeat":{"description":"Schedule
task at absolute time that will send data to external application and
block (to deblock call
Run).","parameters":{"time":{"description":"Absolute time (in
milliseconds).","type":"integer"}},"returns":"success"},"getposition":{"description":"Get
position of a node.","parameters":{"nodeid":{"description":"Node
identifier.","type":"integer"}},"returns":{"dir":{"description":"Direction
value (in gradients)","type":"double"},"nodeid":{"description":"Node
identifier.","type":"integer"},"x":{"description":"X
value","type":"double"},"y":{"description":"Y
value","type":"double"},"z":{"description":"Z
value","type":"double"}}},"run":{"description":"Deblock the simulator
(after a CallMeAt / CallMeAfter
call)","parameters":null,"returns":null},"setposition":{"description":"Set
position of a node.","parameters":{"dir":{"description":"Direction value
(in gradients)","type":"double"},"nodeid":{"description":"Node
identifier.","type":"integer"},"x":{"description":"X
value","type":"double"},"y":{"description":"Y
value","type":"double"},"z":{"description":"Z
value","type":"double"}},"returns":"success |
failure"},"stopafter":{"description":"Stop the simulation at relative
time.","parameters":{"time":{"description":"Relative time (in
milliseconds).","type":"integer"}},"returns":"success"},"stopat":{"description":"Stop
the simulation at absolute
time.","parameters":{"time":{"description":"Absolute time (in
milliseconds).","type":"integer"}},"returns":"success"},"system.describe":{"description":"List
the RPC methods available","parameters":null,"returns":"Object that
contains description of all methods registered"}}}
# Non-existent RPC => error
--> {"jsonrpc": "2.0", "id":0, "method":"rrrrun"}
<-- {"error":{"code":-32601,"message":"Procedure not
found"},"id":3,"jsonrpc":"2.0"}
# Build a simulation (create nodes, topology, ...)
--> {"jsonrpc":"2.0", "id":1, "method":"buildsimulation",
"params":{"topology":"csma", "mobility":true, "network_stack":"ipv4",
"application":"udpecho", "number":50}}
<-- {"id":1,"jsonrpc":"2.0","result":"success"}
# CallMeAt procedure
--> {"jsonrpc":"2.0", "id":2, "method":"callmeat", "params":{"time":1000}}
<-- {"id":2,"jsonrpc":"2.0","result":"success"}
# Run the simulation
--> {"jsonrpc":"2.0", "id":3, "method":"run"}
# Time has come, ns-3 callback external program
<-- {"jsonrpc":"2.0","result":{"time":1000,"type":"icallyou"}}
# Get position of node 1
--> {"jsonrpc":"2.0", "id":4, "method":"getposition", "params":{"nodeid":1}}
<--
{"id":4,"jsonrpc":"2.0","result":{"dir":1.0,"nodeid":1,"x":115.3533790562678,"y":91.14797921132934,"z":0.0}}
# Set position of node 2
--> {"jsonrpc":"2.0", "id":5, "method":"setposition",
"params":{"nodeid":2, "x":1.0, "y":2.0, "z":3.0, "dir":4.221}}
<-- {"id":5,"jsonrpc":"2.0","result":"success"}
# Get position of node 2
--> {"jsonrpc":"2.0", "id":6, "method":"getposition", "params":{"nodeid":2}}
<--
{"id":6,"jsonrpc":"2.0","result":{"dir":4.2210,"nodeid":2,"x":1.0,"y":2.0,"z":3.0}}
# Notification of order, node 1 has to send a packet to 10.0.0.30
--> {"jsonrpc":"2.0", "id":7, "method":"udpecho.1.send",
"params":{"remote_address":"10.0.0.30", "remote_port":9}}
<-- {"id":7,"jsonrpc":"2.0","result":"success"}
# Continue the simulation
--> {"jsonrpc":"2.0", "id":8, "method":"run"}
# A packet has been received by a node, ns-3 callback external program
<--
{"jsonrpc":"2.0","result":{"data":"xxx","local_address":"10.0.0.30","local_port":9,"remote_address":"10.0.0.2","remote_port":49153,"time":1007,"type":"packetreceived"}}
# Continue the simulation
--> {"jsonrpc":"2.0", "id":9, "method":"run"}
# The simulation is finished, ns-3 callback external program
<-- {"jsonrpc":"2.0","result":{"time":2007,"type":"endofsimulation"}}
5) Modifications in mainline code
We defined a new tag (SourceSocketAddressTag) and added it in
UdpL4Protocol::Receive() to later retrieve source address at Application
level. We also add libraries linking in ./wscript.
To conclude we would like to know if this kind of features is
interesting to the community. If so, we have several questions we
would like to discuss with you regarding the implementation of node
mobility in ns-3. We would be glad to discuss and improve our
design solution.
Any feedback is very welcome.
Regards,
--
Sebastien Vincent
Network engineer, University of Strasbourg
[1] http://tools.ietf.org/rfc/rfc4627.txt
[2] http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
[3] http://sourceforge.net/projects/jsoncpp/
[4] http://sourceforge.net/projects/jsonrpc-cpp/
[5] http://cr.yp.to/proto/netstrings.txt
[6] http://www.mobilitymodels.org/lemma/
More information about the Ns-developers
mailing list