インターンシップ体験記 〜SRv6 機能を Pola PCE に実装してみた〜

はじめに

こんにちは、インターン生の 魏心宇 と申します。 2023 年 2 月 6 日から 2 週間にわたって、NTT Com の現場受け入れ型インターンシップに参加させていただきました。 普段は大学で SR (セグメントルーティング) を用いたトラフィックエンジニアリングについて研究しています。

今回のインターンシップでは「SR を用いたキャリアネットワークの開発」をテーマに、NTT Com 発の OSS である Pola PCE への SRv6 機能実装・検証に取り組みました。 この記事では、2 週間の開発体験をご紹介します!

インターンシップに参加したモチベーション

私は学部 3 年の冬、NTT Com TechWorkshop 「プロのネットワークエンジニアと学ぶ!ISP ネットワークのつくりかた」 に参加したことがきっかけで、SR という斬新なネットワークアーキテクチャを知り、技術の素晴らしさに感銘を受けました。

SR を利用することで、従来では実現困難であった複雑な経路制御が簡単に実現可能となるため、国境を超えた高品質なネットワーク回線や特定のアプリに対するネットワークの混雑解消など様々な可能性を生み出し、新たなネットワークサービスを開拓できると期待しています。

NTT Com の R&D 部門であるイノベーションセンターでは SR をはじめとする次世代インターネット技術の研究開発が行われており、独自のアーキテクチャである Multi-AS SR をはじめとした先進的な社会実装の最前線となっています。 大学院に入り、日々の研究とネットワーク運用を通して最先端技術に触れる機会が増える中で、企業ではどのようなネットワーク技術が求められ活用されているのかを学びたいと思うようになり、今回のインターンシップに応募いたしました。

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

今回のインターンシップでは、SR-TE を一元的に管理するための OSS 実装である Pola PCE に対する SRv6 機能の追加と、複数のベンダーの機器(Cisco、Juniper)との相互接続を目標として定めました。

インターンシップの前提として、NTT Com イノベーションセンターではマルチベンダー機器環境で SR を検証しています。 検証内容については Multi-AS Segment Routing 検証連載 で公開されています。 SR、SR-TE、PCE とは何かについて詳しく紹介されているため是非参考にしてください。

[Multi-AS Segment Routing 検証連載#10] PCE を用いた SR-TE の一元管理 で紹介されている通り、SR-TE の実装には各 PE ルーターへの設定が必要であるため、台数が増えると管理が複雑になります。 そこでコントローラーを用いた SR-TE の一元管理により、パス計算の効率化や機器設定の簡素化などが期待できます。

Path Computation Element(PCE)は、前述した SR-TE の一元管理を実現するための技術です。 PCE には発行済み LSP/SR Policy を管理しない Stateless PCE と、これらの情報を管理する Stateful PCE が存在します。 Stateful PCE は既存の LSP/SR Policy を管理するため、それらを条件として考慮した複雑な経路を計算することが可能となります。

また、Stateful PCE には、クライアントである PCC からのリクエストに基づいた経路計算のみを行う Passive Stateful PCE と、自ら経路を発行し PCC へ送信する Active Stateful PCE が存在します。 Active Stateful PCE は新規 SR Policy の発行や既存 SR Policy の更新などの操作ができるため、SR Policy の集中的な管理に適しています。 PCE-PCC の間や複数の PCE 間の通信は、 RFC5440 により定義される Path Computation Element Protocol(PCEP)を用いて行われます。

Pola PCE は、SR-TE の一元管理を実現するための Stateful PCE であり、Active Stateful PCE として動作し、SR-TE の一元管理を実現します。 Pola PCE の概要は Pola PCE で SR 網の TE を体験してみよう! にて紹介されていますので、ぜひ参考にしてみてください。

インターンシップ参加時点では、Pola PCE の実装は SR-MPLS のみを対象としていました。 そのため、Pola PCE を Multi-vendor で動作する SRv6 PCE へと拡張することを今回のインターンシップの目標と定めました。

PCEP プロトコルの SRv6 拡張は draft-ietf-pce-segment-routing-ipv6 にて定義されています。 まだ Internet-Draft で議論中の技術であるため、各ベンダーの SRv6 PCC 機能の実装状況を確認しつつ、開発内容を調整する必要があります。 以下では、PCEP プロトコルの SRv6 拡張およびインターンシップでの実装内容を具体的に紹介します。

PCEP Extensions for SRv6

draft-ietf-pce-segment-routing-ipv6 では、RFC8664 と比べて主に以下の内容が追加されています。

  • SRv6 PCE Capability sub-TLV
    • PCC/PCE は、PCEP セッション初期化フェーズ(OPEN object)において、PATH-SETUP-TYPE-CAPILITYTLV に含まれる新しいPath Setup Type: 3と新しいSRv6-PCE-CAPABILITYsub-TLV を介して、SRv6 をサポートする能力を示します。
  • SRv6-ERO Subobject
    • SRv6 をサポートするために、ERO オブジェクトに含まれる新しいSRv6-EROサブオブジェクトが定義されました。
  • SRv6-RRO Subobject
    • SRv6 をサポートするために、RRO オブジェクトに含まれる新しいSRv6-RROサブオブジェクトが定義されました。

実装内容

今回のインターンシップでの実装内容について、Pola PCE への SRv6-ERO Subobject の追加 を例に説明します。

パケットキャプチャによるプロトコルの確認・理解

RFC 準拠のコントローラー&プロトコルライブラリ開発の進め方 で紹介されている通り、PCEP メッセージは単一の Common header と複数の Object から構成されます。 それをより直感的に理解するため、関連の RFC を読みながら、パケットキャプチャツールである Wireshark を使って PCEP メッセージの構造を確認します。

上図は、実際に PCC から PCE 宛てに送信された PCRpt メッセージのパケットキャプチャです。 図の通り、PCEP メッセージは、1 つの Common header と複数の Object から構成されています。 Common header にはそれがどの PCEP メッセージであるかを示す Message type が格納されており、今回の例で Common header の Message type は 10 で、RFC8231 の 8.2 節の定義の通り、PCRpt メッセージであることが分かります。 そして各 Object は Common object header と Object body から構成されていて、Common object header には、その Object を示す Object class と Object type が格納されています。 今回の例では Object class は 7、Object type は 1 であり、RFC5440 の 7.9 節を参照すると Explicit Route Object (ERO)であることが分かります。

上図の ERO Object の内容を詳しく確認すると、黄色でハイライトされいる Non defined subobject (40) が 2 つあります。内容は次の通りです。

SRv6 関連の Subobject はまだ Internet-Draft で議論中のため、Wireshark ではまだうまく認識されていません。 そこで前述の draft-ietf-pce-segment-routing-ipv6 の 4.3.1 節を参照すると、SRv6-ERO Subobject は下記のように定義されています。

    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
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |L|   Type=40   |     Length    | NT    |     Flags     |V|T|F|S|
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |              Reserved         |      Endpoint Behavior        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   |                      SRv6 SID (optional)                      |
   |                     (128-bit)                                 |
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   //                    NAI (variable, optional)                 //
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                     SID Structure (optional)                  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

パケットキャプチャの内容を見ると、Non defined subobject (40) の部分の 1 バイト目は A8 で、2 進数で変換すると 1010 1000 になります。 SRv6-ERO Subobject の定義と合わせて見ると、1010 1000 の 1 ビット目は'L' Flag の値、2 ビット目から 8 ビット目の 0101000Type の値で 10 進数の 40 と合致しているため、SRv6-ERO Subobject は、Non defined subobject (40) と表示されていることが分かります。

プロトコルライブラリの実装 - Pola PCE への SRv6-ERO Subobject の追加

これから Pola PCE へ SRv6-ERO Subobject を追加します。 まず、pkg/packet/pcep/object.go に SRv6-ERO Subobject の定義を追加します。

const ERO_SUBOBJECT_SRV6 uint8 = 0x28
const (
    NT_MUST_NOT_BE_INCLUDED     uint8 = 0x00 // draft-ietf-pce-segment-routing-ipv6 4.3.1
    NT_SRV6_NODE                uint8 = 0x02 // draft-ietf-pce-segment-routing-ipv6 4.3.1
    NT_SRV6_ADJACENCY_GLOBAL    uint8 = 0x04 // draft-ietf-pce-segment-routing-ipv6 4.3.1
    NT_SRV6_ADJACENCY_LINKLOCAL uint8 = 0x06 // draft-ietf-pce-segment-routing-ipv6 4.3.1
)

次に、draft-ietf-pce-segment-routing-ipv6 の 4.3.1 節を参照し、SRv6-ERO Subobject の構造体とパケット操作に関するメソッドを追加します。 パケット操作メソッドとしては、バイト列を構造体に格納する DecodeFromBytes()、構造体をバイト列に変換する Serialize() の 2 つのメソッドと、新たに構造体を作成する NewSRv6EroSubObject 関数と SRv6-ERO Subobject から SID を抽出する ToSegment() メソッドが必要となります。

type SRv6EroSubobject struct {
    LFlag         bool
    SubobjectType uint8
    Length        uint8
    NaiType       uint8
    VFlag         bool
    TFlag         bool
    FFlag         bool
    SFlag         bool
    Behavior      uint16
    Segment       table.SegmentSRv6
    Nai           netip.Addr
}

func (o *SRv6EroSubobject) DecodeFromBytes(subObj []uint8) error {
    o.LFlag = (subObj[0] & 0x80) != 0
    o.SubobjectType = subObj[0] & 0x7f
    o.Length = subObj[1]
    o.NaiType = subObj[2] >> 4
    o.VFlag = (subObj[3] & 0x08) != 0
    o.TFlag = (subObj[3] & 0x04) != 0
    o.FFlag = (subObj[3] & 0x02) != 0
    o.SFlag = (subObj[3] & 0x01) != 0
    o.Behavior = binary.BigEndian.Uint16(subObj[6:8])

    sid, _ := netip.AddrFromSlice(subObj[8:24])
    o.Segment = table.NewSegmentSRv6(sid)
    if o.NaiType == 2 {
        o.Nai, _ = netip.AddrFromSlice(subObj[24:40])
    }
    return nil
}

func (o *SRv6EroSubobject) Serialize() []uint8 {
    buf := make([]uint8, 4)
    buf[0] = o.SubobjectType
    if o.LFlag {
        buf[0] = buf[0] | 0x80
    }
    buf[1] = o.Length
    buf[2] = o.NaiType * 16
    if o.VFlag {
        buf[3] = buf[3] | 0x08
    }
    if o.TFlag {
        buf[3] = buf[3] | 0x04
    }
    if o.FFlag {
        buf[3] = buf[3] | 0x02
    }
    if o.SFlag {
        buf[3] = buf[3] | 0x01
    }
    reserved := make([]uint8, 2)
    behavior := make([]uint8, 2)
    binary.BigEndian.PutUint16(behavior, o.Behavior)
    byteSid := o.Segment.Sid.AsSlice()
    byteSRv6EroSubobject := AppendByteSlices(buf, reserved, behavior, byteSid)
    return byteSRv6EroSubobject
}

func (o *SRv6EroSubobject) Len() (uint16, error) {
    // The Length MUST be at least 24, and MUST be a multiple of 4.
    // An SRv6-ERO subobject MUST contain at least one of a SRv6-SID or an NAI.
    if o.NaiType == NT_MUST_NOT_BE_INCLUDED {
        // Type, Length, Flags (4byte) + Reserved(2byte) + Behavior(2byte) + SID (16byte)
        return uint16(24), nil
    } else if o.NaiType == NT_IPV6_NODE {
        // Type, Length, Flags (4byte) + Reserved(2byte) + Behavior(2byte) + SID (16byte) + Nai (16byte)
        return uint16(40), nil
    } else {
        return uint16(0), errors.New("unsupported naitype")
    }
}

func NewSRv6EroSubObject(seg table.SegmentSRv6) (*SRv6EroSubobject, error) {
    subo := &SRv6EroSubobject{
        LFlag:         false,
        SubobjectType: ERO_SUBOBJECT_SRV6,
        NaiType:       NT_MUST_NOT_BE_INCLUDED,
        VFlag:         false,
        TFlag:         false,
        FFlag:         true,
        SFlag:         false,
        Behavior:      uint16(1),
        Segment:       seg,
    }
    length, err := subo.Len()
    if err != nil {
        return subo, err
    }
    subo.Length = uint8(length)
    return subo, nil
}

func (o *SRv6EroSubobject) ToSegment() table.Segment {
    return o.Segment
}

そして、ERO Object から SR-ERO Subobject と SRv6-ERO Subobject を区別するために、ERO Object の DecodeFromBytes() メソッドを修正します。

func (o *EroObject) DecodeFromBytes(typ uint8, objectBody []uint8) error {
    o.ObjectType = typ
    if len(objectBody) == 0 {
        return nil
    }
    for {
        var eroSubobj EroSubobject
        if (objectBody[0] & 0x7f) == 36 {
            eroSubobj = &SREroSubobject{}
        } else if (objectBody[0] & 0x7f) == 40 {
            eroSubobj = &SRv6EroSubobject{}
        } else {
            return errors.New("invalid Subobject type")
        }
        if err := eroSubobj.DecodeFromBytes(objectBody); err != nil {
            return err
        }
        o.EroSubobjects = append(o.EroSubobjects, eroSubobj)
        if objByteLength, err := eroSubobj.Len(); err != nil {
            return err
        } else if int(objByteLength) < len(objectBody) {
            objectBody = objectBody[objByteLength:]
        } else if int(objByteLength) == len(objectBody) {
            break
        } else {
            return errors.New("srerosubobject parse error")
        }
    }
    return nil
}

ERO Object の新構造体を作成する NewEroSubobject 関数も同様に修正します。

func NewEroSubobject(seg table.Segment) (EroSubobject, error) {
    if v, ok := seg.(table.SegmentSRMPLS); ok {
        subo, err := NewSREroSubObject(v)
        if err != nil {
            return nil, err
        }
        return subo, nil
    } else if v, ok := seg.(table.SegmentSRv6); ok {
        subo, err := NewSRv6EroSubObject(v)
        if err != nil {
            return nil, err
        }
        return subo, nil
    } else {
        return nil, errors.New("invalid Segment type")
    }
}

これにより、Pola PCE へ SRv6-ERO Subobject に対する処理機能を追加できました。

以上では、Pola PCE への SRv6-ERO Subobject の追加を例に、今回のインターンシップでの Pola PCE 開発の流れを紹介しました。 それ以外にも、SRv6 に対応するため、Pola PCE における LSP Object の IPV6-LSP-IDENTIFIERS TLV の追加や、explicit SRv6-TE Policy を扱うための関数の修正などの変更を加えました。 詳しい説明は記事の長さの都合上、割愛しますが、ご興味のある方はこちらの PR をご参照ください。

動作検証

本章では、作成した実装を用い、複数のベンダーの機器(Cisco、Juniper)との相互接続検証を実施します。

検証環境は上図の通りで、環境の構築には OSS ツールの Containerlab を使用しました。 Containerlab を用いることで、YAML ファイルベースで簡単に検証環境を構築できます。 今回の検証用の Containerlab 設定ファイルはこちらの Repo で公開しています。 ぜひご活用ください。

SRv6 機能を追加した Pola PCE とベンダー機器との相互接続試験を行います。 まずは Containerlab の動作状態を確認します。

% sudo containerlab inspect
INFO[0000] Parsing & checking topology file: nttcom-internship.clab.yml
+---+---------------------------------+--------------+--------------------------------+--------+---------+----------------+----------------------+
| # |              Name               | Container ID |             Image              |  Kind  |  State  |  IPv4 Address  |     IPv6 Address     |
+---+---------------------------------+--------------+--------------------------------+--------+---------+----------------+----------------------+
| 1 | clab-nttcom-internship-host01   | 2758bf898616 | wbitt/network-multitool:latest | linux  | running | 172.20.20.5/24 | 2001:172:20:20::5/64 |
| 2 | clab-nttcom-internship-host02   | ee82fd3f4bba | wbitt/network-multitool:latest | linux  | running | 172.20.20.2/24 | 2001:172:20:20::2/64 |
| 3 | clab-nttcom-internship-host03   | f2747f464ffd | wbitt/network-multitool:latest | linux  | running | 172.20.20.8/24 | 2001:172:20:20::8/64 |
| 4 | clab-nttcom-internship-pcc-rt01 | acb1ba1cb010 | vrnetlab/vr-vmx:22.4R1.10      | vr-vmx | running | 172.20.20.7/24 | 2001:172:20:20::7/64 |
| 5 | clab-nttcom-internship-pcc-rt02 | 1928d17fae80 | ios-xr/xrd-control-plane:7.8.1 | xrd    | running | 172.20.20.3/24 | 2001:172:20:20::3/64 |
| 6 | clab-nttcom-internship-pcc-rt03 | f29848103688 | vrnetlab/vr-vmx:22.4R1.10      | vr-vmx | running | 172.20.20.6/24 | 2001:172:20:20::6/64 |
| 7 | clab-nttcom-internship-pola-pce | 0dc707b9dd09 | ghcr.io/nttcom/pola:latest     | linux  | running | 172.20.20.9/24 | 2001:172:20:20::9/64 |
| 8 | clab-nttcom-internship-xtc-pce  | f31bc03d953a | ios-xr/xrd-control-plane:7.8.1 | xrd    | running | 172.20.20.4/24 | 2001:172:20:20::4/64 |
+---+---------------------------------+--------------+--------------------------------+--------+---------+----------------+----------------------+

次に、Pola PCE のコンテナで Pola PCE を起動し、動作を確認します。

% sudo docker exec -it clab-nttcom-internship-pola-pce bash

root@pola-pce:~# cat polad.yaml
# polad.yaml
global:
  pcep:
    address: "[fc00::ffff]"
    port: 4189
  grpc-server:
    address: "127.0.0.1"
    port: 50051
  log:
    path: "/var/log/pola/"
    name: "polad.log"
  ted:
    enable: false

root@pola-pce:~# polad > /var/log/polad.log 2>&1 &
[1] 65

root@pola-pce:~# ps -aux | grep polad
root          65  0.0  0.0 1083748 11216 pts/1   Sl   07:18   0:00 polad
root          73  0.0  0.0   3468  1720 pts/1    S+   07:19   0:00 grep polad

pola session コマンドにより PCEP Session を確認します。

root@pola-pce:~# pola session
sessionAddr(1): fc00::1
sessionAddr(2): fc00::2
sessionAddr(3): fc00::3

pola sr-policy list コマンドにより各ルーター既存の SR Policy を確認します。

root@pola-pce:~# pola sr-policy list
Session: fc00::1
  PolicyName: vrf-100-policy-pcc-rt03
    SrcAddr: fd00:ffff::1
    DstAddr: 2a0b:2542:400:3:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:3:1::

Session: fc00::2
  PolicyName: cfg_vrf-100-policy-pcc-rt03_discr_10
    SrcAddr: fd00:ffff::2
    DstAddr: 2a0b:2542:400:3:1::
    Color: 100
    Preference: 10
    SegmentList: None

Session: fc00::3
  PolicyName: vrf-100-policy-pcc-rt01
    SrcAddr: fd00:ffff::3
    DstAddr: 2a0b:2542:400:1:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:1:1::
  PolicyName: vrf-100-policy-pcc-rt02
    SrcAddr: fd00:ffff::3
    DstAddr: 2a0b:2542:400:2:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:2:1::

SR Policy のパラメータを指定する yaml ファイルを確認します。

root@pola-pce:~# cat policy_explicit_rt01.yaml
# policy_explicit_rt01.yaml
srPolicy:
  pcepSessionAddr: "fc00::1"
  srcAddr: "fd00:ffff::1"
  dstAddr: "2a0b:2542:400:2:1::"
  name: vrf-100-policy-pcc-rt02-by-pola
  color: 100
  segmentList:
    - sid: "2a0b:2542:400:3:1::"
      nai: "fd00:ffff::3"
    - sid: "2a0b:2542:400:2:1::"
      nai: "fd00:ffff::2"

root@pola-pce:~# cat policy_explicit_rt02.yaml
# policy_explicit_rt02.yaml
srPolicy:
  pcepSessionAddr: "fc00::2"
  srcAddr: "fd00:ffff::2"
  dstAddr: "2a0b:2542:400:1:1::"
  name: vrf-100-policy-pcc-rt01-by-pola
  color: 100
  segmentList:
    - sid: "2a0b:2542:400:3:1::"
      nai: "fd00:ffff::3"
    - sid: "2a0b:2542:400:1:1::"
      nai: "fd00:ffff::1"

pola sr-policy add コマンドにより SR Policy を新たに発行します。

root@pola-pce:~# pola sr-policy add -f policy_explicit_rt01.yaml -l
success!

root@pola-pce:~# pola sr-policy add -f policy_explicit_rt02.yaml -l
success!

再び pola sr-policy list コマンドを実行し各ルーターの SR Policy を確認します。

root@pola-pce:~# pola sr-policy list
Session: fc00::1
  PolicyName: vrf-100-policy-pcc-rt03
    SrcAddr: fd00:ffff::1
    DstAddr: 2a0b:2542:400:3:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:3:1::
  PolicyName: vrf-100-policy-pcc-rt02-by-pola
    SrcAddr: fd00:ffff::1
    DstAddr: 2a0b:2542:400:2:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:3:1:: -> 2a0b:2542:400:2:1::

Session: fc00::2
  PolicyName: cfg_vrf-100-policy-pcc-rt03_discr_10
    SrcAddr: fd00:ffff::2
    DstAddr: 2a0b:2542:400:3:1::
    Color: 100
    Preference: 10
    SegmentList: None

Session: fc00::3
  PolicyName: vrf-100-policy-pcc-rt01
    SrcAddr: fd00:ffff::3
    DstAddr: 2a0b:2542:400:1:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:1:1::
  PolicyName: vrf-100-policy-pcc-rt02
    SrcAddr: fd00:ffff::3
    DstAddr: 2a0b:2542:400:2:1::
    Color: 100
    Preference: 100
    SegmentList: 2a0b:2542:400:2:1::

上記の pola sr-policy add コマンドおよび pola sr-policy list コマンドを実行すると、Pola PCE から以下のようなログが出力されます。

2023-04-14T11:39:28.367Z        info    Received Keepalive      {"session": "fc00::1"}
2023-04-14T11:39:46.795Z        info    received CreateSRPolicy API request     {"input": "{\"SRPolicy\":{\"pcepSessionAddr\":\"/AAAAAAAAAAAAAAAAAAAAQ==\",\"srcAddr\":\"/QD//wAAAAAAAAAAAAAAAQ==\",\"dstAddr\":\"KgslQgQAAAIAAQAAAAAAAA==\",\"color\":100,\"policyName\":\"vrf-100-policy-pcc-rt02-by-pola\",\"segmentList\":[{\"sid\":\"2a0b:2542:400:3:1::\"},{\"sid\":\"2a0b:2542:400:2:1::\"}]}}", "server": "grpc"}
2023-04-14T11:39:46.795Z        info    Send PCInitiate {"session": "fc00::1"}
2023-04-14T11:39:46.895Z        info    Received PCRpt  {"session": "fc00::1"}
2023-04-14T11:39:46.895Z        info    Finish Stateful PCE request     {"session": "fc00::1", "srpID": 1}
2023-04-14T11:39:48.829Z        info    Send Keepalive  {"session": "fc00::2"}
2023-04-14T11:39:48.837Z        info    Received Keepalive      {"session": "fc00::2"}
2023-04-14T11:39:51.430Z        info    received CreateSRPolicy API request     {"input": "{\"SRPolicy\":{\"pcepSessionAddr\":\"/AAAAAAAAAAAAAAAAAAAAg==\",\"srcAddr\":\"/QD//wAAAAAAAAAAAAAAAg==\",\"dstAddr\":\"KgslQgQAAAEAAQAAAAAAAA==\",\"color\":100,\"policyName\":\"vrf-100-policy-pcc-rt01-by-pola\",\"segmentList\":[{\"sid\":\"2a0b:2542:400:3:1::\"},{\"sid\":\"2a0b:2542:400:1:1::\"}]}}", "server": "grpc"}
2023-04-14T11:39:51.430Z        info    Send PCInitiate {"session": "fc00::2"}
2023-04-14T11:39:56.697Z        info    Send Keepalive  {"session": "fc00::3"}
2023-04-14T11:39:56.778Z        info    Received Keepalive      {"session": "fc00::3"}
2023-04-14T11:39:58.285Z        info    Send Keepalive  {"session": "fc00::1"}
2023-04-14T11:40:06.190Z        info    Receive GetSRPolicyList API request     {"server": "grpc"}
2023-04-14T11:40:06.190Z        info    Send SRPolicyList API reply     {"server": "grpc"}
2023-04-14T11:40:16.903Z        info    Received Keepalive      {"session": "fc00::1"}

確認した結果、Pola PCE と各ルーターとの PCEP Session が確立されており、Juniper vMX ルーターである pcc-rt01 の SR Policy が発行され、pcc-rt01 からの既存 SR Policy のレポートも正しく受け取っていることがわかります。 Cisco IOS XRd ルーターである pcc-rt02 については既存 SR Policy のレポートが受け取れておらず、Pola PCE から pcc-rt02 に対して SR Policy を発行できそうに見えますが、pcc-rt02 側が正しく受け取れているかどうかは不明のため、もっと詳細を調べる必要があります。

Wireshark を利用して PCEP パケットをキャプチャします。 Containerlab 環境からのパケットキャプチャは以下のように行います。

Windows WSL2 環境から SSH 経由でパケットキャプチャをする場合のコマンド例:

ssh $clab_host "sudo ip netns exec clab-nttcom-internship-pola-pce tcpdump -U -nni eth1 -w -" | /mnt/c/Program\ Files/Wireshark/wireshark.exe -k -i -

macOS/Linux 環境から SSH 経由でパケットキャプチャをする場合のコマンド例:

ssh $clab_host "sudo ip netns exec clab-nttcom-internship-pola-pce tcpdump -U -nni eth1 -w -" | wireshark -k -i -

Cisco IOS XRd との相互接続検証

Cisco IOS XRd ルーターである pcc-rt02 側でのパケットキャプチャ結果を以下に示します。

パケットキャプチャ結果を確認すると、Pola PCE の実装通りに PCInitiate メッセージが送られましたが、pcc-rt02 からは PCReport メッセージが返ってきていません。

pcc-rt02 で show コマンドによって SR Policy の状態を確認すると、以下のように新規発行された SR Policy は存在していないことがわかります。

RP/0/RP0/CPU0:pcc-rt02#show segment-routing traffic-eng policy
Fri Apr 14 13:43:15.021 UTC

SR-TE policy database
---------------------

Color: 100, End-point: 2a0b:2542:400:3:1::
  Name: srte_c_100_ep_2a0b:2542:400:3:1::
  Status:
    Admin: up  Operational: down for 02:52:28 (since Apr 14 10:50:46.274)
  Candidate-paths:
    Preference: 10 (configuration) (inactive)
      Name: vrf-100-policy-pcc-rt03
      Last error: SRv6 locator does not support USID
      Requested BSID: dynamic
      PCC info:
        Symbolic name: cfg_vrf-100-policy-pcc-rt03_discr_10
        PLSP-ID: 1
      Constraints:
        Protection Type: protected-preferred
        Maximum SID Depth: 19
      Explicit: segment-list vrf-100-segment-list-pcc-rt03 (inactive)
        Weight: 10, Metric Type: TE
          SID[0]: 2a0b:2542:400:3:1::
      SRv6 Information:
        Locator: WOLFLAB
        Binding SID requested: Dynamic
        Binding SID behavior: End.B6.Insert.Red
  Attributes:
    Forward Class: 0
    Steering labeled-services disabled: no
    Steering BGP disabled: no
    IPv6 caps enable: yes
    Invalidation drop enabled: no
    Max Install Standby Candidate Paths: 0

また、初期設定による既存の SR Policy である vrf-100-policy-pcc-rt03 に関しても、Last error: SRv6 locator does not support USID が原因で (inactive) が表示されます。

pcc-rt02 の SRv6 関連設定は以下のようになっています。

RP/0/RP0/CPU0:pcc-rt02#show running-config segment-routing
Fri Apr 14 13:44:27.988 UTC
segment-routing
 traffic-eng
  segment-lists
   srv6
    sid-format usid-f3216
   !
   segment-list vrf-100-segment-list-pcc-rt03
    srv6
     index 10 sid 2a0b:2542:400:3:1::
    !
   !
  !
  policy vrf-100-policy-pcc-rt03
   srv6
    locator WOLFLAB binding-sid dynamic behavior ub6-insert-reduced
   !
   color 100 end-point ipv6 2a0b:2542:400:3:1::
   candidate-paths
    preference 10
     explicit segment-list vrf-100-segment-list-pcc-rt03
      weight 10
     !
    !
   !
  !
  pcc
   source-address ipv6 fc00::2
   pce address ipv6 fc00::ffff
   !
   report-all
  !
 !
 srv6
  locators
   locator WOLFLAB
    prefix 2a0b:2542:400:2::/64
   !
  !
 !
!

pola sr-policy list コマンドによる既存 SR Policy の確認結果では、 SegmentList: None と表示され、SR Policy が学習されていません。 Cisco のドキュメント Segment Routing Configuration Guide for Cisco ASR 9000 Series Routers, IOS XR Release 7.8.x を参照したところ、インターンシップ参加時点(2023年2月)の最新版である Cisco IOS XR 7.8.1 では SRv6 policy が uSID 以外の SID 形式をサポートしていないようであり、これが原因と考えられます。 また同様に、Cisco のドキュメントからはインターンシップ参加時点で PCE-initiated SRv6 policies via PCEP 機能がサポートされていないことがわかります。 以上が原因で残念ながら今回のインターンシップでは Cisco IOS XRd との接続検証は成功しませんでした。

Juniper vMX との相互接続検証

Juniper vMX ルーターである pcc-rt01 側でのパケットキャプチャ結果を以下に示します。

パケットキャプチャ結果を確認すると、Pola PCE の実装通りに PCInitiate メッセージが送られ、pcc-rt01 から PCReport メッセージが返ってきていることがわかります。

pcc-rt01 で show コマンドによって SR Policy の状態を確認すると、以下のように新規発行された SR Policy である vrf-100-policy-pcc-rt02-by-pola が存在していることがわかります。

admin@pcc-rt01> show spring-traffic-engineering lsp
To              State     LSPname
2a0b:2542:400:2:1::-100<c6> Up vrf-100-policy-pcc-rt02-by-pola
2a0b:2542:400:3:1::-100<c6> Up vrf-100-policy-pcc-rt03


Total displayed LSPs: 2 (Up: 2, Down: 0)

最後に、疎通確認を行います。

% sudo docker exec -it clab-nttcom-internship-host01 /bin/bash

bash-5.1# ping -c 3 fd00:192:168:2::1
PING fd00:192:168:2::1(fd00:192:168:2::1) 56 data bytes
64 bytes from fd00:192:168:2::1: icmp_seq=1 ttl=62 time=4.00 ms
64 bytes from fd00:192:168:2::1: icmp_seq=2 ttl=62 time=3.61 ms
64 bytes from fd00:192:168:2::1: icmp_seq=3 ttl=62 time=3.77 ms

--- fd00:192:168:2::1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 3.610/3.795/4.003/0.161 ms

host01 から host02 への経路は、SR Policy(vrf-100-policy-pcc-rt02-by-pola) により pcc-rt01 -> pcc-rt03 -> pcc-rt02(緑色の線)経由になると期待されます。 pcc-rt03 の ge-0/0/0 でパケットキャプチャし、SR Policy の挙動を確認します。

% sudo ip netns exec clab-nttcom-internship-pcc-rt03 tcpdump -nni eth1 -v ip6
tcpdump: listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:22:59.940361 IP6 (flowlabel 0x47e1e, hlim 255, next-header Routing (43) payload length: 128) fd00:ffff::1 > 2a0b:2542:400:3:1::: RT6 (len=2, type=4, segleft=1, last-entry=0, flags=0x0, tag=0, [0]2a0b:2542:400:2:40::) IP6 (flowlabel 0xe793a, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:1::1 > fd00:192:168:2::1: [icmp6 sum ok] ICMP6, echo request, id 13, seq 1
00:23:00.939071 IP6 (flowlabel 0x47e1e, hlim 255, next-header Routing (43) payload length: 128) fd00:ffff::1 > 2a0b:2542:400:3:1::: RT6 (len=2, type=4, segleft=1, last-entry=0, flags=0x0, tag=0, [0]2a0b:2542:400:2:40::) IP6 (flowlabel 0xe793a, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:1::1 > fd00:192:168:2::1: [icmp6 sum ok] ICMP6, echo request, id 13, seq 2
00:23:01.941300 IP6 (flowlabel 0x47e1e, hlim 255, next-header Routing (43) payload length: 128) fd00:ffff::1 > 2a0b:2542:400:3:1::: RT6 (len=2, type=4, segleft=1, last-entry=0, flags=0x0, tag=0, [0]2a0b:2542:400:2:40::) IP6 (flowlabel 0xe793a, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:1::1 > fd00:192:168:2::1: [icmp6 sum ok] ICMP6, echo request, id 13, seq 3

3 packets captured
3 packets received by filter
0 packets dropped by kernel

結果の通り、pcc-rt03 でパケットキャプチャして確認すると、pcc-rt01 から pcc-rt03 へ ICMP Request パケットが想定した経路で送られていることがわかります。 また、host02 から host01 へ ICMP Reply パケットについて、前述の原因により pcc-rt02 側で有効な SR Policy が存在しないため、経路は IGP に従って pcc-rt02 -> pcc-rt01(青色の線)になります。 同様に pcc-rt02 の Gi0/0/0/0 でパケットキャプチャして確認します。

% sudo ip netns exec clab-nttcom-internship-pcc-rt02 tcpdump -nni Gi0-0-0-0 -v ip6
tcpdump: listening on Gi0-0-0-0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:45:20.822110 IP6 (flowlabel 0x5aab2, hlim 64, next-header IPv6 (41) payload length: 104) fd00:ffff::2 > 2a0b:2542:400:1:6::: IP6 (flowlabel 0x5aab2, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:2::1 > fd00:192:168:1::1: [icmp6 sum ok] ICMP6, echo reply, id 16, seq 1
00:45:21.820163 IP6 (flowlabel 0x5aab2, hlim 64, next-header IPv6 (41) payload length: 104) fd00:ffff::2 > 2a0b:2542:400:1:6::: IP6 (flowlabel 0x5aab2, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:2::1 > fd00:192:168:1::1: [icmp6 sum ok] ICMP6, echo reply, id 16, seq 2
00:45:22.822313 IP6 (flowlabel 0x5aab2, hlim 64, next-header IPv6 (41) payload length: 104) fd00:ffff::2 > 2a0b:2542:400:1:6::: IP6 (flowlabel 0x5aab2, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:2::1 > fd00:192:168:1::1: [icmp6 sum ok] ICMP6, echo reply, id 16, seq 3

3 packets captured
3 packets received by filter
0 packets dropped by kernel

キャプチャした結果、ICMP Reply パケットの経路は想定通り pcc-rt02 -> pcc-rt01(青色の線)になっていることがわかります。 これにより、今回のインターンシップで実装した Pola PCE の SRv6 機能が正しく動作していることが確認できました。

まとめと今後の課題

本体験記では、インターンシップで取り組んだ Pola PCE への SRv6 新規機能実装・検証内容をご紹介しました。 前半では、Pola PCE への SRv6-ERO Subobject の追加を例に、今回のインターンシップでの開発の流れについて説明しました。 後半では、インターンシップ中に実施した複数のベンダーの機器(Cisco、Juniper)とPola PCE との相互接続検証を紹介しました。 結果として、Pola PCE へ実装した SRv6 機能が正しく動作していることが確認できました。

今回のインターンシップでは、実装状況の関係から、残念ながら本来の目標であった複数のベンダーの機器との相互接続は実現できませんでした。 SRv6 の TE は今後の発展が期待される分野であり、また私自身の研究にもつながるトピックです。 今後もベンダーの動向に注視しつつ、研究や運用に役立つ機能の開発に積極的に取り組んでいきたいと思います。

また、Pola PCE では次のステップとして、Dynamic SRv6-TE や BGP SR Policy NLRI への対応を検討しています。 これらの技術開発についても引き続き携わりたいと期待しています。

インターンシップの感想

わずか 2 週間のインターンシップでしたが、Pola PCE の開発準備から相互接続検証までの全プロセスに取り組むことができ、大変素晴らしい経験となりました。 Go 言語と PCEP 初心者の私にとっては大きな挑戦でしたが、Pola PCE のコア開発者の三島さんと竹中さんの丁寧な指導のお陰で、数々の課題を乗り越え、インターンシップの最終日までに開発目標を達成できました。

また、チーム全体の定例ミーティングへの参加を通して、Pola PCE の開発者の先輩社員の方だけでなく配属チームの社員の方々と交流でき、チーム全体の雰囲気を感じると共に普段の業務内容をより深く理解できました。 今回インターンシップで獲得した貴重な経験を今後の研究活動に活かしていきたいと思います。 トレーナーの三島さん、竹中さん、上長の木村さん、そしてお世話になった NTT Com の皆さん、2 週間大変貴重な経験をさせていただき、本当にありがとうございました。

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

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

今回のインターンシップでは、魏さんには主に SR PCE である Pola PCE の SRv6 拡張を実施していただきました。プロトコルやパケット構造の理解からはじめ、RFC や Internet-Draft とコードを比較しつつ拡張する部分を検討し、そして実際に機能拡張・動作確認まで行い成功させてくれました。インターン前は PCEP も Go 言語にもあまり馴染みがなかったということでしたが、その中でもプロトコルや実装を即座に理解し、手早く機能拡張されている様子はとても印象的でした。 また、本ブログでは SRv6 機能拡張について触れていただきましたが、それ以外に IPv6 underlay の環境情報を取得するための BGP-LS の各ベンダ実装状況など、PCE を活用する上で必要となる機能を調査していただきました。

SR PCE は SR 技術において様々な Traffic Engineering を行う上で今後重要となる技術です。 今回実施いただいた Pola PCE の SRv6 拡張によって、SRv6 環境においてより柔軟な TE の管理や検証が行えるようになりました。我々の SRv6 環境へ導入し、運用や検証に活用していきます! また、調査いただいたベンダの機能実装状況なども、今後の検証方針の決定やベンダへの問い合わせに活用させていただきます。

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

© NTT Communications Corporation All Rights Reserved.