ns-3 とはオープンソースの離散事象ネットワークシミュレータです。ns-3を使ってLTE通信のシミュレーションを行っていた時、TCPを使うとスループットが出ない現象がありました。その原因と解決方法について書いていきます。
要約#
本記事の要約は以下の通りです。
- LTEモジュールのサンプルスクリプトを動かす
- スループットの表示機能を追加する
- サンプルスクリプトを改造してUDPからTCPに変更したところスループットが出なくなる
- RLCレイヤの送信バッファが溢れ、パケットがドロップして、輻輳制御が働いていたことが原因
ns-3について#
本記事で扱うns-3のバージョンは現在の最新版であるns-3.28です。ns-3のインストール方法につきましては Getting Started — Tutorial または ns-3.26で始めるネットワークシミュレーション を参照ください。
ns-3に付属しているLTEモジュールのドキュメントは以下の通りです。
LTEモジュールのサンプルスクリプト#
まず、LTEの通信を行うサンプルスクリプトを動かしてみます。サンプルスクリプトのトラフィックは以下のようになっています (EPCは省略しています)。

矢印はトラフィックを表しています。各トラフィックは100ミリ秒おきにUDPパケットを1個送信しています。また、各eNBとUEのペアの距離は0です。
それではサンプルスクリプトを./scratch/にコピーして動かしてみます。
$ cd ~/ns-allinone-3.28/ns-3.28
$ cp ./src/lte/examples/lena-simple-epc.cc ./scratch/lte-example.cc
$ ./waf --run lte-exampleeNBとUEの無線区間に関するログファイルがいくつかファイル出力されていますが、スループットは表示されません。
以降では、./scratch/lte-example.ccに手を加えていきます。
スループットの表示#
lte-example.cc(以降、スクリプト)にコードを追加して、スループットを表示します。スループットの表示にはいくつか方法がありますが、今回はns-3のモジュールの Flow Monitor を使います。
まずはFlow Monitorをインクルードします。
#include "ns3/flow-monitor-helper.h"
#include "ns3/flow-monitor-module.h"スクリプト内のSimulator::Stop(Seconds(simTime));の前に以下のコードを追加します。
FlowMonitorHelper flowmon;
Ptr<FlowMonitor> monitor = flowmon.InstallAll ();
Simulator::Stop(Seconds(simTime));Simulator::Run();の後に以下のコードを追加します。
Simulator::Run();
// Print per flow statistics
monitor->CheckForLostPackets ();
Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier> (flowmon.GetClassifier ());
FlowMonitor::FlowStatsContainer stats = monitor->GetFlowStats ();
for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator i = stats.begin (); i != stats.end (); ++i)
{
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow (i->first);
std::cout << "Flow " << i->first << " (" << t.sourceAddress << " -> " << t.destinationAddress << ")\n";
std::cout << " Tx Packets: " << i->second.txPackets << "\n";
std::cout << " Tx Bytes: " << i->second.txBytes << "\n";
std::cout << " TxOffered: " << i->second.txBytes * 8.0 / (simTime) / 1000 / 1000 << " Mbps\n";
std::cout << " Rx Packets: " << i->second.rxPackets << "\n";
std::cout << " Rx Bytes: " << i->second.rxBytes << "\n";
std::cout << " Throughput: " << i->second.rxBytes * 8.0 / (simTime) / 1000 / 1000 << " Mbps\n";
}動かしてみます。
$ ./waf --run lte-example
--- (省略) ---
Flow 1 (1.0.0.2 -> 7.0.0.2)
Tx Packets: 11
Tx Bytes: 11572
TxOffered: 0.08416 Mbps
Rx Packets: 10
Rx Bytes: 10520
Throughput: 0.0765091 Mbps
--- (省略) ---
Flow 6 (7.0.0.3 -> 1.0.0.2)
Tx Packets: 11
Tx Bytes: 11572
TxOffered: 0.08416 Mbps
Rx Packets: 10
Rx Bytes: 10520
Throughput: 0.0765091 Mbps1.0.0.2はRemoteHostです。7.0.0.2、7.0.0.3はそれぞれUE1とUE2です。ここでは省略していますが、実際の出力では、3つのトラフィックを持つUEが2つがあることが確認できます。Flow MonitorはIPレイヤを監視して、送受信パケット数を取得しています。各ノードが100ミリ秒おきに1パケットを送信していて、シミュレーションの実行時間が1.1秒なので、11パケット送信しています。受信パケット数が10の理由は、クライアントとサーバの起動時間が同時のため、サーバが1個目のパケットを取りこぼしているからです。
以下のようにしてクライアントの起動時間を遅らせると、受信パケット数が11になります。
serverApps.Start (Seconds (0.01));
clientApps.Start (Seconds (0.03));TCPアプリケーション#
トラフィック種別とトポロジを変更してみます。サンプルスクリプトでは100ミリ秒おきに1個のUDPパケットを送信していました。これを変更して、送信側の送信バッファがいっぱいになるまでTCPパケットを送信し続け、バッファに空きが出たら送信を再開するようにします。ns-3のモジュールのBulkSendApplicationを使います。
また、トラフィックの見やすさのために、eNBとUEの数を1つに減らし、RetoteHostからUEへの一方向のトラフィックにします。図に示すと以下のようになります。eNBとUEの距離は0になります。

eNBとUEの個数を1つにします。
uint16_t numberOfNodes = 1;トラフィックの設定を変更します。スクリプト中の"// Install and start applications on UEs and remote host"から"serverApps.Start (Seconds (0.01));“の間を以下のように変更します。
// Install and start applications on UEs and remote host
uint16_t dlPort = 1234;
ApplicationContainer clientApps;
ApplicationContainer serverApps;
for (uint32_t u = 0; u < ueNodes.GetN (); ++u)
{
PacketSinkHelper dlPacketSinkHelper ("ns3::TcpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), dlPort));
serverApps.Add (dlPacketSinkHelper.Install (ueNodes.Get(u)));
BulkSendHelper dlClient ("ns3::TcpSocketFactory", InetSocketAddress (ueIpIface.GetAddress (u), dlPort));
clientApps.Add (dlClient.Install (remoteHost));
}
serverApps.Start (Seconds (0.01));サーバとクライアントの起動が同時になると、SYNパケットが通らずに通信が行われないので、起動時間をずらします。
serverApps.Start (Seconds (0.01));
clientApps.Start (Seconds (0.1));スクリプトを実行します。シミュレーション時間は5.1秒に設定しています。
$ ./waf --run "lte-example simTime=5.1"
Flow 1 (1.0.0.2 -> 7.0.0.2)
Tx Packets: 1632
Tx Bytes: 958548
TxOffered: 1.5036 Mbps
Rx Packets: 1613
Rx Bytes: 947376
Throughput: 1.48608 Mbps
Flow 2 (7.0.0.2 -> 1.0.0.2)
Tx Packets: 871
Tx Bytes: 46320
TxOffered: 0.0726588 Mbps
Rx Packets: 864
Rx Bytes: 45956
Throughput: 0.0720878 Mbps※ 1.0.0.2から7.0.0.2への通信がRemoteHostからUEへのアプリケーションによる通信で、逆方向の通信はAckになります。
LTEモジュールのリソースブロック(RB)のデフォルト値は25になっているため、最大18Mbps程度のスループットが期待できますが、出力結果を見ると出ていません。
原因は以下の2点です。
- RLCレイヤの送信バッファの溢れ
- RemoteHostとEPC間のディレイ
RLCレイヤの送信バッファサイズを変更します。”// Command line arguments"の直前に以下のコードを追加します。
Config::SetDefault("ns3::LteRlcUm::MaxTxBufferSize", UintegerValue(1024 * 1024));
// Command line arguments
RemoteHostとEPC間のディレイを変更します。
p2ph.SetChannelAttribute ("Delay", TimeValue (Seconds (0.001)));スクリプトを実行します。
$ ./waf --run "simple-epc --simTime=5.1"
Flow 1 (1.0.0.2 -> 7.0.0.2)
Tx Packets: 18923
Tx Bytes: 10688960
TxOffered: 16.767 Mbps
Rx Packets: 18687
Rx Bytes: 10555856
Throughput: 16.5582 Mbps
Flow 2 (7.0.0.2 -> 1.0.0.2)
Tx Packets: 9344
Tx Bytes: 485892
TxOffered: 0.762184 Mbps
Rx Packets: 9334
Rx Bytes: 485372
Throughput: 0.761368 Mbpsスループットが出るようになりました。
最後に、帯域幅を変更してみます。ここではRB数を25から100に変更します。帯域幅でいうと5MHzから20MHzになります。RB数が100の場合、最大72Mbps程度のスループットが期待されます。
以下のようにコードを追加します。
Config::SetDefault("ns3::LteEnbNetDevice::UlBandwidth", UintegerValue(100));
Config::SetDefault("ns3::LteEnbNetDevice::DlBandwidth", UintegerValue(100));
Config::SetDefault("ns3::LteRlcUm::MaxTxBufferSize", UintegerValue(1024 * 1024));スクリプトを実行します。
$ ./waf --run "simple-epc --simTime=5.1"
Flow 1 (1.0.0.2 -> 7.0.0.2)
Tx Packets: 79477
Tx Bytes: 44841416
TxOffered: 70.3395 Mbps
Rx Packets: 79305
Rx Bytes: 44744408
Throughput: 70.1873 Mbps
Flow 2 (7.0.0.2 -> 1.0.0.2)
Tx Packets: 39653
Tx Bytes: 2061960
TxOffered: 3.23445 Mbps
Rx Packets: 39611
Rx Bytes: 2059776
Throughput: 3.23102 Mbps70Mbps程度の速度が出ていることが確認できます。
LTEでは、1つのUEが1つのeNBを独占する形、かつ通信品質が良好の場合、およそ70Mbps出ることがわかります。
おわりに#
LTEのシミュレーションでTCPのスループットが出ない現象について紹介しました。期待した数値が出ないときには、LTE固有のレイヤでのバッファ溢れや、ノード間の遅延が原因であったりします。LTEモジュールの解析には各レイヤを注意深く見る必要があります。