ns-3.30でネットワーク構築（前編）

【　[ns-3.30の使い方](https://qiita.com/dorapon2000/items/72dcc08eb3c8857f5603) → [1](https://qiita.com/dorapon2000/items/5c0c0a399aeee629be63) → [2] → [3](https://qiita.com/dorapon2000/items/87c1037f7da7c1676837) → [4](https://qiita.com/dorapon2000/items/4b95e5658d537c2b5e72) → [5](https://qiita.com/dorapon2000/items/fe8151f7ee6bcc30c72e) → [6](https://qiita.com/dorapon2000/items/494164b004e2f42a4553) → [7](https://qiita.com/dorapon2000/items/fadabf05a9c947bee87c)　】

ネットワークシミュレータであるns3の説明をいくつかにわけて投稿しています．この投稿は「2. ネットワーク構築（前編）」です．

# 2. ネットワーク構築（前編）

サンプルコードを用いて，最も基本的なネットワークをシミュレーションしてみましょう．

- first.ccの実行
- ns3のコンポネント
- first.ccの解説
- まとめ

### 文献
https://www.nsnam.org/docs/release/3.30/tutorial/html/conceptual-overview.html

## first.ccの実行

用いるサンプルコードはダウンロードしたフォルダns-3.30（以下ns3のルートフォルダと呼びます）以下に用意されている[examples/tutorial/first.cc](https://gitlab.com/nsnam/ns-3-dev/blob/ns-3.30/examples/tutorial/first.cc)です．2つのマシンが1本のケーブルで接続されており，片方からもう片方へpingを1つだけ送るという非常にシンプルなシミュレーションになります．

<img width="575" alt="first_topology.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/454a12ef-16c0-3b18-17b4-456051dbed22.png">

自作のシナリオファイルは，ns3ルートフォルダ直下にあるscratchフォルダに置きます．このフォルダ内のファイルはns3にシナリオファイルと認識され，自動でビルドされます．first.ccをscratchフォルダにmyfirst.ccという名前でコピーして実行しましょう[^1]．実行時の指定は拡張子を除いたmyfirstです．

```sh
$ cp examples/tutorial/first.cc scratch/myfirst.cc
$ ./waf --run myfirst
Waf: Entering directory `/home/ns3/workspace/ns-3-allinone/ns-3.30/build'
[2794/2869] Compiling scratch/myfirst.cc
[2825/2869] Linking build/scratch/myfirst
Waf: Leaving directory `/home/ns3/workspace/ns-3-allinone/ns-3.30/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (7.281s)
At time 2s client sent 1024 bytes to 10.1.1.2 port 9
At time 2.00369s server received 1024 bytes from 10.1.1.1 port 49153
At time 2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
At time 2.00737s client received 1024 bytes from 10.1.1.2 port 9
```

実行後の前半6行はシミュレーションのためのビルドが成功したことを示しています．`./waf --run`を実行すると毎回表示されるため，今後は実行結果を示す際に省略します．シミュレーションの様子は後半4行で示されており，2秒にクライアントがサーバ(10.1.1.2:9)へ向けパケット(ping)を送り．サーバは2.00369秒にそのパケットを受け取っています．同時刻に，サーバからクライアントへのパケット(pong)の送受信が発生していることがわかります．

シミュレーションを視覚化してみましょう．--visオプションをつけて実行するとビジュアライザーが起動します．起動しない方は[1. インストール](https://qiita.com/dorapon2000/items/5c0c0a399aeee629be63)の追加パッケージを確認してみてください．

```sh
./waf --run myfirst --vis
```

![first_pyviz.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/12671e06-16eb-f2b8-cc23-85cece49ff92.gif)

Simulateボタンを押すとシミュレーションが開始します．緑色の矢印はトラフィックの発生を意味します．

ネットワークシミュレーションをしている実感は湧いてきたでしょうか．ここからfirst.ccの解説を通して，まったりシナリオファイルについて理解していきましょう．

##ns3のコンポネント

first.ccの中身を解説する前に，ns3で用いられるコンポネントを説明します．コンポネントは日本語にすると構成要素という意味です．ここではNode，NetDevice，Channel，Applicationに加えてトポロジーヘルパーを紹介します．

### Node

現実世界のホスト/マシンに相当します．

![sozai_cman_jp_20190724051606.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/a477eef1-b5fc-cb97-3826-8282a68ab565.png)

### NetDevice

現実世界のネットワークインターフェースに相当します．ここでチャンネルのリンク速度やIPアドレスを設定します．

### Channel

現実世界の通信媒体/リンクに相当します．例えば，Ethernet(csma)やWiFi，Point-to-Point．もしCsmaChannelを使う場合，NetDeviceも対応するCsmaNetDeviceである必要があります．

<img width="386" alt="channel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/93459e76-a610-581c-be81-72c789b477bd.png">

### Application

現実世界のアプリケーションとは少し違い，ns3のアプリケーションはノード間で意図したトラフィックを発生させるためだけに実行されます．ホスト上でタスクをこなすために実行されるわけではありません．

<img width="122" alt="application.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/fd50d765-3328-e046-46cd-7be912896de1.png">

### トポロジーヘルパー

上記の4つを組み合わせてシミュレーションシナリオを作成しますが，大量のノードを1つ1つ作成したりIPアドレスを手動で設定するのは大変なため，ns3には大量のヘルパー関数が存在します．ヘルパー関数を用いれば，コード量をすっきりさせることができます．ヘルパー関数では実現できない細かい設定をしたい場合，自身でスクラッチする必要があります．



## first.ccの解説

[examples/tutorial/first.cc](https://gitlab.com/nsnam/ns-3-dev/blob/ns-3.30/examples/tutorial/first.cc)を適当なエディタで開いてください（scratch/myfirst.ccでもOK）．

### コード概要

![first_code.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/50544797-cc3e-1da1-41a0-3c713df4c51c.jpeg)

### ボイラーテンプレート

```c++:first.cc
/* -*- 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
 */
```

emacsのコーディングスタイルとコードのライセンスが表記されています．

### モジュールのインクルード

```c++:first.cc
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"
```

このシナリオファイルを動かすために必要なns3モジュールをインクルードします．最初のうちはサンプルコードを見て，見まねで列挙するとよいでしょう．必要なモジュールのインクルードを忘れるとエラーが出ますが，余計なものが含まれる分にはビルド時間が長くなるだけで済みます．具体的な調べ方は次の記事で取り上げますね．

### ns3名前空間

```c++:first.cc
using namespace ns3;
```

ns3のシナリオファイルやモジュールはすべてns3名前空間に属します．

### ロギング

```c++:first.cc
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
```

大量のns3のコードの中で，特定の場所のログだけ有効にしたいという時に，その特定の場所に名前がある必要があります．それをここで定義しています．ロギングについては別の記事で詳しく取り上げます．

<img width="790" alt="pro1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/7862ab11-fa99-bbc2-7e99-60d6143e2595.png">

### main関数の最初

```c++:first.cc
int
main (int argc, char *argv[])
{
  CommandLine cmd;
  cmd.Parse (argc, argv);
```

CommandLineはコマンドラインのオプションを自作できる便利なクラスです．ここでは肝心のオプションを自作していないため，使い方の説明は別の機会にします．

```c++:first.cc
  Time::SetResolution (Time::NS);
```

シミュレーションの最小単位を1ナノ秒に指定しています．デフォルトでも1ナノ秒ですが，将来のアップデートのために明示的に記述しているそうです．

```c++:first.cc
  LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
  LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
```

ロギングの部分で触れたように，特定の場所のログを有効にしています．

<img width="787" alt="pro2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/795dd1c4-d604-ca4e-0cfa-93ac2e46e631.png">

### NodeContainer

```c++:first.cc
  NodeContainer nodes;
  nodes.Create (2);
```

ノードを2つ作成します（0番目がクライアント，1番目がサーバ）．NodeContainerはノードのリストと捉えられます．コンテナに入れずに1つずつノードを管理する場合は以下のようになるでしょう．

```c++
Ptr<Node> client = CreateObject<Node> ();
Ptr<Node> server = CreateObject<Node> ();
```

あとからコンテナに追加することもできます．

```c++
NodeContainer nodes;
nodes.Add (client);
nodes.Add (server);
```

Get()でコンテナからノードを取り出せます．

```c++
Ptr<Node> client = nodes.Get (0);
```

<img width="790" alt="pro3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/17fd8e76-e08b-200a-a01b-062540c5b8aa.png">

### PointToPointHelper

```c++:first.cc
  PointToPointHelper pointToPoint;
  pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
  pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
```

PointToPoint（1対1）のトポロジを作成するにはPointToPointHelperが便利です．PointToPointHelperがまとめて，ネットデバイスとチャンネルの両方を設定してくれます．

後ろ2つに共通してAttribute（属性変数）という単語が現れますが，ns3における重要な用語です．"DataRate"はネットデバイスが持つ属性変数の1つで，リンク速度を表します．"Delay"はチャンネルが持つ属性変数の1つで，リンクの遅延を表します．属性変数の指定方法はいくつかあり，ここではPointToPointHelperのメソッドを介して変更しています．なお，属性変数を明示的に指定しなければ，あらかじめ用意されているデフォルト値になります．その場合の"DataRate"は32768bps，"Delay"は0.0ナノ秒です．

### NetDeviceContainer

```c++:first.cc
  NetDeviceContainer devices;
  devices = pointToPoint.Install (nodes);
```

ネットデバイスにもコンテナが存在し，1行目で作成しています．2行目でPointToPointHelperが，ネットデバイスの作成，チャンネルの作成，先ほどの属性変数の反映をすべてやってくれます．戻り値は作成されたネットデバイスのコンテナなため，1行目で作成したコンテナに代入します．devicesにはnodes同様に2つのネットデバイスが入っています．

### InternetStackHelper

```c++:first.cc
  InternetStackHelper stack;
  stack.Install (nodes);
```

プロトコルスタックの設定をInternetStackHelperでします．IPv4とIPv6のどちらで処理するかをここで決定しています．stack.Install()の引数は，ノードまたはノードコンテナなので注意してください．

### Ipv4AddressHelper

```c++:first.cc
  Ipv4AddressHelper address;
  address.SetBase ("10.1.1.0", "255.255.255.0");
```

IPアドレスの割り当てはIpv4AddressHelperで行います．SetBase()の第一引数にネットワークアドレスを，第二引数にネットマスクを指定します．

```c++:first.cc
  Ipv4InterfaceContainer interfaces = address.Assign (devices);
```

Assignで先ほどのアドレス範囲がネットデバイスに割り当てられます．ホスト部は1から順に割り当てられるため，ネットデバイスコンテナの0番目（クライアント）は10.1.1.1，1番目（サーバ）は10.1.1.2です．

ネットデバイスとIPアドレスを結びつけて管理するために，ns3ではIpv4Interfaceオブジェクトを用意しています．Ipv4InterfaceContainerはそのコンテナで，特定のネットデバイスのIPアドレスを取得したいときにGetAddress()を使って取得できます．

```c++
# devices.Get(1)のIPアドレスを取得する
interfaces.GetAddress(1);
```

<img width="791" alt="pro4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/40ee68c3-ceaf-cb81-f17c-855f83e0b4ec.png">

### UdpEchoServerHelper

ここからアプリケーションの設定です．サーバ側とクライアント側でそれぞれ設定する必要があります．ここではechoサービス（ping）を再現するために，UdpEchoServerHelperとUdpEchoClientHelperを使います．

```c++:first.cc
  UdpEchoServerHelper echoServer (9);

  ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
  serverApps.Start (Seconds (1.0));
  serverApps.Stop (Seconds (10.0));
```

UdpEchoServerHelperのコンストラクタの引数に9を指定することで，9番ポートをリッスンします．echoServer.Install()の引数で，サーバアプリケーションを配置するノードを指定します．ここでは1番目のノードがサーバになります．戻り値はアプリケーションのコンテナです．

Start()とStop()でコンテナ内のアプリケーションの開始時刻と終了時刻を指定します．

### UdpEchoClientHelper

```c++:first.cc
  UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
  echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
  echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
  echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

  ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
  clientApps.Start (Seconds (2.0));
  clientApps.Stop (Seconds (10.0));
```

UdpEchoClientHelperのコンストラクタには送信相手のIPアドレスとポート番号をどちらも指定する必要があります．ここらへんはソケットの考え方と近いです．また，pingがきたらpongを返すだけのサーバ側と違い，クライアント側はpingを送る回数や送信間隔を属性変数で指定します．"MaxPackets"は送るパケットの数，"Interval"は送信間隔，"PacketSize"はパケットサイズです．

クライアント側アプリケーションはサーバ側アプリケーションが開始してから1秒後の2秒目を開始時刻にしています．

<img width="788" alt="pro5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32402/ee637f8f-1b95-f080-42ae-51f8c9749fd0.png">

### シミュレータの設定

```c++:first.cc
  Simulator::Run ();
  Simulator::Destroy ();
  return 0;
}
```

Run()でこれまでのシナリオを実行します．全てのシナリオが終わるとDestroy()が呼ばれ，メモリが解放されます．

## まとめ

複雑なシナリオファイルも基本に立ち返ればfirst.ccの構成と同じです．わからなくなった時はここに戻ってきましょう．

[次の記事](https://qiita.com/dorapon2000/items/87c1037f7da7c1676837)はオンラインドキュメントの使い方について説明します．インクルードするファイルは何か，どのような属性変数があるか，モジュールの使い方はどうやって調べればいいか等の疑問を自己解決できるようになることを目指します．



[^1]: ns3のサンプルコードもシナリオファイルとして登録されているため，実は`./waf --run first`としても実行できます．myfirst.ccにコピーしてから実行したのは，自作シナリオファイルはscratchフォルダ内に入れておくことを示したかったからです．