LoginSignup
2
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-20

はじめに

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.goP4Runtime を用いてデータプレーンを制御するための各種変数や関数が定義されています.特に上述した各処理はいずれも下記で定義される P4RuntimeClient の具備する関数を用いて実装されます.

p4runtime.pb.go
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 言語では下記のように実装されます.

init_p4runtimeclient.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 言語では下記のように実装されます.

init_streamchannel.go
import(
    "context"
)

func main(){

    /* StreamChannel 確立 */
    channel, err := client.StreamChannel(context.TODO())
    if err != nil {
        /* Error 処理 */
    }
}

P4RuntimeClient の具備する StreamChannel() を用いてデータプレーンとの間で StreamChannel を確立しますが,この時点ではまだ Primary ではなく単に Channel が確立しただけなので,データプレーンの制御を行う場合は自身が Primary として選出される必要があります.こちらは Go 言語で下記のように実装されます.

arbitration_update.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 というデータ構造を用います(HighLow という 2 つの符号無し 64 bit 整数で構成されています).

StreamChannel の Send() により device_idelection_id を含む MasterArbitrationUpdate をデータプレーンに通知すると,データプレーン側で Primary 選出を行ってその結果をコントロールプレーンに返信します.コントロールプレーンはデータプレーンからの返信を Recv() で待機します.

Send() の引数のデータ構造はやや複雑ですが,StreamMessageRequest という変数の中に Update を格納する形をしており,上記で使用した MasterArbitrationUpdate は Primary 選出を行うための Update の一種となります.Updateの候補としては,上記の MasterArbitrationUpdate 以外に PacketOutDigestListAck がありますが,詳細は割愛します.Digest はデータプレーンからコントロールプレーンに情報を通知する機能のため,データプレーンとコントロールプレーン間でのパケットやパケットの中身に関する情報交換に使用するようです.詳細は p4runtime.pb.go 等を参照下さい.

上記では省略していますが MasterArbitrationUpdate には role_id を指定することが可能で,そのコントロールプレーンが Primary に選出された場合は role_id で規定される範囲内でデータプレーンの排他的な制御が可能となります(何も指定しない場合は default role となり full pipeline access が可能となります).

ForwardingPipelineConfig の設定

データプレーンとの間で StreamChannel を確立し Primary に選出されると,次は ForwardingPipelineConfig をデータプレーンに設定する必要があります.ForwardingPipelineConfig は端的に言うとデータプレーンの設定ファイルで,P4Runtime では下記のようなデータ構造で扱われます.

p4runtime.pb.go
import(
    v1 "github.com/p4lang/p4runtime/go/p4/config/v1"
)

type ForwardingPipelineConfig struct {
    P4Info            *v1.P4Info
    P4DeviceConfig    []byte           
    Cookie            *ForwardingPipelineConfig_Cookie 
}

上記で重要な変数は P4InfoP4DeviceConfig で,それぞれ下記を格納します.

  • 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 の各変数に値を格納する部分は下記のように実装されます.

init_config.go
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,
    }
}

ForwardingPipelineConfigP4RuntimeClientSetForwardingPipelineConfig() を用いてデータプレーンに設定します.こちらは下記のように実装されます.

set_config.go
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_idelection_id およびデータプレーンの設定である ForwardingPipelineConfig に加えて,下記いずれかの Actionを設定します.

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

上記が問題なく完了すると自作データプレーンがデバイスにロードされ,テーブルエントリの登録やトラヒックカウンタ値の取得等が可能となります.

Entity の Write/Read

テーブルエントリの登録やトラヒックカウンタ値の取得は Entity(P4Runtime で制御可能な要素)の WriteRead という形で実装されます. p4runtime.pb.go では Entity を下記のように定義しています.

p4runtime.pb.go
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

テーブルエントリ / マルチキャストグループの登録を行う場合は,上記の EntityUpdate に格納して Write() でデータプレーンに通知します. p4runtime.pb.go では Update を下記のように定義しています.

p4runtime.pb.go
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 言語で下記のように実装されます.

prepare_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 言語で下記のように実装されます.

write_entity.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() を用いて実装出来ます.こちらは下記のように実装されます.

read_entity.go
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 を格納した ReadRequestRead() でデータプレーンに通知すると,その Entity の情報を取得するための ReadClient が返ってきます. なお,上記で指定する Entity は複数(Go 言語でいうスライスで)格納する点に注意してください.1つの Entity を取得したい場合でも要素数が1つのスライスを用いる必要があります.

実際にデータプレーンからデータを取得する際は所望のタイミングで ReadClientRecv() を用いることでデータ取得が可能です.こちらは Go 言語で下記のように実装されます.

retrieve_data.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 から情報を取得する実装になります.以降は,今回使用する各 EntityEntity_TableEntry Entity_PacketReplicationEngineEntry Entity_CounterEntry)について詳細なデータ構造を示しつつ Entity を生成する部分の実装について説明します.

テーブルエントリのデータ構造

テーブルエントリを表す Entity_TableEntry および関連する各変数は p4runtime.pb.go でそれぞれ下記のように定義されています.

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 がどのように生成されるかを説明します.使用するテーブルは下記のような感じです(エントリはあくまで一例です).

スクリーンショット 2020-12-20 19.58.23.png

赤字は Entity_TableEntry 生成時に指定すべき項目,青字は今回使用する P4 プログラムでの識別名,緑字はコントロールプレーンから設定する値をそれぞれ表しています.例えば,上記の No.1 のエントリを表す Entity_TableEntry は下記のように生成されます.

entity_tableentry.go
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 */,
                        }
                    },
                },
            },
        },
    }
}

上記の通り TableAction 等を指定するために 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 でそれぞれ下記のように定義されています.

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 は下記のように生成されます.

entity_packetreplicationengineentry.go
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 の組を管理するための識別子 */,
                        }
                    },
                },
            },
        },
    }
}

MulticastGroupIdEgressPort の組み合わせ毎にコントロールプレーン側で管理する識別子となります.

トラヒックカウンタ値のデータ構造

トラヒックカウンタ値の取得には Entity_CounterEntry を使用します.Entity_CounterEntry および関連する各変数は p4runtime.pb.go でそれぞれ下記のように定義されています.

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 が返ってきます.

entity_counterentry.go
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 データプレーンからの情報取得が可能となります.

2
2
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
2
2