Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Masaru OKI
January 29, 2020

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

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

Masaru OKI

January 29, 2020
Tweet

More Decks by Masaru OKI

Other Decks in Technology

Transcript

  1. 自己紹介 名前: 沖 勝 (おき まさる) 所属: 株式会社インターネットイニシアティブ プロダクト本部 SDN開発部 ネットワーク基盤開発課

    主な業務: ホワイトボックススイッチを用いた、サービス基盤向け データプレーンの開発 3
  2. 実際に天板を開けてみた 7 スイッチASIC (Application Specific Integrated Circuit) PSU (Power Supply

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

    SSD 64GB) BMC (Board Management Controller)
  4. プログラマブル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
  5. プログラマブル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
  6. NPL • Network Programming Language • https://nplang.org/ ◦ 単に ‘NPL’

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

    • Parallelism • Advanced logical table capabilities • An integrated instrumentation plane • Simple, intuitive control flow 22
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. l2_switch通信の実際 • ip linkなどで確認するとわかるが、vethは作られていない。 ◦ test.pyは9090/tcpでbmodel.simと接続していた。 • つまり、実通信をさばくデータプレーンとして素直には使えない。 ◦ これはl3_appなど他のサンプルも同様。

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

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

    P4で想定されるPISAはmatch-actionをつないでいくモデル。 ◦ NPLは複数テーブルを同時に lookupし並列処理できるよう設計されている。 • 言語仕様 ◦ P4はP4-16で仕様が大幅に拡張され、よりソフトウェア的になっている。 (154ページ) ◦ 一方NPLは言語仕様がコンパクト。 (80ページ) • 周辺環境 ◦ P4は言語としてだけでなく P4Runtimeなど周辺環境も整備されつつある。 ◦ NPLは現在、言語仕様および仮想実行環境のみ公開されている。 34
  18. リファレンス • 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
  19. 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
  20. ヘッダグループと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
  21. 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
  22. 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
  23. 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
  24. 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
  25. extern • special_functionと似ている。外部機能をfunctionとして呼ぶ。 • Cのexternのように外部にあることを宣言する。 • 繰り返し何度も呼ばれる機能で使用される。 • 例 ◦

    パケットのdrop ◦ CPUあるいは他ポートへのパケットのコピー ◦ パケットカウンタ 49 extern packet_drop(in bit[1] trigger, in const value, in const drop_code);
  26. 値の表現 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
  27. 型 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
  28. 演算子 P4-14には除算がない。 NPL P4-14 P4-16 (参考)C 四則演算+剰余 +, *, -,

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