Share
Embed
NPLによるデータプレーンプログラミング
2019年6月にBroadcomより公開された、データプレーン用プログラミング言語NPL(Network Programming Language)について、概要や文法および実装の現状を紹介します。
Masaru OKI
January 29, 2020
Transcript
60 slides
-
Contents • 自己紹介 • スイッチ機器の構造 • データプレーンのプログラミング • NPL概説 •
まとめ • Appendix NPL詳解 • Appendix 2 他言語との比較 2 -
自己紹介 名前: 沖 勝 (おき まさる) 所属: 株式会社インターネットイニシアティブ プロダクト本部 SDN開発部 ネットワーク基盤開発課
主な業務: ホワイトボックススイッチを用いた、サービス基盤向け データプレーンの開発 3 -
スイッチ機器のハードウェア構造 高速パケット転送のためスイッチASICを搭載し モニターやキーボード接続を排して ストレージやメモリサイズを小さめにした サーバーPCのような構造。 6 CPU Module (CPU+Mem+Storage) スイッチASIC
(Application Specific Integrated Circuit) PSU (Power Supply Unit) FAN SFP (Small Form-factor Pluggable) -
実際に天板を開けてみた 7 スイッチASIC (Application Specific Integrated Circuit) PSU (Power Supply
Unit) FAN SFP (Small Form-factor Pluggable) CPU Module (CPU+Mem+Storage) -
CPU ModuleとBMC 8 CPU Memory (8GB DDR4 SO-DIMM) Storage (M.2
SSD 64GB) BMC (Board Management Controller) -
コントロールプレーンとデータプレーン スコープによってどこをそう呼ぶかが変わるので注意 9 サーバー = コントロールプレーン スイッチ = データプレーン CPU
Module = コントロールプレーン スイッチASIC = データプレーン -
スイッチASICによるパケット処理 • 通常のスイッチASICにおけるパケット処理の内部ブロック(概略) • ブロックはあらかじめ用意されていて処理順(パイプライン)は固定 • 各ブロック用のテーブルの種類や最大サイズも基本的に固定 • できることがあらかじめはっきりしていて機能追加はできない 13
packet in packet out parser match drop copy forward edit TCAM, SRAM ACL, LPM, etc. VLAN, IPv4, IPv6 TCP, etc. dst mac書換 VLAN変更 トンネル処理, etc. multicast, ACL, etc. -
一般のスイッチ製品でできないことは? • どうやれば実現できる? • スイッチASICに機能が用意されていれば、コントロールプレーン次第 ◦ たとえばホワイトボックススイッチ用 OS • なかったら、無理?
◦ ソフトウェアで頑張ると遅い 14 単なるパケット転送ではなく、 通常のスイッチ製品にない機能を 実現したい ??? -
プログラマブルASIC • 従来のスイッチASICと比較して、 柔軟に構成変更ができ、ブロックの組み合わせやテーブルの内容、 サイズをカスタマイズできるASICが考案され、市場に出始めた。 • プログラマブルASICと呼ばれる。有名なのはBarefoot Tofino。 • FPGAのように回路を組むのではなく、パケット処理に特化した
プログラミング言語を用いてブロック、テーブル、処理内容を記述する。 • 従来のASIC制御(C言語によるSDKのAPI呼び出し)が用意される製品も。 15 -
プログラマブルASICを使うと • パイプラインに処理ブロックを追加し、独自の処理を加えられる • parserで処理できるパケットヘッダも自由に定義できる • テーブルも自由に定義し、サイズも変更できる 16 packet in
packet out parser match drop copy forward edit special action -
プログラマブルASIC 製品あれこれ • Merchant Silicon ◦ 2014 Cavium XPliant ◦
2016 Barefoot (Intel) Tofino ◦ 2017 Broadcom Trident 3 ◦ 2017 Mellanox Spectrum-2 ◦ 2019 Broadcom Jericho2 ◦ 2019 Broadcom Trident 4 ◦ 2019 Marvell Prestera CX • Custom Silicon ◦ Juniper Trio ◦ Cisco UADP 17 -
プログラマブルASICの開発手段 • Cavium XPliant ◦ 独自言語によるparserおよびテーブル定義、Excelシートによるヘッダ定義、C言語によるアクション定義 ◦ NDAベースで、開発環境が一般公開されることはないままチップがdiscon • Barefoot
Tofino ◦ P4言語でparser、ヘッダ、テーブル、アクションを定義 • Mellanox Spectrum-2 ◦ P4(予定) • Marvell Prestera CX ◦ 不明 (SDKのAPIによる制御?) • Broadcom Trident 3 ◦ ソフトウェア開発者にはファームウェアバイナリが支給されるのみ ◦ 実質的にプログラマブルではなく機能が固定されたASICの扱い • Broadcom Trident 4 ◦ NPLでparser、ヘッダ、テーブル、アクションを定義(予定) 18 -
NPL • Network Programming Language • https://nplang.org/ ◦ 単に ‘NPL’
で検索すると、同名の別言語が引っ掛かるので注意 • Broadcomが開発した、オープンなデータプレーンプログラミング言語。 • 2019年6月に公開。仕様の最新バージョンは1.3 • 仕様書は表紙・目次含め80ページ。 • ターゲットはプログラマブルASIC、NIC、FPGA、ソフトウェアスイッチ。 • Broadcom Trident 4とJericho 2が対応予定。 20 -
イベント https://nplang.org/npl/events-list/ • OnCon Asia 2019: SDKLT and NPL •
SIGCOMM 2019: Building Efficient Packet Processing Flows with NPL • ONF CONNECT 2019: Building Efficient Network Stack with SDKLT and NPL 21 -
NPLの特徴 公式ページに記載されている特徴 • Customized table pipelines • Intelligent action processing
• Parallelism • Advanced logical table capabilities • An integrated instrumentation plane • Simple, intuitive control flow 22 -
NPLソースプログラム(例) program l2_switch { ing.usage_mode_create(...); ing.execute(); parse_begin(start); port_table_lookup(0); l2_host_table_lookup(0); resolve_destination();
do_packet_modif(); egr.usage_mode_create(...); egr.execute(); } struct l2_t { } parser_node start { extract_fields(ing_pkt.l2); } bus cmdbus_t cmdbus; logical_table port_table { keys { … } fields { … } } logical_table l2_host_table { } function resolve_destination() { } function do_packet_modif() { } パケットヘッダを定義。 パケットヘッダ 解析ツリーを定義。 key-fieldテーブルを定義。 処理を定義。 ・解析結果を変数に代入 ・パケットヘッダの変更 変数(メタデータ)を定義。 プログラムを定義。 受信パケットの一連の処理を記述。 パケット処理の起点。 23 -
NPLによるデータプレーン開発の流れ NPLソース プログラム (*.npl) コンパイラ ツールチェイン (nlc, g++) バイナリ イメージ
ASIC あるいはエミュレータ 25 -
NPLを実際に試せる環境 • https://github.com/nplang/NPL-Tutorials • VirtualBox用のVMイメージ(NCSC_June_2019.ova)が配布されている。 ◦ インポートして使う。 ◦ Ubuntu 16.04LTSにNPL開発環境、エミュレータ、サンプルがインストールされている。
◦ NPLツール類はMITライセンス。 ◦ ユーザnpl、パスワードnplでログインする。 ◦ VMイメージ内に用意されているサンプル NPLプログラム ▪ npl_tidbits (NPLの構成要素を一通り並べたもの ) ▪ l2_switch ▪ l3_app • Trident4搭載スイッチ製品は、2020年に出荷される模様。 26 -
VM環境でl2_switchをビルドする export NPL_EXAMPLES=/home/npl/ncsc-1.3.3rc4/examples cd $NPL_EXAMPLES/l2_switch make fe_nplsim make nplsim_comp C++ソースコードを生成する。
生成されたコードはSystemCを呼び出す。 NPLコンパイラは nlc C++ソースコードをコンパイルし、 ネイティブ実行ファイルを生成。 fe_output/bmodel/bin/bmodel.sim 27 -
l2_switchを実行する make nplsim_run • 実行するとターミナルウィンドウが2つ開く。BMODELとBMCLI。 ◦ BMODELでは、ビルドしたバイナリ bmodel.simが起動する。 ◦ BMCLIではPythonで作られたシェルbmif_cli.pyが起動する。bmodel.simとsocket通信する。
• BMCLIで下記を実行すると、テーブルエントリ情報をBMODELに流し込む。 rcload /home/npl/ncsc-1.3.3rc4/examples/l2_switch/bm_tests/l2_test/tbl_cfg.txt • makeを実行したターミナルで下記を実行すると、 BMODELにパケットを送り込み、動作を確認できる。 python bm_tests/l2_test/test.py 28 -
l2_switchソースコード /home/npl/ncsc-1.3.3rc4/examples/l2_switch/npl の下にある。 config.iniにはコンパイラに渡すパラメータ(warning許容数など)が書かれている。 ~/ncsc-1.3.3rc4/examples/l2_switch$ ls -l npl total 28
-r--r--r-- 1 npl npl 284 Jun 7 13:51 config.ini -r-xr-xr-x 1 npl npl 2598 Jun 7 13:51 l2_bus.npl -r--r--r-- 1 npl npl 496 Jun 7 13:51 l2_header_format.npl -r-xr-xr-x 1 npl npl 2684 Jun 7 13:51 l2_parser.npl -r-xr-xr-x 1 npl npl 789 Jun 7 13:51 l2_sf_defines.npl -r-xr-xr-x 1 npl npl 7851 Jun 7 13:51 l2_switch.npl 30 -
l2_switch通信の実際 • ip linkなどで確認するとわかるが、vethは作られていない。 ◦ test.pyは9090/tcpでbmodel.simと接続していた。 • つまり、実通信をさばくデータプレーンとして素直には使えない。 ◦ これはl3_appなど他のサンプルも同様。
• 現状提供されている環境は、単体テスト用と割り切って使うしかない。 ◦ mininetで動かせるといいのだが …… • NPLソースコードの問題ではない。ツールチェインとライブラリ次第。 • 9090/tcpで通信し、vethとの間でパケットを橋渡しするプログラムを書けば仮想環 境でパケット転送を含め動作確認できるかもしれない。 31 -
NPLでできないこと • NPLの言語仕様の範囲で記述できない機能がある。たとえば ◦ パケットとは独立して保持する情報 (カウンターなど) ◦ キュー ◦ 優先制御
◦ 送出ポート選択 • NPLでは、外部にこれらを処理する機能ブロックが用意されていれば special_functionを定義して、NPLプログラムから呼び出すことができる。 実際にはベンダーがspecial_functionの情報を開発者に提供するだろう。 • special_functionは実装依存な点に注意。 32 -
special_functionの例 program l2_switch { ing.usage_mode_create(...); ing.execute(); parse_begin(start); port_table_lookup(0); l2_host_table_lookup(0); resolve_destination();
do_packet_modif(); egr.usage_mode_create(...); egr.execute(); } struct l2_t { } parser_node start { extract_fields(ing_pkt.l2); } bus cmdbus_t cmdbus; logical_table port_table { keys { … } fields { … } } logical_table l2_host_table { } function resolve_destination() { } function do_packet_modif() { } 33 program l2_switchの中で太字の部分が special_functionであり、 この例では示されていないが special_functionの宣言が必要。 用意されたサンプルにも存在する。 -
NPLはP4と何が違うのか • 開発の経緯 ◦ P4はスタンフォード大学の学術研究を発端として産学共同で開発・発展している。 ◦ NPLは(P4を参考にしつつ)企業(Broadcom)によって開発された。 • モデル ◦
P4で想定されるPISAはmatch-actionをつないでいくモデル。 ◦ NPLは複数テーブルを同時に lookupし並列処理できるよう設計されている。 • 言語仕様 ◦ P4はP4-16で仕様が大幅に拡張され、よりソフトウェア的になっている。 (154ページ) ◦ 一方NPLは言語仕様がコンパクト。 (80ページ) • 周辺環境 ◦ P4は言語としてだけでなく P4Runtimeなど周辺環境も整備されつつある。 ◦ NPLは現在、言語仕様および仮想実行環境のみ公開されている。 34 -
まとめ • 2019年6月に、BroadcomからNPLが公開された。 • NPLはデータプレーン用プログラミング言語。 • 適用できるハードウェアはまだ市場にはない。 • 提供されるソフトウェア実装はテスト用のみだが、単体テストには十分。 •
P4_16と比べると仕様はシンプル。(といっても80ページ) • 競争が生まれることによる市場の活性化・健全化を期待したい。 36 -
リファレンス • NPL https://nplang.org/ • NPL github https://github.com/nplang/ • BCM56880
(Broadcom Trident 4) https://jp.broadcom.com/products/ethernet-connectivity/switching/strataxgs/bcm56880-series • P4 https://p4.org/ 37 -
NPLプログラムの構造 1. struct(パケットヘッダ)を定義する 2. ヘッダグループ用structとpacketを定義する 3. parser_nodeを定義する 4. logical_busを定義する 5.
logical_tableを定義する 6. 複雑さなど必要に応じてfunctionを定義する 7. programを定義する 40 -
struct(パケットヘッダ) • パケットヘッダを定義する。 • fieldsの内側にヘッダフィールドを並べる。 • bit(1bit)、bit[](固定長bit列), varbit(可変長bit列) • varbitを使う場合ヘッダ長計算式を別途指定。
• overlays指定でunionのようなことができる。 struct ipv4_t { fields { bit[8] vh: bit[8] tos; bit[32] sa; bit[32] da; varbit[320] option; } overlays { version: vh[7:4]; hdr_len: vh[3:0]; } header_length_exp: hdr_len*4; } struct vlan_t { fields { bit[3] pcp; bit cfi; bit[12] vid; bit[16] ethertype; } } 41 -
ヘッダグループとpacket • 複数のstructをまとめたものをヘッダグループと呼ぶ。ヘッダグループの定義も struct。 • ipv4とipv6など、一方のみ有効となるヘッダもまとめる。 (選択はparserにより実行) • ヘッダグループのfieldに書けるのはstructのみ。bitやvarbitを書くことはできない。 •
parseする最初のヘッダグループを指定し、 packetを定義する。 struct l2_t { fields { macs_t macs; vlan_t ctag; ethertype_t etype; } } struct l3_t { fields { ipv4_t ipv4; ipv6_t ipv6; } } struct ingress_packet_t { fields { l2_t l2; vlan_t vlan; l3_t l3; } } packet ingress_packet_t ing_pkt; 42 ヘッダグループ packet -
parser_node • root_node: 1を指定したノードから順に解析していく。 • extract_fields(packet.header)でパケットヘッダの値を参照できるようにする。 • メンバーの値を参照し、 if else、switchで分岐する。
• next_nodeで次のノードの解析。 end_nodeで解析終了。 • parse_break, parse_continue parser_node start { root_node: 1; next_node ethernet; } parser_node ethernet { extract_fields(ing_pkt.l2); switch (latest.ethertype) { 0x8100 : {next_node ctag}; default: {next_node ingress}; } } parser_node ctag { extract_fields(ing_pkt.vlan); next_node ingress; } parser_node ingress { end_node : 1; } 44 -
logical_bus • フィールドの集まり、すなわち変数を定義する。 • structを指定することで作成できる。 • parser_nodeの中で参照でき、logical_tableやfunction(後述)の中で代入できる。 struct obj_bus_t {
fields { bit[16] port; bit[16] dst; bit dst_discard; bit[16] src; bit src_discard; bit[16] vid; } } bus obj_bus_t objbus; 45 -
logical_table • match actionテーブルを定義する。テーブルタイプは index, tcam, hash, alpm。(拡張可能) • キー(keys)はbitあるいはbit配列で指定。(structは書けない)
• 値(fields)はbit、bit配列あるいはauto_enumで定義する。(structは書けない) • key_construct()でパケットヘッダからキーを作る式を指定。 • field_assignment()でフィールドの値をlogical busに代入できる。 • 同一テーブルを2種の用途で使い分けできる。 lookup(0)とlookup(1)。 logical_table my_station_hit { table_type : tcam; maxsize : 512; minsize : 512; keys { bit[48] macda; bit[12] vid; } fields { bit[2] mpls_tunnel_type; bit local_l3_host; } key_construct() { macda = ing_pkt.l2.macs.macda; vid = obj_bus.vlan_id; } } 46 -
function, program • 処理のかたまりを定義する。パラメータを与えることはできない。 • 全ての演算を記述でき、全ての logical busを操作できる(パラメータ相当はlogical bus経由)。 •
parser, logical table lookup, editorなどを呼び出せる。 • programは最初に呼び出されるロジック。 parse_begin()でヘッダ解析開始。 function push_vlan_tag() { egr_pkt.vlan.vid = 100; egr_pkt.vlan.tpid = 0x8100; add_header(egr_pkt.vlan); } program app { parse_begin(start); my_station_hit.lookup(0); if (ing_pkt.l3.ipv4._PRESENT) { push_vlan_tag(); } } 47 -
special_function • ターゲットデバイス依存の機能を呼び出す。ベンダにより提供される。 ◦ チップの内蔵回路で実現される機能、既存の実装済みパケット処理ロジック ◦ パケットに含まれない (しかしパケット処理に必要な )情報の取得、設定 •
たとえばポート番号の情報をspecial_functionを用いて取得する。 special_function iarb { usage_mode_create(in const eindex, out bit[4] otpid_enable, out bit[1] ts_enable, out bit[7] port_num, out bit[2] port_type_cfg, out bit[16] currenttime ); usage_mode_select(in bit[1] eindex); } program app { iarb.usage_mode_create( 0, control_id.otpid_enable, control_id.ts_enable, obj_bus.port_num, obj_bus.port_type_cfg, time_bus.currenttime ); iarb.execute(); ... 48 -
extern • special_functionと似ている。外部機能をfunctionとして呼ぶ。 • Cのexternのように外部にあることを宣言する。 • 繰り返し何度も呼ばれる機能で使用される。 • 例 ◦
パケットのdrop ◦ CPUあるいは他ポートへのパケットのコピー ◦ パケットカウンタ 49 extern packet_drop(in bit[1] trigger, in const value, in const drop_code); -
Editor パケットの編集機能。functionから呼び出す。 • add_header • delete_header • replace_header_field • create_checksum
• update_packet_length 50 -
その他の構文 • コメントはC++同様の /* … */ および // • C言語ライクなプリプロセッサ
◦ #include ◦ #ifdef …#else … #endif ◦ #define 51 -
NPLにないもの • 統計情報収集 • キュー制御 • メーター制御 • ポート制御 externやspecial_functionで機能が提供されるか、
あるいはNPLで書ける範囲外のところで制御すると思われる。 52 -
値の表現 NPLは10進数と16進数のみでシンプル。負数がない。 NPL P4-14 P4-16 (参考)C 10進数 42 42 42
42 2進数 0b101010 0b101010 8進数 0o52 052 16進数 0x2a 0x2a 0x2a 0x2a ビット幅指定 (fieldに合わせる) 8’42 8w42 (fieldに合わせる) マイナス値 -42 -42 -42 54 -
型 NPLにはtypedefがなく、整数や文字列は定数のみ。 NPL P4-14 P4-16 (参考)C ビット bit var; var:
1; bit var; int var: 1; ビット列 bit[16] var; var: 16; bit<16> var; int var:16; 可変長ビット列 varbit[32] var; var: *; varbit<16> var; 32bit符号なし整数 constのみ uint32_t var; int<32> var; uint32_t var; 文字列 print(即値);のみ string var; char [] var; 型名定義 (#define) typedef typedef typedef 55 -
演算子 P4-14には除算がない。 NPL P4-14 P4-16 (参考)C 四則演算+剰余 +, *, -,
/, % +, *, - +, *, -, /, % +, *, -, /, % ビット演算 &, |, ^ &, |, ^ &, |, ^ &, |, ^ ブール演算 ||, && or, and or, and, ||, && ||, && 比較演算 !=, ==, <. >, <=, >= !=, ==, <. >, <=, >= !=, ==, <. >, <=, >= ne, eq, le, ge !=, ==, <. >, <=, >= 56 -
制御構文 for, whileなどの繰り返し構文はNPL, P4ともに用意されていない。 NPL P4-14 P4-16 (参考)C 条件分岐 if
(expr) { func; } else { func; } if (expr) { func; } else { func; } if (expr) { func; } else { func; } if (expr) { func; } else { func; } 値による分岐 switch (expr) { 値1: { … } default: { … } } (parser内のみ) return select (expr) { 値1: name; default: ... } switch (expr) { 値1: { … } default: { … } } switch (expr) { case 値1: …; break; default: …; } 57 -
パケットヘッダ定義 どれもほぼ似通っている。 struct ipv4_t { fields { bit[4] version; bit[4]
hdr_len; bit[8] tos; bit[32] sa; bit[32] da; varbit[320] option; } header_length_exp:hdr_len*4; } header_type ipv4_t { fields { version : 4; hdr_len : 4; tos : 8; sa : 32; da : 32; option : *; } length: hdr_len*4; } header ipv4_t { bit<4> version; bit<4> hdr_len; bit<8> tos; bit<32> sa; bit<32> da; varbit<320> option; } NPL P4-14 P4-16 58 -
parser parser_node start { root_node: 1; next_node ethernet; } parser_node
ethernet { extract_fields(ing_pkt.l2); switch (latest.ethertype) { 0x8100 : {next_node ctag}; default : {next_node ingress}; } } parser_node ctag { extract_fields(ing_pkt.vlan); next_node ingress; } parser_node ingress { end_node : 1; } NPL P4-14 P4-16 parser ethernet { extract(l2); return select (latest.ethertype) { 0x8100 : ctag; default : ingress; } } parser ctag { extract(vlan); return ingress; } parser GenericParser(packet_in b, out Packet_header p) { state start { b.extract(p.l2); transition select (p.l2.ethertype) { 16w0x8100 : ctag; } } state ctag { b.extract(p.vlan); accept; } 59 -
テーブル定義 P4はmatchと組にする選択可能なactionを書く。NPLは値。 logical_table my_station_hit { table_type : tcam; maxsize :
512; minsize : 512; keys { bit[48] macda; bit[12] vid; } fields { bit[2] mpls_tunnel_type; bit local_l3_host; } key_construct() { macda = ing_pkt.l2.macda; vid = obj_bus.vlan_id; } } NPL P4-14 P4-16 table my_station_hit { reads { l2.macs.macda: exact; l2.vlan.vlan_id: exact; } actions { add_mTag; common_copy_pkt_to_cpu; no_op; } min_size : 512; max_size : 512; } table my_station_hit { key - { l2.macs.macda: exact; l2.vlan.vlan_id: exact; } actions - { add_mTag; common_copy_pkt_to_cpu; no_op; } default_action - no_op; } 60