APRESIA Technical Blog

Stratum on BMv2をGNS3上で動かしてみた(第四弾)

はじめに

「Stratum on BMv2 を GNS3 上で動かしてみた」の第四弾です。
前回は、ONOSを使ってStratumを操作しました。今回は、Stratumで動作させるP4プログラムを改造して動きを見ていきます。
今回も、ONF Connect ’19 にて開催されていたチュートリアル(EXERCISE 4)をベースに進めていきます。

構成

前回と同様、Leaf-Spine のIP CLOSファブリック構成にします。
IP CLOS

問題点の確認

前回、ONOSからP4のルーティングテーブルを正しく設定したにも関わらず、別サブネット間通信(例えばh2とh3の通信)が出来ませんでした。
別サブネットへルーティングする際には、ルーティングテーブルの他にNDPを解決し、ゲートウェイのMACアドレスを知る必要があります。しかし現状の実装では、NDPのハンドリングを実装していないためMACアドレスが分からず通信出来ません。
試しに、静的にMACアドレスを登録して確認してみます。
h2, h3それぞれにゲートウェイのMACアドレスを登録します。
  • h2
ip -6 neigh replace 2001:1:2::ff lladdr 00:aa:00:00:00:01 dev eth0
  • h3
ip -6 neigh replace 2001:2:3::ff lladdr 00:aa:00:00:00:02 dev eth0
すると、以下のように通信出来ることが確認出来ます。
## h2 -> h3 root@h2:/# ping6 2001:2:3::1 -c 4 PING 2001:2:3::1(2001:2:3::1) 56 data bytes 64 bytes from 2001:2:3::1: icmp_seq=1 ttl=61 time=3.56 ms 64 bytes from 2001:2:3::1: icmp_seq=2 ttl=61 time=3.91 ms 64 bytes from 2001:2:3::1: icmp_seq=3 ttl=61 time=3.55 ms 64 bytes from 2001:2:3::1: icmp_seq=4 ttl=61 time=3.41 ms --- 2001:2:3::1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 3.413/3.614/3.919/0.195 ms ## h3 -> h2 root@h3:/# ping6 2001:1:2::1 -c 4 PING 2001:1:2::1(2001:1:2::1) 56 data bytes 64 bytes from 2001:1:2::1: icmp_seq=1 ttl=61 time=3.89 ms 64 bytes from 2001:1:2::1: icmp_seq=2 ttl=61 time=3.96 ms 64 bytes from 2001:1:2::1: icmp_seq=3 ttl=61 time=3.96 ms 64 bytes from 2001:1:2::1: icmp_seq=4 ttl=61 time=3.99 ms --- 2001:1:2::1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3002ms rtt min/avg/max/mdev = 3.891/3.952/3.991/0.057 ms
NDPを解決するためには、ホストが送信するNeighbor Solicitation (NS)パケットに対して、ルーターがNeighbor Advertisement (NA)パケットを返す必要があります。
実現方法はいくつか考えられますが、今回はP4を使って実現していきます。

P4プログラムの変更

NSとNAは構造が同じなため、受信したNSのフィールドを書き換えて受信ポートに送り返すという実装をしていきます。
どのように書き換えるかは、solution/exercise4配下にある回答を確認してください。
新たに追加したテーブルを確認してみます。
action ndp_ns_to_na(mac_addr_t target_mac) { hdr.ethernet.src_addr = target_mac; hdr.ethernet.dst_addr = IPV6_MCAST_01; ipv6_addr_t host_ipv6_tmp = hdr.ipv6.src_addr; hdr.ipv6.src_addr = hdr.ndp.target_ipv6_addr; hdr.ipv6.dst_addr = host_ipv6_tmp; hdr.ipv6.next_hdr = IP_PROTO_ICMPV6; hdr.icmpv6.type = ICMP6_TYPE_NA; hdr.ndp.flags = NDP_FLAG_ROUTER | NDP_FLAG_OVERRIDE; hdr.ndp.type = NDP_OPT_TARGET_LL_ADDR; hdr.ndp.length = 1; hdr.ndp.target_mac_addr = target_mac; standard_metadata.egress_spec = standard_metadata.ingress_port; } table ndp_reply_table { key = { hdr.ndp.target_ipv6_addr: exact; } actions = { ndp_ns_to_na; } @name("ndp_reply_table_counter") counters = direct_counter(CounterType.packets_and_bytes); }
テーブル(ndp_reply_table)では、ゲートウェイのIPv6アドレスをキーとして、アクション(ndp_ns_to_na)を実行しています。
ndp_ns_to_naでは、各フィールドの書き換え処理を行っています。また、折り返し送信するために、受信ポート(ingress_port)を送信ポート(egress_port)として設定していることが分かります。
このテーブルを使うためにコントローラー側から入力が必要な値は、キーとなるルーターのIPv6アドレスおよびアクションの引数である解決すべきルーターのMACアドレスとなります。それらの設定はONOSから設定します。

ONOSアプリ実装

上記定義したテーブルの値を設定するために、ONOSアプリを実装する必要があります。
チュートリアルではすでに実装済み(app/src/main/java/org/onosproject/ngsdn/tutorial/NdpReplyComponent.java)のものがあるためそちらを利用します。(TODOの部分はsolutionフォルダのもので置き換えてください)
詳細は割愛しますが、NdpReplyComponentで実施していることは以下の通りです。
  • netcfgからそれぞれのルーターのMACアドレスを取得
  • netcfgのそれぞれのインターフェースから、ゲートウェイのIPv6アドレスを取得
  • 取得したIPv6アドレスをキーとして、MACアドレスを引数としたアクション(ndp_ns_to_na)を設定

ONOSアプリのビルド

以下コマンドで、P4およびONOSアプリのコンパイルを実施してくれます。
make app-build

ONOSアプリのリロード

以下コマンドで、作成したONOSアプリをONOSにインストールします。
make app-reload

netcfgの設定

以下コマンドでネットワーク設定ファイルを流し込みます。
設定ファイルの内容は前回と同様です。
make netcfg

通信の確認

GNS3上のh2とh3のコンソールを開き、お互いに通信します。
  • h2 -> h3
root@h2:/# ping6 2001:2:3::1 -c 4 PING 2001:2:3::1(2001:2:3::1) 56 data bytes 64 bytes from 2001:2:3::1: icmp_seq=1 ttl=61 time=3.83 ms 64 bytes from 2001:2:3::1: icmp_seq=2 ttl=61 time=4.09 ms 64 bytes from 2001:2:3::1: icmp_seq=3 ttl=61 time=3.89 ms 64 bytes from 2001:2:3::1: icmp_seq=4 ttl=61 time=3.73 ms --- 2001:2:3::1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3003ms rtt min/avg/max/mdev = 3.731/3.889/4.095/0.139 ms
  • h3 -> h2
root@h3:/# ping6 2001:1:2::1 -c 4 PING 2001:1:2::1(2001:1:2::1) 56 data bytes 64 bytes from 2001:1:2::1: icmp_seq=1 ttl=61 time=4.23 ms 64 bytes from 2001:1:2::1: icmp_seq=2 ttl=61 time=3.67 ms 64 bytes from 2001:1:2::1: icmp_seq=3 ttl=61 time=3.83 ms 64 bytes from 2001:1:2::1: icmp_seq=4 ttl=61 time=3.97 ms --- 2001:1:2::1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 3.678/3.931/4.238/0.215 ms
お互いなにも通信していない最初の状態ではNDP解決されていないため通信に失敗しますが、お互いにPingすることでNDP解決されるため通信可能になります。

Flowの確認

Leaf1のFlowを確認してみます。
ONOS flows

以下のように、先ほど追加したndp_reply_tableが設定されていることが確認出来ます。
ルーターのIPv6アドレスをキーにしてMACアドレスが設定されています。
ADDED, bytes=172, packets=2, table=IngressPipeImpl.ndp_reply_table, priority=10, selector=[hdr.ndp.target_ipv6_addr=0x200100010002000000000000000000ff], treatment=[immediate=[IngressPipeImpl.ndp_ns_to_na(target_mac=0xaa00000001)]]

最後に

今回はP4を変更してスイッチの動作を自分で実装するということを実施しました。
今までなかなか難しかったデータプレーンの動きをお手軽に変更出来るのはとても面白いです。
本番環境で動作させる場合は考慮すべき点が多く決して簡単なことではありませんが、データプレーンの動作をお手軽に自作出来るのは非常に強力なものになるのではないでしょうか。
これまで数回にわたりStratumを触ってきました。データプレーンの動作を含め、すべて自分で作っていけるのは面白いです。逆に自分で定義していく必要があるため難しい面もあります。
Stratumはまだリリースされたばかりで情報等も少ないですが、今後どのように発展していくのか引き続き注目していきたいです。

参考