ns3におけるシミュレーションシナリオ作成法
本記事では,ネットワークシミュレータであるns3におけるシナリオコードの書き方を大雑把にまとめ,説明しています.
想定している読者さんは,
- C++をある程度知っている方
- ns3での基本的なシナリオ作成方法を知りたい方
- 「Helperって結局裏で何してるの?」と疑問をお持ちの方
です.
(本記事にあるプログラムコードはns-3.37をベースにしています.)
(本記事は著者の備忘録としてまとめたものです. また著者自身ns3の経験が浅いため,誤った表記をしているかもしれません. その際はコメント等でご指摘いただけると有難いです.)
1. ns3の概念
ns3では,実世界でのネットワークを構築する作業と同じような論理的なプロセスを採用している. まず,物理層とデータリンク層では,ノードと呼ばれる通信デバイスの筐体にNICなどのネットワークデバイスを装着して,有線,または無線メディアをその通信属性に合わせて「配線」する. 次に,IPアドレスや,ルーティング方式などのネットワーク層の情報を設定した後,トランスポート層で使用するプロトコルの設定を行う. 最後に,ネットワーク上で使用されるアプリケーションを装着する.
銭 飛著『ns3によるネットワークシミュレーション』森北出版 (2014) p.18より
ns3はC++で記述されたオブジェクトベースのシステムです. "通信端末"や"NIC","伝送媒体","プロトコル","アプリケーション","パケット"などといったネットワークシステムの構成要素の概念が,それぞれ抽象化され,C++のクラスで定義されています.
シナリオプログラミングでは,ns3のライブラリをインクルードし,必要となる構成要素のクラスからオブジェクトを生成して,属性値を変更したり互いに組み合わせながら,任意のネットワークシステムを構築します. この構築作業は「各PC (Node) にNIC (NetDevice) を装着し,ケーブル (Channel) をつなげ,アプリケーション (Application) を動かし通信する......」といったように,実際にネットワークを構築するようなイメージをもつとわかりやすいと思います.
2. オブジェクトモデル
構成要素を表しているクラスのモデルについて説明します.
ns3ではオブジェクト指向の設計をとっています. 例えば,エコーサーバアプリを表すns3::UdpEchoServerクラスは,ns3::Applicationクラスから継承して定義されています.
すべての構成要素のクラスは基本的にns3::SimpleRefCountクラス,ns3::ObjectBaseクラス,ns3::Objectクラスのいずれかを継承しています(ns3::Objectクラスは他2つから派生しています). これらの違いはサポートしている機能の有無です.
スマートポインタ (ns3::Ptr) | ns3::TypeId, 属性値 | オブジェクトの集約機能 | |
---|---|---|---|
class ns3::SimpleRefCount | o | x | x |
class ns3::ObjectBase | x | o | o |
class ns3::Object | o | o | o |
ns3のクラスはすべてこちらのドキュメントにまとめられています.
2.1. スマートポインタ (ns3::Ptr)
ns3ではメモリ管理を容易にするため,スマートポインタをベースにしたns3::Ptrを用いてオブジェクトを参照します.
2.2. ns3::TypeId
ns3::ObjectBaseおよびns3::Objectクラスから派生するクラスは,クラスのメタデータを表すns3::TypeIdを含んでいます.
- クラスを一意に識別する文字列
- 親クラス
- コンストラクタで用いるクラス
- 属性値(値の種類,最小値や最大値の制約など)
2.3. 属性値 (Attribute)
先のns3::TypeIdを用いて,そのオブジェクトの性質を表す属性値 (Attribute) が設定されます. この属性値は,シミュレーションシナリオに沿って変更できます(属性値の設定および変更方法については後述します).
属性値の一覧はこちらから確認できます.
2.4. オブジェクトの集約
多くのオブジェクトを作成すると参照が大変になります. このときオブジェクトの集約機能を使うと,オブジェクト間で関連付けがされ参照を簡単にできます(この方法についても後述します).
3. オブジェクトの生成,設定,Tips
オブジェクトの生成や属性値の設定はいくつかやり方があるのですが,ここでは基本的な方法を紹介します.
3.1. オブジェクトの生成
new演算子の代わりにns3::CreateObject(), ns3::Create()を用います. 返り値はns3::Ptrとなります.
ns3::Objectから派生したクラスのオブジェクトの生成は,ns3::CreateObject()を使います.
Ptr<Node> node = CreateObject<Node>();
それ以外のns3::SimpleRefCountクラスから派生するクラス(ns3::Ptrをサポートするクラス)のオブジェクトの生成は,ns3::Create()を使います.
Ptr<Packet> packet = Create<Packet>();
3.2. 属性値の設定
オブジェクトの属性値の設定,変更はns3::ObjectBaseクラスのメンバ関数SetAttribute()を用います. 第一引数に属性名,第二引数に指定の属性値を与えます.
Ptr<UdpEchoServer> app = CreateObject<UdpEchoServer>(); app->SetAttribute("Port", UintegerValue(12345)); app->SetAttribute("StartTime", TimeValue(Seconds(10.0))); app->SetAttribute("StopTime", TimeValue(Seconds(20.0))); node->AddApplication(app);
3.3. ObjectFactory
同じ属性値のオブジェクトを複数生成したい場合,それぞれにSetAttribute()で設定していくのは面倒です. そのときはObjectFactoryが便利です.
ObjectFactory factory; factory.SetTypeId("ns3::UdpEchoServer"); factory.Set("Port", UintegerValue(12345)); factory.Set("StartTime", TimeValue(Seconds(10.0))); factory.Set("StopTime", TimeValue(Seconds(20.0))); Ptr<UdpEchoServer> app1 = factory.Create<UdpEchoServer>(); node1->AddApplication(app1); Ptr<UdpEchoServer> app2 = factory.Create<UdpEchoServer>(); node2->AddApplication(app2);
3.4. Helper API
規模が巨大なシナリオを構成する場合,必要となる構成要素が膨大となり,各オブジェクト間の依存関係が複雑になります. そこでns3では,モデルごとにHelper APIが提供されています. Helper APIを用いれば,そのモデルの構成要素ををまとめて生成し,構築してくれます.
UdpEchoServerHelper helper(12345); server.SetAttribute("StartTime", TimeValue(Seconds(10.0))); server.SetAttribute("StopTime", TimeValue(Seconds(20.0))); ApplicationContainer apps = helper.Install(nodes);
3.5. Container
ns3ではContainerというオブジェクトをよく用います. Containerは同じクラスのオブジェクトをまとめて管理するためのもので,ns3::NodeContainerやns3::ApplicationContainerなどがあります. 中身のデータ構造はただのstd::vectorです.
3.6. オブジェクトの集約方法
オブジェクトの集約は,ns3::Objectクラスのメンバ関数AggregateObject()を用います. 次の例では,ns3::Nodeオブジェクトとns3::Socketオブジェクトを関連付けしています.
int main(){ Ptr<Node> node = CreateObject<Node>(); ... Ptr<Socket> socket = Socket::CreateSocket(node, UdpSocketFactory::GetTypeId()); node->AggregateObject(socket); ... f(node); ... }
2つのオブジェクトを関連付けした後は,ns3::Objectクラスのメンバ関数GetObject()を用いてクラスの型を指定することで,任意のオブジェクトを参照できます.
void f(Ptr<Node> node){ Ptr<Socket> socket = node->GetObject<Socket>(); ... }
ただし,1つのオブジェクトに2つ以上の同じクラスのオブジェクトを集約できません.
4. 主な構成要素
次にns3においてネットワークを構築する際に必要となる主な構成要素について説明します.
4.1. Node
ネットワークに接続している機器(ホスト,エンドシステム)を指します. 例えばPCやルータ,サーバ,他にもIoT機器もこれに対応します. ns3ではこれらをもまとめて抽象化し,ns3::Nodeクラスで表しています.
さらにワイヤレスネットワークにおける移動端末を表現するため,Nodeの位置や速度といったモビリティも指定できます(モビリティはns3::MobilityModelクラスで定義されています).
4.2. NetDevice
NIC(機器とネットワークをつなぐ拡張装置,ネットワークへのインターフェイス)に相当します. ns3ではns3::NetDeviceクラスで表されており,さらにこの子クラスで,Point-to-PointリンクやEthernet,Wi-Fiなど使用するネットワークモデルに対応したNetDeviceが定義されています.
- ns3::PointToPointNetDevice
- ns3::CsmaNetDevice
- ns3::WifiNetDevice etc.
4.3. Channel
有線や無線などの伝送媒体を指します. ns3ではns3::Channelクラスで定義されており,これも同様にネットワークモデルに応じて子クラスが定義されています.
- ns3::PointToPointChannel
- ns3::CsmaChannel
- ns3::WifiChannel etc.
4.4. Application
トラフィックを生成したり,パケットを送受信するなど任意のアクションを行う,Node上で実行されるアプリケーションを指します. これはns3::Applicationで定義されており,よく使われるアプリケーションはすでに子クラスで定義,提供されています.
- ns3::PacketSinkApplication
- ns3::OnOffApplication
- ns3::BulkSendApplication
- ns3::UdpEchoServer
- ns3::UdpEchoClient etc.
またns3ではソケットAPIも提供しており (ns3::Socket),ソケットプログラミングを行えば任意の通信を行うApplicationを定義することも可能です.
5. シナリオプログラミング実践例
5.1. シナリオ概要
2つのNodeをP2Pリンクで繋ぎ,200bytesのデータを送受信させる,といったシナリオを考えます.
時間 | イベント |
---|---|
シミュレーション開始. | |
5秒後 | サーバを起動する. |
10秒後 | クライアントからサーバへ,UDPで200bytesのデータを送信する. |
25秒後 | サーバを停止する. |
30秒後 | シミュレーション終了. |
5.2. 基本的なシナリオプログラミング
オブジェクトを意識して構築します.
- Nodeを作成する.
- NetDeviceを作成し,各Nodeに搭載する.
- Channelを作成し,各NetDeviceに配線する.
- Applicationを作成し,各Nodeにインストールする.
#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" using namespace ns3; int main() { LogComponentEnable("UdpServer", LOG_LEVEL_INFO); LogComponentEnable("UdpClient", LOG_LEVEL_INFO); LogComponentEnableAll(LOG_PREFIX_ALL); // (1) Nodeを作成する. NodeContainer nodes; Ptr<Node> node1 = CreateObject<Node>(); // 送信側のNodeを作成する. nodes.Add(node1); Ptr<Node> node2 = CreateObject<Node>(); // 受信側のNodeを作成する. nodes.Add(node2); // (2) NetDeviceを作成し,各Nodeに搭載する. NetDeviceContainer devices; ObjectFactory deviceFactory("ns3::PointToPointNetDevice"); deviceFactory.Set("DataRate", StringValue("5Mbps")); // NetDeviceの送信レートを設定する. Ptr<PointToPointNetDevice> device1 = deviceFactory.Create<PointToPointNetDevice>(); // NetDeviceを作成する. device1->SetAddress(Mac48Address::Allocate()); // MACアドレスを割り当てる. node1->AddDevice(device1); // Nodeに搭載する. devices.Add(device1); Ptr<PointToPointNetDevice> device2 = deviceFactory.Create<PointToPointNetDevice>(); device2->SetAddress(Mac48Address::Allocate()); node2->AddDevice(device2); devices.Add(device2); // パケットキューを設定する. ObjectFactory queuelFactory("ns3::DropTailQueue<Packet>"); Ptr<Queue<Packet> > queue1 = queuelFactory.Create<Queue<Packet> >(); device1->SetQueue(queue1); Ptr<Queue<Packet> > queue2 = queuelFactory.Create<Queue<Packet> >(); device2->SetQueue(queue2); // (3) Channelを設定する. ObjectFactory channelFactory("ns3::PointToPointChannel"); channelFactory.Set("Delay", StringValue("2ms")); // 伝搬による遅延時間を設定する. Ptr<PointToPointChannel> channel = channelFactory.Create<PointToPointChannel>(); // Channelを作成する. // 各NetDeviceにChannelを配線する. device1->Attach(channel); device2->Attach(channel); // 各Nodeにプロトコルスタックを積む. InternetStackHelper internet; internet.Install(node1); internet.Install(node2); // 各NetDeviceにIPアドレスを割り当てる. Ipv4AddressHelper ipv4; ipv4.SetBase("10.1.1.0", "255.255.255.0"); Ipv4InterfaceContainer ifs = ipv4.Assign(devices); // (4) Applicationを作成する. uint16_t port = 12345; Ptr<UdpServer> serverApp = CreateObject<UdpServer>(); serverApp->SetAttribute("Port", UintegerValue(port)); // ポート番号を設定する. serverApp->SetAttribute("StartTime", TimeValue(Seconds(5.0))); // サーバアプリの開始時間を設定する. serverApp->SetAttribute("StopTime", TimeValue(Seconds(25.0))); // サーバアプリの終了時間を設定する. node2->AddApplication(serverApp); Ptr<UdpClient> clientApp = CreateObject<UdpClient>(); clientApp->SetAttribute("RemoteAddress", AddressValue(Ipv4Address("10.1.1.2"))); // 宛先のIPアドレスを設定する. clientApp->SetAttribute("RemotePort", UintegerValue(port)); // 宛先のポート番号を設定する. clientApp->SetAttribute("PacketSize", UintegerValue(200)); // 送信データサイズを200bytesと設定する. clientApp->SetAttribute("MaxPackets", UintegerValue(1)); // 送信回数を1回と設定する. clientApp->SetAttribute("StartTime", TimeValue(Seconds(10.0))); // 送信開始時間を設定する. node1->AddApplication(clientApp); Simulator::Stop(Seconds(30.0)); Simulator::Run(); Simulator::Destroy(); }
5.3. Helper APIを用いたシナリオプログラミング
Helper APIを利用すれば,ぐっとコード量が減り,わかりやすくなります.
#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" using namespace ns3; int main() { LogComponentEnable("UdpServer", LOG_LEVEL_INFO); LogComponentEnable("UdpClient", LOG_LEVEL_INFO); LogComponentEnableAll(LOG_PREFIX_ALL); // (1) Nodeを作成する. NodeContainer nodes(2); // (2, 3) Node間にP2Pリンクを張る. PointToPointHelper p2p; p2p.SetDeviceAttribute("DataRate", StringValue("5Mbps")); p2p.SetChannelAttribute("Delay", StringValue("2ms")); NetDeviceContainer devices = p2p.Install(nodes); // 各Nodeにプロトコルスタックを積む. InternetStackHelper internet; internet.Install(nodes); // 各NetDeviceにIPアドレスを割り当てる. Ipv4AddressHelper ipv4; ipv4.SetBase("10.1.1.0", "255.255.255.0"); Ipv4InterfaceContainer ifs = ipv4.Assign(devices); // (4) Applicationを作成する. uint16_t port = 12345; UdpServerHelper server(port); server.SetAttribute("StartTime", TimeValue(Seconds(5.0))); server.SetAttribute("StopTime", TimeValue(Seconds(25.0))); ApplicationContainer serverApp = server.Install(nodes.Get(1)); UdpClientHelper client(Ipv4Address("10.1.1.2"), port); client.SetAttribute("PacketSize", UintegerValue(200)); client.SetAttribute("MaxPackets", UintegerValue(1)); client.SetAttribute("StartTime", TimeValue(Seconds(10.0))); ApplicationContainer clientApp = client.Install(nodes.Get(0)); Simulator::Stop(Seconds(30.0)); Simulator::Run(); Simulator::Destroy(); }
5.4. シミュレーション結果
ログの出力結果は次のようになりました.クライアントがデータを送信した2.368ms後にサーバが受信しています.
+10.000000000s 0 UdpClient:Send(): [INFO ] TraceDelay TX 200 bytes to 10.1.1.2 Uid: 0 Time: +10s +10.002368000s 1 UdpServer:HandleRead(): [INFO ] TraceDelay: RX 200 bytes from 10.1.1.1 Sequence Number: 0 Uid: 0 TXtime: +1e+10ns RXtime: +1.00024e+10ns Delay: +2.368e+06ns
今回送受信されたパケットサイズは230bytes (ペイロード:200bytes,UDPヘッダ:8bytes,IPv4ヘッダ:20bytes,PPPのヘッダ:2bytes) ,データレートは5Mbpsなので,伝送遅延は です. さらにChannelによる伝搬遅延は2ms. よって全体の遅延は2.368msとなります.
参考文献
書籍
- 銭 飛著,ns3によるネットワークシミュレーション,森北出版 (2014).
Webサイト
- ns3, Conceptual Overview.
- ns3, Object model.
- ns3, Configuration and Attributes.
- ns3, Helper.
- ns3, Node and NetDevices Overview.
- dorapon2000,ns-3.30の使い方,Qiita.
※ 記事中のアイコンはwww.flaticon.comを利用しています. Icons made by srip from www.flaticon.com.