[Ns-developers] Cooperative ns-3 simulation with third party software via JSON-RPC

Alexander Pelov apelov at gmail.com
Tue Jan 20 11:23:07 PST 2009

Hello Mathieu, everyone,

I have been working with Sébastien on this project, and I was really hoping
that this announcement would produce the kind of feedback you have provided
in your mail.
Constructive discussion is indeed what is needed for this initiative to
succeed and become something useful for a maximal number of people.
Thank you!

(More inline)

On Tue, Jan 20, 2009 at 1:59 PM, Mathieu Lacage <
mathieu.lacage at sophia.inria.fr> wrote:

> On Wed, 2009-01-14 at 17:24 +0100, Sébastien Vincent wrote:
> ok,
> Coding style: you already went through a previous review for the ipv6
> stuff so, I was hoping I would not have to point you to:
> http://www.nsnam.org/codingstyle.html
> One recurring example:
>      if(m_remoteAddressLength == 0)
>      {
>        memcpy(&m_remoteAddress, &addr, sizeof(struct
> sockaddr_storage));
>        m_remoteAddressLength = addrlen;
>      }
> should be:
>      if(m_remoteAddressLength == 0)
>        {
>          memcpy(&m_remoteAddress, &addr, sizeof(struct
> sockaddr_storage));
>          m_remoteAddressLength = addrlen;
>         }
> > > New files:
> > >
> > > README.JsonRpc
> > >
> > > src/rpc-agent/ns-3-rpc-server.cc,h
> a) doxygen should include a brief summary of the purpose of this class:
> if I am not mistaken, "receive json requests from a socket and forward
> them to the json parser/handler".
> b) can't you merge the udp and tcp implementations: from a high-level
> review, these look almost exactly similar
> c) ns3::rpc::Ns3RpcServer is fully redundant: ns3::rpc::Server or
> ns3::RpcServer would be more appropriate.
> d) although this is not specified in the coding style, all the ns-3
> codebase does this. The following:
> namespace ns3
> {
>  namespace rpc
>  {
>    Ns3RpcServer::Ns3RpcServer(const std::string& address, uint16_t
> port) : Json::Rpc::Server(address, port)
>    {
> is re-indented to be:
> namespace ns3
> {
> namespace rpc
> {
> Ns3RpcServer::Ns3RpcServer(const std::string& address, uint16_t port) :
> Json::Rpc::Server(address, port)
> {
> same for the header and all other files you submitted.
> > > src/rpc-agent/rpc-agent.cc,h
> doxygen should include a short description of the purpose of this class.
> I have no idea what it is.
> > > src/rpc-agent/synchronisation-agent.cc,h
> I think I understand what you are doing here.
> > > src/rpc-agent/update-agent.cc,h
> If the purpose is to "get/set attributes in NS-3 objects", why not name
> UpdateAgent an AttributeAgent instead ?

First, I must underline that all names are opened to discussion, especially
the UpdateAgent. We were wondering about AttributeAgent, but were reluctant
to use such a general name, as in its current form, this class does not
provide the functionality to change all possible attributes of any given
ns-3 object.

> I went through the code and I
> have to confess that I don't really see the relationship between the
> code and the stated purpose of the class (in doxygen). This class seems
> to do a lot of very different things, a lot of which have nothing to do
> with setting and getting attributes in ns-3 objects.
> > > src/rpc-agent/wscript
> > >
> > > src/rpc-applications/rpc-application.cc,h
> This is a subclass of the Application base class. It should be located
> in src/applications/rpc-applications.
> > > src/rpc-applications/rpc-udp-echo/rpc-udp-echo-client.cc,h
> > > src/rpc-applications/rpc-udp-echo/rpc-udp-echo-server.cc,h
> > > src/rpc-applications/rpc-udp-echo/wscript
> > >
> > > Modified files:
> > >
> > > src/node/socket.cc,h => SourceSocketAddressTag class
> > > Note that this can be avoided by adding two SocketAddressTag (one for
> > > destination and one for source address) and know at the application
> > > level which is source and which is destination.
> why not.
> > >
> > > src/internet-stack/udp-l4-protocol.cc => add a SourceSocketAddressTag
> > > tag in the packet (::Receive())
> > > The same modification should be done for tcp-l4-protocol.cc to allow
> > > an "rpc-tcp-echo-server/client" like to be implemented.
> ok.
> > >
> > > src/wscript => add modules (rpc-agent, rpc-applications and
> > > rpc-applications/rpc-udp-echo)
> > >
> > > wscript => Add link to libjson and libjsonrpc
> > I forgot some files:
> > src/example/rpc-synchronisation.cc
> > src/example/rpc-simulation.cc
> > src/example/rpc-udp-echo.cc
> > src/example/wscript (modified)
> >
> > src/mobility/rpc-mobility-model.cc,h
> > src/mobility/wscript (modified)
> >
> > src/helper/rpc-udp-echo-helper.cc,h
> > src/helper/wscript (modified)
> ok.
> Overall, I have to confess that I positively dislike what you have done
> here. Code such as this:
>    bool UpdateAgent::GetDirection(const Json::Value& msg, Json::Value&
> response)
>    {
>      if(!msg.isMember("params") ||
>          !msg["params"].isMember("nodeid") || !
> msg["params"]["nodeid"].isInt())
>      {
>        /* error */
>        Json::Value error;
>        response["jsonrpc"] = "2.0";
>        error["code"] = Json::Rpc::INVALID_PARAMS;
>        error["message"] = "Invalid parameter(s)";
>        response["id"] = msg["id"];
>        response["error"] = error;
>        return false;
>      }
>      uint32_t nodeId = msg["params"]["nodeid"].asInt();
>      Json::Value params;
>      Ptr<Node> object = GetNode(nodeId);
>      if(!object)
>      {
>        /* TODO send error message */
>        return false;
>      }
>      Ptr<RpcMobilityModel> model = object->GetObject<RpcMobilityModel>
> ();
>      if(!model)
>      {
>        /* no mobility model installed on this node */
>        response["jsonrpc"] = "2.0";
>        response["id"] = msg["id"];
>        params["x"] = Json::Value::null;
>        params["y"] = Json::Value::null;
>        params["z"] = Json::Value::null;
>        response["params"] = params;
>        return false;
> Should not exist: you are implementing your own string-based RPC all by
> hand. This makes absolutely zero sense: the very large amount of code
> dedicated to the sole purpose of marshalling and demarshalling all the
> input and output arguments is scary and is going to be horribly
> unmaintainable.

The code is of course something that can always be improved - thanks to
useful propositions and specific remarks/ideas. However, this may wait for
the moment, because I feel that your next questions touch the most important
aspects of the subject.

> The choice of API you export is also obviously very focused on solving
> your own set of simulation scenarios. I mostly refer to
> UpdateAgent::BuildSimulation which hardcodes all the simulation topology
> and most parameters. This is probably the biggest issue with the
> approach you have chosen: I fail to see how it could be of much use
> outside of your specific simulation scenarios to other people. More
> specifically, I see how we could extend your current approach to make
> the system export more flexbility to different kinds of users but I also
> see the huge implementation cost of doing so.

The question about BuildSimulation was worrying us a lot - we wanted to make
it as flexible as possible, yet Keep It Simple.

The general idea of the whole solution from the beginning was, that the user
would have to manually create the topology in ns-3, and the external
software would be able to modify the behavior of the different nodes
afterwards. Thus, the BuildSimulation is provided only as a convenience. I
do agree that it should be more flexible, and I think that we may have a lot
of useful discussions on this subject (for example - how to manage
topologies in a non-IP environment, etc). Could you please sketch how can we
make it as general as possible, while KISSing it?

> Now, I understand that you might be reluctant to use RPC-XML for various
> reasons but:
>  - it is totally free because we have a complete python binding so, you
> can trivially call RPC-XML services _today_ and remotely construct
> arbitrary ns3 topologies
>  - it is orders of magnitude more extendable and flexible
>  - it will solve your problem (among others): your mobility simulator
> is written in python so, you don't even have the excuse that you don't
> have an XML-RPC layer in your target language
> I believe that you mentioned performance as a potential issue with
> XML-RPC (other than sheer dislike of XML): do you have any hard numbers
> which show that this would be problematic for your use-case ? I mean,
> sure, json is theoretically faster than an xml-based serialization, but,
> does it actually matter for your use-case ?

The following part is discussing JSON-RPC vs XML-RPC - a subject I consider
not worth getting much into, so if you're in a hurry you can skip it.
START SKIP SECION +++++++++++++++++++++++++++++++++

Now, I don't want to turn this discussion into the n-th XML-RPC vs JSON-RPC
that can be found on the Internet. In my personal opinion, the only genuine
advantage of XML-RPC over JSON-RPC is the number of implementations. Indeed,
XML-RPC has been well tested and is a great solution - that is, if you are
building a web-store and have to interoperate with other businesses or
already existing platform solutions. However, XML-RPC is much more verbose,
not only because of the verboseness of XML, but also because it only runs
over HTTP. JSON-RPC on the other hand may use much simpler protocols, such
as Netstrings (which is basically STRING_LENGTH:string,).

About the choice of JSON over XML (
Both JSON and XML can be used to represent native, in-memory objects in a
text-based, human-readable, data exchange format. Furthermore, the two data
exchange formats are isomorphic—given text in one format, an equivalent one
is conceivable in the other. For example, when calling one of Yahoo!'s
publicly accessible web services, you can indicate via a querystring
parameter whether the response should be formatted as XML or JSON.
Therefore, when deciding upon a data exchange format, it's not a simple
matter of choosing one over the other as a silver bullet, but rather what
format has the characteristics that make it the best choice for a particular
application. For example, XML has its roots in marking-up document text and
tends to shine very well in that space (as is evident with XHTML). JSON, on
the other hand, has its roots in programming language types and structures
and therefore provides a more natural and readily available mapping to
exchange structured data.

If you want a particular example, let's take the case of a single method
invocation with both JSON-RPC and XML-RPC:
(I've optimized the JSON-RPC request/response as to reflect the lack of
named parameters in XML-RPC)

76:{"jsonrpc":"2.0", "id":5, "method":"setposition",
That is - a total of  4 + 76 + 4 + 44 = 128 bytes

User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 346

<?xml version="1.0"?>

HTTP/1.1 200 OK
Connection: close
Content-Length: 169
Content-Type: text/xml
Date: Fri, 17 Jul 1998 19:55:08 GMT
Server: UserLand Frontier/5.1.2-WinNT

<?xml version="1.0"?>

Which is a total of (I've counted the characters after removing all
unnecessary whitespaces) 373+279 = 652 bytes

I'm not an XML-RPC specialist, and I'm sure these things can be optimized,
but hey - 5x more data to transfer in a typical method call. A very
different question is, if this is the bottleneck of the approach.
END SKIP SECION +++++++++++++++++++++++++++++++++++

Why I see the question on XML-RPC vs JSON-RPC as a non-issue - once the
functionality is there, it should be (mostly) trivial to change the specific
information encoding. XML, JSON, binary - as you wish.

Additionally, I would like to point out, that this is just the beginning of
a more complete solution,  and adding Python bindings is the next logical
step. This way both the C++ and the Python parts of the ns-3 "world" would
be able to use the same functionality with ease.

> To summarize, I see very little added benefit in merging this code and a
> lot of downsides. I would much rather point users who wish to
> inter-operate with ns-3 to XML-RPC than this very partial, complex, and
> hard to maintain solution.

If I have understood correctly (correct me if I'm wrong), the major issue
which is bothering you is - why bother writing marshalling/unmarshalling
code at all? Why not "just" allow the third party to execute whatever
function she likes and forget about all that crap?

I should say that Python is a great language, so it would probably be
feasible to write a single XML-RPC entry point that would go through all
existing object in the Python namespace and execute that method on the fly.
That's great - it would save us the need to write code for each RPC function
we expose. However, I think that this is going to create a very convoluted
way of calling the functions.

But this will not solve the problems, as Python bindings are only what they
are - bindings - the return value of a method invocation is a proxy to the
underlying C++ object. You cannot automatically marshal/unmarshal that
(unless you force ALL methods to provide the necessary methods). This is
going to create a true nightmare for all third party software authors - they
are going to be forced to understand the way these objects serialize
themselves OR stream the object back to ns-3 to call a particular function
on it, and then get it back. And that is not guaranteed to work, unless you
force all member functions that perform in-place changes to also return
"this" (uploading an object to ns-3, just to receive an "OK" is not going to
help the poor caml user on the other side of the fence).

Lets look at the way mobility is implemented in the moment. There are two
methods acting on node movement direction - GetDirection and SetDirection,
which return/receive a Vector. The  Vector does support an internal
serialization format, but it does not have anything to do with XML. So, in
the best case, the caller will obtain something she has to parse (after the
whole XML-RPC over HTTP processing), change, then generate the fixed string
format and send it over the net. And remember - all this is that easy just
because there is a

So, we are going to have to write several lines of code for each function
exposed via RPC (which is what everyone does) - exactly the approach we have
taken. I do agree that the code would be more succinct in Python than in
C++, but I think that with the right helper functions the difference will be
minimal, and the user will be able to harvest the full potential of the C++
roots of ns-3.

I think that a better approach is to define interfaces - specify a set of
universally accepted methods, along with their parameters and return values
in a language-independent way. Doing so would have several benefits:

1) Implementing it in several network simulators would allow third party
software to communicate interchangeably with all of them. It would become
trivial to write an application in Java and see how it would behave in ns-3
and omnet++ for example.

2) Be implementation independent. Changing an implementation detail would be
hidden from the external user.

3) Be easier to use.

I'm sure this list could go on, but I think that these reasons are

> I am really very sorry to be so negative after the review :/
> Mathieu
In conclusion I have to say, that maybe we didn't prepare the announcement
to the full extent, and we have taken note from your remarks. We are going
to prepare an additional document explaining the proposed RPC functions, so
that we could have a fruitful discussion on concrete use-cases.

Thank you again for your remarks and suggestions!

Alexander PELOV

More information about the Ns-developers mailing list