Developers Guide
Developers Guide
Release 4.5.4
1 Introduction 3
1.1 What is INET Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Scope of this Manual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4 Using Sockets 25
4.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2 UDP Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.3 TCP Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.4 SCTP Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.5 IPv4 Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.6 IPv6 Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.7 L3 Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.8 TUN Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
i
5 Testing 39
5.1 Regression Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
ii
INET Framework Developer’s Guide, Release 4.5.4
Release: 4.5.4
CONTENTS 1
INET Framework Developer’s Guide, Release 4.5.4
2 CONTENTS
CHAPTER
ONE
INTRODUCTION
INET Framework is an open-source model library for the OMNeT++ simulation environment. It provides pro-
tocols, agents and other models for researchers and students working with communication networks. INET is
especially useful when designing and validating new protocols, or exploring new or exotic scenarios.
INET supports a wide class of communication networks, including wired, wireless, mobile, ad hoc and sensor
networks. It contains models for the Internet stack (TCP, UDP, IPv4, IPv6, OSPF, BGP, etc.), link layer protocols
(Ethernet, PPP, IEEE 802.11, various sensor MAC protocols, etc), refined support for the wireless physical layer,
MANET routing protocols, DiffServ, MPLS with LDP and RSVP-TE signalling, several application models, and
many other protocols and components. It also provides support for node mobility, advanced visualization, network
emulation and more.
Several other simulation frameworks take INET as a base, and extend it into specific directions, such as vehicular
networks, overlay/peer-to-peer networks, or LTE.
This manual is written for developers who intend to extend INET with new components, written in C++. This
manual is accompanied by the INET Reference, which is generated from NED and MSG files using OMNeT++’s
documentation generator, and the documentation of the underlying C++ classes, generated from the source files
using Doxygen. A working knowledge of OMNeT++ and the C++ language is assumed.
3
INET Framework Developer’s Guide, Release 4.5.4
4 Chapter 1. Introduction
CHAPTER
TWO
2.1 Overview
The INET Packet API is designed to ease the implementation of communication protocols and applications by
providing many useful C++ components. In the following sections, we introduce the Packet API in detail, and we
shed light on many common API usages through examples.
Note: Code fragments in this chapter have been somewhat simplified for brevity. For example, some const
modifiers and const casts have been omitted, setting fields have been omitted, and some algorithms have been
simplified to ease understanding.
The representation of packets is essential for communication network simulation. Applications and communica-
tion protocols construct, deconstruct, encapsulate, fragment, aggregate, and manipulate packets in many ways. In
order to ease the implementation of these behavioral patterns, INET provides a feature-rich general data structure,
the Packet class.
The Packet data structure is capable of representing application packets, TCP segments, IP datagrams, Ethernet
frames, IEEE 802.11 frames, and all kinds of digital data. It is designed to provide efficient storage, duplication,
sharing, encapsulation, aggregation, fragmentation, serialization, and data representation selection. Additional
functionality, such as support for enqueueing data for transmisson and buffering received data for reassembly
and/or for reordering, is provided as separate C++ data structures on top of Packet.
The Packet data structure builds on top of another set of data structures called chunks. Chunks provide several
alternatives to represent a piece of data.
INET provides the following built-in chunk C++ classes:
• Chunk, the base class for all chunk classes
• repeated byte or bit chunk (ByteCountChunk, BitCountChunk)
• raw bytes or bits chunk (BytesChunk, BitsChunk)
• ordered sequence of chunks (SequenceChunk)
• slice of another chunk designated by offset and length (SliceChunk)
• many protocol specific field based chunks (e.g. Ipv4Header subclass of FieldsChunk)
In addition, communication protocols and applications often define their own chunk types. User-defined chunks
are normally defined in msg files as a subclass of FieldsChunk, which the OMNeT++ MSG compiler turns
into C++ code. It is also possible to write a user defined chunk from scratch.
Chunks usually represent application data and protocol headers. The following examples demonstrate the con-
struction of various chunks.
5
INET Framework Developer’s Guide, Release 4.5.4
In general, chunks must be constructed with a call to the makeShared function instead of the standard C++ new
operator, because chunks are shared among packets using C++ shared pointers.
Packets most often contain several chunks, inserted by different protocols, as they are passed through the protocol
layers. The most common way to represent packet contents is to form a compound chunk by concatenation.
Protocols often need to slice data, for example to provide fragmentation, which is also directly supported by the
chunk API.
In order to avoid cluttered data representation due to slicing, the chunk API provides automatic merging for
consecutive chunk slices.
Alternative representations can be easily converted into one another using automatic serialization as a common
ground.
The following MSG fragment is a more complete example which shows how a UDP header could be defined:
enum CrcMode
{
CRC_DISABLED = 0; // CRC is not set, serializable
CRC_DECLARED = 1; // CRC is correct without the value, not serializable
CRC_COMPUTED = 2; // CRC is potentially incorrect, serializable
}
It’s important to distinguish the two length related fields in the UdpHeader chunk. One is the length of the chunk
itself (chunkLength), the other is the value in the length field of the header (lengthField).
The Packet data structure uses a single chunk data structure to represent its contents. The contents may be as
simple as raw bytes (BytesChunk), but most likely it will be the concatenation (SequenceChunk) of various
protocol specific headers (e.g., FieldsChunk subclasses) and application data (e.g., ByteCountChunk).
Packets can be created by both applications and communication protocols. As packets are passed down through
the protocol layers at the sender node, new protocol specific headers and trailers are inserted during processing.
In order to facilitate packet processing by communication protocols at the receiver node, Packet maintains two
offsets into the packet data that divide the data into three regions: front popped part, data part, and back popped
part. During packet processing, as the packet is passed through the protocol layers, headers and trailers are popped
from the beginning and from the end of the packet, moving the corresponding offsets. This effectively reduces the
remaining unprocessed part called the data part, but it doesn’t affect the data stored in the packet.
Protocols and applications use the Packet data structure to represent digital data during the processing within
the network node. In contrast, the wireless transmission medium uses a different data structure called Signal to
represent the physical phenomena used to transmit packets.
Signals always encapsulate a packet and also contain a description of the analog domain representation. The most
important physical properties of a signal are the signal duration and the signal power.
An essential part of communication network simulation is the understanding of protocol behavior in the presence
of errors. The Packet API provides several alternatives for representing errors. The alternatives range from simple,
but computationally cheap, to accurate, but computationally expensive solutions.
• mark erroneous packets (simple)
• mark erroneous chunks (good compromise)
• change bits in raw chunks (accurate)
The first example shows how to represent transmission erros on the packet level. A packet is marked as erroneous
based on its length and the associated bit error rate. This representation doesn’t give too much chance for a
protocol to do anything else than discard an erroneous packet.
The second example shows how to represent transmission errors on the chunk level. Similarly to the previous
example, a chunk is also marked as erroneous based on its length and the associated bit error rate. This repre-
sentation allows a protocol to discard only certain parts of the packet. For example, an aggregated packet may be
partially discarded and processed.
The last example shows how to actually represent transmission errors on the byte level. In contrast with the
previous examples, this time the actual data of the packet is modified. This allows a protocol to discard or correct
any part based on checksums.
The physical layer models support the above mentioned different error representations via configurable parame-
ters. Higher layer protocols detect errors by chechking the error bit on packets and chunks, and by standard CRC
mechanisms.
Within network nodes, supplementary data often needs to be transmitted alongside a packet. For instance, when
an application-layer module intends to transfer data using TCP, it must specify a connection identifier for TCP.
Similarly, when TCP transmits a segment via IP, IP requires a destination address, and when IP sends a datagram
to an Ethernet interface for transmission, a destination MAC address must be specified. These additional details
are attached to a packet as tags.
The following code fragment demonstrates how packet tags could be set in the IPv4 protocol module:
Packet tags are not transmitted from one network node to another. All physical layer protocols delete all packet
tags from a packet before sending it to the connected peer or to the transmission medium.
For more details on what kind of tags are there see the
To gather certain statistics, it might be necessary to add metadata to various regions of packet data. For instance,
determining the end-to-end delay of a TCP stream necessitates labeling data regions at the source with their
creation timestamp. Subsequently, as the data arrives, the receiver calculates the end-to-end delay for each region.
void ClientApp::send()
{
auto data = makeShared<ByteCountChunk>(); // create new data chunk
auto creationTimeTag = data->addTag<CreationTimeTag>(); // add new tag
creationTimeTag->setCreationTime(simTime()); // store current time
auto packet = new Packet("Data", data); // create new packet
socket.send(packet); // send packet using TCP socket
}
Within a TCP stream, the data may be split, rearranged, and combined in various ways by the underlying network.
The packet data representation is responsible for preserving the associated region tags as if they were individually
attached to each bit. To prevent a cluttered data representation resulting from the aforementioned characteristics,
the tag API offers automatic merging for successive, equivalent tag regions.
The above loop could execute once for the entirety of the data, or it could execute multiple times, depending on
the data’s creation at the sender and the operation of the underlying network.
Understanding what’s inside a packet is a very important and often used functionality. Simply using the repre-
sentation may be insufficient, because the Packet may be represented with a BytesChunk, for exmple. The
Packet API provides a PacketDissector class which analyzes a packet solely based on the assigned packet
protocol and the actual data it contains.
The analysis is done according to the protocol logic as opposed to the actual representation of the data. The
PacketDissector works similarly to a parser. Basically, it walks through each part (such as protocol headers)
of a packet in order. For each part, it determines the corresponding protocol and the most specific representation
for that protocol.
The PacketDissector class relies on small registered protocol-specific dissector classes (e.g.
Ipv4ProtocolDissector) subclassing the required ProtocolDissector base class. Implementors are
expected to use the PacketDissector::ICallback interface to notify the parser about the packet structure.
Filtering packets based on the actual data they contain is another widely used and very important feature. With the
help of the packet dissector, it is very simple to create arbitrary custom packet filters. Packet filters are generally
used for recording packets and visualizing various packet related information.
In order to simplify filtering, the Packet API provides a generic expression based packet filter which is imple-
mented in the PacketFilter class. The expression syntax is the same as other OMNeT++ expressions, and
the data filter is matched against individual chunks of the packet as found by the packet dissector.
For example, the packet filter expression “ping*” matches all packets having the name prefix ’ping’, and the
packet chunk filter expression “inet::Ipv4Header and srcAddress(10.0.0.*)” matches all packets that contain an
IPv4 header with a ’10.0.0’ source address prefix.
PacketFilter filter; // patterns for the whole packet and for the data
filter.setPattern("ping*", "Ipv4Header and sourceAddress(10.0.0.*)");
filter.matches(packet); // returns boolean value
During model development, packets often need to be displayed in a human readable form. The Packet
API provides a PacketPrinter class which is capable of forming a human readable string representation
of Packet’s. The PacketPrinter class relies on small registered protocol-specific printer classes (e.g.
Ipv4ProtocolPrinter subclassing the required ProtocolPrinter base class.
The packet printer is automatically used by the OMNeT++ runtime user interface to display packets in the packet
log window. The packet printer contributes several log window columns into the user interface: ’Source’, ’Destina-
tion’, ’Protocol’, ’Length’, and ’Info’. These columns display packet data similarly to the well-known Wireshark
protocol analyzer.
The PacketPrinter provides a few other functions which have additional options to control the details of the
resulting human readable form.
Exporting the packets from a simulation into a PCAP file allows further processing with 3rd party tools. The
Packet API provides a PcapDump class for creating PCAP files. Packet filtering can be used to reduce the file
size and increase performance.
PcapDump dump;
dump.openPcap("out.pcap", 65535, 0); // maximum length and PCAP type
dump.writePacket(simTime(), packet); // record with current time
Many communication protocols work with simple packet encapsulation. They encapsulate packets with their own
protocol specific headers and trailers at the sender node, and they decapsulate packets at the reciver node. The
headers and trailers carry the information that is required to provide the protocol specific service.
For example, the Ethernet MAC protocol encapsulates an IP datagram by prepending the packet with an Ethernet
MAC header, and also by appending the packet with an optional padding and an Ethernet FCS. The following
example shows how a MAC protocol could encapsulate a packet:
When receiving a packet, the Ethernet MAC protocol removes an Ethernet MAC header and an Ethernet FCS from
the packet, and passes the resulting IP datagram along. The following example shows how a MAC protocol could
decapsulate a packet:
Although the popAtFront and popAtBack functions change the remaining unprocessed part of the packet,
they don’t have effect on the actual packet data. That is when the packet reaches high level protocol, it still contains
all the received data but the remaining unprocessed part is smaller.
Communication protocols often provide fragmentation to overcome various physical limits (e.g. length limit,
error rate). They split packets into smaller pieces at the sender node, which send them one-by-one. They form the
original packet at the receiver node by combining the received fragments.
For example, the IEEE 802.11 protocol fragments packets to overcome the increasing probability of packet loss
of large packets. The following example shows how a MAC protocol could fragment a packet:
When receiving fragments, protocols need to collect the coherent fragments of the same packet until all fragments
becomes available. The following example shows how a MAC protocol could form the original packet from a set
of coherent fragments:
Communication protocols often provide aggregation to better utilize the communication channel by reducing
protocol overhead. They wait for several packets to arrive at the sender node, then they form a large aggregated
packet which is in turn sent at once. At the receiver node the aggregated packet is split into the original packets,
and they are passed along.
For example, the IEEE 802.11 protocol aggregates packets for better channel utilization at both MSDU and MPDU
levels. The following example shows a version of how a MAC protocol could create an aggregate packet:
The following example shows a version of how a MAC protocol could disaggregate a packet:
In real communication systems packets are usually stored as a sequence of bytes directly in network byte order.
In contrast, INET usually stores packets in small field based C++ classes (generated by the OMNeT++ MSG
compiler) to ease debugging. In order to calculate checksums or to communicate with real hardware, all protocol
specific parts must be serializable to a sequence of bytes.
The protocol header serializers are separate classes from the actual protocol headers. They must be registered in
the ChunkSerializerRegistry in order to be used. The following example shows how a MAC protocol
header could be serialized to a sequence of bytes:
void MacHeaderSerializer::serialize
(MemoryOutputStream& stream, Ptr<Chunk>& chunk)
{
auto header = staticPtrCast<MacHeader>(chunk);
stream.writeUint16Be(header->getType()); // unsigned 16 bits, big endian
stream.writeMacAddress(header->getTransmitterAddress());
stream.writeMacAddress(header->getReceiverAddress());
}
Deserialization is somewhat more complicated than serialization, because it must be prepared to handle incomplete
or even incorrect data due to errors introduced by the network. The following example shows how a MAC protocol
header could be deserialized from a sequence of bytes:
In order to be able to communicate with real hardware, packets must be converted to and from a sequence of bytes.
The reason is that the programming interface of operating systems and external libraries work with sending and
receiving raw data.
All protocol headers and data chunks which are present in a packet must have a registered serializer to be able
to create the raw sequence of bytes. Protocol modules must also be configured to either disable or compute
checksums, because serializers cannot carry out the checksum calculation.
The following example shows how a packet could be converted to a sequence of bytes to send through an external
interface:
The following example shows how a packet could be converted from a sequence of bytes when receiving from an
external interface:
In INET, all protocols automatically support hardware emulation due to the dual representation of packets. The
above example creates a packet which contains a single chunk with a sequence of bytes. As the packet is passed
through the protocols, they can interpret the data (e.g. by calling peekAtFront) as they see fit. The Packet API
always provides the requested representation, either because it’s already available in the packet, or because it gets
automatically deserialized.
Some protocols store packet data temporarily at the sender node before actual processing can occur. For exam-
ple, the TCP protocol must store the outgoing data received from the application in order to be able to provide
transmission flow control.
The following example shows how a transport protocol could store the received data temporarily until the data is
actually used:
class TransportSendQueue
{
ChunkQueue queue; // stores application data
B sequenceNumber; // position in stream
The ChunkQueue class acts similarly to a binary FIFO queue except it works with chunks. Similarly to the
Packet it also automatically merge consecutive data and selects the most appropriate representation.
Protocols at the receiver node often need to buffer incoming packet data until the actual processing can occur. For
example, packets may arrive out of order, and the data they contain must be reassembled or reordered before it
can be passed along.
INET provides a few special purpose C++ classes to support data buffering:
• ChunkBuffer provides automatic merging for large data chunks from out of order smaller data chunks.
• ReassemblyBuffer provides reassembling for out of order data according to an expected length.
• ReorderBuffer provides reordering for out of order data into a continuous data stream from an expected
offset.
All buffers deal with only the data, represented by chunks, instead of packets. They automatically merge con-
secutive data and select the most appropriate representation. Protocols using these buffers automatically sup-
port all data representation provided by INET, and any combination thereof. For example, ByteCountChunk,
BytesChunk, FieldsChunk, and SliceChunk can be freely mixed in the same buffer.
Some protocols may use an unreliable service to transfer a large piece of data over the network. The unreliable
service requires the receiver node to be prepared for receiving parts out of order and potentially duplicated.
For example, the IP protocol must store incoming fragments at the receiver node, because it must wait until the
datagram becomes complete, before it can be passed along. The IP protocol must also be prepared for receiving
the individual fragments out of order and potentially duplicated.
The following example shows how a network protocol could store and reassemble the data of the incoming packets
into a whole packet:
class NetworkProtocolDefragmentation
{
ReassemblyBuffer buffer; // stores received data
Packet *NetworkProtocolDefragmentation::getReassembledDatagram()
{
if (!buffer.isComplete()) // if reassembly isn't complete
return nullptr; // there's nothing to return
auto data = buffer.getReassembledData(); // complete reassembly
return new Packet("Datagram", data); // create new packet
}
The ReassemblyBuffer supports replacing the stored data at a given offset, and it also provides the complete
reassembled data with the expected length if available.
Some protocols may use an unreliable service to transfer a long data stream over the network. The unreliable
service requires the sender node to resend unacknowledged parts, and it also requires the receiver node to be
prepared for receiving parts out of order and potentially duplicated.
For example, the TCP protocol must buffer the incoming data at the receiver node, because the TCP segments
may arrive out of order and potentially duplicated or overlapping, and TCP is required to provide the data to the
application in the correct order and only once.
The following example shows how a transport protocol could store and reorder the data of incoming packets,
which may arrive out of order, and also how such a protocol could pass along only the available data in the correct
order:
class TransportReceiveQueue
{
ReorderBuffer buffer; // stores receive data
B sequenceNumber;
Packet *TransportReceiveQueue::getAvailableData()
{
if (buffer.getAvailableDataLength() == b(0)) // if no data available
return nullptr;
auto data = buffer.popAvailableData(); // remove all available data
return new Packet("Data", data);
}
The ReorderBuffer supports replacing the stored data at a given offset, and it provides the available data from
the expected offset if any.
THREE
3.1 Overview
Modules often exchange information by sending packets along with supplementary data, referred to as tags. A
tag is usually a small data structure that focuses on a single parameterization aspect of a protocol. Tags can be
attached to the whole packet, known as packet tags, or to specific parts of the packet, known as region tags. Tags
are implemented as data container C++ classes and they are usually generated by the OMNeT++ MSG compiler.
These are the primary types of tags:
• requests carry information from higher layers to lower layers (e.g. MacAddressReq).
• indications carry information from lower layers to higher layers (e.g. InterfaceInd).
• plain tags contain some meta-information (e.g. PacketProtocolTag).
• base classes must not be attached to packets (e.g. TagBase).
For example, a request tag that specifies the source and destination MAC address could be implemented in an
MSG file as follows:
The following list gives a short description of several often used packet tags:
• PacketProtcolTag specifies the protocol of the packet’s contents
• DispatchProtocolReq specifies the receiver protocol module inside the network node
• EncapsulationProcotolReq specifies the requested protocol header encapsulation order
• SocketReq specifies the application socket
• L4PortReq specifies the source and destination ports
• L3AddressReq specifies source and destination network addresses
• InterfaceReq specifies the outgoing network interface
• NextHopAddressReq specifies the next hop address for packet routing
• VlanReq specifies the virtual LAN identifier of IEEE 802.1Q
• PcpReq specifies the priority code point of IEEE 802.1Q
• StreamReq specifies the TSN stream identifier inside the network node
• MacAddressReq specifies source and destination MAC addresses
• Ieee80211ModeReq specifies the IEEE 802.11 PHY mode
• Ieee80211ChannelReq specifies the IEEE 802.11 channel
19
INET Framework Developer’s Guide, Release 4.5.4
Tags can pass through protocol modules, and they can reach far beyond the module that initially attached them, in
both downward and upward directions. Typically, tags are removed at the point where they are processed, usually
being transformed into header fields within a packet, or used for some protocol specific decisions. Protocols have
the liberty to disregard any tags at their discretion based on their configuration and state.
Both packet tags and region tags remain unchanged for many operations that protocol modules carry out with
packets. For example, when packets are enqueued/dequeued, encapsulated/decapsulated, cloned, buffered, or
stored for later reuse, the tags remain unchanged.
The most important packet tag is the PacketProtocolTag. It specifies the outermost protocol of the packet.
This tag should always be present, because the packet protocol cannot be correctly determined just by looking at
the raw data. In contrast, the inner protocol headers in the packet can be usually recursively identified by protocol
fields such as the protocol ID field of the IPv4 header. The PacketProtocolTag is used among others for
dissecting the packet along the protocol headers, or for printing the packet as a human readable string to help
interpreting its contents.
Normally a packet is transformed from one protocol to another in a single step, so the packet protocol tag either
specifies the protocol before the operation or the protocol after the operation. For example, the Udp protocol
module encapsulates the outgoing packet using a UdpHeader. The packet protocol is set to an application
specific protocol before the UDP encapsulation and it’s set to UDP protocol after the encapsulation.
Sometimes, protocol implementations themselves are split up into several smaller modules. For example, the
modular Ethernet implementation uses a separate module for the insertion of the Ethernet MAC header and the
Ethernet FCS. This module structure implies that the packet can be seen between the modules as a partially built
Ethernet MAC protocol packet. In such a case the packet protocol tag can only specify the inner protocol that is
being encapsulated into an Ethernet MAC frame.
Inside a network node, protocol modules interact with one another by sending Packet or Message objects.
INET is very flexible in terms of what structure the protocol modules can be connected. Protocols can be connected
directly to each other, or they can be connected through one or more MessageDispatcher modules. This flexibility
allows for the creation of both simple and complex network node architectures.
Simple network nodes can be constructed, for example, using a linear protocol stack, where protocol modules are
directly connected to one another without using message dispatcher modules.
More complex network nodes can be created by grouping protocols into layers and connecting them through
MessageDispatcher modules, which facilitates many-to-one and many-to-many relationships among the protocols
of the layers.
It’s also possible to use message dispatcher modules hierarchically within multiple levels of nested compound
modules. Ultimately, one could even connect all protocols to a single central message dispatcher module. There
is an important limitation though, only one instance of a given protocol module can be connected to a message
dispatcher.
To support the packet dispatching mechanism, certain additional requirements must be met in C++ code:
• protocols must be registered using registerProtocol
• packets must have DispatchProtocolReq tags attached
Protocol modules must call the registerProtocol function from the initialize method to inform con-
nected MessageDispatcher modules of their presence. The following code fragment demonstrates this for the IPv4
protocol implementation:
Registering the protocols allows the dispatcher modules to learn which gates the protocol modules are connected
to. The same protocol is not allowed to be registered in the same message dispatcher using different gates, because
that would make the dispatching mechanism ambiguous.
Packets and messages must have the DispatchProtocolReq tag attached to them in order for the message
dispatcher modules to correctly dispatch them to the intended recipient within the network node. The following
example shows how a MAC protocol could send up a packet to the Ipv4 protocol module without actually knowing
where that module is connected in the network node architecture:
The DispatchProtocolReq tag specifies both the intended recipient protocol and the requested service prim-
itive. The service primitive, similarly to OSI terminology, can be one of:
• SP_REQUEST for service requests from layer N+1 to layer N
• SP_CONFIRM for service confirmations from layer N to layer N+1
• SP_INDICATION for protocol indications from layer N to layer N+1
• SP_RESPONSE for protocol response from layer N+1 to layer N
Currently, INET modules only use the SP_REQUEST and SP_INDICATION service primitives, the other two
are only present for completeness. The request service primitive is used when a higher layer protocol module (e.g.
Tcp) wants to deliver a packet to a lower layer protocol module (e.g. Ipv4). Similarly, the SP_INDICATION
service primitive is used when a lower layer protocol module (e.g. Ethernet) wants to deliver a packet to a higher
layer protocol module (e.g. Ipv4).
A protocol module has several options for determining which protocol to forward a packet to. The list below
shows some possibilities:
• The next protocol can be hard-coded in C++. For example, the UDP protocol is hard-coded in C++ in the
UdpSocket class, and similarly other protocols are also hard-coded in other protocol specific sockets.
• The next protocol can be specified by a module parameter, as is the case of a network interface module
specifying its expected protocol. The Ipv4 module uses this information to dispatch a packet to the expected
protocol of the selected route’s network interface.
• The next protocol can be dependent on module state. For example, the TSN stream encoder module forwards
packets that match the TSN stream mapping to the 802.1 Q-TAG protocol for encapsulation.
• The next protocol can be determined by a packet header field. For example, the Ipv4 module uses the IP
protocol ID header field from the Ipv4Header to look up the next protocol as shown below:
• The next protocol can be determined by some packet meta-data. For example, the Tcp module uses the type
of the destination address from the L3AddressReq tag to determine if the packet should be sent to the
Ipv4 or Ipv6 module.
• The next protocol can be indirectly specified by a protocol encapsulation request. For example, other mod-
ules may have attached an EncapsulationProtocolReq to the packet in an earlier stage of the packet
processing.
A packet typically contains multiple protocol-specific headers, such as TCP, IP, Ethernet, and sometimes additional
optional headers like 802.1Q, 802.1R, 802.1AE, and others. The order of packet headers is determined by the order
in which the packet reaches the relevant protocol modules for encapsulation.
The encapsulation process may need to be different for each packet. For example, an application may need to send
a packet to a specific VLAN. In this case, the application should attach a VlanReq tag to the packet with the
desired VLAN ID. However, it cannot directly send the packet to the relevant 802.1Q protocol module, because the
packet may need to be delivered to the UDP protocol first. To achieve the desired protocol encapsulation order,
the application should also attach an EncapsulationProtocolReq tag specifying that the packet should
ultimately be delivered to the 802.1Q protocol for encapsulation. The underlying protocol modules will use this
information to determine when the 802.1Q encapsulation should take place.
The EncapsulationProtocolReq generally outlines the sequence of protocol modules that a packet should
be delivered to for further encapsulation. Additional tags attached to the packet are used as additional parameters
for the requested processing steps. The attached encapsulation request may be changed several times during packet
processing, new protocols may be added, already added protocols may be removed, and so on.
For example, the IP protocol determines the outgoing interface using the routing table and the destination address.
The selected network interface specifies the expected protocol that the packet should have in order for the interface
module to operate properly. The specified protocol is appended to the end of the requested encapsulation protocols
of the packet, because it should be the last encapsulation before the packet reaches the network interface. For
example, if the IP module selects an Ethernet network interface, then it appends the Ethernet MAC protocol to the
EncapsulationProtocolReq, and the packet is ultimately encapsulated into an EthernetMacHeader
before reaching the network interface module.
As part of the forwarding process of Ethernet switches, an inbound packet is transformed into an outbound packet.
This process is carried out by default in the PacketDirectionReverser module. The transformation is more like
a policy and it can be replaced by the user with other modules. The default module doesn’t change the packet
contents except for removing the already popped front and back parts, but it changes the attached packet tags
significantly.
The inbound packet usually contains a few tags such as PacketProtocolTag and DirectionTag, and
it also contains several indications such as the InterfaceInd, MacAddressInd, VlandInd, PcpInd,
EncapsulationProtocolInd and so on. The transformation keeps only the PacketProtocolTag, it
removes all attached indications, and attaches a set of requests so that the packet will be encapsulated in the same
protocol headers, and it will be sent out on the same interface as it came in.
Of course, this is just the start of the processing of the outbound packet. During the several steps that follows any
of the attached requests can be replaced with new ones potentially ultimately resulting in the packet to be handled
in a completely different way.
FOUR
USING SOCKETS
4.1 Overview
The INET Socket API provides special C++ abstractions on top of the standard OMNeT++ message passing
interface for several communication protocols.
Sockets are most often used by applications and routing protocols to acccess the corresponding protocol services.
Sockets are capable of communicating with the underlying protocol in a bidirectional way. They can assemble
and send service requests and packets, and they can also receive service indications and packets.
Applications can simply call the socket class member functions (e.g. bind(), connect(), send(),
close()) to create and configure sockets, and to send and receive packets. They may also use several different
sockets simulatenously.
The following sections first introduce the shared functionality of sockets, and then list all INET sockets in detail,
mostly by shedding light on many common usages through examples.
Note: Code fragments in this chapter have been somewhat simplified for brevity. For example, some virtual
modifiers and override qualifiers have been omitted, and some algorithms have been simplified to ease under-
standing.
Although sockets are always implemented as protocol specific C++ classes, INET also provides C++ socket inter-
faces. These interfaces allow writing general C++ code which can handle many different kinds of sockets all at
once.
For example, the ISocket interface is implemented by all sockets, and the INetworkSocket interface is
implemented by all network protocol sockets.
All sockets have a socket identifier which is unique within the network node. It is automatically assigned to the
sockets when they are created. The identifier can accessed with getSocketId() throughout the lifetime of the
socket.
The socket identifier is also passed along in SocketReq and SocketInd packet tags. These tags allow applica-
tions and protocols to identify the socket to which Packet’s, service Request’s, and service Indication’s
belong.
25
INET Framework Developer’s Guide, Release 4.5.4
Since all sockets work with message passing under the hoods, they must be configured prior to use. In order to
send packets and service requests on the correct gate towards the underlying communication protocol, the output
gate must be configured:
In contrast, incoming messages such as service indications from the underlying communication protocol can be
received on any application gate.
To ease application development, all sockets support storing a user specified data object pointer. The pointer is
accessible with the setUserData(), getUserData() member functions.
Another mandatory configuration for all sockets is setting the socket callback interface. The callback interface is
covered in more detail in the following section.
Other socket specific configuration options are also available, these are discussed in the section of the correspond-
ing socket.
To ease centralized message processing, all sockets provide a callback interface which must be implemented by
applications. The callback interface is usually called ICallback, and it’s defined as an inner class of the socket
it belongs to. These interfaces often contain some generic notification methods along with several socket specific
methods.
For example, the most common callback method is the one which processes incoming packets:
In general, sockets can process all incoming messages which were sent by the underlying protocol. The received
messages must be processed by the socket where they belong to.
For example, an application can simply go through each knonwn socket in any order, and decide which one should
process the received message as follows:
Sockets usually deconstruct the received messages and update their state accordingly if necessary. They also
automatically dispatch received packets and service indications for further processing to the appropriate functions
in the corresponding ICallback interface.
All sockets provide one or more send() functions which send packets using the current configuration of the
socket. The actual means of packet delivery depends on the underlying communication protocol, but in general
the state of the socket is expected to affect it.
For example, after the socket is properly configured, the application can start sending packets without attaching
any tags, because the socket takes care of the necessary technical details:
For example, the application may directly implement the ICallback interface of the socket and print the re-
ceived data as follows:
Sockets must be closed before deleting them. Closing a socket allows the underlying communication protocol to
release allocated resources. These resources are often allocated on the local network node, the remote nework
node, or potentially somewhere else in the network.
For example, a socket for a connection oriented protocol must be closed to release the allocated resources at the
peer:
If the application needs to manage a large number of sockets, for example in a server application which handles
multiple incoming connections, the generic SocketMap class may be useful. This class can manage all kinds of
sockets which implement the ISocket interface simultaneously.
For example, processing an incoming packet or service indication can be done as follows:
In order for the SocketMap to operate properly, sockets must be added to and removed from it using the
addSocket() and removeSocket() methods respectively.
4.1. Overview 27
INET Framework Developer’s Guide, Release 4.5.4
The UdpSocket class provides an easy to use C++ interface to send and receive UDP datagrams. The underlying
UDP protocol is implemented in the Udp module.
Processing packets and indications which are received from the Udp module is pretty simple. The incoming
message must be processed by the socket where it belongs as shown in the general section.
The UdpSocket deconstructs the message and uses the UdpSocket::ICallback interface to notify the
application about received data and error indications:
For receiving UDP datagrams on a socket, it must be bound to an address and a port. Both the address and port is
optional. If the address is unspecified, than all UDP datagrams with any destination address are received. If the
port is -1, then an unused port is selected automatically by the Udp module. The address and port pair must be
unique within the same network node.
Here is how to bind to a specific local address and port to receive UDP datagrams:
For only receiving UDP datagrams from a specific remote address/port, the socket can be connected to the desired
remote address/port:
There are several other socket options (e.g. receiving broadcasts, managing multicast groups, setting type of
service) which can also be configured using the UdpSocket class:
After the socket has been configured, applications can send datagrams to a remote address and port via a simple
function call:
If the application wants to send several datagrams, it can optionally connect to the destination.
The UDP protocol is in fact connectionless, so when the Udp module receives the connect request, it simply
remembers the remote address and port, and use it as default destination for later sends.
The application can call connect several times on the same socket.
For example, the application may directly implement the UdpSocket::ICallback interface and print the
received data as follows:
The TcpSocket class provides an easy to use C++ interface to manage TCP connections, and to send and receive
data. The underlying TCP protocol is implemented in the Tcp, TcpLwip, and TcpNsc modules.
Messages received from the various Tcp modules can be processed by the TcpSocket where they belong to.
The TcpSocket deconstructs the message and uses the TcpSocket::ICallback interface to notify the
application about the received data or service indication:
The Tcp module supports several TCP different congestion algorithms, which can also be configured using the
TcpSocket:
socket.setTCPAlgorithmClass("TcpReno");
Upon setting the individual parameters, the socket immediately sends sevice requests to the underlying Tcp pro-
tocol module.
Since TCP is a connection oriented protocol, a connection must be established before applications can exchange
data. On the one side, the application listens at a local address and port for incoming TCP connections:
On the other side, the application connects to a remote address and port to establish a new connection:
The Tcp module automatically notifies the TcpSocket about incoming connections. The socket in turn noti-
fies the application using the ICallback::socketAvailable method of the callback interface. Finally,
incoming TCP connections must be accepted by the application before they can be used:
After the connection is accepted, the Tcp module notifies the application about the socket being established and
ready to be used.
After the connection has been established, applications can send data to the remote application via a simple
function call:
socket.send(packet1);
// ...
socket.send(packet42);
Packet data is enqueued by the local Tcp module and transmitted over time according to the protocol logic.
Receiving data is as simple as implementing the corresponding method of the TcpSocket::ICallback in-
terface. One caveat is that packet data may arrive in different chunk sizes (but the same order) than they were sent
due to the nature of TCP protocol.
For example, the application may directly implement the TcpSocket::ICallback interface and print the
received data as follows:
The SctpSocket class provides an easy to use C++ interface to manage SCTP connections, and to send and
receive data. The underlying SCTP protocol is implemented in the Sctp module.
Messages received from the Sctp module can be processed by the SctpSocket where they belong to. The
SctpSocket deconstructs the message and uses the SctpSocket::ICallback interface to notify the ap-
plication about the received data or service indication:
The SctpSocket class supports setting several SCTP specific connection parameters directly:
socket.setOutboundStreams(2);
socket.setStreamPriority(1);
socket.setEnableHeartbeats(true);
// ...
Upon setting the individual parameters, the socket immediately sends sevice requests to the underlying Sctp pro-
tocol module.
Since SCTP is a connection oriented protocol, a connection must be established before applications can exchange
data. On the one side, the application listens at a local address and port for incoming SCTP connections:
On the other side, the application connects to a remote address and port to establish a new connection:
socket.connect(Ipv4Address("10.0.0.42"), 42);
The Sctp module automatically notifies the SctpSocket about incoming connections. The socket in turn no-
tifies the application using the ICallback::socketAvailable method of the callback interface. Finally,
incoming SCTP connections must be accepted by the application before they can be used:
After the connection has been established, applications can send data to the remote applica- tion via a simple
function call:
socket.send(packet1);
// ...
socket.send(packet42);
Packet data is enqueued by the local Sctp module and transmitted over time according to the protocol logic.
The Ipv4Socket class provides an easy to use C++ interface to send and receive IPv4 datagrams. The underly-
ing IPv4 protocol is implemented in the Ipv4 module.
Messages received from the Ipv4 module must be processed by the socket where they belong as shown in the gen-
eral section. The Ipv4Socket deconstructs the message and uses the Ipv4Socket::ICallback interface
to notify the application about the received data:
In order to only receive IPv4 datagrams which are sent to a specific local address or contain a specific protocol,
the socket can be bound to the desired local address or protocol.
For example, the following code fragment shows how the INET PingApp binds to the ICMPv4 protocol to receive
all incoming ICMPv4 Echo Reply messages:
For only receiving IPv4 datagrams from a specific remote address, the socket can be connected to the desired
remote address:
After the socket has been configured, applications can immediately start sending IPv4 datagrams to a remote
address via a simple function call:
If the application wants to send several IPv4 datagrams to the same destination address, it can optionally connect
to the destination:
The IPv4 protocol is in fact connectionless, so when the Ipv4 module receives the connect request, it simply
remembers the remote address, and uses it as the default destination address for later sends.
The application can call connect() several times on the same socket.
For example, the application may directly implement the Ipv4Socket::ICallback interface and print the
received data as follows:
The Ipv6Socket class provides an easy to use C++ interface to send and receive IPv6 datagrams. The underly-
ing IPv6 protocol is implemented in the Ipv6 module.
Messages received from the Ipv6 module must be processed by the socket where they belong as shown in the gen-
eral section. The Ipv6Socket deconstructs the message and uses the Ipv6Socket::ICallback interface
to notify the application about the received data:
In order to only receive IPv6 datagrams which are sent to a specific local address or contain a specific protocol,
the socket can be bound to the desired local address or protocol.
For example, the following code fragment shows how the INET PingApp binds to the ICMPv6 protocol to receive
all incoming ICMPv6 Echo Reply messages:
For only receiving IPv6 datagrams from a specific remote address, the socket can be connected to the desired
remote address:
After the socket has been configured, applications can immediately start sending IPv6 datagrams to a remote
address via a simple function call:
If the application wants to send several IPv6 datagrams to the same destination address, it can optionally connect
to the destination:
The IPv6 protocol is in fact connectionless, so when the Ipv6 module receives the connect request, it simply
remembers the remote address, and uses it as the default destination address for later sends.
The application can call connect() several times on the same socket.
For example, the application may directly implement the Ipv6Socket::ICallback interface and print the
received data as follows:
4.7 L3 Socket
The L3Socket class provides an easy to use C++ interface to send and receive datagrams using the conceptual
network protocols. The underlying network protocols are implemented in the NextHopForwarding, Flooding,
ProbabilisticBroadcast, and AdaptiveProbabilisticBroadcast modules.
Messages received from the network protocol module must be processed by the associated socket where as shown
in the general section. The L3Socket deconstructs the message and uses the L3Socket::ICallback inter-
face to notify the application about the received data:
Since the L3Socket class is network protocol agnostic, it must be configured to connect to a desired network
protocol:
L3Socket socket(&Protocol::flooding);
In order to only receive datagrams which are sent to a specific local address or contain a specific protocol, the
socket can be bound to the desired local address or protocol. The conceptual network protocols can work with the
ModuleIdAddress class which contains a moduleId of the desired network interface.
For example, the following code fragment shows how the INET PingApp binds to the Echo protocol to receive all
incoming Echo Reply messages:
socket.bind(&Protocol::echo, ModuleIdAddress(42));
For only receiving datagrams from a specific remote address, the socket can be connected to the desired remote
address:
After the socket has been configured, applications can immediately start sending datagrams to a remote address
via a simple function call:
If the application wants to send several datagrams to the same destination address, it can optionally connect to the
destination:
The network protocols are in fact connectionless, so when the protocol module receives the connect request, it
simply remembers the remote address, and uses it as the default destination address for later sends.
The application can call connect() several times on the same socket.
For example, the application may directly implement the L3Socket::ICallback interface and print the re-
ceived data as follows:
The TunSocket class provides an easy to use C++ interface to send and receive datagrams using a TUN interface.
The underlying TUN interface is implemented in the Tun module.
A TUN interface is basically a virtual network interface which is usually connected to an application (from the
outside) instead of other network devices. It can be used for many networking tasks such as tunneling, or virtual
private networking.
Messages received from the Tun module must be processed by the socket where they belong as shown in the
general section. The TunSocket deconstructs the message and uses the TunSocket::ICallback interface
to notify the application about the received data:
socket.open(interface->getId());
As soon as the TunSocket is associated with a TUN interface, applications can immediately start sending
datagrams via a simple function call:
socket.send(packet);
When the application sends a datagram to a TunSocket, the packet appears for the protocol stack within the
network node as if the packet were received from the network.
Messages received from the TUN interface must be processed by the corresponding TunSocket. The
TunSocket deconstructs the message and uses the TunSocket::ICallback interface to notify the applica-
tion about the received data:
When the protocol stack within the network node sends a datagram to a TUN interface, the packet appears for the
application which uses a TunSocket as if the packet were sent to the network.
FIVE
TESTING
INET contains a comprehensive test suite. The test suite can be found under the tests folder. Testing is usually
done from the command line after setting up the environment with:
$ cd ~/workspace/omnetpp
$ . setenv
$ cd ~/workspace/inet
$ . setenv
The most often used tests are the so called fingerprint tests, which are generally useful for regression testing.
Fingerprint tests are a low-cost but effective tool for regression testing of simulation models during development.
For more details on fingerprint testing, please refer to the OMNeT++ manual.
The INET fingerprint tests can be found under the tests/fingerprint folder. Each test is basically a sim-
ulation scenario described by one line in a CSV file. INET contains several such CSV files for different groups
of tests. One CSV line describes a simulation by specifying the working directory, command line arguments,
simulation time limit, expected fingerprint and test result (e.g. pass or fail), and several user defined tags to help
filtering.
Fingerprint tests can be run using the fingerprinttest script found in the above folder. To run all fingerprint
tests, simply run the script without any arguments:
$ ./fingerprinttest
The script also has various command line arguments, which can be understood by asking for help with:
$ ./fingerprinttest -h
Running all INET fingerprint tests takes several minutes even on a modern computer. By default, the script utilizes
all available CPUs, the tests are run in parallel (non-deterministic order).
The simplest way to make the tests run faster, is to run only a subset of all fingerprint tests. The script provides a
filter parameter (-m) which takes a regular expression that is matched against all information of the test.
Another less commonly used technique is running the fingerprint tests in release mode. One disadvantage of
release mode is that certain assertions, which are conditionally compiled, are not checked.
For example, the following command runs all wireless tests in release mode:
While the tests are running, the script prints some information about each test followed by the test result. The test
result is either PASS, FAIL or ERROR (plus some optional details):
39
INET Framework Developer’s Guide, Release 4.5.4
When testing is finished, the script also prints a short summary of the results with the total number of tests, failures
and errors:
Failures:
/examples/bgpv4/BgpCompleteTest/ -f omnetpp.ini -c config1 -r 0
----------------------------------------------------------------------
Ran 2 tests in 3.615s
FAILED (failures=1)
Apart from the information printed on the console, fingerprint testing also produces three CSV files. One for
the updated fingerprints, one for the failed tests, and one for the test which couldn’t finish without an error. The
updated file can be used to overwrite the original CSV file to accept the new fingerprints.
Usually, when a simulation model is being developed, the fingerprint tests should be run every now and then, to
make sure there are no regressions (i.e. all tests run with the expected result). How often tests should be run
depends on the kind of development, tests may be run from several times a day to a few times a week.
For example, if a completely new simulation model is developed with new examples, then the new fingerprint tests
can be run as late as when the first version is pushed into the repository. In contrast, during an extensive model
refactoring, it’s adivsable to run the affected tests more often (e.g. after each small step). Running the tests more
often helps avoiding getting into a situation where it’s difficult to tell if the new fingerprints is acceptable or not.
In any case, certain correct changes in the simulation model break fingerprint tests. When this happens, there are
two ways to validate the changes:
• One is when the changes are further divided until the smallest set of changes are found, which still break
fingerprints. In this case, this set is carefully reviewed (potentially by multiple people), and if it is found to
be correct, then the fingerprint changes are simply accepted.
• The other is to actually change the fingerprint calculation in a way that ignores the effects of the set of
changes. To this end, you can change the fingerprint ingredients, use filtering, or change the fingerprint
calculation algorithm in C++. These options are covered in the OMNeT++ manual.
With this method, the fingerprint tests must be re-run before the changes are applied using the modified
fingerprint calculation. The updated fingerprint CSV files must be used to run the fingerprint tests after
applying the changes. This method is often time consuming, but may be worth the efforts for complex
changes, because it allows having a far greater confidence in correctness.
For a simple example, a timing change which neither changes the order of events nor where the events happen, can
be easily validated by changing the fingerprint and removing time from the ingredients (default is tplx) as follows:
Another common example is when a submodule is renamed. Since the default fingerprint ingredients contain the
full module path of all events, this change will break fingerprint tests. If the module is not optional or not accessed
by other modules using its name, then removing the module path from the fingerprint ingredients (i.e. using tlx)
can be used to validate this change.
For a more complicated example, when the IEEE 802.11 MAC (very complex) model is refactored to a large
extent, then it’s really difficult to validate the changes. If despite the changes, the model is expected to keep its
external behavior, then running the fingerprint tests can be used to prove this to some extent. The easy way to do
this is to actually ignore all events executed by the old and the new IEEE 802.11 MAC models:
# please note the quoting uses both ' and " in the right order
./fingerprinttest -a --fingerprint-modules='"not(fullPath(**.wlan[*].mac.**))"'
INET uses continuous integration on Github. Fingerprint and other tests are run automatically for changes on the
master and integration branches. Moreover, all submitted pull requests are automatically tested the same way. The
result of the test suite is clearly marked on the pull request with check mark or a red cross.
In general, contributors are expected to take care of not breaking the fingerprint tests. In case of a necessary
fingerprint change, the CSV files should be updated in separate patches.
40 Chapter 5. Testing
CHAPTER
SIX
6.1 Overview
This chapter is intended for authors and contributors of this INET Developer’s Guide, and covers the guidelines
for deciding what type of content is appropriate for this Guide and what is not.
The main guiding principle is to avoid redundancy and duplication of information with other pieces of documen-
tation, namely:
• Standards documents (RFCs, IEEE specifications, etc.) that describe protocols that INET modules imple-
ment;
• INET User’s Guide, which is intended for users who are interested in assembling simulations using the
components provided by INET;
• INET Framework Reference, directly generated from NED and MSG comments by OMNeT++ documenta-
tion generator;
• Showcases, tutorials and simulation examples (showcases/, tutorials/ and examples/ folders in
the INET project)
Why is duplication to be avoided? Multiple reasons:
• It is a waste of our reader’s time they have to skip information they have already seen elsewhere
• The text can easily get out of date as the INET Framework evolves
• It is extra effort for maintainers to keep all copies up to date
6.2 Guidelines
When describing a module that implements protocol X, do not go into lengths explaining what protocol X does
and how it works, because that is appropriately (and usually, much better) explained in the specification or books
on protocol X. It is OK to summarize the protocol’s goal and principles in a short paragraph though.
In particular, do not describe the format of the protocol messages. It surely looks nice and takes up a lot of space,
but the same information can probably be found in a myriad places all over the internet.
41
INET Framework Developer’s Guide, Release 4.5.4
Things like module parameters, gate names, emitted signals and collected statistics are appropriately and formally
part of the NED definitions, and there is no need to duplicate that information in this Guide.
Detailed information on the module, such as usage details and the list of implemented standards should be covered
in the module’s NED documentation, not in this Guide.
Describing every minute detail of C++ classes, methods, arguments are expected to be appropriately present in
their doxygen documentation.
Concentrate on giving a “big picture” of the implementation: what it is generally capable of, how the parts fit
together, how to use the provided APIs, what were the main design decisions, etc. Give simple yet meaningful
examples and just enough information about the API that after a quick read, users can “bootstrap” into imple-
menting their own protocols and applications. If they have questions afterwards, they will/should refer to the C++
documentation.