/*
 * Copyright (c) 2015 - Chip Webb
 *
 * 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
 *
 * Author: Chip Webb  <ns3 (at) chipwebb.com>
 *
 */

// ###################################################################### //
//                         Network topology                               //
// ---------------------------------------------------------------------- //
//                                                                        //
// This example shows two L2 LANs connected by a WAN link and illustrates //
// a network that has multiple L2 switches between L3 routers.            //
//                                                                        //
// It serves as a test case to verify a patch to global-router-interface  //
// that fixes a previous bug (#2102 in the ns-3 tracker) but is also      //
// another example program.                                               //
//                                                                        //
// The LANs are "top" [192.168.1/24] and "bottom" [192.168.2/24].         //
// Each LAN network is interconnected by several L2 switches, and each    //
// LAN has its own router to act as a gateway with the WAN. Each LAN      //
// has two endpoints upon which is installed a UDP echo client or server  //
// that are used to test connectivity over the LANs & WAN.                //
//                                                                        //
// One pair of UDP endpoints (t3 and b3) have LAN connections with only   //
// one switch between them and their local routers. This path works with  //
// unpatched ns3 code (3.24 & earlier) as well as with the patch applied. //
//                                                                        //
// Another pair of endpoints (t2 and b2) have LAN connections with        //
// a chain of multiple switches between them and their local router.      //
// This path will only work after applying the associated patch.          //
//                                                                        //
// The LAN links are modeled by half-duplex Ethernet CSMA links which     //
// have command-line-configurable data rate and latency.                  //
//                                                                        //
// There are two types of CSMA links: 100Mbit and 10Mbit. The 100Mbit     //
// links are called csmaX, are denoted by [X] in the diagram and can      //
// be controlled with the --csmaXRate and --csmaXDelay command line args. //
// The 10Mbit links are called csmaY, are denoted by [Y] in the diagram   //
// and can be controlled with the --csmaYRate and --csmaYDelay command    //
// line arguments. Both the top and bottom LAN have a mixture of          //
// 100Mbit/s and 10Mbit/s links.                                          //
//                                                                        //
// The WAN is modeled by a point-to-point link which has configurable     //
// data rate and latency. Unlike many typical home/work networks,         //
// the routers do not perform NAT.                                        //
//                                                                        //
// The WAN link is denoted by [P] in the diagram, and the                 //
// speed and latency can be set from the command line with the            //
// --p2pRate and --p2pDelay options. The default for this link is 5Mbit/s //
// and 50ms delay                                                         //
//                                                                        //
// Note: Names in parenthesis after NetDevices are pcap tap locations.    //
//                                                                        //
// ---------------------------------------------------------------------- //
//                                                                        //
//   192.168.  192.168.                                                   //
//    .1.2      .1.3                                                      //
//  ---------  ---------                                                  //
//  |  t2   |  |  t3   |                                                  //
//  |  UDP  |  |  UDP  |                                                  //
//  |  echo |  |  echo |    Node t2 is a UDP echo client (multi-switch)   //
//  | client|  | server|    Node t3 is a UDP echo server (single-switch)  //
//  ---------  ---------                                                  //
//   CSMA(t2)   CSMA(t3)                                                  //
//     [X]        [X]                                                     //
//     [X]        [X]                                                     //
//    CSMA        [X]                                                     //
//  ---------     [X]                                                     //
//  |  ts4  |     [X]        Nodes ts1, ts2, ts3 and ts4 are L2 switches  //
//  |  (sw) |     [X]        The top LAN is subnet 192.168.1.*            //
//  ---------     [X]                                                     //
//    CSMA        [X]        The long chain of switches is designed       //
//     [Y]        [X]        to test whether global-router-interface      //
//     [Y]        [X]        can fully enumerate an IP subnet that has    //
//    CSMA        [X]        multiple interconnected L2 switches.         //
//  ---------     [X]        The problem is documented in Bug #2102.      //
//  |  ts3  |     [X]                                                     //
//  |  (sw) |     [X]                                                     //
//  ---------     [X]                                                     //
//    CSMA        [X]                                                     //
//     [X]        [X]                                                     //
//     [X]        [X]                                                     //
//    CSMA        [X]                                                     //
//  ---------     [X]                                                     //
//  |  ts2  |     [X]                                                     //
//  |  (sw) |     [X]                                                     //
//  ---------     [X]                                                     //
//    CSMA        [X]                                                     //
//     [Y]        [X]                                                     //
//     [Y]        [X]                                                     //
//    CSMA       CSMA                                                     //
//   ------------------                                                   //
//   |  ts1 (switch)  |                                                   //
//   ------------------                                                   //
//          CSMA                                                          //
//           [Y]                                                          //
//           [Y]                                                          //
//          CSMA(trlan)    192.168.1.1                                    //
//   ------------------                                                   //
//   |  tr (router)   |    Node tr is an L3 router                        //
//   ------------------      (between 192.168.1.* & 76.1.1.*)             //
//           P2P(trwan)    76.1.1.1                                       //
//           [P]                                                          //
//           [P]                                                          //
//           [P]                                                          //
//           [P]                                                          //
//           [P]           The WAN is 76.1.1.*                            //
//           [P]                                                          //
//           [P]                                                          //
//           [P]                                                          //
//           P2P(brwan)    76.1.1.2                                       //
//   ------------------                                                   //
//   |  br (router)   |    Node br is an L3 router                        //
//   ------------------      (between 192.168.2.* & 76.1.1.*)             //
//          CSMA(brlan)    192.168.2.1                                    //
//           [X]                                                          //
//           [X]                                                          //
//          CSMA                                                          //
//   ------------------     Nodes bs1 to bs5 are L2 switches              //
//   |  bs1 (switch)  |     The bottom LAN is subnet 192.168.2.*          //
//   ------------------                                                   //
//    CSMA       CSMA                                                     //
//     [Y]        [Y]                                                     //
//     [Y]        [Y]                                                     //
//    CSMA        [Y]                                                     //
//  ---------     [Y]                                                     //
//  | bs2   |     [Y]                                                     //
//  | (sw)  |     [Y]                                                     //
//  ---------     [Y]                                                     //
//    CSMA        [Y]                                                     //
//     [X]        [Y]                                                     //
//     [X]        [Y]                                                     //
//    CSMA        [Y]                                                     //
//  ---------     [Y]                                                     //
//  | bs3   |     [Y]                                                     //
//  | (sw)  |     [Y]                                                     //
//  ---------     [Y]                                                     //
//    CSMA        [Y]                                                     //
//     [Y]        [Y]                                                     //
//     [Y]        [Y]                                                     //
//    CSMA        [Y]                                                     //
//  ---------     [Y]                                                     //
//  | bs4   |     [Y]                                                     //
//  | (sw)  |     [Y]                                                     //
//  ---------     [Y]                                                     //
//    CSMA        [Y]                                                     //
//     [X]        [Y]                                                     //
//     [X]        [Y]                                                     //
//    CSMA        [Y]                                                     //
//  ---------     [Y]                                                     //
//  | bs5   |     [Y]                                                     //
//  | (sw)  |     [Y]                                                     //
//  ---------     [Y]                                                     //
//    CSMA        [Y]                                                     //
//     [Y]        [Y]                                                     //
//     [Y]        [Y]                                                     //
//    CSMA(b2)   CSMA(b3)                                                 //
//  ---------  ---------                                                  //
//  |  b2   |  |  b3   |                                                  //
//  |  UDP  |  |  UDP  |                                                  //
//  |  echo |  |  echo |    Node b2 is a UDP echo server (multi-switch)   //
//  | server|  | client|    Node b3 is a UDP echo client (single-switch)  //
//  ---------  ---------                                                  //
//   192.168.  192.168.                                                   //
//    .2.2      .2.3                                                      //
//                                                                        //
// ---------------------------------------------------------------------- //
//                            Explanation                                 //
// ---------------------------------------------------------------------- //
//                                                                        //
// UDP packet flows are configured between nodes on the top and bottom    //
// LANs (using UDP echo client & server).                                 //
//                                                                        //
// The network carrying the "multi switch" UDP flow is connected with     //
// multiple L2 switches between L3 nodes so it should only work if the    //
// global-router-interface source code properly supports bridging.        //
//                                                                        //
// The network carrying the "single switch" UDP flow is connected with    //
// only one L2 switch between L3 nodes so it should work with or          //
// without the patch                                                      //
//                                                                        //
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =  //
// Traffic summary:                                                       //
// ---------------------------------------------------------------------- //
//                                                                        //
// - UDP flow  from t2 (192.168.1.2) to b2 (192.168.2.2) [Multi Switch]   //
//             from b3 (192.168.2.3) to t3 (192.168.1.3) [Single Switch]  //
//                                                                        //
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =  //
// Node List & IP addresses assigned during simulation                    //
// ---------------------------------------------------------------------- //
//      t2     : 192.168.1.2 : Top multi-switch UDP echo client           //
//      t3     : 192.168.1.3 : Top single-switch UDP echo server          //
//             :                                                          //
//      ts1    : <no IP>     : Top switch 1 (bridge)                      //
//      ts2    : <no IP>     : Top switch 2 (bridge)                      //
//      ts3    : <no IP>     : Top switch 3 (bridge)                      //
//      ts4    : <no IP>     : Top switch 4 (bridge)                      //
//             :                                                          //
//      tr     : 192.168.1.1 : Router connecting top LAN (192.168.1.*)    //
//             : 76.1.1.1    :                to the WAN                  //
//             :                                                          //
//      br     : 76.1.1.2    : Router connecting the WAN                  //
//             : 192.168.2.1 :                to bot LAN (192.168.2.*)    //
//             :                                                          //
//      bs1    : <no IP>     : Bottom switch 1 (bridge)                   //
//      bs2    : <no IP>     : Bottom switch 2 (bridge)                   //
//      bs3    : <no IP>     : Bottom switch 3 (bridge)                   //
//      bs4    : <no IP>     : Bottom switch 4 (bridge)                   //
//      bs5    : <no IP>     : Bottom switch 5 (bridge)                   //
//             :                                                          //
//      b2     : 192.168.2.2 : Bottom multi-switch UDP echo server        //
//      b3     : 192.168.2.3 : Bottom single-switch UDP echo client       //
//             :                                                          //
// ---------------------------------------------------------------------- //
// Author:     Chip Webb <ns3 (a) chipwebb dot com>                       //
// ###################################################################### //

#include "ns3/applications-module.h"
#include "ns3/bridge-module.h"
#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"

#include <fstream>
#include <iostream>

using namespace ns3;

// ########################################################################
// Main routine
// ########################################################################
NS_LOG_COMPONENT_DEFINE("GlobalRoutingMultiSwitchPlusRouter");

#define vssearch(loc, vec) std::find((vec).begin(), (vec).end(), (loc)) != (vec).end()

int
main(int argc, char* argv[])
{
    // ----------------------------------------------------------------------
    // Default values for command line arguments
    // ----------------------------------------------------------------------
    bool verbose = true;

    int simDurationSeconds = 60;

    bool enableUdpMultiSW = true;
    bool enableUdpSingleSW = true;

    std::string pcapLocations = "";
    uint32_t snapLen = PcapFile::SNAPLEN_DEFAULT;

    std::string csmaXLinkDataRate = "100Mbps";
    std::string csmaXLinkDelay = "500ns";

    std::string csmaYLinkDataRate = "10Mbps";
    std::string csmaYLinkDelay = "500ns";

    std::string p2pLinkDataRate = "5Mbps";
    std::string p2pLinkDelay = "50ms";

    uint16_t udpEchoPort = 9; // The well-known UDP echo port

    // ----------------------------------------------------------------------
    // Create command line options and get them
    // ----------------------------------------------------------------------
    CommandLine cmd(__FILE__);

    cmd.Usage("NOTE: valid --pcap arguments are: 't2,t3,b2,b3,trlan,trwan,brlan,brwan'");

    cmd.AddValue("verbose", "Enable printing informational messages", verbose);

    cmd.AddValue("duration", "Duration of simulation.", simDurationSeconds);

    cmd.AddValue("udpMultiSW", "Enable udp over multi-switch links", enableUdpMultiSW);
    cmd.AddValue("udpSingleSW", "Enable udp over single-switch links", enableUdpSingleSW);

    cmd.AddValue("pcap", "Comma separated list of PCAP Locations to tap", pcapLocations);
    cmd.AddValue("snapLen", "PCAP packet capture length", snapLen);

    cmd.AddValue("csmaXRate", "CSMA X Link data rate", csmaXLinkDataRate);
    cmd.AddValue("csmaXDelay", "CSMA X Link delay", csmaXLinkDelay);

    cmd.AddValue("csmaYRate", "CSMA Y Link data rate", csmaYLinkDataRate);
    cmd.AddValue("csmaYDelay", "CSMA Y Link delay", csmaYLinkDelay);

    cmd.AddValue("p2pRate", "P2P Link data rate", p2pLinkDataRate);
    cmd.AddValue("p2pDelay", "P2P Link delay", p2pLinkDelay);

    cmd.Parse(argc, argv);

    // --------------------------------------------------------------------
    // Users may find it convenient to turn on explicit debugging
    // for selected modules; the below lines suggest how to do this
    // --------------------------------------------------------------------
    if (verbose)
    {
        LogComponentEnable("GlobalRoutingMultiSwitchPlusRouter", LOG_LEVEL_INFO);
    }

    // ======================================================================
    // Define the list of valid PCAP taps
    // ----------------------------------------------------------------------
    const std::vector<std::string> pcapTaps{
        "t2",    // multi-switch  UDP echo client
        "t3",    // single-switch UDP echo server
        "b2",    // multi-switch  UDP echo server
        "b3",    // single-switch UDP echo client
        "trlan", // top router    LAN side
        "trwan", // top router    WAN side
        "brlan", // bottom router LAN side
        "brwan", // bottom router WAN side
    };

    // ----------------------------------------------------------------------
    // Parse the pcapLocations string into pcapLocationVec
    // ----------------------------------------------------------------------
    std::vector<std::string> pcapLocationVec;
    if (!pcapLocations.empty())
    {
        std::stringstream sStream(pcapLocations);

        while (sStream.good())
        {
            std::string substr;
            getline(sStream, substr, ',');
            if (vssearch(substr, pcapTaps))
            {
                pcapLocationVec.push_back(substr);
            }
            else
            {
                NS_LOG_ERROR("WARNING: Unrecognized PCAP location: <" + substr + ">");
            }
        }

        for (auto ploc = pcapLocationVec.begin(); ploc != pcapLocationVec.end(); ++ploc)
        {
            NS_LOG_INFO("PCAP capture at: <" + *ploc + ">");
        }
    }

    // ======================================================================
    // Set some simulator-wide values
    // ======================================================================

    // ----------------------------------------------------------------------
    // Set PCAP packet capture maximum packet length
    // ----------------------------------------------------------------------
    if (snapLen != PcapFile::SNAPLEN_DEFAULT)
    {
        Config::SetDefault("ns3::PcapFileWrapper::CaptureSize", UintegerValue(snapLen));
    }

    // ======================================================================
    // Create the nodes & links required for the topology shown in comments above.
    // ----------------------------------------------------------------------
    NS_LOG_INFO("INFO: Create nodes.");   // - - - - - - - - - - - - - - - -
                                          // Node IP     : Description
                                          // - - - - - - - - - - - - - - - -
    Ptr<Node> t2 = CreateObject<Node>();  // 192.168.1.2 : Top multi-switch udp echo client
    Ptr<Node> t3 = CreateObject<Node>();  // 192.168.1.3 : Top single-switch   udp echo server
                                          //             :
    Ptr<Node> ts1 = CreateObject<Node>(); // <no IP>     : Top switch #1 (bridge)
    Ptr<Node> ts2 = CreateObject<Node>(); // <no IP>     : Top switch #2 (bridge)
    Ptr<Node> ts3 = CreateObject<Node>(); // <no IP>     : Top switch #3 (bridge)
    Ptr<Node> ts4 = CreateObject<Node>(); // <no IP>     : Top switch #4 (bridge)
                                          //             :
    Ptr<Node> tr = CreateObject<Node>();  // 192.168.1.1 : Router connecting top LAN & WAN
                                          // 76.1.1.1    :
                                          //             :
    Ptr<Node> br = CreateObject<Node>();  // 76.1.1.2    : Router connecting WAN & bottom LANs
                                          // 192.168.2.1 :
                                          //             :
    Ptr<Node> bs1 = CreateObject<Node>(); // <no IP>     : Bottom switch #1 (bridge)
    Ptr<Node> bs2 = CreateObject<Node>(); // <no IP>     : Bottom switch #2 (bridge)
    Ptr<Node> bs3 = CreateObject<Node>(); // <no IP>     : Bottom switch #3 (bridge)
    Ptr<Node> bs4 = CreateObject<Node>(); // <no IP>     : Bottom switch #4 (bridge)
    Ptr<Node> bs5 = CreateObject<Node>(); // <no IP>     : Bottom switch #5 (bridge)
                                          //             :
    Ptr<Node> b2 = CreateObject<Node>();  // 192.168.2.2 : Bottom multi-switch udp echo server

    Ptr<Node> b3 = CreateObject<Node>(); // 192.168.2.3 : Bottom single-switch   udp echo client
                                         // - - - - - - - - - - - - - - - -

    // ----------------------------------------------------------------------
    // Give the nodes names
    // ----------------------------------------------------------------------
    Names::Add("t2", t2);
    Names::Add("t3", t3);
    Names::Add("ts1", ts1);
    Names::Add("ts2", ts2);
    Names::Add("ts3", ts3);
    Names::Add("ts4", ts4);
    Names::Add("tr", tr);
    Names::Add("br", br);
    Names::Add("bs1", bs1);
    Names::Add("bs2", bs2);
    Names::Add("bs3", bs3);
    Names::Add("bs4", bs4);
    Names::Add("bs5", bs5);
    Names::Add("b2", b2);
    Names::Add("b3", b3);

    // ======================================================================
    // Create CSMA links to use for connecting LAN nodes together
    // ----------------------------------------------------------------------

    // ----------------------------------------
    // CSMA [X]
    // ----------------------------------------
    NS_LOG_INFO("L2: Create a " << csmaXLinkDataRate << " " << csmaXLinkDelay
                                << " CSMA link for csmaX for LANs.");
    CsmaHelper csmaX;
    csmaX.SetChannelAttribute("DataRate", StringValue(csmaXLinkDataRate));
    csmaX.SetChannelAttribute("Delay", StringValue(csmaXLinkDelay));

    // ----------------------------------------
    // CSMA [Y]
    // ----------------------------------------
    NS_LOG_INFO("L2: Create a " << csmaYLinkDataRate << " " << csmaYLinkDelay
                                << " CSMA link for csmaY for LANs.");
    CsmaHelper csmaY;
    csmaY.SetChannelAttribute("DataRate", StringValue(csmaYLinkDataRate));
    csmaY.SetChannelAttribute("Delay", StringValue(csmaYLinkDelay));

    // ----------------------------------------------------------------------
    // Now, connect the top LAN nodes together with csma links.
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L2: Connect nodes on top LAN together with half-duplex CSMA links.");

    // Multi-switch top LAN chain: t2-ts4-ts3-ts2-ts1-tr
    NetDeviceContainer link_t2_ts4 = csmaX.Install(NodeContainer(t2, ts4));
    NetDeviceContainer link_ts4_ts3 = csmaY.Install(NodeContainer(ts4, ts3));
    NetDeviceContainer link_ts3_ts2 = csmaX.Install(NodeContainer(ts3, ts2));
    NetDeviceContainer link_ts2_ts1 = csmaY.Install(NodeContainer(ts2, ts1));

    // Single-switch top LAN link: t3-ts1-tr
    NetDeviceContainer link_t3_ts1 = csmaX.Install(NodeContainer(t3, ts1));

    // Common link for top LAN between ts1 and tr (for t2 and t3 to get to tr)
    NetDeviceContainer link_tr_ts1 = csmaY.Install(NodeContainer(tr, ts1));

    // ----------------------------------------------------------------------
    // And repeat above steps to connect the bottom LAN nodes together
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L2: Connect nodes on bottom LAN together with half-duplex CSMA links.");

    // Multi-switch bottom LAN chain: b2-bs5-bs4-bs3-bs2-bs1-br
    NetDeviceContainer link_b2_bs5 = csmaY.Install(NodeContainer(b2, bs5));
    NetDeviceContainer link_bs5_bs4 = csmaX.Install(NodeContainer(bs5, bs4));
    NetDeviceContainer link_bs4_bs3 = csmaY.Install(NodeContainer(bs4, bs3));
    NetDeviceContainer link_bs3_bs2 = csmaX.Install(NodeContainer(bs3, bs2));
    NetDeviceContainer link_bs2_bs1 = csmaY.Install(NodeContainer(bs2, bs1));

    // Single-switch bottom LAN link: b3-bs1-br
    NetDeviceContainer link_b3_bs1 = csmaY.Install(NodeContainer(b3, bs1));

    // Common link for bottom LAN between bs1 and br (for b2 and b3 to get to br)
    NetDeviceContainer link_br_bs1 = csmaX.Install(NodeContainer(br, bs1));

    // ======================================================================
    // Create a point-to-point link for connecting WAN nodes together
    // (this type of link is full-duplex)
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L2: Create a " << p2pLinkDataRate << " " << p2pLinkDelay
                                << " Point-to-Point link for the WAN.");

    PointToPointHelper p2p;
    p2p.SetDeviceAttribute("DataRate", StringValue(p2pLinkDataRate));
    p2p.SetChannelAttribute("Delay", StringValue(p2pLinkDelay));

    // ----------------------------------------------------------------------
    // Now, connect top router to bottom router with a p2p WAN link
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L2: Connect the routers together with the Point-to-Point WAN link.");

    NetDeviceContainer link_tr_br;
    link_tr_br = p2p.Install(NodeContainer(tr, br));

    // ======================================================================
    // Manually create the list of NetDevices for each switch
    // ----------------------------------------------------------------------

    // Top Switch 4 NetDevices
    NetDeviceContainer ts4nd;
    ts4nd.Add(link_t2_ts4.Get(1));
    ts4nd.Add(link_ts4_ts3.Get(0));

    // Top Switch 3 NetDevices
    NetDeviceContainer ts3nd;
    ts3nd.Add(link_ts4_ts3.Get(1));
    ts3nd.Add(link_ts3_ts2.Get(0));

    // Top Switch 2 NetDevices
    NetDeviceContainer ts2nd;
    ts2nd.Add(link_ts3_ts2.Get(1));
    ts2nd.Add(link_ts2_ts1.Get(0));

    // Top Switch 1 NetDevices
    NetDeviceContainer ts1nd;
    ts1nd.Add(link_ts2_ts1.Get(1));
    ts1nd.Add(link_t3_ts1.Get(1));
    ts1nd.Add(link_tr_ts1.Get(1));

    // Bottom Switch 1 NetDevices
    NetDeviceContainer bs1nd;
    bs1nd.Add(link_br_bs1.Get(1));
    bs1nd.Add(link_bs2_bs1.Get(1));
    bs1nd.Add(link_b3_bs1.Get(1));

    // Bottom Switch 2 NetDevices
    NetDeviceContainer bs2nd;
    bs2nd.Add(link_bs2_bs1.Get(0));
    bs2nd.Add(link_bs3_bs2.Get(1));

    // Bottom Switch 3 NetDevices
    NetDeviceContainer bs3nd;
    bs3nd.Add(link_bs3_bs2.Get(0));
    bs3nd.Add(link_bs4_bs3.Get(1));

    // Bottom Switch 4 NetDevices
    NetDeviceContainer bs4nd;
    bs4nd.Add(link_bs4_bs3.Get(0));
    bs4nd.Add(link_bs5_bs4.Get(1));

    // Bottom Switch 5 NetDevices
    NetDeviceContainer bs5nd;
    bs5nd.Add(link_bs5_bs4.Get(0));
    bs5nd.Add(link_b2_bs5.Get(1));

    // ======================================================================
    // Install bridging code on each switch
    // ----------------------------------------------------------------------
    BridgeHelper bridge;

    bridge.Install(ts1, ts1nd);
    bridge.Install(ts2, ts2nd);
    bridge.Install(ts3, ts3nd);
    bridge.Install(ts4, ts4nd);

    bridge.Install(bs1, bs1nd);
    bridge.Install(bs2, bs2nd);
    bridge.Install(bs3, bs3nd);
    bridge.Install(bs4, bs4nd);
    bridge.Install(bs5, bs5nd);

    // ======================================================================
    // Install the L3 internet stack  (TCP/IP)
    // ----------------------------------------------------------------------
    InternetStackHelper ns3IpStack;

    // ----------------------------------------------------------------------
    // Install the L3 internet stack on UDP endpoints
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L3: Install the ns3 IP stack on udp client and server nodes.");
    NodeContainer endpointNodes(t2, t3, b2, b3);
    ns3IpStack.Install(endpointNodes);

    // ----------------------------------------------------------------------
    // Install the L3 internet stack on routers.
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L3: Install the ns3 IP stack on routers.");
    NodeContainer routerNodes(tr, br);
    ns3IpStack.Install(routerNodes);

    // ======================================================================
    // Assign top LAN IP addresses
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L3: Assign top LAN IP Addresses.");

    NetDeviceContainer topLanIpDevices;      // - - - - - -- - - - - - -
    topLanIpDevices.Add(link_tr_ts1.Get(0)); // NOTE: order matters here
    topLanIpDevices.Add(link_t2_ts4.Get(0)); //       for IP address
    topLanIpDevices.Add(link_t3_ts1.Get(0)); //       assignment
                                             // - - - - - -- - - - - - -
    Ipv4AddressHelper ipv4;
    ipv4.SetBase("192.168.1.0", "255.255.255.0");
    ipv4.Assign(topLanIpDevices);

    // ----------------------------------------------------------------------
    // Assign bottom LAN IP addresses
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L3: Assign bottom LAN IP Addresses.");

    NetDeviceContainer botLanIpDevices;      // - - - - - -- - - - - - -
    botLanIpDevices.Add(link_br_bs1.Get(0)); // NOTE: order matters here
    botLanIpDevices.Add(link_b2_bs5.Get(0)); //       for IP address
    botLanIpDevices.Add(link_b3_bs1.Get(0)); //       assignment
                                             // - - - - - -- - - - - - -

    ipv4.SetBase("192.168.2.0", "255.255.255.0");
    ipv4.Assign(botLanIpDevices);

    // ----------------------------------------------------------------------
    // Assign WAN IP addresses
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L3: Assign WAN IP Addresses.");

    ipv4.SetBase("76.1.1.0", "255.255.255.0");
    ipv4.Assign(link_tr_br);

    // ======================================================================
    // Calculate and populate routing tables
    // ----------------------------------------------------------------------
    NS_LOG_INFO("L3: Populate routing tables.");
    Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    // ======================================================================
    // Multi-Switch UDP traffic generation
    // ----------------------------------------------------------------------
    ApplicationContainer apps;

    if (enableUdpMultiSW)
    {
        // ------------------------------------------------------------------
        // Install multi-switch UDP echo server on b2
        // ------------------------------------------------------------------
        NS_LOG_INFO("APP: Multi-Switch UDP server (on node b2 of bottom LAN)");

        UdpEchoServerHelper server(udpEchoPort);

        ApplicationContainer serverApp = server.Install(b2);
        serverApp.Start(Seconds(0.5));
        serverApp.Stop(Seconds(simDurationSeconds));

        // ------------------------------------------------------------------
        // Install multi-switch UDP echo client on t2
        // ------------------------------------------------------------------
        NS_LOG_INFO("APP: Multi-Switch UDP client (on node t2 of top LAN)");

        Time interPacketInterval = Seconds(0.005);
        uint32_t packetSize = 1000;
        uint32_t maxPacketCount = (simDurationSeconds - 2.0) / 0.005;

        UdpEchoClientHelper client(Ipv4Address("192.168.2.2"), udpEchoPort);

        client.SetAttribute("MaxPackets", UintegerValue(maxPacketCount));
        client.SetAttribute("Interval", TimeValue(interPacketInterval));
        client.SetAttribute("PacketSize", UintegerValue(packetSize));

        ApplicationContainer clientApp = client.Install(t2);
        clientApp.Start(Seconds(0.5));
        clientApp.Stop(Seconds(simDurationSeconds));
    }

    // ======================================================================
    // Single-Switch UDP traffic generation
    // ----------------------------------------------------------------------
    if (enableUdpSingleSW)
    {
        // ------------------------------------------------------------------
        // Install single-switch UDP echo server on t3
        // ------------------------------------------------------------------
        NS_LOG_INFO("APP: Single-Switch UDP server (on node t3 of top LAN)");

        UdpEchoServerHelper server(udpEchoPort);

        ApplicationContainer serverApp = server.Install(t3);
        serverApp.Start(Seconds(0.5));
        serverApp.Stop(Seconds(simDurationSeconds));

        // ------------------------------------------------------------------
        // Install single-switch UDP echo client on b3
        // ------------------------------------------------------------------
        NS_LOG_INFO("APP: Single-Switch UDP client (on node b3 bottom LAN)");

        Time interPacketInterval = Seconds(0.005);
        uint32_t packetSize = 1000;
        uint32_t maxPacketCount = (simDurationSeconds - 2.0) / 0.005;

        UdpEchoClientHelper client(Ipv4Address("192.168.1.3"), udpEchoPort);

        client.SetAttribute("MaxPackets", UintegerValue(maxPacketCount));
        client.SetAttribute("Interval", TimeValue(interPacketInterval));
        client.SetAttribute("PacketSize", UintegerValue(packetSize));

        ApplicationContainer clientApp = client.Install(b3);
        clientApp.Start(Seconds(0.5));
        clientApp.Stop(Seconds(simDurationSeconds));
    }

    // ======================================================================
    // Print routing tables at T=0.1
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // NOTE: Node 0 and Node 13 must have non-empty tables (except for local
    //       loopback and local LAN) if routing is operating correctly.
    // ----------------------------------------------------------------------
    NS_LOG_INFO("Set up to print routing tables at T=0.1s");

    Ptr<OutputStreamWrapper> routingStream =
        Create<OutputStreamWrapper>("global-routing-multi-switch-plus-router.routes",
                                    std::ios::out);

    Ipv4RoutingHelper::PrintRoutingTableAllAt(Seconds(0.1), routingStream);

    // ======================================================================
    // Configure PCAP traces
    // ----------------------------------------------------------------------
    NS_LOG_INFO("Configure PCAP Tracing (if any configured).");

    // - - - - - - - - - - - - - -
    // multi-switch UDP echo client
    // - - - - - - - - - - - - - -
    if (vssearch("t2", pcapLocationVec))
    {
        csmaX.EnablePcap("t2.pcap", topLanIpDevices.Get(1), true, true);
    }

    // - - - - - - - - - - - - - -
    // multi-switch UDP echo server
    // - - - - - - - - - - - - - -
    if (vssearch("b2", pcapLocationVec))
    {
        csmaY.EnablePcap("b2.pcap", botLanIpDevices.Get(1), true, true);
    }

    // - - - - - - - - - - - - - -
    // single-switch UDP echo client
    // - - - - - - - - - - - - - -
    if (vssearch("b3", pcapLocationVec))
    {
        csmaY.EnablePcap("b3.pcap", botLanIpDevices.Get(2), true, true);
    }

    // - - - - - - - - - - - - - -
    // single-switch UDP echo server
    // - - - - - - - - - - - - - -
    if (vssearch("t3", pcapLocationVec))
    {
        csmaX.EnablePcap("t3.pcap", topLanIpDevices.Get(2), true, true);
    }

    // - - - - - - - - - - - - - -
    // top router, LAN side
    // - - - - - - - - - - - - - -
    if (vssearch("trlan", pcapLocationVec))
    {
        csmaY.EnablePcap("trlan.pcap", topLanIpDevices.Get(0), true, true);
    }

    // - - - - - - - - - - - - - -
    // bottom router, LAN side
    // - - - - - - - - - - - - - -
    if (vssearch("brlan", pcapLocationVec))
    {
        csmaX.EnablePcap("brlan.pcap", botLanIpDevices.Get(0), true, true);
    }

    // - - - - - - - - - - - - - -
    // top router, WAN side
    // - - - - - - - - - - - - - -
    if (vssearch("trwan", pcapLocationVec))
    {
        p2p.EnablePcap("trwan.pcap", link_tr_br.Get(0), true, true);
    }

    // - - - - - - - - - - - - - -
    // bottom router, WAN side
    // - - - - - - - - - - - - - -
    if (vssearch("brwan", pcapLocationVec))
    {
        p2p.EnablePcap("brwan.pcap", link_tr_br.Get(1), true, true);
    }

    // ======================================================================
    // Now, do the actual simulation.
    // ----------------------------------------------------------------------
    NS_LOG_INFO("Run Simulation for " << simDurationSeconds << " seconds.");

    Simulator::Stop(Seconds(simDurationSeconds));
    Simulator::Run();

    Simulator::Destroy();
    NS_LOG_INFO("Done.");

    return 0;
}
