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 上で構成すると以下のようになります。
確認項目
今回確認したい動作は、以下の2点です。- 同一サブネット内のホスト間で通信可能なこと(h1a, h1b, h1cでの通信確認)
- 別サブネットのホスト間では通信出来ないこと(ルーティングテーブルは正しく設定されているが、NDPハンドリングが未実装なため)
ONOS内部のコンポーネント
今回のチュートリアル内で利用するONOSアプリケーションは以下のようなコンポーネントから構成されています。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ハンドリングを実装し、別サブネット間の通信が出来ることを確認します。