VPPにSRv6 MUP Plugin APIを追加している話

この記事は BBSakura Networksアドベントカレンダー 2022 の23日目の記事です。

こんにちは。BBSakura Networksのエンジニアをしている( @takemioIO )です。

普段はモバイルコアを開発しているチームでソフトウェア開発しています。1日目の記事を書いたり、このアドベントカレンダーの主宰をしています。

背景

SRv6 MUP の OSS について何か行う際には DPlane を選択する必要がありますが、世の中での実装は大変少なく ebikenさんのP4, 拙作のXDP, VPP での実装が世の中に存在しています。

この中でソフトウェア実装としてエコシステムも含めると一番成熟しているのは VPP の実装で、以前JANOGでArcOSでSRv6MUPに関する実装の発表をされていた村上さんが SRv6 MUP Plugin のメンテナーですので、大変しっかりとした実装になっています。

ですが高度な操作を行うには CPlane を経由して経路を注入することが必要で、現状では VPP の CLI 経由でのみでしか行えず残念ながらプログラマビリティが足りてない状態にあります。 VPP は本来 API を提供していて、それを使って C や Go などから操作することが可能になっていることから、コントローラーを開発しやすくする工夫がされています。

本記事ではその VPP に実装されてる SRv6 MUP Plugin に対して の API を実装して便利にしようという記事です。

注意: ここでの引用するコードや解説のリンクは タグ stable/2210 を対象としています。

調査

既存APIの調査

本体に入っている SRv6 を見るとこちらは API が実装されてて/src/vnet/srv6/sr.apiを見ると API 定義が書かれています。 例えば以下のようなフォーマットがあって、SRv6 policy を追加するAPIスキーマがあります。

autoreply define sr_policy_add
{
  u32 client_index;
  u32 context;
  vl_api_ip6_address_t bsid_addr;
  u32 weight;
  bool is_encap;
  bool is_spray;
  u32 fib_table;
  vl_api_srv6_sid_list_t sids;
};
...

このAPIスキーマは VPP が独自実装&定義しているもので、これをベースに C のヘッダー定義等のAPIのスケルトンが出力されます。このスケルトンに合わせて実装を書いていく感じです。詳しくは周辺コードを読んだりしていくとよくわかります。 もし API を実装するならばここの機能を拡張するか、別途定義することになりそうですね。

SRv6 MUP のプラグイン周りの実装調査

/src/plugins/srv6-mobile に MUP 関連のソースファイルが置かれています。 ここのコードを読むコツを End.M.GTP6.D を例にして出すと localsidpolicyの機能を追加するレジスター関数を中心に読むと良いです。

cf. /src/plugins/srv6-mobile/gtp6_d.c#L236-L249

  rc = sr_localsid_register_function (vm, fn_name, keyword_str, def_str, param_str, 128, //prefix len
                      &dpo_type,
                      clb_format_srv6_end_m_gtp6_d,
                      clb_unformat_srv6_end_m_gtp6_d,
                      clb_creation_srv6_end_m_gtp6_d,
                      clb_removal_srv6_end_m_gtp6_d);
  if (rc < 0)
    clib_error_return (0, "SRv6 Endpoint GTP6.D LocalSID function"
               "couldn't be registered");
  rc = sr_policy_register_function (
    vm, fn_name, keyword_str, def_str, param_str, 128, // prefix len
    &dpo_type, clb_format_srv6_end_m_gtp6_d, clb_unformat_srv6_end_m_gtp6_d,
    clb_creation_srv6_end_m_gtp6_d_2, clb_removal_srv6_end_m_gtp6_d_2);

これらはざっくりいうと以下のこの4つに機能に必要なものを載せています。

  • format: 実際にこの機能をコールしてトレースした時のフォーマットを出力します。
  • unformat: CLI で入力したときに実際にパースを行います。
  • creation: コンストラクタ、初期化を行うときはここで行います。 MUP では使ってませんがAD-flow などでは使ってます。
  • removal: デストラクタ、CLI でパースした後に保存したパラメーターを保存するメモリーを解放等をします。

今回はここの unformat でどんなパラメーターを受け取っているのかを確認し、それをAPIでどのように表現するか考える必要がありそうです。

方針

前述の通り、今回の場合では VPP CLI に対応した機能を追加する必要がありました。 実際に VPP にあるSRv6 MUPのドキュメントのCLIの例を見ると policy localsid のどちらも利用して実装されてることから2種類の API の注入機能を実装することになります。

# 以下、t.m.gtp4.d、end.m.gtp6.dの例
sr policy add bsid SID behavior t.m.gtp4.d DST-PREFIX v6src_prefix SRC-PREFIX [nhtype {ipv4|ipv6|non-ip}]
sr localsid prefix SID-PREFIX behavior end.m.gtp6.d DST-PREFIX [nhtype {ipv4|ipv6|non-ip}]

方法としては実際には2つの方法があり、「本体にあるSRv6機能を拡張することで実現する」、または「別途専用にAPIを定義する」という選択肢がありますが、今回は Plugin としてせっかく実装されているので後者を選択し API もちゃんと分割することにしました。(ただし削除などの機能は本来の localsidpolicyのAPIより削除することとします)

また同時に unformat で利用されてるパース部分を切り出し、API側の実装と共通化を図ります。

開発方法とパッチを出すときの Tips

ここでは開発の時の Tips とパッチを出すときに知ってたら便利だったことを書きます。

開発で使えるTips

buildしてinstall までの基本形

make install-dep
make install-ext-deps
make build-release
make pkg-deb
sudo dpkg -i build-root/*.deb
# 開発中にビルドして即実行する時(イテレーション)
make build
make run

特定のテストを実行する

# https://github.com/FDio/vpp/blob/stable/2210/test/test_srv6_mobile.py#L10 のテストだけ実行させる例
# V=2 で詳しくログを出す, TEST_JOBS=auto タスクランナーの並列数を自動化
V=2 TEST_JOBS=auto TEST="test_srv6_mobile.TestSRv6EndMGTP4E" make test

VPP 本体に関する操作

# vpp の interfaceがおかしい場合は以下のコマンドでバス情報を取得して動作させる
sudo lshw -class network -businfo
# vpp に着信してきたパケットを見るとき
# cf. https://fd.io/docs/vpp/v2101/gettingstarted/progressivevpp/traces.html
trace add dpdk-input 10
show trace
clear trace
# vppのfib, arpのテーブルを見る
show ip fib
show ip neighbors
show ip6 fib

Goライブラリから API で叩けるようにする時

govpp ライブラリが提供されており、そこからも API が実行できます。その際に新しく追加した API を動かしたい場合は自分でスキーマからビルドしてあげる必要があります。以下はその例です。binapi というディレクトリに一式出力されます。

go install go.fd.io/govpp/cmd/binapi-generator@master
binapi-generator --input-dir=$VPP_PATH/build-root/install-vpp-native/vpp/share/vpp/api --output-dir=binapi

パッチを出す時のTips

基本的にhttps://s3-docs.fd.io/vpp/22.10/contributing/gitreview.htmlを見ると色々書いてるのでこれ通りに進めればオッケー

注意点は commit message に Type を書いてあげる必要があって機能追加ならばType: feature、バグフィックスならばType: fix などがあるのでちゃんと記述する必要があります。

また以下のようにして commit 時に Signoff-By をしてあげる必要があります。

git commit -s

これでコミットを作成した後は以下のコマンドでコードや Git のコミットメッセージの体裁を確認します。

make checkstyle-all

あとはパッチ提出時に git review command を使うので結構慣れてない自分は戸惑いながら使っています。Gerrit 使い方 とか調べて利用するのも良いでしょう。

後は CI が動作するので無事通ることを祈るばかりです。 この開発で自分が引いた問題の一つに CI 環境でのコンパイラが GCC-11 起因で通らなかったケースがありましたのでそういう場合は ci-management という CIに関するリポジトリを見て察することができます。

この場合のケース対策として export CC=gcc-11 とすることで手元でも無事デバッグできます。

で、その実装とそのパッチは結局どうなってるの?

実装はこちらで、https://gerrit.fd.io/r/c/vpp/+/37628 で今パッチ出して作業中です。

状態としてはメンテナーの村上さんからは Approve は出ましたがマージ権限を持つメンテナから Approve が出ていないのでテストを追加等を鋭意作業中です...🥺

(2023/12/21 追記) https://gerrit.fd.io/r/c/vpp/+/40104 で無事マージされました! レビュワーの村上さんに大変な感謝を表明します。

最後に

今回は、VPP の SRv6 MUP Plugin を弄れる API 機能を追加してみる話でした。いやーブログまでにマージまで持っていきたかったがなかなかうまくいかないものですね...

初めて VPP のコードを触ったんですが実際にはどうするといいか結構悩みました。例えば Plugin 機能で分けてはいるが同じ CLI コマンドを拡張するような実装にしたゆえの抽象化の漏れみたいになっている点をどうしたら綺麗に出来るかなどは考えることがあったなーと思いました。結局素直に実装することにしましたが・・・

BBSakura Networks ではこのように IETF で標準化中の新しい技術を使った業務を普段から行っており、BGP や SRv6 等のネットワーク技術に明るく、3GPP の仕様が読めて、プログラミングもできる仲間を募集しています。 気になった方は 採用情報 からご連絡ください。

よろしくお願いいたします。