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?

IxNetwork利用経験者のための ixia-c入門(3) ~Port EndpointとDevice Endpointの違いを理解する~

1
Last updated at Posted at 2026-07-03

はじめに

前回の記事では、Topology(Device)をFlowのEndpointとしてTrafficを開始したところ、MAC address resolution failed for IP: 10.1.0.2というエラーが発生した。
当初はARP解決に問題があると考えていたが、切り分けを進めた結果、Flowの作成方法そのものに原因があることが分かった。
本記事では、その切り分けの過程と最終的に動作した最小構成を紹介する。

前回記事はこちら

Port EndpointとDevice Endpointの違い

以前に利用していたPort Endpointと、今回利用するDevice Endpointの違いを図にすると以下のようになる。

【Port Endpoint】

Port
  ↓
Flow
 ├ Ethernet
 ├ IPv4
 └ UDP

≒ IxExplorerのStream作成
≒ IxNetworkのL2-3 Quick Flow Group + Packet Editor


【Device Endpoint】

Topology(Device)
 └ Ethernet
      └ IPv4
            ↓
          Flow

≒ IxNetworkのTopology + Traffic Item

Device EndpointではTopology(Device)配下に構成したInterfaceをFlowのEndpointとして利用する。Packet Headerは省略することもできるが、Flow内でEthernet、IPv4、UDPなどのPacket Headerを定義し、各フィールド値を明示的に設定することも可能である。

Device Endpointを使ったFlow作成の検証

前回はTopology(Device)とIPv4 Interfaceを作成した。

Topology(Device)をFlowのEndpointとして指定し、実際にトラフィックを印加したところ、MAC address resolution failed for IP: 10.1.0.2というエラーが発生した。Port Endpointを使用した前回のサンプルでは、Ethernet/IPv4/UDPヘッダ内で明示的にIP/MACアドレスを指定したTrafficの印加は確認していたため、veth-a/veth-z間でフレームを流すこと自体は可能であると判明していた。

こちらはDevice Endpointを使用して試したが、MACアドレス解決に失敗したコンフィグである。
※現行のサンプルコードをチェックしたところ、protocol_state() / transmit_state() が今の snappiでは互換性は維持するもののcontrol_state() の利用が推奨される方向のようなので設定をいくつか変更をしている

import time
import snappi


def test_topology_ipv4_udp():
    # ------------------------------------------------------------------
    # Controllerへ接続
    # ------------------------------------------------------------------
    api = snappi.api(
        location="https://localhost:8443",
        verify=False
    )

    # ------------------------------------------------------------------
    # Config作成
    # ------------------------------------------------------------------
    cfg = api.config()

    # ------------------------------------------------------------------
    # Port作成
    # ------------------------------------------------------------------
    p1 = cfg.ports.add(name="p1", location="veth-a")
    p2 = cfg.ports.add(name="p2", location="veth-z")

    # ------------------------------------------------------------------
    # Topology(Device)作成
    # ------------------------------------------------------------------
    topo1 = cfg.devices.add(name="Topology1")
    topo2 = cfg.devices.add(name="Topology2")

    # ------------------------------------------------------------------
    # Topology1にEthernet/IPv4 Interfaceを作成
    # ------------------------------------------------------------------
    eth1 = topo1.ethernets.add(name="Ethernet1")
    eth1.connection.port_name = p1.name
    eth1.mac = "00:00:00:00:00:01"

    ip1 = eth1.ipv4_addresses.add(name="IPv4-1")
    ip1.address = "10.1.0.1"
    ip1.gateway = "10.1.0.2"
    ip1.prefix = 24

    # ------------------------------------------------------------------
    # Topology2にEthernet/IPv4 Interfaceを作成
    # ------------------------------------------------------------------
    eth2 = topo2.ethernets.add(name="Ethernet2")
    eth2.connection.port_name = p2.name
    eth2.mac = "00:00:00:00:00:02"

    ip2 = eth2.ipv4_addresses.add(name="IPv4-2")
    ip2.address = "10.1.0.2"
    ip2.gateway = "10.1.0.1"
    ip2.prefix = 24

    # ------------------------------------------------------------------
    # Flow1作成
    # Device EndpointではIPv4 Interface名を指定
    # ------------------------------------------------------------------
    flow1 = cfg.flows.add(name="Flow1")
    flow1.tx_rx.device.tx_names = [ip1.name]
    flow1.tx_rx.device.rx_names = [ip2.name]

    flow1.metrics.enable = True
    flow1.duration.fixed_packets.packets = 100
    flow1.size.fixed = 512

    eth_f1, ip_f1, udp1, cus1 = flow1.packet.ethernet().ipv4().udp().custom()
    udp1.src_port.value = 2025
    udp1.dst_port.value = 2026
    cus1.bytes = "".join([hex(c)[2:] for c in b"Topology1"])

    # ------------------------------------------------------------------
    # Flow2作成
    # Device EndpointではIPv4 Interface名を指定
    # ------------------------------------------------------------------
    flow2 = cfg.flows.add(name="Flow2")
    flow2.tx_rx.device.tx_names = [ip2.name]
    flow2.tx_rx.device.rx_names = [ip1.name]

    flow2.metrics.enable = True
    flow2.duration.fixed_packets.packets = 100
    flow2.size.fixed = 512

    eth_f2, ip_f2, udp2, cus2 = flow2.packet.ethernet().ipv4().udp().custom()
    udp2.src_port.value = 2026
    udp2.dst_port.value = 2025
    cus2.bytes = "".join([hex(c)[2:] for c in b"Topology2"])

    # ------------------------------------------------------------------
    # Config確認
    # ------------------------------------------------------------------
    print("\nCONFIGURATION", cfg.serialize(encoding=cfg.JSON), sep="\n")

    # ------------------------------------------------------------------
    # Config反映
    # ------------------------------------------------------------------
    api.set_config(cfg)

    # ------------------------------------------------------------------
    # Protocol起動
    # ------------------------------------------------------------------
    cs = api.control_state()
    cs.protocol.all.state = cs.protocol.all.START
    api.set_control_state(cs)

    time.sleep(10)

    # ------------------------------------------------------------------
    # Traffic開始
    # ここで MAC address resolution failed が発生
    # ------------------------------------------------------------------
    cs = api.control_state()
    cs.traffic.flow_transmit.state = cs.traffic.flow_transmit.START
    api.set_control_state(cs)

失敗ログの抜粋

Exception: code: 500
errors:
- Error occurred while starting flows:
  [error starting tx port p1:
   unsuccessful Response:
   MAC address resolution failed for IP: 10.1.0.2.]
kind: internal

次にtcpdumpを使ってARP解決のやりとりをしているのかを確認しようとveth-a/veth-zおよびtraffic-engine内でtcpdumpを使ってパケットキャプチャを試みたがARP Requestを確認することはできなかった。

veth-a/veth-zのキャプチャ(vethにDevice EndpointのARPが出ていない)

sudo tcpdump -eni veth-a arp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth-a, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

sudo tcpdump -eni veth-z arp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth-z, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

traffic-engineでのキャプチャ

docker exec -it ixia-c-traffic-engine-veth-z tcpdump -eni veth-a arp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth-a, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

docker exec -it ixia-c-traffic-engine-veth-z tcpdump -eni veth-z arp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on veth-z, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

ここまでの切り分けの結果としては以下となる。

Port Endpoint で MAC/IP を明示的に指定した場合は Traffic を流せたが、
Device Endpoint では ARP 解決時に MAC address resolution failed となった。

traffic-engine コンテナ内で veth-a / veth-z を tcpdump しても
Traffic開始前からtcpdumpを実行した状態で確認したが、ARP Requestは一切観測できなかった。

次の切り分けとして、失敗したコードからPacket Headerの定義を除いた最小構成を試みた。
flow.packet.ethernet().ipv4().udp() を入れず、Packet Headerを明示せず、DeviceのIPv4 Interface情報だけで Flow を作る方法を試みている。

# Python標準ライブラリ(試験時間の待機処理に使用)
import time

# OTG APIを操作するPython SDK
import snappi


def test_device_endpoint_minimum():
    # ------------------------------------------------------------------
    # Controllerへ接続
    # ------------------------------------------------------------------
    api = snappi.api(
        location="https://localhost:8443",
        verify=False
    )

    # ------------------------------------------------------------------
    # Config作成
    # ------------------------------------------------------------------
    cfg = api.config()

    # ------------------------------------------------------------------
    # Port作成
    # ------------------------------------------------------------------
    p1 = cfg.ports.add(
        name="p1",
        location="veth-a"
    )

    p2 = cfg.ports.add(
        name="p2",
        location="veth-z"
    )

    # ------------------------------------------------------------------
    # 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.2"
    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.1.0.2"
    ip2.gateway = "10.1.0.1"
    ip2.prefix = 24

    # ------------------------------------------------------------------
    # Flow: 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]

    # Frame Size
    flow1.size.fixed = 512

    # Rate
    flow1.rate.percentage = 10

    # Duration
    flow1.duration.fixed_seconds.seconds = 10

    # Metrics
    flow1.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]
    api.set_control_state(state)

    print("Traffic Started")

    # ------------------------------------------------------------------
    # Metrics取得
    # ------------------------------------------------------------------
    request = api.metrics_request()
    request.flow.flow_names = [flow1.name]

    for _ in range(20):
        metrics = api.get_metrics(request)
        print(metrics)

        if metrics.flow_metrics[0].transmit == metrics.flow_metrics[0].STOPPED:
            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]
    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")

結果はこちら

./do.sh pytest examples/minimum-device-endpoint.py
============================= 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/minimum-device-endpoint.py::test_device_endpoint_minimum 2026-07-02 14:00:48.685 [snappi] [WARNING] Version check is disabled

-------------------------------- live log call ---------------------------------
WARNING  snappi:snappi.py:106 Version check is disabled
2026-07-02 14:00:48.685 [snappi] [WARNING] Certificate verification is disabled
WARNING  snappi:snappi.py:164 Certificate verification is disabled

CONFIGURATION
{
  "devices": [
    {
      "ethernets": [
        {
          "connection": {
            "choice": "port_name",
            "port_name": "p1"
          },
          "ipv4_addresses": [
            {
              "address": "10.1.0.1",
              "gateway": "10.1.0.2",
              "name": "Device1.IPv4",
              "prefix": 24
            }
          ],
          "mac": "00:00:00:00:00:01",
          "mtu": 1500,
          "name": "Device1.Ethernet1"
        }
      ],
      "name": "Device1"
    },
    {
      "ethernets": [
        {
          "connection": {
            "choice": "port_name",
            "port_name": "p2"
          },
          "ipv4_addresses": [
            {
              "address": "10.1.0.2",
              "gateway": "10.1.0.1",
              "name": "Device2.IPv4",
              "prefix": 24
            }
          ],
          "mac": "00:00:00:00:00:02",
          "mtu": 1500,
          "name": "Device2.Ethernet1"
        }
      ],
      "name": "Device2"
    }
  ],
  "flows": [
    {
      "duration": {
        "choice": "fixed_seconds",
        "fixed_seconds": {
          "gap": 12,
          "seconds": 10
        }
      },
      "metrics": {
        "enable": true,
        "loss": false,
        "timestamps": false
      },
      "name": "Device1_to_Device2",
      "rate": {
        "choice": "percentage",
        "percentage": 10
      },
      "size": {
        "choice": "fixed",
        "fixed": 512
      },
      "tx_rx": {
        "choice": "device",
        "device": {
          "mode": "mesh",
          "rx_names": [
            "Device2.IPv4"
          ],
          "tx_names": [
            "Device1.IPv4"
          ]
        }
      }
    }
  ],
  "ports": [
    {
      "location": "veth-a",
      "name": "p1"
    },
    {
      "location": "veth-z",
      "name": "p2"
    }
  ]
}
Protocol Started
Traffic Started
choice: flow_metrics
flow_metrics:
- bytes_rx: '249856'
  bytes_tx: '304640'
  frames_rx: '488'
  frames_rx_rate: 0
  frames_tx: '595'
  frames_tx_rate: 0
  name: Device1_to_Device2
  rx_rate_bps: 0
  transmit: started
  tx_rate_bps: 0

--------
choice: flow_metrics
flow_metrics:
- bytes_rx: '1218657792'
  bytes_tx: '1218657792'
  frames_rx: '2380191'
  frames_rx_rate: 237863.45
  frames_tx: '2380191'
  frames_tx_rate: 237859.34
  name: Device1_to_Device2
  rx_rate_bps: 974288700
  transmit: stopped
  tx_rate_bps: 974271900

Traffic Stopped
Protocol Stopped
PASSED

このコードを使ったところ試験は通った。MetricsでもTx/Rx が試験停止後の最終値での一致を確認した(以下抜粋)。

frames_tx: 2380191
frames_rx: 2380191
transmit: stopped

切り分け等による知見

失敗したコードではPacket Header部分を自分で定義していた。

flow1.packet.ethernet()
flow1.packet.ipv4()
flow1.packet.udp()

今回の切り分けでは、Packet Headerを定義しない最小構成でTrafficを印加できることを確認した。

flow1.tx_rx.device.tx_names = [ip1.name]
flow1.tx_rx.device.rx_names = [ip2.name]

その後さらに検証を進めた結果、Packet Headerを追加し、MACアドレス・IPアドレス・UDP Portを適切に設定したところ、正常にTrafficを印加できることが分かった。

Packet Headerを定義しない最小構成では何が送信されるのか

最小構成でTrafficは印加できたため、実際にどのようなフレームが送信されているのかを確認する目的でキャプチャしたパケットをWiresharkで確認した。
0xffffでIPv4 0x0800でもない。Device1 / Device2 に設定した 10.1.0.1, 10.1.0.2, 00:00:00:00:00:01, 00:00:00:00:00:02 が、Flowの実パケットヘッダに入っていない状態であることが見て取れた。
今回確認した最小構成では、flow1.tx_rx.device.tx_names / rx_names はFlowの送信・受信Endpointを指定するものであり、Deviceに設定したMACアドレスやIPアドレスがFlowのPacket Headerへ自動反映される動作は確認できなかった。

image.png

$ sudo timeout 15 tcpdump -i veth-z -w device_endpoint.pcap
tcpdump: listening on veth-z, link-type EN10MB (Ethernet), snapshot length 262144 bytes
658770 packets captured
658971 packets received by filter

※今回使っている設定は片方向のFlow(Device1→Device2)への印加の設定しか使ってないため、Device2(veth-z)のキャプチャを実施している。

Flowの設定でPacket Headerへ各フィールド値を設定する

IPアドレス/MACアドレス/Port番号を入れる場合の設定例は以下になる。

# ------------------------------------------------------------------
# 今までのFlow設定
# ------------------------------------------------------------------
flow1.tx_rx.device.tx_names = [ip1.name]
flow1.tx_rx.device.rx_names = [ip2.name]

# ------------------------------------------------------------------
# 追加箇所
# ------------------------------------------------------------------
eth, ipv4, udp = flow1.packet.ethernet().ipv4().udp()

eth.src.value = "00:00:00:00:00:01"
eth.dst.value = "00:00:00:00:00:02"

ipv4.src.value = "10.1.0.1"
ipv4.dst.value = "10.1.0.2"

udp.src_port.value = 50000
udp.dst_port.value = 50001

Wiresharkで再度確認。IPアドレス/MACアドレス 任意に設定したUDPポート番号が付与されている。

image.png

当初は、Device EndpointではPacket Headerを定義せず、Device配下のIPv4 InterfaceからPacket Headerが自動生成されるものと考えていた。しかし実際に検証を進めたところ、その理解は正確ではなかった。
Device EndpointでもFlow内でPacket Headerを定義でき、MACアドレスやIPアドレスなどを適切に設定することで正常にTrafficを印加できることを確認した。

まとめ

今回の切り分けで分かったことは以下である。

Port Endpointでは、Flow内でPacket Header(Ethernet/IPv4/UDPなど)を明示的に定義する。
一方、Device Endpointでは、Device配下のIPv4 InterfaceをEndpointとして指定してTrafficを生成する。Device EndpointはFlowの送受信Endpointを指定するための設定であり、Packet Headerを自動生成する機能とは別であることが分かった。
また、Packet Headerを省略した最小構成でもTrafficを印加できることを確認した。一方で、MACアドレスやIPアドレス、UDP Portを利用した通信を行う場合は、Flow内でPacket Headerを定義し、各フィールドを明示的に設定する必要があることも確認できた。

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?