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-example
eNBと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 Mbps
1.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 Mbps
70Mbps程度の速度が出ていることが確認できます。
LTEでは、1つのUEが1つのeNBを独占する形、かつ通信品質が良好の場合、およそ70Mbps出ることがわかります。
おわりに
LTEのシミュレーションでTCPのスループットが出ない現象について紹介しました。期待した数値が出ないときには、LTE固有のレイヤでのバッファ溢れや、ノード間の遅延が原因であったりします。LTEモジュールの解析には各レイヤを注意深く見る必要があります。