---
# System prepended metadata

title: NS3 安裝說明

---

## 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)
