## NS-3 安裝說明 ### 設定 Ubuntu 環境 * [How to Install Ubuntu 22.04 LTS on VirtualBox in Windows 11](https://www.youtube.com/watch?v=v1JVqd8M3Yc) * [How to Install Ubuntu on Windows 11 (WSL)](https://www.youtube.com/watch?v=wjbbl0TTMeo&t=19s) * [在 Windows 10 中安裝 Ubuntu (WSL)](https://www.kwchang0831.dev/dev-env/wsl/ubuntu) 這裡使用 "Ubuntu 24.04.2 LTS" 做示範,並安裝在 windows 11 中的 WSL ### 安裝 ns-3 依賴 以下是安裝 ns-3 前所需安裝的依賴,可以按照順序一行行輸入,抑或打包成 bash 文件 ```bash #!/bin/bash # 更新系統 sudo apt update && sudo apt upgrade -y # 基本開發工具 sudo apt install -y g++ python3 python3-dev cmake ninja-build git ccache pkg-config sqlite3 # Python 相關工具 python3 -m pip install --user cppyy cxxfilt sudo apt install -y python3-setuptools python3-pip python3-sphinx ipython3 # GUI & 圖形相關依賴 sudo apt install -y qt5-qmake qtbase5-dev qtbase5-dev-tools \ gir1.2-goocanvas-2.0 python3-gi python3-gi-cairo python3-pygraphviz gir1.2-gtk-3.0 # 科學計算與數學函式庫 sudo apt install -y gsl-bin libgsl-dev libgslcblas0 # 網絡相關工具 sudo apt install -y tcpdump lvm lxc uml-utilities # 文檔生成與格式化工具 sudo apt install -y doxygen graphviz imagemagick \ texlive texlive-extra-utils texlive-latex-extra texlive-font-utils dvipng latexmk # 調試工具 sudo apt install -y gdb valgrind clang-format # 開發環境相關依賴 sudo apt install -y openmpi-bin openmpi-common openmpi-doc libopenmpi-dev \ mercurial unzip automake # 低層庫與 XML 相關依賴 sudo apt install -y libxml2 libxml2-dev libboost-all-dev libgtk-3-dev \ libsqlite3-dev libc6-dev libc6-dev-i386 libclang-dev llvm-dev ``` 下載時的注意事項 ```bash $ python3 -m pip install --user cppyy $ python3 -m pip install --user cxxfilt ``` 這兩段命令不可以使用 sudo 管理權限執行,此外,安裝 cppyy 時需要將 `~/.local/bin` 添加到環境變數,具體 Shell 腳本如下: ```bash $ echo "export PATH=/home/$(whoami)/.local/bin:\$PATH" >> ~/.bashrc $ source ~/.bashrc ``` ### 下載 ns-3 ```shell $ git clone https://github.com/nsnam/ns-3-dev-git ``` ### 編譯與安裝 ns-3 ```shell $ cd ns-3-dev-git $ ./ns3 clean $ ./ns3 configure --enable-examples --enable-tests --enable-python-bindings --build-profile=debug $ ./ns3 build ``` 其中 configure 的前兩個參數是編譯時包含範例腳本&測試腳本,`--prefix=` 可以指定安裝路徑。完整參數可以參考 `./ns3 configure --help` ### Testing ns-3 驗證安裝結果 ```shell $ ./test.py ``` ### Running ns-3 在安裝完 ns-3 後,只要向下面一樣下 command 即可啟動 ns-3 ```shell $ ./ns3 run simple-global-routing ``` `simple-global-routing` 可替換成其他檔案名 如 : ```shell $ ./ns3 run hello-simulator ``` 🚀 注意要在安裝後的 `ns-3-dev-git` 路徑下執行,並注意自己的檔案夾名稱 ## 範例操作 ### NS3中的常用概念 > * Node(節點): > ns3 用 Node 類來抽象網絡中的計算設備,可以理解為路由器、感測器等設備,通過向節點添加應用程式、協議棧等內容,使其能夠模擬某些設備的運行。 > > * Application(應用): > ns3 的 Node 類並沒有真實的硬體設備和對應的驅動程式,因此若要模擬某些設備,還需要對其功能進行模擬,Application 類的作用就是對其功能的模擬,例如收發數據包,以完成對某些設備的模擬。 > > * Channel(信道): > 現實中的網絡有有線信道和無線信道,在 ns3 中,一個 Node 將連接 Channel 對象上來實現通訊,通過對 Channel 類的設置,可以模擬延遲或丟包等信道屬性,使其更像真實的通信信道。 > > * NetDevice(網卡): > ns3 中 Node 內的抽象包括軟件驅動程式和模擬硬體。一個網卡被“安裝”在一個節點上,以使該節點能夠通過信道與模擬的其他節點通信。 > 這就像在真實計算機中一樣,一個節點可以通過多個 NetDevices 連接到多條信道,即 NetDevice 類是用來管理 Node 和 Channel 物件的連接方式。 > 例如 CsmaNetDevice 被設計為與 CsmaChannel 一起工作,而 WifiNetDevice 則被設計為與 WifiChannel 一起工作。 > * Helper(助手): > 在使用實體硬體時,通常需要一定的驅動程式和軟件來完成與模組之間的操作,而在 ns3 中,則提供了 Helper 類來輔助完成模組與模組之間的交互。 > 例如:在 GitHub 的 AquaSimNG 專案中,會有 AquaSimHelper、AquaSimChannelHelper 來輔助對模組參數的設置及模擬環境的配置。 ### 創建自己的腳本 在 ns-3 可以用 python 或 C 撰寫腳本,在此先準備一個範例函式庫及腳本 :::spoiler tutorial-app.h ```clike /* * SPDX-License-Identifier: GPL-2.0-only */ #ifndef TUTORIAL_APP_H #define TUTORIAL_APP_H #include "ns3/core-module.h" #include "ns3/internet-module.h" #include "ns3/network-module.h" #include "ns3/applications-module.h" namespace ns3 { class Application; class TutorialApp : public Application { public: TutorialApp(); ~TutorialApp() override; static TypeId GetTypeId(); void Setup(Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate); private: void StartApplication() override; void StopApplication() override; void SendPacket(); void ScheduleTx(); Ptr<Socket> m_socket; Address m_peer; uint32_t m_packetSize; uint32_t m_nPackets; DataRate m_dataRate; EventId m_sendEvent; bool m_running; uint32_t m_packetsSent; }; TutorialApp::TutorialApp() : m_socket(nullptr), m_peer(), m_packetSize(0), m_nPackets(0), m_dataRate(0), m_sendEvent(), m_running(false), m_packetsSent(0) { } TutorialApp::~TutorialApp() { m_socket = nullptr; } TypeId TutorialApp::GetTypeId() { static TypeId tid = TypeId("TutorialApp") .SetParent<Application>() .SetGroupName("Tutorial") .AddConstructor<TutorialApp>(); return tid; } void TutorialApp::Setup(Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate) { m_socket = socket; m_peer = address; m_packetSize = packetSize; m_nPackets = nPackets; m_dataRate = dataRate; } void TutorialApp::StartApplication() { m_running = true; m_packetsSent = 0; m_socket->Bind(); m_socket->Connect(m_peer); SendPacket(); } void TutorialApp::StopApplication() { m_running = false; if (m_sendEvent.IsPending()) { Simulator::Cancel(m_sendEvent); } if (m_socket) { m_socket->Close(); } } void TutorialApp::SendPacket() { Ptr<Packet> packet = Create<Packet>(m_packetSize); m_socket->Send(packet); if (++m_packetsSent < m_nPackets) { ScheduleTx(); } } void TutorialApp::ScheduleTx() { if (m_running) { Time tNext(Seconds(m_packetSize * 8 / static_cast<double>(m_dataRate.GetBitRate()))); m_sendEvent = Simulator::Schedule(tNext, &TutorialApp::SendPacket, this); } } } // namespace ns3 #endif /* TUTORIAL_APP_H */ ``` ::: 將以上程式碼的檔案名改為 `tutorial-app.h` :::spoiler FirstCode ```clike /* SPDX-License-Identifier: GPL-2.0-only */ #include "tutorial-app.h" #include "ns3/applications-module.h" #include "ns3/core-module.h" #include "ns3/internet-module.h" #include "ns3/network-module.h" #include "ns3/point-to-point-module.h" // 搭配 netanim 使用 // #include "ns3/netanim-module.h" // #include "ns3/mobility-module.h" #include <fstream> using namespace ns3; NS_LOG_COMPONENT_DEFINE("ScriptExample"); // =========================================================================== // // leftNode Router0 Router1 rightNode // +----------------+ +----------------+ +----------------+ +----------------+ // | ns-3 TCP | | ns-3 TCP | | ns-3 TCP | | ns-3 TCP | // +----------------+ +----------------+ +----------------+ +----------------+ // | 10.1.1.1 | | 10.1.1.2 | | 10.1.2.1 | | 10.1.3.1 | // +----------------+ +----------------+ +----------------+ +----------------+ // | point-to-point | | point-to-point | | point-to-point | | point-to-point | // +----------------+ +----------------+ +----------------+ +----------------+ // | | | | // +---------------------+--------------------+---------------------+ // 10 Mbps 1 Mbps 10 Mbps // 1 ms 10 ms 1 ms // // Packet Flow: leftNode -> Router0 -> Router1 -> rightNode // // Network Configuration: // - leftNode to Router0: 10.1.1.0/30 (10 Mbps, 1 ms) // - Router0 to Router1: 10.1.2.0/30 (1 Mbps, 10 ms) // - Router1 to rightNode: 10.1.3.0/30 (10 Mbps, 1 ms) // =========================================================================== // /** * Congestion window change callback * * @param oldCwnd Old congestion window. * @param newCwnd New congestion window. */ static void CwndChange(uint32_t oldCwnd, uint32_t newCwnd) { NS_LOG_UNCOND(Simulator::Now().GetSeconds() << "\t" << newCwnd); } /** * Rx drop callback * * @param p The dropped packet. */ static void RxDrop(Ptr<const Packet> p) { NS_LOG_UNCOND("RxDrop at " << Simulator::Now().GetSeconds()); } void QueueSizeLogger(uint32_t oldVal, uint32_t newVal) { static std::ofstream queueLog("queue-size.csv", std::ios_base::app); // Open in append mode queueLog << Simulator::Now().GetSeconds() << "," << newVal << std::endl; } int main(int argc, char* argv[]) { CommandLine cmd(__FILE__); cmd.Parse(argc, argv); LogComponentEnable("ScriptExample", LOG_LEVEL_ALL); // In the following three lines, TCP NewReno is used as the congestion // control algorithm, the initial congestion window of a TCP connection is // set to 1 packet, and the classic fast recovery algorithm is used. Note // that this configuration is used only to demonstrate how TCP parameters // can be configured in ns-3. Otherwise, it is recommended to use the default // settings of TCP in ns-3. Config::SetDefault("ns3::TcpL4Protocol::SocketType", StringValue("ns3::TcpNewReno")); Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(1)); Config::SetDefault("ns3::TcpL4Protocol::RecoveryType", TypeIdValue(TypeId::LookupByName("ns3::TcpClassicRecovery"))); // Create nodes NodeContainer leftNodes, rightNodes, routers; routers.Create(2); leftNodes.Create(1); rightNodes.Create(1); /////////////////////////////////////////////////////////////////////////////////// //// 搭配 netanim 使用 /* // Adding Mobility Model MobilityHelper mobility; // 設定節點為靜止 (stationary) mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); // 左邊節點 mobility.Install(leftNodes); Ptr<MobilityModel> mobLeft = leftNodes.Get(0)->GetObject<MobilityModel>(); mobLeft->SetPosition(Vector(0.0, 0.0, 0.0)); // 右邊節點 mobility.Install(rightNodes); Ptr<MobilityModel> mobRight = rightNodes.Get(0)->GetObject<MobilityModel>(); mobRight->SetPosition(Vector(100.0, 0.0, 0.0)); // 路由器 0 mobility.Install(routers.Get(0)); Ptr<MobilityModel> mobRouter0 = routers.Get(0)->GetObject<MobilityModel>(); mobRouter0->SetPosition(Vector(30.0, 0.0, 0.0)); // 路由器 1 mobility.Install(routers.Get(1)); Ptr<MobilityModel> mobRouter1 = routers.Get(1)->GetObject<MobilityModel>(); mobRouter1->SetPosition(Vector(70.0, 0.0, 0.0));*/ /////////////////////////////////////////////////////////////////////////////////// // std::vector<NetDeviceContainer> leftToRouter; // std::vector<NetDeviceContainer> routerToRight; // Create the point-to-point link helpers and connect two router nodes PointToPointHelper pointToPointRouter; pointToPointRouter.SetDeviceAttribute("DataRate", StringValue("1Mbps")); pointToPointRouter.SetChannelAttribute("Delay", StringValue("10ms")); // NetDeviceContainer r1r2ND = pointToPointRouter.Install(routers.Get(0), routers.Get(1)); // Create the point-to-point link helpers and connect leaf nodes to router PointToPointHelper pointToPointLeaf; pointToPointLeaf.SetDeviceAttribute("DataRate", StringValue("10Mbps")); pointToPointLeaf.SetChannelAttribute("Delay", StringValue("1ms")); // leftToRouter.push_back(pointToPointLeaf.Install(leftNodes.Get(0), routers.Get(0))); // routerToRight.push_back(pointToPointLeaf.Install(routers.Get(1), rightNodes.Get(0))); // 節點 0 <-> 路由器 0 NetDeviceContainer devices1 = pointToPointLeaf.Install(leftNodes.Get(0), routers.Get(0)); // 路由器 0 <-> 路由器 1 NetDeviceContainer devices2 = pointToPointRouter.Install(routers.Get(0), routers.Get(1)); // 路由器 1 <-> 節點 1 NetDeviceContainer devices3 = pointToPointLeaf.Install(routers.Get(1), rightNodes.Get(0)); // Ptr<RateErrorModel> em = CreateObject<RateErrorModel>(); // em->SetAttribute("ErrorRate", DoubleValue(0.00001)); // devices2.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(em)); // // 將協定堆疊安裝到所有節點和路由器上 InternetStackHelper stack; stack.Install(leftNodes); stack.Install(rightNodes); stack.Install(routers); // // Configure IP addresses for each network segment Ipv4AddressHelper address("10.0.0.0", "255.255.255.0"); Ipv4InterfaceContainer interfaces1 = address.Assign(devices1); address.NewNetwork(); Ipv4InterfaceContainer interfaces2 = address.Assign(devices2); address.NewNetwork(); Ipv4InterfaceContainer interfaces3 = address.Assign(devices3); address.NewNetwork(); Ipv4GlobalRoutingHelper::PopulateRoutingTables(); // TCP 伺服器應用設定 (接收端) (PacketSink) uint16_t sinkPort = 8080; Address sinkAddress(InetSocketAddress(interfaces3.GetAddress(1), sinkPort)); PacketSinkHelper packetSinkHelper("ns3::TcpSocketFactory", InetSocketAddress(Ipv4Address::GetAny(), sinkPort)); ApplicationContainer sinkApps = packetSinkHelper.Install(rightNodes.Get(0)); sinkApps.Start(Seconds(0.)); // Server 啟動 sinkApps.Stop(Seconds(20.)); // TCP 用戶端應用設定 (發送端) (TutorialApp) Ptr<Socket> ns3TcpSocket = Socket::CreateSocket(leftNodes.Get(0), TcpSocketFactory::GetTypeId()); ns3TcpSocket->TraceConnectWithoutContext("CongestionWindow", MakeCallback(&CwndChange)); Ptr<TutorialApp> app = CreateObject<TutorialApp>(); app->Setup(ns3TcpSocket, sinkAddress, 1040, 1000, DataRate("1Mbps")); leftNodes.Get(0)->AddApplication(app); app->SetStartTime(Seconds(1.)); // Client 啟動 app->SetStopTime(Seconds(20.)); // ===================== Queue Size Trace 設定 ===================== // 取得 Router0 出口 (指向 Router1) 的 NetDevice Ptr<PointToPointNetDevice> router0Dev = DynamicCast<PointToPointNetDevice>(devices2.Get(0)); Ptr<Queue<Packet>> queue = router0Dev->GetQueue(); // 開啟輸出檔案 static std::ofstream queueLog("queue-size.csv"); queueLog << "Time(s),QueueSize(packets)" << std::endl; // Trace callback:每次 queue size 改變時寫入檔案 // 註冊 callback queue->TraceConnectWithoutContext("PacketsInQueue", MakeCallback(&QueueSizeLogger)); // Trace //EnablePcap(std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false); pointToPointLeaf.EnablePcap("mytrace-left", devices1.Get(0), true); pointToPointLeaf.EnablePcap("mytrace-right", devices3.Get(0), true); pointToPointRouter.EnablePcap("mytrace-router", devices2.Get(0), true); devices1.Get(0)->TraceConnectWithoutContext("PhyRxDrop", MakeCallback(&RxDrop)); devices2.Get(0)->TraceConnectWithoutContext("PhyRxDrop", MakeCallback(&RxDrop)); devices3.Get(0)->TraceConnectWithoutContext("PhyRxDrop", MakeCallback(&RxDrop)); // 搭配 netanim 使用 // AnimationInterface anim("anim.xml"); Simulator::Stop(Seconds(20)); Simulator::Run(); Simulator::Destroy(); // 關閉輸出檔案 queueLog.close(); return 0; } ``` ::: 這裡取名 `FirstCode.cc` ,接著將兩份程式碼放置於 scratch 目錄底下,然後於主目錄底下編譯 ```shell $ ./ns3 build ``` 執行 ```shell $ ./ns3 run FirstCode ``` :::info 在舊版 ns-3 使用 `./war` configure & build 文件,在新版已使用 `./ns3` 代替,另外注意 `./war --run yourCode` `./ns3 run yourCode` 的不同 ::: ## 數據分析 ### 輸出 & 分析數據 在執行程式期間,可以將程式的結果統一輸出到 `cwnd.dat` 方便後續進行資料分析 ```shell $ ./ns3 run FirstCode > cwnd.dat 2>&1 ``` 在資料收集完成後,接著使用 `gnuplot` 將資料彙整成圖片 ```shell $ gnuplot # 進入 gnuplot gnuplot> set terminal png size 640,480 # 設定圖片大小 gnuplot> set output "cwnd.png" # 設定圖片名稱 gnuplot> set format y "%.1t*10^{%T}" # 設定縱坐標為科學記號 gnuplot> set xlabel "Elapsed Time" gnuplot> set ylabel "Congestion Window" # 設定橫縱軸的名稱 gnuplot> plot "cwnd.dat" using 1:2 title 'Congestion Window' with linespoints # 告知 gnuplot 使用 cwnd.dat 為資料來源做繪圖 gnuplot> exit ``` 最後會輸出如下圖 : <!-- ![image](https://hackmd.io/_uploads/SyIBnok-xe.png)--> ![image](https://hackmd.io/_uploads/r1Rq5sHZll.png) * X 軸: 時間 (Time)。 * Y 軸: 擁塞窗口大小 (Congestion Window),單位是 Bytes 程式碼會產生 ~~`tcp-newreno.tr` 文字檔~~ 與 二進制的`mytrace-0-0.pcap` 日誌檔,兩者可以分別藉由下面的 command line 進行查看 在產生出來的 packet capture 檔案中,檔案名稱會以 `<prefix>-<node>-<device>` 這個邏輯下去命名 <!-- ```shell $ cat tcp-newreno.tr | less ``` --> ### 搭配 wireshark 進行分析 如下匯入 `mytrace-left-2-0.pcap ` 檔案 ![image](https://hackmd.io/_uploads/SkMnzxI2Jl.png) 於工具列選擇 statistics -> TCP Stream Graphs -> Window Scaling ![image](https://hackmd.io/_uploads/SyUC_cZ0kg.png) 並點選右下角 `Switch Direction` 按鍵,最後輸出 ![image](https://hackmd.io/_uploads/ryQOOqZC1e.png) * X 軸: 時間 (Time)。 * Y 軸: 窗口大小 (Window Size),單位是 Bytes (位元組)。 ## 同場加映 [NS-3 搭配 NetAnim 可視化網路拓譜](https://hackmd.io/@IsPlpVINQVGLm_dcip1iMg/Hy-85eWRyx) ## 參考 [在 Windows 10 中安裝和使用 Ubuntu 子系統](https://hackmd.io/@CynthiaChuang/WSL-Install-and-Use-the-Ubuntu-subsystem-in-Win10) [[Github] The Network Simulator, Version 3](https://github.com/nsnam/ns-3-dev-git) [[Github] Tracing through NS-3](https://github.com/nsnam/ns-3-dev-git/blob/master/doc/tutorial/source/tracing.rst) [[CSDN] NS3下载与安装](https://blog.csdn.net/qq_43268767/article/details/134276308) [ns-3 入门 1:介绍与安装](https://cyp0633.icu/post/ns3-intro-installation/) [[官方文件] TCP models in ns-3](https://www.nsnam.org/docs/release/3.44/models/html/tcp.html#writing-tcp-tests) [gnuplot 語法解說和示範](https://hackmd.io/@sysprog/Skwp-alOg)