LinuxでIPv6 Segment Routing (SRv6)を試す
追記: 本記事の内容は標準化される前の内容であり、プロトコルの挙動(とくにSRHの処理)については標準化されている動作とは異なります。幸いなことに多くの方にお読みいただいておりますが、上記の点についてご留意いただきますよう、何卒ご了承いただきますようお願いいたします。
SFC-RG Advent Calendar 2017 - Qiita 16日目の記事です。今回は IPv6 Segment Routing (SRv6) の紹介と、Linux Kernel の実装と iproute2 を使った簡単な動作検証を行います。Segment Routing (SR) ではデータプレーンとして IPv6 と MPLS が選択可能ですが、今回の記事ではデータプレーンが MPLS となる SR-MPLS については取り扱いません。あらかじめご承知おきください。
Segment Routing について
Segment Routing は IETFが中心となってアーキテクチャの策定が進められているルーティングプロトコルです。2017年12月現在、RFCとして標準化はされていませんが、Ciscoをはじめ、一部のベンダで先行して実装されていたり、国内のISPにおいて採用されるなど、深く注目を浴びている技術の一つです。
通常IPトラフィックは最短経路に基づいて転送されますが、Segment Routing を用いると、トラフィックが指定したノードやパスを通過するように制御することができます。具体的には SID (Segment Identifier) と呼ばれる識別子を用いてネットワーク上のノードやリンクに付与し、識別することでこのような制御を可能としています。SRv6 の場合、SID はIPv6アドレスがその代替となっており、指定したIPv6アドレスを経由するようにトラフィックが転送されます。
SRv6に限らず、Segment Routing の動作については SDN 系のカンファレンスである ACM SOSR 2017 (Symposium on SDN Research) で発表されたこちらの動画が参考になると思います。(元論文: IPv6 Segment Routing to the End Host: A Linux Kernel Implementation)
SRv6 の動作原理
IPv6をデータプレーンとして用いるSRv6では、トラフィックの転送にあたってSRH (Segment Routing Header) と呼ばれる拡張ヘッダが挿入されます。拡張ヘッダの構造は次のようになっています。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Header | Hdr Ext Len | Routing Type | Segments Left | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Last Entry | Flags | Tag | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Segment List[0] (128 bits IPv6 address) | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | ... | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Segment List[n] (128 bits IPv6 address) | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // // Optional Type Length Value objects (variable) // // // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Segment List が経由するIPv6アドレス1つ1つを表し、Segment List の合計が Segment Left のフィールドに格納されています。また、SRv6では HMAC というメッセージ認証符号が用いられています。IPv6では以前、Type 0 Routing Header (RH0) を用いてソースルーティングを実現していたのですが、このヘッダを使うことで最大88倍の増幅攻撃が可能であることが発覚したため、RFC 5095 でRH0の実装が反対されています。こうした増幅攻撃を抑えるために、SRv6ではHMACを用いてトラフィックの認証が行われています。
具体的な動作原理については、下図のようになります。SR Ingress Node と呼ばれるノードがSRHを挿入し、Segment Endpoint と呼ばれる中間ルータが次の宛先を確認し、Segment Leftの値を1つ減じて次の経路へ転送する役割を果たします。残りのセグメントが0となった場合に SR Egress Node と呼ばれるノードが SRH を剥がし、もとのトラフィックが本来の宛先へと転送される仕組みになっています。
Linux Kernel の実装を使った検証
上記の説明を踏まえたうえで、実際にSRv6を動かしてみましょう。Linux Kernel 4.10 より SRv6 が マージされたので、今回ではその実装を用いてSRv6を試してみます。Ubuntu 17.04 が Linux Kernel 4.10 を採用しているので、今回はこれを利用して環境の構築と動作確認をします。
また、検証に用いたトポロジは下図のようになります。各ノードはLinuxのNetwork Namespaceを使ってエミュレートしています。router1 がSRHを挿入するSR Ingress Node, router2 がSRHを検査する Segment Endpoint, router3 がSRHを除去する SR Egress Node として動作します。
検証に用いた仮想環境の Vagrantfile を下記のリポジトリに置いています。仮想マシンを起動後、 /root/scripts 以下の create-namespaces.sh を実行すると、それぞれの namespace が作成され、SRv6の設定に必要なカーネルパラメータが設定されます。
github.com
経由する経路を追加する
通常の経路選択では host1 から host2 への通信において node1, node2 のいずれも通過しません。これを SRv6 を設定することで、 fc00:c::/64 宛の通信は node1 を経由するように設定します。
$ ip netns exec router1 bash # すでに設定されている経路を書き換えるため、一旦 fc00:c::/64 への経路を削除 $ ip netns exec router1 ip -6 route del fc00:c::/64 $ ip netns exec router1 ip -6 route add fc00:c::/64 encap seg6 mode encap segs fc00:b::10 dev veth-rt1-h1
コマンドを実行後、router1 で IPv6 の経路を確認してみます。
$ ip netns exec router1 ip -6 route show fc00:a::/64 dev veth-rt1-h1 proto kernel metric 256 pref medium fc00:b::/64 via fc00:12::2 dev veth-rt1-rt2 metric 1024 pref medium fc00:c::/64 encap seg6 mode encap segs 1 [ fc00:b::10 ] dev veth-rt1-rt2 metric 1024 pref medium fc00:12::/64 dev veth-rt1-rt2 proto kernel metric 256 pref medium fc00:23::/64 via fc00:12::2 dev veth-rt1-rt2 metric 1024 pref medium fe80::/64 dev veth-rt1-h1 proto kernel metric 256 pref medium fe80::/64 dev veth-rt1-rt2 proto kernel metric 256 pref medium
fc00:c::/64 (host2の属するnetmask) への経路が fc00:b::10 を経由するように設定されていることが確認できます。
パケットの送信とキャプチャ
この状態で node1 で tcpdump を実行し、host1 から host2 へ ICMPパケットを送信してみます。
正しく SRv6 が動作しているか確認するために、 node1 で tcpdump を実行します。
$ ip netns exec node1 bash $ tcpdump -i veth-node1-rt2 -w seg6.pcap
準備が完了したら別ウインドウを開き、 host1(fc00:a::2)からhost2(fc00:c::2)へパケットを送信します。
$ ip netns exec host1 ping6 -c 3 fc00:c::2
パケットが送信し終わったら、キャプチャされたパケットを確認してみます。Wireshark でキャプチャしたパケットを確認してみると、SRHが挿入されていることが見て取れます。encap seg6 mode encap にするとIPv6パケットがSRHを含むIPv6パケットでカプセリング(IPv6 over IPv6 でカプセリング)されます。
(参考) 複数の経路を挿入する
複数の経路を挿入する場合は、下記のように encap segs の後にカンマ区切りでIPv6アドレスを指定することで設定できます。設定後、別ウインドウを開いて node1, node2 で tcpdump を実行してみると、確かにトラフィックが流れていることが確認できます。
$ ip netns exec router1 ip -6 route add fc00:c::/64 encap seg6 mode encap segs fc00:b::10,fc00:b::20 dev veth-rt1-h1
終わりに
本記事では Linux Kernel の実装を用いて SRv6 の検証を行い、実際に動作することを確認しました。Linux KernelにおけるSRv6の実装については SRv6 - Linux Kernel implementation | Main / HomePage や Implementing IPv6 Segment Routing in the Linux kernel も参考になるので、より深く学びたい方はこちらも参照してください。
また、まだ試せていないのですが、VPP を用いた SRv6 も動作するようです(VPPでSRv6を動かしてみた - Qiita)。こちらと Linux Kernel の実装の相互接続性についても今後試してみたいです。