無料でも使えるトラフィックジェネレータ Ixia-c について、前回は「同じIPセグメント間でトラフィックを流すケース」について書いてみました。
今回は、ルーターなどを挟んで、テスターとして Ixia-c を利用して、異なるIPセグメント間でトラフィックを流すに方法について書いていきます。
前回の第1回の記事はこちらです。
Ixia-c とは
(第1回と同じ内容書いてます)
コンテナベースのネットワークテストツールで、Open Traffic Generator(OTG)APIに準拠したトラフィックジェネレーターおよびプロトコルエミュレーターです。
Ixia-cには無償で利用できるCommunity Editionがあり、今回のような単純なトラフィックジェネレータとしての確認なら無償版でも十分でした。
Community Editionと他の商用ライセンスとの違いは以下をご参照ください。
https://ixia-c.dev/licensing/
実施した環境
今回は以下の Examples の内容を参考に試してみました。
上記リンクのサンプルでは、サーバー内部にFRRoutingのコンテナを立ててルーターとして動作させていますが、
今回はサーバー外部のネットワーク機器と通信させるために、
ルーターとして VyOS を使用し、ルーターを通過するトラフィックの動作確認を行いました。
各ソフトウェアのバージョンは以下の通りです。
- 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側のコンフィグ設定を行います。
今回の構成では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 も必要になります。
サーバーのポート設定
ポートの構成
以下の図のように今回は、前回と異なり複雑になっています。
複雑になってしまった理由は以下通りです
- 前回は 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
に以下の設定を行います
- 前回と同様に
ens4
とens5
を測定ポートとして設定を入れる -
ens
とveth
をブリッジするbr1
とbr2
を作成して、それぞれにens
を入れる - dhcpを無効と合わせてIPv6のリンクローカルを無効
-
ens3
は利用環境に合わせてIPアドレス等を変更してください - コンテナの
veth
(仮想IF)については、コンテナ起動後に設定するのでnetplan
には書かない
以下のように/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
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
が入ってます。(こちらの内容を参考に作成しました)
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.yaml
にrestart: always
のコメントを外すとサーバーの再起動時などは自動で起動されます)
コンテナ起動後は、この後にある手順を早めに実施してください。
実施せずに放置すると、コンテナがcompose.yaml
で指定されて veth が見つけられず停止してしまいます。
コンテナに仮想インターフェース(veth)を追加して、ブリッジ接続
次に下の画像のように仮想インターフェースとブリッジ接続を次に実施します
① コンテナとホストに仮想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
{
"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アドレス向けにトラフィックを流してルーターを経由したトラフィックが流せていることが確認できます。
Pythonスクリプトにする
curlコマンドをいちいち手動で実行するのも面倒だったので、一連の手順を ChatGPTさんに相談しつつ Python スクリプトにまとめました。
以下のPythonスクリプトを作成
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)で制御できるみたいなんでやってみたい。