Files
unison/doc/tutorial/other.texi
2008-02-04 12:56:08 -08:00

1868 lines
78 KiB
Plaintext

@c ========================================================================
@c Other Network Topologies
@c ========================================================================
@node Other-network-topologies
@chapter Other Network Topologies
@cindex topology
@cindex Channel
@cindex NetDevice
@cindex topology!bus
@cindex topology!point-to-point
@cindex PointToPointChannel
@cindex PointToPointNetDevice
@emph{Network topology} is the study of the arrangement of of the elements
(in @command{ns-3} represented by the classes @code{Channel} and @code{Node})
of a network. Two fundamental types of physical topologies are the
@emph{point-to-point} and @emph{bus} topologies. We have already been exposed
to the @command{ns-3} channel specialization named @code{CsmaChannel}. This is
a simulation of a bus network. We also provide a simulation of a
point-to-point channel with associated net devices. As described previously,
the associated C++ classes specialize the @command{ns-3} base classes
@code{NetDevice} and @code{Channel} and are called @code{PointToPointNetDevice}
and @code{PointToPointChannel} respectively.
We will use combinations of these bus and point-to-point topology elements
to show how to create several commonly seen network topologies.
@section A Point-to-Point Network
We're going to take what might be seen as a step backward and look at a simple
point-to-point network. We will be building the simplest network you can
imagine. A serial link (point to point) between two computers. When you
see this point-to-point network, you can think of an RS-422 (or RS-232 for
you old-timers) cable. This topology is shown below.
@sp 1
@center @image{pp,,,,png}
We have provided a file for you in the @code{tutorial}
directory called @code{tutorial-point-to-point.cc}. You should now be
familiar enough with the system to pick out fairly easily what has been
changed. Let's focus on the following lines:
@verbatim
Ptr<Node> n0 = CreateObject<InternetNode> ();
Ptr<Node> n1 = CreateObject<InternetNode> ();
Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
n0, n1, DataRate (38400), MilliSeconds (20));
PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
n1, "10.1.1.2");
@end verbatim
You can see that we created two @code{InternetNode} objects in the usual way.
Then, instead of creating a @code{CsmaChannel} we create a
@code{PointToPointChannel}. This point-to-point channel, which we call
@code{link}, connects node zero (@code{n0}) and node one (@code{n1}) over a
simulated link that runs at 38400 bits per second and has a 20 millisecond
simulated speed-of-light delay. This call also creates appropriate net devices
and attaches them to nodes zero and one.
We then add IP addresses to the net devices we just created using the topology
helper @code{AddIpv4Addresses}. Node zero gets the IP address 10.1.1.1 and
node one gets the IP address 10.1.1.2 assigned.
The alert tutorial user may wonder what the network number or prefix is of
those IP addresses. The point-to-point topology assumes that you want a
@code{/30} subnet and assigns an appropriate net mask for you. It then then
@emph{asserts} that the network numbers of the two net devices match. So there
is an implicit network mask created down in the topology code that looks like,
@verbatim
Ipv4Mask netmask("255.255.255.252");
@end verbatim
The rest of the code you should recognize and understand. We are just going
to echo one packet across the point-to-point link. You should be now be able
to build and run this example and to locate and interpret the ASCII trace
file. This is left as an exercise for you.
The file @code{tutorial-point-to-point.cc} is reproduced here for your
convenience:
@verbatim
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "ns3/log.h"
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/mac48-address.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-topology.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-server.h"
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/ascii-trace.h"
#include "ns3/pcap-trace.h"
#include "ns3/global-route-manager.h"
NS_LOG_COMPONENT_DEFINE ("PointToPointSimulation");
using namespace ns3;
// Network topology
//
// point to point
// +--------------+
// | |
// n0 n1
//
int
main (int argc, char *argv[])
{
LogComponentEnable ("PointToPointSimulation", LOG_LEVEL_INFO);
NS_LOG_INFO ("Point to Point Topology Simulation");
Ptr<Node> n0 = CreateObject<InternetNode> ();
Ptr<Node> n1 = CreateObject<InternetNode> ();
Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
n0, n1, DataRate (38400), MilliSeconds (20));
PointToPointTopology::AddIpv4Addresses (link, n0, "10.1.1.1",
n1, "10.1.1.2");
uint16_t port = 7;
Ptr<UdpEchoClient> client = CreateObject<UdpEchoClient> (n0, "10.1.1.2",
port, 1, Seconds(1.), 1024);
Ptr<UdpEchoServer> server = CreateObject<UdpEchoServer> (n1, port);
server->Start(Seconds(1.));
client->Start(Seconds(2.));
server->Stop (Seconds(10.));
client->Stop (Seconds(10.));
AsciiTrace asciitrace ("tutorial.tr");
asciitrace.TraceAllQueues ();
asciitrace.TraceAllNetDeviceRx ();
Simulator::Run ();
Simulator::Destroy ();
}
@end verbatim
@section A Star Network
A point-to-point network is considered a special case of a star network. As
you might expect, the process of constructing a star network is an extension
of the very simple process used for a point-to-point link. We have provided
a file for you in the @code{tutorial} directory called @code{tutorial-star.cc}
that implements a simple star network as seen below.
@sp 1
@center @image{star,,,,png}
In order to create a star network, we need to be able to instantiate some
number (greater than one) of net devices on a node. In the name of simplicity
of use, the @code{PointToPointTopology} topology helper does not allow one to
do this. We provided a separate topology helper class, the
@code{PointToPointIpv4Topology} helper class that provides the slightly finer
granularity we need to accomplish a star network. In order to use this new
helper we have to load the definitions by including the appropriate file.
@verbatim
#include "ns3/point-to-point-ipv4-topology.h"
@end verbatim
The star that we're going to create has a node in the center (@code{n0}) with
six nodes surrounding (@code{n1} - @code{n6}). You should be able to easily
find and understand the code that creates these nodes.
@verbatim
Ptr<Node> n0 = CreateObject<InternetNode> ();
Ptr<Node> n1 = CreateObject<InternetNode> ();
Ptr<Node> n2 = CreateObject<InternetNode> ();
Ptr<Node> n3 = CreateObject<InternetNode> ();
Ptr<Node> n4 = CreateObject<InternetNode> ();
Ptr<Node> n5 = CreateObject<InternetNode> ();
Ptr<Node> n6 = CreateObject<InternetNode> ();
@end verbatim
Next, we get into the differences between the @code{PointToPointTopology}
helper and the @code{PointToPointIpv4Topology} helper. The
@code{PointToPointIpv4Topology} helper looks and feels a little like the
@code{CsmaIpv4Topology} helper. Just like you created a CSMA channel
previously, you need to create a point-to-point channel. The following
code creates a @code{PointToPointChannel} and calls it @code{link01}. You can
interpret this name as being the channel (or @emph{link}) from node zero to
node one.
@verbatim
Ptr<PointToPointChannel> link01 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
@end verbatim
You need to provide a data rate for the channel which we set at 38400 bits
per second. You must also provide a speed-of-light delay which we set at
20 milliseconds.
Just as you added a net device to the nodes in the CSMA tutorial section, you
do the same here but with a point-to-point net device. The following code
illustrates how we do that:
@verbatim
uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
link01);
@end verbatim
We call the @code{PointToPointIpv4Topology} helper and ask it to add a net
device to node zero (@code{n0}) and connect it to the appropriate
point-to-point link (@code{link01}) which you will recall is the serial link
from node zero to node one.
If you look at the following code, you will see the same calls are repeated
to create the remaining five point-to-point channels and connect them
to net devices on node zero.
The next new code is found after the ``spokes'' of the star have been created.
It looks like the following:
@verbatim
uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);
@end verbatim
Here we are creating the net devices on the nodes surrounding the center node.
In the first call, we are adding a net device on node one (@code{n1}) and
connecting that net device to the channel named @code{link01}. Remember that
we created the channel @code{link01} as the channel connecting node zero and
node one. We previously created a net device on node zero and attached that
device to @code{link01}. Here we are connecting the other side of that link
to node one. The return value from this call is the net device index of the
created net device.
The next section of code adds addresses to the net devices we just created.
The first call adds the IP address 10.1.1.1 to the net device going from
node zero to node one. Recall that we first created a node named @code{n0}
and a channel called @code{link01}. We added a net device to @code{n0} and
remembered the net device index as the @code{uint32_t nd01}. This meant
the net device @emph{nd} on node @emph{0} that we connected to node @emph{1}.
We call @code{AddAddress} to add an IP address (10.1.1.1) to the net device
on node zero identified by the net device index @code{nd01}. We provide a
net mask suitable for a point to point network. This is typically a /30
address but we don't force that in this API.
After setting up the address on node zero, we do the same for the node on
the other end of the ``spoke'' --- in this case node one, with its single
net device. Note that the network number is the same on both sides of this
network.
@verbatim
PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
``255.255.255.252'');
PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
``255.255.255.252'');
@end verbatim
The following code repeats this pattern assining similar IP addresses to the
remaining net devices. Note that there are no @code{Mac48Address} address
assignments --- they are not required.
The rest of the code you should recognize and understand. We are just going
to echo one packet across the point-to-point link. You should be now be able
to build and run this example and to locate and interpret the ASCII trace
file. This is left as an exercise for you.
The file @code{tutorial-star.cc} is reproduced here for your convenience:
@verbatim
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "ns3/log.h"
#include "ns3/ptr.h"
#include "ns3/internet-node.h"
#include "ns3/point-to-point-channel.h"
#include "ns3/mac48-address.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-ipv4-topology.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-server.h"
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/ascii-trace.h"
#include "ns3/pcap-trace.h"
#include "ns3/global-route-manager.h"
NS_LOG_COMPONENT_DEFINE ("StarSimulation");
using namespace ns3;
// Network topology
//
// n3 n2
// | /
// | /
// n4 --- n0 --- n1
// / |
// / |
// n5 n6
int
main (int argc, char *argv[])
{
LogComponentEnable ("StarSimulation", LOG_LEVEL_INFO);
NS_LOG_INFO ("Star Topology Simulation");
Ptr<Node> n0 = CreateObject<InternetNode> ();
Ptr<Node> n1 = CreateObject<InternetNode> ();
Ptr<Node> n2 = CreateObject<InternetNode> ();
Ptr<Node> n3 = CreateObject<InternetNode> ();
Ptr<Node> n4 = CreateObject<InternetNode> ();
Ptr<Node> n5 = CreateObject<InternetNode> ();
Ptr<Node> n6 = CreateObject<InternetNode> ();
Ptr<PointToPointChannel> link01 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
uint32_t nd01 = PointToPointIpv4Topology::AddNetDevice (n0,
link01);
Ptr<PointToPointChannel> link02 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
uint32_t nd02 = PointToPointIpv4Topology::AddNetDevice (n0,
link02);
Ptr<PointToPointChannel> link03 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
uint32_t nd03 = PointToPointIpv4Topology::AddNetDevice (n0,
link03);
Ptr<PointToPointChannel> link04 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
uint32_t nd04 = PointToPointIpv4Topology::AddNetDevice (n0,
link04);
Ptr<PointToPointChannel> link05 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
uint32_t nd05 = PointToPointIpv4Topology::AddNetDevice (n0,
link05);
Ptr<PointToPointChannel> link06 =
PointToPointIpv4Topology::CreateChannel (DataRate (38400),
MilliSeconds (20));
uint32_t nd06 = PointToPointIpv4Topology::AddNetDevice (n0, link06);
uint32_t nd1 = PointToPointIpv4Topology::AddNetDevice (n1, link01);
uint32_t nd2 = PointToPointIpv4Topology::AddNetDevice (n2, link02);
uint32_t nd3 = PointToPointIpv4Topology::AddNetDevice (n3, link03);
uint32_t nd4 = PointToPointIpv4Topology::AddNetDevice (n4, link04);
uint32_t nd5 = PointToPointIpv4Topology::AddNetDevice (n5, link05);
uint32_t nd6 = PointToPointIpv4Topology::AddNetDevice (n6, link06);
PointToPointIpv4Topology::AddAddress (n0, nd01, "10.1.1.1",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n1, nd1, "10.1.1.2",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n0, nd02, "10.1.2.1",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n2, nd2, "10.1.2.2",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n0, nd03, "10.1.3.1",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n3, nd3, "10.1.2.2",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n0, nd04, "10.1.4.1",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n4, nd4, "10.1.4.2",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n0, nd05, "10.1.5.1",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n5, nd5, "10.1.5.2",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n0, nd06, "10.1.6.1",
"255.255.255.252");
PointToPointIpv4Topology::AddAddress (n6, nd6, "10.1.6.2",
"255.255.255.252");
uint16_t port = 7;
Ptr<UdpEchoClient> client = CreateObject<UdpEchoClient> (n0, "10.1.1.2",
port, 1, Seconds(1.), 1024);
Ptr<UdpEchoServer> server = CreateObject<UdpEchoServer> (n1, port);
server->Start(Seconds(1.));
client->Start(Seconds(2.));
server->Stop (Seconds(10.));
client->Stop (Seconds(10.));
AsciiTrace asciitrace ("tutorial.tr");
asciitrace.TraceAllQueues ();
asciitrace.TraceAllNetDeviceRx ();
Simulator::Run ();
Simulator::Destroy ();
}
@end verbatim
@subsection Routing
If you are really excited about this simulator you may have already tried to
modify the scripts outside the tutorial. I know that one of the first things
that would have occurred to me when I saw the star network would have been to
start trying to add applications to echo packets from nodes other than zero.
If you tried, for example, to start the echo client on node one instead of
node zero, you would have found an empty trace file. The reason for this
is that you have now created an internetwork. This means you will need to
enable internetwork routing.
We have provided a file for you in the @code{tutorial} directory called
@code{tutorial-star-routing.cc} to show you how this is done. This extremely
tricky and difficult change is shown below:
@verbatim
GlobalRouteManager::PopulateRoutingTables ();
@end verbatim
This one-line addition, located just before the simulation runs, tells the
@command{ns-3} @emph{global route manager} to walk the topology you created and
build internetwork routing tables for all of the nodes in the simulation.
We changed the client application so that it runs on node four:
@verbatim
Ptr<UdpEchoClient> client = CreateObject<UdpEchoClient> (n4, "10.1.1.2",
port, 1, Seconds(1.), 1024);
@end verbatim
Now if you build and run @code{tutorial-star-routing.cc} you can examine the
@code{tutorial.tr} file and see that your UDP echo packets are now correctly
routed through the topology.
@section A Dumbbell Network
One of the most interesting simple topologies (from a phenomenological point of
view) is commonly called a dumbbell network. The name derives from a
superficial similarity in form to a piece of exercise equipment.
The dumbbell model is typically composed of two bus or star network elements
connected via a point-to-point link. The point-to-point link is usually
configured with a lower bandwidth than the bus elements to provide a
@emph{choke point}.
The following is a representation of the topology.
@sp 1
@center @image{dumbbell,,,,png}
We have provided a file that constructs this dumbbell network and creates
enough data flowing across the choke point that some packets will be dropped.
The file is called @code{tutorial-linear-dumbbell.cc} and is located in the
@code{tutorial} directory. We have already covered all of the code used to
create this network, so we will just quickly go over the main sections of the
script.
The first section creates a CSMA lan that will become the left side of the
dumbbell network. This code should be very familiar since we used the same
process to create our first example.
@verbatim
//
// Create the lan on the left side of the dumbbell.
//
Ptr<Node> n0 = CreateObject<InternetNode> ();
Ptr<Node> n1 = CreateObject<InternetNode> ();
Ptr<Node> n2 = CreateObject<InternetNode> ();
Ptr<Node> n3 = CreateObject<InternetNode> ();
Ptr<CsmaChannel> lan1 =
CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));
uint32_t nd0 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n0, lan1,
"08:00:2e:00:00:00");
uint32_t nd1 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n1, lan1,
"08:00:2e:00:00:01");
uint32_t nd2 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n2, lan1,
"08:00:2e:00:00:02");
uint32_t nd3 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n3, lan1,
"08:00:2e:00:00:03");
CsmaIpv4Topology::AddIpv4Address (n0, nd0, "10.1.1.1", "255.255.255.0");
CsmaIpv4Topology::AddIpv4Address (n1, nd1, "10.1.1.2", "255.255.255.0");
CsmaIpv4Topology::AddIpv4Address (n2, nd2, "10.1.1.3", "255.255.255.0");
CsmaIpv4Topology::AddIpv4Address (n3, nd3, "10.1.1.4", "255.255.255.0");
@end verbatim
The code to generate the CSMA lan on the right side is similar; only the names
have been changed.
@verbatim
//
// Create the lan on the right side of the dumbbell.
//
Ptr<Node> n4 = CreateObject<InternetNode> ();
Ptr<Node> n5 = CreateObject<InternetNode> ();
Ptr<Node> n6 = CreateObject<InternetNode> ();
Ptr<Node> n7 = CreateObject<InternetNode> ();
Ptr<CsmaChannel> lan2 =
CsmaTopology::CreateCsmaChannel (DataRate (10000000), MilliSeconds (2));
uint32_t nd4 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n4, lan2,
"08:00:2e:00:00:04");
uint32_t nd5 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n5, lan2,
"08:00:2e:00:00:05");
uint32_t nd6 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n6, lan2,
"08:00:2e:00:00:06");
uint32_t nd7 = CsmaIpv4Topology::AddIpv4CsmaNetDevice (n7, lan2,
"08:00:2e:00:00:07");
CsmaIpv4Topology::AddIpv4Address (n4, nd4, "10.1.2.1", "255.255.255.0");
CsmaIpv4Topology::AddIpv4Address (n5, nd5, "10.1.2.2", "255.255.255.0");
CsmaIpv4Topology::AddIpv4Address (n6, nd6, "10.1.2.3", "255.255.255.0");
CsmaIpv4Topology::AddIpv4Address (n7, nd7, "10.1.2.4", "255.255.255.0");
@end verbatim
Next, we create a point to point link to connect the two lans. We connect
the point-to-point channel between nodes three (on the left lan) and four
(on the right lan). You should recoginze this as substantially similar to
the link setup from the @code{point-to-point} example.
@verbatim
//
// Create the point-to-point link to connect the two lans.
//
Ptr<PointToPointChannel> link = PointToPointTopology::AddPointToPointLink (
n3, n4, DataRate (38400), MilliSeconds (20));
PointToPointTopology::AddIpv4Addresses (link, n3, "10.1.3.1",
n4, "10.1.3.2");
@end verbatim
Then we configure data flows. We create four echo clients that send UDP
packets from the left side lan to servers created on the right side lan.
Notice that we send 100 packets with an inter-packet gap of ten milliseconds
instead of the single packet we have previously used. This data rate is
sufficient to saturate the point-to-point link and will cause packets to be
dropped when the queue on the link net devices overflows (the default maximum
queue depth is 100 packets). Note that we stagger the start of the echo
clients to slowly bring up the data rates.
@verbatim
//
// Create data flows across the link:
// n0 ==> n4 ==> n0
// n1 ==> n5 ==> n1
// n2 ==> n6 ==> n2
// n3 ==> n7 ==> n3
//
uint16_t port = 7;
Ptr<UdpEchoClient> client0 = CreateObject<UdpEchoClient> (n0, "10.1.2.1",
port, 100, Seconds(.01), 1024);
Ptr<UdpEchoClient> client1 = CreateObject<UdpEchoClient> (n1, "10.1.2.2",
port, 100, Seconds(.01), 1024);
Ptr<UdpEchoClient> client2 = CreateObject<UdpEchoClient> (n2, "10.1.2.3",
port, 100, Seconds(.01), 1024);
Ptr<UdpEchoClient> client3 = CreateObject<UdpEchoClient> (n3, "10.1.2.4",
port, 100, Seconds(.01), 1024);
Ptr<UdpEchoServer> server4 = CreateObject<UdpEchoServer> (n4, port);
Ptr<UdpEchoServer> server5 = CreateObject<UdpEchoServer> (n5, port);
Ptr<UdpEchoServer> server6 = CreateObject<UdpEchoServer> (n6, port);
Ptr<UdpEchoServer> server7 = CreateObject<UdpEchoServer> (n7, port);
server4->Start(Seconds(1.));
server5->Start(Seconds(1.));
server6->Start(Seconds(1.));
server7->Start(Seconds(1.));
client0->Start(Seconds(2.));
client1->Start(Seconds(2.1));
client2->Start(Seconds(2.2));
client3->Start(Seconds(2.3));
server4->Stop (Seconds(10.));
server5->Stop (Seconds(10.));
server6->Stop (Seconds(10.));
server7->Stop (Seconds(10.));
client0->Stop (Seconds(10.));
client1->Stop (Seconds(10.));
client2->Stop (Seconds(10.));
client3->Stop (Seconds(10.));
@end verbatim
The remainder of the file should be quite familiar to you. Go ahead and
run @code{tutorial-linear-dumbbell}. Now take a look at the trace
(@code{tutorial.tr}) file. You will now see trace lines that begin with
@code{d}. Alternatively you can search for the string ``queue-drop'' which
is the expansion of the drop code ('d').
Interpretation of a dropped packet is straightforward. We have expanded
the first @code{queue-drop} trace for you below. See the section on ASCII
tracing for details.
@verbatim
00 d
01 2.40938
02 nodeid=3
03 device=1
04 queue-drop
05 pkt-uid=124
06 LLCSNAP(type 0x800)
07 IPV4(
08 tos 0x0
09 ttl 63
10 id 20
11 offset 0
12 flags [none]
13 length: 1052) 10.1.1.3 > 10.1.2.3
14 UDP(length: 1032)
15 49153 > 7
16 DATA (length 1024)
@end verbatim
We leave it as an exercise to examine the trace files in more detail.
@c ========================================================================
@c Nonlinear Thinking
@c ========================================================================
@node Nonlinear-Thinking
@chapter Nonlinear Thinking
One thing that all of our examples so far have in common is that they are
composed of a linear collection of calls into the @command{ns-3} system. The
programmers among the readers may have wondered why there is not as much
as a for-loop in all of the examples. The answer is that we wanted to
introduce you to @command{ns-3} scripting with a minimum of conceptual
overhead. We're going to remedy that situation shortly.
We have written a number of @command{ns-3} scripts in C++. Although we have
been perfectly linear in our script implementations, just like any other C++
program, an @command{ns-3} script can use any features of the language you
desire. If you will look back at the @code{tutorial-linear-dumbbell.cc}
example, you may notice that the code to create the left and right sides of
the dumbbell is operationally identical --- only the names change. An obvious
improvement of this program would be to use subroutines to create the sides.
Since we are working with C++, we should probably do this in an
object-oriented way. Since object-oriented design is somewhat of a black art
to some people, we'll take some time here and outline a simple methodology
you can follow.
@section Object Design 101 --- Class Ipv4BusNetwork
If you are a master of object oriented design, feel free to skip or skim this
section, in which we derive a simplistic but fully operational bus network
class.
So you want to create a BusNetwork class. Often the biggest hurdle in a
design is figuring out how to get started. One of the simplest and most
straightforward ways to do an object decomposition of a problem is to simply
write down a description of the problem and take a look at the words
you used. Let's take some time and do that, first at a very high level.
@example
A bus network is an implementation of a particular network topology that
contains some number of nodes. Each of these nodes is attached to a single
multi-drop channel. The network itself has some attributes independent of
the topology such as a network mask, network number (prefix) and base IP
address.
@end example
The first thing to do is to focus on the nouns and adjectives. These will
give you a starting point for required classes and member variables.
Immediately we can notice that at the highest level we are talking about the
noun @emph{network}. This probably won't surprise you. We also have an
adjective that modifies the noun --- @emph{bus}. This should lead us to our
first class defintion. Usually class names are constructed in the same way
as an English language sentence would be spoken. For example, one would speak
of a @emph{bus network} in conversation, so we would normally create a
@code{class BusNetwork} to represent it.
One thing to note is that we have used two words in our description quite
naturally: @emph{is} and @emph{has}. When you see these words should should
immediately think of the object-oriented concepts of @emph{ISA} (inheritance)
and @emph{HASA} (containment) respectively. We wrote that a bus network
@emph{is} an implementation of a particular network topology. Perhaps you
will agree that there is a natural base class called @code{Network} that
@emph{has} the attributes discussed above. The fact that a @code{BusNetwork}
@emph{ISA} kind of @code{Network} suggests inheritance. Let's capture that
thought right away remembering that we're focused on IP version four here:
@verbatim
class Ipv4Network
{
public:
Ipv4Address m_network;
Ipv4Mask m_mask;
Ipv4Address m_baseAddress;
};
class Ipv4BusNetwork : public Ipv4Network
{
};
@end verbatim
Let's take a look at the @emph{HASA} relationships of the bus network. Clearly
it will @emph{have} a reference to the underlying channel that implements the
actual communications medium. We use smart pointers for those references, so
one member variable is obvious:
@verbatim
Ptr<CsmaChannel> m_channel;
@end verbatim
A bus network will also need to contain references to all of the nodes we
eventually want to create. If you are working in C++ and see the words contain
or container, you should immediately think of the Standard Template Library
or STL. A quick search of the available containers there will probably lead
you to consider the vector class. A vector is a container that looks like an
array. This is just what we need here. Again, we want to use smart pointers
to reference our nodes, so the declaration of the vector would look like,
@verbatim
std::vector<Ptr<Node> > m_nodes;
@end verbatim
It will save you headaches in the future if you notice that the space between
the two right brackets is required to differentiate this situation from a
right-shift operator. So we have a pretty good start already after just a
little work. Now we need to turn our attention to actions. Let's write
another little description of the things you consider doing to a Bus network.
@example
We need to be able to create a bus network. We need to be able to delete a
bus network. We need to be able to get a handle to a node in order to add
applications. We need to be able to set the network, mask and base address
somehow, specify how many nodes to create and provide the underlying channel
its required bandwidth and delay parameters.
@end example
We now look at the @emph{verbs} in that sentence. These will give a good
starting point for the methods of the classes. For example, the verbs
@emph{create} and @emph{delete} should suggest @emph{constructor} and
@emph{destructor}. The verb @emph{get} leads us to providing a method called
@code{GetNode}. We have to provide a number of parameters so we can either
provide @emph{setters} or we can simply pass them in as parameters to our
constructors. Since this is a simple example, we won't bother to implement
getters and setters (methods to get and set member variables to enhance data
hiding). Let's use this guidance to finish up our class declarations:
@verbatim
class Ipv4Network
{
public:
Ipv4Network (Ipv4Address network, Ipv4Mask mask, Ipv4Address address);
virtual ~Ipv4Network ();
Ipv4Address m_network;
Ipv4Mask m_mask;
Ipv4Address m_baseAddress;
};
class Ipv4BusNetwork : public Ipv4Network
{
public:
Ipv4BusNetwork (
Ipv4Address network,
Ipv4Mask mask,
Ipv4Address startAddress,
DataRate bps,
Time delay,
uint32_t n);
virtual ~Ipv4BusNetwork ();
Ptr<Node> GetNode (uint32_t n);
private:
std::vector<Ptr<Node> > m_nodes;
Ptr<CsmaChannel> m_channel;
};
@end verbatim
That's it. We have actually already walked through almost all of the code
required to construct a bus network in our @code{tutorial-csma-echo.cc}
example, so let's just jump forward and take a look at an implementation
of this thing. We provide an implementation for you in the files
@code{ipv4-bus-network.h} and @code{ipv4-bus-network.cc} located in the
@code{tutorial} directory. We also provide an example that uses the new
class in the file @code{tutorial-bus-network.cc}.
The interesting method from our current perspective is the Ipv4BusNetwork
constructor, shown below:
@verbatim
Ipv4BusNetwork::Ipv4BusNetwork (
Ipv4Address network,
Ipv4Mask mask,
Ipv4Address baseAddress,
DataRate bps,
Time delay,
uint32_t n)
:
Ipv4Network (network, mask, baseAddress)
{
Ipv4AddressGenerator::SeedNetwork (mask, network);
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);
for (uint32_t i = 0; i < n; ++i)
{
Ptr<Node> node = CreateObject<InternetNode> ();
uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
Mac48Address::Allocate ());
Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
network);
CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
m_nodes.push_back (node);
}
}
@end verbatim
Notice that we do the simple and straightforward thing and pass all of our
parameters to the constructor. For those unfamiliar with C++, the line after
the colon and before the opening brace (shown below),
@verbatim
:
Ipv4Network (network, mask, baseAddress)
{
@end verbatim
Passes the appropriate parameters to the constructor of the base class
@code{Ipv4Network}. There are two new calls that we haven't seen immediately
after this initialization. They are:
@verbatim
Ipv4AddressGenerator::SeedNetwork (mask, network);
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
@end verbatim
We provide an IP address generator class to allow us to programatically
allocate IP addresses. The first call to @code{SeedNetwork} gives the
address generator a starting network number to use when generating addresses.
The second call to @code{SeedAddress} gives the address generator a starting
IP address to use. There is a starting network and starting address for each
of the 32 possible network masks. Later in the for loop, you will see a
call to @code{AllocateAddress} in which the IP address for each node created
in the loop is actually generated.
The only unfamiliar call in the reset of the constructor will be:
@verbatim
m_nodes.push_back (node);
@end verbatim
This is the STL code to add the newly created node to the vector of nodes
attached to the bus.
For your convenience, we reproduce the entire bus network implementation below:
@verbatim
/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2007 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "ns3/mac48-address.h"
#include "ns3/csma-net-device.h"
#include "ns3/csma-topology.h"
#include "ns3/csma-ipv4-topology.h"
#include "ipv4-bus-network.h"
#include "ipv4-address-generator.h"
namespace ns3 {
Ipv4Network::Ipv4Network (
Ipv4Address network,
Ipv4Mask mask,
Ipv4Address address)
:
m_network (network), m_mask (mask), m_baseAddress (address)
{
}
Ipv4Network::~Ipv4Network ()
{
}
Ipv4BusNetwork::Ipv4BusNetwork (
Ipv4Address network,
Ipv4Mask mask,
Ipv4Address baseAddress,
DataRate bps,
Time delay,
uint32_t n)
:
Ipv4Network (network, mask, baseAddress)
{
Ipv4AddressGenerator::SeedNetwork (mask, network);
Ipv4AddressGenerator::SeedAddress (mask, baseAddress);
m_channel = CsmaTopology::CreateCsmaChannel (bps, delay);
for (uint32_t i = 0; i < n; ++i)
{
Ptr<Node> node = CreateObject<InternetNode> ();
uint32_t nd = CsmaIpv4Topology::AddIpv4CsmaNetDevice (node, m_channel,
Mac48Address::Allocate ());
Ipv4Address address = Ipv4AddressGenerator::AllocateAddress (mask,
network);
CsmaIpv4Topology::AddIpv4Address (node, nd, address, mask);
m_nodes.push_back (node);
}
}
Ipv4BusNetwork::~Ipv4BusNetwork ()
{
}
Ptr<Node>
Ipv4BusNetwork::GetNode (uint32_t n)
{
return m_nodes[n];
}
}; // namespace ns3
@end verbatim
@section Using Ipv4BusNetwork
If all you ever want to do with a bus network can be captured in a topology
with four nodes on the bus, the preceeding section may seem like a colossal
waste of time. This is probably not the case, though. Now that we have a
relatively abstract bus class, we can create bus networks with 4, 40 or 4000
nodes with no additional effort.
A use of the bus network class is shown in the file
@code{bus-netowrk.cc} located in the @code{tutorial} directory. The
interesting code is,
@verbatim
Ipv4BusNetwork bus ("10.1.0.0", "255.255.0.0", "0.0.0.3",
DataRate(10000000), MilliSeconds(20), 10);
@end verbatim
Here we create a bus network with the network number ``10.1.0.0'' and the
network mask ``255.255.0.0'' that completes the IP network definition. You
can consider these together as ``10.1.0.0/16'' if you prefer. The next
parameter tells the bus to start numbering IP addresses of contained nodes at
``10.1.0.3'' (remember the network number will be combined). We provided a
data rate of 10 megabits per second and a latency of 20 milliseconds.
Finally, we ask the @code{Ipv4BusNetwork} object to create ten nodes in the
network.
If you are feeling brave, go ahead and change the number of nodes to be 100,
1000, 10,000 or more to generate larger and larger networks. Before you go
too far, remember that a trace file will be generated when you run your
resulting program and ee asked the trace facility to trace all net device
receive events. This will include the reception of the broadcast ARP request
by all of the nodes in the simulation, so this can add up quickly.
@c ========================================================================
@c Summary
@c ========================================================================
@node Summary
@chapter Summary
This concludes the first part of the tutorial. We have focused on
using the @command{ns-3} system to construct various network topologies and to
simulate sendng data across the networks; and we've shown you how to use the
trace facility to get access to simulation results.
We now encourage you to play with the system a little. Experiment with what
we have provided. Build a hierarchical network simulation. Perhaps exercise
your object design skills and create a new @code{Ipv4DumbbellNetwork} class
to create dumbbell networks using the Ipv4BusNetwork class we just created.
Hint: An Ipv4DumbbellNetwork @emph{has} two @code{Ipv4BusNetwork} objects;
a left side and a right side.
In the next part of the tutorial we are going to drop down a level and begin
examining the lower levels of the system in more detail. We are going to
explain how to change the behavior of the system and eventually how to write
new models and applications. This is a good time to make sure that you
thorougly understand what we've gone over so far.
@c ========================================================================
@c Object Model
@c ========================================================================
@node Object-Model
@chapter Object Model
There are two distinctly different meanings associated with the term Object
Model. The first speaks to the implementation of an object system --- a system
view; and the second speaks to the application programming interface (classes
or objects) one uses to access some service or system --- an application view.
As an example of the system view sense of the term, the C++ language has an
associated object model that describes how objects are laid out in memory,
how virtual functions work, how inheritance is implemented, constructor and
destructor execution ordering, template instantiation, etc.
In the case of the application view, the Document Object Model is a good
example. In the words of W3C, the Document Object Model (DOM) is an
application programming interface (API) for HTML and XML documents. It defines
the logical structure of documents and the way a document is accessed and
manipulated.
The Component Object Model (COM) from Microsoft actually spans both meanings
of the term and extends further into policy statements. From a system
perspective, COM specifies an interface definition language, the layout of
objects virtual function tables, the formats of Globally Unique Identifiers
and also specifies lifetime management mechanisms for objects via reference
counting. From the point of view of the API, COM specifies a number of
Interfaces as well as functions such as CoCreateInstance and various
threading models. The COM specification extends to policy by disallowing
implementation inheritance.
The @command{ns-3} object model takes the C++ language (system level) object
model as its basis, and extends that model by providing an API for software
componentry. You will find terms like Component, Interface and QueryInterface
in the following discussion. It is important to understand from the outset
that this is the @command{ns-3} object model, and not any other object model.
Richard Feynman (an American physicist) once described the behavior of matter
and light on a very small scale in the following way,
@quotation
``They do not behave like waves, they do not behave like particles, they do
not behave like clouds, or billiard balls, or weights on springs, or like
anything that you have ever seen.''
@end quotation
Just as students of quantum mechanics must rid themselves of preconceptions
regarding the behavior of matter at small scales, you should rid yourself of
any preconceptions you may have about components, interfaces and APIs for
software componentry before continuing. To paraphrase Feynman, @command{ns-3}
components do not behave like COM Components, or Java Beans, or CORBA
objects, or clouds or weights on springs, or like anything that you have
ever seen they are @command{ns-3} components.
@section The C++ Object Model is the Root of all Things
@command{Ns-3} is primarily a C++ system. The system is written in C++ and
one can use standard C++ mechanisms for creating and using ns-3 objects. We
do not change this at all, nor do we make any pronouncements about the
superiority of one mechanism or another. What we will do is provide
convenience functions that we think will make creating and managing simulation
objects easier.
Previously, you have seen objects created using the template function
@code{Create} as in the following example:
@verbatim
Ptr<Node> n0 = CreateObject<InternetNode> ();
@end verbatim
This line of code, while it may be unfamiliar to some, is pure C++. If you
were to look in the header file ptr.h, you would find the following definition
of the @code{Create} template.
@verbatim
template <typename T>
Ptr<T> Create (void)
{
T *obj = new T ();
Ptr<T> p = obj;
obj->Unref ();
return p;
}
@end verbatim
As you can see, this template creates objects of type @code{T} using the
operator @code{new}. Its a little harder to find the corresponding delete ---
it's in the file @code{object.cc} inside the method @code{Object::MaybeDelete},
but when that @code{Ptr} which you see above goes out of scope it will call
@code{Unref} and ultimately the C++ @code{delete} operator will be called.
The ns-3 system uses the C++ @code{new} and @code{delete} operators, so there
is really no reason that you as a user of the ns-3 system are forbidden from
using these or any other C++ mechanism. If you so desire, you can take on
the responsibility for managing object lifetime (i.e., do not use the
@code{Ptr} smart pointer), work directly with the @code{new} and @code{delete}
operators and call methods like any C++ object as in the following example:
@verbatim
MyClass *obj = new MyClass ();
obj->Method();
delete obj;
@end verbatim
You, as a competent model author, are encouraged to use whatever methods you
think are appropriate in your private code. Remember, however, that the
public ns-3 APIs do use smart pointers to pass objects around in an effort to
reduce the burden of object lifetime management. If you do intend to export
an API publicly, you should use the same object lifetime management approaches
as those found in the ns-3 public API if only for consistency.
These APIs are there for convenience and consistency, but do not change the
fact that in ns-3 all of the objects are really just C++ objects, ultimately
created using the C++ new operator with C++ constructor semantics and are
ultimately deleted using the C++ delete operator, following C++ destructor
semantics. Although it may sometimes appear so, there is really no system-
level magic going on in ns-3. Ns-3 components and interfaces are C++ objects
just like any other object and our object model is simply a collection of APIs
built on the normal C++ object model.
@section Interfaces
There are many different ideas floating around of what exactly the term
@emph{Interface} means. Originally an interface just meant a communication
boundary between two entities. As the concepts of object oriented programming
(OOP) were surfacing in the 1980s, the term interface was applied to the
collection of access methods for the modular entities that were being defined.
Two distinct approaches developed regarding specifying access mechanisms for
objects. The OOP purists were very concerned about object reuse and were led
to Abstract Data Types (ADT). These were eventually implemented in the case
of C++, as pure virtual methods in Abstract Base Classes (ABC). Another group
of folks was more interested in simply specifying object access methods in one
place and using inheritance as the primary reuse mechanism.
Bjarne Stroustroup, the creator of C++, embraced both approaches. He makes
the following interesting observation:
@quotation
``Many classes [@dots{}] are useful both as themselves and also as bases for
derived classes. [@dots{}] Some classes, such as class @strong{Shape},
represent abstract concepts for which objects cannot exist.''
@end quotation
@command{Ns-3} does not pick and enforce a particular approach. In
@command{ns-3} an interface is determined completely by a class declaration
just as any C++ object interface is declared. If you think of an object as
an abstract concept that should be implemented by derived classes, by all
means, use the Abstract Base Class approach to interface declaration. If you
think that an object should be completely concrete and you foresee no need
to ever modify its behavior, feel free to avoid declaring any methods virtual.
If you think that an object could be useful as a base class, feel free to
declare its methods virtual. If you like to use the PIMPL idiom, again, feel
free. If you want to use any combination of these techniques, feel free.
We make no restrictions.
When we speak of an ns-3 interface, we do not worry about interface definition
languages, or pure virtual classes, or registries we just think about C++
object declarations and their associated methods. When we instantiate an
@command{ns-3} Interface, it is the C++ object model that dictates how that
object is brought into existence. When a method is called on an @command{ns-3}
Interface, it is the C++ object model that dictates how that method is
dispatched.
The only difference between a vanilla C++ object and an ns-3 Interface, is
that an object acting as an ns-3 Interface must inherit from the base class
Object. This inheritance gives the Interface object a very useful capability.
@section The Ns-3 Capital I Interface and QueryInterface
One thing that Microsoft got right in the Component Object Model was the idea
of Interface aggregation and discovery via QueryInterface. We have embraced
these ideas in @command{ns-3}. This was done primarily to address a common
problem in large software systems. A good example of this problem happens
in the @command{ns-3} Node class.
If one were to take the standard OOP view of specializing a @code{Node} into
an internet host, for example, one would typically inherit from the @code{Node}
base class and include functionality to implement such things as internet
routing and a TCP / IP protocol stack. Other types of @code{Node}s might
inherit from the node class and specialize in different ways, or further
specialize the internet host class, treating it as a base class. This can
result in a complicated inheritance tree in which some specializations are
simply not available to other branches of the tree which can make reuse
difficult or impossible. This is known as the @emph{weak base class} problem
and creates pressure to drive functionality up the inheritance tree into the
base classes. This, in turn, results in @emph{base class bloat} and the
resulting @emph{swiss army knife} base classes which end up trying to do
everything in one place.
Even if one successfully avoided these swiss army knife base classes, one
would also want to be able to treat new specializations of @code{Node}
generically in the system. This means one would pass references to the base
class (@code{Node}) across public APIs. This introduces @emph{upcasts} prior
to passing across public APIs and corresponding @emph{downcasts} on the other
side in order to gain access to required specialized functions. As the
inheritance tree becomes more complicated, this approach can cause another
related problem known as the @emph{fragile base class} problem. This happens
when changes to the base class cause unexpected problems in the various and
sundry subclasses.
These effects seem always to result in a positive feedback loop driving
everything into the base class and destroying much of the encapsulation which
is a hallmark of the object oriented approach.
@subsection Interface Composition
There is a completely different way to address the Node specialization
problem. Instead of approaching the situation using inheritance, one can
look at the problem as one of composition. We can look at the @code{Node}
class as a container of sorts that holds other objects. In this case, the
objects would be instances of the classes implementing the internetwork
routing code, or the TCP / IP protocol stack described above. This approach
preserves the encapsulation and solves the weak base class, base class bloat
and fragile base class problems; but the question of method dispatch
immediately comes to mind.
In many systems, @emph{delegation} is used. The base class, @code{Node},
in this approach would provide methods that simply forward to the objects
implementing the desired functionality. This situation clearly does not
address the base class bloat problem since dispatch methods must be added
to the base class. The situation is mitigated somewhat by pushing the
implementation of the dispatch methods to contained objects, but the
fundamental problems are still present. What is really needed is a way
to compose objects but at the same time keep the interfaces to those
objects separated.
Composition, usually called @emph{aggregation}, along with runtime Interface
discovery is the solution that Microsoft originally championed and that
@command{ns-3} has adopted. In our example a @code{Node} would contain
separate Interface objects implementing internetwork routing and TCP/IP.
These contained objects have interfaces in the C++ sense of collections of
method signatures. When objects are capable of participating in this
aggregation process, they are called @command{ns-3} Interfaces and they
receive the functionality required for this participation by inheriting
from the base class @code{Object}.
@subsection Object, interfaces and Interfaces
As mentioned above, the class that implements the aggregation mechanism for
@command{ns-3} objects is called @code{Object}. The class named @code{Object}
is simply a base class that you will inherit from if you want your objects
to support aggregation and QueryInterface. Many systems have a base class
that implements common functionality and these base classes are typically
called Object. The @command{ns-3} version of this object base class relates
primarily to Interface aggregation, although it does provide methods to help
with intrusive reference counting and tracing as well.
When a C++ object inherits from the ns-3 Object base class, it is conceptually
promoted to an ns-3 Interface (note the capital I in Interface) irrespective
of how the object was declared (e.g., as an abstract base class, concrete
class, with virtual methods, etc.). In ns-3, you should associate
inheritance from the class named @code{Object} with promotion of an object to
the status of Interface rather than the form of the Interface declaration.
When you inherit from @code{Object}, you will get new methods and an
Interface Identifier. The Interface Identifer, or @emph{iid}, is the
@command{ns-3} version of the @emph{Universally Unique ID} (UUID) or
@emph{Globally Unique ID} (GUID) found in other systems. Unlike the GUID, it
is really a dynamically created process-local ID. For now, consider it as
simply a number which the system will generate for you that uniquely
identifies an Interface class within the ns-3 system and allows you to
specify an interface type to @code{QueryInterface}.
To summarize, when you instantiate an object that inherits from the
@code{Object} class, you will have a C++ object that has four important
properties:
@itemize @bullet
@item The object has a C++ interface defined by the collection of method signatures in its inheritance tree;
@item The object has an Interface ID that uniquely identifies the C++ interface of its class;
@item The object is a container that has the ability to aggregate other interfaces;
@item The object exports a method that allows for discovery of aggregated interfaces (@code{QueryInterface}) according to Interface ID.
@end itemize
It is crucially important to understand what we have described here. A given
C++ class has an object access interface that is essentially the collection
of method signatures specified in its inheritance tree. This is a C++ object
model thing. Ns-3 provides a base class from which the class in question can
inherit and be promoted to the status of Interface. Once a class becomes
an Interface it has inherited the ability to set its own interface identifier
(@code{iid}), and exports methods to aggregate and search other Interfaces
that are added to its aggregation.
That last detail is important. In @command{ns-3} Interfaces are both
containers and specifications for object method access. We have previously
mentioned the @code{Node} class acts as a container. In fact, the @code{Node}
class inherits from @code{Object} and is itself also an @command{ns-3}
Interface. When the @code{Node} object is created it is really an aggregation
of one Interface, the @code{Node} Interface. This is generally true ---
Interfaces are both containers and Interfaces.
@subsection Aggregations
The figure below shows how an Interface could be illustrated in detail. The
line with the circle at the top of the diagram represents the appearance of the
Interface to the external world. This circle and line are called a lollipop
because of its superficial similarity to a kind of childs candy.
@sp 1
@center @image{oneif,,,,png}
You could declare this interface quite simply using a non-virtual class as
follows,
@verbatim
class A : public Object {
public:
static const InterfaceId iid;
void MethodA (void);
};
@end verbatim
The methods that are then available via the Interface labeled @code{A} in the
figure above are the methods inherited from the @code{Object} base class (
@code{QueryInterface}, @code{Ref}, and @code{Unref}) and those from class
@code{A} (@code{MethodA}). Note that you must declare an @code{InterfaceId}
for your Interface class, and it must be declared static to make it class-wide
in scope. This @code{iid} can be thought of as a kind of type information
that uniquely identifies objects as being instantiated from this class.
You can think of the arc and arrow device coming off each side of the
Interface as part of a connector. These connectors allow @code{QueryInterface}
to search aggregations for a particular @code{iid}. The figure below shows an
aggregation of three Interfaces: A, B and C. The class declarations for
classes @code{B} and @code{C} are substantially similar to that of class
@code{A}.
@sp 1
@center @image{threeif,,,,png}
You can visualize these Interfaces as being snapped together like Lego
building blocks if you like. When the Interfaces are aggregated, a
@code{QueryInterface} search path is formed through the connectors. In order
to create this aggregation we first need to create the Interface objects.
These are just normal, everyday C++ objects that we can create using the
@code{Create} template function and manage using smart pointers. The
following code should be obvious to you by now:
@verbatim
Ptr<A> a = CreateObject<A> ();
Ptr<B> b = CreateObject<B> ();
Ptr<C> c = CreateObject<C> ();
@end verbatim
When you create an aggregation, you pick one of the Interfaces to act as
the container. In this case well pick Interface A. In order to aggregate
an Interface, you simply call the method @code{AddInterface} that your class
inherited from @code{Object}. The following code will aggregate Interface
@code{B} and Interface @code{C} onto the Interface (and container) @code{A}.
@verbatim
a->AddInterface (b);
a->AddInterface (c);
@end verbatim
Thats all there is to it. Now that you have those connectors snapped
together, you can ask each of the Interfaces in the aggregation for any of
the Interfaces in the aggregation. Lets look at a simple example:
@verbatim
Ptr<B> newB = a->QueryInterface<B> (B:iid);
@end verbatim
The left hand side of this assignment declares a smart pointer to the class
@code{B} to help with memory management of the returned Interface pointer.
Object lifetime management is very important when dealing with Interfaces
and our smart pointer will simply take care of it all for you.
The right hand side illustrates the basic idea of @code{QueryInterface}. We
take a take a (smart) pointer to Interface @code{A} and ask it to search the
aggregation for an interface associated with an interface identifier with
the value of @code{B:iid} which is passed as a parameter. Recall that
@code{B::iid} is the @code{static InterfaceId} of the Interface class
@code{B}. Observe that @code{QueryInterface} is a template function and the
type specified in the angle brackets, here @code{<B>}, tells it what kind of
smart pointer to return. In this case @code{QueryInterface} will find an
Interface object of type @code{B::iid} in its list of Interfaces and return a
smart pointer to @code{B} as instructed.
Now that you have those connectors snapped together, you can ask each of
the Interfaces in the aggregation for any of the Interfaces in the
aggregation. For example we could walk the Interfaces asking each for the
next in the aggregation. First we would ask the Interface pointed to by the
smart pointer a to look for the InterfaceId representing @code{B}:
@verbatim
Ptr<B> newB = a->QueryInterface<B> (B:iid);
@end verbatim
Next, we can ask the Interface pointed to by the smart pointer @code{newB}
to look for the @code{InterfaceId} representing @code{C}:
@verbatim
Ptr<C> newC = newB->QueryInterface<C> (C:iid);
@end verbatim
Then, we can ask the Interface pointed to by the smart pointer @code{newC}
to look for the InterfaceId representing A and complete our circuit of the
aggregation:
@verbatim
Ptr<A> newA = newC->QueryInterface<A> (A:iid);
@end verbatim
@code{QueryInterface} (often abbreviated QI) has some important properties
that we need to go over. Technically, QI is a @emph{symmetric},
@emph{reflexive} and @emph{transitive} operation with respect to the set of
aggregated Interfaces.
@subsubsection Symmetry
The symmetric nature of QI guarantees that if one performs a QI on a given
Interface for the Interface Id of that same interface, that
@code{QueryInterface} must succeed. The existence of interface A in the
aggregation implies the reachability of Interface A in the aggregation. This
is usually written (by Microsoft) as,
@center must succeed (A >> A)
We can illustrate this property with the code snippet,
@verbatim
Ptr<A> symmetricA = a->QueryInterface<A> (A:iid);
NS_ASSERT (symmetricA);
@end verbatim
Here we take as given an interface (smart) pointer named a on which we
perform a QI looking for the InterfaceId of that same Interface. This call
must always succeed and a smart pointer to the Interface a is returned by QI.
@subsubsection Reflexivity
Calls to QI must also be reflexive. This means that if you successfully QI
for interface B from interface A, then you must always be able to QI for A
from B. This is usually written as,
@center must succeed (A >> B, then B >> A)
This property can be illustrated with the code snippet,
@verbatim
Ptr<B> b = a->QueryInterface<B> (B:iid);
Ptr<A> reflexiveA = b->QueryInterface<A> (A:iid);
NS_ASSERT (reflexiveA);
@end verbatim
If the first @code{QueryInterface} on Interface A looking for Interface B
succeeds, then a @code{QueryInterface} on Interface B looking for Interface A
must succeed.
@subsubsection Transitivity
@code{QueryInteface} must also be transitive. This means that if one can
find Interface B from Interface A, and Interface C from Interface B, then one
must also be able to find interface C from Interface A. This is usually
written as,
@center must succeed (A >> B, and B >> C, then A >> C)
This property can be illustrated with the code snippet,
@verbatim
Ptr<B> b = a->QueryInterface<B> (B:iid);
Ptr<C> c = b->QueryInterface<C> (C:iid);
Ptr<C> transitiveC = a->QueryInterface<C> (C:iid);
NS_ASSERT (transitiveC);
@end verbatim
If you can get to Interface B from Interface A, and you can get to Interface C
from Interface B, then a QueryInterface on Interface A looking for Interface C
must also succeed.
@subsection Creating the InterfaceId
The final piece of this puzzle is to locate where the interface Ids actually
come from. The answer is from a static initializer that must be located in
the @code{.cc} file corresponding to the Interface. For example, to
initialize the Interface Id for the class A above, you would simply add the
following code to the source file that implements class A,
@verbatim
const InterfaceId A::iid =
MakeInterfaceId (``A'', Object::iid);
@end verbatim
This code is guaranteed by the C++ language definition to be executed before
your main procedure is entered. The call to MakeInterfaceId will assign a
process-local unique identifier to your class and associate your interface
with the name (string) ``A.'' This allows you to look up an InterfaceId by
a human readable string.
An advanced ns-3 specific feature of QueryInterface is exposed here.
@code{MakeInterfaceId} takes an @code{InterfaceId} as a parameter. This is
the @code{iid} of the base class from which you inherited. In most cases
this will be @code{Object::iid}, which is the @code{InterfaceId} of the
@code{Object} base class. In @command{ns-3}, the @code{Object} base class
has its own @code{iid} and you can QI for that @code{iid}. The @code{Object}
base class has a rough equivalence to the @emph{IUnknown} Interface in
Microsofts COM, so you can QI for @code{Object::iid} in @command{ns-3}f just
as you might QI for IID_IUnknown in COM.
The InterfaceId you pass to @code{MakeInterfaceId} is used to create an
inheritance tree in the ns-3 interface manager. This inheritance tree is also
walked in @code{QueryInterface} Interface searches. Consider a simple case
of a base class and a derived class as shown below,
@verbatim
class Base : public Object
{
public:
static const InterfaceId iid;
...
};
class Derived : public Base
{
public:
static const InterfaceId iid;
...
};
@end verbatim
To assign the InterfaceId for each of these classes, we could add two calls
to @code{MakeInterfaceId} reflecting the class hierarchy we just created.
@verbatim
const InterfaceId Base::iid =
MakeInterfaceId (``Base'', Object::iid);
const InterfaceId Derived::iid =
MakeInterfaceId (``Derived'', Base::iid);
@end verbatim
The first Interface is shown to inherit from class @code{Object} and the
second inherits from class @code{Base}. We could create these interfaces
as we usually do,
@verbatim
Ptr<Base> base = CreateObject<Base> ();
Ptr<Derived> derived = CreateObject<Derived> ();
@end verbatim
The derived and base @code{InterfaceIds} are either present or not present
based on the inheritance tree. For example, a QI for the @code{Base
InterfaceId} must succeed when done against a @code{Ptr<Base>}; but a QI for
the @code{Derived InterfaceId} must fail when done against a @code{Ptr<Base>}.
However, a QI for the @code{Base InterfaceId} must succeed when done against a
@code{Ptr<Derived>}; and a QI for the @code{Derived InterfaceId} must succeed
when done against a @code{Ptr<Derived>}.
This feature allows you to use implementation inheritance to easily create
new Interfaces. You are prevented from doing so in Microsoft COM, but this
was almost universally identified as a problem.
@subsection A Real Example
At this point you may be asking yourself what the point of all of this is,
since you already had those pointers laying around when you created the
objects. The typical case is that you would forget about the pointers to the
contained objects and only export a single Interface. Other Interfaces could
be discovered using QI.
Generally one tends to think of one of the Interfaces in the aggregation
as being the container and other Interfaces being aggregated to that
container. In the case of a Node, for example, it is quite natural to think
of the Node as being the container which contains Interfaces for the protocol
stacks, internet routing, etc. So, lets start developing an example by
calling the container Interface Node instead of A. The creation of this
Interface is found all over our example programs. For example, you will
find code like the following in @code{samples/simple-point-to-point.cc}:
@verbatim
Ptr<Node> n = CreateObject<InternetNode> ();
@end verbatim
This code is described in detail in previous sections, but the important thing
to realize here is that the resulting @code{Node} is an @command{ns-3}
Interface. This is not at all obvious -- you must look at the source code to
see that this is true. Take a look at @code{src/node/node.h} and find the
class declaration for class @code{Node}. There you will find,
@verbatim
class Node : public Object
{
public:
static const InterfaceId iid;
...
};
@end verbatim
Class @code{Node} inherits from class @code{Object} and provides an
@code{InterfaceId}, therefore it is an @command{ns-3} interface. You now
know you can use @code{AddInterface} for aggregation and @code{QueryInterface}
for Interface discovery against any @code{Node} in the system.
We spoke of a protocol stack that is aggregated to a @code{Node} in our
discussions above, what we see in the real @command{ns-3} code is that this
is represented by the @code{Ipv4} Interface. If you look in
@code{src/node/ipv4.h} you will find,
@verbatim
class Ipv4 : public Object
{
public:
static const InterfaceId iid;
...
};
@end verbatim
Since class @code{Ipv4} inherits from class @code{Object} and has a
@code{static InterfaceId}, it is an @command{ns-3} Interface. If you look in
@code{src/node/ipv4.cc} you will find,
@verbatim
const InterfaceId Ipv4::iid =
MakeInterfaceId (``Ipv4'', Object::iid);
@end verbatim
After all of this reading you now know that this code snippet is asking the
system to create a unique @code{InterfaceId} for the @code{Ipv4} class and
declares that @code{Ipv4} inherits from class @code{Object}.
It turns out that the Ipv4 class is an abstract base class (ABC). There are
a number of pure virtual methods declared in that class. This means that
an @code{Ipv4} object may not be instantiated. What is instantiated is an
implementation class, called @code{Ipv4Impl}. This class inherits from
@code{Ipv4} and provides the required virtual methods. This is where
understanding what is an Interface and what is not gets tricky. The
Interface is the @code{Ipv4} class since that is where the @code{InterfaceId}
is found. The fact that you see @code{ipv4::iid} tells you that the
@code{Ipv4} class is the Interface and has the associated @code{InterfaceId}.
The class @code{Ipv4Impl} provides an implementation for the pure virtual
methods in @code{Ipv4}. Since class @code{Ipv4} cannot be instantiated, one
instantiates the @code{Ipv4Impl} class to create an @code{Ipv4} Interface.
Once the @code{Ipv4Impl} class is instantiated, the pointer to it is
immediately cast to an @code{Ipv4} pointer. Clients will then use the
@code{Ipv4} object access methods (see @code{ipv4.h}) to talk to the
@code{Ipv4Impl} object over the @code{Ipv4} Interface. I urge you to not go
any further until you thoroughly understand what youve just read.
If you now look in the file, @code{src/internet-node/internet-node.cc} you
will see the following code in @code{InternetNode::Construct} that creates the
@code{Ipv4} Interface and aggregates it to the @code{Node} interface (recall
that class @code{Node} is an Interface and class @code{InternetNode} inherits
from class @code{Node}):
@verbatim
Ptr<Ipv4Impl> ipv4Impl = CreateObject<Ipv4Impl> (ipv4);
...
Object::AddInterface (ipv4Impl);
@end verbatim
Note that the parameter @code{ipv4} passed to the @code{Create} template
function is actually a pointer to an @code{Ipv4L3Protocol} which you can
ignore at this point --- it doesn't really have anything to do with the
@code{Ipv4} Interface.
This last example does illustrate that the fact that whether an @command{ns-3}
object is or is not an Interface can be quite well hidden. The designers of
the system had long and involved discussions on this issue and in the end
decided that mnemonic aids such as Hungarian notation were a stylistic thing
and you should just refer to the system documentation to determine what
objects are ns-3 Interfaces and what those Interfaces actually are (RTFM ---
Read the Fine Manual).
In this case, you know that the class @code{Ipv4Impl} inherits from some
Interface since there is a call to @code{AddInterface} that refers to it.
You can go to the header file @code{src/internet-node/ipv4-impl.h} and find
that @code{Ipv4Impl} inherits from class @code{Ipv4}. You then go to file
@code{src/node/ipv4.h} and see that it inherits from @code{Object} and
contains an @code{InterfaceId}. Thus the Interface added is really the
@code{Ipv4} Interface with the interface Id @code{Ipv4::iid}.
Returning to some @command{ns-3} example code, lets take a look at
@code{src/examples/simple-point-to-point.cc} again. You will find the
following code:
@verbatim
Ptr<Node> n0 = CreateObject<InternetNode> ();
...
Ptr<Ipv4> ipv4;
ipv4 = n0->QueryInterface<Ipv4> (Ipv4::iid);
ipv4->SetDefaultRoute (Ipv4Address (``10.1.1.2''), 1);
@end verbatim
The first line creates an @code{InternetNode} object and casts the resulting
smart pointer to a @code{Node}. The next line declares a smart pointer to an
@code{Ipv4} object. Because youve been through the code with us, you know
that both the @code{Node} and the @code{Ipv4} objects are Interfaces. They
should be able to participate in a @code{QueryInterface}.
The next line confirms it. We do a @code{QueryInterface} on the @code{Node},
looking for the @code{Ipv4} Interface (@code{Ipv4::iid}).
@code{QueryInterface} then returns a smart pointer to its aggregated
@code{Ipv4} Interface. [Recall that this Interface was aggregated in
@code{InternetNode::Construct}. We knew to start looking for the aggregation
in @code{InternetNode} since we originally created an @code{InternetNode} in
the @code{Create} template function and then implicitly cast it to a
@code{Node}.]
Once you have the @code{Ipv4} smart pointer, you simply use it as if it were
any other C++ object. The last line shows this by setting the default route
for the node.
@section Caveats
There are a few things that you should remember but which may not be
immediately obvious.
@subsection Interface Ids are Associated with Classes not Objects
Interfaces are identified by an @code{InterfaceId} that is associated with
the Interface class, not the Interface object. That is indicated by the
@code{static} keyword in the declaration of the @code{iid} in the class. The
interface Id for a given Interface class exists independently of any objects
of that class that you may instantiate; and all objects of a given Interface
type share the same @code{InterfaceId}.
You cannot add more than one Interface of a given type (@code{iid}) to an
aggregation. If you need to contain a number of Interfaces of the same type
in the same aggregation, you will need to provide a separate container over
which you can iterate. For example, the @code{Node} class provides methods,
@verbatim
uint32_t GetNDevices (void) const;
Ptr<NetDevice> GetDevice (uint32_t index) const;
@end verbatim
that are used iterate over the multiple @code{NetDevice} Interfaces associated
with it.
@emph{Interface Ids do not identify objects.}
@subsection Dont use QI to Check Your Own Type.
It is tempting to use @code{QueryInterface} as a form of runtime type
information. Dont do it. You have no control over what other object may be
added to your aggregation and this may cause problems. Someone else may have
appropriated (reimplemented) your type and aggregated themselves onto your
aggregation.
Consider a socket factory implementation. Sockets can be either UDP sockets
or TCP sockets. A socket factory will have a generic @code{SocketFactory}
Interface and either a UDP specific interface for setting UDP parameters or a
similar TCP-specific interface.
Consider what might happen if you declared your socket factory as a partially
abstract base class, and then provided separate implementations for UDP and
TCP specific methods of this factory in separate concrete classes. Now
consider what might happen if you used QueryInterface in your base class
to determine if you were a UDP or a TCP factory.
If a factory, say the UDP version, were not aggregated to any other Interface,
the base class could QueryInterface on itself for the UDP-specific interface.
It could then infer that it was a UDP implementation and would then do any
UDP-specific tasks it could. [Experienced C++ folks are cringing about how
horrible this design is, but bear with me --- its a simple illustration of
a specific and perhaps not-too-obvious problem.]
If another factory, say the TCP version, were not aggregated to any other
Interface, the base class could QueryInterface on itself for the UDP-specific
interface. If this failed, it could then infer that it had a TCP
implementation and would then do any TCP-specific tasks it could.
Now, what happens when these two working objects are aggregated together.
Since the Interfaces are conceptually snapped together the TCP implementation
would suddenly begin finding the UDP Interface from the other class factory
and fail.
@emph{Interface Ids should not be used as run-time type information.}
@section Connecting the Dots
This may all sound very complicated to you if this is your first exposure to
these concepts. It may be annoying if I tell you that its really not as hard
as it sounds. Rest assured that if you take some time, look at and understand
the examples and write a little test code it will all come together for you.
Grep around the system for AddInterface and QueryInterface and take a look at
how we have used them. This will also give you a good idea of what our core
Interfaces are. If you grep for @code{::iid} you will find most, if not all
of the interface declarations in the system. The more you see this idiom in
use, the more comfortable you will be with the idea and the more you will see
how this addresses the weak base class, swiss army knife base class, and
fragile base class problems I explained at the beginning.
As I alluded to earlier, the developers had long discussions regarding how to
make navigating the QueryInterface environment easier. The primary issue was
how we could make it easier to convey to you, the model writer, that an object
was an Interface. One suggestion was to adopt the convention that classes
that implement Interfaces begin with the letter I. Microsoft does this, as
exemplified by the class IUnknown. We also toyed with the idea of beginning
our header files with i- as in i-ipv4.h. We considered forcing some structure
on Interfaces with a pure virtual class specification, the names of which
begin with an I; and corresponding implementations, the names of which begin
with a C.
In the end we decided that we were really discussing issues of programming
style, and we really could not come up with a strong reason to impose any
particular solution. In the end, we decided that we would not impose any
structure on the source code, nor impose any naming convention. We will
rely on our documentation system (Doxygen) to break out all objects with
InterfaceIds in their class hierarchy into a separate section. For now,
until this is implemented, grep is your friend.
@c ========================================================================
@c Doxygen
@c ========================================================================
@node The-Doxygen-Documentation-System
@chapter The Doxygen Documentation System
@node How-To-Change-Things
@chapter How to Change Things
@node How-To-Set-Default-Values
@chapter How to Set Default Values
@node How-To-Write-A-New-Application
@chapter How to Write a New Application