APRESIA Technical Blog

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

はじめに

「Stratum on BMv2 を GNS3 上で動かしてみた」の第三弾です。
前回は、gNMI を使って Stratum を操作しました。今回は、ONF が開発中の SDN コントローラーである ONOS を使って Stratum を操作していきます。
前回同様、ONF Connect ’19 にて開催されていたチュートリアル(EXERCISE 3)をベースに進めていきます。
尚、チュートリアル内でTODO事項となっている部分に関しては、solution/exercise3に格納されているファイルで置き換え済みとして進めます。

構成

今回は、Leaf-Spine のIP CLOSファブリック構成にします。
MAC アドレスや IP アドレスなどは以下の通りで設定します。

構成

GNS3 上で構成すると以下のようになります。

GNS3構成

確認項目

今回確認したい動作は、以下の2点です。
  1. 同一サブネット内のホスト間で通信可能なこと(h1a, h1b, h1cでの通信確認)
  2. 別サブネットのホスト間では通信出来ないこと(ルーティングテーブルは正しく設定されているが、NDPハンドリングが未実装なため)

ONOS内部のコンポーネント

今回のチュートリアル内で利用するONOSアプリケーションは以下のようなコンポーネントから構成されています。
ONOS app

main.p4

スイッチング動作を定義したP4プログラムの本体です。
P4プログラムはP4コンパイラによってコンパイルされ、スイッチに登録されるバイナリファイル(BMv2の場合はbmv2.jsonというJSONファイル)とP4で定義したテーブルのモデル定義であるp4info.txtが生成されます。

PipeconfLoader

上記生成されたbmv2.jsonおよびp4info.txtをスイッチにインストールするためのコンポーネントです。
コンポーネント起動時に上記ファイルがP4Runtime経由でスイッチに登録されます。

Interpreter

ONOS内部のヘッダやアクションの名前から、P4で定義したテーブルの名前に変換するためのコンポーネントです。
Interpreterを実装することで、既存のONOSアプリケーションが設定するFlowを自分で定義したP4のテーブルに設定することが出来ます。
チュートリアルのInterpreterでは、Packet-in/Packet-outの処理の変換およびヘッダの名前やCPU_PORTのマッピングを定義しています。

Pipeliner

ONOSのFlowObjectiveで定義されたFlowをP4のテーブルに変換するためのコンポーネントです。
チュートリアルのPipelinerでは、ONOSの既存のアプリケーションで定義されているLLDPやARPのPacket-InのFlowをP4で定義したACLテーブルにマッピングする用途で利用しています。
上記のLoader, Interpreter, Pipelinerをあわせて、pipeconfとしてまとめられ、netcfgで対象スイッチ毎に指定することが出来ます。

その他コンポーネント

チュートリアルでは、pipeconf以外のコンポーネントも同梱してビルドしています。
それぞれは以下の通りです。
  • Ipv6Routing: IPv6のルーティングテーブルを設定するためのコンポーネント
  • L2Bridging: L2スイッチングテーブルを設定するためのコンポーネント
  • NdpReply: NDPをハンドリングするためのコンポーネント(EXERCISE-4で利用)

P4で定義されているテーブルを確認

まず、今回適用するP4プログラム(main.p4)を確認していきます。
main.p4で定義されているテーブルは全部で以下の5つあります。
Name Match Fields Actions Descriptions
IngressPipeImpl.l2_exact_table hdr.ethernet.dst_addr IngressPipeImpl.set_egress_port
IngressPipeImpl.drop
L2ユニキャスト。宛先MACアドレスに対して特定ポートに送信する。
IngressPipeImpl.l2_ternary_table hdr.ethernet.dst_addr IngressPipeImpl.set_multicast_group
IngressPipeImpl.drop
L2マルチキャスト。マルチキャスト/ブロードキャストのMACアドレスに対してマルチキャストグループに送信する。
IngressPipeImpl.my_station_table hdr.ethernet.dst_addr NoAction 自局MAC用のテーブル。何もアクションはしないが条件分岐で用いるために定義している。
IngressPipeImpl.routing_v6_table hdr.ipv6.dst_addr IngressPipeImpl.set_next_hop
NoAction
ルーティングテーブル
IngressPipeImpl.acl_table standard_metadata.ingress_port
hdr.ethernet.dst_addr
hdr.ethernet.src_addr
hdr.ethernet.ether_type
hdr.ipv6.next_hdr
hdr.icmpv6.type
local_metadata.l4_src_port
local_metadata.l4_dst_port
IngressPipeImpl.send_to_cpu
IngressPipeImpl.clone_to_cpu
IngressPipeImpl.drop
NoAction
ACLテーブル。特定パケットをCPU宛に送信する(packet-in)

アプリケーションのビルド

ONOSのアプリケーション(ngsdn-tutorial)をコンパイルします。
Makefileで用意されているapp-buildを実行するとP4コンパイルからJavaコードのコンパイルまで一括で実施してくれます。
$ make app-build *** Building P4 program... docker run --rm -v /home/user/ngsdn-tutorial:/workdir -w /workdir opennetworking/p4c:stable \ p4c-bm2-ss --arch v1model -o p4src/build/bmv2.json \ --p4runtime-files p4src/build/p4info.txt --Wdisable=unsupported \ p4src/main.p4 *** P4 program compiled successfully! Output files are in p4src/build *** Copying p4c outputs to app resources... cp -f p4src/build/p4info.txt app/src/main/resources/ cp -f p4src/build/bmv2.json app/src/main/resources/ *** Building ONOS app... [INFO] Scanning for projects... [INFO] [INFO] -------------------< org.onosproject:ngsdn-tutorial >------------------- // 省略
app/target/ngsdn-tutorial-1.0-SNAPSHOT.oarというファイルが生成されていることを確認します。

ONOSの起動

onosはdocker-composeで起動しますが、そのままだとmininetも一緒に起動するためmininet部分を取り除いて以下のdocker-compose.ymlを作成しました。
version: "3" services: onos: image: onosproject/onos:2.2.0 hostname: onos container_name: onos ports: - "8181:8181" # HTTP - "8101:8101" # SSH (CLI) volumes: - ./tmp/onos:/root/onos/apache-karaf-4.2.6/data/tmp environment: - ONOS_APPS=gui,drivers.bmv2,lldpprovider,hostprovider
makeコマンドで起動させます。
$ make reset start docker-compose down -t0 Stopping onos ... done Removing onos ... done Removing network ngsdn-tutorial_default rm -rf ./tmp docker-compose up -d Creating network "ngsdn-tutorial_default" with the default driver Creating onos ... done

ONOS起動の確認

ブラウザから、http://server-address:8181/onos/ui でWEB UIを開くか、または以下のコマンドでkaraf CLIに接続します。

$ make onos-cli *** Connecting to the ONOS CLI... password: rocks *** Top exit press Ctrl-D Password: rocks Welcome to Open Network Operating System (ONOS)! ____ _ ______ ____ / __ \/ |/ / __ \/ __/ / /_/ / / /_/ /\ \ \____/_/|_/\____/___/ Documentation: wiki.onosproject.org Tutorials: tutorials.onosproject.org Mailing lists: lists.onosproject.org Come help out! Find out how at: contribute.onosproject.org Hit '<tab>' for a list of available commands and '[cmd] --help' for help on a specific command. Hit '<ctrl-d>' or type 'logout' to exit ONOS session. onos@root >

アプリケーションのインストール

先ほど作成した、ngsdn-tutorial-1.0-SNAPSHOT.oarをONOSにインストールします。
チュートリアルではMakefileで用意されていますが、内部ではWEB API経由でインストールしています。
$ make app-install *** Installing and activating app in ONOS... curl --fail -sSL --user onos:rocks --noproxy localhost -X POST -HContent-Type:application/octet-stream \ 'http://localhost:8181/onos/v1/applications?activate=true' \ --data-binary @app/target/ngsdn-tutorial-1.0-SNAPSHOT.oar {"name":"org.onosproject.ngsdn-tutorial","id":192,"version":"1.0.SNAPSHOT","category":"Traffic Steering","description":"Provides IPv6 routing capabilities to a leaf-spine network of P4 switches","readme":"Provides IPv6 routing capabilities to a leaf-spine network of P4 switches","origin":"p4.org","url":"http://www.onosproject.org","featuresRepo":"mvn:org.onosproject/ngsdn-tutorial/1.0-SNAPSHOT/xml/features","state":"ACTIVE","features":["ngsdn-tutorial"],"permissions":[],"requiredApps":["org.onosproject.drivers.bmv2","org.onosproject.lldpprovider","org.onosproject.hostprovider"]}

ネットワーク設定

ONOSでは、netcfgと呼ばれるネットワーク設定をWEb API経由で設定する機能があります。
これを用いて設定を入れていきます。流し込む設定ファイル(mininet/netcfg.json)は以下の通りです。
今回はmininetを用いていないため、オリジナルのものとはスイッチのアドレス等が変わっていることに注意してください。
{ "devices": { "device:leaf1": { "basic": { "managementAddress": "grpc://192.168.122.51:50001?device_id=1", "driver": "stratum-bmv2", "pipeconf": "org.onosproject.ngsdn-tutorial" }, "fabricDeviceConfig": { "myStationMac": "00:aa:00:00:00:01", "isSpine": false } }, "device:leaf2": { "basic": { "managementAddress": "grpc://192.168.122.52:50001?device_id=1", "driver": "stratum-bmv2", "pipeconf": "org.onosproject.ngsdn-tutorial" }, "fabricDeviceConfig": { "myStationMac": "00:aa:00:00:00:02", "isSpine": false } }, "device:spine1": { "basic": { "managementAddress": "grpc://192.168.122.101:50001?device_id=1", "driver": "stratum-bmv2", "pipeconf": "org.onosproject.ngsdn-tutorial" }, "fabricDeviceConfig": { "myStationMac": "00:bb:00:00:00:01", "isSpine": true } }, "device:spine2": { "basic": { "managementAddress": "grpc://192.168.122.102:50001?device_id=1", "driver": "stratum-bmv2", "pipeconf": "org.onosproject.ngsdn-tutorial" }, "fabricDeviceConfig": { "myStationMac": "00:bb:00:00:00:02", "isSpine": true } } }, "ports": { "device:leaf1/3": { "interfaces": [ { "name": "leaf1-3", "ips": ["2001:1:1::ff/64"] } ] }, "device:leaf1/4": { "interfaces": [ { "name": "leaf1-4", "ips": ["2001:1:1::ff/64"] } ] }, "device:leaf1/5": { "interfaces": [ { "name": "leaf1-5", "ips": ["2001:1:1::ff/64"] } ] }, "device:leaf1/6": { "interfaces": [ { "name": "leaf1-6", "ips": ["2001:1:2::ff/64"] } ] }, "device:leaf2/3": { "interfaces": [ { "name": "leaf2-3", "ips": ["2001:2:3::ff/64"] } ] }, "device:leaf2/4": { "interfaces": [ { "name": "leaf2-4", "ips": ["2001:2:4::ff/64"] } ] } }, "hosts": { "00:00:00:00:00:1A/None": { "basic": { "name": "h1a" } }, "00:00:00:00:00:1B/None": { "basic": { "name": "h1b" } }, "00:00:00:00:00:1C/None": { "basic": { "name": "h1c" } }, "00:00:00:00:00:20/None": { "basic": { "name": "h2" } }, "00:00:00:00:00:30/None": { "basic": { "name": "h3" } }, "00:00:00:00:00:40/None": { "basic": { "name": "h4" } } } }
以下コマンドで設定を流し込むことが出来ます。
$ make netcfg *** Pushing netcfg.json to ONOS... curl --fail -sSL --user onos:rocks --noproxy localhost -X POST -H 'Content-Type:application/json' \ http://localhost:8181/onos/v1/network/configuration -d@./mininet/netcfg.json

通信の確認

GNS3上のh1aとh1bのコンソールを開き、お互いに通信します。
  • h1a -> h1b
root@h1a:/# ping6 -c 4 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.25 ms 64 bytes from 2001:1:1::b: icmp_seq=2 ttl=64 time=1.30 ms 64 bytes from 2001:1:1::b: icmp_seq=3 ttl=64 time=1.16 ms 64 bytes from 2001:1:1::b: icmp_seq=4 ttl=64 time=1.28 ms --- 2001:1:1::b ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3003ms rtt min/avg/max/mdev = 1.163/1.250/1.302/0.053 ms
  • h1b -> h1a
root@h1b:/# ping6 -c 4 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.47 ms 64 bytes from 2001:1:1::a: icmp_seq=2 ttl=64 time=1.21 ms 64 bytes from 2001:1:1::a: icmp_seq=3 ttl=64 time=1.14 ms 64 bytes from 2001:1:1::a: icmp_seq=4 ttl=64 time=1.21 ms --- 2001:1:1::a ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3002ms rtt min/avg/max/mdev = 1.149/1.262/1.475/0.130 ms
また、h1aからh2には通信できないことを確認します。
root@h1a:/# ping6 2001:1:2::1 PING 2001:1:2::1(2001:1:2::1) 56 data bytes From 2001:1:1::a icmp_seq=1 Destination unreachable: Address unreachable From 2001:1:1::a icmp_seq=2 Destination unreachable: Address unreachable From 2001:1:1::a icmp_seq=3 Destination unreachable: Address unreachable From 2001:1:1::a icmp_seq=4 Destination unreachable: Address unreachable
別のセグメントへの通信をする場合はゲートウェイのMACアドレスを知るためにNDPを解決する必要がありますが、現時点ではNDPのハンドリングが未実装なため失敗します。
NDPのハンドリングは次回(EXERCISE 4)に実施予定です。

Flowの確認

ONOSのGUIやKaraf CLIからそれぞれのデバイスに登録されている実際のFlowを確認することが出来ます。
以下は、Leaf1のFlowを確認したものです。

例えばLeaf1にぶら下がっているh1a(MAC:00:00:00:00:00:1a, IP:2001:1:1::a)宛のフローを見てみます。
まず以下のフローにて、hdr.ipv6.dst_addrがh1aの場合に、Next Hopとしてh1aのMACアドレスが設定されています。
ADDED, bytes=0, packets=0, table=IngressPipeImpl.routing_v6_table, priority=10, selector=[hdr.ipv6.dst_addr=0x2001000100010000000000000000000a/128], treatment=[immediate=[GROUP:0x1a]]
次に、以下のフローにてh1aのMACアドレス宛通信がIngressPipeImpl.l2_exact_tableによって、ポート3に転送されることが確認出来ます。
ADDED, bytes=2414, packets=21, table=IngressPipeImpl.l2_exact_table, priority=10, selector=[hdr.ethernet.dst_addr=0x1a], treatment=[immediate=[IngressPipeImpl.set_egress_port(port_num=0x3)]]
このような流れで、他のルーターより到達したh1a宛てのパケットはh1aのぶら下がっているポート3に送信されていくことが分かります。
※ただし、現時点ではNDPハンドリングが実装されていないため、このフローが使われることはありません。

最後に

今回は、ONOSを用いてStratumが制御出来ることを確認しました。コントローラーからP4テーブルを動的に操作することで、様々な機能が実現でき非常に強力なものとなりそうです。ただし、P4やJavaでのコーディングやプロトコルに関する知識等が必要で学習コストは高めという印象です。
次回は、P4プログラムを改造してNDPハンドリングを実装し、別サブネット間の通信が出来ることを確認します。

参考