More than Engineering

技術メモと日記

RFC7404: Using Only Link-Local Addressing inside an IPv6 Networking

少し古い記事であるが、『Change of paradigm with IPv6 : no global addresses on router interfaces | APNIC Blog』という RFC7404 の内容に関する解説記事を読んだ。

記事の内容を一言で要約すると「IPv6のネットワークを構築するにあたって、ルータ間のリンクにグローバルユニキャストアドレスやユニークローカルアドレスを振る必要ってないし、リンクローカルアドレス (LLA: Link-local Addres) だけでも実現できるよね」というものである。

その内容が技術的に面白く、自分自身関心のある領域であったということもあったので、原文を読んでみてその内容をまとめてみた。

続きを読む

LinuxでIPv6 Segment Routing (SRv6)を試す

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 Routing Header の構造

Segment List が経由するIPv6アドレス1つ1つを表し、Segment List の合計が Segment Left のフィールドに格納されています。また、SRv6では HMAC というメッセージ認証符号が用いられています。IPv6では以前、Type 0 Routing Header (RH0) を用いてソースルーティングを実現していたのですが、このヘッダを使うことで最大88倍の増幅攻撃が可能であることが発覚したため、RFC 5095 でRH0の実装が反対されています。こうした増幅攻撃を抑えるために、SRv6ではHMACを用いてトラフィックの認証が行われています。

具体的な動作原理については、下図のようになります。

f:id:skjune12:20171216224443p:plain
SRv6の動作原理
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 として動作します。

f:id:skjune12:20171216210530p:plain
検証に用いたトポロジー

検証に用いた仮想環境の 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 でカプセリング)されます。

f:id:skjune12:20171216221935p:plain
Wiresharkで見たのSRv6のパケット

(参考) 複数の経路を挿入する

複数の経路を挿入する場合は、下記のように 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 : browseImplementing IPv6 Segment Routing in the Linux kernel も参考になるので、より深く学びたい方はこちらも参照してください。

また、まだ試せていないのですが、VPP を用いた SRv6 も動作するようです(VPPでSRv6を動かしてみた - Qiita)。こちらと Linux Kernel の実装の相互接続性についても今後試してみたいです。

GoBGPでEVPN/VXLANを試す

SFC-RG Advent Calendar 2017 - Qiita の9日目の記事です。EVPNと呼ばれるL2VPNを実現するプロトコルの紹介と、仮想環境上でGoBGPを使って、EVPN/VXLANを試したときの記録になります。

EVPNについて

EVPN (Ethernet VPN) は RFC 7432 で標準化されたL2VPNを実現するプロトコルです。既存のVPNの制約を解決できることからISPやDC事業者を中心に注目を浴びている技術の一つです。用途としては広域イーサーネットサービスの提供におけるL2VPNの実現やDC間の接続が挙げられます。この数年でベンダーの実装が整ってきたため、ここから数年で広くデプロイメントされていく技術ではないかと個人的には考えています。

EVPNの特徴としてコントロールプレーンとデータプレーンが分離されていることを挙げることができます。具体的にはコントロールプレーンにBGP(MP-BGP)を用いてMACアドレスの学習を行うため、BUMトラフィックを抑制することができると同時に、今回紹介するVXLANやMPLS、PBBなど、用途に応じたデータプレーンを選択することができるというメリットがあります。例えばVPLSでは対向のMACアドレスを学習する上でL2の情報をフラッディングしたり、シグナリングにLDPを用いるものとBGPを用いるもので相互接続性がないといった問題がありましたが、EVPNを用いる事でこうした課題を解決することができ、スケールアウトが容易なVPNを実現することが可能であることが期待されています。そのほか、マルチホーム環境下において Active-Active の構成を取ることができることもEVPNのメリットの1つと言うことができるでしょう。

EVPNに関連した日本語の情報は少ないのですが、詳細については EVPN: Intro to next gen L2VPN - Packet Pushers -RFC 7432 - BGP MPLS-Based Ethernet VPN が参考になると思います。

VXLANについて

今回の検証ではデータプレーンにVXLANを用います。VXLANではイーサネットフレームをVXLANヘッダでカプセリングすることで、レイヤ3のIPネットワーク上に仮想的なレイヤ2のネットワークを構築することを可能にします。また、ネットワークの識別に24bit長のVNI (VXLAN Network ID) と呼ばれる識別子を用いることで、最大224個までネットワークを分割することができるため、212個までしかネットワークを分割することのできないVLANよりも多くのネットワークを識別をすることができます。

VXLANではVTEPを検出するためにマルチキャストを使っていますが、EVPNを用いる事でマルチキャストを透過的に扱うことができます。そのため、マルチキャストが使えない環境下においてもVXLANによるL2延伸を行うことができるというメリットがあります。

VXLANの詳細については JANOG37 :: VXLANチュートリアル の資料が参考になるので、興味がありましたらご一読ください。

GoBGPを用いた検証

こうした特徴を持つEVPNですが、GoBGPGoPlaneを使うことで手元でも動作させることができます。今回はVagrantを使って仮想マシン (Ubuntu 16.04) を3台立ち上げ、GoBGPを用いたEVPN/VXLANの動作検証を行いました。

検証に用いたトポロジーは下記の通りになります。仮想マシン3台をPCルータ(PE)として動作させ、それぞれのゲストOS内でNamespaceを分割し、CEとして動作させています。また、ゲストOSではそれぞれGoBGPでBGPデーモンを動かし、それぞれのVMごとにBGP neighborを確立させています。CEとして動作している Network Namespace に属する仮想インターフェースは同じレイヤ2ネットワーク(192.168.1.0/24)に所属しています。
f:id:skjune12:20171209220910p:plain

また、検証では本トポロジーにおける gobgp1からgobgp2 および gobgp1からgobgp3 のスループットを iperf を使って計測しました。

環境のセットアップ

下記のリポジトリに今回の検証で用いたVagrantfileとセットアップの手順を置いてあります。環境のセットアップに関しては README.md を参照してください。
github.com

BGPのステータス確認

EVPNではBGP経由でL2の情報を学習するため、各VMでBGPが Establish しているかどうかを確認します。

root@gobgp1:~# gobgp neigh
Peer          AS  Up/Down State       |#Received  Accepted
10.0.12.20 65000 00:00:32 Establ      |        2         2
10.0.23.20 65000 00:00:09 Establ      |        2         2

ピア同士で BGP status が Establish されていれば OK です。

VTEPの状態の確認

root@gobgp1:~# gobgp global rib -a evpn
    Network                                           Next Hop             AS_PATH              Age        Attrs
*>  [type:multicast][rd:65000:10][etag:10][ip:1.1.1.1]0.0.0.0                                   00:01:20   [{Origin: i} {Pmsi: type: ingress-repl, label: 0, tunnel-id: 1.1.1.1} {Extcomms: [65000:10]}]
*>  [type:multicast][rd:65000:10][etag:10][ip:2.2.2.2]10.0.12.20                                00:01:01   [{Origin: i} {LocalPref: 100} {Extcomms: [65000:10]} {Pmsi: type: ingress-repl, label: 0, tunnel-id: 2.2.2.2}]
*>  [type:multicast][rd:65000:10][etag:10][ip:3.3.3.3]10.0.23.20                                00:00:38   [{Origin: i} {LocalPref: 100} {Extcomms: [65000:10]} {Pmsi: type: ingress-repl, label: 0, tunnel-id: 3.3.3.3}]

このときはまだL2の情報は学習されていません。

gobgp1から gobgp2, gobgp3 にICMPパケットを送信する

gobgp1 の network namespace 内の仮想インターフェース(192.168.1.1) から

  • gobgp2 の network namespace 内の仮想インターフェース(192.168.1.4)
  • gobgp3 の network namespace 内の仮想インターフェース(192.168.1.6)

へICMPパケットを送信してみます。

gobgp1 から gobgp2

root@gobgp1:~# ip netns exec vxlan ping -c 5 192.168.1.4
PING 192.168.1.4 (192.168.1.4) 56(84) bytes of data.
64 bytes from 192.168.1.4: icmp_seq=1 ttl=64 time=1003 ms
64 bytes from 192.168.1.4: icmp_seq=2 ttl=64 time=0.637 ms
64 bytes from 192.168.1.4: icmp_seq=3 ttl=64 time=0.735 ms
64 bytes from 192.168.1.4: icmp_seq=4 ttl=64 time=0.621 ms
64 bytes from 192.168.1.4: icmp_seq=5 ttl=64 time=0.988 ms

--- 192.168.1.4 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4010ms
rtt min/avg/max/mdev = 0.621/201.353/1003.788/401.217 ms

gobgp1 から gobgp3

root@gobgp1:~# ip netns exec vxlan ping -c 5 192.168.1.6
PING 192.168.1.6 (192.168.1.6) 56(84) bytes of data.
64 bytes from 192.168.1.6: icmp_seq=2 ttl=64 time=4.86 ms
64 bytes from 192.168.1.6: icmp_seq=3 ttl=64 time=1.20 ms
64 bytes from 192.168.1.6: icmp_seq=4 ttl=64 time=0.857 ms
64 bytes from 192.168.1.6: icmp_seq=5 ttl=64 time=0.647 ms

--- 192.168.1.6 ping statistics ---
5 packets transmitted, 4 received, 20% packet loss, time 4006ms
rtt min/avg/max/mdev = 0.647/1.894/4.869/1.729 ms

最初のパケットを送信するときに時間がかかりますが、その後で疎通性が取れていることがわかります。

再度VTEPの状態の確認してみる

root@gobgp1:~# gobgp global rib -a evpn
    Network                                                                                       Next Hop             AS_PATH              Age        Attrs
*>  [type:macadv][rd:0:0][esi:single-homed][etag:10][mac:a2:42:36:45:55:07][ip:<nil>][labels:[10]]0.0.0.0                                   00:00:31   [{Origin: i} {Extcomms: [VXLAN]}]
*>  [type:macadv][rd:0:0][esi:single-homed][etag:10][mac:be:5e:f1:3a:50:a8][ip:<nil>][labels:[10]]10.0.12.20                                00:00:31   [{Origin: i} {LocalPref: 100} {Extcomms: [VXLAN]}]
*>  [type:macadv][rd:0:0][esi:single-homed][etag:10][mac:c6:2a:b1:40:ed:07][ip:<nil>][labels:[10]]10.0.23.20                                00:00:22   [{Origin: i} {LocalPref: 100} {Extcomms: [VXLAN]}]
*>  [type:multicast][rd:65000:10][etag:10][ip:1.1.1.1]0.0.0.0                                   00:03:35   [{Origin: i} {Pmsi: type: ingress-repl, label: 0, tunnel-id: 1.1.1.1} {Extcomms: [65000:10]}]
*>  [type:multicast][rd:65000:10][etag:10][ip:2.2.2.2]10.0.12.20                                00:03:16   [{Origin: i} {LocalPref: 100} {Extcomms: [65000:10]} {Pmsi: type: ingress-repl, label: 0, tunnel-id: 2.2.2.2}]
*>  [type:multicast][rd:65000:10][etag:10][ip:3.3.3.3]10.0.23.20                                00:02:53   [{Origin: i} {LocalPref: 100} {Extcomms: [65000:10]} {Pmsi: type: ingress-repl, label: 0, tunnel-id: 3.3.3.3}]

正しくL2の情報が広報され、学習できたことが確認できました。

スループットの評価

参考までにiperfを用いてスループットを計測した際の検証結果を下図に示します。オーバーレイのMTUが1450、アンダーレイのMTUを1500に設定しているため、パケットサイズを 64, 100, 200, …, 1500 に変動させて計測しました。

f:id:skjune12:20171209222727p:plainf:id:skjune12:20171209222723p:plain
スループットの計測結果(左: 帯域、右: 転送量)

ショートパケットではほぼ同等の性能、MTUサイズが大きくなっても9割ほどの性能が出ていることが確認できました。実環境での検証はできていませんが、実運用でも耐えられるのではないでしょうか。

終わりに

本記事ではGoBGPを利用して仮想環境上でのEVPN/VXLANの動作検証を行い、実際にBGP経由でL2の情報を交換し、トラフィックの送受信をするところまで確認しました。
ただ、シングルホームの構成でのみの検証しかできていないので、今度はマルチホームにしたときの Active-Standby の挙動、Active-Active の挙動を確認してみたいと思います。

余談

EVPNやSRなど、ISPやDCのバックボーンで使われている技術について学習したいのですが、学生の身分だと限られた情報しか得られずもどかしいというのが本心です。どこかで情報交換をしているコミュニティがありましたらご教授いただけると幸いです。