TRexで遊ぶ

# TRex
[TRex](https://trex-tgn.cisco.com/)はCisco Systemsが提供する Open Source な Traffic Generator. ライセンスは[Apache 2.0](https://github.com/cisco-system-traffic-generator/trex-core/blob/master/LICENSE) .

Networkの検証での切り替わり断時間などの評価を簡易でやるのならまずはPingになるけれど、
- そもそも大量のTrafficを印加できない
- そもそもL3までしか見れない
- そもそもICMP。Statefulな動きは見えない。
- 片方向の断を測定できない
- 通信断時間の測定が難しい（大体秒間10回くらいが限界）
- 遅延測定もラウンドトリップの時間になる
といった限界があり、プロトコルの動作を追いかけるような検証をやろうとすると物足りない。で、Traffic GeneratorというとIXIA や Spirent が有名だけれども、なにせ高い。あっても、ほかの人とリソースの奪い合いになる。そもそも個人では遊べない。
自分の好きなようにTrafficを印加して<del>遊びたい</del>検証したい、と思って調べて目についたのが[TRex](https://trex-tgn.cisco.com/)。 NATへのTraffic印加を一つの目標として調べてみる。

---

# 環境構築
- ベース環境は[GNS3](https://www.gns3.com/)で、検証実施時に最新版のVersion 2.1.21 を使用。今回利用したGNS3 の VM は ESXi 6.7に構築したUbuntu 18.04 LTS. その上でKVMを使ってUbuntu 16.04を動かし、そこでTrexを動作させる。屋上屋を立てている感じ。オーバーヘッドは酷い。でも、負荷試験をしたいわけではない。Traffic性能の劣化は、やむをえないものとして割り切った。
- TRexサーバにはUbuntu16.04.6 LTSを使用。 18.10で試したら起動しなかった。Ubuntuでは16.04では一部動かない機能があり、14.04が推奨とのこと。ただ、検証した時点でUbuntu 14.04 LTSはすでにサポート終了で選択肢としてあり得ないため、16.04で検証を実施（CentOSでやればよいのだけれど、使い慣れたUbuntuで構築することにした）。

## TRexサーバ
- TRexサーバとするUbuntuは[ここからダウンロード](https://docs.gns3.com/appliances/ubuntu-gui.html) 。
- TRexは[Gitで公開](https://github.com/cisco-system-traffic-generator/trex-core)されている。検証開始時のLatestであったVersion 2.60を使用

### 物理構成
TRexサーバに割り当てるCPUコアとNICへの条件。

|ポイント|最低条件|
|---|---|
|CPU|3コア以上|
|NIC|3枚以上|

<dl>
<dt>CPU</dt>
<dd>2コア以下だと、TRexの初期設定で怒られる。<br /> 1 Interfaceに対して最低1コアを割り当てる必要がある。</dd>
<dt>NIC</dt>
<dd>TypeにはGNS上の設定画面からParavirtualized Network I/O (vitio-network-pci)を選択。Traffic印加するInterfaceはDPDKでTRexに占有され、SSH接続などが使えなくなる。このため、O&M用とTraffic印加用で分けておく必要がある。<br />O&M用NICはInternetに接続し、Local PC側からSSHでログインできるようにする。</dd>
</dl>

## インストール
### 必要なツールの用意
```console
sudo apt install linux-headers-`uname -r`
sudo apt install build-essential
```
### コードのダウンロードとインストール
TRexはパッケージがあるわけではない。ダウンロードしてセットアップする必要がある。[ここ](http://trex-tgn.cisco.com/trex/release/)から最新版をダウンロードする。

```console
sudo mkdir -p /opt/trex
cd /opt/trex
sudo wget --no-cache https://trex-tgn.cisco.com/trex/release/latest
tar xvf ./latest.tar
ls -l
```

# 初期設定～トラフィック印加
## 初期状態の確認
まずは利用できるPortを確認する。

```console
sudo ./dpdk_setup_ports.py -s

Network devices using DPDK-compatible driver
============================================
<none>

Network devices using kernel driver
===================================
0000:00:03.0 'Virtio network device' if=ens3 drv=virtio-pci unused=igb_uio,vfio-pci,uio_pci_generic *Active*
0000:00:04.0 'Virtio network device' if=ens4 drv=virtio-pci unused=igb_uio,vfio-pci,uio_pci_generic
0000:00:05.0 'Virtio network device' if=ens5 drv=virtio-pci unused=igb_uio,vfio-pci,uio_pci_generic
0000:00:06.0 'Virtio network device' if=ens6 drv=virtio-pci unused=igb_uio,vfio-pci,uio_pci_generic

Other network devices
=====================
<none>
```
初期状態であるため、すべての Interface はカーネルドライバで動いている。
Activeが付いている Interface は SSH で利用されていることを示す。この Port はTRexでのTraffic印加には利用できない。
ここでは、ens5 (PCI 00:05) と ens6 (PCI 00:06)を利用することにする。

## 基本設定ファイルの作成
TRexでの基本設定はデフォルトでは、**/etc/trex_cfg.yaml** が利用される。
このファイルは対話的に初期設定可能

```console
sudo ./dpdk_setup_ports.py -i
By default, IP based configuration file will be created. Do you want to use MAC based config? (y/N) N
+----+------+---------+-------------------+-----------------------+------------+----------+----------+
| ID | NUMA |   PCI   |        MAC        |         Name          |   Driver   | Linux IF |  Active  |
+====+======+=========+===================+=======================+============+==========+==========+
| 0  | -1   | 00:03.0 | 0c:b0:8c:22:90:00 | Virtio network device | virtio-pci | ens3     | *Active* |
+----+------+---------+-------------------+-----------------------+------------+----------+----------+
| 1  | -1   | 00:04.0 | 0c:b0:8c:22:90:01 | Virtio network device | virtio-pci | ens4     |          |
+----+------+---------+-------------------+-----------------------+------------+----------+----------+
| 2  | -1   | 00:05.0 | 0c:b0:8c:22:90:02 | Virtio network device | virtio-pci | ens5     |          |
+----+------+---------+-------------------+-----------------------+------------+----------+----------+
| 3  | -1   | 00:06.0 | 0c:b0:8c:22:90:03 | Virtio network device | virtio-pci | ens6     |          |
+----+------+---------+-------------------+-----------------------+------------+----------+----------+
Please choose even number of interfaces from the list above, either by ID , PCI or Linux IF
Stateful will use order of interfaces: Client1 Server1 Client2 Server2 etc. for flows.
Stateless can be in any order.
Enter list of interfaces separated by space (for example: 1 3) : 2 3

For interface 2, assuming loopback to it's dual interface 3.
Putting IP 1.1.1.1, default gw 2.2.2.2 Change it?(y/N).n
For interface 3, assuming loopback to it's dual interface 2.
Putting IP 2.2.2.2, default gw 1.1.1.1 Change it?(y/N).n
Print preview of generated config? (Y/n)y
### Config file generated by dpdk_setup_ports.py ###

- version: 2
  interfaces: ['00:05.0', '00:06.0']
  port_info:
      - ip: 1.1.1.1
        default_gw: 2.2.2.2
      - ip: 2.2.2.2
        default_gw: 1.1.1.1

  platform:
      master_thread_id: 0
      latency_thread_id: 1
      dual_if:
        - socket: 0
          threads: [2]


Save the config to file? (Y/n)Y
```

初期設定はこれで完了。

## トラフィック印加
印加するトラフィックのシナリオファイルのサンプルは **/opt/trex/v2.60/cap2**配下にある。
設定のyamlファイルと、テンプレートとなるPCAPファイルがあるため、これを利用して印加テストを行える。

```console
sudo ./t-rex-64 -f ./cap2/dns.yaml -d 10
（10秒経過後）

 ==================
 interface sum
 ==================
------------------------
 per core stats core id : 1
------------------------
------------------------
 per core per if stats id : 1
------------------------
 port 0, queue id :0  - client
 ----------------------------
 port 1, queue id :0  - server
 ----------------------------
 ==================
 generators
 ==================


normal
-------------
 min_delta  : 10 usec
 cnt        : 0
 high_cnt   : 0
 max_d_time : 0 usec
 sliding_average    : 0 usec
 precent    : -nan %
 histogram
 -----------
 m_total_bytes                           :       1.49 Kbytes
 m_total_pkt                             :      18.00  pkt
 m_total_open_flows                      :       9.00  flows
 m_total_pkt                             : 18
 m_total_open_flows                      : 9
 m_total_close_flows                     : 9
 m_total_bytes                           : 1530
 ---------------
port : 0
------------
 opackets                                 : 9
 obytes                                   : 693
 ipackets                                 : 0
 ibytes                                   : 0
 Tx :     300.99  bps
port : 1
------------
 opackets                                 : 9
 obytes                                   : 837
 ipackets                                 : 21
 ibytes                                   : 1904
 Tx :     363.54  bps
 Cpu Utilization : 0.1  %
 Platform_factor : 1.0
 Total-Tx        :     664.53  bps
 Total-Rx        :     664.75  bps
 Total-PPS       :       0.98  pps
 Total-CPS       :       0.49  cps

 Expected-PPS    :       2.00  pps
 Expected-CPS    :       1.00  cps
 Expected-BPS    :       1.36 Kbps

 Active-flows    :        0  Clients :      511   Socket-util : 0.0000 %
 Open-flows      :        9  Servers :      255   Socket :        0 Socket/Clients :  0.0
 drop-rate       :       0.00  bps
 summary stats
 --------------
 Total-pkt-drop       : 0 pkts
 Warning : number of rx packets exceeds 101% of tx packets!
 Total-tx-bytes       : 1530 bytes
 Total-tx-sw-bytes    : 0 bytes
 Total-rx-bytes       : 1904 byte

 Total-tx-pkt         : 18 pkts
 Total-rx-pkt         : 21 pkts
 Total-sw-tx-pkt      : 0 pkts
 Total-sw-err         : 0 pkts
 Total ARP sent       : 4 pkts
 Total ARP received   : 2 pkts
```

*-d*で指定した秒数を経過すると、その統計情報が得られる。

### t-rex-64コマンドのオプション

|オプション|意味|
|---|---|
|-f [filename]|シナリオを定義したyamlファイル|
|-c <num>|Portあたりに割り当てるCPU Core数|
|--astf| Advanced Stateful モードの有効化。<br />これを有効にする際は、Profileがpython形式で書かれる必要がある|
|-d <sec>|シナリオの実行期間。デフォルトは3600秒|
|-l <rate>|各InterfaceでのPPS|
|-m <num>|基本 Rate を何倍して Traffic を印加するか|
|--learn-mode [1-3]|NAT環境で使用。DUTで実施されたNATを学習する|

#### NATオプション
NAT環境での試験はASTFモードの使用が推奨されており、かなり前から *--learn-mode* オプションは非推奨になっていた。整理すると意味は以下。

|モード| 概要|
|---|---|
|1| TCPフロー: 最初のTCP SYNパケットに埋め込まれたACKで識別 <br />UDPフロー: フローの最初のパケットのIdentificationフィールドで識別<br />このモードはFirewall がある環境でのNATの検証のために開発された。モード2ではだいたい動かない。<br />このモードではDUTにて行われるTCPシーケンスのランダムに追随して学習する。<br />Trexは両方向の接続のシーケンスナンバーのランダム化に対応する。|
|2|特別なIPv4オプションヘッダ（0x10）を埋め込むことでフロー情報を識別する。<br />このヘッダはフローの最初のパケットにだけ入れられる。このモードはDUTが IPオプションをドロップさせる場合には動作しない（Cisco ASAとか） |
|3|モード1と同様だが、モード3ではTrexは Server → Client方向のシーケンス番号ランダム化を学習しない。<br />このモードはモード1と比較してCPS（Connections Per Second）が向上する。しかしFirewallがある場合、モード1のCPS性能が妥当なものになる。 |

NAT装置を挟んで試した場合、*--learn-mode 1* を入れることで戻りパケットがyamlファイルで指定されたものではなく、実際にNAPTされて受け取ったPacketに対して応答するようになる。

### 雑事
Traffic印加をしている最中、Wiresharkでは印加したPortをキャプチャできない。これはDPDKでKernel空間がバイパスされるため。知らなかった最初は驚いた。

### arp 解決
TRex は ARP Request を投げても反応しない。どうやっているかというと、試験トラフィックを流す際、最初にG-ARPを投げている。相手先のARP Table にARP Entry がない状態で受けてもARP Tableに乗るのだろうかと思ったのだが、VyOSで試した感じだと乗ってくれていた。