APRESIA Technical Blog

Stratum on BMv2をGNS3上で動かしてみた

はじめに

Open Network Foundation (ONF)は、以前より Google と共同で次世代型の SDN インターフェースと呼ばれている「P4Runtime」「gNMI」「gNOI」(いずれも gRPC で通信)をベースにした新たな Network OS「Stratum」の開発を進めていました。先日、このプロジェクトが OSS として Github に公開 されましたので、早速触っていきたいと思います。
今回は、ONF Connect ’19 にて開催されていたチュートリアルをベースに進めていきたいと思います。チュートリアルでは Stratum の環境を Mininet で構築しており、Mininet はとても便利ですが構成が見えづらいため今回は GNS3 で構築してみました。

準備

まず、GNS3 上で Stratum を動作させるための準備を行います。Stratum の BMv2 版を自身でビルドすることも可能ですが、今回は公式で用意しているDocker イメージを活用します。
公式のイメージをそのまま起動すると、Mininet 起動コマンドが実行されているため、以下のような Dockerfile で Entrypoint を上書きして独自のイメージを作成します。
FROM opennetworking/mn-stratum RUN mkdir -p /config VOLUME /config COPY chassis-config.txt /config/. COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/bin/bash"]

docker-entrypoint.sh

docker-entrypoint.shは以下のように作成しました。stratum_bmv2 におけるパラメータは公式の Mininet 起動時のパラメータを参考に設定しました。
Mininet と違い、Docker コンテナの場合はプロセス空間も分離されているため、gRPC の待ち受けポートとして同じ値(50001)を使用できます。
#!/bin/bash set -e stratum_bmv2 -device_id=1 \ -chassis_config_file=/config/chassis-config.txt \ -forwarding_pipeline_configs_file=/tmp/pipe.txt \ -persistent_config_dir=/config \ -initial_pipeline=/root/dummy.json \ -cpu_port=255 \ -external_stratum_urls=0.0.0.0:50001 \ -local_stratum_url=localhost:55555 \ -max_num_controllers_per_node=10 \ -write_req_log_file=/tmp/write-reqs.txt \ -logtosyslog=false \ -logtostderr=true & exec "$@"

chassis-config.txt

Stratum のポート情報として設定ファイルを作成する必要があります。/config/chassis-config.txtが該当します。
ポートの数は任意ですが、今回は 8 ポート(eth0 を管理ポート、eth1-7 をユーザーポート)の GNS3 アプライアンスを作成する予定のため、以下のように作成しました。
description: "Sample config" chassis { platform: PLT_P4_SOFT_SWITCH name: "bmv2-simple_switch" } nodes { id: 1 slot: 1 index: 1 } singleton_ports { id: 1 name: "eth1" slot: 1 port: 1 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 } singleton_ports { id: 2 name: "eth2" slot: 1 port: 2 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 } singleton_ports { id: 3 name: "eth3" slot: 1 port: 3 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 } singleton_ports { id: 4 name: "eth4" slot: 1 port: 4 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 } singleton_ports { id: 5 name: "eth5" slot: 1 port: 5 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 } singleton_ports { id: 6 name: "eth6" slot: 1 port: 6 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 } singleton_ports { id: 7 name: "eth7" slot: 1 port: 7 channel: 1 speed_bps: 100000000000 config_params { admin_state: ADMIN_STATE_ENABLED } node: 1 }

Docker イメージのビルド

GNS3 サーバーが動作しているサーバー上で Docker イメージをビルドします。
※イメージ名は任意です
docker build -t aps/stratum-bmv2 .

GNS3 アプライアンス作成

GNS3 の GUI よりアプライアンスを作成します。
Ctrl + Shift + P で設定(Preferences)を開きます。
Docker Containers から New で新しいアプライアンスの作成ウィザードを開きます。 GNS3.png
Existing image で先ほど作成した Docker イメージを選択、Network Adaptars を 8 つに増やしてあとはデフォルトのままで最後まで進めます。
GNS3_2.png
これで BMv2 版の Stratum の GNS3 アプライアンスが作成できました。

動作確認

チュートリアルのEXERCISE 1を動かしながら動作を確認してみます。

アプライアンスの配置


EXERCISE 1 では、スイッチ 1 台のホスト 2 台構成で動作させるため、以下のように配置します。 GNS3_3.png
スイッチの管理 IP として、NAT アプライアンスからアクセス可能な IP アドレスを付与しておきます。
以下のように Network configuration を設定しました。
auto eth0 iface eth0 inet static address 192.168.122.51 netmask 255.255.255.0 gateway 192.168.122.1
ホストは IPv6 設定が可能なものであればどんなものでも構いません。今回は Debian の Docker アプライアンスを利用しました。(Debian アプライアンスは別途作成必要)
ホストに Static なアドレス付与するために、Network configuration にあらかじめ以下の設定を実施しています。
  • Host h1a
auto eth0 iface eth0 inet6 static address 2001:1:1::a netmask 64 gateway 2001:1:1::ff pre-up echo 0 > /proc/sys/net/ipv6/conf/eth0/accept_ra hwaddress ether 00:00:00:00:00:1A
  • Host h1b
auto eth0 iface eth0 inet6 static address 2001:1:1::b netmask 64 gateway 2001:1:1::ff pre-up echo 0 > /proc/sys/net/ipv6/conf/eth0/accept_ra hwaddress ether 00:00:00:00:00:1B

P4 プログラムのビルド

まずは、デフォルトで用意されている P4 プログラムをビルドします。 Makefile が用意されているので、それを用いてビルドできます。
make p4-build
p4src/build/配下にファイルが生成されていることを確認します。

P4Runtime でテーブルの書き込み

先ほどビルドした P4 プログラムを P4Runtime 経由でデバイスに流し込みます。
./util/p4rt-sh --grpc-addr 192.168.122.51:50001 --config p4src/build/p4info.txt,p4src/build/bmv2.json --election-id 0,1
そのまま P4Runtime の IPython シェルが起動するため、テーブルエントリーの登録を行っていきます。
今回利用するテーブルは、以下のl2_exact_tableとなります。
table l2_exact_table { key = { hdr.ethernet.dst_addr: exact; } actions = { set_egress_port; @defaultonly drop; } const default_action = drop; // The @name annotation is used here to provide a name to this table // counter, as it will be needed by the compiler to generate the // corresponding P4Info entity. @name("l2_exact_table_counter") counters = direct_counter(CounterType.packets_and_bytes); }
P4 の詳細は割愛しますが、key としてhdr.ethernet.dst_addr、action としてset_egress_portのマッピングテーブルを登録していきます。
  • h1a -> h1b
P4Runtime sh >>> te = table_entry["IngressPipeImpl.l2_exact_table"](action = "IngressPipeImpl.set_egress_port") P4Runtime sh >>> te.match["hdr.ethernet.dst_addr"] = ("00:00:00:00:00:1B") P4Runtime sh >>> te.action['port_num'] = ("4") P4Runtime sh >>> te.insert()
  • h1b -> h1a
P4Runtime sh >>> te = table_entry["IngressPipeImpl.l2_exact_table"](action = "IngressPipeImpl.set_egress_port") P4Runtime sh >>> te.match["hdr.ethernet.dst_addr"] = ("00:00:00:00:00:1A") P4Runtime sh >>> te.action['port_num'] = ("3") P4Runtime sh >>> te.insert()

NDP エントリーの登録

上記テーブルを登録した段階では通信はできません。NDP のハンドリングがされないため、対向の MAC アドレス解決が出来ていないためです。
通常 NDP のハンドリングも実装することが望ましいですが、ここでは簡単のため静的に設定します。
※P4 と ONOS を使った NDP ハンドリング実装方法は EXERCISE4 で実施

それぞれのホストにコンソール接続し以下のように設定します。
  • h1a
ip -6 neigh replace 2001:1:1::B lladdr 00:00:00:00:00:1B dev eth0
  • h1b
ip -6 neigh replace 2001:1:1::A lladdr 00:00:00:00:00:1A dev eth0

通信確認

以上で 2 ホスト間での経路設定が完了しました。ping コマンドで通信可能かを確認します。
  • h1a -> h1b
root@h1a:/# ping6 -c 3 2001:1:1::b PING 2001:1:1::b(2001:1:1::b) 56 data bytes 64 bytes from 2001:1:1::b: icmp_seq=1 ttl=64 time=1.18 ms 64 bytes from 2001:1:1::b: icmp_seq=2 ttl=64 time=1.14 ms 64 bytes from 2001:1:1::b: icmp_seq=3 ttl=64 time=1.18 ms --- 2001:1:1::b ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 1.147/1.171/1.184/0.032 ms
  • h1b -> h1a
root@h1b:/# ping6 -c 3 2001:1:1::a PING 2001:1:1::a(2001:1:1::a) 56 data bytes 64 bytes from 2001:1:1::a: icmp_seq=1 ttl=64 time=1.21 ms 64 bytes from 2001:1:1::a: icmp_seq=2 ttl=64 time=1.16 ms 64 bytes from 2001:1:1::a: icmp_seq=3 ttl=64 time=1.19 ms --- 2001:1:1::a ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 1.168/1.194/1.218/0.034 ms

最後に

BMv2 上で動作する Stratum を GNS3 上で構築し簡単な P4 のテーブルエントリーの登録まで確認できました。Stratum は P4Runtime だけでなく、gNMI や gNOI といった他のインターフェースの操作も可能なため、次回以降でそちらも触っていきたいと思います。

参考