LoginSignup
6
5

More than 3 years have passed since last update.

P4Runtime を用いて P4 で記述した簡易 L2 Switch のテーブルエントリ登録とトラヒックカウンタ値取得を行う(準備編)

Last updated at Posted at 2020-10-05

はじめに

前回の記事では P4 で記述した簡易 L2 Switch に VLAN (802.1q) 対応,およびトラヒックカウンタ実装を行いました. 本記事では,前回実装した簡易 L2 Switch を P4Runtime で制御し,MACテーブルのエントリ登録,およびカウンタ値の取得を行う P4Runtime client を実装して, P4Runtime を用いたコントロールプレーン開発の基礎について説明します.なお,言語は Go 言語を用いて実装します.

Go を用いた P4Runtime client については既に実装・公開されているものもあり,例えば ebiken さんの github antonin さんの github が大変参考になります.本記事と併せて参照すれば相乗効果が高いと思います.

本記事のサマリ

本記事には P4Runtime を用いてコントロールプレーンを開発するために必要な要素として,下記の内容が記載されています.

  • P4Runtime の概要(コントロールプレーン実装の基礎)
  • テーブルエントリの登録方法
  • マルチキャストグループの登録方法
  • トラヒックカウンタ(Counter)値の取得方法

P4Runtime を用いたコントロールプレーン実装の基礎を「準備編(本記事)」で, P4Runtime の Go 実装を用いたテーブルエントリ登録やトラヒックカウンタ値の取得を「実装編」でそれぞれ説明します.そもそも P4 って何?という方は前回の記事を参照頂ければと思います.

おことわり

本記事は 2020.12 時点の P4Runtime Specification を参照した説明となっています. P4Runtime は標準化作業中の内容であるため,最新版の P4Runtime Specification を公式ページで参照するようお願いいたします.

P4Runtime によるコントロールプレーン実装の基礎

準備編では,そもそも P4Runtime とは何か, を起点とし P4Runtime を用いたコントールプレーン開発の要素として,大きく下記3つについて概説します.

  • StreamChannel と Primary 選出
    • コントロールプレーンとデータプレーンの接続処理を行います.
  • ForwardingPipelineConfig の設定
    • P4 で記述した自作データプレーンを target(P4対応デバイス)に設定します.
  • Entity の Write/Read
    • Entity(データプレーンの制御可能なオブジェクト)の読み書きを行います.

P4Runtime とは

P4Runtime とは P4.org にて仕様化が進められている P4 のコントロールプレーン向けの制御インターフェースです. protocol buffers を用いて記述されており p4lang の github で公開されています. 複数の proto ファイルが存在しますがメインとなるのは p4runtime.proto で,特に P4Runtime の client(コントロールプレーン)は下記のように定義されています(コメントを一部省略しています).


service P4Runtime {
  // Update one or more P4 entities on the target.
  rpc Write(WriteRequest) returns (WriteResponse) {
  }
  // Read one or more P4 entities from the target.
  rpc Read(ReadRequest) returns (stream ReadResponse) {
  }

  // Sets the P4 forwarding-pipeline config.
  rpc SetForwardingPipelineConfig(SetForwardingPipelineConfigRequest)
      returns (SetForwardingPipelineConfigResponse) {
  }
  // Gets the current P4 forwarding-pipeline config.
  rpc GetForwardingPipelineConfig(GetForwardingPipelineConfigRequest)
      returns (GetForwardingPipelineConfigResponse) {
  }

  // Represents the bidirectional stream between the controller and the
  // switch (initiated by the controller) ...
  rpc StreamChannel(stream StreamMessageRequest)
      returns (stream StreamMessageResponse) {
  }

  rpc Capabilities(CapabilitiesRequest) returns (CapabilitiesResponse) {
  }
}

P4Runtime を用いたコントロールプレーンは,上記で定義される RPC (Remote Procedure Call) をベースに gRPC client として実装されます. それぞれの RPC の役割はざっくりと下記のようになります.

  • StreamChannel RPC
    • コントロールプレーンとデータプレーン間で双方向のセッションを確立します.
  • SetForwardingPipelineConfig RPC
    • データプレーンに対し forwarding-pipeline config を設定します.
  • GetForwardingPipelineConfig RPC
    • データプレーンから forwarding-pipeline config を取得します.
  • Write RPC
    • P4 Entity (P4Runtime で制御可能なオブジェクト) の update を行います.
  • Read RPC
    • P4 Entity (P4Runtime で制御可能なオブジェクト) の read を行います.

Capabilities については本記事では割愛しますが, p4runtime.proto で定義される CapabilitiesRequest/Response(下記)より推測される限り, データプレーン側が対応している P4Runtime のバージョンを確認する RPC のようです.

message CapabilitiesRequest {
}

message CapabilitiesResponse {
  // The full semantic version string (e.g. "1.1.0-rc.1") corresponding to the
  // version of the P4Runtime API currently implemented by the server.
  string p4runtime_api_version = 1;
}

P4Runtime に対応した target には gRPC server が用意されており,コントロールプレーン(gRPC client)と target(上の gRPC server) が上記の RPC で通信し,テーブルエントリの登録やトラヒックカウンタ値の取得等を行います.

StreamChannel と Primary 選出

P4Runtime でデータプレーンを制御するためには,まずコントロールプレーンとデータプレーン間にセッションを確立する必要があります. StreamChannel RPC はこのセッション確立を行うための RPC で specification には下記のように記載されています.

To support multiple controllers, P4Runtime uses the streaming channel (available via StreamChannel RPC) for session management. The workflow is described as follows:

  • Each controller instance (e.g. a controller process) can participate in one or more roles. For each (device_id, role_id), the controller receives an election_id. This election_id can be the same for different roles and/or devices, as long as the tuple (device_id, role_id, election_id) is unique. For each (device_id, role_id) that the controller wishes to control, it establishes a StreamChannel with the P4Runtime server responsible for that device, and sends a MasterArbitrationUpdate message containing that tuple of (device_id, role_id, election_id) values. The P4Runtime server selects a primary independently for each (device_id, role_id) pair. <以下略>

詳細は前後の文脈を参照頂きたいですが,ざっくりと下記のような意味です.

P4Runtime は複数のコントロールプレーンの接続を許容するために device_id role_id election_id の 3 つのパラメータを用いてコントロールプレーンプロセスを識別します. 特に device_idrole_id の組み合わせ毎に election_id が最大となるコントロールプレーンを Primary に選出します. Primary となったコントロールプレーンは role_id に紐付く "役割" の範囲内でデータプレーンの排他的な制御が可能となります.

なお role_id に紐付く "役割" をどのように規定するかは P4Runtime の対象外となります.また role_id を指定しない場合は "full pipeline access" を表す default role が設定され,その role_id は 0 となります.

コントロールプレーンは StreamChannel を介して MasterArbitrationUpdate message を送信し device_id role_id election_id をデータプレーンに通知します.データプレーンは device_id role_id に対応するその時点での Master の election_idMasterArbitrationUpdateelection_id を比較して Primary の選出を行います.

MasterArbitrationUpdate は実際には StreamMessageRequest に含まれる形で StreamChannel RPC を介してデータプレーンに送信されます. 各 message は p4runtime.proto にてそれぞれ下記のように定義されています(一部コメントを省略しています).

message StreamMessageRequest {
  oneof update {
    MasterArbitrationUpdate arbitration = 1;
    PacketOut packet = 2;
    DigestListAck digest_ack = 3;
    .google.protobuf.Any other = 4;
  }
}

...

message MasterArbitrationUpdate {
  uint64 device_id = 1;
  Role role = 2;
  Uint128 election_id = 3;
  .google.rpc.Status status = 4;
}

StreamMessageRequest に含まれる PacketOutDigestListAck は本記事では割愛しますが,コントロールプレーンとの PacketI/O や Digest 利用時に用いるようです. こちらは ebiken さんの github で実装があるため興味のある方はこちらを参照ください.

このように P4Runtime でデータプレーンを制御するためには,まず P4Runtime client(コントロールプレーン)がデータプレーンとの間で StreamChannel を確立し Primary となる必要があります.

ForwardingPipelineConfig の設定

コントロールプレーンとデータプレーンの間で StreamChannel を確立し Primary に選出されたら,次はデータプレーンそのものの設定を行う必要があります. データプレーンに関する設定は ForwardingPipelineConfig として p4runtime.proto で下記のように定義されています.

message ForwardingPipelineConfig {
  config.v1.P4Info p4info = 1;
  // Target-specific P4 configuration.
  bytes p4_device_config = 2;
  // Metadata (cookie) opaque to the target. A control plane may use this field
  // to uniquely identify this config. ...
  message Cookie {
    uint64 cookie = 1;
  }
  Cookie cookie = 3;
}

p4info はデータプレーンの各オブジェクト(table や action 等)に関する情報を管理する補助情報で p4info.proto で定義されています.

p4info および p4_device_config はいずれも P4 プログラムのコンパイル時に生成されます. コンパイラによって生成方法にばらつきがあるかと思いますが,例えば p4.org による P4 コンパイラの reference 実装 では下記のようにコンパイルを行うことで p4info.txtmy-p4-program.json が生成されます(targetarch は環境に合わせて変更してください).

p4c --target bmv2 --arch v1model --p4runtime-files p4info.txt my-p4-program.p4

コンパイラによって生成された p4infop4_device_config を格納した ForwardingPipelineConfig よりSetForwardingPipelineConfig RPC でデータプレーンの設定を行います. 実際には下記で定義される SetForwardingPipelineConfigRequest message に ForwardingPipelineConfig を格納してデータプレーンに送信します(コメントを一部省略しています).

message SetForwardingPipelineConfigRequest {
  enum Action {
    UNSPECIFIED = 0;
    // Verify that the target can realize the given config. ...
    VERIFY = 1;
    // Save the config if the target can realize it. ...
    VERIFY_AND_SAVE = 2;
    // Verify, save and realize the given config. ...
    VERIFY_AND_COMMIT = 3;
    // Realize the last saved, but not yet committed, config. ...
    COMMIT = 4;
    // Verify, save and realize the given config, while preserving the
    // forwarding state in the target. ...
    RECONCILE_AND_COMMIT = 5;
  }
  uint64 device_id = 1;
  uint64 role_id = 2;
  Uint128 election_id = 3;
  Action action = 4;
  ForwardingPipelineConfig config = 5;
}

コントロールプレーンの識別のために device_id role_id election_id も同時に格納していますが,これらに加えて下記いずれかの action を選択します.

  • VERIFY
    • ForwardingPipelineConfig が適用可能かの確認のみ.
  • VERIFY_AND_SAVE
    • ForwardingPipelineConfig が適用可能かを確認した後に保存(適用はしない).
  • VERIFY_AND_COMMIT
    • ForwardingPipelineConfig が適用可能かを確認した後に適用.
  • COMMIT
    • 最後に保存した ForwardingPipelineConfig を適用.
  • RECONCILE_AND_COMMIT
    • データプレーンの状態を保護しつつ ForwardingPipelineConfig の確認と適用.

ForwardingPipelineConfig を Commit まで実施することで,自作データプレーンを target にデプロイすることが出来ます.

Entity の Write/Read

StreamChannel を確立し ForwardingPipelineConfig の設定まで完了したら,Entity の Write/Read を通したデータプレーンの制御・データプレーンからの情報取得が可能となります. Entity とは P4Runtime で制御可能なオブジェクトの総称で p4runtime.proto では下記のように定義されています.


message Entity {
  oneof entity {
    ExternEntry extern_entry = 1;
    TableEntry table_entry = 2;
    ActionProfileMember action_profile_member = 3;
    ActionProfileGroup action_profile_group = 4;
    MeterEntry meter_entry = 5;
    DirectMeterEntry direct_meter_entry = 6;
    CounterEntry counter_entry = 7;
    DirectCounterEntry direct_counter_entry = 8;
    PacketReplicationEngineEntry packet_replication_engine_entry = 9;
    ValueSetEntry value_set_entry = 10;
    RegisterEntry register_entry = 11;
    DigestEntry digest_entry = 12;
  }
}

上記の proto ファイルより,テーブルエントリ(TableEntry)やトラヒックカウンタ(CounterEntry)も Entity として定義されていることが分かります.これらの Entity に対し Write RPC や Read RPC を通した制御あるいは情報取得を行うことで,コントロールプレーンによるデータプレーンの制御やデータプレーンからの情報取得が可能となります. なお,上記の Entity のうち本記事で取り上げるものは TableEntry PacketReplicationEngineEntry CounterEntry の3つになります.これらの Entity の詳細は「実装編(執筆予定)」で Go 言語による実装も加味しつつ掘り下げて説明します.

Entity の Write

Entity への書き込み(テーブルエントリの登録等)は Write RPC を用いて行います. コントロールプレーン側では p4runtime.proto で定義される下記の WriteRequest message に必要な情報を入力しWrite RPC でデータプレーン側に通知します(下記の記載はコメントを一部省略しています).


message WriteRequest {
  uint64 device_id = 1;
  uint64 role_id = 2;
  Uint128 election_id = 3;
  // The write batch, comprising a list of Update operations. ...
  repeated Update updates = 4;
  enum Atomicity {
    // Required. This is the default behavior. The batch is processed in a
    // non-atomic manner from a data plane point of view. ...
    CONTINUE_ON_ERROR = 0;
    // Optional. Operations within the batch are committed to data plane until
    // an error is encountered. At this point, the operations must be rolled
    // back such that both software and data plane state is consistent with the
    // state before the batch was attempted. ...
    ROLLBACK_ON_ERROR = 1;
    // Optional. Every data plane packet is guaranteed to be processed according
    // to table contents before the batch began, or after the batch completed
    // and the operations were programmed to the hardware. ...
    DATAPLANE_ATOMIC = 2;
  }
  Atomicity atomicity = 5;
}

上記より WriteRequest message では,コントロールプレーンを識別するための device_id role_id election_id に加えて Update message および atomicity を指定することが分かります.
atomicity は Entity の制御にエラーが発生した際の挙動を指定するパラメータで,それぞれ下記のように規定されているようです.

  • CONTINUE_ON_ERROR
    • エラーが発生しても処理を続行する(特に復元処理は行わない).
  • ROLLBACK_ON_ERROR
    • エラー発生時は処理を無効化し,データプレーンを Write RPC 以前の状態に戻す.
  • DATAPLANE_ATOMIC
    • 処理適用中のデータプレーンは Write RPC の直前の状態であることを保証する.

Update message は p4runtime.proto で下記のように定義されています.


message Update {
  enum Type {
    UNSPECIFIED = 0;
    INSERT = 1;
    MODIFY = 2;
    DELETE = 3;
  }
  Type type = 1;
  Entity entity = 2;
}

Write RPC で Entity を制御する際は WriteRequest message に制御したい Entity の Update message を格納し(複数指定可),かつこれらの Update message に UpdateTypeEntity の情報をそれぞれ格納する形になります.なお UpdateType としては下記の3パターンがあります.

  • INSERT
    • 新規 Entity を登録する.
  • MODIFY
    • 既存 Entity を修正する.
  • DELETE
    • 既存 Entity を削除する.

このように Entity を制御する場合は WriteRequest message に上記で説明した各種情報を入力し Write RPC で server(データプレーン)側に通知することで,テーブルエントリ登録などの処理を行うことが出来ます.

Entity の Read

Entity に関する情報の取得は Read RPC を用いて行います.コントロールプレーン側では ReadRequest message を Read RPC でデータプレーンに通知し,その返信として ReadResponse message を受け取ります.それぞれ p4runtime.proto で下記のように定義されています.


message ReadRequest {
  uint64 device_id = 1;
  repeated Entity entities = 2;
}

message ReadResponse {
  repeated Entity entities = 1;
}

情報を取得したい Entity を ReadRequest message に入れてデータプレーンに通知すると,その値が格納された Entity が返ってくる,という非常にシンプルな仕組みになっています.

ReadRequest message では device_id のみを指定しています. この点について specification では下記のような記載があります.

Since ReadRequests do not mutate any state on the switch, they do not require an election_id, and they do not require the presence of an open StreamChannel between the server and client.

Read RPC はデータプレーンの状態を変更するものではないため,Entity の情報を読み込むだけならば Primary である必要はなく,Entity を含むデバイスがどれかが分かれば十分のようです.

おわりに

本記事では P4Runtime を用いてコントロールプレーンを実装する際の基礎として,下記について整理しました.

  • P4Runtime の概要
  • StreamChannel と Primary 選出
  • ForwardingPipelineConfig の設定
  • Entity の Write/Read

実装編」では,これらの各処理を Go 言語を用いて実装し,前回の記事で実装した簡易 L2 Switch のテーブルエントリ登録とトラヒックカウンタ値取得を行います.

改訂記録

  • P4Runtime の仕様更新 (2020.12.12)
    • 2020.12.1 付けで P4Runtime の仕様が v1.2.0 → v1.3.0 に更新されたことに伴い,下記修正しました.
      • P4Runtime 仕様のリンク更新
      • master の表記をそれぞれ primary に変更.
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5