Ethernetの静的multicastとコピーの配送先

文書バージョン:1.2.0 対象GitHubバージョン:v1.2.0 文書ID:guide-ethernet-static-multicast 文書状態:公開用完成稿

更新履歴

文書バージョン 更新日 更新内容
1.2.0 2026-10-22 v1.2.0の対象タグ・文書版を確定。過去の測定基準と公開済み履歴を保持
1.1.0 2026-10-14 初版:静的groupの出口リストだけを変える9入力実習、正式版CLI実測と実Viewer、転送コピーとEndpoint購読の境界を追加

目的:一つのフレームがどこへ複製されるか調べる

同じデータを複数の宛先へ配るとき、「Switchが転送した数」と「Endpointが受信した数」は一致するとは限りません。この実習ではmulticast(グループ宛の配送)のフレームを1個だけ生成し、最初のSwitchの出口リストだけを変えます。出口なし・近くのcだけ・下流にも複製する、の3条件を比べます。

要求は、元フレーム・各hopの転送コピー・受信記録を区別し、届かなかった場所と理由を説明できることです。Switchの静的group表は配送経路を決め、Endpointの購読は届いたgroupを受け取るか決めます。購読設定だけでSwitchの経路が自動的に作られるモデルではありません。

使用profileはethernet.l2.vlan.v1です。数字と画像は正式v1.1.4 ReleaseのCLIで取得しました。実測source commitはb45515644dfc65c205216d564d5bf7ef00280622です。VLAN受入・priority・TASを変える既存実習とは異なり、本実習は静的multicastの経路を比較します。

構成と責務:経路の指定と受信の購読を分ける

公開サンプルexamples/ethernet/vlan-multicast.iniのconnected treeを使います。図は今回の送信方向です。NEDには逆方向のLinkもあり、全14方向をそのまま残します。

a.tx → s1.rx_a
       s1.tx_c → c.rx(group購読あり)
       s1.tx_b → s2.rx_a
                 s2.tx_c → b.rx(未購読)
                 s2.tx_b → s3.rx_a
                           s3.tx_b → d.rx(未購読)
                           s3.tx_c → e.rx(未購読)

名前の完全パスはVlanDemo.a、VlanDemo.s1等です。全Linkは1Gbps・伝搬1000ps、Switchの処理は2000ps、EndpointのTX/RX処理は0psです。接続はNEDで決まり、JSONの出口名で新しいLinkを追加することはできません。

構成要素 責務・境界・依存する入力
NED Endpoint 5個・Switch 3個とLinkを接続。出力txの完全パスはmodel-configの参照先になる
INI network、NED探索先、profile、model-config、workload、終了時刻を結ぶ
workload aが時刻0に1個生成。宛先01:00:5e:00:00:01、payload 010203、VID20・PCP4・DEI1、priority4、deadlineなし
Port policy 受入は全てall、VID20は全Portでtagged。今回は受入条件やtagを変えない
Switchのgroup表 (VID, group MAC)に一致する静的出口を選び、ingressと同じPortを除く。s1の出口リストだけが比較変数
出力キュー・Link 候補ごとに別のコピーを作り、容量を判定して送信。全Portの総容量64・各class容量64を固定する
Endpointの購読 cだけがVID20のこのgroupを購読。b/d/eへのwire到達は購読を意味しない
Viewer 保存済み結果を表示し、frame→transfer→reception→子transferを追う。表示時にシミュレーションを再実行しない

s2は同groupをtx_b(s3へ)とtx_c(bへ)に、s3はtx_b(dへ)とtx_c(eへ)に転送する設定です。この2表とEndpoint購読を固定します。unknown_multicastも全Switchでfloodのままですが、今回の宛先は登録済みです。登録の出口リストが空でも「未登録」には戻らず、floodしません。

入力を用意して正式CLIを準備する

独立入力ZIPを展開するとexamples/guide/ethernet-multicastに9ファイルがあります。ZIP単独で再現でき、既存の記事のZIPは不要です。mainをcloneした場合は同じ入力が同梱されています。

ファイル 内容
models/ethdemo/Main.ned / VlanDemo.ned 公開Ethernetサンプルの型と接続を変更せず使用
workload.json 公開vlan-multicast.jsonと同じ1フレーム
none.json / local.json / branch.json s1のmulticast[0].egressesだけが異なる。branchは公開model-vlan.jsonそのもの
none.ini / local.ini / branch.ini model-config名だけが異なる。終了4000000ps、集計窓4000000psは共通

比較するJSON項目はswitches[0].multicast[0].egressesです。switches[0].instanceがVlanDemo.s1、groupがVID20・01:00:5e:00:00:01であることを確認します。

条件 出口リスト
none []
local ["VlanDemo.s1.tx_c"]
branch ["VlanDemo.s1.tx_b", "VlanDemo.s1.tx_c"]

以下はWSL上のUbuntu 24.04 LTS x86_64、Rust 1.85.0の手順です。Gitとrustup/Cargoを準備済みのシェルを使います。dir-v114-cliは新しい名前にしてください。SHAが一致した場合だけビルドします。-j 1は同時コンパイルを1個に制限します。

git -c core.autocrlf=false clone --depth 1 --branch v1.1.4 --single-branch \
  https://github.com/hideki-ozu/DIR-Simulator.git dir-v114-cli &&
cd dir-v114-cli &&
test "$(git rev-parse HEAD)" = b45515644dfc65c205216d564d5bf7ef00280622 &&
cargo build --locked --release -j 1 --target-dir target -p dir-simulator &&
export PATH="$PWD/target/release:$PATH" &&
command -v dir-simulator

次にZIPを展開したディレクトリへ移動し、examples/guide/ethernet-multicastがあることを確認します。同じシェルでPATHを維持します。出力には新しい名前を使ってください。

mkdir multicast-results
for case in none local branch; do
  dir-simulator validate --config examples/guide/ethernet-multicast/$case.ini &&
  dir-simulator run --config examples/guide/ethernet-multicast/$case.ini \
    --output multicast-results/$case &&
  dir-simulator view --input multicast-results/$case/results.json \
    --output multicast-results/$case-viewer.html || break
done

各runはmanifest.json、results.json、events.csv、summary.csv、diagnostics.jsonlを保存します。validate→run→viewの順で失敗を確認します。CLIの終了0とmanifestのstatus:complete・partial:falseを確認し、生成した*-viewer.htmlをブラウザで開きます。Viewerはサーバー不要でオフライン表示できます。

実測:転送が増えても受信Endpointは増えない

3条件とも元frameは1個で、全copyは送信完了しています。終了はevents_exhausted、未処理イベント0、途中結果ではありません。receivedはEndpointの受信記録、filteredはSwitchまたはEndpointの受信拒否記録、serializedはhopごとの送信完了コピーです。

条件 generated serialized forwarded received filtered dropped
none 1 1 0 0 1 0
local 1 2 1 1 0 0
branch 1 7 3 1 3 0

noneではa→s1の1コピーだけです。s1が609000psで観測し、処理後611000psにmulticast_no_egressでfilteredになります。空の登録を削除して未登録groupのfloodに変える比較ではありません。

localではs1→cを1個追加し、cは1220000psでreceivedになります。branchではs1→s2、s2→s3/b、s3→d/eも追加しますが、cの受信時刻は同じ1220000psです。bは1831000ps、d/eは2442000psに到達した時点でmulticast_not_subscribedになります。Switchの転送先指定とEndpointの購読が別であるためです。

filteredは出力キュー満杯によるdropped:queue_fullとは別です。本実習ではdroppedが全条件0であり、容量不足を測定したことにはなりません。生成1個から7コピーを作ったからといって、7個のアプリケーション受信を数えることもできません。

時間の確認には1copyのMAC長68byteを使います。payload3byteとpadding43byteにtagged header/FCSを加えた長さです。1Gbpsでpreamble/SFD込みの直列化は(68+8)×8×1000 = 608000ps、IFG込み占有は(68+20)×8×1000 = 704000psです。arrivalはEOF+1000ps、Switchの次のSOFはarrival+2000psです。

hop(branch) SOF ps EOF ps arrival ps release ps
a→s1 0 608000 609000 704000
s1→s2 / s1→c 611000 1219000 1220000 1315000
s2→s3 / s2→b 1222000 1830000 1831000 1926000
s3→d / s3→e 1833000 2441000 2442000 2537000

異なるLinkは別資源なので、上流のIFGが終わる前に下流の送信が始まることがあります。s1→s2とs1→cも別出力で同時に進みます。sourceのframeを増やす処理ではなく、同じframeを参照するtransferが増えます。

Viewerでコピーの親子関係を追う

生成したViewerで時刻入力に4000000を入れ、移動ボタンを押します。全条件の最終状態を見てから、転送表のVlanDemo.a.tx行をクリックします。詳細の元frame、転送copyと受信記録を照合します。

branchの実Viewerでs1からs2へのコピーの親と受信結果を確認

branchのVlanDemo.s1.tx_b行(s1→s2)を選んだ実画面です。詳細JSONのparent_transfer_idはvlan_multicast:0@VlanDemo.a.txで、a→s1のincoming copyを指します。a.tx行を選ぶとparent_transfer_id:null、画面の「親copy」はsourceになります。元frameのIDは全copyで共通です。受信欄は選んだcopyだけでなく、この元フレームの受信記録を一覧表示します。詳細JSONは最終runの記録なので、過去時刻に移動した場合も実績と予定fieldを読み分けます。

次にVlanDemo.s2.tx_cの行を選び、経路がb.rxであることと、受信欄のb側のmulticast_not_subscribedを照合します。VlanDemo.s1.tx_cの行ではc.rxへの経路とc側の「受信完了」(JSONのreceived)を確認できます。Switchの「転送判断済み」はJSONのforwardedです。同じMACへの複製でも受信結果が違うことを、表示線だけでなく受信欄のdevice名と理由で読んでください。s1の子copyリストegress_transfer_idsは次の結果JSON抽出で確認します。

noneの実Viewerで空のgroup出口によるfilterを確認

noneの同じa.tx行の実画面です。s1はmulticast_no_egress、子copyは空です。cは購読済みでも、cへ到達したcopyもreceptionもありません。「未購読だから届かない」と「経路がないから到達しない」を区別できます。Viewerの全run集計と時刻位置の状態表示は母数が異なる場合があるため、今回は4000000psに揃えています。

結果JSONを手元でも確認できます。次はコピーの親と受信状態だけを取り出す例です。

python3 - <<'PY'
import json
from pathlib import Path
for case in ('none', 'local', 'branch'):
    sim = json.loads(Path(f'multicast-results/{case}/results.json').read_text())['simulation']
    print(case)
    for row in sim['model_records']:
        d = row['data']
        if row['schema_name'] == 'ethernet.transfer':
            print('copy', d['from_port'], d['to_port'], d['parent_transfer_id'])
        elif row['schema_name'] == 'ethernet.reception':
            print('rx', row['subject'], d['status'], d['reason'], d['egress_transfer_ids'])
PY

制約と次に確認すること

この実習は静的表・単一C-tag・非巡回tree・競合なしの1フレームに限定しています。VID20、全Portの受入・tagged設定、priority4、購読、終了時刻は固定です。未登録group、部分queue満杯、途中停止、動的membership変更は測定していません。IGMP/MLD snoopingやSTPによる自動経路構築、上位IP protocolの処理やEthernet規格全体への適合を示すものでもありません。

設定の契約はVLAN・multicast仕様を参照してください。受入で拒否される場合はVLAN受入条件の実習、送信待ちを調べる場合は優先キューの実習へ進めます。