はじめに
P4Runtime を用いて下記を行うコントロールプレーンを Go 言語で実装します.
- テーブルエントリの登録
- マルチキャストグループの登録
- トラヒックカウンタ(Counter)値の取得
前回の記事の続編となりますので続けて参照されることを推奨します. P4 ってそもそも何?という方はこちらの記事を参照ください.なお,本記事で説明するコードはこちらで公開しました.動作確認のためのチュートリアルも用意していますので併せて参照ください.
P4Runtime を用いた機能実装
前回の記事でも触れたように,P4Runtime を用いた機能実装は大きく下記の流れとなります.
- StreamChannel と Primary 選出
- ForwardingPipelineConfig の設定
- Entity の Write/Read
上記の各処理はインターフェース定義言語(protocol buffers)で抽象化して規定され p4lang の github で公開されていますが,特に Go 言語向けのライブラリはこちらで公開されており,下記のように go get
コマンドを実行することでこれらのライブラリを自作コントロールプレーンにて利用可能となります.
> go get github.com/p4lang/p4runtime/go/p4/v1
> go get github.com/p4lang/p4runtime/go/p4/config/v1
Go 言語向けのライブラリは複数のファイルが存在しますが,メインとなるのは p4runtime.pb.go で P4Runtime を用いてデータプレーンを制御するための各種変数や関数が定義されています.特に上述した各処理はいずれも下記で定義される P4RuntimeClient
の具備する関数を用いて実装されます.
type P4RuntimeClient interface {
Write(ctx context.Context, in *WriteRequest, opts ...grpc.CallOption) (*WriteResponse, error)
Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (P4Runtime_ReadClient, error)
SetForwardingPipelineConfig(ctx context.Context, in *SetForwardingPipelineConfigRequest, opts ...grpc.CallOption) (*SetForwardingPipelineConfigResponse, error)
GetForwardingPipelineConfig(ctx context.Context, in *GetForwardingPipelineConfigRequest, opts ...grpc.CallOption) (*GetForwardingPipelineConfigResponse, error)
StreamChannel(ctx context.Context, opts ...grpc.CallOption) (P4Runtime_StreamChannelClient, error)
Capabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error)
}
前回の記事で P4Runtime による制御は gRPC を用いて実装されることを説明しました.そのため上記の P4runtimeClient
も初期化時に gRPC connection を渡す必要があります.Go 言語では下記のように実装されます.
import(
"google.golang.org/grpc"
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* gRPC connection 確立 */
addr := "127.0.0.1" /* gRPC サーバのアドレス */
port := "50051" /* gRPC サーバのポート番号 */
conn, err := grpc.Dial(addr+":"+port, grpc.WithInsecure())
if err != nil {
log.Fatal("ERROR: failed to establish gRPC connection. ", err)
}
defer conn.Close()
/* P4runtime Client インスタンス生成 */
client := v1.NewP4RuntimeClient(conn)
}
以降の処理は上記でインスタンス化した P4RuntimeClient
の具備する各関数を用いて実装されます.以下では各処理について Go 言語で実装する上での要点を説明します.要点のみの説明となるため実装の細かい部分は公開したコードを参照ください.
StreamChannel と Primary 選出
データプレーンと通信を行うためには,まずデータプレーンの制御 / データプレーンからの情報取得を行うための StreamChannel
をデータプレーンとの間で確立する必要があります.Go 言語では下記のように実装されます.
import(
"context"
)
func main(){
/* StreamChannel 確立 */
channel, err := client.StreamChannel(context.TODO())
if err != nil {
/* Error 処理 */
}
}
P4RuntimeClient
の具備する StreamChannel()
を用いてデータプレーンとの間で StreamChannel
を確立しますが,この時点ではまだ Primary ではなく単に Channel が確立しただけなので,データプレーンの制御を行う場合は自身が Primary として選出される必要があります.こちらは Go 言語で下記のように実装されます.
import(
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* MasterArbitrationUpdate */
deviceid := /* device_id (uint64) */
electionid := &v1.Uint128{
High: /* election_id の上位64bit (uint64) */,
Low: /* election_id の下位64bit (uint64) */,
}
stream_request := v1.StreamMessageRequest{
Update: &v1.StreamMessageRequest_Arbitration{
Arbitration: &v1.MasterArbitrationUpdate{
DeviceId: deviceid,
ElectionId: electionid,
},
},
}
err := channel.Send(stream_request)
if err != nil {
/* Error 処理 */
}
stream_response, err := channel.Recv()
if err != nil {
/* Error 処理 */
}
}
前回の記事で説明したように election_id
が最大となるコントロールプレーンプロセスが Primary として選出されます.なお election_id
は符号無し 128bit 整数のため,実装時は p4runtime.pb.go で定義される Uint128
というデータ構造を用います(High
と Low
という 2 つの符号無し 64 bit 整数で構成されています).
StreamChannel の Send()
により device_id
と election_id
を含む MasterArbitrationUpdate
をデータプレーンに通知すると,データプレーン側で Primary 選出を行ってその結果をコントロールプレーンに返信します.コントロールプレーンはデータプレーンからの返信を Recv()
で待機します.
Send()
の引数のデータ構造はやや複雑ですが,StreamMessageRequest
という変数の中に Update
を格納する形をしており,上記で使用した MasterArbitrationUpdate
は Primary 選出を行うための Update
の一種となります.Update
の候補としては,上記の MasterArbitrationUpdate
以外に PacketOut
や DigestListAck
がありますが,詳細は割愛します.Digest はデータプレーンからコントロールプレーンに情報を通知する機能のため,データプレーンとコントロールプレーン間でのパケットやパケットの中身に関する情報交換に使用するようです.詳細は p4runtime.pb.go 等を参照下さい.
上記では省略していますが MasterArbitrationUpdate
には role_id
を指定することが可能で,そのコントロールプレーンが Primary に選出された場合は role_id
で規定される範囲内でデータプレーンの排他的な制御が可能となります(何も指定しない場合は default role となり full pipeline access が可能となります).
ForwardingPipelineConfig の設定
データプレーンとの間で StreamChannel
を確立し Primary に選出されると,次は ForwardingPipelineConfig
をデータプレーンに設定する必要があります.ForwardingPipelineConfig
は端的に言うとデータプレーンの設定ファイルで,P4Runtime では下記のようなデータ構造で扱われます.
import(
v1 "github.com/p4lang/p4runtime/go/p4/config/v1"
)
type ForwardingPipelineConfig struct {
P4Info *v1.P4Info
P4DeviceConfig []byte
Cookie *ForwardingPipelineConfig_Cookie
}
上記で重要な変数は P4Info
と P4DeviceConfig
で,それぞれ下記を格納します.
-
P4Info
:データプレーンの各要素(Table や Action 等)を制御するための識別子 -
P4DeviceConfig
:データプレーンの挙動を規定する設定
いずれも P4 プログラムのコンパイル時に生成されるファイルをロードして各変数に値を格納します.例えば下記のように自作データプレーンである switching.p4
をコンパイルすると P4Info
のためのファイルとして p4info.txt
が,P4DeviceConfig
のためのファイルとして switching.json
がそれぞれ生成されます.
> p4c --std p4_16 -b bmv2 --p4runtime-files p4info.txt switching.p4
なお,上記の P4 コンパイラは BMv2(P4動作確認用ソフトウェア)のためにこちらで公開されているもので,実際には使用するデバイス毎に P4コンパイラが異なります.
上記ファイルより ForwardingPipelineConfig
の各変数に値を格納する部分は下記のように実装されます.
import(
"ioutil"
"github.com/golang/protobuf/proto"
config_v1 "github.com/p4lang/p4runtime/go/p4/config/v1"
)
func main(){
/* InitConfig (P4Info / ForwardingPipelineConfig) */
p4infoPath := /* p4info.txt のファイルパス */
devconfPath := /* switching.json のファイルパス */
p4info := &config_v1.P4Info{}
p4infoBytes, err := ioutil.ReadFile(p4infoPath)
if err != nil {
/* Error 処理 */
}
err = proto.UnmarshalText(string(p4infoBytes), p4info)
if err != nil {
/* Error 処理 */
}
devconf, err := ioutil.ReadFile(devconfPath)
if err != nil {
/* Error 処理 */
}
config := &v1.ForwardingPipelineConfig{
P4Info: p4info,
P4DeviceConfig: devconf,
}
}
ForwardingPipelineConfig
は P4RuntimeClient
の SetForwardingPipelineConfig()
を用いてデータプレーンに設定します.こちらは下記のように実装されます.
import(
"context"
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* SetForwardingPipelineConfig */
actionType := /* Action Type を指定 */
var action v1.SetForwardingPipelineConfigRequest_Action
switch actionType {
case "VERIFY":
action = v1.SetForwardingPipelineConfigRequest_VERIFY
case "VERIFY_AND_SAVE":
action = v1.SetForwardingPipelineConfigRequest_VERIFY_AND_SAVE
case "VERIFY_AND_COMMIT":
action = v1.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT
case "COMMIT":
action = v1.SetForwardingPipelineConfigRequest_COMMIT
case "RECONCILE_AND_COMMIT":
action = v1.SetForwardingPipelineConfigRequest_RECONCILE_AND_COMMIT
default:
action = v1.SetForwardingPipelineConfigRequest_UNSPECIFIED
}
set_request := v1.SetForwardingPipelineConfigRequest{
DeviceId: deviceid,
ElectionId: electionid,
Action: action,
Config: config,
}
set_response, err := client.SetForwardingPipelineConfig(context.TODO(), &set_request)
if err != nil {
/* Error 処理 */
}
}
前回の記事でも触れたように SetForwardingPipelineConfig()
ではコントロールプレーンプロセスの識別のための device_id
と election_id
およびデータプレーンの設定である ForwardingPipelineConfig
に加えて,下記いずれかの Action
を設定します.
- VERIFY
-
ForwardingPipelineConfig
が適用可能かの確認のみ. - VERIFY_AND_SAVE
-
ForwardingPipelineConfig
が適用可能かを確認した後に保存(適用はしない). - VERIFY_AND_COMMIT
-
ForwardingPipelineConfig
が適用可能かを確認した後に適用. - COMMIT
- 最後に保存した
ForwardingPipelineConfig
を適用. - RECONCILE_AND_COMMIT
- データプレーンの状態を保護しつつ
ForwardingPipelineConfig
の確認と適用.
上記が問題なく完了すると自作データプレーンがデバイスにロードされ,テーブルエントリの登録やトラヒックカウンタ値の取得等が可能となります.
Entity の Write/Read
テーブルエントリの登録やトラヒックカウンタ値の取得は Entity
(P4Runtime で制御可能な要素)の Write
や Read
という形で実装されます. p4runtime.pb.go では Entity
を下記のように定義しています.
type Entity struct {
// Types that are valid to be assigned to Entity:
// *Entity_ExternEntry
// *Entity_TableEntry
// *Entity_ActionProfileMember
// *Entity_ActionProfileGroup
// *Entity_MeterEntry
// *Entity_DirectMeterEntry
// *Entity_CounterEntry
// *Entity_DirectCounterEntry
// *Entity_PacketReplicationEngineEntry
// *Entity_ValueSetEntry
// *Entity_RegisterEntry
// *Entity_DigestEntry
Entity isEntity_Entity
}
上記のように Entity
としては様々な種類のものが定義されています.各要素の詳細は公式ページ等を参照ください.今回は次の3つの処理を行いますが,関連する Entity
はそれぞれ下記となります.
- テーブルエントリの登録:
Entity_TableEntry
- マルチキャストグループの登録:
Entity_PacketReplicationEngineEntry
- トラヒックカウンタ値の取得:
Entity_CounterEntry
テーブルエントリ / マルチキャストグループの登録を行う場合は,上記の Entity
を Update
に格納して Write()
でデータプレーンに通知します. p4runtime.pb.go では Update
を下記のように定義しています.
type Update_Type int32
const (
Update_UNSPECIFIED Update_Type = 0
Update_INSERT Update_Type = 1
Update_MODIFY Update_Type = 2
Update_DELETE Update_Type = 3
)
type Update struct {
Type Update_Type
Entity *Entity
}
前回の記事 でも触れたように Type
には Update
の種類として次のいずれかを格納します.
- INSERT
- 新規 Entity を登録する.
- MODIFY
- 既存 Entity を修正する.
- DELETE
- 既存 Entity を削除する.
Update
を新たに生成する部分は Go 言語で下記のように実装されます.
import(
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* Prepare Update */
updateType := /* Update Type を指定 */
var type v1.Update_Type
switch updateType {
case "INSERT":
type = v1.Update_INSERT
case "MODIFY":
type = v1.Update_MODIFY
case "DELETE":
type = v1.Update_DELETE
default:
type = v1.Update_UNSPECIFIED
}
update := &v1.Update{
Type: type,
Entity: /* 任意の Entity */
}
}
上記の Update
をデータプレーンに通知する場合は WriteRequest
に上記 Update
等を格納して Write()
に渡します.これらは Go 言語で下記のように実装されます.
import(
"context"
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* Write Entities */
atomicityType := /* Atomicity を指定 */
var atomicity v1.WriteRequest_Atomicity
switch atomicityType {
case "CONTINUE_ON_ERROR":
atomicity = v1.WriteRequest_CONTINUE_ON_ERROR
case "ROLLBACK_ON_ERROR": // OPTIONAL
atomicity = v1.WriteRequest_ROLLBACK_ON_ERROR
case "DATAPLANE_ATOMIC": // OPTIONAL
atomicity = v1.WriteRequest_DATAPLANE_ATOMIC
default:
atomicity = v1.WriteRequest_CONTINUE_ON_ERROR
}
write_request := v1.WriteRequest{
DeviceId: deviceid,
ElectionId: electionid,
Updates: []*v1.Update{update},
Atomicity: atomicity,
}
write_response, err := client.Write(context.TODO(), &write_request)
if err != nil {
/* Error 処理 */
}
}
Write()
の引数である WriteRequest
にはコントロールプレーンプロセスの識別のための device_id
/ election_id
および Update
に加えて Entity
の更新方法として下記いずれかの Atomicity
を格納します.
- CONTINUE_ON_ERROR
- エラーが発生しても処理を続行する(特に復元処理は行わない).
- ROLLBACK_ON_ERROR
- エラー発生時は処理を無効化し,データプレーンを Write RPC 以前の状態に戻す.
- DATAPLANE_ATOMIC
- 処理適用中のデータプレーンは Write RPC の直前の状態であることを保証する.
なお WriteRequest
内の Update
は複数指定が基本である点に注意してください. Go で実装する場合は Update
が1つの場合もスライスで実装する形となります.
Update
内の Type
および Entity
に所望の情報を入力して上記を実施することでテーブルエントリやマルチキャストグループの登録が可能です.なお,各 Entity
の中身については Entity
の種類毎にやや複雑なデータ構造となっているため,それぞれ取り上げて後述します.
ここまでは Entity
の制御に関する説明ですが,トラヒックカウンタ値の取得等,データプレーンからの情報取得も Entity
および Read()
を用いて実装出来ます.こちらは下記のように実装されます.
import(
"context"
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* Create Read Client */
read_request := v1.ReadRequest{
DeviceId: deviceid,
Entities: /* 任意の Entity. []*v1.Entity である点に注意 */,
}
read_client, err := client.Read(context.TODO(), &read_request)
if err != nil {
/* Error 処理 */
}
Read()
の場合は非常にシンプルで,情報を取得したい対象デバイスの device_id
および取得したい情報を表す Entity
を格納した ReadRequest
を Read()
でデータプレーンに通知すると,その Entity
の情報を取得するための ReadClient
が返ってきます. なお,上記で指定する Entity
は複数(Go 言語でいうスライスで)格納する点に注意してください.1つの Entity
を取得したい場合でも要素数が1つのスライスを用いる必要があります.
実際にデータプレーンからデータを取得する際は所望のタイミングで ReadClient
の Recv()
を用いることでデータ取得が可能です.こちらは Go 言語で下記のように実装されます.
func main(){
/* Read Data from DP */
read_response, err := read_client.Recv()
if err != nil {
/* Error 処理 */
}
entities := read_response.GetEntities()
if entity == nil {
/* Error 処理 */
}
}
Recv()
から ReadResponse
(上記の response
)を受け取り,その中に所望のデータを含む Entity
が指定した分だけ格納されています.Entity
を取り出す際は GetEntities()
を使用します(response
から直接取り出すことも可能です).なお,取り出した Entity
もスライスのため各情報を取得する際は実装に注意が必要です. 詳細なデータ構造等は p4runtime.pb.go を参照ください.
以上が P4Runtime で Entity
を制御 or Entity
から情報を取得する実装になります.以降は,今回使用する各 Entity
(Entity_TableEntry
Entity_PacketReplicationEngineEntry
Entity_CounterEntry
)について詳細なデータ構造を示しつつ Entity
を生成する部分の実装について説明します.
テーブルエントリのデータ構造
テーブルエントリを表す Entity_TableEntry
および関連する各変数は p4runtime.pb.go でそれぞれ下記のように定義されています.
type Entity_TableEntry struct {
TableEntry *TableEntry
}
// Entity_TableEntry の "TableEntry" に格納する変数
type TableEntry struct {
TableId uint32
Match []*FieldMatch
Action *TableAction
Priority int32
MeterConfig *MeterConfig
CounterData *CounterData
IsDefaultAction bool
IdleTimeoutNs int64
TimeSinceLastHit *TableEntry_IdleTimeout
Metadata []byte
}
// TableEntry の "Match" に格納する変数
type FieldMatch struct {
FieldId uint32
// Types that are valid to be assigned to FieldMatchType:
// *FieldMatch_Exact_
// *FieldMatch_Ternary_
// *FieldMatch_Lpm
// *FieldMatch_Range_
// *FieldMatch_Optional_
// *FieldMatch_Other
FieldMatchType isFieldMatch_FieldMatchType
}
// FieldMatch の "FieldMatchType" に格納する変数(他にも候補あり)
type FieldMatch_Exact_ struct {
Exact *FieldMatch_Exact
}
// FieldMatch_Exact_ の "Exact" に格納する変数
type FieldMatch_Exact struct {
Value []byte
}
// TableEntry の "Action" に格納する変数
type TableAction struct {
// Types that are valid to be assigned to Type:
// *TableAction_Action
// *TableAction_ActionProfileMemberId
// *TableAction_ActionProfileGroupId
// *TableAction_ActionProfileActionSet
Type isTableAction_Type
}
// TableAction の "Type" に格納する変数(他にも候補あり)
type TableAction_Action struct {
Action *Action
}
// TableAction_Action の "Action" に格納する変数
type Action struct {
ActionId uint32
Params []*Action_Param
}
// Action の "Params" に格納する変数
type Action_Param struct {
ParamId uint32
Value []byte
}
上記は一部のみ記載していますがEntity_TableEntry
ひとつとっても複数のパラメータによる入子構造になっており,非常に複雑なデータ構造となっています.利用する Entity
毎に p4runtime.pb.go を参照しつつデータ構造を把握する必要があります.
今回使用する P4 プログラム で定義する L2 転送用のテーブル(mac_exact
)を例に Entity_TableEntry
がどのように生成されるかを説明します.使用するテーブルは下記のような感じです(エントリはあくまで一例です).
赤字は Entity_TableEntry
生成時に指定すべき項目,青字は今回使用する P4 プログラムでの識別名,緑字はコントロールプレーンから設定する値をそれぞれ表しています.例えば,上記の No.1 のエントリを表す Entity_TableEntry
は下記のように生成されます.
import(
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* Entity_TableEntry の生成 */
entity_tableentry := &v1.Entity_TableEntry{
TableEntry: &v1.TableEntry{
TableId: /* "mac_exact" の table-id を p4info.txt から取得 */,
Match: []*v1.FieldMatch{
&v1.FieldMatch{
FieldId: /* "hdr.ethernet.dstAddr" の field-id を p4info.txt から取得 */,
FieldMatchType: &v1.FieldMatch_Exact_{
Exact: &v1.FieldMatch_Exact{
Value: /* "aa:bb:cc:dd:ee:ff" in []byte */,
},
},
}
},
Action: &v1.TableAction{
Type: &v1.TableAction_Action{
Action: &v1.Action{
ActionId: /* "switching" の action-id を p4info.txt から取得 */,
Params: []*v1.Action_Param{
&v1.Action_Param{
ParamId: /* "port" の param-id を p4info.txt から取得 */,
Value: /* 0 in []byte */,
}
},
},
},
},
}
}
上記の通り Table
や Action
等を指定するために p4info.txt
を参照しつつ各要素の識別名からデータプレーンでの識別子を取得する必要があります.また match
の key 値や action param.
の値は []byte 形式で格納し,かつバイト数はその値を格納するために必要最低限のバイト数とする必要があります(例えば上記テーブルの port
は 9bit のため 2byte のデータとする必要があります).
今回の実装では上記の "指定すべき項目(赤字記載のパラメータ)" を格納する Helper 変数( TableEntryHelper
という変数)を用意し,こちらの変数から Entity_TableEntry
を生成する関数( BuildTableEntry()
という関数)を使用して Entity
を生成しています.なお,コントロールプレーンから設定すべき値(緑字記載のパラメータ)は TableEntryHelper
のための json ファイル(runtime.json)に記載し,こちらを読み込む形で実装しています.
マルチキャストグループのデータ構造
マルチキャストグループを表す Entity_PacketReplicationEngineEntry
および関連する各変数は p4runtime.pb.go でそれぞれ下記のように定義されています.
type Entity_PacketReplicationEngineEntry struct {
PacketReplicationEngineEntry *PacketReplicationEngineEntry
}
/* Entity_PacketReplicationEngineEntry の "PacketReplicationEngineEntry" に格納する変数 */
type PacketReplicationEngineEntry struct {
// Types that are valid to be assigned to Type:
// *PacketReplicationEngineEntry_MulticastGroupEntry
// *PacketReplicationEngineEntry_CloneSessionEntry
Type isPacketReplicationEngineEntry_Type
}
/* PacketReplicationEngineEntry の "Type" に格納する変数 */
type PacketReplicationEngineEntry_MulticastGroupEntry struct {
MulticastGroupEntry *MulticastGroupEntry
}
/* PacketReplicationEngineEntry の "MulticastGroupEntry" に格納する変数 */
type MulticastGroupEntry struct {
MulticastGroupId uint32
Replicas []*Replica
}
/* MulticastGroupEntry の "Replica" に格納する変数 */
type Replica struct {
EgressPort uint32
Instance uint32
}
例えば出力先ポートが 0
1
2
となるマルチキャストグループを表す Entity_PacketRepolicationEngineEntry
は下記のように生成されます.
import(
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* Entity_PacketReplicationEngineEntry の生成 */
entity_packetreplicationengineentry := &v1.Entity_PacketReplicationEngineEntry{
PacketReplicationEngineEntry: &v1.PacketReplicationEngineEntry{
Type: &v1.PacketReplicationEngineEntry_MulticastGroupEntry{
MulticastGroupEntry: &v1.MulticastGroupEntry{
MulticastGroupId: /* 任意のマルチキャストグループエントリ ID. コントロールプレーンで管理. */,
Replicas: []*v1.Replica{
&v1.Replica{
EgressPort: /* 0 in uint32 */,
Instance: /* EgressPort の組を管理するための識別子 */,
},
&v1.Replica{
EgressPort: /* 1 in uint32 */,
Instance: /* EgressPort の組を管理するための識別子 */,
},
&v1.Replica{
EgressPort: /* 2 in uint32 */,
Instance: /* EgressPort の組を管理するための識別子 */,
}
},
},
},
},
}
}
MulticastGroupId
は EgressPort
の組み合わせ毎にコントロールプレーン側で管理する識別子となります.
トラヒックカウンタ値のデータ構造
トラヒックカウンタ値の取得には Entity_CounterEntry
を使用します.Entity_CounterEntry
および関連する各変数は p4runtime.pb.go でそれぞれ下記のように定義されています.
type Entity_CounterEntry struct {
CounterEntry *CounterEntry
}
/* Entity_CounterEntry の "CounterEntry" に格納する変数 */
type CounterEntry struct {
CounterId uint32
Index *Index
Data *CounterData
}
/* CounterEntry の "Index" に格納する変数 */
type Index struct {
Index int64
}
/* CounterEntry の "Data" に格納する変数(Read の場合はデータプレーン側で値を格納する) */
type CounterData struct {
ByteCount int64
PacketCount int64
}
今回使用する P4 プログラム では traffic_cnt
という識別名で Counter を定義しているため,例えば index
が 100 のトラヒックカウンタ値を取得したい場合,下記のように Entity_CounterEntry
を生成し Read()
を使用することでデータプレーンから所望のデータを取得出来ます.なお,データプレーンからは上記の CounterData
に値が格納された形で Entity
が返ってきます.
import(
v1 "github.com/p4lang/p4runtime/go/p4/v1"
)
func main(){
/* Entity_CounterEntry の生成 */
entity_counterentry := &v1.Entity_CounterEntry{
CounterEntry: &v1.CounterEntry{
CounterId: /* "traffic_cnt" の counter-id を p4info.txt から取得 */,
Index: &v1.Index{
Index: /* 100 in int64 */,
},
},
}
}
テーブルエントリの場合と同様に CounterId
は P4 プログラムで定義したカウンタ名から p4info.txt
を参照しつつ取得する必要があります.
おわりに
本記事では P4Runtime を用いて下記を行う Go 実装について説明しました.
- テーブルエントリの登録
- マルチキャストグループの登録
- トラヒックカウンタ(Counter)値の取得
今回は一部の Entity
(P4Runtime で制御可能な要素)のみを説明しましたが,その他の Entity
についても基本は同様で,必要な Entity
変数を用意し Write()
あるいは Read()
を用いることでデータプレーンの制御 or データプレーンからの情報取得が可能となります.