SRv6 コントロールプレーン調査 (I-D,RFC読書メモ)
概要と目的
この記事は下書き中なので, 書きかけの文章を含むことがあります. また情報の過不足や要望に関してなにかあれば私までTwitterかなにかで こっそり教えていただけるととても参考になりありがたいです. (twitter; slankdev)
私はSRv6のVPNv4を実装したい. そのために必要なコントロールプレーン周りの 知識をある程度網羅的に理解することがこの調査の目標. まずはよく説明される I-Dを流し読みする. 間違った情報を含んでいる可能性が高い. 参照は自己責任でお願いします. 2019.10時点でのSRv6 dataplaneの挙動に関しては理解しているという前提ですすめる. 勉強をしながらこれは修正していけたらよいと思っている... 基本的に僕がサラッと目を通したのは, network-prog, service-prog,のみだった気がするが... srhの定義と, T.hoge, End.hogeが理解出来ていたらよいと思う.
IANA's Border Gateway Protocol Parameters definition
IANA(Internet assigned numbers authority)で定義されるBGPに関する番号 に関しての原本情報. たとえ実装がなくとも,提案と定義がされてればここに番号 が記録されているので, わからなくなったらこれを確認するとよい. (SSHのポート番号はなに? とかの答えの正確なソースはこれ)
Multiprotocol Extensions for BGP-4
MP-BGPのRFC. これに対応するBGPをBGP4+と呼ぶこともあるようだ. 通常BGPはIPv4 prefixの情報のみを送るためのものだったが, MP-BGPではそれ以外の情報も通知できるためのメッセージフォーマットの 定義をしている. 新たにAddress familyという識別子を定義して, それを用いることによって, IPv4, IPv6の識別や, それ上のVPN等の情報を伝播できるようにした. 現代ではL3VPNやEVPN, BGP flowspecなど様々な技術で活用されている.
BGP/MPLS IP Virtual Private Networks (VPNs)関連 (RFC4364, RFC4659)
そもそもSRv6のVPNv4を考えるときはベースとなるMPLSのVPNv4について復習が必要. RFC4364ではIPバックボーンを使用してIP-VPNを提供する方法について書かれている. RFC4364のIPv6 VPN版がRFC4659である.今回はVPNv6は対象外なので一旦放置. このRFCでは新たに以下を定義している.
- Route Distingisher
- Route Traget
- VPNv4 Address-Family
- VPNv6 Address-Family
Segment Routing Prefix SID extensions for BGP (draft-ietf-idr-bgp-prefix-sid-27)
このI-Dでは以下を定義 - BGP Path Attribute, Type40 (https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-2)
BGP Prefix SID TLV --> BGPでSRの情報を通知するメッセージフォーマットの基本
- Type0: reserved
- Type1: label index tlv
- Type2: ipv6 sid
- Type3: originator SRGB tlv
- Type 4-254: unassigned (後のdraftですでに定義されているものあり.)
- Type: 255: reserved
SR-MPLS SIDs. のための仕様
要するに, SR-MPLSを使って, IP-VPNをどのように実現するかのための 基本的な情報を定義している. このI-DではSRv6は範囲外だねと議論されている.
BGP Signaling for SRv6 based Services (draft-dawra-idr-srv6-vpn-05)
このI-DではSR policyをVPNに適用する方法やEVPNへの適用に関しても かかれているが,その部分はこの記事ではすべて省略している.
このI-DではSRv6でl3vpn/evpnを行うための, 手順と必要なメッセージタイプ の定義を行っている. RFC7432(BGP MPLS-Based Ethernet VPN)をベースに どのようにSRv6をそれにアプライするかが主眼であるようだ.
アンダーレイの到達正やそこでのTEは別に記述されていて, VPNで使われるprefixの伝搬をBGPでやるよという話. それらをSRv6 Net-progの考えで抽象化して広告するよという話.
以下のBGPで使われるTLVが定義される. 各PEルータは, VPNプレフィックに合わせたSRv6 SID(IPv6 address prefix)を 広告してそのVPNプレフィックに対する処理を各PEルータ間でやり取りする. (実際は裏でEnd.DXとかEnd.DTとかになっている.) このdraftでは前で説明したdraftのbgp-prefix-sidのBGP-prefix SIDの新しい Type5,6を定義している.
BGP Prefix SID の TLV ()
- Type5: SRv6 L3 Service TLV
- L3VPNを識別する. End.DX{4,6},DT{4,6}等に適用される
- Type6: SRv6 L2 Service TLV
- L2VPNを識別する. End.DX{2,2V},DT{2U,2M}等に適用される
- Type5: SRv6 L3 Service TLV
BGP Prefix SID の Sub-TLV (https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#tunnel-sub-tlvs)
- Type
VPNで利用されるSIDはRFC4364(BGP/MPLS IP Virtual Private Networks (VPNs)) と同様の方法で広告される. SID広告のフォーマットはI-D.ietf-idr-bgp-prefix-sidと 同様の形式で広告される. それぞれ以下のようなAFI/SAFIペアになる.
- VPNv4 o-SRv6: AFI/SAFI = 1/128
- VPNv6 o-SRv6: AFI/SAFI = 2/128
- Global IPv4 o-SRv6: AFI/SAFI = 1/1
- Global IPv6 o-SRv6: AFI/SAFI = 2/1
- 参考: AFI{1:IPv4, 2:IPv6}, SAFI{1:Ucast, 128:UNDEFINED-yet new...?}
基本的にMPLS-VPNv4のMP_REACH_NLRIをそのまま同じフォーマットで流用する. SID情報に関しては別のTLV(SRv6 Service TLV)で情報通知をするようだ.
開発時のBGP動作のDebugger
WiresharkはBGPをよく解析してくれる. そしてWiresharkはBGP-Prefix-SID path attr を理解しているらしいので, BGPdを拡張してその通信をwiresharkに食わせればおそらく 解析できるはずだ. sourceにもdissectorの情報があった. ただし, TLV typeは1-3までじか実装されていないので, 5,6は実装しないと解析出来ない. まあこれでええやろと思うが, BGPdが出来たあかつきにはそういうのも手を出してみたい.
参考文献
このメモを作るために参考にしたスライド等を示す.
- BGPチュートリアル2017 :: JANOG40 BGPの基本的なことを学ぶのにはこのスライドを使った. MP-BGPの 説明は殆どないが, 基本的な BGPまでの内容はすべてこのスライドでよいのでは ないかと思う. Cplaneパケットのフォーマット等も記載されている.
- http://enog.jp/wp-content/uploads/2018/12/MK_ENOG-SRv6-CP.pdf SRv6のコントロールプレーンに関する内容をIETF目線で説明してくれている. 今回読んだRFCやI-Dはこのスライドに乗っているものを優先的に参照した.
Cuishark version 2.0開発の雑記
cuishark内部でのパケット解析をlibwiresharkを用いて行うことで, wiresharkの対応プロトコルを全てcuisharkでいじれるようにするという素晴らしい話のための雑記.
開発方針
version1は以下のようになっていた.
version2では以下のようにしようと思う.
そのようにした理由を論す. バックエンドに関しては単純に「wiresharkのdissectorを持ってきたい. 」という単純な理由である. しかしlibwireshark はドキュメントがなかったり少しめんどくさいので苦労すると思われる.
フロントエンドに関しては「TUI書くのクソだるい」という理由である. さすがにcuisharkのフロントエンドをCでゴリゴリ書くよりは書きやすい言語でやりたいなということ.
グルー部分に関しては, バックエンドを「libwiresharkpy」見たいな形にしてctypesでpythonラップしてフロントから叩こうかなと思っている. 今時, linuxのネットワークインターフェースとpcap/pcapngからパケットが読めれば満足なはず(DPDKでもtapリダイレクトは簡単)なので. パケット入力はその二種類でやろうかなと思っている.
Cuishark ver2 設計
以下のコンポーネントを考えている
libws_cpp
以下tsharkを参考にしている.
- dissector_new/freeは一回しか行わない
- dissectorは1パケットごとにresetする
- 以下の機能を自由に使えることを確認する
- pcapファイルから受信
- netifから受信
- dfilterが使える
- packet summaryが表示できる
- packet detail が表示できる (ツリー構造)
libws_py
pythonから以下のことができればOKと思っている.
- パケットフィルタ(display filter)を叩ける
- 入力IFを選べる (pcap/pcapng/netif)
- パケットの生dataが取れる
- パケットの dissect結果がツリーレベルで取れる.
これらのことを可能にするC/C++プログラムをまずかいて, それをpythonでラップすることにしている.
余談: libwiresharkのいじり方
libwiresharkとは
libwiresharkとは, wiresharkの中で利用されている. パケット解析のライブラリである. wiresharkは莫大な量のプロトコルに対応しているが, それらのプロトコルの解析部分 (wireshark内部ではdissectorと呼ぶ) はlibwiresharkのサブセットとして, wiresharkとは独立している. libwiresharkはwiresharkの開発者があとから実装の一部を分離しただけの「ドキュメントもほとんどない. 使用方法が謎なライブラリである.」
しかし, これを使いこなすことができれば, 事実上ではwiresharkの対応している豊富なプロトコルが簡単に解析できるプログラムがかけるはずである. (そのようなソフトウェアはとても少ない.)
もっともシンプルなコード
write soon
typedef struct _packet_info { const char *current_proto; /**< name of protocol currently being dissected */ struct epan_column_info *cinfo; /**< Column formatting information */ guint32 presence_flags; /**< Presence flags for some items */ guint32 num; /**< Frame number */ nstime_t abs_ts; /**< Packet absolute time stamp */ nstime_t rel_ts; /**< Relative timestamp (yes, it can be negative) */ frame_data *fd; union wtap_pseudo_header *pseudo_header; wtap_rec *rec; /**< Record metadata */ GSList *data_src; /**< Frame data sources */ address dl_src; /**< link-layer source address */ address dl_dst; /**< link-layer destination address */ address net_src; /**< network-layer source address */ address net_dst; /**< network-layer destination address */ address src; /**< source address (net if present, DL otherwise )*/ address dst; /**< destination address (net if present, DL otherwise )*/ guint32 vlan_id; /**< First encountered VLAN Id if present otherwise 0 */ const char *noreassembly_reason; /**< reason why reassembly wasn't done, if any */ gboolean fragmented; /**< TRUE if the protocol is only a fragment */ struct { guint32 in_error_pkt:1; /**< TRUE if we're inside an {ICMP,CLNP,...} error packet */ guint32 in_gre_pkt:1; /**< TRUE if we're encapsulated inside a GRE packet */ guint32 in_erspan_i:1; /**< TRUE if we're encapsulated inside an ERSPAN type I packet */ } flags; port_type ptype; /**< type of the following two port numbers */ guint32 srcport; /**< source port */ guint32 destport; /**< destination port */ guint32 match_uint; /**< matched uint for calling subdissector from table */ const char *match_string; /**< matched string for calling subdissector from table */ gboolean use_endpoint; /**< TRUE if endpoint member should be used for conversations */ struct endpoint* conv_endpoint; /**< Data that can be used for conversations */ guint16 can_desegment; guint16 saved_can_desegment; int desegment_offset; /**< offset to stuff needing desegmentation */ #define DESEGMENT_ONE_MORE_SEGMENT 0x0fffffff #define DESEGMENT_UNTIL_FIN 0x0ffffffe guint32 desegment_len; guint16 want_pdu_tracking; guint32 bytes_until_next_pdu; int p2p_dir; GHashTable *private_table; /**< a hash table passed from one dissector to another */ wmem_list_t *layers; /**< layers of each protocol */ guint8 curr_layer_num; /**< The current "depth" or layer number in the current frame */ guint16 link_number; guint16 clnp_srcref; /**< clnp/cotp source reference (can't use srcport, this would confuse tpkt) */ guint16 clnp_dstref; /**< clnp/cotp destination reference (can't use dstport, this would confuse tpkt) */ int link_dir; /**< 3GPP messages are sometime different UP link(UL) or Downlink(DL) */ GSList* proto_data; /**< Per packet proto data */ GSList* dependent_frames; /**< A list of frames which this one depends on */ GSList* frame_end_routines; wmem_allocator_t *pool; /**< Memory pool scoped to the pinfo struct */ struct epan_session *epan; const gchar *heur_list_name; /**< name of heur list if this packet is being heuristically dissected */ } packet_info; struct epan_dissect { struct epan_session *session; tvbuff_t *tvb; proto_tree *tree; packet_info pi; }; typedef struct epan_dissect epan_dissect_t; struct data_source { tvbuff_t *tvb; char *name; };
References
某メモ
この記事はメモであり, 編集中の内容です.
こんなことがありまして...
DPDKを用いた高性能パケットフィルタの開発とかを!
— Hiroki SHIROKURA (@slankdev) 2018年2月20日
- パケット解析プログラム開発
- DPDKの基礎,入門
- 簡単なパケットフィルタ開発
- 10GbEの高性能化#もしセキュキャン講師になったらやりたいこと #seccamp
- パケット解析プログラム開発
- DPDKの基礎,入門
- 簡単なパケットフィルタ開発
- 10GbEの高性能化
いただいたご意見
NFVガイドラインのようなものを少しまとめた
以下に置いた(画像部分をhatebuに変換するのが大変だったのでリンクにした) もしリンクがきれていたら, Twitter: @slankdevまで連絡をください. すぐ直します。
DPDKアプリをビルドする自前Makefile
DPDKどドキュメントによると、DPDKを使用したアプリケーションでは用意された雛形のMakefileを 一部変更することでビルド環境を提供しているが、自分で何かをつくりたいときにそんなことを やってられない。
ここでは自分で作成したMakefileでDPDKのアプリケーションをビルドする方法をまとめていく。
答えを見つけるまでのアプローチ方法
以下のアプローチで進めていけば一応絶対わかる。
- オリジナルのビルド手順をすべて確認する
- makeにnオプションを与えて実行されるコマンドを確認する
- そのコマンドから少しずついらないオプションを減らしていく
- どんなDPDKアプリでもビルドできる状態で限界まで減らす
- 完成