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 で新しいアプライアンスの作成ウィザードを開きます。
Existing image で先ほど作成した Docker イメージを選択、Network Adaptars を 8 つに増やしてあとはデフォルトのままで最後まで進めます。
これで BMv2 版の Stratum の GNS3 アプライアンスが作成できました。
動作確認
チュートリアルのEXERCISE 1を動かしながら動作を確認してみます。アプライアンスの配置
EXERCISE 1 では、スイッチ 1 台のホスト 2 台構成で動作させるため、以下のように配置します。
スイッチの管理 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