はじめに
今回はContainerlab上でixia-cとDUTを接続し、トラフィックを印加した試験を試してみる。
構成としては非常にシンプルでixia-cの間にDUTを挟み込む構成とした。
DUTはcEOS(4.34.6M)を使う。
labのデプロイ
トポロジー定義ファイル(YAML)は、eth1とeth2の間にDUTを挟み込んだ構成とした。
前回はixia-cのトポロジーのみ作成していたが、今回はDUTであるcEOSのトポロジーを追加している。
また、Linkの設定でixia-cとcEOSが接続するように設定を加えている。
トポロジー設定
name: ixia-c-one-dut-test
topology:
nodes:
ixia-c:
kind: keysight_ixia-c-one
image: ghcr.io/open-traffic-generator/ixia-c-one:1.55.0-1
ceos:
kind: arista_ceos
image: ceos:4.34.6M
startup-config: ceos.cfg
links:
- endpoints: ["ixia-c:eth1", "ceos:eth1"]
- endpoints: ["ixia-c:eth2", "ceos:eth2"]
topologyの項目に記載するcEOSの今回のstartup-config(ceos.cfg)は以下になる。
hostname ceos
service routing protocols model multi-agent
interface Ethernet1
no switchport
ip address 10.1.0.254/24
no shutdown
interface Ethernet2
no switchport
ip address 10.2.0.254/24
no shutdown
ip routing
デプロイ実施
containerlab deploy --topo ixia-c-one-dut-test.yml
23:46:23 INFO Containerlab started version=0.76.1
23:46:23 INFO Parsing & checking topology file=ixia-c-one-dut-test.yml
23:46:23 INFO Creating docker network name=clab IPv4 subnet=172.20.20.0/24 IPv6 subnet=3fff:172:20:20::/64 MTU=0
23:46:23 INFO Creating lab directory path=/home/test/lab/containerlab/clab-ixia-c-one-dut-test
23:46:23 INFO Creating container name=ixia-c
23:46:24 INFO Creating container name=ceos
23:46:24 INFO Created link: ixia-c:eth1 ▪┄┄▪ ceos:eth1
23:46:24 INFO Running postdeploy actions for keysight_ixia-c-one 'ixia-c' node
23:46:24 INFO Created link: ixia-c:eth2 ▪┄┄▪ ceos:eth2
23:46:24 INFO Running postdeploy actions for Arista cEOS 'ceos' node
23:47:05 INFO Adding host entries path=/etc/hosts
23:47:05 INFO Adding SSH config for nodes path=/etc/ssh/ssh_config.d/clab-ixia-c-one-dut-test.conf
23:47:05 INFO containerlab version
🎉=
│ A newer containerlab version (0.77.0) is available!
│ Release notes: https://containerlab.dev/rn/0.77/
│ Run 'clab version upgrade' or see https://containerlab.dev/install/ for other installation options.
╭─────────────────────────────────┬────────────────────────────────────────────────────┬─────────┬────────── ─────────╮
│ Name │ Kind/Image │ State │ IPv4/6 Address │
├─────────────────────────────────┼────────────────────────────────────────────────────┼─────────┼────────── ─────────┤
│ clab-ixia-c-one-dut-test-ceos │ arista_ceos │ running │ 172.20.20.3 │
│ │ ceos:4.34.6M │ │ 3fff:172:20:20::3 │
├─────────────────────────────────┼────────────────────────────────────────────────────┼─────────┼────────── ─────────┤
│ clab-ixia-c-one-dut-test-ixia-c │ keysight_ixia-c-one │ running │ 172.20.20.2 │
│ │ ghcr.io/open-traffic-generator/ixia-c-one:1.55.0-1 │ │ 3fff:172:20:20::2 │
╰─────────────────────────────────┴────────────────────────────────────────────────────┴─────────┴────────── ─────────╯
ixia-cの試験コード(Port EndpointとDevice Endpointどちらを選択するか)
トラフィック印加自体はPort Endpointでも実現できる。ただし、DUTを介して異なるサブネット間で通信させる場合は、送信パケットの宛先MACアドレスをDUTのインターフェースMACアドレスに設定する必要がある。また、Port EndpointはARPへ応答するDeviceを持たないため、DUT側にもIxia-c側IPアドレスに対応する静的ARPを設定する必要がある。
一方、Device Endpointでは、Ethernet Interface、IPv4 Interface、DefaultGatewayを設定することで、Ixia-cがARP解決を行う。そのため、FlowごとにDUTのMACアドレスを手動で指定したり、DUT側へ静的ARPを設定したりする必要がなく、L3のDUTを介した試験をより自然に実行できる。
Device Endpointは送受信EndpointやARP解決を担う設定であり、Flowで使用するEthernet、IPv4、UDPなどのPacket Header設定とは別である。この点は以前実施したDevice Endpointの検証結果と同様であり、今回の DUT(cEOS)を挟んだ構成でも、Packet Headerを明示することで期待どおりにL3転送できることを確認した。
動作確認用サンプルコード
# Python標準ライブラリ(試験時間の待機処理に使用)
import time
# OTG APIを操作するPython SDK
import snappi
def test_device_endpoint_minimum():
# ------------------------------------------------------------------
# Controllerへ接続
# ------------------------------------------------------------------
api = snappi.api(
location="https://clab-ixia-c-one-dut-test-ixia-c:8443",
verify=False,
)
# ------------------------------------------------------------------
# Config作成
# ------------------------------------------------------------------
cfg = api.config()
# ------------------------------------------------------------------
# Port作成
# ------------------------------------------------------------------
p1 = cfg.ports.add(
name="p1",
location="eth1",
)
p2 = cfg.ports.add(
name="p2",
location="eth2",
)
# ------------------------------------------------------------------
# Device作成
# ------------------------------------------------------------------
dev1 = cfg.devices.add(name="Device1")
dev2 = cfg.devices.add(name="Device2")
# ------------------------------------------------------------------
# Device1: Ethernet / IPv4 Interface
# ------------------------------------------------------------------
eth1 = dev1.ethernets.add(name="Device1.Ethernet1")
eth1.connection.port_name = p1.name
eth1.mac = "00:00:00:00:00:01"
ip1 = eth1.ipv4_addresses.add(name="Device1.IPv4")
ip1.address = "10.1.0.1"
ip1.gateway = "10.1.0.254"
ip1.prefix = 24
# ------------------------------------------------------------------
# Device2: Ethernet / IPv4 Interface
# ------------------------------------------------------------------
eth2 = dev2.ethernets.add(name="Device2.Ethernet1")
eth2.connection.port_name = p2.name
eth2.mac = "00:00:00:00:00:02"
ip2 = eth2.ipv4_addresses.add(name="Device2.IPv4")
ip2.address = "10.2.0.1"
ip2.gateway = "10.2.0.254"
ip2.prefix = 24
# ------------------------------------------------------------------
# Flow1: Device1 → Device2
# ------------------------------------------------------------------
flow1 = cfg.flows.add(name="Device1_to_Device2")
# Device Endpointを使用
flow1.tx_rx.device.tx_names = [ip1.name]
flow1.tx_rx.device.rx_names = [ip2.name]
# Packet Headerを明示
eth, ipv4, udp = flow1.packet.ethernet().ipv4().udp()
# IP Address
ipv4.src.value = "10.1.0.1"
ipv4.dst.value = "10.2.0.1"
# UDP Port
udp.src_port.value = 50000
udp.dst_port.value = 50001
# Frame Size
flow1.size.fixed = 512
# Rate
flow1.rate.percentage = 10
# Duration
flow1.duration.fixed_seconds.seconds = 10
# Metrics
flow1.metrics.enable = True
# ------------------------------------------------------------------
# Flow2: Device2 → Device1
# ------------------------------------------------------------------
flow2 = cfg.flows.add(name="Device2_to_Device1")
# Device Endpointを使用
flow2.tx_rx.device.tx_names = [ip2.name]
flow2.tx_rx.device.rx_names = [ip1.name]
# Packet Headerを明示
eth, ipv4, udp = flow2.packet.ethernet().ipv4().udp()
# IP Address
ipv4.src.value = "10.2.0.1"
ipv4.dst.value = "10.1.0.1"
# UDP Port
udp.src_port.value = 50001
udp.dst_port.value = 50000
# Frame Size
flow2.size.fixed = 512
# Rate
flow2.rate.percentage = 10
# Duration
flow2.duration.fixed_seconds.seconds = 10
# Metrics
flow2.metrics.enable = True
# ------------------------------------------------------------------
# Config確認
# ------------------------------------------------------------------
print(
"\nCONFIGURATION",
cfg.serialize(encoding=cfg.JSON),
sep="\n",
)
# ------------------------------------------------------------------
# Config反映
# ------------------------------------------------------------------
api.set_config(cfg)
# ------------------------------------------------------------------
# Protocol起動
# ------------------------------------------------------------------
state = api.control_state()
state.protocol.all.state = state.protocol.all.START
api.set_control_state(state)
print("Protocol Started")
# ARP解決待ち
time.sleep(10)
# ------------------------------------------------------------------
# Traffic印加開始
# ------------------------------------------------------------------
state = api.control_state()
state.traffic.flow_transmit.state = (
state.traffic.flow_transmit.START
)
state.traffic.flow_transmit.flow_names = [
flow1.name,
flow2.name,
]
api.set_control_state(state)
print("Traffic Started")
# ------------------------------------------------------------------
# Metrics取得
# ------------------------------------------------------------------
request = api.metrics_request()
request.flow.flow_names = [
flow1.name,
flow2.name,
]
for _ in range(20):
metrics = api.get_metrics(request)
print(metrics)
if metrics.flow_metrics and all(
metric.transmit == metric.STOPPED
for metric in metrics.flow_metrics
):
break
time.sleep(1)
# ------------------------------------------------------------------
# Traffic印加停止
# ------------------------------------------------------------------
state = api.control_state()
state.traffic.flow_transmit.state = (
state.traffic.flow_transmit.STOP
)
state.traffic.flow_transmit.flow_names = [
flow1.name,
flow2.name,
]
api.set_control_state(state)
print("Traffic Stopped")
# ------------------------------------------------------------------
# Protocol停止
# ------------------------------------------------------------------
state = api.control_state()
state.protocol.all.state = state.protocol.all.STOP
api.set_control_state(state)
print("Protocol Stopped")
動作確認結果
Device Endpointの動作確認としてトラフィックを印加した。Flow Metricsでは送受信レートやフレーム数を確認できる。また、cEOS側ではARPテーブルやInterface Counterを確認することで、DUTが期待どおりにパケット転送をしていることを確認した。
$ ./do.sh pytest examples/clab-device-endpoint2.py -s
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-7.1.1, pluggy-1.6.0 -- /home/test/lab/ixia-c/conformance/.env/bin/python
cachedir: .pytest_cache
rootdir: /home/test/lab/ixia-c/conformance, configfile: pytest.ini
plugins: html-4.2.0
collecting ... collected 1 item
examples/clab-device-endpoint2.py::test_device_endpoint_minimum 2026-07-13 13:23:02.988 [snappi] [WARNING] Version check is disabled
-------------------------------- live log call ---------------------------------
WARNING snappi:snappi.py:106 Version check is disabled
2026-07-13 13:23:02.988 [snappi] [WARNING] Certificate verification is d
flow_metrics:
name: Device1_to_Device2
frames_tx: '1809955'
frames_rx: '37916'
tx_rate_bps: 773928500
rx_rate_bps: 16764332
name: Device2_to_Device1
frames_tx: '1793259'
frames_rx: '37918'
tx_rate_bps: 787894400
rx_rate_bps: 16760348
Traffic Stopped
Protocol Stopped
PASSED
※frames_tx が180万前後に対し frames_rx が3.8万程度と大きな差があるが、Discardについては後述
cEOS側の確認。Startup-configにSSHの設定を書く等でも良いが、今回は別の方法でceosにログインする。
docker exec -it clab-ixia-c-one-dut-test-ceos Cli
ceos#show arp
Address Age (sec) Hardware Addr Interface
10.1.0.1 2:14:01 0000.0000.0001 Ethernet1
10.2.0.1 1:40:24 0000.0000.0002 Ethernet2
ceos#show interfaces counters
Port InOctets InUcastPkts InMcastPkts InBcastPkts
Et1 22240302 43438 0 1
Et2 22239790 43437 0 1
Ma0 0 0 0 0
Port OutOctets OutUcastPkts OutMcastPkts OutBcastPkts
Et1 22239744 43437 0 0
Et2 22240256 43438 0 0
Ma0 1410 6 0 0
ceos#
ceos#show interfaces counters discards
Port InDiscards OutDiscards
--------------- ---------------- -----------
Et1 1766517 0
Et2 1749822 0
Ma0 0 0
--------- --------- ---------
Totals 3516339 0
Flow Metricsでは、片方向あたり約0.78Gbps、双方向合計約1.56Gbpsのトラフィックを送信できることを確認した。一方、cEOSではInput Discardsが多数発生していた。送信レートをrate.percentage = 0.01へ変更するとDiscardは発生しなくなったことから、今回のInput Discardsは、cEOSを含む仮想検証環境の処理能力に対して送信レートが高かったことが主な要因と考えられる。
送信レートを下げた試験での結果
flow_metrics:
name: Device1_to_Device2
frames_tx: '2351'
frames_rx: '2351'
tx_rate_bps: 962770.6
rx_rate_bps: 962405.4
flow_metrics:
Device2_to_Device1
frames_tx: '2350
frames_rx: '2350'
tx_rate_bps: 962406.06
rx_rate_bps: 962406
ceos#show interfaces counters
Port InOctets InUcastPkts InMcastPkts InBcastPkts
Et1 1203758 2351 0 1
Et2 1203246 2350 0 1
Ma0 0 0 0 0
Port OutOctets OutUcastPkts OutMcastPkts OutBcastPkts
Et1 1203200 2350 0 0
Et2 1203712 2351 0 0
Ma0 235 1 0 0
ceos#
ceos#show interfaces counters discards
Port InDiscards OutDiscards
--------------- ---------------- -----------
Et1 0 0
Et2 0 0
Ma0 0 0
--------- --------- ---------
Totals 0 0
まとめ
今回はixia-cに加えDUTとしてcEOSを間に挟んだ構成でトラフィック印加を試みた。cEOSや他の仮想ルータを並べた試験やルーティングの試験等様々なケースに適用できると感じた。物理アプライアンスを繋げる試験も実施したいところではあるが現在環境が準備できないので、準備できる機会があれば試してみたいと考えている。
今回もDevice Endpointによる検証は以前と同様の結果となり、L3 DUTを挟んだ構成でも期待どおりの転送を確認できた。
今回作成した試験コードは、Containerlab上で仮想DUTを組み合わせたL3試験のひな形として利用できる。
また、ixia-c・ixia-c-oneであっても試験コードは流用して使えることが分かった。