1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

トラフィックジェネレータ Ixia-c を試してみた(第2回)

Last updated at Posted at 2025-04-29

無料でも使えるトラフィックジェネレータ Ixia-c について、前回は「同じIPセグメント間でトラフィックを流すケース」について書いてみました。
今回は、ルーターなどを挟んで、テスターとして Ixia-c を利用して、異なるIPセグメント間でトラフィックを流すに方法について書いていきます。

image.png

前回の第1回の記事はこちらです。

Ixia-c とは

(第1回と同じ内容書いてます)
コンテナベースのネットワークテストツールで、Open Traffic Generator(OTG)APIに準拠したトラフィックジェネレーターおよびプロトコルエミュレーターです。

Ixia-cには無償で利用できるCommunity Editionがあり、今回のような単純なトラフィックジェネレータとしての確認なら無償版でも十分でした。
Community Editionと他の商用ライセンスとの違いは以下をご参照ください。
https://ixia-c.dev/licensing/

実施した環境

今回は以下の Examples の内容を参考に試してみました。

上記リンクのサンプルでは、サーバー内部にFRRoutingのコンテナを立ててルーターとして動作させていますが、
今回はサーバー外部のネットワーク機器と通信させるために、
ルーターとして VyOS を使用し、ルーターを通過するトラフィックの動作確認を行いました。

image.png

各ソフトウェアのバージョンは以下の通りです。

  • Ubuntu 24.04.1 LTS
  • Python 3.12.3
  • Ixia-c
    • keng-controller 1.28.0-6
    • ixia-c-traffic-engine 1.8.0.245
    • ixia-c-protocol-engine 1.00.0.448

L3ネットワーク構成とルーター設定

以下のようなL3ネットワーク構成でトラフィックを流すため、まずはVyOS側のコンフィグ設定を行います。
image.png

今回の構成ではVyOS側は特にルーティングの追加は必要ないため、インターフェースにIPアドレスだけ設定します。

VyOSのIPアドレス設定内容
set interfaces ethernet eth1 address '192.168.101.254/24'
set interfaces ethernet eth2 address '192.168.102.254/24'

あとはサーバー側の設定になります。

セットアップ

セットアップのイメージ

以下のようなイメージで動作させるため、Ixia-c のセットアップを行います。
第1回のときと異なり、ゲートウェイアドレス(GWアドレス)向けのARP処理が必要になるため、
前回使用した controller / traffic-engine に加えて、今回は protocol-engine も必要になります。

image.png

サーバーのポート設定

ポートの構成

以下の図のように今回は、前回と異なり複雑になっています。

image.png

複雑になってしまった理由は以下通りです

  • 前回は traffic-engine のみを利用し、environment:OPT_LISTEN_PORT=5556を設定して controller の待ち受けポートを変更。その結果、2台の traffic-engine で待ち受けるポート番号を別にできるため、controller と同様にnetwork_mode: "host"を利用できた
  • 今回は protocol-engine を利用するが
    • 待ち受けポートが50071に固定されており、2台起動するとポートが重複
    • 2台目は別ポートで待ち受けるように50072 → 50071といったポートフォワーディング設定が必要
    • そのため、network_mode: "host"は使えなくなる
  • network_mode: "host"を外すと
    • environment:ARG_IFACE_LIST=virtual@af_packet,ens4のように物理インターフェースを直接指定できず、コンテナ内からens4が認識されなくなるため、コンテナにvethを追加して物理IFensとブリッジして接続

もし、protocol-engine の待ち受けポート(50071)からを変更できれば、前回同様にnetwork_mode: "host"構成も可能だと思いましたが、変更方法が不明だったため、今回はこの構成で無理やり対応しました。

設定

設定については、再起動後も残るようにnetplanに以下の設定を行います

  • 前回と同様にens4ens5を測定ポートとして設定を入れる
  • ensvethをブリッジするbr1br2を作成して、それぞれにensを入れる
  • dhcpを無効と合わせてIPv6のリンクローカルを無効
  • ens3は利用環境に合わせてIPアドレス等を変更してください
  • コンテナのveth(仮想IF)については、コンテナ起動後に設定するのでnetplanには書かない

以下のように/etc/netplan/99-custom.yamlを作成

/etc/netplan/99-custom.yaml
network:
  version: 2
  renderer: networkd

  # 物理インターフェースを定義
  ethernets:
    ens3:
      dhcp4: false
      addresses:
        - 192.168.1.101/24
      nameservers:
        addresses:
          - 1.1.1.1
          - 8.8.8.8
        search: []
      routes:
      - to: default
        via: 192.168.1.1

    ens4:                  # 測定用ポート1
      dhcp4: false
      dhcp6: false
      link-local: []       # IPv4/IPv6 のリンクローカルを無効化

    ens5:                  # 測定用ポート2
      dhcp4: false
      dhcp6: false
      link-local: []       # IPv4/IPv6 のリンクローカルを無効化

  # ブリッジ br を定義し、メンバーにする
  bridges:
    br1:
      interfaces: [ens4]   # 測定用ポート1 をbr1 に入れる
      dhcp4: false
      dhcp6: false
      link-local: []       # IPv4/IPv6 のリンクローカルを無効化
    br2:
      interfaces: [ens5]   # 測定用ポート2 をbr2 に入れる
      dhcp4: false
      dhcp6: false
      link-local: []       # IPv4/IPv6 のリンクローカルを無効化

上記ファイルを作成したら、以下の設定で反映されます。

netplan apply

netplan apply実行時の注意

/etc/netplan/99-custom.yaml で現在利用している環境から IP アドレス等を変更すると、ログインセッションが切れてしまう可能性があるため注意してください。
netplan get で現在の状態が .yaml ファイル形式で出力されるので、それを参考にしながら 99-custom.yaml を作成することをおすすめします。

vethを追加するツール「add_veth_to_docker」を入れる

事前に、動作中のDockerコンテナに対して、新しいvethインターフェースを簡単に追加できるBashスクリプトの「add_veth_to_docker」を入れておきます。
https://github.com/cslev/add_veth_to_docker

以下のコマンドを入れればOKです。(gitコマンドがない場合は apt-get install git でインストールしてから実施してください)

git clone --depth=1 https://github.com/cslev/add_veth_to_docker.git /opt/add_veth_to_docker

後から実施しますがこれで以下のように動作してるコンテナに対してvethのIFを追加できます。

/opt/add_veth_to_docker/add_veth2container.sh <コンテナ名> <ホスト側に作られるveth-IF名> <コンテナ側に作られるveth-IF名>

Ixia-c のセットアップ

docker-compose でのセットアップになります。サーバーにDocker関連のインストールされていない場合は以下のリンクを参照にインストールしてください。

ディレクトリを作ってその中で各ファイルを作成します。

mkdir ixia-c-l3
cd ixia-c-l3/

compose.yaml の作成

前回と同様にcompose.yamlを作成。
内容については前回と異なり、protocol-engine のコンテナも入ってます。また protocol-engine は traffic-engine とネットワークを共用利用の設定network_mode: service:traffic_engine_1が入ってます。(こちらの内容を参考に作成しました)

compose.yaml
services:
  controller:
    image: ghcr.io/open-traffic-generator/keng-controller:1.28.0-6
    container_name: ixia-c-controller
    command: --accept-eula --http-port 8443
    network_mode: "host"
#    restart: always      # サーバー再起動でも自動で起動させたい場合はコメント外す

  traffic_engine_1:
    image: ghcr.io/open-traffic-generator/ixia-c-traffic-engine:1.8.0.245
    container_name: ixia-c-traffic_engine_1
    privileged: true
#    restart: always      # サーバー再起動でも自動で起動させたい場合はコメント外す
    ports:
      - "5555:5555"
      - "50071:50071"
    environment:
      - OPT_LISTEN_PORT=5555
      - ARG_IFACE_LIST=virtual@af_packet,veth21  # コンテナ内に後で追加する veth のIF名を指定
      - OPT_NO_HUGEPAGES=Yes
      - OPT_NO_PINNING=Yes
      - WAIT_FOR_IFACE=Yes

  traffic_engine_2:
    image: ghcr.io/open-traffic-generator/ixia-c-traffic-engine:1.8.0.245
    container_name: ixia-c-traffic_engine_2
    privileged: true
#    restart: always      # サーバー再起動でも自動で起動させたい場合はコメント外す
    ports:
      - "5556:5556"
      - "50072:50071"    # 50072 → 50071 に変換
    environment:
      - OPT_LISTEN_PORT=5556
      - ARG_IFACE_LIST=virtual@af_packet,veth22  # コンテナ内に後で追加する veth のIF名を指定
      - OPT_NO_HUGEPAGES=Yes
      - OPT_NO_PINNING=Yes
      - WAIT_FOR_IFACE=Yes

  protocol_engine_1:
    image: ghcr.io/open-traffic-generator/ixia-c-protocol-engine:1.00.0.448
    container_name: ixia-c-protocol_engine_1
    privileged: true
#    restart: always      # サーバー再起動でも自動で起動させたい場合はコメント外す
    network_mode: service:traffic_engine_1
    environment:
      - INTF_LIST=veth21  # コンテナ内に後で追加する veth のIF名を指定

  protocol_engine_2:
    image: ghcr.io/open-traffic-generator/ixia-c-protocol-engine:1.00.0.448
    container_name: ixia-c-protocol_engine_2
    privileged: true
#    restart: always      # サーバー再起動でも自動で起動させたい場合はコメント外す
    network_mode: service:traffic_engine_2
    environment:
      - INTF_LIST=veth22  # コンテナ内に後で追加する veth のIF名を指定

Docker Composeでビルド&起動

以下のコマンドで Ixia-c のビルドおよび起動が行われます。

docker compose up -d

初回実行時には、Docker イメージのダウンロードとビルドが行われるため、少し時間がかかりますが、2回目以降はダウンロードはスキップされ、すぐに起動されます。
(compose.yamlrestart: alwaysのコメントを外すとサーバーの再起動時などは自動で起動されます)

コンテナ起動後は、この後にある手順を早めに実施してください。
実施せずに放置すると、コンテナがcompose.yamlで指定されて veth が見つけられず停止してしまいます。

コンテナに仮想インターフェース(veth)を追加して、ブリッジ接続

次に下の画像のように仮想インターフェースとブリッジ接続を次に実施します

image.png

① コンテナとホストに仮想IF(veth)のペアを作成は以下のコマンドで実施

# traffic_engine_1 側の設定
/opt/add_veth_to_docker/add_veth2container.sh ixia-c-traffic_engine_1 veth11 veth21
# traffic_engine_2 側の設定
/opt/add_veth_to_docker/add_veth2container.sh ixia-c-traffic_engine_2 veth12 veth22

② ホスト側に作成した仮想インターフェース(veth)をブリッジ(br)に接続し、ブリッジを介して仮想インターフェース(veth)と物理インターフェースを連携させる。

# traffic_engine_1 側の設定
ip link set veth11 master br1
# traffic_engine_2 側の設定
ip link set veth12 master br2

ブリッジにどのインターフェースがいるかの確認はip link show master <br-IF>で確認できます。
下記の内容は、br1を確認した例です。

# ip link show master br1
3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br1 state UP mode DEFAULT group default qlen 1000
    link/ether 50:63:17:00:48:01 brd ff:ff:ff:ff:ff:ff
    altname enp0s4
17: veth11@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br1 state UP mode DEFAULT group default qlen 1000
    link/ether 96:ae:a3:ae:89:c5 brd ff:ff:ff:ff:ff:ff link-netns ixia-c-traffic_engine_1

ここまでで Ixia-c の準備は完了になります。

トラフィックを流す

次にトラフィック送信の設定について説明します。前回は snappi モジュールを使った Python スクリプトでトラフィックを流しましたが、今回は試行錯誤したものの上手くいかなかったため、以下の手順で動作を確認しました。

  • 設定内容を JSON 形式 で作成
  • curl コマンドで Open Traffic Generator(OTG)の REST API を呼び出し制御

最終的には、JSON を読み込んで API を叩く Python スクリプトを作成して対応しています。
curl を使った操作方法については、こちらのリポジトリを参考にしました

JSONつくる

まず、デバイス設定やトラフィック送信の条件を記載した JSON ファイルを作成します。
大きな変更点は以下のとおりです。

  • devices セクションで各ポートの MAC アドレスや IP アドレスを設定します。IP アドレスはゲートウェイアドレスを指定し、読み込み時にそのゲートウェイに対して ARP 解決を行います。
  • flow セクションでは宛先 MAC アドレスを auto に設定し、上記の ARP 解決で取得した MAC アドレスを利用します。

今回作成したotg.jsonは以下となります。(3ppsを10秒間流す設定です)

otg.json
otg.json
{
  "ports": [
    {
      "location": "localhost:5555+localhost:50071",
      "name": "port_1"
    },
    {
      "location": "localhost:5556+localhost:50072",
      "name": "port_2"
    }
  ],
  "layer1": [
    {
      "name": "layer_1",
      "port_names": [
        "port_1",
        "port_2"
      ],
      "speed": "speed_1_gbps",
      "mtu": 1500
    }
  ],
  "devices": [
    {
      "name": "device_1",
      "ethernets": [
        {
          "name": "device_1.port_1",
          "mac": "00:00:00:00:00:aa",
          "connection": {
            "choice": "port_name",
            "port_name": "port_1"
          },
          "ipv4_addresses": [
            {
              "name": "device_1.ipv4",
              "address": "192.168.101.11",
              "prefix": 24,
              "gateway": "192.168.101.254"
            }
          ]
        }
      ]
    },
    {
      "name": "device_2",
      "ethernets": [
        {
          "name": "device_2.port_2",
          "mac": "00:00:00:00:00:bb",
          "connection": {
            "choice": "port_name",
            "port_name": "port_2"
          },
          "ipv4_addresses": [
            {
              "name": "device_2.ipv4",
              "address": "192.168.102.11",
              "prefix": 24,
              "gateway": "192.168.102.254"
            }
          ]
        }
      ]
    }
  ],
  "flows": [
    {
      "name": "flow_port_1_port_2",
      "tx_rx": {
        "choice": "device",
        "device": {
          "mode": "one_to_one",
          "tx_names": [
            "device_1.ipv4"
          ],
          "rx_names": [
            "device_2.ipv4"
          ]
        }
      },
      "packet": [
        {
          "choice":  "ethernet",
          "ethernet":  {
            "src":  {
              "choice":  "value",
              "value":  "00:00:00:00:00:aa"
            },
            "dst":  {
              "choice":  "auto",
              "auto":  "00:00:00:00:00:00"
            }
          }
        },
        {
          "choice":  "ipv4",
          "ipv4":  {
            "version":  {
              "choice":  "value",
              "value":  4
            },
            "src":  {
              "choice":  "value",
              "value" : "192.168.101.11"               
            },
            "dst":  {
              "choice":  "value",
              "value" : "192.168.102.11"               
            }
          }
        },
        {
          "choice":  "udp",
          "udp": {
            "src_port": {
              "choice":  "value",
              "value": 80
            },
            "dst_port": {
              "choice":  "value",
              "value": 80
            }
          }
        }
      ],
      "size": {
        "fixed": 256
      },
      "rate": {
        "pps": 3
      },
      "duration": {
        "fixed_seconds": {
          "seconds": 10
        }
      },
      "metrics": {
        "enable": true,
        "loss": false,
        "timestamps": false
      }
    }
  ]
}

curlで動作確認する

① JSON ファイルの設定反映と GW に対して ARP 解決

OTG_HOST="https://localhost:8443"

curl -k "${OTG_HOST}/config" -H "Content-Type: application/json" -d @otg.json

② ARPで取得したMACアドレス確認

curl -sk "${OTG_HOST}/monitor/states" -X POST -H 'Content-Type: application/json' -d '{ "choice": "ipv4_neighbors" }'

正常に取得できた場合は以下のようにlink_layer_addressに ARP で確認できた MAC アドレスが表示されます

# curl -sk "${OTG_HOST}/monitor/states" -X POST -H 'Content-Type: application/json' -d '{ "choice": "ipv4_neighbors" }''
{
  "choice": "ipv4_neighbors",
  "ipv4_neighbors": [
    {
      "ethernet_name": "device_2.port_2",
      "ipv4_address": "192.168.102.254",
      "link_layer_address": "50:9c:23:00:49:02"
    },
    {
      "ethernet_name": "device_1.port_1",
      "ipv4_address": "192.168.101.254",
      "link_layer_address": "50:9c:23:00:49:01"
    }
  ]
}

③ トラフィックを流す
"warnings": []と表示されたらトラフィックが流れます

curl -sk "${OTG_HOST}/control/state" -H "Content-Type: application/json" -d '{"choice": "traffic", "traffic": {"choice": "flow_transmit", "flow_transmit": {"state": "start"}}}'

④ カウンター確認

curl -sk "${OTG_HOST}/monitor/metrics" -X POST -H 'Content-Type: application/json' -d '{ "choice": "flow" }'

以下が測定中に取得した表示の例です

# curl -sk "${OTG_HOST}/monitor/metrics" -X POST -H 'Content-Type: application/json' -d '{ "choice": "flow" }''
{
  "choice": "flow_metrics",
  "flow_metrics": [
    {
      "name": "flow_port_1_port_2",
      "transmit": "started",
      "frames_tx": "8",
      "frames_rx": "8",
      "bytes_tx": "2048",
      "bytes_rx": "2048",
      "frames_tx_rate": 0,
      "frames_rx_rate": 0
    }
  ]
}

パケットキャプチャをしてみて ① の時にARP処理して、③ のトラフィックを流すときにARPで取得したMACアドレス向けにトラフィックを流してルーターを経由したトラフィックが流せていることが確認できます。
image.png

Pythonスクリプトにする

curlコマンドをいちいち手動で実行するのも面倒だったので、一連の手順を ChatGPTさんに相談しつつ Python スクリプトにまとめました。

以下のPythonスクリプトを作成

run_otg_traffic.py
run_otg_traffic.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import json
import urllib3
import time
import sys

# 証明書警告を抑制
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

OTG_HOST = "https://localhost:8443"
HEADERS = {"Content-Type": "application/json"}

def post_config(config_file):
    with open(config_file, 'r') as f:
        cfg = json.load(f)
    r = requests.post(f"{OTG_HOST}/config", json=cfg, headers=HEADERS, verify=False)
    print("POST /config →", r.status_code, r.json())

def get_ipv4_neighbors():
    payload = {"choice": "ipv4_neighbors"}
    r = requests.post(f"{OTG_HOST}/monitor/states", json=payload, headers=HEADERS, verify=False)
    data = r.json()
    neighbors = data.get("ipv4_neighbors", [])
    for nbr in sorted(neighbors, key=lambda x: x["ethernet_name"]):
        print("----")
        print(f"ethernet_name     : {nbr['ethernet_name']}")
        print(f"ipv4_address      : {nbr['ipv4_address']}")
        print(f"link_layer_address: {nbr['link_layer_address']}")
    print("----")

def start_traffic():
    payload = {
        "choice": "traffic",
        "traffic": {
            "choice": "flow_transmit",
            "flow_transmit": {"state": "start"}
        }
    }
    r = requests.post(f"{OTG_HOST}/control/state", json=payload, headers=HEADERS, verify=False)
    print("POST /control/state →", r.status_code, r.json())

def stop_traffic():
    payload = {
        "choice": "traffic",
        "traffic": {
            "choice": "flow_transmit",
            "flow_transmit": {"state": "stop"}
        }
    }
    r = requests.post(f"{OTG_HOST}/control/state", json=payload, headers=HEADERS, verify=False)
    print("POST /control/state →", r.status_code, r.json())

def fetch_and_print_metrics(prev_lines):
    payload = {"choice": "flow"}
    r = requests.post(f"{OTG_HOST}/monitor/metrics", json=payload, headers=HEADERS, verify=False)
    data = r.json()
    flows = sorted(data.get("flow_metrics", []), key=lambda x: x["name"])

    # ブロックを組み立て
    lines = ["===== Flow metrics ====="]
    for flow in flows:
        lines += [
            "----",
            f"name           : {flow['name']}",
            f"transmit       : {flow['transmit']}",
            f"frames_tx      : {flow['frames_tx']}",
            f"frames_rx      : {flow['frames_rx']}",
            f"bytes_tx       : {flow['bytes_tx']}",
            f"bytes_rx       : {flow['bytes_rx']}",
            f"frames_tx_rate : {flow['frames_tx_rate']}",
            f"frames_rx_rate : {flow['frames_rx_rate']}",
        ]
    lines.append("----")

    # 前回出力分だけカーソルを上に戻す
    if prev_lines:
        sys.stdout.write(f"\033[{prev_lines}A")

    # 出力
    print("\n".join(lines))
    sys.stdout.flush()

    # 全てのフローが stopped なら True
    all_stopped = all(flow["transmit"] == "stopped" for flow in flows)
    return len(lines), all_stopped

if __name__ == "__main__":
    # 1) 設定を投げる
    print('===== Arp Request =====')
    post_config("otg.json")
    time.sleep(3)

    # 2) ARP解決後の情報を取得
    print('\n===== ARP resolution check =====')
    get_ipv4_neighbors()
    time.sleep(1)

    # 3) トラフィック送信開始
    print('\n===== Traffic transmission =====')
    start_traffic()
    time.sleep(1)

    # 4) メトリクスを1秒ごとに同じ場所で更新し、
    #    transmit が stopped になったら1秒後に終了メッセージを出して終了
    prev_lines = 0
    try:
        while True:
            prev_lines, stopped = fetch_and_print_metrics(prev_lines)
            if stopped:
                time.sleep(1)
                print("\n=== Traffic completed. Exiting. ===")
                break
            time.sleep(1)
    except KeyboardInterrupt:
        stop_traffic()
        print("\n=== Traffic stopped by user ===")

以下で実行すると、JSON ファイルの内容を読み込んでトラフィック送信してくれます。

python3 ./run_otg_traffic.py

実行すると以下のように表示されます。

# python3 ./run_otg_traffic.py
===== Arp Request =====
POST /config → 200 {'warnings': []}

===== ARP resolution check =====
----
ethernet_name     : device_1.port_1
ipv4_address      : 192.168.101.254
link_layer_address: 50:9c:23:00:49:01
----
ethernet_name     : device_2.port_2
ipv4_address      : 192.168.102.254
link_layer_address: 50:9c:23:00:49:02
----

===== Traffic transmission =====
POST /control/state → 200 {'warnings': []}
===== Flow metrics =====
----
name           : flow_port_1_port_2
transmit       : started
frames_tx      : 13
frames_rx      : 13
bytes_tx       : 3328
bytes_rx       : 3328
frames_tx_rate : 2
frames_rx_rate : 2
----

トラフィックの条件など変えたい場合は、JSON ファイルの内容を変更すれば変更されます。

最後に

2回に分けてトラフィックジェネレータの Ixia-c を試してみました。

  • 第1回の内容は、L2SW などにトラフィックを流す
  • 第2回の内容は、ルーターや L3SW などにトラフィックを流す

の用途で利用できると思いました。

Ixia-c 以外にても TRex も、Open Traffic Generator(OTG)で制御できるみたいなんでやってみたい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?