SRv6/SR-MPLS相互接続を実現するための機能をFRRに実装してみた(インターンシップ体験記)

こんにちは、インターン生の横尾です。

2024年2月に2週間実施されたNTTコミュニケーションズの現場受け入れ型インターンシップに参加させていただきました。普段は、大学院でユーザサイトにおけるIPv6マルチホーミングなどの研究に取り組んでいます。

今回のインターンシップでは、「次世代キャリアネットワークの開発エンジニア」というテーマで、OSSのソフトウェアルータであるFRRouting(以降、FRR)に、SRv6とMPLS/SR-MPLSの相互接続を実現するための機能を実装しました。この記事では、このテーマで取り組んだ内容について具体的に紹介します。

目次

インターンシップに参加した経緯

私は以前からSegment Routing(SR)という技術に興味を持っていましたが、Linuxでデータプレーンを少し触った程度で、次のステップにハードルを感じていました。 私が今回インターンシップで参加させていただいたチームでは、Segment Routingに関する取り組みを外部カンファレンス等で発表しており、しばしば目にする機会があったため、より詳細な業務内容を知りたいと思いました。 そして、過去にこのインターンシップへ参加された方々の参加Blogを拝見し、「ネットワーク × ソフトウェア開発」という内容に興味を惹かれ応募を決めました。 このインターンシップを通して、Segment Routingやネットワークに関するソフトウェア開発の知見を得たいと思いました。

インターンシップで取り組んだこと

今回のインターンシップでは、SRv6とMPLS/SR-MPLSの相互接続について説明されている SRv6 and MPLS interworking(draft-agrawal-spring-srv6-mpls-interworking-14)を参考に、FRRへのSRv6とMPLS/SR-MPLSの相互接続機能の実装に取り組みました。 現在、NTT コミュニケーションズでは、ドコモグループの持つSR-MPLSネットワークにおいて、新たな付加価値の提供や運用の効率化を目指し、サービスファンクションチェイニングの利用やモバイルネットワークでの活用が期待されているSRv6ネットワークとの相互接続検証が実施されています。 今後の相互接続検証にあたって、独自の機能拡張が可能かつSRv6/SR-MPLSの相互接続機能を提供するルータが必要になってきます。 そこで今回のインターンシップでは、OSSソフトウェアルータであるFRRにSRv6とSR-MPLSの相互接続機能を実装することを目標としています。

インターンシップの目標達成に向けて、まずは複数のSR-MPLSネットワークを接続するMulti-AS Segment Routingの仕組みと挙動の理解を進めました。 その後、SR-MPLSネットワークとSRv6ネットワークの相互接続に置き換えて、現行実装での挙動と本来期待する挙動の差分を整理し、拡張が可能なソフトウェアルータであるFRRに目的の機能を実装しました。

以下では、私が今回のインターンシップにおいて実施した業務内容を具体的に紹介します。

L3VPN Inter-AS Option-B w/MPLS の動作確認

まずはじめに、現在NTTコミュニケーションズのTestbedにて運用されているMulti-AS SR-MPLSネットワーク(L3VPN Inter-AS Option-B w/MPLS)について理解を深めます。 そもそも、なぜSingle-ASではなくMulti-ASなのかや、なぜInter-ASの接続方式がOption-Bなのかといった技術選定の背景については以下の外部向け発表資料や連載記事をご参照ください。

以下のような開発環境でFRRを設定しながら、経路情報の広告とパケットの流れについて確認します。

この環境は、2つのルータで構成されるAS同士を接続したコア網で、顧客1(Customer-1)を収容しています。各ルータにはFRRをインストールし、AS内はIS-IS、AS間はBGPを用いて経路情報を交換します。VPNのパラメータは以下の通りです。

  • SR Domain 1
    • AS 番号: 65001
    • VRF 100:
      • RD/RT: 65001:100
      • AS間でのVPNv4経路の広告用RT: 64999:100
  • SR Domain 2
    • AS 番号: 65002
    • VRF 100:
      • RD/RT: 65002:100
      • AS間でのVPNv4経路の広告用RT: 64999:100

はじめに、PE-MPLS-1が受け取った経路情報を確認します。192.168.2.0/24の経路が受信できています。ここでは省略しますが、PE-MPLS-2では192.168.1.0/24の経路の受信を確認できます。

PE-MPLS-1# show bgp ipv4 vpn neighbors 10.255.1.2 received-routes detail
BGP table version is 1, local router ID is 10.255.1.1, vrf id 0
Default local pref 100, local AS 65001
Route Distinguisher: 65002:100
BGP routing table entry for 192.168.2.0/24, version 1
not allocated
Paths: (1 available, best #1)
  Not advertised to any peer
  65002
    10.255.1.2 (metric 20) from 10.255.1.2 (10.255.1.2)
      Origin incomplete, localpref 100, valid, internal, best (First path received)
      Extended Community: RT:64999:100
      Remote label: 81
      Last update: Sun Mar 24 17:20:14 2024

Total number of prefixes 1

host1からhost2に対してpingによる疎通確認を行います。疎通確認によって、L3VPNが構築できていることが確認できます。

root@host1:/# ping 192.168.2.254 -c 3
PING 192.168.2.254 (192.168.2.254) 56(84) bytes of data.
64 bytes from 192.168.2.254: icmp_seq=1 ttl=60 time=0.428 ms
64 bytes from 192.168.2.254: icmp_seq=2 ttl=60 time=0.071 ms
64 bytes from 192.168.2.254: icmp_seq=3 ttl=60 time=0.073 ms

--- 192.168.2.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2052ms
rtt min/avg/max/mdev = 0.071/0.190/0.428/0.167 ms

このとき、ASBR間のパケットをキャプチャしてみると、MPLS ラベルを用いてパケットが転送されていることが確認できます。

root@ASBR-MPLS-1:~#  sudo tcpdump -nni net1
sudo: unable to resolve host ASBR-MPLS-1: Temporary failure in name resolution
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on net1, link-type EN10MB (Ethernet), capture size 262144 bytes
17:24:21.208266 MPLS (label 81, exp 0, [S], ttl 62) IP 192.168.1.254 > 192.168.2.254: ICMP echo request, id 6, seq 1, length 64
17:24:21.208555 MPLS (label 80, exp 0, [S], ttl 62) IP 192.168.2.254 > 192.168.1.254: ICMP echo reply, id 6, seq 1, length 64
17:24:22.236020 MPLS (label 81, exp 0, [S], ttl 62) IP 192.168.1.254 > 192.168.2.254: ICMP echo request, id 6, seq 2, length 64
17:24:22.236079 MPLS (label 80, exp 0, [S], ttl 62) IP 192.168.2.254 > 192.168.1.254: ICMP echo reply, id 6, seq 2, length 64
17:24:23.260009 MPLS (label 81, exp 0, [S], ttl 62) IP 192.168.1.254 > 192.168.2.254: ICMP echo request, id 6, seq 3, length 64
17:24:23.260064 MPLS (label 80, exp 0, [S], ttl 62) IP 192.168.2.254 > 192.168.1.254: ICMP echo reply, id 6, seq 3, length 64
^C
6 packets captured
6 packets received by filter
0 packets dropped by kernel

以上を踏まえて、経路情報の広告とパケットの流れについて以下の図にまとめます。

SRv6とMPLS/SR-MPLSの相互接続を実現するために必要な機能の確認

続いて、先ほどの開発環境のうちAS65002をSRv6網に変更し、現状を確認します。

先ほどと同様に、PE-MPLSが受け取った経路情報を確認します。192.168.2.0/24の経路は受け取れているようですが、SRv6で転送する経路だと判断し、SRv6拡張ヘッダでカプセル化するようにカーネルへ設定されてしまっています。PE-MPLSはSR-MPLS網のルータであるため、MPLSヘッダでのカプセル化が期待されます。

PE-MPLS# show bgp ipv4 vpn neighbors 10.255.1.2 received-routes detail
BGP table version is 1, local router ID is 10.255.1.1, vrf id 0
Default local pref 100, local AS 65001
Route Distinguisher: 65002:100
BGP routing table entry for 192.168.2.0/24, version 1
not allocated
Paths: (1 available, best #1)
  Not advertised to any peer
  65002
    10.255.1.2 (metric 20) from 10.255.1.2 (10.255.1.2)
      Origin incomplete, localpref 100, valid, internal, best (First path received)
      Extended Community: RT:64999:100
      Remote label: 16
      Remote SID: fd00:0:0:44::
      Last update: Sun Mar 24 19:55:00 2024

Total number of prefixes 1
PE-MPLS#
root@PE-MPLS:~# ip route show vrf USER-1
192.168.1.0/24 dev net1 proto kernel scope link src 192.168.1.1
192.168.2.0/24 nhid 12  encap seg6 mode encap segs 1 [ fd00:0:0:44:1:: ] via 10.1.2.2 dev net0 proto bgp metric 20

なぜこのようなことが起きているのかを確かめるため、ASBR-MPLSからPE-MPLSへ送信されるMP-BGPのUPDATEメッセージのパケットをキャプチャしてみると、「Path Attribute - BGP Prefix-SID」が含まれていることがわかります。このAttributeが含まれているため、PE-MPLSはこの経路情報をSRv6で転送する経路だと判断してしまったと考えられます。

続いて、PE-SRv6が受け取った経路情報を確認します。192.168.1.0/24の経路は受け取れているようですが、MPLSで転送する経路だと判断し、MPLSヘッダでカプセル化するようにカーネルへ設定されてしまっています。PE-SRv6はSRv6網のルータであるため、SRv6拡張ヘッダでのカプセル化が期待されます。

PE-SRv6# show bgp ipv4 vpn neighbors fd00::3 received-routes detail
BGP table version is 1, local router ID is 10.255.1.4, vrf id 0
Default local pref 100, local AS 65002
Route Distinguisher: 65001:100
BGP routing table entry for 192.168.1.0/24, version 1
not allocated
Paths: (1 available, best #1)
  Not advertised to any peer
  65001
    0.0.0.0 (metric 20) from fd00::3 (10.255.1.3)
      Origin incomplete, localpref 100, valid, internal, best (First path received)
      Extended Community: RT:64999:100
      Remote label: 80
      Last update: Sun Mar 24 19:25:54 2024

Total number of prefixes 1
PE-SRv6#
root@PE-SRv6:~# ip route show vrf USER-1
192.168.1.0/24 nhid 17  encap mpls  80 via inet6 fe80::4819:ccff:fe22:992d dev net0 proto bgp metric 20
192.168.2.0/24 dev net1 proto kernel scope link src 192.168.2.1

以上のことから、この環境では大きく2つの問題点が挙げられます。

  • 問題1: SR-MPLS網にも関わらず、SRv6拡張ヘッダでencapしようとしている(192.168.2.0/24 の経路)
  • 問題2: SRv6網にも関わらず、MPLSヘッダでencapしようとしている(192.168.1.0/24 の経路)

次に、ASBR間のeBGPピアを一度no activateし、上記の2つの問題が生じない環境下で、データプレーンへ直接経路情報を挿入して最終的な理想の形を確認します。データプレーンへの経路情報の挿入には、iproute2を用いて行います。先ほどと同じように経路情報の広告とパケットの流れについて、iproute2で挿入する経路情報と一緒に以下の図にまとめます。

iproute2を用いてデータプレーンへ経路情報を直接挿入した後、host1からhost2に対してpingによる疎通確認を行います。疎通確認によって、SRv6とMPLS/SR-MPLSの相互接続をした上でL3VPNが構築できていることが確認できました。これで、最終的にデータプレーンにどのような設定が挿入されれば良いのか、イメージを掴むことができました。

root@host1:/# ping 192.168.2.254 -c 3
PING 192.168.2.254 (192.168.2.254) 56(84) bytes of data.
64 bytes from 192.168.2.254: icmp_seq=1 ttl=61 time=0.201 ms
64 bytes from 192.168.2.254: icmp_seq=2 ttl=61 time=0.126 ms
64 bytes from 192.168.2.254: icmp_seq=3 ttl=61 time=0.138 ms

--- 192.168.2.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2049ms
rtt min/avg/max/mdev = 0.126/0.155/0.201/0.032 ms

実装

続いて、先ほどiproute2で直接データプレーンに設定した内容をコントロールプレーン(BGP)で配布するための実装をします。問題1と問題2を解決するために必要なBGPの処理を考えます。 前提条件として、MPLS網のルータは、SRv6の機能が実装されていないと仮定します。そのため、追加実装はSRv6網のルータ(ASBR-SRv6)を対象とします。 また、各構造体の処理条件は現状の実装に従って紹介します。これらの条件については、拡張性を考慮してもっと検討する必要があるかもしれません。

  • 問題1:
    • ASBR-SRv6で、PE-SRv6から受け取ったVPNv4経路情報をSRv6からMPLSに変換してASBR-MPLSへ配布する
    • このとき、UPDATEメッセージに含まれるBGP Prefix-SID Attributeを削除する
  • 問題2:
    • ASBR-SRv6で、ASBR-MPLSから受け取ったVPNv4経路情報をMPLSからSRv6に変換してPE-SRv6へ配布する
    • このとき、新たにUPDATEメッセージにBGP Prefix-SID Attributeを追加する

はじめに、追加機能を設定するためのコマンドを作成します。BGPのピアに対してneighbor <A.B.C.D|X:X::X:X|WORD> seg6-mpls-label-switchingが設定されていれば、そのピアに対してSRv6とMPLS/SR-MPLSの相互接続を実施すると判断します。それに伴い、BGPのピアに関するあらゆる情報を持つstruct peerに新たなフラグPEER_FLAG_SEG6_MPLS_LABEL_SWITCHINGを追加します。

次に、ラベルのマッピングを行います。Inter-AS Option-B方式において、隣接ASからラベル付きのL3VPN経路情報を受信し、ローカルラベルで再広告する際に、受信ラベルとローカルラベルのマッピング情報を扱うための構造体があります(struct bgp_mplsvpn_nh_label_bind_cache)。l3vpn-multi-domain-switchingが設定されている場合にnew_labelがセットされ、ラベルのSWAPがカーネルへ設定されます。

/* used to bind a local label to the (label, nexthop) values
 * from an incoming BGP mplsvpn update
 */
struct bgp_mplsvpn_nh_label_bind_cache {

    /* RB-tree entry. */
    struct bgp_mplsvpn_nh_label_bind_cache_item entry;

    /* The nexthop and the vpn label are the key of the list.
     * Only received BGP MPLSVPN updates may use that structure.
     * orig_label is the original label received from the BGP Update.
     */
    struct prefix nexthop;
    mpls_label_t orig_label;

    /* resolved interface for the paths */
    struct nexthop *nh;

    /* number of mplsvpn path */
    unsigned int path_count;

    /* back pointer to bgp instance */
    struct bgp *bgp_vpn;

    /* MPLS label allocated value.
     * When the next-hop is changed because of 'next-hop-self' or
     * because it is an eBGP peer, the redistributed orig_label value
     * is unmodified, unless the 'l3vpn-multi-domain-switching'
     * is enabled: a new_label value is allocated:
     * - The new_label value is sent in the advertised BGP update,
     * instead of the label value.
     * - An MPLS entry is set to swap <new_label> with <orig_label>.
     */
    mpls_label_t new_label;

    /* list of path_vrfs using it */
    LIST_HEAD(mplsvpn_nh_label_bind_path_lists, bgp_path_info) paths;

    time_t last_update;

    bool allocation_in_progress;
};

既存の実装では、受信したMPLSのVPNラベル配布するMPLSのVPNラベル のマッピングしかできません。そこで、以下を定義します。

  • 受信したSRv6のVPN SID配布するMPLSのVPNラベル にマッピングする関数
  • 受信したMPLSのVPNラベル配布するSRv6のVPN SID にマッピングする関数

実際にマッピングを行う関数はvoid bgp_mplsvpn_nh_label_bind_register_local_label()として定義されています。これを参考に、 MPLSのVPNラベルSRv6のVPN SID のマッピングを行う関数を定義します。

extern void bgp_mplsvpn_sid_bind_register_local_label(  
    struct bgp *bgp, struct bgp_dest *dest, struct bgp_path_info *pi, afi_t afi);  
extern void bgp_mplspvn_nh_label_bind_register_sid(  
    struct bgp *bgp, struct bgp_dest *dest, struct bgp_path_info *pi, afi_t afi);  

そして、VPNv4経路情報を受け取りラベル情報をインポートする際に、場合分けを行って適切にマッピングします。今回は、受け取ったUPDATEメッセージにBGP Prefix-SID Attributeが含まれていれば、受信したSRv6のVPN SID配布するMPLSのVPNラベル にマッピング、含まれていないかつPEER_FLAG_SEG6_MPLS_LABEL_SWITCHINGが設定されたピアからの経路情報であれば 受信したMPLSのVPNラベル配布するSRv6のVPN SID にマッピングするという単純な条件で処理を行います。

static void
bgp_mplsvpn_handle_label_allocation(struct bgp *bgp, struct bgp_dest *dest,
                    struct bgp_path_info *new_select,
                    struct bgp_path_info *old_select, afi_t afi)
{
  ...
        } else if (new_select->attr->srv6_l3vpn) {
            // srv6 -> mpls
            bgp_mplsvpn_sid_bind_register_local_label(
                bgp, dest, new_select, afi);
        } else if (!new_select->attr->srv6_l3vpn && 
                    CHECK_FLAG(new_select->peer->af_flags[afi][SAFI_MPLS_VPN], 
                        PEER_FLAG_SEG6_MPLS_LABEL_SWITCHING)) {
            // mpls -> srv6
            bgp_mplspvn_nh_label_bind_register_sid(
                bgp, dest, new_select, afi);
        } else
  ...
}

次に、BGP Prefix-SID Attributeの削除・追加部分を実装します。UPDATEメッセージに付与するAttributeの構築はbgp_size_t bgp_packet_attribute()で行います。この関数では、struct stream *sへ必要なAttributeのデータを挿入します。以下は、Origin Attributeを構築している箇所です。

 /* Origin attribute. */
    stream_putc(s, BGP_ATTR_FLAG_TRANS);
    stream_putc(s, BGP_ATTR_ORIGIN);
    stream_putc(s, 1);
    stream_putc(s, attr->origin);

struct attr *attrには、利用可能なAttributeの構造体がたくさん定義されています。たとえば、BGP Prefix-SID Attributeの情報は、attr->srv6_l3vpnにあります。このポインタがNULLであれば付与しません。今回は、以下のようにピアがeBGPかつ、PEER_FLAG_SEG6_MPLS_LABEL_SWITCHINGフラグがオンであればattr->srv6_l3vpnをNULLにすることで削除を実現しています。

 /* SRv6 Service Information Attribute. */
    if ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_MPLS_VPN) {
        /* draft-spring-srv6-mpls-interworking-service-iw (yokoo) */
        if (peer->sort == BGP_PEER_EBGP && 
            CHECK_FLAG(peer->af_flags[afi][safi], 
                PEER_FLAG_SEG6_MPLS_LABEL_SWITCHING) &&
            afi == AFI_IP) { /* not supported ipv6 vpn */
            attr->srv6_l3vpn = NULL;
        }

一方付与の場合は、ピアがiBGPかつ、SAFI(Subsequent Address Family Indicator)がMPLS VPNであれば、attr->srv6_l3vpnを構築するようにしています。attr->srv6_l3vpnの構築は、既存コードを参考に行います。

bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer,
                struct stream *s, struct attr *attr,
                struct bpacket_attr_vec_arr *vecarr,
                struct prefix *p, afi_t afi, safi_t safi,
                struct peer *from, struct prefix_rd *prd,
                mpls_label_t *label, uint32_t num_labels,
                bool addpath_capable, uint32_t addpath_tx_id,
                struct bgp_path_info *bpi)
{
...
    if (peer->sort == BGP_PEER_IBGP &&
            afi == AFI_IP && safi == SAFI_MPLS_VPN) { /* not supported ipv6 vpn */
        zlog_info("Create BGP Prefix-SID attribute for SRv6 MPLS interworking");
        /* attr->srv6_l3vpn を構築する */
        ...
    }
...
}

動作検証

最後に動作検証を行います。host1からhost2へpingを送信し、その様子を各リンクでパケットキャプチャします。MPLS網ではMPLSヘッダで、SRv6網ではSRv6拡張ヘッダでカプセル化されて転送されているのがわかります。また、Inter-ASはMPLSラベルを用いて転送されていることも確認できます。

root@Customer-1:/# ping 192.168.2.254 -c 3
PING 192.168.2.254 (192.168.2.254) 56(84) bytes of data.
64 bytes from 192.168.2.254: icmp_seq=1 ttl=61 time=0.747 ms
64 bytes from 192.168.2.254: icmp_seq=2 ttl=61 time=0.194 ms
64 bytes from 192.168.2.254: icmp_seq=3 ttl=61 time=0.215 ms

感想

2週間という短い期間で、事前知識の学習から実装まで完走でき、非常に良い経験になりました。FRRoutingに機能を追加するのは初めての試みであり、膨大なソースコードや多様な構造体を理解することは難しかったですが、大きなやりがいを感じることができて非常に楽しかったです。

トレーナーの竹中さんには、全工程を通して手厚くサポートいただき、ネットワーク技術やソフトウェア開発に関するさまざまなことを学ぶことができました。特に実装工程では、多くの相談に乗っていただき、最後の最後までデバッグにお付き合いいただきました。また、ミーティングに参加させていただいたり、データセンターを見学させていただいたりと、貴重な経験を積むことができました。本当にありがとうございました。

私は、前半はリモート、後半は出社という少し特殊な形で参加させていただきましたが、リモートと対面の両方を経験できたのは非常に良かったです。 対面だけでなく、リモートでもコミュニケーションが取りやすい環境を提供していただき、作業をスムーズに進められました。また、懇親会はハイブリットで開催していただき、他のインターン生やチームの社員の方々との交流の場を提供していただき、職場の雰囲気を肌で感じることができました。

本インターンを通して多くの学びや経験を得ることができました。改めまして、本当にありがとうございました。

トレーナーからのコメント

トレーナーを担当したイノベーションセンターの竹中です。横尾さん、2週間のインターンシップお疲れ様でした。

今回のインターンではSRv6ネットワークとSR-MPLSネットワークの相互接続という新規機能を、ソフトウェアルータへ実装いただきました。 祝日を含んだ2週間という短い作業期間でこの難易度の高い目標を見事達成したことは本当に嬉しい成果です。

ソフトウェアルータの機能拡張は、プログラミングスキルだけでなく、ルータが対話に用いるルーティングプロトコルに関する深い知識も必要になってきます。そのため前半数日をSegment Routingを用いたネットワークにおけるルーティングプロトコルの挙動学習、残りの作業日をコードリーディング・機能実装の時間としていました。 インターン開始当初は作業可能な期間から想定し、プロトコルの挙動学習は軽い動作確認、コードリーディングは問題点を洗い出したもの・実装する箇所をピンポイントで共有して実装だけに注力してもらう予定でした。 しかし、持ち前の高い技術学習力による想定を大きく超えた進捗だったため、不足している機能の洗い出しから実装方針の検討まで実施してもらう方針へ変更しました。インターンシップ目標を達成した横尾さんの実装力はもちろんのこと、その過程でのプロトコルの挙動学習速度もとても印象に残っています。

実装いただいたSRv6ネットワークとSR-MPLSネットワークの相互接続機能は、今後我々が実施していくSRv6/SR-MPLS相互接続環境における検証の核になる機能です。今後の我々の検証において大いに活用させていただきます。

このインターンシップを通して得られた経験が今後の横尾さんの活動にも役立つことを願っています。 改めて、インターンシップへのご参加とご活躍、ありがとうございました!

© NTT Communications Corporation All Rights Reserved.