Slide 1

Slide 1 text

NPLによる データプレーンプログラミング ネットワークプログラマビリティ勉強会 #19 Jan 29, 2020 Masaru OKI [email protected]

Slide 2

Slide 2 text

Contents ● 自己紹介 ● スイッチ機器の構造 ● データプレーンのプログラミング ● NPL概説 ● まとめ ● Appendix NPL詳解 ● Appendix 2 他言語との比較 2

Slide 3

Slide 3 text

自己紹介 名前: 沖 勝 (おき まさる) 所属: 株式会社インターネットイニシアティブ プロダクト本部 SDN開発部 ネットワーク基盤開発課 主な業務: ホワイトボックススイッチを用いた、サービス基盤向け データプレーンの開発 3

Slide 4

Slide 4 text

調査の動機 一般のスイッチ製品でできないことを、実現したい。 ソフトウェア処理よりも高速に。 データプレーンのプログラミングで実現できるか調査する。 4 単なるパケット転送ではなく、 通常のスイッチ製品にない機能を 実現したい

Slide 5

Slide 5 text

スイッチ機器の構造 5

Slide 6

Slide 6 text

スイッチ機器のハードウェア構造 高速パケット転送のためスイッチASICを搭載し モニターやキーボード接続を排して ストレージやメモリサイズを小さめにした サーバーPCのような構造。 6 CPU Module (CPU+Mem+Storage) スイッチASIC (Application Specific Integrated Circuit) PSU (Power Supply Unit) FAN SFP (Small Form-factor Pluggable)

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

CPU ModuleとBMC 8 CPU Memory (8GB DDR4 SO-DIMM) Storage (M.2 SSD 64GB) BMC (Board Management Controller)

Slide 9

Slide 9 text

コントロールプレーンとデータプレーン スコープによってどこをそう呼ぶかが変わるので注意 9 サーバー = コントロールプレーン スイッチ = データプレーン CPU Module = コントロールプレーン スイッチASIC = データプレーン

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

スイッチ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.

Slide 14

Slide 14 text

一般のスイッチ製品でできないことは? ● どうやれば実現できる? ● スイッチASICに機能が用意されていれば、コントロールプレーン次第 ○ たとえばホワイトボックススイッチ用 OS ● なかったら、無理? ○ ソフトウェアで頑張ると遅い 14 単なるパケット転送ではなく、 通常のスイッチ製品にない機能を 実現したい ???

Slide 15

Slide 15 text

プログラマブルASIC ● 従来のスイッチASICと比較して、 柔軟に構成変更ができ、ブロックの組み合わせやテーブルの内容、 サイズをカスタマイズできるASICが考案され、市場に出始めた。 ● プログラマブルASICと呼ばれる。有名なのはBarefoot Tofino。 ● FPGAのように回路を組むのではなく、パケット処理に特化した プログラミング言語を用いてブロック、テーブル、処理内容を記述する。 ● 従来のASIC制御(C言語によるSDKのAPI呼び出し)が用意される製品も。 15

Slide 16

Slide 16 text

プログラマブルASICを使うと ● パイプラインに処理ブロックを追加し、独自の処理を加えられる ● parserで処理できるパケットヘッダも自由に定義できる ● テーブルも自由に定義し、サイズも変更できる 16 packet in packet out parser match drop copy forward edit special action

Slide 17

Slide 17 text

プログラマブル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

Slide 18

Slide 18 text

プログラマブル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

Slide 19

Slide 19 text

NPL概説 19

Slide 20

Slide 20 text

NPL ● Network Programming Language ● https://nplang.org/ ○ 単に ‘NPL’ で検索すると、同名の別言語が引っ掛かるので注意 ● Broadcomが開発した、オープンなデータプレーンプログラミング言語。 ● 2019年6月に公開。仕様の最新バージョンは1.3 ● 仕様書は表紙・目次含め80ページ。 ● ターゲットはプログラマブルASIC、NIC、FPGA、ソフトウェアスイッチ。 ● Broadcom Trident 4とJericho 2が対応予定。 20

Slide 21

Slide 21 text

イベント 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

Slide 22

Slide 22 text

NPLの特徴 公式ページに記載されている特徴 ● Customized table pipelines ● Intelligent action processing ● Parallelism ● Advanced logical table capabilities ● An integrated instrumentation plane ● Simple, intuitive control flow 22

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

l2_switchサンプルプログラム https://github.com/nplang/NPL-Example-Applications/tree/master/Layer-2 24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

l2_switch実行画面 BMCLI bmif_cli.py BMODEL bmodel.sim 29 test.py

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

l2_switch通信の実際 ● ip linkなどで確認するとわかるが、vethは作られていない。 ○ test.pyは9090/tcpでbmodel.simと接続していた。 ● つまり、実通信をさばくデータプレーンとして素直には使えない。 ○ これはl3_appなど他のサンプルも同様。 ● 現状提供されている環境は、単体テスト用と割り切って使うしかない。 ○ mininetで動かせるといいのだが …… ● NPLソースコードの問題ではない。ツールチェインとライブラリ次第。 ● 9090/tcpで通信し、vethとの間でパケットを橋渡しするプログラムを書けば仮想環 境でパケット転送を含め動作確認できるかもしれない。 31

Slide 32

Slide 32 text

NPLでできないこと ● NPLの言語仕様の範囲で記述できない機能がある。たとえば ○ パケットとは独立して保持する情報 (カウンターなど) ○ キュー ○ 優先制御 ○ 送出ポート選択 ● NPLでは、外部にこれらを処理する機能ブロックが用意されていれば special_functionを定義して、NPLプログラムから呼び出すことができる。 実際にはベンダーがspecial_functionの情報を開発者に提供するだろう。 ● special_functionは実装依存な点に注意。 32

Slide 33

Slide 33 text

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の宣言が必要。 用意されたサンプルにも存在する。

Slide 34

Slide 34 text

NPLはP4と何が違うのか ● 開発の経緯 ○ P4はスタンフォード大学の学術研究を発端として産学共同で開発・発展している。 ○ NPLは(P4を参考にしつつ)企業(Broadcom)によって開発された。 ● モデル ○ P4で想定されるPISAはmatch-actionをつないでいくモデル。 ○ NPLは複数テーブルを同時に lookupし並列処理できるよう設計されている。 ● 言語仕様 ○ P4はP4-16で仕様が大幅に拡張され、よりソフトウェア的になっている。 (154ページ) ○ 一方NPLは言語仕様がコンパクト。 (80ページ) ● 周辺環境 ○ P4は言語としてだけでなく P4Runtimeなど周辺環境も整備されつつある。 ○ NPLは現在、言語仕様および仮想実行環境のみ公開されている。 34

Slide 35

Slide 35 text

まとめ 35

Slide 36

Slide 36 text

まとめ ● 2019年6月に、BroadcomからNPLが公開された。 ● NPLはデータプレーン用プログラミング言語。 ● 適用できるハードウェアはまだ市場にはない。 ● 提供されるソフトウェア実装はテスト用のみだが、単体テストには十分。 ● P4_16と比べると仕様はシンプル。(といっても80ページ) ● 競争が生まれることによる市場の活性化・健全化を期待したい。 36

Slide 37

Slide 37 text

リファレンス ● 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

Slide 38

Slide 38 text

Appendix: NPL詳解 38

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

NPLプログラムの構造 1. struct(パケットヘッダ)を定義する 2. ヘッダグループ用structとpacketを定義する 3. parser_nodeを定義する 4. logical_busを定義する 5. logical_tableを定義する 6. 複雑さなど必要に応じてfunctionを定義する 7. programを定義する 40

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

ヘッダグループと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

Slide 43

Slide 43 text

header, header groupとpacketの関係 ● NPL v1.3仕様書 21ページより。 ● この場合、Header Groupをまとめたstructをpacketとして定義する。 43

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

extern ● special_functionと似ている。外部機能をfunctionとして呼ぶ。 ● Cのexternのように外部にあることを宣言する。 ● 繰り返し何度も呼ばれる機能で使用される。 ● 例 ○ パケットのdrop ○ CPUあるいは他ポートへのパケットのコピー ○ パケットカウンタ 49 extern packet_drop(in bit[1] trigger, in const value, in const drop_code);

Slide 50

Slide 50 text

Editor パケットの編集機能。functionから呼び出す。 ● add_header ● delete_header ● replace_header_field ● create_checksum ● update_packet_length 50

Slide 51

Slide 51 text

その他の構文 ● コメントはC++同様の /* … */ および // ● C言語ライクなプリプロセッサ ○ #include ○ #ifdef …#else … #endif ○ #define 51

Slide 52

Slide 52 text

NPLにないもの ● 統計情報収集 ● キュー制御 ● メーター制御 ● ポート制御 externやspecial_functionで機能が提供されるか、 あるいはNPLで書ける範囲外のところで制御すると思われる。 52

Slide 53

Slide 53 text

Appendix 2: 他言語との比較 53

Slide 54

Slide 54 text

値の表現 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

Slide 55

Slide 55 text

型 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

Slide 56

Slide 56 text

演算子 P4-14には除算がない。 NPL P4-14 P4-16 (参考)C 四則演算+剰余 +, *, -, /, % +, *, - +, *, -, /, % +, *, -, /, % ビット演算 &, |, ^ &, |, ^ &, |, ^ &, |, ^ ブール演算 ||, && or, and or, and, ||, && ||, && 比較演算 !=, ==, <. >, <=, >= !=, ==, <. >, <=, >= !=, ==, <. >, <=, >= ne, eq, le, ge !=, ==, <. >, <=, >= 56

Slide 57

Slide 57 text

制御構文 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

Slide 58

Slide 58 text

パケットヘッダ定義 どれもほぼ似通っている。 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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

テーブル定義 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