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.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 処理を記載します
- 次に、今回実装した、SRv6 の処理について、コードを解説致します。
-
- 定義した関数を 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 にて追加します
- 概要
- SRv6 マッチアクションテーブルを制御するコマンドの追加
- 以下のコマンドを追加してきます
gRPC Client の追加
- 以下の図ように、BFRuntime が インテル® から制御用に提供されております。これは インテル® P4 SDE内に配置されており、BFRuntime を使用して実装した機能の制御が可能です。Syncd Container 内に、gRPC Client を追加することで、実装した機能を制御致します。
- 以下の図ように、BFRuntime が インテル® から制御用に提供されております。これは インテル® P4 SDE内に配置されており、BFRuntime を使用して実装した機能の制御が可能です。Syncd Container 内に、gRPC Client を追加することで、実装した機能を制御致します。
- マッチアクションテーブルの追加方法
- 実装対象 : Syncd に srv6.py を作成して追加
- 追加する API
entry_add
(target, key_list=None, data_list=None,atomicity=bfruntime_pb2.WriteRequest.CONTINUE_ON_ERROR, metadata=None)
- 実装例
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(マッチアクション) テーブルの削除方法
- 実装対象 : Syncd に srv6.py を作成して追加
- 追加する 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)
エントリのマッチ条件を指定し、削除を実施。全エントリの削除には、key_list に “None” を渡す。if match_dst_addr == 'all'以下にて、
- マッチアクションテーブルの取得方法
- 実装対象 : Syncd に srv6.py を作成して追加
- 追加する 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 を活用して ユーザー様ご所望の機能の実装をお試しください