技術

P4 言語を用いた SRv6 と INT 機能の実装例

はじめに

日本 P4 ユーザ会で話題になりましたが、インテル® Tofino ASIC 向けプログラムの公開 がされております。今回は、Tofino 向けに提供されいている P4 コードを活用して、SRv6 と INT 機能の実装例と記述のご紹介を致します。


構築環境 (対象のハードウェアとソフトウェア)

Switch : Edge-Core Wedge 100BF-32X (インテル® Tofino™搭載)
FPGAカード : インテル® PAC N3000
Server : HPE DL380 (FPGA カード搭載サーバー)
ネットワークOS : SONiC

Switch で Encap した SRv6パケットは、FPGA 搭載サーバーで END 処理され、上位の Switch に渡される構成です。FPGAには、SRv6の END 機能を P4 で実装していますが、本記事では、インテル®Tofino への実装について記載をします。


概要

今回は、インテル®提供の Switch.p4 群のいくつかのファイルを修正し、SRv6 と INT の Function を実装方法とコードの解説を記事に致しました。また、実装した Function を NOS(SONiC) 上で M&A テーブルにアクセスし制御する方法についても記載致します。


インテル® Tofino ASIC 向けプログラムについて

最初に、インテル® が提供する P4(Switch.p4) について簡単に構成を記載します。

L2/L3 の機能ごとに複数の Function コードが提供されています。

Top ファイルでは、Ingress から Egress までのパイプラインフローが記述されています。
併せて ACL, header, type, port, QoS, Tunneling, など複数の L2/L3 Function コードも提供されています。
今回はこの Swtich.p4 中に、SRv6 Encap/Decap 機能と INT の Function を追加してみます。


機能の実装 (データプレーン : P4)

  • 追加した機能について
    • SRv6 の Encap/Decap 処理
    • INT(In network telemetry)による、Timesamp 情報の挿入
  • SRv6 コードのご紹介
    • 次に、今回実装した、SRv6 の処理について、コードを解説致します。
      実装した機能は、以下の2つです。
      ・END 処理 : SRH を削除する (FPGA)
      ・ENCAP 処理 : SID を追加する (Switch)
    • 以下 Switch に実装した ENCAP 処理を記載します

 

    • 定義した関数を Apply 

apply {
pkt_validation.apply(
hdr, ig_md.flags, ig_md.lkp, ig_intr_md_for_tm, ig_md.drop_reason);
ingress_port_mapping.apply(hdr, ig_md, ig_intr_md_for_tm, ig_intr_md_for_dprsr);
srv6.apply(hdr, ig_md.lkp);

上記の、srv6.apply(hdr, ig_md.lkp); は、Srv6 の Function に、hdr, ig_md.lkp の2つの引数を渡すための記述。
“ig_md” は、Ingress の Metadata になります。

Port.p4 の中で、ig_md のタイプを switch_ingress_metadata_t として定義しています。

control IngressPortMapping(
inout switch_header_t hdr,
inout switch_ingress_metadata_t ig_md,"

    • Egress への処理追加 (INT for Timestamp)

control SwitchEgress(
inout switch_header_t hdr,
inout switch_egress_metadata_t eg_md,
in egress_intrinsic_metadata_t eg_intr_md,
in egress_intrinsic_metadata_from_parser_t eg_intr_md_from_prsr,
inout egress_intrinsic_metadata_for_deparser_t eg_intr_md_for_dprsr,
inout egress_intrinsic_metadata_for_output_port_t eg_intr_md_for_oport) {
//…
SystemInt() system_int;

    • 定義した関数を以下にて、アプライします。System_int に対して、hdr, eg_md を引数として渡す

apply {

system_int.apply(hdr, eg_md);
}

※上から実行されるので、Timestamp は処理の最後に追加をしています。

※System_Int の本体は、port.p4 で上述のように定義しました。行きと帰りで2度挿入する動作します

 


INT 機能コード例

Port.p4 に追加

control SystemInt(inout switch_header_t hdr,
in switch_egress_metadata_t eg_md)(
switch_uint32_t table_size=10) {

//行きと帰りの Timestamp 挿入のアクション

action int_timestamp0() {
#ifdef SYSTEM_INT_ENABLE
hdr.int_header.timestamp_0 = eg_md.ingress_timestamp;
#endif
}

action int_timestamp1() {
#ifdef SYSTEM_INT_ENABLE
hdr.int_header.timestamp_1 = eg_md.ingress_timestamp;
#endif
}

//IP address が Exact Match したら挿入する Match & Action Table

table sys_int {
key = {
hdr.ipv6.dst_addr : exact;
}
actions = {
int_timestamp0;
int_timestamp1;
}
size = table_size;
}

//Valid であれば、Apply する記述

apply {
if (hdr.int_header.isValid()) {
sys_int.apply();
}


SRv6 ENCAP/DECAP コード例

次に、今回実装した、SRv6 の処理について、コードを解説致します。
実装した機能は、以下の2つです。
・END 処理 : SRH を削除する
・ENCAP 処理 : SID を追加する


//END 処理
//hdr.srh_segment_list[] を無効とする記述。setInvalid ()で無効とします。


action remove_srh_header() {
hdr.srh.setInvalid();
hdr.srh_segment_list[0].setInvalid();
hdr.srh_segment_list[1].setInvalid();
hdr.srh_segment_list[2].setInvalid();
hdr.srh_segment_list[3].setInvalid();
}

//end_dx6 のアクションは以下になります。

//上で作成したアクションの実施。これにより hdr_srh_segment_list が無効化されます。 

action end_dx6( ) {
// Removing SRH and SIDs
remove_srh_header();
// Copying inner IPv6 hdr to outer IPv6
hdr.ipv6 = hdr.inner_ipv6;
// Deleting inner IPv6
hdr.inner_ipv6.setInvalid();
}


//上記アクションを実施するための、Match & Action table の記述は以下です。
//IPは Exact Match 条件で指定し、該当したら上記で記述したアクションを実施します

table srv6_end_dx6 {
key = {
hdr.ipv6.dst_addr : exact;
}
actions = {
@defaultonly NoAction;
end_dx6;
}
const default_action = NoAction;
}


機能の実装 (制御系 : Python)

先ほどまでの作業にて、 switch.p4 に 上述の SRv6 ENCAP 機能を追加致しました。

次に、実装した SRv6 機能 を NOS(SONiC)上で M&A(マッチアクション) テーブルにアクセスし制御する方法についても記載致します。

  • 最初に実装先の SW の構造を簡単にご紹介します。(引用先 : https://github.com/Azure/SONiC/wiki/Architecture)
    • 概要
      • 上記で実装した SRv6の END/ENCAP 機能は、インテル® Tofino ASIC と ASIC Driver に実装されます
      • 実装された機能を制御するために、User Space 内にて、Syncd Container が提供されております
      • 今回 Syncd Container にSRv6テーブル制御の処理を追加します
      • 追加した処理を実行するためのコマンドを、CLI にて追加します

  1. SRv6 マッチアクションテーブルを制御するコマンドの追加
    • 以下のコマンドを追加してきます
  2. gRPC Client の追加
    • 以下の図ように、BFRuntime が インテル® から制御用に提供されております。これは インテル® P4 SDE内に配置されており、BFRuntime を使用して実装した機能の制御が可能です。Syncd Container 内に、gRPC Client を追加することで、実装した機能を制御致します。
  3. マッチアクションテーブルの追加方法
    1. 実装対象 : Syncd に srv6.py を作成して追加
    2. 追加する API

      entry_add(target, key_list=None, data_list=None,

                        atomicity=bfruntime_pb2.WriteRequest.CONTINUE_ON_ERROR, metadata=None)

    3. 実装例
      • def add_srv6_encap_entry(self, match_dst_addr, sid1, sid2, sid3):
        srv6_table_obj = self.bfrt_info.table_get("SwitchIngress.srv6.srv6_transit_v6")
        srv6_table_obj.entry_add(
        self.target,
        [srv6_table_obj.make_key([
        gc.KeyTuple('hdr.ipv6.dst_addr', gc.ipv6_to_bytes(match_dst_addr))
        ])],
        [srv6_table_obj.make_data([gc.DataTuple('sid1', gc.ipv6_to_bytes(sid1)),
        gc.DataTuple('sid2', gc.ipv6_to_bytes(sid2)),
        gc.DataTuple('sid3', gc.ipv6_to_bytes(sid3))],
        'SwitchIngress.srv6.t_encaps_sid3')]
        )
      • self.bfrt_info.table… 内の()にてP4 で定義したテーブル名を指定し、テーブルを参照するオブジェクトを取得。

      • srv6_table_obj.make_data にて 'SwitchIngress.srv6.t_encaps_sid3' としてマッチ条件を指定し、Entry する
  • M&A(マッチアクション) テーブルの削除方法
    1. 実装対象 : Syncd に srv6.py を作成して追加
    2. 追加する API
      • entry_del(target, key_list=None, metadata=None)
      • 実装例
        • def del_srv6_encap_entry(self, match_dst_addr):
          srv6_table_obj = self.bfrt_info.table_get("SwitchIngress.srv6.srv6_transit_v6")
          if match_dst_addr == 'all':
          key_list = None
          else:
          key_list = [srv6_table_obj.make_key([gc.KeyTuple('hdr.ipv6.dst_addr',
          gc.ipv6_to_bytes(match_dst_addr))])]
          srv6_table_obj.entry_del(self.target, key_list=key_list)
        • if match_dst_addr == 'all'以下にて、エントリのマッチ条件を指定し、削除を実施。全エントリの削除には、key_list に “None” を渡す。

  • マッチアクションテーブルの取得方法
    1. 実装対象 : Syncd に srv6.py を作成して追加
    2. 追加する API
      • entry_get(target, key_list=None, flags={“from_hw”:True}, required_data=None, metadata=None)

      • 実装例
        • def show_srv6_encap_entry(self):
          srv6_table_obj = self.bfrt_info.table_get("SwitchIngress.srv6.srv6_transit_v6")
          resp = srv6_table_obj.entry_get(self.target, None, {"from_hw": False})
          i = 0
          for data, key in resp:
          key_dict = key.to_dict()
          match_dst_addr = key_dict["hdr.ipv6.dst_addr"]["value"]
          data_dict = data.to_dict()
          sid1 = data_dict["sid1"]
          sid2 = data_dict["sid2"]
          sid3 = data_dict["sid3"]
          # Display the contetns of the entry
          print("Entry %d:" % i)
          print("Entry key:")
          print(" hdr.ipv6.dst_addr : {:#032x}".format(match_dst_addr))
          print("Entry data (action : SwitchIngress.srv6.t_encaps_sid3):")
          print(" sid1 : {:#032x}".format(sid1))
          print(" sid2 : {:#032x}".format(sid2))
          print(" sid3 : {:#032x}".format(sid3))
          print("")
          i += 1
      • srv6_table_obj.entry_get(self.target, None, {"from_hw": False})にて、エントリのマッチ条件は指定せず、全エントリのデータをテーブルから取得
      • for data, key in resp : にて、エントリから設定値を取得
      • print にて Entry 内容の表示処理を実施

サマリ

・インテル®提供の Tofino ASIC 向け switch.p4 コードに、ユーザー所望の機能を P4 で追加する方法を記載しました

・追加した機能を NOS から制御するために CLI コマンドを追加する方法を記載致しました

・ご興味ある方は、是非、P4 を活用して ユーザー様ご所望の機能の実装をお試しください

 

コメントを残す