Professional Documents
Culture Documents
6 Developer’s Guide
Phone: 310-338-3318
Fax: 310-338-7213
http://www.scalable-networks.com
http://www.qualnet.com
Copyright Information
All other trademarks and trade names used are property of their respective companies.
CHAPTER 1
Introduction to QualNet .................................................... 1
Chapter Overview ......................................................... 1
CHAPTER 2
QualNet Architecture ........................................................ 3
Chapter Overview ......................................................... 3
QualNet Components ................................................... 4
File Organization........................................................... 5
Obtaining and Installing QualNet.............................................. 5
Directory Organization.............................................................. 6
Compiling QualNet ................................................................... 6
Active optimization and Debugging....................................................... 8
Customized Development: Modifying makefiles ....................... 8
Addons ........................................................................ 11
Addon Overview ..................................................................... 11
Activating an Addon Module................................................... 11
Creating an Addon.................................................................. 12
The 'Hello' Addon ................................................................................ 12
CHAPTER 4
Networking Using QualNet ............................................. 61
Chapter Overview ....................................................... 61
Application Layer ....................................................... 62
Application Layer Overview .................................................... 62
Application Layer Protocols In QualNet .................................. 63
Traffic Generating Protocols ................................................................64
Service Providing (Routing) Protocols .................................................65
Application Layer Organization: Files And Folders ................. 66
CHAPTER 5
QualNet Graphical User Interface (GUI)....................... 229
Chapter Overview ..................................................... 229
Overview of GUI Components ................................. 230
Using QualNet Animator .......................................... 231
Adding New GUI Parameters and Protocols ........................ 231
Variable Token .................................................................................. 232
Option Token..................................................................................... 235
Categories, Subcategories, and Applications ................................... 236
1. Chapter Overview
This chapter provides an overview of the QualNet Developer’s Guide, it’s
purpose, and what to expect in it.
The intended audience of the Developer’s Guide are developers who want to use
the interface and programming functions in QualNet for their own simulation
purposes.
1. Chapter Overview
This chapter on QualNet Architecture covers the following topics:
• QualNet Components
• File Organization
• Add-ons
2. QualNet Components
QualNet Developer has several core components, as well as various add-on
components.
Descriptions, functions, and usage instructions for each of the QualNet Developer
components is available in the QualNet User Manual. Contact
support@qualnet.com for more information on this and other QualNet
documentation.
On the download page, all of the resources necessary to install QualNet are
available, including optional QualNet model libraries and the latest QualNet user
manual.
To get started, select the appropriate installation guide PDF file (requires Adobe
Acrobat) under 'Installation Packages' and follow the instructions provided. Users
should complete the Product Tour PDF file to become familiarized with QualNet's
features. Access to this file is available until the license expires.
After installation, copy the license file into QualNet's license_dir directory. QualNet
evaluators receive this file as an e-mail attachment from SNT.
For users that have purchased QualNet, copy the client.lic file to the license_dir
directory. If this file cannot be found, please inquire with the person(s) responsible
acquiring QualNet.
Users running Red Hat Linux 7.1 or higher that receive the error Invalid
(inconsistent) license key or observe an immediate segmentation fault when
attempting to run QualNet should access the solution to this problem at http://
www.scalable-networks.com/help/index.html. Click on the Search tab and type
Invalid (inconsistent) license key in the search field.
The following table lists the default QualNet subdirectories and a brief description
of their content:
cp Makefile-linux Makefile
cp Makefile-solaris Makefile
cp Makefile-solaris-cc Makefile
When run without parameters, nmake and make will use a file named
Makefile. The user can also type nmake -f Makefile-windowsnt or
make -f Makefile-linux if the copy step is not performed.
This will create the QualNet executable in the bin/ directory. In Windows, the
executable is qualnet.exe; in UNIX, the executable is qualnet.
DEBUG = -g
#OPT = -O3
For Windows:
DEBUG = /Zi
#OPT = /Ox /Ob2
Standard debugging tools such as gdb and Visual C++ Debugger can be used
with QualNet.
The Windows and Unix Makefiles only include one common file because the
Visual C++ development environment differs from Unix compilers and their
common set of commands. However, the general organization of the files is
similar. The main/Makefile-common-[platform] file is for making changes to the
OPT and DEBUG macros, as well as including any Addon Makefiles. Other
modifications should be made to the files that it includes. For instance, the user
can add or modify compiler flags in main/Makefile-[platform]-common files.
Example:
CFLAGS = \
$(INCLUDE_DIRS) \
$(DEBUG) \
$(OPT) \
$(ADDON_OPTIONS) \
-DTEST_FLAG
QualNet is usually distributed with only one addon, addons/seq/. This seq addon
provides sequential execution functionality to QualNet—this is in contrast to the
par addon, which provides parallel execution on multiple processors.
Addons are an important way in which the user can modularize a protocol,
propagation model, map format decoder, or other capability. The following
sections cover how to activate a received addon, and also how to create an
addon.
Addon modules are available for download from the same location as the main
QualNet distribution
After downloading the appropriate file, extract the contents to the QualNet
installation directory. Be sure to include the addon Makefile in the QualNet
Makefile-<platform> in the /main subdirectory. Place the include line in the
addons section of the Makefile-<platform>. If the user had the Link16 addon and
were running Windows, they would add the following beneath the # INSERT
ADDONS HERE line:
include ../addons/dted/Makefile-windows
Save the file and then rebuild QualNet using make clean and make, or nmake
clean and nmake if running Windows. Then execute QualNet using the config file
(a sample config file may be provided).
#ifndef HELLO_H
#define HELLO_H
#endif
#include <stdlib.h>
#include "api.h"
#include "hello.h"
The ‘hello’ addon will only contain one source file, but as many as required can
be added.
4. In the hello addon directory, create the necessary Makefiles for each platform
and a Makefile-common that defines which source files to include.
Makefile-common:
HELLO_OPTIONS = -DADDON_HELLO
HELLO_HDRS = \
../addons/hello/hello.h
HELLO_SRCS = \
../addons/hello/hello.c
HELLO_INCLUDES = \
-I../addons/hello
Makefile-windows:
include ../addons/hello/Makefile-common
Makefile-unix:
include ../addons/hello/Makefile-common
ADDON_OPTIONS += $(HELLO_OPTIONS)
ADDON_HDRS += $(HELLO_HDRS)
ADDON_SRCS += $(HELLO_SRCS)
ADDON_INCLUDES += $(HELLO_INCLUDES)
ADDON_LIBRARIES +=
#ifdef ADDON_HELLO
#include "hello.h"
#endif /* ADDON_HELLO */
#ifdef ADDON_HELLO
AppHelloInit(node, nodeInput);
#endif /* ADDON_HELLO */
Within APP_ProcessEvent:
#ifdef ADDON_HELLO
case APP_HELLO:
{
AppHelloProcessEvent(node,msg);
break;
}
#endif /* ADDON_HELLO */
Within APP_Finalize:
#ifdef ADDON_HELLO
case APP_HELLO:
{
AppHelloFinalize(node);
break;
}
#endif /* ADDON_HELLO */
#ifdef ADDON_HELLO
APP_HELLO,
#endif /* ADDON_HELLO */
1. Chapter Overview
• Events
• Experiment Configuration
• Layering
• Communication Between Layers
2. Events
This section covers the following topics:
• Events Overview
• Events and Messages
• Types of Events
• Event Handling
1. Initialization
2. Event dispatcher
3. Finalization
Example:
MSG_APP_FromNetwork
MSG_Route_FromNetwork
The QualNet kernel uses the above fields associated with a message to call the
appropriate function to process a message. Detailed discussion on both timer and
packet events follows.
Packet events are used to simulate sending of packets across the network. A
packet is defined as a unit of virtual or real data at any layer of the protocol stack.
When a node needs to send a packet to an adjacent layer in the QualNet protocol
stack, it schedules a packet event at the adjacent layer. The occurrence of the
packet event at the adjacent layer simulates the arrival of the packet.
When an application sends packets from one node to another, the packet is
passed down through the protocol stack on the sending node, across the network,
and then up through the protocol stack on the receiving node. At each level of the
protocol stack, header information is added to the packet as it is sent. Each layer
is responsible for sending the packet to its adjacent layer. On the receiving node,
this information is stripped off until the original packet is finally available to the
receiving application. In QualNet this header information is added/ stripped using
the Message API. This process looks something like the following illustration:
The layer API functions vary for each layer. The API calls available at each layer
to send packets are discussed in the Developer's Guide section corresponding to
each layer. To understand the available APIs at each layer, look up API functions
used for sending packets in the source code of QualNet native protocols operating
at that layer. Although each layer's API details are not discussed here, however as
2.3.1.1 Sending Packets From Application Layer Using Layer Specific API
Packet exchange API at application layer falls into two categories:
• Exchanging packets with UDP
• Exchanging packets with TCP
The following table lists details of the API calls available for sending packets at
application layer using UDP at the transport layer:
The following are the API calls available for sending packets at application layer
using TCP at transport layer:
At this point, the user should be able to understand the following piece of code,
which uses the API function APP_UdpSendNewDataWithPriority mentioned
previously, to send packets from application layer. The code is taken from the
Ripv2 implementation in QualNet (QUALNET_HOME/application/ripv2.c). Notice
how initially a response variable is allocated. This variable is the packet payload. It
is then filled with information (command and version). The code then gets the
destination address and calls the API function to send the packet out. The
parameters to APP_UdpSendNewDataWithPriority are explained in the figure.
The layer specific API uses the message API to create and send packets. The
layer API provides easy function calls for sending packets through the protocol
stack. It acts as an abstraction layer that hides detailed function calls to the raw
message API.
However sometimes it may be desirable to bypass the layer specific API. This
may be due to the protocol being developed residing in a protocol stack not
supported by the layer specific API, or due to certain specifics of protocol design.
This section describes details of how to send packets using the message API.
void
APP_UdpSendNewDataWithPriority(
Node *node,
AppType appType,
NodeAddress sourceAddr,
short sourcePort,
NodeAddress destAddr,
int outgoingInterface,
char *payload,
int payloadSize,
NetworkQueueingPriorityType priority,
clocktype delay,
TraceProtocolType traceProtocol)
{
Message *msg;
AppToUdpSend *info;
msg = MESSAGE_Alloc(
node,
TRANSPORT_LAYER,
TransportProtocol_UDP,
MSG_TRANSPORT_FromAppSend);
The payload is then copied into the allocated payload field of the packet.
Additional information can be allocated using MESSAGE_InfoAlloc (discussed in
detail in Message Info Field section). The packet is then sent to the next layer
using the MESSAGE_send function.
void
TransportUdpSendToNetwork(Node *node, Message *msg)
{
TransportDataUdp *udp = (TransportDataUdp *) node->transportData.udp;
TransportUdpHeader *udpHdr;
AppToUdpSend *info;
if (udp->udpStatsEnabled == TRUE)
{
udp->statistics->numPktFromApp++;
}
udpHdr->sourcePort = info->sourcePort;
udpHdr->destPort = info->destPort;
udpHdr->length = MESSAGE_ReturnPacketSize(msg);
udpHdr->checksum = 0; /* checksum not calculated */
The previous figure shows the code that executes when the packet sent using the
code in Figure 4 on page 26 arrives at the transport layer. Notice how API function
MESSAGE_AddHeader is used to add header before a packet. This function
reserves additional space in the packet, for a header. The header size is specified
by the third parameter. MESSAGE_AddHeader also appropriately increases thee
packetSize variable in the message structure. After this function is called, the
packet variable in the message structure will point the space occupied by this new
header.
Notice how after the header information is set, Transport layer specific API
function NetworkIpReceivePacketFromTransportLayer is called to send the
packet to the next layer (Network, IP Layer). In this way the packet travels down
the protocol stack with each layer adding its own header. This is graphically
illustrated in Figure 2, “The Life Cycle of a Packet,” on page 22.
The Message Info field is generally used with timers, although it can be used with
packets also. It is commonly used to store additional information with a timer e.g.
for route expired timer, the message info field can be used to store the destination
address for the expired route. This can assist the timer event handler to locate the
correct entry in the routing table and remove it.
The following are the API functions provided for using this field:
After the message info field has been allocated, it can be accessed using API
function MESSAGE_ReturnInfo. The size of the info field can be read using
MESSAGE_ReturnInfoSize. The following code snippet from QUALNET_HOME/
application/cbr.c, function AppCbrClientInit, illustrates how to set this field in an
outgoing message:
AppTimer *timer;
Message *timerMsg;
timerMsg = MESSAGE_Alloc(node,
APP_LAYER,
APP_CBR_CLIENT,
MSG_APP_TimerExpired);
Timer events are used to perform the function of alarms. They essentially allow
applications to schedule events for a future time. Periodic alarms are
implemented by re-setting the timer event after it is fired. Timer events are set and
received by the same protocol and they do not travel through the protocol stack.
Example:
Timer alarm to remove expired route from routing table 3 seconds after it is
installed
Message *newMsg;
newMsg = MESSAGE_Alloc(node,
APP_LAYER,
APP_ROUTING_RIPV2,
MSG_APP_RIPV2_RegularUpdateAlarm);
Set a clocktype variable (delay) to the number of seconds from the current
simulation time after which this event should occur. When delay is set to 0, the
event occurs immediately (before the current function finishes execution).
clocktype delay;
delay = 5* SECOND;
It may be required to store some additional information with the timer. Message
info field is used with timers for this purpose. As an example, consider a timeout
timer for missed packet reception, e.g. ACK. In this case the Info Field can store
the sequence number, and neighbor IP address of the packet for which it expects
an ACK. These are discussed in detail in the Message Info Field section.
Do not free the message explicitly or re-use the message after canceling it. The
function MESSAGE_CancelSelfMsg also frees the memory associated with the
message.
When an event occurs, the QualNet kernel gets a handle to the node for which the
event is scheduled. It then calls a dispatcher function in QUALNET_HOME/
addons/seq/node.c called NODE_ProcessEvent. This function determines the
layer for which the event has occurred and calls the LAYER_ProcessEvent
function for the appropriate layer e.g. if the packet/event is for the application
layer, the function APP_ProcessEvent in application.c gets called.
snippet in Figure 6. The code snippet contains sections of code from the
application layer dispatcher. The developer needs to add his protocol to the
appropriate layer dispatcher, from where the protocol's event dispatcher gets
called.
The protocol event dispatcher like the other dispatcher functions comprises of a
series of switch statements. It calls the required event handler function of the
protocol. The event handler is code specific to the event and performs the
required action on the occurrence of the event e.g. TransportUdpSendToApp
(QUALNET_HOME/transport/udp.c) is event handler for packet traveling up the
stack. It removes the UDP header and passes the packet to the application layer.
3. Experiment Configuration
This section covers the following topics:
• Reading Input from a Configuration File
• Adding Trace Collection
• Adding Statistics
• Collecting Statistics
• Timed-based Statistics
The qualifier field is used to specify a specific node that the parameter's value will
be valid for. The instance field allows multiple values of the parameter to be
specified in the experiment (no correlation to specific nodes). Both fields are
optional, and the instance takes precedence over the qualifier.
The value specified for a variable in the configuration file can take several forms:
string, integer, double, float, and clocktype. The QualNet API supplies a function
for reading each variable format from the configuration file.
A synonymous usage of "instance ID" is array index. The types of variables that
require instance IDs are typically arrays of values, such as for priority queues. In
the default configuration each node has three priority queues on each interface.
To set the values of the interface queues, the user would make use of instance
IDs such as the following:
QUEUE-WEIGHT[0] 0.5
QUEUE-WEIGHT[1] 0.3
QUEUE-WEIGHT[2] 0.2
For example, to read a string, one would use the following API:
void
IO_ReadString(
const NodeAddress nodeId,
const NodeAddress interfaceAddress,
const NodeInput *nodeInput,
const char *index,
BOOL *wasFound,
char *readVal);
The node's ID and address are passed to the function (in case a qualifier is
specified in the parameter), along with a pointer to the data representation of a file
(nodeInput), usually representing the configuration file. As expected, the variable
name (index) and destination string pointer (readVal). A pointer to a boolean
variable is also passed in (wasFound), to inform the caller if the parameter was
found in the file.
The API also supplies a set of similar functions for reading parameters with the
instance field.
As an illustration, the mirrored function for the above string example is as follows:
void
IO_ReadStringInstance(
const NodeAddress nodeId,
const NodeAddress interfaceAddress,
const NodeInput *nodeInput,
const char *parameterName,
const int parameterInstanceNumber,
const BOOL fallbackIfNoInstanceMatch,
BOOL *wasFound,
char *parameterValue);
There are two extra parameters in the instance version of the readString function.
One is fairly self-explanatory, parameterInstanceNumber, which identifies which
instance of the parameter is to be read. The other, fallbackIfNoInstanceMatch, is a
boolean that dictates if a parameter without an instance number is chosen if there
is no instance match.
Besides reading the standard types described above, the QualNet I/O API allows
to treat a parameter value as a file name, and store the file's content into a
NodeInput data structure.
void
IO_ReadCachedFile(
const NodeAddress nodeId,
const NodeAddress interfaceAddress,
const NodeInput *nodeInput,
const char *parameterName,
BOOL *wasFound,
NodeInput *parameterValue);
This is similar to the previous functions described, except that the parameter's
value will be treated as a filename where the cached contents will be stored in
parameterValue. There is also an instance version of this function.
IO_ReadString(
node->nodeId,
ANY_ADDRESS,
nodeInput,
"TRACE-UDP",
&retVal,
buf);
if (retVal) {
if (strcmp(buf, "YES") == 0) {
TRACE_EnableTrace(node, TRACE_UDP);
} else if (strcmp(buf, "NO") == 0) {
TRACE_DisableTrace(node, TRACE_UDP);
}
3. Create a function that prints out the protocols header. This function will be
called when the header needs to be traced.
MESSAGE_ReturnPacket(msg);
sprintf(buf, "%d", udpHdr->sourcePort);
IO_PrintTrace(node, buf);
sprintf(buf, "%d", udpHdr->destPort);
IO_PrintTrace(node, buf);
sprintf(buf, "%d", udpHdr->length);
IO_PrintTrace(node, buf);
sprintf(buf, "%d", udpHdr->checksum);
IO_PrintTrace(node, buf);
}
4. Call the QualNet printTrace function in appropriate places of the protocol. The
printTrace API is usually called when the protocol is sending a packet to an
adjacent layer (the time a header is added or removed).
void
TRACE_PrintTrace(Node *node, Message* message,
TraceLayerType layerType);
5. Make sure to pass the protocol's trace enumeration value in calls for adding
and removing message headers and allocating packet payload. The following
are the APIs for adding/removing headers, and allocating packet payload.
8. Enable the QualNet Tracer GUI to recognize the new traced headers. There
are two file formats to be concerned with for Tracer to read the produced trace
file.
• The Header Description File
The header description file is located in
QUALNET_HOME\gui\settings\tracerheaderdescription.txt.
This file tells Tracer how to interpret the headers in the trace file.
The format of this file is as follows:
For example UDP has been designated protocol 1 in the trace protocol
enumeration, and its header is 8 bytes (consisting of four 2-byte fields).
<header 1 8 UDP>
Source Port,2,int
Destination Port,2,int
Length,2,int
Checksum,2,int
</header>
Add a <header> entry for the new protocol in this file as shown above.
• The Trace File
The trace file format is involved. The important thing to learn is
that each header is printed as a comma-separated list of integers
corresponding to the fields described in the Header Description File. It is not
necessary to make any changes to the trace file.
Each of the following sections of each line are separated by semicolons.
typedef struct {
int BytesSent;
int BytesReceived;
} VbrStatsType;
Be sure to add an instance of this structure into the protocol data structure field
as well.
struct struct_vbr_str
{
...
...
2. Create a function to initialize statistics in the protocol source file. All of the
statistics variables must be initialized. Create a function that sets all the
variables to zero.
In the initialization function, check if statistics are enabled for the current
protocol, and call the initStats routine above accordingly.
if (node->appData.appStats == TRUE) {
VbrInitStats(node, dataPtr);
}
Use the following list to determine if statistics are enabled for the current layer
the model resides on.
• Application Layer—node->appData.appestats
• Application Routing—node->appData.routingStats
• Transport Layer—Protocol specific parameter in configuration file (See
UDP)
• Network Layer—ROUTING-STATISTICS parameter in configuration file
• MAC Layer—node->macData[interfaceIndex]->macStats
3. Create a function that outputs statistics. Every protocol needs a function that
will output statistics once the protocol has finished running it's course in the
simulation. This function will use the IO_PrintStat QualNet API to write out
statistics to the simulation statistics file.
This function is to be called from the protocol's finalize procedure. This function
call is contingent on statistics being enabled, so we must do a similar check as
the one done in the initialization function.
if (node->appData.appStats == TRUE) {
VbrPrintStats(node, dataPtr);
}
If the format above was used to add statistics to a protocol, statistic variables
should be accessible by the following convention in the protocol code:
dataPtr->stats.<statsVariableName>
int <statisticName>ID;
typedef struct {
int BytesSent;
int BytesSentId;
int BytesReceived;
int BytesReceivedId;
} VbrStatsType;
2. Initialize the statistic handles in the InitStats function. The QualNet API that
creates unique handles for statistics is GUI_DefineMetric (can be found in
main/gui.c).
The name variable specifies the description label of the statistic in the GUI.
The linkID refers to the application session ID (zero if not applicable). The
GuiLayers, GuiDataTypes, and GuiMetrics enumeration definitions can be
found in include/gui.h.
GuiLayers specifies the protocol layer the model resides at. GuiDataTypes
specifies the statistic's data type (GUI_INTEGER_TYPE,
GUI_DOUBLE_TYPE, GUI_UNSIGNED_TYPE). GuiMetrics specifies if the
statistic is cumulative or averaged (GUI_CUMULATIVE_METRIC,
GUI_AVERAGE_METRIC).
In the InitStats procedure that was created in the Adding Stats section would
contain all the calls to the DefineMetric routine:
stats->BytesReceivedId = GUI_DefineMetric("Total
Bytes Received",
node->nodeId,
GUI_APP_LAYER, 0,
GUI_INTEGER_TYPE,
GUI_CUMULATIVE_METRIC);
}
...
if (node->guiOption) {
GUI_SendIntegerData(node->nodeId, dataPtr-
>stats.BytesSentId,
dataPtr->stats.BytesSent, now);
GUI_SendIntegerData(node->nodeId, dataPtr-
>stats.BytesReceivedId,
dataPtr->stats.BytesReceived, now);
}
}
void
APP_RunTimeStat(Node *node) {
…
…
switch (appList->appType) {
…
…
case APP_VBR_CLIENT:
case APP_VBR_SERVER:
{
VbrRunTimeStat(node, (VbrData *)
appList->appDetail);
}
break;
The RunTimeStat facility is now complete. The entire stats/runtime stats tutorial is
based on creating protocols by hand. If a protocol is created via Designer, all
statistic code is generated and inserted automatically into the protocol files.
The other alternative to pass data between adjacent layers is to use function calls.
Below is an example of how data is passed from the transport to the network
layer, called within QUALNET_HOME/transport/udp.c using
NetworkIpReceivePacketFromTransportLayer().
NetworkIpReceivePacketFromTransportLayer(
node,
msg,
info->sourceAddr,
info->destAddr,
info->outgoingInterface,
info->priority,
IPPROTO_UDP,
FALSE);
Although the design philosophy of QualNet is to utilize the network stack for
communication between layers and then across layers, it is possible to also have
protocols communicate between non-adjacent layers. For instance, protocols
written at the application layer, if desired, may bypass the transport layer and
communicate directly with the network layer. This procedure would entail the
application protocol to use the network layer API calls instead of the transport
layer APIs.
Now go over code snippets of what modifications are required for data passing
from CBR to the network layer, and omitting UDP.
Start by looking at how to modify CBR to bypass UDP and communicate directly
with IP (network layer). To do so, declare the following in QUALNET_HOME/
application/cbr.c.
#include "ip.h"
// Create a message to hold the data. Use 0’s as we are not using
// MESSAGE_Send() to pass data to IP. We use a function call instead.
// Therefore, we do not care what values are being initialized.
msg = MESSAGE_Alloc(node, 0, 0, 0);
// Allocate enough payload within the message to store the CBR data.
MESSAGE_PacketAlloc(
node,
msg,
sizeof(CbrData),
TRACE_CBR);
The comments in the code shown in the previous figure explain the function of
each statement. Now that communication from CBR to IP has been covered,
communication from IP to CBR comes next. In QUALNET_HOME/network/ip.h,
first add the following line:
#define IPPROTO_CBR 22
This allows IP to put in its header which protocol IP should pass the packet to
once it is received at the destination node. The value of 22 is picked arbitrarily
(making sure that 22 is not already in use by another protocol). Then, the user
should define the function SendToCbr() in QUALNET_HOME/network/ip.c. This is
shown in Figure 10, “Code to bypass UDP transport layer”
void SendToCbr(
Node *node,
Message *msg,
NetworkQueueingPriorityType priority,
NodeAddress sourceAddress,
NodeAddress destinationAddress,
int incomingInterfaceIndex)
{
CbrData data;
UdpToAppRecv *info;
info->sourceAddr = sourceAddress;
info->sourcePort = data.sourcePort;
info->destAddr = destinationAddress;
info->destPort = APP_CBR_SERVER;
info->incomingInterfaceIndex = incomingInterfaceIndex;
case IPPROTO_CBR:
{
SendToCbr(node, msg, priority, sourceAddress,
destinationAddress, interfaceIndex);
break;
}
The previous case statement tells IP to send the packet up to CBR for further
processing (once IP receives data from the link (MAC) layer) if the protocol
number in the IP header is IPPROTO_CBR.
Of course, the user could also disregard the existing APIs (such as
NetworkIpReceivePacketFromTransportLayer()) and create a new set of APIs
between the desired layers, either with new functions or message send's. In
general, communication among layers may be non-adjacent as long as the APIs
in the layers of interest are met or new APIs are created for them.
First include partition.h to access the partition data structure, which are required to
map the destination node ID to a Node pointer.
#include "partition.h"
Next, replace the code snippet in Figure 10, “Code to bypass UDP transport layer”
with the following code snippet depicted in Figure 11, “Code sample to bypass
layers and directly communicate between nodes” .
// Get the destination Node pointer associated with the destination nodeId.
destNode = MAPPING_GetNodePtrFromHash(node->partitionData->nodeIdHash,
destId);
info->sourceAddr = clientPtr->localAddr;
info->sourcePort = clientPtr->sourcePort;
info->destAddr = clientPtr->remoteAddr;
info->destPort = APP_CBR_SERVER;
info->incomingInterfaceIndex = 0;
// Send the message to the destination node with a fixed 1ms delay.
MESSAGE_Send(destNode, msg, 1 * MILLI_SECOND);
The comments in the code from the previous figure explain what each line of code
does. Note the first parameter in the MESSAGE_Send() call. The variable
destNode is used instead of node. By using destNode, QualNet knows to send the
message to the destination node directly. While the name UdpToAppRecv could
be changed to be more descriptive. However, for simplicity in this example,
UdpToAppRecv is used.
In general, direct communication across nodes may occur as long as the APIs in
the layers of interest are met or new APIs are created for them.
Developers familiar with the seven layers of networking may want to know how
those layers work with QualNet. QualNet follows the protocol stack approach,
similar to that of TCP/IP protocol stack, but can also deviate from such as stack.
1. Chapter Overview
• Application Layer
• Transport Layer
• Network Layer
• MAC Layer
• Physical Layer
2. Application Layer
This section covers the following topics:
• Application Layer Overview
• Application Layer Protocols In QualNet
• Application Layer Organization: Files And Folders
• Writing and Adding an Application Protocol
• Tips For Application Layer Routing Protocol
• Writing and Adding an Application Layer Routing Protocol
• Using QualNet Designer For Application Layer Development
Figure 1, “The Application Layer,” on page 63, illustrates application layer residing
at the top in the QualNet protocol stack. Since the application layer resides on top
of transport layer it is responsible for interfacing with the transport layer and
passing messages to and from the transport layer. It uses this interaction to
implement functionality, such as ensuring available resources for communication
and synchronizing data transmission.
This layer includes protocols like Telnet, CBR, and File Transfer Protocol (FTP).
An understanding of this layer is important for modeling today's networks.
The following section describes the different kinds of application layer protocols in
QualNet.
• QUALNET_HOME/include/application.h
This file contains definitions common to application layer protocols and
application data structure in node structure.
• QUALNET_HOME/application /app_util.h
This file contains prototypes of the functions defined in app_util.c.
Additionally, the following header files are also relevant to the application layer:
• QUALNET_HOME/include/fileio.h
• QUALNET_HOME/include/mapping.h
These files are described in greater detail in the application layer section of the
API reference document. Alternatively the source code also contains detailed
comments on functions and other code components.
The following are the source files (*.c) associated with the application layer:
• QUALNET_HOME/application
This folder contains the source and header files for the various QualNet native
applications. The file names are indicative of the application for which they
provide an implementation e.g. to see the implementation for CBR (Constant
Bit Rate), look at cbr.c and cbr.h files.
• QUALNET_HOME/application/application.c
This file contains initialization function, message processing function, and
finalize function used by application layer.
Although the working of each application layer protocol is different, there are
certain functions that are performed by most application layer protocols. This
section provides an architectural overview of the flow of an application layer
protocol and gives an insight into developing and adding an application layer
protocol to QualNet. It describes in detail how to develop code components
common to most application protocols such as sending packet, receiving packets
etc. As these steps are followed, users will see the implementation code for CBR
(Constant Bit Rate), which is one of most frequently used protocols.
It is strongly recommended to have separate header and source files. Not having
a header file, sometimes leads to unexpected problems even if the compilation
process does not indicate any error.
Next, add these files to the QualNet source tree. Modify the file
QUALNET_HOME/main/Makefile-common to include the created files. Add the
header file (*.h) to the simulator header files section and the source file(*.c) to the
simulator source file section of Makefile-common. This addition ensures that the
files will be compiled. The files are sorted alphabetically for simplicity, but it
doesn't matter where it is inserts into the list.
Test that the files are successfully added to the QualNet source tree by compiling
QualNet. This is done by performing the following steps:
• cd main
• Copy Makefile-windowsnt to Makefile
While adding code to the files, it’s important to organize code well between the
files. The header file should generally contain the following:
• Prototypes for functions in source file
• Constant definitions: All # defines
• Data structure definitions and data types: struct, enums
Add the following files to the source file. A typical application layer file includes
these files:
#include <stdio.h>
• Dispatcher code
• Protocol implementation functions
Where:
• Keyword: Uniquely identifies the protocol.
• Param1 Param2 .. ParamN: User specified configuration parameter values. An
application protocol may have any number of required /optional parameters.
For example, in order to specify CBR traffic in the default.app configuration file,
make entries according to the following format:
Where:
• <src> refers to the client node's nodeId or IP Address.
• <dest> indicates the server node's nodeId or IP Address.
• <items_to_send> specifies the number of items to send.
• <item_size> specifies the size of each item.
• <interval> indicates the pause time between transmission of each item.
• <start_time> indicates when the transmissions should begin.
• <end_time> indicates when the transmissions should cease
The time is always specified in QualNet Time Format which explained in greater
detail in the simulator manual.
The following example specifies that the node with nodeId 1 (node 1) will send
500 2-kilobyte items to node 2, sending one per minute, starting from 50
simulation seconds into the total simulation time, and ending 100 seconds later:
Similarly decide on the configuration input format for the application. The next
section explains how to read this user input to initialize the application.
And
Or
Always add to the end of lists in header files. QualNet's pre-built object files use
the values which existed when the object files were created. By inserting the
constant at the top of the list, the values below will be offset in any new object files
and may lead to the simulator crashing.
Specify the event dispatcher function of the protocol. This function is called when
event / messages are received for the protocol. Event dispatchers are explained
in detail in Chapter 3, “Events and Messages” on page 20.
the dispatcher function of the destination protocol. To call the protocol's event
dispatcher function, add code to this function.
Messages/Events contain the name of the protocol that they are destined for.
APP_ProcessEvent implements a switch statement on the protocol name read
from the message. This is the application protocol name specified in the
enumerated data type called AppType. To enable the protocol to receive events,
add code to APP_ProcessEvent to call the protocol's event dispatcher function
when messages for the protocol are received. For a traffic generating application
protocol, do this separately for both the server and the client.
explains how to read these user specified configuration parameters for the
application protocol and providing them to the protocol's initialization function.
The parameters containing time related information are converted from string to
QualNet's clocktype variables by calling QualNet library function
TIME_ConvertToClock.
AppCbrClientInit(
node,
sourceAddr,
destAddr,
itemsToSend,
itemSize,
interval,
startTime,
endTime,
tos,
isRsvpTeEnabled);
}
node = MAPPING_GetNodePtrFromHash(nodeHash, destNodeId);
if (node != NULL)
{
AppCbrServerInit(node);
}
Like all other functions belonging to the application, the prototype for init should
be included in the application's header file.
After understanding the discussed snippets, look at the complete init code of a
protocol. CBR is a starting point because many of its code fragments are covered
in other sections.
To store the state, declare the structure to hold the protocol state in the header
file. Then, create an instance of the application by allocating memory to the state
structure.
AppDataCbrClient *cbrClient;
cbrClient =
(AppDataCbrClient*)MEM_malloc(sizeof(AppDataCbrClient));
cbrClient->interval = interval;
cbrClient->sessionStart = getSimTime(node) + startTime;
APP_RegisterNewApp(node, APP_CBR_CLIENT, cbrClient);
return cbrClient;
The next step after creating the application instance is to register the instance as
one of the protocols running at the node. This is done by making a call to
APP_RegisterNewApp, which is a QualNet library function to add an application
to the list of applications running at the node. When the application needs to
access it's state variable, it retrieves it from this list. This list is maintained using
the AppInfo data structure defined in QUALNET_HOME/include/application.h.
At later stages in the protocol the list can be traversed. This is useful when a
handle needs to get to an application instance; at this point, find the state of the
if (cbrClient->sourcePort == sourcePort)
{
return cbrClient;
}
}
}
return NULL;
}
This section discusses in detail how to use timers. Since each node can have
multiple applications of the same type, application layer timers frequently use
message info field, to distinguish which application instance the timer is for.
The Timer Type can be one of the following three pre-defined types:
• Name: APP_TIMER_SEND_PKT
Purpose: Timer to send a packet. Used to simulate data sending rate
• Name: APP_TIMER_UPDATE_TABLE
Purpose: Timer to close a session. Used to notify of application end time.
• Name: APP_TIMER_CLOSE_SESS
Purpose: Timer to update local table e.g., update entries, remove timed-out
entries from a table etc.
The following code from AppCbrClientInit, is used by CBR to set a timer to inform
itself of when to start sending data. It demonstrates how a timer can store the
source port of the application instance in the message info field. This source port
is used to identify the instance of the CBR application, incase there are multiple
CBR applications running at the node. The timer type used is
APP_TIMER_SEND_PKT because it notifies an application to start sending
packets.
timerMsg = MESSAGE_Alloc(node,
APP_LAYER,
APP_CBR_CLIENT,
MSG_APP_TimerExpired);
MESSAGE_InfoAlloc(node, timerMsg, sizeof(AppTimer));
timer = (AppTimer *)MESSAGE_ReturnInfo(timerMsg);
timer->sourcePort = clientPtr->sourcePort;
timer->type = APP_TIMER_SEND_PKT;
MESSAGE_Send(node, timerMsg, startTime);
The API function APP_SetTimer can also be used instead of the above code to
set a new App Layer Timer and send to self after specified delay. The function is
implemented in QUALNET_HOME/application/app_util.c.
A protocol's event dispatcher should include a switch on all message types that
the protocol may receive. It can then process each message type either inside the
switch or by calling a function to handle the message type received. However
before processing a message it must be determined which instance of the
application protocol this message is for. This can be done by looking up additional
information stored in the message info field such as source/destination port.
The following snippet from CBR handles the timer event that is set in the init
function. CBR operates by setting periodic timers to itself. Each time the timer
goes off, it sends virtual data packets to destination. It then sets a new timer to
occur after the periodic interval. In this way the data sending rate is achieved and
maintained.
After the message is handled by the event dispatcher, it frees the memory
associated with the message by calling MESSAGE_Free(node, msg). It is
important to free the memory after the message has been handled as otherwise
the protocol will leak memory.
The event dispatcher should also include a default case in the switch statement to
handle error packets which contain an undefined msg->eventType.
Packet exchange at any layer can be performed either by using the raw the
message API or by using the Layer specific API.
The complete and detailed API at the application layer is also available in the
application layer section of the QualNet API reference document. This also
includes API for using the SRM transport protocol. The implementation of these
API functions can be found in QUALNET_HOME/application/app_util.c.
While sending a packet to the transport layer the API generates the message
MSG_TRANSPORT_FromAppSend. This message is passed down the protocol
stack at the sender and up the protocol stack at the receiver. This process
happens transparently to the application layer. The application layer developer
only needs to be concerned with receiving the message from the transport layer at
the receiver. The transport layer sends the message MSG_APP_FromTransport
to the application layer when it has a packet for the application layer.
NodeAddress destAddress;
if (MAC_IsWiredNetwork(node, interfaceIndex))
{
destAddress =
NetworkIpGetInterfaceBroadcastAddress(node,
interfaceIndex);
}
else
{
destAddress = ANY_DEST;
}
variable in the message structure can be used to access the payload. The API call
MESSAGE_ReturnPacket (msg) is used for this purpose. It is essentially a
#define stated in QUALNET_HOME/addons/seq/message.h.
UdpToAppRecv *info;
CbrData data;
To enable statistics collection for the protocol, include the statistic collection
variables in the structure used to hold the protocol state e.g. the
AppDataCbrServer structure in cbr.h includes stat variables such as the following:
• long numBytesRecvd; // variable to record number of received bytes
• long numPktsRecvd; // variable to record number of received packets
• clocktype totalEndToEndDelay; // variable used to calculate throughput
The statistics related variables can also be defined in a structure and then that
structure be included in the state variable. QualNet designer follows this
approach.
cbrServer->numBytesRecvd = 0;
cbrServer->numPktsRecvd = 0;
cbrServer->totalEndToEndDelay = 0;
serverPtr->numBytesRecvd +=
MESSAGE_ReturnPacketSize(msg);
serverPtr->numPktsRecvd++;
As a final step towards statistics collection create a function to print statistics. This
function will be called in the finalize function of the protocol, which is discussed in
the next section.
In the print function, create a string containing the information to be printed along
with the statistic and statistic value into a combined single string e.g. the print
function of CBR, AppCbrServerPrintStats performs this as follows:
char buf[MAX_STRING_LENGTH];
The statistics are finally sent to the stat file by making a call to the IO_PrintStat
function (QUALNET_HOME/main/fileio.c). This function requires the following
parameters:
• Node pointer: Pointer to the node reporting the stat
• Layer: Set this to Application for application layer
• Protocol: String indicating protocol name
• Interface Address: Set to ANY_DEST if not needed.
• Instance Id: Instance identifier / port number
• Buf: String containing the stat
IO_PrintStat( node,
"Application",
"CBR Server",
ANY_DEST,
serverPtr->sourcePort,
buf);
Like all other functions specify the prototype of the finalize function in the
protocol's header file. In the finalize function, make a call to the function
responsible for printing the statistics if application statistics collection was set to
true in the configuration file. Sample code follows:
if (strcmp(buf, "RIPv2") == 0)
{
NetworkIpAddUnicastRoutingProtocolType(
node,
ROUTING_PROTOCOL_RIPV2,
i);
RoutingRipv2Init(node, i, nodeInput);
}
The routing protocol state is stored in AppData. While AppData stores a list
containing all instances of the application layer traffic protocols, this is not the
case for routing protocols. It is recommended to store the single instance of any
custom routing protocol in AppData member routingVar. AppData contains the
following declarations for routing protocols:
Another pointer can even be added in AppData for storing state if routingVar is
used by another custom routing protocol. The following code from
RoutingRipv2Init demonstrates how the routing protocol state is stored in
AppData.
Another commonly performed task in the init function is to add directly connected
networks as permanent routes to the route table. This can be performed by
looping through all the interfaces of the node and writing its network address,
interface address, and subnet mask to the route table.
if (MAC_IsWiredNetwork(node, i))
{
if (!GetRoute(node,NetworkIpGetInterfaceNetworkAddress(node, i)))
{
routePtr = AddRoute(
node,
NetworkIpGetInterfaceNetworkAddress(node, i),
NetworkIpGetInterfaceSubnetMask(node, i),
NetworkIpGetInterfaceAddress(node, i),
0,
i,
i);
}
….
}
If (node->appData.routingStats == TRUE)
{
printStat(….);
}
The remainder of the development process remains the similar except that code
can be directly added to the states.
3. Transport Layer
This section covers the following topics:
• Transport Layer Overview
• Transport Protocols in QualNet
• Transport Layer Organization: Files and Folders
• Data Structure
• API
• Writing and Adding a Transport Protocol
When the application layer sends a data packet to the transport layer, the data
packet is encapsulated into the transport layer protocol header and passed down
to the next layer Network (IP).
Similarly, when the transport layer receives a packet from Network layer, it
removes the transport layer header residing above the data packet and sends it
up to the application layer.
In QualNet, the CBR (constant bit rate) application uses UDP in the transport
layer.
• Connection Establishment
TCP is connection-oriented and it uses a three-way handshake before it starts
transmitting data. A TCP connection is always point-to-point, i.e., between a
single sender and a single receiver.
• Connection State
TCP maintains connection state in the end systems. This connection state
includes receive and send buffers, congestion control parameters, and
sequence and acknowledgment number parameters. This state information is
needed to implement TCP's reliable data transfer service and to provide
congestion control.
Additionally, the following header files are also relevant to the transport layer:
• QUALNET_HOME/include/fileio.h
The source code contains detailed comments on functions and other code
components. The following are the source files (*.c) associated with the transport
layer:
• QUALNET_HOME/transport
This folder contains the source and header files for the various QualNet
transport layer protocol implementations. The file names are indicative of the
protocols for which they provide an implementation e.g. to see the
implementation for UDP (User Datagram Protocol), look at udp.c and udp.h
files. For TCP implementation, see tcp.c and tcp.h and other associated files
kept at QUALNET_HOME/transport/tcp. RSVP-TE implementation is made at
rsvp.c and rsvp.h file.
• QUALNET_HOME/transport/transport.c
This file contains initialization function, message-processing function, and
finalize function used by transport layer.
typedef struct
{
unsigned short sourcePort; /* source port */
unsigned short destPort; /* destination port */
unsigned short length; /* length of the packet */
unsigned short checksum; /* checksum */
} TransportUdpHeader;
typedef struct
{
int numPktFromApp;
int numPktToApp;
} TransportUdpStat;
struct TransportDataUdpStruct
{
BOOL udpStatsEnabled; /* whether to collect stats */
TransportUdpStat *statistics; /* statistics */
BOOL traceEnabled;
};
3.4.2 Transport
• TransportProtocol—This is the structure to keep different transport layer
protocol. Add the new transport protocol at the end of this structure with a
proper name.
typedef enum {
TransportProtocol_UDP,
TransportProtocol_TCP,
TransportProtocol_SRM,
//InsertPatch TRANSPORT_ENUMERATION
TransportProtocol_RSVP
} TransportProtocol;
struct struct_transport_str
{
TransportDataUdp* udp;
TransportDataTcp* tcp;
BOOL rsvpProtocol;
void *rsvpVariable;
BOOL srmProtocol;
void *srmVariable;
};
Typedef struct
{
NodeAddress sourceAddr;
NodeAddress destinationAddr;
NetworkQueueingPriorityType priority;
int incomingInterfaceIndex;
unsigned receivingTtl;
BOOL isCongestionExperienced; /* ECN related variables */
} NetworkToTransportInfo;
The same types of structures are available for all other transport layer protocol.
For a new protocol written in the transport layer, users may need to add this kind
of structure in api.h to interact between application-transport and transport-
network.
3.5 APIs
This section covers the following topics:
• Message APIs
• Transport to Network APIs
3.5.1 Message
These APIs are called when transport layer forwards packets to the application
layer, and receives packets from application and network layer.
• MESSAGE_Send—This API is used to send a message within QualNet. When
a message is sent using this mechanism, only the pointer to the message is
actually sent through the system. So the user has to be careful not to do
anything with the content of the pointer once MESSAGE_Send has been
called.
Syntax: void MESSAGE_Send(Node *node, Message *msg, clocktype delay);
Parameters:
node: node which is sending message
msg: message to be delivered
delay: delay suffered by this message
might be added to the packet. The packetSize variable is set to the packetSize
parameter specified in the function call.
Syntax: void MESSAGE_PacketAlloc(Node *node, Message *msg,
int packetSize, TraceProtocolType originalProtocol);
Parameters:
node: node which is allocating message
msg: message for which data has to be allocated
payLoadSize: size of the payLoad to be allocated
originalProtocol: protocol allocating this packet
• MESSAGE_AddHeader—This API is called to reserve additional space for a
header of size hdrSize for the packet enclosed in the message. The
packetSize variable in the message structure will be increased by hdrSize.
After this function is called, the packet variable in the message structure points
to the space occupied by this new header.
Syntax: void MESSAGE_AddHeader(Node *node, Message *msg,
int hdrSize, TraceProtocolType traceProtocol);
Parameters:
node: node which is adding header
msg: message for which header has to be added
hdrSize: size of the header to be added
traceProtocol: protocol name, from trace.h
• MESSAGE_RemoveHeader—This function is called to remove a header from
the packet enclosed in the message. The packetSize variable in the message
will be decreased by hdrSize.
Syntax: void MESSAGE_RemoveHeader(Node *node, Message *msg,
int hdrSize, TraceProtocolType traceProtocol);
Parameters:
node: node which is removing the header
msg: message for which header is being removed
hdrSize: size of the header being removed
traceProtocol: protocol removing this header.
• MESSAGE_Free—When the message is no longer needed it can be freed.
Firstly the payLoad and info fields of the message are freed. Then the
Example:
It is strongly recommended to have separate header and source files. Not having
a header file sometimes leads to unexpected problems, even if the compilation
process does not indicate any error.
Next, add these files to the QualNet source tree. Modify the file
QUALNET_HOME/main/Makefile-common to include the newly created files.
Add the header file (*.h) to the simulator header files section and the source file
(*.c) to the simulator source file section of Makefile-common. This addition
ensures that the files will be compiled. The files are sorted alphabetically for
easier organization, but it doesn't matter where it is inserted into the list.
Test that the files have been successfully added to the QualNet source tree by
compiling QualNet. This is done by performing the following steps:
1. Type the following command:
cd main
For linux:
As code is added to the files, it is important to organize the code well between the
files. The header file should generally contain the following:
• Prototypes for functions in source file
• Constant definitions: All # defines
• Data structure definitions and data types: struct, enums
#include <stdio.h>
#include "api.h"
#include "ip.h"
#include "udp.h"
• Dispatcher code
Here all the events specific to the protocols are dispatched.
• Protocol implementation functions
When adding the protocol, it is recommended to compile frequently to test the
code.
<Keyword> <value>
Where:
Keyword: Uniquely identifies the protocol.
Value: Identifies the value of that keyword.
TCP RENO
The new transport layer protocol may not be a mandatory service in the transport
layer. In that case, keep an option in the configuration to make the protocol
enabled or disabled. The syntax of this configuration is as follows:
TRANSPORT-PROTOCOL-<Protocol_name> <status>
Where:
<Protocol_name>: the new transport layer protocol
<Status>: value is YES (protocol enabled) or NO (protocol disabled)
It is useful to understand how a node accesses the list of transport layer protocols.
Each node has a node structure, which holds all the information for the node. This
node structure contains a pointer to a structure called struct_transport_str
declared in file QUALNET_HOME/include/transport.h. A pointer to the newly
Always add to the end of lists in header files. QualNet's pre-built object files use
the values, which existed when the object files were created. If a constant is
inserted at the top of the list, the values below will be offset in any new object files
and may lead to the simulator crashing.
If the transport protocol is not a mandatory one, such as TCP or UDP, a Boolean
flag should be added in TransportData structure along with the protocol structure
pointer. This Boolean is only true during runtime if the newly created protocol is
enabled.
Messages/Events contain the name of the protocol that they are destined for.
TRANSPORT_ProcessEvent implements a switch statement on the protocol
name read from the message. This is the transport protocol name specified in the
enumerated data type called TransportData. To enable the protocol to receive
events, add code to TRANSPORT_ProcessEvent, which calls the protocol's event
dispatcher function when messages for the protocol are received.
IO_ReadString(node->nodeId, ANY_ADDRESS,
nodeInput, "TRANSPORT-PROTOCOL-SRM", &wasFound, buf);
The initialization of a transport protocol happens in the init function of the protocol
that is called by the transport layer. The init function of a transport protocol
commonly performs the following tasks:
• Create an instance of the transport data structure. The transport protocol
should have a data structure that stores configuration variables and any other
information required. For example, TransportDataUdp is such a data structure
for UDP. The transport protocol's header file contains this data structure.
• Initialize data structures and variables as required, e.g. allocate memory to
tables, set default values etc.
• Initialize the state and store the user specified configuration parameters. The
Init routine should read QualNet configuration and store the different
configurable information in the protocol data structure. Functions such as
IO_ReadString are helpful to read them from the configuration.
• Schedule a timer to itself if required. This timer is protocol specific and if
required they are scheduled from Init routine. How to schedule a timer is
described below.
Like all other functions belonging to the transport, the prototype for init should be
included in the header file.
if (strcmp(buf, "YES") == 0)
{
udp->udpStatsEnabled = TRUE;
}
else if (strcmp(buf, "NO") == 0)
{
udp->udpStatsEnabled = FALSE;
}
else
{
abort();
}
if (udp->udpStatsEnabled == TRUE)
{
udp->statistics = (TransportUdpStat *)
MEM_malloc(sizeof(TransportUdpStat));
if (udp->statistics == NULL)
{
printf("TRANSPORT UDP: cannot allocate memory\n");
abort();
}
memset(udp->statistics, 0, sizeof(TransportUdpStat));
}
}
Chapter 3, “Timer Events” on page 29 discusses in detail how to use timers. The
transport layer timers frequently use the message info field to distinguish the
message from which transport protocol appears. The info field is also used to
send any protocol specific information.
refreshPathMsg = MESSAGE_Alloc(node,
TRANSPORT_LAYER,
TransportProtocol_RSVP,
MSG_TRANSPORT_RSVP_PathRefresh);
All the events used in the protocol should be included in the event enum structure,
with unique enumerated value.
A protocol's event dispatcher should include a switch on all message types that
the protocol may receive. It can then process each message type either inside the
The following example from UDP handles the event that is set from the application
and network layer. Against each such event, the program should have specific
activity, which can be written in a separate function. In the following example, the
name of such function is TransportUdpSendToApp. After the message is handled
by the event dispatcher, it frees the memory associated with the message by
calling MESSAGE_Free(node, msg) unless the called function frees the memory
itself. It is important to free the memory after the message has been handled.
Otherwise the protocol will leak memory.
The event dispatcher should also include a default case in the switch statement to
handle error packets which contain an undefined msg->eventType.
If the event is a timer, there may be a need to reschedule it after the processing is
finished. A rescheduled event triggers after the rescheduled time and will be
processed similarly.
Now it is determined that this packet is for UDP protocol and the function
SendToUdp sends this packet up the hierarchy to the transport layer. The
following function shows how that can be done.
SendToUdp(….)
{
MESSAGE_SetEvent(msg, MSG_TRANSPORT_FromNetwork);
MESSAGE_SetLayer(msg, TRANSPORT_LAYER, TransportProtocol_UDP);
MESSAGE_InfoAlloc(node, msg, sizeof(NetworkToTransportInfo));
((NetworkToTransportInfo *) msg->info)->sourceAddr = sourceAddress;
((NetworkToTransportInfo *) msg->info)->destinationAddr =
destinationAddress;
((NetworkToTransportInfo *) msg->info)->priority = priority;
((NetworkToTransportInfo *) msg->info)->incomingInterfaceIndex =
incomingInterfaceIndex;
MESSAGE_Send(node, msg, PROCESS_IMMEDIATELY);
}
QualNet provides set of APIs that are helpful to send and receive packets to
different layers. Packet exchanges at any layer can be performed either by using
the raw the message API or by using the layer-specific API.
The complete and detailed API at the transport layer is also available in the
QualNet API reference document. Transport protocols send packets to the
application layer by using different API sets from
QUALNET_HOME/main/message.c. The transport layer sends packets to the
network layer by using a set of APIs available at QUALNET_HOME/network/ip.c.
While sending packets to the application layer, the transport layer generates the
message MSG_APP_FromTransport. This message is passed up the protocol
stack to the application layer. The new protocol should handle this after receiving
it from the network layer. The following example is provided to demonstrate how
the packet is forwarded to the application layer properly. In this example, Info field
is used to carry the session information from the transport layer to the application
layer. The application layer developer should only be concerned with receiving the
message from the transport layer at the receiver.
NodeAddress destAddress;
if (MAC_IsWiredNetwork(node, interfaceIndex))
{
destAddress = NetworkIpGetInterfaceBroadcastAddress(node, interfaceIndex);
}
else
{
destAddress = ANY_DEST;
}
The application layer sends packets to the transport layer by using the event
MSG_TRANSPORT_FromAppSend. To receive packets at transport layer, add a
switch on MSG_TRANSPORT_FromAppSend in the protocol's event dispatcher.
An example of this event trapping is shown at the function TransportUdpLayer in
“Specifying the Event Dispatcher” on page 71.
After receiving packets from the application layer, the transport layer forwards
them to the network layer by using APIs available in
QUALNET_HOME/network/ip.c. The transport protocol calls the network layer
APIs with the source and destination session information (IP address and port of
the source and destination address). The transport protocol must add the
transport layer header before sending packets to network layer. The following
example function shows how this has been done in the case of UDP. The
TransportUdpSendToNetwork function is called when
MSG_TRANSPORT_FromAppSend event is received. The message received
from the application layer has got the session information in the info field. The new
protocol should extract them from info field and creates transport header with the
help of them.
When the transport protocol receives packets from the network layer, it should
check whether this packet is for this layer only. If so, the protocol should trap the
message MSG_TRANSPORT_FromNetwork and the process it, as it requires.
The following code sample shows how RSVP handles
MSG_TRANSPORT_FromNetwork, this function, and process according to the
protocol.
If the packet is for the application layer, then the transport layer should forward
this packet to the application layer. This is shown in the function
TransportUdpSendToApp above.
typedef struct
{
int numPktFromApp;
int numPktToApp;
} TransportUdpStat;
This statistics structure is kept inside the main UDP data structure as follows.
Here, udpStatsEnabled is the Boolean flag that stores whether statistics to be
shown is enabled or not in the configuration. As described earlier, the init routine
reads the statistics configuration variable from the configuration file and stores it in
a variables.
struct TransportDataUdpStruct
{
BOOL udpStatsEnabled;
TransportUdpStat *statistics;
….
};
Next, initialize stat variables in the init function of the protocol. The following code
shows how statistics is handled for UDP in the init routine.
udp->statistics->numPktFromApp++;
if (strcmp(buf, "YES") == 0)
{
udp->udpStatsEnabled = TRUE;
}
else
{
if (strcmp(buf, "NO") == 0)
{
udp->udpStatsEnabled = FALSE;
}
}
else
{
printf("TransportUdp unknown setting (%s) for UDP-STATISTICS.\n", buf);
abort();
}
if (udp->udpStatsEnabled == TRUE)
{
udp->statistics = (TransportUdpStat *) MEM_malloc(sizeof(TransportUdpStat));
memset(udp->statistics, 0, sizeof(TransportUdpStat));
}
}
In the print function, create a string containing the information to be printed along
with the statistic and statistic value into a combined single string e.g. the print
function of UDP for the above statistics should perform as follows:
char buf[MAX_STRING_LEN];
TransportDataUdp* udp = node->transportData.udp;
TransportUdpStat* st = udp->statistics;
The statistics are finally sent to the stat file by making a call to the IO_PrintStat
function (QUALNET_HOME/main/fileio.c). This function requires the following
parameters:
Node pointer: pointer to the node reporting the stat
Layer: set this to Transport for transport layer
Protocol: string indicating protocol name
Interface Address: set to ANY_DEST if not needed.
Instance Id: instance identifier / port number
Buf: string containing the stat
Like all other functions, specify the prototype of the finalize function in the
protocol's header file. In the finalize function, make a call to the function
responsible for printing the stats if the transport statistics collection was set to true
in the configuration file.
TransportUdpFinalize(Node *node)
{
char buf[200];
TransportDataUdp* udp = node->transportData.udp;
TransportUdpStat* st = udp->statistics;
if (udp->udpStatsEnabled == TRUE)
{
// call print statistics function
}
Figure 20, “Network layer” illustrates the network layer residing in between the
transport and link layer in the QualNet protocol stack.
4.2.1 IP (Version 4)
This section covers the following topics:
• IPv4 Addressing
• Fragmentation
The Internet Protocol (IP, documented in RFC 791) is the primary network layer
protocol in the TCP/IP protocol suite. It has two primary responsibilities:
• Providing connectionless, best-effort delivery of datagrams through an
internetwork.
• Providing fragmentation and reassembly of datagrams to support data links
with different MTU size.
When the network layer at the sending host receives a segment from the transport
layer, it encapsulates the segment into an IP datagram, writes the destination and
source IP address along with any other fields in the datagram, and sends it to the
first router on the path toward the destination host. The protocol treats each
datagram as an independent entity unrelated to any other datagram. There are no
connections or logical circuits. The protocol uses four key mechanisms in
providing its service:
• Type of Service (TOS)
• Time to Live (TTL)
• Options
• Header Checksum
reserved for multicast addresses and class E are reserved for future use. The
table below describes a range of possible values exists for each address class.
It should be noted that these address classes are no longer formally part of the IP
addressing architecture. For a better utilization of the address space in 1993 IETF
standardize Classless Interdomain Routing (commonly known as CIDR).
4.2.1.2 Fragmentation
Fragmentation of an Internet datagram is necessary when it originates in a local
net that allows a large packet size and must traverse a local net that limits packets
to a smaller size to reach its destination.
Routing algorithms determine optimal path and store the information in routing
table. Routers communicate with one another and maintain their routing tables
through the transmission of a variety of messages. Based on the information of a
router, the packets are forwarded to the desired destination.
QualNet develops a variety of routing protocols, which can be grouped into the
following sections:
• Unicast routing: wireless ad hoc
• Unicast routing: wired
• Multicast Routing: wireless
• Multicast Routing: wired
The following table describes which protocols support which type of network.
Some other routing protocols are implemented at application layer and they
are not included here.
• Wireless ad hoc
One of the primary characteristics of ad hoc network is that network topology
constantly changing. It is recommended to evaluate routes only on an as-
needed basis. AODV and DSR are two typical example of this type of routing
protocol. Reactive routing protocols normally have two types of routing
packets, request/discovery and reply packet. Request packets are normally
flooded while reply packets are unicasted backed. These types of routing
protocols are efficient if routes are used infrequently and routes can become
sub optimal over time. These types of protocols (AODV, DSR, LAR1) uses their
own router function to forward a data packet, they don't populate IP forwarding
table.
Proactive routing is also popular for ad hoc networks. An example of this type
of routing protocol is STAR. Proactive routing protocols mainly used two types
of broadcast, periodic and triggered. They are efficient if routes are popular
and routes are always optimal. These types of protocols populate IP
forwarding table each time they calculated routes and data packets are
forwarded through IP module.
• Wired
Wired networks are comparatively stable and topology changes occur less
frequently than with wireless ad hoc networks. QualNet currently has four
routing protocols that support wired networks. These protocols normally
populate IP forwarding table with calculated routes and packets are forwarded
from IP module (i.e. routing protocols don't need to handle data packet
directly).
• Mixed Network
For mixed networks (i.e., switch ethernet, point-to-point, and wireless ad hoc
networks connected together,) OSPF is the only network layer routing protocol
that can be used for the entire mixed network.
With strict priority scheduling, the queue with highest priority served first, and
continues to be served until it is empty which lead to starvation of queues with
lower priority.
Round Robin Scheduling avoids starvation of some queues and ensures that
traffic gets fair share of bandwidth. With this type of scheduling, services cannot
be prioritized.
Weighted Round Robin improves the performance of Round Robin and ensures
traffic to get fair share of total bandwidth. However it does not solve the problem of
delay, because a packet arriving into a queue that has just been served has to
wait for the complete round robin before getting served, regardless of the
importance of the queue. This algorithm can't identify and punish ill-behaved
sessions, because it does not have congestion control and is sending large
amounts of traffic.
This means that when there are inactive sessions, the virtual time progresses
faster. The virtual finish time (F) of the first packet in each queue is stored in the
queue header. Whenever a new packet is enqueued, the corresponding F is
updated accordingly. The algorithm selects the queue with the smallest virtual
finish time (SF) for dequeue. Thus, an ill-behaved session, which is sending huge
traffic, will not affect the performance of other sessions, but will reduce its own
performance.
A strong limitation to the deployment of WFQ derives from its high computational
cost that arises from updating the virtual time on the arrival and departure of each
packet (keeping track of active flows).
There can be malicious effects due to the source synchronization, where one
source does not experience any drop and another does.
• RED—Random Early Detection
It is an Active Queue Management (AQM) technique that detects incipient
congestion and provides feedback to end hosts by dropping the packets. The
motivation behind RED is to keep the queue size small, reduce burstiness and
solve the problem of global synchronization. RED drops packets before the
actual physical queue is full. It operates based on an average queue length
that is calculated using an exponential weighted average of the instantaneous
queue length. RED drops packets with certain probability depending on the
average length of the queue. The drop probability increases from 0 to
maximum drop probability (maxp) as average queue size increases from
minimum threshold (minth) to maximum threshold (maxth). If average queue
size goes above maxth, all packets are dropped.
However, RED suffers from some fairness problem—it cannot identify any
preferentially nor can it differentiate and penalize misbehaving users.
A mechanism that helped to differentiate traffic is needed. The rise in usage and
popularity of the Internet, coupled with new applications such as voice, video, and
the web has fueled research to improve the Quality of Service delivered by the
best effort networks today. The underlying concept in IP Quality of Service (IP-
QoS) is the ability of network operators to offer differing levels of treatment to user
traffic based on their requirements.
The Differentiated Services (DS) architecture has recently become the preferred
method to address QoS issues in IP networks. This packet marking based
approach to IP-QoS is attractive due to its simplicity and ability to scale. DS
domain, a Diffserv Architecture term, refers to a contiguous set of nodes which
operate with a common set of service provisioning policies and Per Hop Behavior
(PHB) definitions. Per domain services are realized by traffic conditioning at the
edge and simple differentiated forwarding mechanisms (such as Expedited
Forwarding (EF), Assured Forwarding (AF), etc.) at the core of the network.
Assured forwarding is a group of 12 PHBs (4 classes X 3 levels of drop
precedence). The levels of drop precedence are as follows:
• Level Low Drop, DP0
• Level Medium Drop, DP1
• Level High Drop, DP2
To build service with AF, subscribed traffic profiles for customers are maintained at
the network edge traffic conditioning nodes. The aggregated traffic is monitored
and packets are marked at the traffic conditioner on the basis of Service Level
Agreement (SLA) between the customer and service-provider. The marking can
be two colors marking:
• In profile (Packets with lower drop precedence, DP0)
• Out Profile (Packets with higher drop precedence)
In RIO-DC (RIO with Decoupled Queues), the average queue size for packets
of each color is calculated independently. In this scheme, the average queue
length for a color is calculated using number of packets of that color in the
queue only. The average queue length for green, yellow and red packets are
calculated using the number of green, yellow and red packets in the queue
respectively. The RED parameter settings can be chosen depending on the
treatment to be given to different colors. RIO-DC appears to be the most
suitable MRED-variant if the operator desires to assign weights to different
colored packets within the same queue i.e. there is no relationship between the
packets marked green and yellow. Thus, the RIO-DC scheme could start to
emulate the WRR scheduling scheme except that a single queue is used.
specific IP protocol, port number, TOS, precedence and other protocol specific
parameters.
Access lists are a group of statements, also called criteria's. Each statement
defines a pattern that would be found in an IP packet. As each packet enters or
leaves an interface with an associated access list, the list is scanned from top to
bottom--in the exact order that it was entered for a pattern that matches the
incoming packet. A permit or deny rule associated with the pattern determines
that packet's fate. A mask, which is like a wild card, to determine how much of an
IP source or destination address to apply to the pattern match.
Once the access list is entered, it must associate with the interface on the router
to apply the filtering. Access lists do not act on packets that originate from the
router itself, such as routing updates or outgoing telnet sessions.
When it enters the network, flow of traffic is classified and assigned a PHB (Per-
Hop Behavior) based on that classification. PHB is the externally observable
forwarding treatment that packets with the same DiffServ Code Point (DSCP)
receive from a network node. The contents of the DSCP are the six most
significant bits in the IP header Type of Service (TOS) field. Within the IP packet,
the DSCP tells how the packet should be treated at each network node.
Additionally, the mapping of DSCP to PHB is configurable, and DSCP may be re-
marked as it passes into a DiffServ network. Re-marking of DSCP allows for the
variable treatment of packets based on network specifications or desired levels of
service.
Within PHB, there are two standard groups: Assured Forwarding (AF) and
Expedited Forwarding (EF). Within AF, the group is further divided into four
independent classes with three drop precedence levels. AF offers different levels
of forwarding resources in each DiffServ node. In the case of network congestion,
the relative importance of the packet determines its drop precedence among other
packets in its AF class. EF, on the other hand, known as premium service, gives
the best service a network can offer. EF is defined as a forwarding treatment
where the rate of packet flow from any DiffServ node is whatever rate ensures
highest priority and no packet loss for in-profile traffic.
4.2.8 MPLS
Multi Protocol Label Switching (MPLS) is a Network layer protocol that enables
levels of Quality of Service (QoS) by forwarding packets towards their destination
Each FEC has an LSP associated with it. An LSR builds up LSPs by exchanging
Label Request Messages and Label Reply Messages, with the ultimate goal of
having a path for each FEC of packets. If an LSR does not have a label for a
particular FEC, it requests a label from its most promising downstream neighbor. If
that downstream node has a label for that FEC, it immediately replies back with a
label; otherwise the original request is forwarded to the next downstream neighbor
for a label.
When a new data packet arrives at the network, it is mapped to a particular FEC.
LDP associates a label to that FEC. At each hop, unlike IP, there is no need to
analyze the packet's network layer header and make a forwarding decision.
Rather, the label is used as an index into a table that specifies the next hop and a
new label. The old label is replaced with the new label, and the packet is
forwarded to its next hop, and so on until the packet reaches its final destination.
The following header files are also relevant to the network layer:
• QUALNET_HOME/include/fileio.h
This file contains various prototypes defined in fileio.c that might be needed for
parsing of input configuration file.
• QUALNET_HOME/include/buffer.h
This file contains data structures and prototypes to allocate, manipulate and
delete packet memory and generic data buffer.
• QUALNET_HOME/network/list.h
This file defines a generic doubly link list structure and prototypes related to
insertion, deletion etc.
• QUALNET_HOME/network/ipqueue.h
This file defines IP Queue/Scheduler Structures, function pointers, prototypes
of various utility functions related to QualNet queue and scheduler.
• QUALNET_HOME/network
This folder contains various network layer protocols implemented in QualNet.
The file names are indicative of the protocol for which they provide an
implementation. To implement a network layer protocol, place the source files
here.
This section describes various enums, structure that are commonly used and
accessed in writing a network layer protocol. Commonly used structures and
enums are defined in mainly two files. QUALNET_HOME/include/network.h and
QUALNET_HOME/network/ip.h.
The default priority values for queues and packets are as follows:
• CONTROL 0
• REAL_TIME 1
• NON_REAL_TIME 2
Because there are three types, 3 is a good minimum value for the input
configuration parameter IP-QUEUE-NUM-PRIORITIES. If it's specified as less
than 3, the lower priority packets will be placed in the lowest-priority queue.
Default input/output Queue size is taken when they are not specified in the input
configuration file through IP-QUEUE-PRIORITY-QUEUE-SIZE.
Most of the variables have a self-descriptive name and one can understand its
purpose by looking its name. Some of the important member variables are
described in each following subsection.
• NetworkDataIp
This is the main structure of IP module that is liked to the universal node
structure through the networkVar pointer variable of NetworkData. All other
background network protocol (e.g. routing protocol, icmp, etc.) should have a
link with this structure.
Member: forwardTable
Data Type: NetworkForwardingTable
Description: IP forwarding table. Wired routing protocols populate this
table as they calculate new routes. Wireless routing
protocols normally don't use this table (STAR is an exception
of this).
Member: interfaceInfo[MAX_NUM_INTERFACES]
Data Type: IpInterfaceInfoType*
Description: Array of pointer, of which each element points to an interface
structure indexed by array index. MAX_NUM_INTERFACES
is the maximum number of interface QualNet support. The
value is defined in QUALNET_HOME/include/main.h.
Member: phbInfo
Data Type: IpPerHopBehaviorInfoType*
Description: Contains TOS field to Queue mapping information that was
given in input configuration file specified by
PER-HOP-BEHAVIOR-FILE in default.config. Based on this
information a node decides which queue will be used to store
a data packet.
Member: mftcInfo
Data Type: IpMultiFieldTrafficConditionerInfo*
Description: Main data structure for Diffserv.
Member: ipMftcParameter
Data Type: IpMftcParameter*
Description: Contains multifield traffic conditioner parameters used in
Diffserv.
Member: ipForwardingEnabled
Data Type: BOOL
Description: Specify whether this node will act as a router (i.e. whether
they'll forward data packet or not). Setting
IP-FORWARDING to YES in default.config file can set the
value.
Member: multicastGroupList
Data Type: List*
Description: List of multicast group this node has joined.
Member: routeUpdateFunction
Data Type: NetworkRouteUpdateEventType
Description: Pointer to a function used by IP to trigger a protocol (e.g.
PIM-DM) when a unicast route add/delete or change.
Member: inputScheduler
Data Type: SchedulerType
Description: Holds generic structure for scheduler applied to input and
cpu queue.
Member: inputScheduler
Data Type: SchedulerType
Description: Holds generic structure for scheduler applied to input and
cpu queue.
Member: routerFunction
Data Type: RouterFunctionType
Description: A pointer to the function provided by routing protocol running
over this interface. The function will be responsible to route a
packet to its final destination. This function generally used by
wireless ad hoc routing protocols. Wired routing protocols
normally don't use this function. The function can be set by
calling NetworkIpSetRouterFunction() from the protocol
module.
Member: routingProtocol
Data Type: void*
Description: Data pointer pointing to the main data structure of the routing
protocol running over this interface. The address of this
pointer normally passed to the routing protocol module at
initialization time.
Member: multicastRouterFunction
Data Type: MulticastRouterFunctionType
Description: The purpose of this function pointer is same as
routerFunction described above except this if used by
multicast routing protocols. The pointer can be set by calling
NetworkIpSetMulticastRouterFunction().
Member: multicastRoutingProtocol
Data Type: void*
Description: Data pointer pointing to the main data structure of multicast
routing protocol.
Member: macLayerStatusEventHandlerFunction
Data Type: MacLayerStatusEventHandlerFunctionType
Description: Function pointer that allows the MAC layer to send status
messages (e.g., packet drop, link failure) to a network-layer
routing protocol for routing optimization. It’s used by wireless
routing protocols. The pointer is set by calling
NetworkIpSetMacLayerStatusEventHandlerFunction().
Member: promiscuousMessagePeekFunction
Data Type: PromiscuousMessagePeekFunctionType
Description: Sometimes it might be a necessity for a network layer
protocol to overhear packets destined to neighboring nodes.
This function pointer will be set by the protocol wants to
check every packets passing through its own interface. The
setting can be done by calling
NetworkIpSetPromiscuousMessagePeekFunction(). To
check packet promiscuously one need to set
PROMISCUOUS-MODE to YES in default.config file. When
this is enable mac call NetworkIpSneakPeekAtMacPacket()
directly from mac layer which in turn call the appropriate
function through this function pointer. Currently dsr is using
this feature so one can check the related file for a good
example.
• NetworkForwardingTableRow
Structure that describe a single row in IP forwarding table.
Member: destAddress
Data Type: NodeAddress
Description: Destination address for which this row has been inserted in
routing table.
Member: destAddressMask
Data Type: NodeAddress
Description: Mask of the destination. This in combination with
destAddress gives the destination network/host.
Member: interfaceIndex
Data Type: int
Description: Outgoing interface index to reach this destination.
Member: nextHopAddress
Data Type: NodeAddress
Description: Address of next hop router (or the final node/network if it's
directly reachable).
Member: cost
Data Type: int
Description: Cost of sending a packet out this interface.
Member: protocolType
Data Type: NetworkRoutingProtocolType
Description: Routing protocol that has contributed this route to IP
forwarding table.
Member: adminDistance
Data Type: NetworkRoutingAdminDistanceType
Description: Administrative distance of the protocol. If there are two routes
(contributed by two different protocol) to the same destination
with same cost then this will be the determining factor of
choosing the preferred route.
Member: interfaceIsEnabled
Data Type: BOOL
Description: It's a flag indicating whether the outgoing interface is enabled
or not.
4.5 APIs
This section discusses what the different APIs QualNet provide at the network
layer. This section contains the following topics:
• APIs for Transport Layer Interface
• APIs for IP Background Protocols
• APIs for Mac to Network Layer Interface
• APIs for Mac to Network Layer Callback Function
• APIs Related to Router Function
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with UDP packet.
Priority: Priority of packet.
sourceAddress: Source IP address.
destinationAddress: Destination IP address.
incomingInterfaceIndex: interface that received the packet.
RETURN: void
SYNTAX:
void SendToTcp(
Node *node,
Message *msg,
NetworkQueueingPriorityType priority,
NodeAddress sourceAddress,
NodeAddress destinationAddress,
BOOL aCongestionExperienced);
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with TCP packet.
Priority: Priority of packet.
sourceAddress: Source IP address.
destinationAddress: Destination IP address.
aCongestionExperienced:Determine if congestion is experienced (via ECN).
RETURN: void
SYNTAX:
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with RSVP packet.
Priority: Priority of packet.
sourceAddress: Source IP address.
destinationAddress:Destination IP address.
interfaceIndex: incoming interface index.
ttl: Receiving TTL.
RETURN: void
SYNTAX:
void SendToSrm(
Node *node,
Message *msg,
NetworkQueueingPriorityType priority,
NodeAddress sourceAddress,
NodeAddress destinationAddress);
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with SRM packet.
Priority: Priority of packet.
sourceAddress: Source IP address.
destinationAddress:Destination IP address.
RETURN: void
SYNTAX:
void NetworkIpSendRawMessage(
Node *node,
Message *msg,
NodeAddress sourceAddress,
NodeAddress destinationAddress,
int outgoingInterface,
NetworkQueueingPriorityType priority,
unsigned char protocol,
unsigned ttl);
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with payload data for IP packet.
(IP header needs to be added).
sourceAddress: Source IP address. If sourceAddress is ANY_IP, lets IP
assign the source address (depends on the route).
destinationAddress:Destination IP address.
outgoingInterface: outgoing interface to use to transmit packet.
priority: Priority of packet.
protocol: IP protocol number.
ttl: Time to live. See AddIpHeader() for special values.
RETURN: void
• NetworkIpSendRawMessageWithDelay—Same as
NetworkIpSendRawMessage(), but schedules event after a simulation delay.
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with payload data for IP packet. (IP
header needs to be added).
sourceAddress: Source IP address. If sourceAddress is ANY_IP, lets IP
assign the source address (depends on the route).
destinationAddress:Destination IP address.
outgoingInterface: outgoing interface to use to transmit packet.
priority: Priority of packet.
protocol: IP protocol number.
ttl: Time to live.
delay: Scheduled delay.
RETURN: void
SYNTAX:
void NetworkIpSendRawMessageToMacLayer(
Node *node,
Message *msg,
NodeAddress sourceAddress,
NodeAddress destinationAddress,
NetworkQueueingPriorityType priority,
unsigned char protocol,
unsigned ttl,
int interfaceIndex,
NodeAddress nextHop);
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with payload data for IP packet. (IP
header needs to be added).
sourceAddress: Source IP address.
destinationAddress:Destination IP address.
priority: Priority of packet.
protocol: IP protocol number.
ttl: Time to live. See AddIpHeader() for special values.
interfaceIndex: outgoing interface to use to transmit packet.
nextHop: Next hop IP address.
RETURN: void
• NetworkIpSendRawMessageToMacLayerWithDelay—Same as
NetworkIpSendRawMessageToMacLayer(), but schedules the event after a
simulation delay by calling NetworkIpSendPacketOnInterfaceWithDelay().
SYNTAX:
void NetworkIpSendRawMessageToMacLayerWithDelay (
Node *node,
Message *msg,
NodeAddress sourceAddress,
NodeAddress destinationAddress,
NetworkQueueingPriorityType priority,
unsigned char protocol,
unsigned ttl,
int interfaceIndex,
NodeAddress nextHop,
clocktype delay);
SYNTAX:
void NetworkIpSendPacketToMacLayer(
Node *node,
Message *msg,
int interfaceIndex,
NodeAddress nextHop);
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with ip packet.
interfaceIndex: outgoing interface to use to transmit packet.
nextHop: Next hop IP address.
RETURN: void
• NetworkIpSendPacketToMacLayerWithNewStrictSourceRoute—Tacks on
a new source route to an existing IP packet and then sends the packet to the
MAC layer.
SYNTAX:
void NetworkIpSendPacketToMacLayerWithNewStrictSourceRoute(
Node *node,
Message *msg,
NodeAddress newRouteAddresses[],
int numNewRouteAddresses,
BOOL removeExistingRecordedRoute);
PARAMETERS:
node: Pointer to node.
msg: Pointer to message with ip packet.
newRouteAddresses: Source route (address array).
numNewRouteAddresses: Number of array elements.
removeExistingRecordedRoute: Flag to indicate previous record route should
be removed or not.
RETURN: void
SYNTAX:
void NetworkIpReceivePacketFromMacLayer(
Node *node,
Message *msg,
NodeAddress previousHopNodeId,
int interfaceIndex);
PARAMETERS:
SYNTAX:
void NetworkIpSneakPeekAtMacPacket(
Node *node,
const Message *msg,
int interfaceIndex,
NodeAddress prevHop);
PARAMETERS:
node: Pointer to node.
msg: The message being peeked at from the MAC layer. It must
not be freed or modified.
interfaceIndex: The interface of which the viewed message belongs to
packet drop.
prevHop: Previous hop address of the packet.
RETURN: void
SYNTAX:
void NetworkIpReceiveMacAck(
Node* node,
int interfaceIndex,
const Message* msg,
NodeAddress nextHop);
PARAMETERS:
node: Pointer to node.
interfaceIndex: Interface associated with the ACK handler function.
msg: Message that was ACKed.
nextHop: Next hop that sent the MAC layer ACK.
RETURN: void
SYNTAX:
void NetworkIpSetRouterFunction(
Node *node,
RouterFunctionType RouterFunctionPtr,
int interfaceIndex);
PARAMETERS:
node: Pointer to node.
routerFunctionPtr: Pointer to the router function to be set.
SYNTAX:
RouterFunctionType NetworkIpGetRouterFunction(
Node *node,
int interfaceIndex);
PARAMETERS:
node: Pointer to node.
interfaceIndex: Interface for which we want the router function.
RETURN: Router function pointer associated with this interface.
The logical path followed by the packet is illustrated with following figure.
RoutePacketAndSendToMac
YES
Destination is a
broadcast address?
Destination is a YES
multicast address?
YES
Packet already
routed?
Packet already
routed?
End NO
Get router function associated with YES NO
the interface determined at first
step. If router function is available
let it handle the packet. If it has a source route try to
forward with it. Otherwise try to
forward with IP forwarding table.
End
Once the routing decision has been made (i.e. once the outgoing interface and
next hop address is known), the NetworkIpSendPacketOnInterface() function is
called to send the packet on desired interface. This function queues an IP packet
for delivery to the MAC layer. It takes the following arguments.
• node: Pointer to the node.
• msg: Pointer to the message containing IP packet.
• IncomingInterface: Index of incoming interface
• outgoingInterface: Index of outgoing interface
• nextHop: IP address of next hop.
If an access list is specified over this interface this function checks whether
access list permits the packet. If not, then it simply drops the packet silently.
Otherwise it calls NetworkIpSendOnBackplane().
• NetworkIpSendOnBackplane()—This is an internal static function of IP
module. It simulates the backplane delay before they are further processed. Its
arguments are same as previous function. If backplane throughput capacity is
specified as NETWORK_IP_UNLIMITED_BACKPLANE_THROUGHPUT it
simply call QueueUpIpFragmentForMacLayer() with appropriate interface
index, otherwise it tries to insert the packet in CPU Queue (if queue is not full
already, if so then just drop the packet) by calling the function
NetworkIpCpuQueueInsert() and call the function
NetworkIpUseBackplaneIfPossible().
• NetworkIpUseBackplaneIfPossible()—This function basically determines
whether backplane is busy. If so it just returns. Otherwise if there is any packet
that needs to go through the backplane then it just calculates the required
delay and sends a self-timer with event MSG_NETWORK_Backplane.
• NetworkIpLayer()—Catches the event when this timer expire and calls
NetworkIpReceiveFromBackplane().
• NetworkIpReceiveFromBackplane()—This function de-queued the packet
from the queue, set the backplane status to idle so that other packets waiting at
queue can use it and then calls QueueUpIpFragmentForMacLayer() with
appropriate interface index.
• QueueUpIpFragmentForMacLayer()—If the Diffserv Multi-Field Traffic
Conditioner is enabled it first checks whether or not the Diffserv Multi-Field
Traffic Conditioner will drop this packet. If Diffserv allows the packet it checks if
the output queue for the specified interface is empty or full, and calls
NetworkIpOutputQueueInsert() to queue the IP packet. If queue is already full
QualNet queuing protocols currently implemented in network layer and all source
and header files for queues are found in the QualNet/network folder.
The following section describes how to add a new queuing protocol e.g. 'GREEN'
in QualNet. Assume the source filename is greenqueue.c and the corresponding
header is greenqueue.h.
typedef enum {
FIFO_QUEUE,
RED_QUEUE,
RIO_QUEUE,
WRED_QUEUE,
// Add any new Queue Type before this entry
DEFAULT_QUEUE
} NetworkIpQueueType;
To insert a new Queue Type, a symbolic constant for the queuing type (e.g.,
GREEN_QUEUE), should be inserted just before the DEFAULT_QUEUE entry.
Example
queue->queueVar = greenQueue;
There are six interface functions for QualNet queues as depicted in the IpQueue
structure.
IpQueueInsertFunctionType insertFunction;
IpQueueRetrieveMsgAndMaybeDequeueFunctionType retrieveFunction;
IpQueueIsEmptyFunctionType isEmptyFunction;
IpQueueNumberInQueueFunctionType numberInQueueFunction
IpQueueFinalizationFunctionType finalizationFunction;
IpQueueDropPacketFromQueueFunctionType packetDropFunction;
These interface functions are accessed via function pointers and kept separately
in the source file specified for that queue, e.g., greenqueue.c. These functions
are briefly described in the following sections.
NB: User written functions should match the function prototype of these interface
functions.
4.8.2.1 Enqueue
The Enqueue function inserts an incoming packet in the Queue. This is
associated with the function pointer IpQueueInsertFunctionType.
Example
For GREEN queue, the insert function will look like the follwoing:
GreenInsert()
{
// Do GREEN queue specific operations
……….
// NB. Insert a packet in the queue using this generic
function kept in ipqueue.h.
GenericQueueInsert();
………
}
4.8.2.2 Dequeue
The Dequeue function Dequeue a packet from the Queue. This is associated with
the function pointer IpQueueRetrieveMsgAndMaybeDequeueFunctionType.
Example
For GREEN queue, the Dequeue function will look like the following:
GreenDequeue()
{
// Do GREEN queue specific operations
……….
// NB. Dequeue a packet from queue using this generic
function kept in ipqueue.h.
GenericQueueDequeue();
// Update variables and statistic changes during this
For GREEN queue, the isEmptyFunction will look like the following:
GreenIsEmpty()
{
// NB. Checks for a packet in the queue using this
generic function kept in ipqueue.h.
return(GenericQueueIsEmpty());
}
For GREEN queue, the numberInQueueFunction will look like the following:
GreenNumberInQueue ()
{
// NB. Get number of packet in the queue using this
generic function kept in ipqueue.h.
return(GenericNumberInQueue());
}
For GREEN queue, the packetDropFunction will look like the following:
GreenDropPacketFromQueue()
{
// NB. Drop a packet from the queue using this
generic function kept in ipqueue.h.
GenericDropPacketFromQueue();
}
4.8.2.6 Finalize
The finalizationFunction is required to print final statistics collected for this queue
at the end of the simulation. This functionality is optional.
If one doesn't want to print final statistics for this protocol, the associated function
pointer for this functionality, IpQueueFinalizationFunctionType, should be
initialized to NULL. Otherwise this function would look like the following:
GreenFinalize()
{
// Print Extra statistics associated with this
queue
……………..
// NB. Get generic statistics for queue using this
generic function kept in ipqueue.h.
GenericQueueFinalize();
}
4.8.3 Initialization
QualNet queue type is specified in configuration file using the key IP-QUEUE-
TYPE. This is read by the network layer from the file QualNet/network/ip.c.
IP-QUEUE-TYPE GREEN
NetworkIpReturnQueueType()
{
else if (strcmp(qTypeString, "GREEN") == 0)
{
return GREEN_QUEUE;
NetworkIpSetupQueue()
{
// Add initialization functions for new queueing
types here.
switch (queueType)
{
case FIFO_QUEUE:
{
FifoInitialize();
break;
}
…………….
case GREEN_QUEUE:
{
GreenQueueInitialize();
break;
}
default:
{
UserInterfaceInitializeQueue();
}
}//switch//
……….
}
The following steps will lead to the integration of a new queue, e.g., GREEN, in
QualNet.
GreenQueueInitialize(…,IpQueue *queuePtr,…)
{
//NB. Initializing the common variables and structures
for queue using this generic function kept in
ipqueue.h.
GenericQueueInit();
queuePtr->insertFunction = &GreenInsert;
queuePtr->retrieveFunction = &GreenDequeue;
queuePtr->isEmptyFunction = &GreenIsEmpty;
queuePtr->numberInQueueFunction = &GreenNumberInQueue;
queuePtr->packetDropFunction =
&GreenDropPacketFromQueue;
if (Want to Collect Statistics)
{
queuePtr->finalizationFunction = &GreenFinalize;
}
else
{
queuePtr->finalizationFunction = NULL;
}
}
Additionally, the following header files are also relevant to the MAC layer which
contains general API functions relevant to all layers:
• QUALNET_HOME/include/fileio.h
• QUALNET_HOME/include/mapping.h
These files are described in greater detail in “Application Layer” on page 62.
Alternatively the source code also contains detailed comments on functions and
other code components.
The following are the source files and folders (*.c) associated with the MAC layer:
• QUALNET_HOME/mac/
This folder contains the source and header files for the various QualNet MAC
protocols. The file names are indicative of the MAC protocol for which they
provide an implementation e.g. to see the implementation for IEEE 802.3, look
at mac_802_3.c and mac_802_3.h files. There is one exception. IEEE 802.11
(mac_802_11.c and mac_802_11.h) has been placed in QUALNET_HOME/
addons/seq.
• QUALNET_HOME/mac/mac.h and QUALNET_HOME/mac/mac.c
These files contain initialization function, message-processing function, and
finalize function used by MAC layer which in tern calls protocol specific
initialization, message processing, event handling, and finalize functions. It
also contains some function to trigger upper layer on event of errors in
physical.
MacHasFrameToSendFn sendFrameFn;
MacReceiveFrameFn receiveFrameFn;
List *interfaceStatusHandlerList;
NodeAddress virtualMacAddress;
MacVlan *vlan;
};
• mplsVar: This variable points to MPLS data if MPLS is selected to run at this
interface.
• sendFrameFn: Used to send frames to the network.
• receiveFrameFn: Used to receive frames from the network
• interfaceStatusHandlerList: List of functions to let the upper layer protocols
know about packet drop.
• virtualMacAddress: Virtual MAC address needed for HSRP
• vlan: This variable points to the interface VLAN data when it is an interface of a
switch node and VLAN information is provided for this interface.
5.4.2 Enumerations
The main ENUMERATION for the MAC layer is provided in this section. All
protocols must be enlisted in the enumeration. Depending on this enumeration
value MAC kernel determines which protocol functions to call.
typedef enum
{
MAC_PROTOCOL_MPLS = 1,
MAC_PROTOCOL_CSMA,
MAC_PROTOCOL_FCSC_CSMA,
MAC_PROTOCOL_MACA,
MAC_PROTOCOL_FAMA,
MAC_PROTOCOL_802_11,
MAC_PROTOCOL_802_3,
MAC_PROTOCOL_DAWN,
MAC_PROTOCOL_LINK,
MAC_PROTOCOL_ALOHA,
MAC_PROTOCOL_SWITCHED_ETHERNET,
MAC_PROTOCOL_TDMA,
MAC_PROTOCOL_GSM,
MAC_PROTOCOL_LINK16,
MAC_PROTOCOL_SATTSM,
MAC_PROTOCOL_SATCOM,
MAC_SWITCH
} MAC_PROTOCOL;
One more enumeration will be needed for writing a MAC protocol. This
enumeration is in QUALNET_HOME/include/trace.h
The following are API functions necessary to interface with the network layer:
• MAC_NetworkLayerHasPacketToSend
The network layer to trigger MAC when a packet arrives at empty queue uses
this API function. Receiving this trigger a MAC protocol runs protocol specific
access technique to retrieve the packet from the queue.
• MAC_HandOffSuccessfullyReceivedPacket
After receiving a frame from channel, MAC uses this API to pass the received
frame to upper layer. In addition to data, this API also provides incoming
interface of the node and previous hop address of the frame.
• MAC_SneakPeekAtMacPacket
To allow a peek on a packet by network layer when it is not the intended
receiver of that packet. This is needed if a node is operating in promiscuous
mode.
• MAC_MacLayerAcknowledgement
This API function allows the network layer to know that a packet has been
successfully delivered.
• MAC_NotificationOfPacketDrop
This API function is used to let upper layer protocols to know about a unicast
packet drop in MAC or Physical layer.
Before adding, write this new protocol in two files, nwmp.c and nwmp.h. Like other
protocols, provide data structure definitions and interface functions in the header
file. Place these two files in the 'mac' folder, i.e., QUALNET_HOME/mac. To
compile these files along with other QualNet files, add them to the
QUALNET_HOME/main/Makefile-common file. Add the header file (*.h) to the
simulator header files section and the source file (*.c) to the simulator source file
section of Makefile-common. This addition ensures that the files are compiled.
5.6.1.1 Configuration
QualNet supports the following two types of networks:
• point-to-point network
• broadcast type network
This will specify to run NWMP in the [N16-0] network. In addition, each protocol
requires some configuration parameters for its own operations. Generally existing
wired MAC protocols require data rate and propagation delay to specify.
Now QualNet knows the new protocol type. It is time to add this new protocol's
data to a node. The MAC protocol is interface-specific and the node structure has
the provision to add different MAC protocols at each interface. This is elaborated
in the following figure.
As the previous figure shows, a generic MAC data is created for each interface.
This MAC data provides a pointer to hold protocol data for particular protocol that
is configured for that interface. The tie up between MAC data and protocol data is
done during initialization.
if (strcmp(macProtocolName, "SWITCHED-ETHERNET") == 0) {
...
...
return;
}
else if (strcmp(macProtocolName, "MAC802.3") == 0) {
...
...
return;
}
else if (strcmp(macProtocolName, "NWMP") == 0) {
NwmpGetSubnetParameters(interfaceAddress, nodeInput,
&subnetBandwidth, &subnetPropDelay);
node->macData[interfaceIndex]->macProtocol = MAC_PROTOCOL_NWMP;
node->macData[interfaceIndex]->bandwidth = subnetBandwidth;
node->macData[interfaceIndex]->propDelay = subnetPropDelay;
node->macData[interfaceIndex]->mplsVar = NULL;
//
// MAC protocol models below require a PHY model
//
...
...
...
As seen from the above code segment, NwmpInit is responsible to initialize this
new protocol. It assigns the initial values for NwmpData members. As said earlier,
wired MAC protocol takes the additional responsibility to provide physical layer
functionality. So it keeps a list of its members that are connected to same network,
to broadcast frames. In general, a wired initialization routine routing do the
following activity.
• Creates the protocol data and links it to macVar present in MacData for a
interface.
• Initialize the members belongs to protocol data.
• Reads additional user configurable parameters if required for the protocol.
• Adds an entry for the attached network to forwarding table for this newly
created interface.
• Schedules periodic times if require by the protocol.
• Collects neighbor information to create neighbor list necessary for physical
functionality.
Generally a MAC protocol waits for any packets to come from either upper layer or
below the mac layer. It then processes the packet and passes it to proper layer.
When it gets packets from upper layer in idle state, it broadcasts it and schedules
a self-timer to make it busy. Besides, some MAC protocols sends protocol specific
control packets that are received by this protocol at receiving nodes. This requires
an event dispatcher to handle all the timers and protocol events.
...
...
/* Select the MAC protocol model, and direct it to handle the
message. */
switch (node->macData[interfaceIndex]->macProtocol)
{
case MAC_PROTOCOL_802_11:
{
Mac802_11Layer(node, interfaceIndex, msg);
break;
}
...
...
case MAC_PROTOCOL_NWMP:
{
NwmpLayer(node, interfaceIndex, msg);
break;
}
}
}
This update provides all the new protocol related self-timers and protocol events
to reach up to the protocol. But proper flow requires correct scheduling of these
timers and events. According to convention, message can handle all the self-
timers and protocol events. The following steps should be executed during a
scheduling message.
1. Set the layer as MAC_LAYER.
2. Set the protocol as MAC_PROTOCOL_NWMP.
3. Set the event to proper event for which the message is going to schedule.
4. Set the instance ID with interfaceIndex where the message is going to fire.
At the protocol level all timers and events are separated using event field of
message. So each protocol should provide well-defined events to execute its
protocol activity. Unknown events should handled properly to eliminate any
malfunctioning. A generic construction of NwmpLayer is provided with an example
of message scheduling.
switch (eventType)
{
case MSG_MAC_Event_A:
case MSG_MAC_Event_B:
case MSG_MAC_Event_C:
default:
ERROR_Assert(FALSE,
"Received a message of unknown event");
MESSAGE_Free(node, msg);
break;
}
}
void
MAC_NetworkLayerHasPacketToSend(Node *node, int interfaceIndex)
{
...
...
...
switch (node->macData[interfaceIndex]->macProtocol)
{
case MAC_PROTOCOL_802_11:
{
Mac802_11NetworkLayerHasPacketToSend(
node,
(MacData802_11 *) node->macData[interfaceIndex]->macVar);
break;
}
...
...
case MAC_PROTOCOL_NWMP:
{
NwmpNetworkLayerHasPacketToSend(
node,
(MacData802_11 *) node->macData[interfaceIndex]->macVar);
break;
}
}
This modification in existing API provides the necessary changes to get proper
trigger. Now the protocol specific function NwmpNetworkLayerHasPacketToSend
is responsible for retrieving datagram. A generic example is provided.
static
void NwmpNetworkLayerHasPacketToSend(
Node *node, int interfaceIndex)
{
if (channel is busy)
{
return;
}
MAC_OutputQueueDequeuePacket(node,
interfaceIndex,
&newMsg,
&nextHopAddress,
&networkType,
&priority);
All neighbors will get this frame and decides if the frame is sent for this station. If
frame is intended for this station, it will retrieve the datagram from the received
frame and send it to upper layer. MAC_HandOffSuccessfullyReceivedPacket is
the API that is used by a MAC protocol to send datagram to upper layer.
for (interfaceIndex = 0;
interfaceIndex < node->numberInterfaces;
interfaceIndex++)
{
/* Select the MAC protocol model and finalize it. */
if (node->macData[interfaceIndex])
{
switch (node->macData[interfaceIndex]->macProtocol)
{
case MAC_PROTOCOL_802_11:
{
Mac802_11Finalize(node, interfaceIndex);
break;
}
...
...
case MAC_PROTOCOL_NWMP:
{
NwmpFinalize(node, interfaceIndex);
break;
}
}
}
}
...
...
The previously displayed code provides the necessary changes to layer finalize
API. NwmpFinalize is responsible for printing statistics.
6. Physical Layer
This section covers the following topics
• Physical Layer Overview
• Physical Layer Organization: Files and Folders
• Writing and Adding an Antenna Model
• Writing and Adding a Radio Model
• Writing and Adding a Channel Model
• Adding a Mobility Model
In addition, the physical layer models also utilize functions from the following:
• main/fileio.c—parsing input files for parameters
• main/gui.c—GUI animation of physical layer events
• main/random.c—random number generation
6.3.2 Initialization
Initialization of an antenna model starts with reading the antenna properties such
as ANTENNA-GAIN, ANTENNA-HEIGHT, and ANTENNA-MODEL. Add the
initialization code or function call of the antenna model to the AntennaInit function
in phy/antenna.c. The AntennaInit function is called during the physical layer
model initialization of each node. If needed, the model may use the antennaVar
pointer in PhyData to store model specific data in addition to the antenna
properties stored in PhyData. This model-specific data structure may be created
in a separate header file supporting the antenna's main source file. The header
file should be included in the necessary places, such as any source files of
physical layer models that intend to use the antenna model. Figure 28, “Antenna
Model Initialization” presents some sample code displaying the initialization of
antenna models.
if (wasFound) {
phyData->antennaGain_dBi = (float)antennaGain_dBi;
}
else {
phyData->antennaGain_dBi = ANTENNA_DEFAULT_GAIN_dBi;
}
#ifndef ANTENNA_YOUR_MODEL_H
#define ANTENNA_YOUR_MODEL_H
typedef struct {
int numPatterns;
int patternSetRepeatAngle;
int patternIndex;
…
float **patternAzimuth_dBi;
float **patternElevation_dB;
BOOL antennaIsLocked;
} YourAntennaModelData;
// Function declarations
void AntennaYourModelNameInit (
Node *node,
int phyIndex,
const NodeInput *nodeInput);
…
#endif
Function Explanation
float AntennaGainForThisDirection( Provide the antenna
gain value for the
Node *node, signal's given
int phyIndex, direction of arrival.
Orientation DOA);
void AntennaLockAntennaDirection(Node *node, For non-omni-
int phyIndex); directional antennas,
lock the direction from
which maximum gain
was observed until the
entire signal is
received.
void AntennaUnlockAntennaDirection(Node *node, Unlock the antenna's
int phyIndex); reception direction.
BOOL AntennaIsLocked(Node *node, int Checks to see whether
phyIndex); the antenna's reception
direction is locked.
void AntennaSetToOmnidirectionalMode(Node Set the antenna to use
*node, int phyIndex); an omnidirectional
pattern
void AntennaSetToBestGainConfiguration If the antenna's
ForThisSignal( direction is not locked,
then identify the
Node *node, radiation pattern which
int phyIndex, provides the best gain
PropRxInfo *propRxInfo); for a given signal and
use it for further
reception.
void AntennaSetBestConfigurationForAzimuth(
Node *node,
int phyIndex,
double azimuth);
void AntennaYourModelNameInit (
Node *node,
int phyIndex,
const NodeInput *nodeInput)
{
PhyData *phyData = node->phyData[phyIndex];
IO_ReadCachedFile(
node->nodeId,
phyData->macInterfaceAddress,
nodeInput,
"ANTENNA-AZIMUTH-PATTERN-FILE",
&wasFound,
&antennaInput);
AntennaReadPatterns( node,
phyIndex,
&antennaInput,
&numPatterns,
&(steerable->patternSetRepeatAngle),
&(steerable->patternAzimuth_dBi),
TRUE);
yourAntennaData->numPatterns = numPatterns;
…
// Read in elevation data if specified in simulation configuration file
}
Although each radio model works differently, there are certain functions that are
performed by most radio models. To write a new radio model, it is recommended
that the user copy most of the functions from a radio model already existing in
QualNet and create or modify only the parts that do not meet the requirements.
The new radio model source file should contain the following:
• Include directives for appropriate header files, a typical radio model file
includes these files:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "api.h"
#include "antenna.h"
#include "antenna_switched.h"
#include "antenna_steerable.h"
• Dispatcher code
• Radio model implementation functions
The following functions are generally required in the new radio model (assuming
NewRadioModel is the name of the newly created protocol):
• PhyNewRadioModelInit
• PhyNewRadioModelTransmissionEnd
• PhyNewRadioModelGetStatus
• PhyNewRadioModelStartTransmittingSignal
• PhyNewRadioModelSignalArrivalFromChannel
• PhyNewRadioModelSignalEndFromChannel
• PhyNewRadioModelGetDataRate
• PhyNewRadioModelGetDataRate
• PhyNewRadioModelGetFrameDuration
• PhyNewRadioModelSetTransmitPower
• PhyNewRadioModelGetTransmitPower
• PhyNewRadioModelFinalize
In addition the new radio model needs to be called in the basic physical functions
such as the following:
• PHY_CreateAPhyForMac
• PHY_ProcessEvent
• PHY_GetStatus
• PHY_StartTransmittingSignal
• PHY_SignalArrivalFromChannel
• PHY_SignalEndFromChannel
• PHY_GetTxDataRate
• PHY_GetRxDataRate
• PHY_GetFrameDuration
• PHY_SetTransmitPower
• PHY_GetTransmitPower
• PHY_Finalize
The next twelve figures provide the practical function calls that need to be
implemented in the QualNet physical layer.
….
switch(node->phyData[phyNum]->phyModel) {
case PHY802_11b:
case PHY802_11a: {
Phy802_11Finalize(node, phyNum);
break;
}
….
case PHY_ABSTRACT: {
PhyAbstractFinalize(node, phyNum);
break;
}
….
case PHY_NEW_RADIO_MODEL: {
PhyNewRadioModelFinalize(node, phyNum);
break;
}
….
}
}
}
} ….
break;
}
….
case PHY_ABSTRACT: {
switch (msg->eventType) {
case MSG_PHY_TransmissionEnd: {
PhyAbstractTransmissionEnd(node, phyIndex);
MESSAGE_Free(node, msg);
break;
}
….
case PHY_New_Radio_model: {
switch (msg->eventType) {
case MSG_PHY_TransmissionEnd: {
PhyNewRadioModelTransmissionEnd(node, phyIndex);
MESSAGE_Free(node, msg);
break;
}
….
}
….
}
}
• PHY_GetTransmissionChannel
• PHY_StartListeningToChannel
• PHY_SignalInterference
• Physical model check and setting
• PhyNewRadioModelReportStatusToMac
void PHY_StartTransmittingSignal( )
{
….
switch(node->phyData[phyNum]->phyModel) {
case PHY802_11b:
case PHY802_11a: {
Phy802_11StartTransmittingSignal(
node, phyNum, msg,
useMacLayerSpecifiedDelay, delayUntilAirborne);
break;
}
….
case PHY_ABSTRACT: {
PhyAbstractStartTransmittingSignal(
node, phyNum, msg,
useMacLayerSpecifiedDelay, delayUntilAirborne);
break;
}
….
case PHY_NEW_RADIO_MODEL: {
PhyNewRadioModelStartTransmittingSignal(
node, phyNum, msg,
useMacLayerSpecifiedDelay, delayUntilAirborne);
break;
}
….
void PHY_SignalArrivalFromChannel( )
{
….
switch(node->phyData[phyIndex]->phyModel) {
case PHY802_11b:
case PHY802_11a: {
Phy802_11SignalArrivalFromChannel(
node, phyIndex, channelIndex, propRxInfo);
break;
}
case PHY_ABSTRACT: {
PhyAbstractSignalArrivalFromChannel(
node, phyIndex, channelIndex, propRxInfo);
break;
}
case PHY_NEW_RADIO_MODEL: {
PhyNewRadioModelSignalArrivalFromChannel(
node, phyIndex, channelIndex, propRxInfo);
break;
}
….
break;
}
….
case PHY_ABSTRACT: {
PhyAbstractSignalEndFromChannel(
node, phyIndex, channelIndex, propRxInfo);
break;
}
case PHY_NEW_RADIO_MODEL: {
PhyNewRadioModelSignalEndFromChannel(
node, phyIndex, channelIndex, propRxInfo);
break;
}
….
}
….
case PHY_ABSTRACT: {
return PhyAbstractGetDataRate(node->phyData[phyIndex]);
}
….
case PHY_NEW_RADIO_MODEL: {
return PhyNewRadioModelGetDataRate(node->phyData[phyIndex]);
}
….
PHY_GetTxDataRate returns the current data rate in physical model, the function
PhyNewRadioModelGetDataRate should be called in this function as shown in the
previous figure.
….
case PHY_ABSTRACT: {
return PhyAbstractGetDataRate(node->phyData[phyIndex]);
}
….
case PHY_NEW_RADIO_MODEL: {
return PhyNewRadioModelGetDataRate(node->phyData[phyIndex]);
}
….
}
….
}
clocktype PHY_GetFrameDuration(
Node *node,
int phyIndex,
int dataRateIndex,
int size)
{
….
switch(thisPhy->phyModel) {
case PHY802_11b:
case PHY802_11a: {
return Phy802_11GetFrameDuration(thisPhy, dataRateIndex, size);
}
….
case PHY_ABSTRACT: {
int dataRate = PHY_GetTxDataRate(node, phyIndex);
return PhyAbstractGetFrameDuration(thisPhy, size, dataRate);
}
….
case PHY_NEW_RADIO_MODEL: {
int dataRate = PHY_GetTxDataRate(node, phyIndex);
return PhyNewRadioModelGetFrameDuration
(thisPhy, size, dataRate);
}
….
}
….
return;
}
….
case PHY_ABSTRACT: {
PhyAbstractSetTransmitPower(node->phyData[phyIndex],
newTxPower_mW);
return;
}
….
case PHY_NEW_RADIO_MODEL: {
PhyNewRadioModelSetTransmitPower
(node->phyData[phyIndex], newTxPower_mW);
return;
}
….
}
….
void PHY_GetTransmitPower(
Node *node,
int phyIndex,
double *txPower_mW)
{
….
switch(node->phyData[phyIndex]->phyModel) {
case PHY802_11b:
case PHY802_11a: {
Phy802_11GetTransmitPower(node->phyData[phyIndex],
txPower_mW);
return;
}
….
case PHY_ABSTRACT: {
PhyAbstractGetTransmitPower(node->phyData[phyIndex],
txPower_mW);
return;
}
….
case PHY_NEW_RADIO_MODEL: {
PhyNewRadioGetTransmitPower(node->phyData[phyIndex],
txPower_mW);
return;
}
….
}
….
}
break;
}
….
case PHY_ABSTRACT: {
return PhyAbstractGetStatus(node, phyNum);
break;
}
….
case PHY_NEW_RADIO_MODEL: {
return PhyNewRadioModelGetStatus(node, phyNum);
break;
}
….
}
….
}
Function PHY_GetStatus Retrieves the Phy's current status, and report it to MAC
layer, the function PhyNewRadioModelGetStatus should be called as in the
previous figure.
After coding work, one should add the new radio models to QualNet. To do so,
add these files to the QualNet source tree by modifying the file Makefile-common
in QUALNET_HOME/main/ to include the created files. Add the header file (*.h)
to the simulator header files section and the source file (*.c) to the simulator
source file section of Makefile-common. This ensures the files will be compiled.
Test that the files are successfully added to the QualNet source tree by compiling
QualNet. Refer to the parts in this file to see how to compile QualNet.
6.5.2 Initialization
During initialization, propagation model parameters for each channel defined in
the simulation's configuration file are read-in and set. These parameters include
PROPAGATION-PATHLOSS-MODEL, PROPAGATION-SHADOWING-SIGMA,
and PROPAGATION-FADING-MODEL. QualNet calls the global propagation
model initialization function PROP_GlobalInit in phy/propagation.c for this. Add
code necessary for the model's global initialization to this function. Node-based
initialization of channel model properties is performed by the PROP_Init function
in phy/propagation.h.
if (wasFound) {
if (strcmp(buf, "FREE-SPACE") == 0) {
propProfile->pathlossModel = FREE_SPACE;
}
else if (strcmp(buf, "TWO-RAY") == 0) {
propProfile->pathlossModel = TWO_RAY;
}
else if (strcmp(buf, "YOUR_PATHLOSS_MODEL") == 0) {
propProfile->pathlossModel = YOUR_PATHLOSS_MODEL;
}
}
// Read-in and set Shadowing sigma for log-normal-shadowing
if (propProfile->fadingModel == RICEAN) {
…
const float kFactor = propProfile->kFactor;
const int numGaussianComponents =
propProfile0->numGaussianComponents;
const int startingPoint =
RandomizeGaussianComponentStartingPoint(
node1->nodeId, node2->nodeId, channelIndex,
numGaussianComponents);
arrayIndexInDouble =
node1->propData[channelIndex].fadingStretchingFactor *
(double)currentTime;
…
arrayIndex =
((int)arrayIndexInDouble + startingPoint) %
numGaussianComponents;
value1 = propProfile0->gaussianComponent1[arrayIndex] +
sqrt(2.0 * kFactor);
value2 = propProfile0->gaussianComponent2[arrayIndex];
*fading_dB =
IN_DB((value1 * value1 + value2 * value2) / (2.0 * (kFactor + 1))
}
else if (propProfile->fadingModel == YOUR_FADING_MODEL) {
*fading_dB = CalculateFadingForYourModel (…);
// Perform your fading model’s calculations
}
else {
*fading_dB = 0.0;
}
}
*pathloss_dB = PathlossTwoRay(pathProfile->distance,
wavelength,
txAntennaHeight,
rxAntennaHeight);
}
*pathloss_dB += shadowing_dB;
return;
}
case YOUR_PATHLOSS_MODEL:
{
*pathloss_dB = PathlossYourModel(…)
// Perform model specific calculations
*pathloss_dB += shadowing_dB;
}
break;
…
}
6.5.4 Finalization
The following lists summarize the models distributed with QualNet 3.6 and its
optional integration modules. Explanations of the models and their usage can be
found in the QualNet User's Manual, and source code for most is available in the
QUALNET_HOME/mobility directory of the QualNet installation. The existing
source files can be used as reference when creating a new model.
Mobility:
• Random waypoint
• Trace files
Node Distribution:
• Uniform
• Random
• Grid
• File
Terrain:
• DTED (Digital Terrain Elevation Data)
• DEM (Digital Elevation Model)
In the QualNet mobility model, Cartesian coordinate system is used for small
terrain where the curvature of the Earth can be abstracted out, and Spherical
(Latitude-Longitude-Altitude) coordinate system is used for large terrain where the
In summary, a mobility model needs to specify the positions and times where the
node changes speed or direction, and QualNet will calculate all the intermediate
positions where the node is moving at a constant velocity.
QualNet provides a simple API for add new mobility model that significantly
reduces the effort required to create new mobility models. One simple technique
for creating a new model is to copy one of the existing QualNet mobility models in
the QUALNET_HOME/mobility directory and modify or create only the parts
necessary.
#ifndef MOBILITY_NEWMOBILITYMODEL_H
#define MOBILITY_NEWMOBILITYMODEL_H
#endif /* MOBILITY_NEWMOBILITYMODEL_H */
The initialization function shown here is required for all mobility models as it will be
called from the QualNet kernel during initialization. The function prototype should
include the three parameters (as shown above):
• node: a pointer to the node's data structures.
• nodeInput: a pointer to the input configuration file.
• maxSimClock: the length of the simulation in nanoseconds.
After the configuration parameter is read, the function determines the initial
position of the node (node->mobilityData->current.position) on line 24. The
remainder of the function repeatedly calls an algorithm function (not shown) on
line 28 to determine each future position of the node until the end of the simulation
period. Each position is added to the node's future position buffer with the function
call MOBILITY_AddDest on line 31.
To add the new mobility model into QualNet, it needs to be called in the function of
MOBILITY_Initialize, in the file QUALNET_HOME/mobility/mobility.c, and the new
source files need to be added to the master Makefile so that they can be
compiled. The following figure shows the MOBILITY_Initialize, which first reads
the user defined parameter in configuration file for the new mobility model, as
shown on lines 4-10, and then calls the created function for the new mobility
model on lines 26-30.
The QualNet Graphical User Interface, or GUI, has several components that are
useful for developers.
1. Chapter Overview
They are covered in the following sections:
• Overview of GUI Components
• Using QualNet Animator
• Adding GUI Animation to a Protocol
• Adding New GUI Parameters
• Using QualNet Designer
• Updating Tracer to Support New Protocol Traces
These tools in QualNet allow for a limited amount of customization. While the
source code for these tools is not provided, configuration files in the gui/settings
directory allow the tools to be re-configured.
Each of these files uses an HTML-style syntax which contains nested tokens
composed of several named keys. Token syntax appears as follows:
This section explains the general structure and usage of these files, but is not a
complete reference.
• variable
• option
The variable & option tokens define the variables and their values.
For example:
</option>
</variable>
The "default" key in the variable token refers to the name of the option
rather than the value.
A subcategory has one or more variable tokens. The following are the keys of the
subcategory token:
• title—This is the title of the subcategory
• help—This is used for pop-up info bubbles.
• class—This can have any one of the following values:
• device (means that the subcategory will appear in the "Node properties"
window
Any token that has one or more child tokens must have an ending tag. For
example the category token always contains one or more subcategory tokens, so
it always starts with <category… and ends with </category>. Similarly, whenever
a variable contains option token, it always ends with </variable> token.
New applications are added to the applications.txt file. This application will be
made available for selection in the Application palette window.
1. Search the choices.txt for the variable token whose key is ROUTING-
PROTOCOL.
2. Add the new protocol as an option. The user needs to find out the right file and
right place in the file to add something new. To achieve this, one has to
typically search the choices.txt for the variable with appropriate key.
if (node->guiOption) {
// GUI commands.
The programmer can customize the appearance of some of the animation using
the GUI_SetEffect function.
As of version 3.6, only the following events can handle the following set of effects.
• GUI_Receive (GUI_FLASH_X, GUI_CIRCLE_NODE,
GUI_DEFAULT_EFFECT)
• GUI_Unicast(GUI_FLASH_X, GUI_CIRCLE_NODE,
GUI_DEFAULT_EFFECT)
Both the C code in the Simulator and the Java code in the Animator that
implements this interface is currently hidden from customers. Some
portions of the C code may be provided to customers upon request.
Upon seeing the -interactive command line flag, QualNet Simulator does the
following:
Steps 2-3 are essentially identical to running the Simulator from the command
line. Steps 1 and 4 establish the communication channel between QualNet
Simulator and Animator. Step 5 is basically identical to the non-animated mode of
execution, with occasional information sent to Animator to configure the network
for viewing. Steps 6 and 7 will be described more fully in the following section.
The commands are sent across the socket as character strings, the command
number (as defined in gui.h) followed by the parameters, if any. For example, the
command to set the communication interval to 100 milliseconds would be 1
100000000 where 1 is the command number for GUI_SET_COMM_INTERVAL
and 100000000 is the time representation of 100 milliseconds. 100MS is also an
acceptable representation.
3.3.3 Finalization
With the Animator involved, simulation execution can end in one of three ways:
• The simulation runs to completion and terminates normally. A GUI_FINISHED
reply is sent from Simulator to Animator.
• The simulation terminates with an error. A GUI_ERROR or GUI_ASSERTION
reply is sent to Animator, the Animator cleans up a little and sends a
GUI_STOP command to the Simulator, and the Simulator shuts down by
sending a GUI_FINISHED reply back to the Animator.
• The User terminates the execution by pressing the Animator's stop button. The
Animator cleans up a little and sends a GUI_STOP command to the Simulator,
and the Simulator shuts down by sending a GUI_FINISHED reply back to the
Animator.
For reference, the statistics file is a text file, with one statistic reported per line.
The line is formatted as a comma separated list of fields:
• Node ID
• IP address (optional)
• Index (used by some protocols for queue number or interface number)
• Protocol Layer
• Protocol Name
• Metric Name = value
The layers.txt file defines the standard APIs between layers that the Designer
uses as event triggers, and also defines some special requirements at each layer.
It is a text file and recognizes both # lines and <comment> tags as comments.
The file is divided into three sections—Layers, API, Special. The Layer section
numbers and names the layers.
<LAYERS>
0 Channel
1 Radio
…
</LAYERS>
The API section lists the standard events between layers, using the layer IDs to
mark the source and destination layers for each event, e.g.
The ACD.rules file defines the functions used in assisted code development. It is
a text file and uses C++ style commenting. Some functions, such as that defined
here, are general purpose "semantic" functions, which aren't necessary a single
function call, but some useful chunk of code that the user might want to use. The
function definition consists of three sections: PARAMETERS, COMMENTS, and
CODE, which are almost self-explanatory. The CODE section uses placeholders
for where the Designer asks the user for input.
<FUNCTION:Initialize Seed:GENERAL:GENERIC>
<PARAMETERS>
</PARAMETERS>
<COMMENTS>
</COMMENTS>
<CODE>
<dataPtr->seed[0] = (unsigned short) (node->seed[0] +
1);>
<dataPtr->seed[1] = (unsigned short) (node->seed[1] +
1);>
<dataPtr->seed[2] = (unsigned short) (node->seed[2] +
1);>
</CODE>
</FUNCTION>
Finally, the patch.rules file tells the Designer how and where to "patch" the
Simulator source code to add the newly designed protocol. The file is divided by
layer. For each layer, there is a set of rules for modifying the files. The format for
a rule is as follows:
The file to change is listed as a relative path from QUALNET_HOME. The type of
file can be C_FILE, GUI_FILE, or MAKE_FILE. A subset of the possible actions is
given in the following table:
To specify the Simple ALOHA data structures, complete the following steps:
1. Choose the Add Data Definitions button from left-hand menu on the Designer
screen.
3. Click the Structure Members button to pop up a dialog to enter the fields.
4. Click the Add button after filling in the type and name for each field.
5. Once all field have been entered, click OK.
6. To add the final structure, click Add.
• Packets Received
dataPtr->stats.<statsVariableName>
NetworkLayerHasPacketToSend
ReceivePacketFromPhy
ReceivePhyStatusChangeNotification
3. Draw an automatic transition from the Initial State to the idle state.
4. Use triggers to link the idle state to the three special MAC states.
This is a mechanism to let Designer know these are the special MAC
functions.
5. Draw transitions from idle to each of three states.
7. Add a state named Transmit and one named Receive for sending and
receiving data.
8. Before adding transition to the Receive and Transmit states, first specify the
special MAC State Entry Code function code for the three MAC functions.
• ReceivePacketFromPhy—Called when the physical layer has a packet to
send up to the MAC.
The entry code is as follows:
This grabs AlohaHeader from the packet. It creates boolean variables that
determine if the packet is a unicast or broadcast.
if (oldPhyStatus == PHY_TRANSMITTING) {
assert(newPhyStatus != PHY_TRANSMITTING);
This creates a boolean that represents if there are any packets on the
queue. It also asserts that if the physical layer was transmitting previously,
then currently it is not.
isUnicastFrame || isBroadcastFrame
Much like the Transmit state, Receive will reenter the idle state after its code
has been executed.
20. Create an automatic transition from the Receive state to the Idle state.
21. Enter the entry code for Transmit and Receive:
assert(dataPtr != NULL);
memset(dataPtr, 0, sizeof(AlohaData));
dataPtr->myMacData = node->macData[interfaceIndex];
dataPtr->initStats = TRUE;
Lines 1-3 allocate the protocol data structure (dataPtr). Lines 4-5 set general
MAC data structure pointer that resides in dataPtr. Line 6 turns on statistic
initialization.
22. The final state usually serves as printing out statistics and freeing structures.
Designer automatically generates all necessary statistic printing code, so all
that is necessary is to turn on the feature.
The entry code is as follows:
dataPtr->printStats = dataPtr->myMacData->macStats;
The generic MAC data structure decides if statistic printing has been enabled.
Tracer customization consists of adding support for new protocols. This is a two
part process. First, the Simulator code must be updated to produce trace output
for a new protocol. Second, a description of that trace output must be made
available to the Tracer.
The following steps must be followed to add packet tracing support to a new
protocol. For the purpose of this listing, we will use UDP as an example. (UDP
already has tracing enabled.)
1. In the file include/trace.h, add a new line to the TraceProtocolType
enumeration, e.g..
enum {
TRACE_TCP,
TRACE_IP,
TRACE_AODV,
TRACE_DSR,
…
TRACE_UDP,
// Must be last one!!!
TRACE_ANY_PROTOCOL
};
traceAll = TRACE_IsTraceAll(node);
IO_ReadString(
node->nodeId,
ANY_ADDRESS,
nodeInput,
"TRACE-UDP",
&retVal,
buf);
if (retVal)
{
if (strcmp(buf, "YES") == 0)
{
if (!traceAll)
{
TRACE_EnableTrace(node, TRACE_UDP);
}
}
else if (strcmp(buf, "NO") == 0)
{
if (traceAll)
{
TRACE_DisableTrace(node, TRACE_UDP);
}
}
else
{
ERROR_ReportError("TRACE-UDP should be either \"YES\" or \""
"NO\".\n");
}
}
else
{
if (!traceAll)
{
TRACE_DisableTrace(node, TRACE_UDP);
}
}
}
/*
* FUNCTION TransportUdpPrintTrace
* PURPOSE Print out packet trace information.
*
* Parameters:
* node: node printing out the trace information.
* msg: packet to be printed.
*/
void TransportUdpPrintTrace(Node *node, Message *msg)
{
char buf[MAX_STRING_LENGTH];
TransportUdpHeader* udpHdr = (TransportUdpHeader *)
MESSAGE_ReturnPacket(msg);
case TRACE_UDP:
{
TransportUdpPrintTrace(node, message);
break;
}
When the packet is moving down the stack, add the header before printing. (In
the current implementation, the MESSAGE_AddHeader function allocates
space for the header, and then the user adds the data.)
udpHdr->sourcePort = info->sourcePort;
udpHdr->destPort = info->destPort;
udpHdr->length = MESSAGE_ReturnPacketSize(msg);
udpHdr->checksum = 0; /* checksum not calculated */
Example:
9,0;70.000000000;19;3;1,2;1024,59,520,0;4,5,1,540,0,000,0,64,17,0,19,17
If the previous line is parsed, it would be like the following:
The important part for a protocol author is that the header fields for a protocol are
printed as a comma separated list.
Field length of less than 1 byte are written as 1 byte (purely an assumption used
by the Tracer tool).
Field data type has no special use now (exception IPADDR) and may not match
the fib6ebld length". Still, it is a mandatory field, which might be used for future
enhancements.
In the example cited, the list of protocols' ids has value "1,2". So bthe UDP and
the IP header fields appear thereafter.
<header 1 8 UDP>
Source Port,2,int
Destination Port,2,int
Length,2,int
Checksum,2,int
</header>