OMNeT++ in a Nutshell

This page is for those who are somewhat familiar with network simulators, and would like to find out in a few minutes what OMNeT++ is about. It is probably a good idea to keep the User Manual and the API open in other browser tabs, so that you can search for more info there when you find something interesting in this text.

How do those simulation models and frameworks mentioned on omnetpp.org relate to OMNeT++?

OMNeT++ provides the basic machinery and tools to write simulations, but itself it does not provide any components specifically for computer network simulations, queueing network simulations, system architecture simulations or any other area. Instead, these application areas are supported by various simulation models and frameworks such as INET/INETMANET, MiXiM or Castalia. These models are developed completely independent of OMNeT++, and follow their own release cycles.

What does OMNeT++ provide then?

A C++ class library which consists of the simulation kernel and utility classes (for random number generation, statistics collection, topology discovery etc) -- this one you will use to create simulation components (simple modules and channels); infrastructure to assemble simulations from these components and configure them (NED language, ini files); runtime user interfaces or environments for simulations (Tkenv, Cmdenv); an Eclipse-based simulation IDE for designing, running and evaluating simulations; extension interfaces for real-time simulation, emulation, MRIP, parallel distributed simulation, database connectivity and so on.

OK. What does an OMNeT++ simulation model look like?

OMNeT++ provides a component architecture. Models are assembled from reusable components, modules. Well-written modules are truly reusable and can be combined in various ways like LEGO blocks.

Modules can be connected with each other via gates (other systems would call them ports), and combined to form compound modules. Connections are created within a single level of module hierarchy: a submodule can be connected with another, or with the containing compound module. Every simulation model is an instance of a compound module type. This level (components and topology) is dealt with in NED files. To give you an idea, a component named EtherMAC would be described in NED like this:

  //
  // Ethernet CSMA/CD MAC
  //
  simple EtherMAC {
      parameters:
          string address; // others omitted for brevity
      gates:
          input phyIn;    // to physical layer or the network
          output phyOut;  // to physical layer or the network
          input llcIn;    // to EtherLLC or higher layer
          output llcOut;  // to EtherLLC or higher layer
  }

And it could be used in the model of an Ethernet station like this:

  //
  // Host with an Ethernet interface
  //
  module EtherStation {
      parameters: ...
      gates: ...
          input in;    // for connecting to switch/hub, etc
          output out;  
      submodules:
          app: EtherTrafficGen;
          llc: EtherLLC;
          mac: EtherMAC;
      connections:
          app.out --> llc.hlIn;
          app.in <-- llc.hlOut;
          llc.macIn <-- mac.llcOut;
          llc.macOout --> mac.llcIn;
          mac.phyIn <-- in;
          mac.phyOut --> out;
  }

Comments are useful for generated documentation; see an example here). Simple modules which, like EtherMAC above, don't have further submodules and are backed up with C++ code that provides their active behaviour, are declared with the simple keyword; compound modules are declared with the module keyword. To simulate an Ethernet LAN, you'd create a compound module EtherLAN and announce that it can run by itself with the network keyword:

  network EtherLAN {
      ... (submodules of type EtherStation, etc) ...
  }

NED files can be edited both graphically and in text mode in the Simulation IDE.

NED only defines the model structure (topology), and leaves behaviour and a subset of module parameters open. As mentioned above, behaviour is added via C++ code behind simple modules, and module parameters which are left unassigned in NED files will get their values from ini files -- we'll cover these topics later.

The OMNeT++ manual has a chapter about the NED language.

How do I run this thing?

Provided that you're lucky enough to have EtherMAC, EtherLLC and EtherTrafficGen already programmed by someone else in C++, you'll need to compile an executable, hack up an omnetpp.ini that says what to run and with what parameters, and then you can run it as a standalone simulation program.

Building the simulation program is usually quite straighforward. In the simplest case, when everything is in one directory (and OMNeT++ is properly set up), you just need to type

  opp_makemake --deep
  make

opp_makemake creates a makefile with the appropriate settings, so you don't have to do anything else.

If you have sources in several directories, such as when you use some model framework like the Mobility Framework or the INET Framework, then this simple method of generating the makefile won't work, because the simulation will need to link with code from other directories as well. Then you'll need to pass additional options to opp_makemake such as -I; it is best to check the given framework's documentation or existing makefile system for a hint.

To run the executable, you need an omnetpp.ini file. Without it you get the following error:

  $ ./etherlan
  OMNeT++/OMNEST Discrete Event Simulation  (C) 1992-2005 Andras Varga  [....]
  <!> Error during startup: Cannot open ini file `omnetpp.ini'

One function of the ini file is to tell which network to simulate (there might be more than one network definitions in the NED files). You can also specify where to load NED files from, assign module parameters, specify how long the simulation should run, what seeds to use for random number generation, how much results to collect, set up several experiments with different parameter settings, etc. An example omnetpp.ini (hopefully self-explaining):

  [General]
  network = etherLAN
  *.numStations = 20
  **.frameLength = normal(200,1400)
  **.station[0].numFramesToSend = 5000
  **.station[1-5].numFramesToSend = 1000
  **.station[*].numFramesToSend = 0

As you can see you can use wildcards when assigning module parameters. Parameter assignments in the NED file take place first, and those left unassigned can be assigned in the ini file. (That is, parameter values in NED cannot be overwritten from the ini file.) If there are still parameters without a value, those ones will be asked interactively at runtime.

Why do we have separate NED and ini files, why isn't everything contained in a single model file? Simulation is about creating a model, experimenting with it, and drawing conclusions. In OMNeT++, C++ and the NED files represent the model, and experiments are described in ini files: parameter values, results to collect, seeds etc. Ini files let you keep the model unchanged while you're exploring the parameter space. (Note that parameters can also affect the topology, e.g. can denote the number of nodes in the network).

To load an ini file with a different name, pass the file name as command-line argument. More than one ini file can also be loaded, with the effect of their contents getting merged.

  $ ./etherlan common-settings.ini params15.ini seeds3.ini

Ini files also support inclusion, which is useful if you have common settings to factor out. The manual describes the ini file facility in detail, including a complete list of ini file options supported.

By default, the simulation executable builds with the graphical user interface, Tkenv. To run the simulation under the command-line (batch) user interface Cmdenv, specify the -u Cmdenv option:

  $ ./etherlan -u Cmdenv

It is also very easy to link the simulation with only Tkenv or Cmdenv. Since it's this easy to switch to a different UI, you could create your own GUI as well if you wanted. This is easier than you think -- reading src/cmdenv/cmdenv.cc would give you an idea how to do it. Embedding OMNeT++ into another application (e.g. some design or analysis tool that employs simulation) would go in a very similar way, and it has already been done by some commercial companies using OMNeT++/OMNEST.

How to use the Tkenv GUI? Here we don't go into details -- please explore it by clicking and right-clicking everywhere, looking into the menus, etc. There are also resources in the Wiki's Omnetpp4 section.

What is the output of the simulation?

The simulation results are recorded into output vector (.vec) and output scalar (.sca) files. The capability to record simulation results has to be programmed into the simple modules, so if you're dealing with a simulation model written by someone else, it may or may not create these files.

An output vector file contains several output vectors, each being a named series of (timestamp, value) pairs. Output vectors can store things like queue length over time, end-to-end delay of received packets, packet drops or channel throughput -- whatever the simple modules in the simulation have been programmed to record. You can configure output vectors from omnetpp.ini: you can enable or disable recording individual output vectors, or limit recording to a certain simulation time interval. To find out what output vectors a simple module can record, look for cOutVector objects in its C++ source code. Newer models record statistics via the signals framework, and statistics are declared in the NED files -- look for @statistic lines in them.

Output vectors capture behaviour over time. Output scalar files, on the other hand, contain summary statistics: number of packets sent, number of packet drops, average end-to-end delay of received packets, peak throughput. To check output scalars in the C++ code, look for recordScalar() calls, typically in the finish() method of simple module classes.

Simulation results can be visualized in the Analysis Editor of the OMNeT++ IDE.

What about random numbers?

OMNeT++'s default random number generator is Mersenne Twister. (A legacy LCG generator with a 231-1 long sequence is also available and can be configured in omnetpp.ini). OMNeT++ provides a configurable number of RNG instances (that is, streams), which can be freely mapped to individual simple modules in omnetpp.ini. This means that you can set up a simulation model so that all traffic generators use global stream 0, all MACs use global stream 1 for backoff calculation, and physical layer uses global stream 2 and global stream 3 for radio channel modelling; you can configure this RNG mapping using wildcards in omnetpp.ini. Seeding can be automatic or manual; manual seeds also come from the ini file.

Several distributions are supported (in the 3.2 version 14 continuous and 6 discrete distributions, see the API doc), and they are available from both NED and C++. Non-const module parameters can be assigned random variates like exponential(0.2), which means that the C++ code will get a different number each time it reads the parameter; this is a very convenient way of specifying parameters for random traffic sources. (const parameters can also be assigned expressions like exponential(0.2), but there it will be evaluated only once and then stuck with it).

Can I do MRIP, parallel distributed simulation, network emulation, or feature X with OMNeT++?

Yes. OMNeT++ is very extensible, plus you have all of the source code, so the sky is the limit. Several features are supported out of the box, and it is easier and cleaner to implement others than you think. Ask for guidance on the mailing list if you have such plans.

MRIP stands for multiple replications in parallel, and Akaroa is an excellent tools for that. You'll need to download and install it separately, then configure and recompile OMNeT++ with Akaroa support enabled (see configure.user). AFAIK Akaroa is available on Linux (*nix) only. Search for Akaroa in the OMNeT++ manual to learn more.

Very large simulations may benefit from the parallel distributed simulation (PDES) feature, either by getting speedup, or distributing memory requirements. If your simulation requires a few Gigabytes of memory, distributing it over a cluster may be the only way to run it. For getting speedup (and not actually slowdown, which is also easily possible), the hardware or cluster should have low latency and the model should have inherent parallelism. Partitioning and other configuration can be configured in omnetpp.ini, the simulation model itself doesn't need to be changed (unless, of course, it contains global variables and the like that prevents distributed execution in the first place.) The communication layer is MPI, but it's actually configurable, so if you don't have MPI you can still run some basic tests over named pipes, or (really for testing or debugging!) file-based message exchange. Or, if you care, you can add new ones by implementing the abstract cParsimCommunications interface.

Network emulation, together with real-time simulation and hardware-in-the-loop like functionality, is available because the event scheduler in the simulation kernel is pluggable. The OMNeT++ distribution contains a demo of real-time simulation and a simplistic example of network emulation, enough to give you hints if you're into this area. Real network emulation with the INET Framework is in the queue.

Interfacing OMNeT++ with other simulators (hybrid operation) or HLA is also largely a matter of implementing one's own scheduler class. Once you get the idea you'll find that it's much easier to do than you would have thought.

It is possible to replace omnetpp.ini with a database as the source of configuration data, and to redirect results (output vectors and scalars) into a database. The standard OMNeT++ distribution also contains demo of that; or you can check out how others have done it.

And again, since you have the full source code, and it's structured and documented, you can implement pretty much all your ideas. If you contact me on the mailing list or directly, I'll be happy to give you some initial directions.

OK! Now, how do I program a model in C++?

Simple modules are C++ classes. You'll subclass from cSimpleModule, redefine a few virtual member functions to add your code, and register the new class with OMNeT++ via the Define_Module() macro.

Modules primarily communicate via message passing, and timers (timeouts) are also handled with messages the module sends to itself (self-messages). Messages are either of class cMessage, or of some class derived from cMessage. Messages are delivered to the handleMessage(cMessage *msg) method of the module, so this is one you will surely want to redefine and add your code into. Almost everything you want the module to do will go inside handleMessage(). It can easily grow too long, so it's a good idea to factor out chunks of it into other member functions which you can name processTimer(), processPacket(), etc.

You can read about activity() as an alternative to handleMessage(), but in practice there are several good reasons to avoid using it.

You can send messages to other modules using the send(cMessage *msg, const char *outGateName) call. For wireless simulations and some other cases it can be more convenient to send messages directly to other modules without having connections set up to them in the NED file; this can be done with sendDirect(cMessage *msg, double delay, cModule *targetModule, const char *inGateName). Self-messages can be sent (that is, timers can be scheduled) via scheduleAt(simtime_t time, cMessage *msg), and before they expire they can be cancelled via cancelEvent(cMessage *msg). Like many others, these functions are all members of the cSimpleModule class; look it up in the API-doc to see further variants and other functions.

The basic cMessage class contains several data members, the practically most important ones being name, length, message "kind" (an int member). Other data members store information about the most recent sending/scheduling of the message: arrival time, arrival gate, etc. To facilitate protocol simulations, cMessage can encapsulate one other cMessage object, see its encapsulate(cMessage *msg), decapsulate() methods; these methods modify the length field accordingly as well. If you need to carry more data in the message, other data members can be added via subclassing. However, you don't need to write the new C++ class by hand, it is more convenient to define it in a .msg file instead, and let OMNeT++ (the opp_msgc tool) generate the C++ class for you. Generated C++ files will have the _m.h, _m.cc suffix. .msg files support further inheritance, composition, array members etc, and have syntax that allows you to customize the class in C++ as well. An example message file:

  message NetworkPacket {
    fields:
      int srcAddr;
      int destAddr;
  }

OMNeT++ is often used to simulate network protocol stacks, and cMessage has a field called control info that carries auxiliary information to facilitate communication between protocol layers. For example, when the application layer sends data (a message object) to TCP for transmission, it can attach a piece of control info (an object) to the message which contains the socket identifier. Or, when TCP sends a TCP segment down to IP for transmission, it attaches control info to it that carries the destination IP address, and possibly other options like TTL. Control info is sent in the other direction (upwards) as well, to indicate source IP address or TCP connection to upper layers. Control info is handled via cMessage's setControlInfo(cPolymorpic *ctrl) and removeControlInfo() methods.

Other cSimpleModule virtual member fuctions you may need to redefine are initialize() (you'll want to do most initialization here, since during the constructor call the model is still being built), initialize(int stage) and int numInitStages() const for multi-stage initialization (that's useful if one module's initialization code depends on another module already having been initialized), and finish() for recording summary results (the destructor is not suitable for that). If you want the module to take notice of runtime changes in its parameters (parameters can be changed interactively from the GUI, or programmatically by another module -- and you may want the module to re-read the parameters then), redefine the handleParameterChange(const char *paramName) method.

A simple module's NED parameters can be read using the par(const char *paramName) method; typically you'll want to do this in initialize(), and store the values in data members of the module class. par() returns a reference to a cPar object which you can cast to the appropriate type (long, double, const char *, etc) with the C/C++ cast syntax or by calling the object's doubleValue() etc. methods. In addition to basic types such as long, bool, double and string, there is XML as well: parameters can be assigned small (or not so small) XML documents or document fragments, which are presented to the C++ code as a DOM-like object tree.

Message exchange is not always the most suitable way of interaction between modules. For example, if you have a designated module for statistics collection (very much preferred over global variables!!!), then instead of sending the statistics updates to it via messages, it is more convenient to regard the statistics module as a C++ object and call a public member function of it created for this purpose (e.g. updateStatistics(...)).

Direct method calls have a few tricky details. First you have to find the other module: the parentModule(), submodule(const char *name) methods of cModule let you navigate relative to the current module, and simulation.moduleByPath(const char *path) lets you find a module globally, by an absolute path name. Once you have the pointer of the other module, you need to cast it to the actual type (i.e. to StatisticsCollector* from cModule*); this is done by check_and_cast which has the same syntax as C++'s dynamic_cast but throws an error if the cast unsuccessful or the pointer is NULL. The public method should have Enter_Method(...) or Enter_Method_Silent(...) at the top -- this enables animation of the call on the GUI, and also does something slightly obscure called temporary context switch (we don't want to go into details here, but it's especially needed if you do message handling in the method). The INET Framework extensively uses method calls to access modules like RoutingTable, InterfaceTable, NotificationBoard, etc.

INET's NotificationBoard is actually a module that can be useful in other nontrivial simulations as well: it supports sharing information among several modules by decoupling producers and consumers of information, by relaying change notifications or event notifications among them. (NotificationBoard is loosely based on the blackboard idea, but in practice it is a lot easier, simpler and more efficient to just relay notifications than storing published information on the blackboard; moreover, notifications may contain a copy of the actual data or a pointer to them.)

For debugging, it is essential that modules print some info about what they're doing. Instead of printf() and cout<<, in OMNeT++ you'd use ev<< -- that works much the same way, but the output goes to GUI windows where it can be filtered etc. It also helps to reduce debugging time when modules display their status in the icon color or in text labels or tooltips; this can be achieved by changing the display string during execution (see cModule's displayString() method for a start). By calling the bubble( const char *text) method, you can get a transient "bubble" or "balloon" displayed above the module, which can also be helpful. Another debugging aid is the WATCH(variableName) macro and variants (WATCH_VECTOR, etc), which let you inspect the value of a variable on the Tkenv GUI (you'll find watched variables in the "Contents" tab of the module's inspector).

To record output vectors, you'd add a cOutVector object to the class as a data member (or create it with new), set its name string which becomes the name of the output vector, then keep calling its record(double value) method to record numbers. Output scalars are best written from the finish() function of the module (see the recordScalar(const char *name, double value) method of cModule), based on counters etc you keep in the module class as data members. It is also possible to calculate basic statistics and histograms; check the cStdDev, cDoubleHistogram and cLongHistogram classes.

NED files describe static topology, but it is also possible to create modules and connections dynamically. This can be useful when you have the network topology in some other form than a NED file (plain text file, Excel sheet, database etc), or you truly want to create and delete modules dynamically during runtime. In the former case, often it is an easier solution to convert the data into NED by using Awk, Perl, Python, Ruby, Tcl or a series of regex find/replace operations in a text editor; however, if you're deciding for using dynamic module creation from C++, then invoking nedtool on a sample NED file and looking at the generated _n.cc file can be very helpful.

* * * * *

Hope you've found this page useful. Suggestions, fixes, questions are welcome -- you can post them on the mailing list, or just add them below by editing the page. --Andras

Edit - History - Print - Recent Changes - Search
Page last modified on July 05, 2010, at 01:39 PM