NPLによるデータプレーンプログラミング

Fab252f33476e4b269b402cbcb6d4db2?s=47 Masaru OKI
January 29, 2020

 NPLによるデータプレーンプログラミング

2019年6月にBroadcomより公開された、データプレーン用プログラミング言語NPL(Network Programming Language)について、概要や文法および実装の現状を紹介します。

Fab252f33476e4b269b402cbcb6d4db2?s=128

Masaru OKI

January 29, 2020
Tweet

Transcript

  1. NPLによる データプレーンプログラミング ネットワークプログラマビリティ勉強会 #19 Jan 29, 2020 Masaru OKI m-oki@iij.ad.jp

  2. Contents • 自己紹介 • スイッチ機器の構造 • データプレーンのプログラミング • NPL概説 •

    まとめ • Appendix NPL詳解 • Appendix 2 他言語との比較 2
  3. 自己紹介 名前: 沖 勝 (おき まさる) 所属: 株式会社インターネットイニシアティブ プロダクト本部 SDN開発部 ネットワーク基盤開発課

    主な業務: ホワイトボックススイッチを用いた、サービス基盤向け データプレーンの開発 3
  4. 調査の動機 一般のスイッチ製品でできないことを、実現したい。 ソフトウェア処理よりも高速に。 データプレーンのプログラミングで実現できるか調査する。 4 単なるパケット転送ではなく、 通常のスイッチ製品にない機能を 実現したい

  5. スイッチ機器の構造 5

  6. スイッチ機器のハードウェア構造 高速パケット転送のためスイッチASICを搭載し モニターやキーボード接続を排して ストレージやメモリサイズを小さめにした サーバーPCのような構造。 6 CPU Module (CPU+Mem+Storage) スイッチASIC

    (Application Specific Integrated Circuit) PSU (Power Supply Unit) FAN SFP (Small Form-factor Pluggable)
  7. 実際に天板を開けてみた 7 スイッチASIC (Application Specific Integrated Circuit) PSU (Power Supply

    Unit) FAN SFP (Small Form-factor Pluggable) CPU Module (CPU+Mem+Storage)
  8. CPU ModuleとBMC 8 CPU Memory (8GB DDR4 SO-DIMM) Storage (M.2

    SSD 64GB) BMC (Board Management Controller)
  9. コントロールプレーンとデータプレーン スコープによってどこをそう呼ぶかが変わるので注意 9 サーバー = コントロールプレーン スイッチ = データプレーン CPU

    Module = コントロールプレーン スイッチASIC = データプレーン
  10. SDNの文脈だと通常はこちら OpenFlowなどでおなじみ 10 サーバー = コントロールプレーン スイッチ = データプレーン

  11. 今回の対象はこちら 11 CPU Module = コントロールプレーン スイッチASIC = データプレーン

  12. データプレーンのプログラミング 12

  13. スイッチ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.
  14. 一般のスイッチ製品でできないことは? • どうやれば実現できる? • スイッチASICに機能が用意されていれば、コントロールプレーン次第 ◦ たとえばホワイトボックススイッチ用 OS • なかったら、無理?

    ◦ ソフトウェアで頑張ると遅い 14 単なるパケット転送ではなく、 通常のスイッチ製品にない機能を 実現したい ???
  15. プログラマブルASIC • 従来のスイッチASICと比較して、 柔軟に構成変更ができ、ブロックの組み合わせやテーブルの内容、 サイズをカスタマイズできるASICが考案され、市場に出始めた。 • プログラマブルASICと呼ばれる。有名なのはBarefoot Tofino。 • FPGAのように回路を組むのではなく、パケット処理に特化した

    プログラミング言語を用いてブロック、テーブル、処理内容を記述する。 • 従来のASIC制御(C言語によるSDKのAPI呼び出し)が用意される製品も。 15
  16. プログラマブルASICを使うと • パイプラインに処理ブロックを追加し、独自の処理を加えられる • parserで処理できるパケットヘッダも自由に定義できる • テーブルも自由に定義し、サイズも変更できる 16 packet in

    packet out parser match drop copy forward edit special action
  17. プログラマブル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
  18. プログラマブル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
  19. NPL概説 19

  20. NPL • Network Programming Language • https://nplang.org/ ◦ 単に ‘NPL’

    で検索すると、同名の別言語が引っ掛かるので注意 • Broadcomが開発した、オープンなデータプレーンプログラミング言語。 • 2019年6月に公開。仕様の最新バージョンは1.3 • 仕様書は表紙・目次含め80ページ。 • ターゲットはプログラマブルASIC、NIC、FPGA、ソフトウェアスイッチ。 • Broadcom Trident 4とJericho 2が対応予定。 20
  21. イベント 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
  22. NPLの特徴 公式ページに記載されている特徴 • Customized table pipelines • Intelligent action processing

    • Parallelism • Advanced logical table capabilities • An integrated instrumentation plane • Simple, intuitive control flow 22
  23. 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
  24. l2_switchサンプルプログラム https://github.com/nplang/NPL-Example-Applications/tree/master/Layer-2 24

  25. NPLによるデータプレーン開発の流れ NPLソース プログラム (*.npl) コンパイラ ツールチェイン (nlc, g++) バイナリ イメージ

    ASIC あるいはエミュレータ 25
  26. 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
  27. 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
  28. 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
  29. l2_switch実行画面 BMCLI bmif_cli.py BMODEL bmodel.sim 29 test.py

  30. 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
  31. l2_switch通信の実際 • ip linkなどで確認するとわかるが、vethは作られていない。 ◦ test.pyは9090/tcpでbmodel.simと接続していた。 • つまり、実通信をさばくデータプレーンとして素直には使えない。 ◦ これはl3_appなど他のサンプルも同様。

    • 現状提供されている環境は、単体テスト用と割り切って使うしかない。 ◦ mininetで動かせるといいのだが …… • NPLソースコードの問題ではない。ツールチェインとライブラリ次第。 • 9090/tcpで通信し、vethとの間でパケットを橋渡しするプログラムを書けば仮想環 境でパケット転送を含め動作確認できるかもしれない。 31
  32. NPLでできないこと • NPLの言語仕様の範囲で記述できない機能がある。たとえば ◦ パケットとは独立して保持する情報 (カウンターなど) ◦ キュー ◦ 優先制御

    ◦ 送出ポート選択 • NPLでは、外部にこれらを処理する機能ブロックが用意されていれば special_functionを定義して、NPLプログラムから呼び出すことができる。 実際にはベンダーがspecial_functionの情報を開発者に提供するだろう。 • special_functionは実装依存な点に注意。 32
  33. 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の宣言が必要。 用意されたサンプルにも存在する。
  34. NPLはP4と何が違うのか • 開発の経緯 ◦ P4はスタンフォード大学の学術研究を発端として産学共同で開発・発展している。 ◦ NPLは(P4を参考にしつつ)企業(Broadcom)によって開発された。 • モデル ◦

    P4で想定されるPISAはmatch-actionをつないでいくモデル。 ◦ NPLは複数テーブルを同時に lookupし並列処理できるよう設計されている。 • 言語仕様 ◦ P4はP4-16で仕様が大幅に拡張され、よりソフトウェア的になっている。 (154ページ) ◦ 一方NPLは言語仕様がコンパクト。 (80ページ) • 周辺環境 ◦ P4は言語としてだけでなく P4Runtimeなど周辺環境も整備されつつある。 ◦ NPLは現在、言語仕様および仮想実行環境のみ公開されている。 34
  35. まとめ 35

  36. まとめ • 2019年6月に、BroadcomからNPLが公開された。 • NPLはデータプレーン用プログラミング言語。 • 適用できるハードウェアはまだ市場にはない。 • 提供されるソフトウェア実装はテスト用のみだが、単体テストには十分。 •

    P4_16と比べると仕様はシンプル。(といっても80ページ) • 競争が生まれることによる市場の活性化・健全化を期待したい。 36
  37. リファレンス • 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
  38. Appendix: NPL詳解 38

  39. NPLが想定するブロックダイヤグラム NPL v1.3仕様書 12ページより 39

  40. NPLプログラムの構造 1. struct(パケットヘッダ)を定義する 2. ヘッダグループ用structとpacketを定義する 3. parser_nodeを定義する 4. logical_busを定義する 5.

    logical_tableを定義する 6. 複雑さなど必要に応じてfunctionを定義する 7. programを定義する 40
  41. 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
  42. ヘッダグループと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
  43. header, header groupとpacketの関係 • NPL v1.3仕様書 21ページより。 • この場合、Header Groupをまとめたstructをpacketとして定義する。

    43
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. extern • special_functionと似ている。外部機能をfunctionとして呼ぶ。 • Cのexternのように外部にあることを宣言する。 • 繰り返し何度も呼ばれる機能で使用される。 • 例 ◦

    パケットのdrop ◦ CPUあるいは他ポートへのパケットのコピー ◦ パケットカウンタ 49 extern packet_drop(in bit[1] trigger, in const value, in const drop_code);
  50. Editor パケットの編集機能。functionから呼び出す。 • add_header • delete_header • replace_header_field • create_checksum

    • update_packet_length 50
  51. その他の構文 • コメントはC++同様の /* … */ および // • C言語ライクなプリプロセッサ

    ◦ #include ◦ #ifdef …#else … #endif ◦ #define 51
  52. NPLにないもの • 統計情報収集 • キュー制御 • メーター制御 • ポート制御 externやspecial_functionで機能が提供されるか、

    あるいはNPLで書ける範囲外のところで制御すると思われる。 52
  53. Appendix 2: 他言語との比較 53

  54. 値の表現 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
  55. 型 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
  56. 演算子 P4-14には除算がない。 NPL P4-14 P4-16 (参考)C 四則演算+剰余 +, *, -,

    /, % +, *, - +, *, -, /, % +, *, -, /, % ビット演算 &, |, ^ &, |, ^ &, |, ^ &, |, ^ ブール演算 ||, && or, and or, and, ||, && ||, && 比較演算 !=, ==, <. >, <=, >= !=, ==, <. >, <=, >= !=, ==, <. >, <=, >= ne, eq, le, ge !=, ==, <. >, <=, >= 56
  57. 制御構文 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
  58. パケットヘッダ定義 どれもほぼ似通っている。 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
  59. 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
  60. テーブル定義 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