Unique IPv6 Prefix per Host について調べてみた


ITRC meet 58 での発表資料の一部抜粋版 です。 2025年11月時点での、IPv6 におけるホストへのプレフィックス配布に関して、調査した内容をまとめました。


2026年1月、Android での追加検証を実施しました。 Google は、端末に単一の IPv6 アドレスの利用を強制する DHCPv6 は、NAT66 の利用を助長するという考えから、Android に DHCPv6 クライアント機能(厳密には IA_NA による非一時アドレス要求)を実装していません。 しかし、上記の資料にもある通り、2025年9月、GoogleAndroid に IA_PD によるプレフィックス要求のサポートを追加しました。

android-developers.googleblog.com

これによって、ネットワークに DHCPv6-PD サーバがあるとき、Androidプレフィックスを要求するようになりました。 『ネットワークに DHCPv6-PD サーバがある』というのは、Router Advertisement の Prefix Information Option に新たなフラグ(RFC9762)を導入することで通知することができるようになっています。 詳細は上記の資料を参照ください。 この追加検証では、実際に Android がIA_PD によるプレフィックス要求を行う様子を確かめてみました。

radvd は、ルータ広告デーモンのデファクトスタンダードな実装ですが、2026年1月時点ではRFC9762をサポートしていません。 AdvDHCPv6PDPreferredFlagを実装しましたので、今回はそのパッチを当てた radvd を使用します。

検証の結果、確かに Android が DHCPv6 IA_PD によるプレフィックス要求を送信している様子が確認できました。

Android による DHCPv6 IA_PD の要求

RFC9762によるフラグを設定した RA パケットの内容

Android が送信した DHCPv6 パケットの内容

「IETF発 ! 技術ハッカソンをやってみよう」参加記

はじめに

IETF発 ! 技術ハッカソンをやってみよう」に参加してきましたので、私が取り組んだ内容について紹介します。 テーマは、講師の方が実際にIETFハッカソンで取り組まれている内容からタスクを細分化して、いくつか提示してくださいました。 もちろん、持ち込みテーマも歓迎という形でした。

私は、提示いただいたテーマの中から、「Traffic Steering using BGP FlowSpec with SR Policy(draft-ietf-idr-ts-flowspec-srv6-policy-05)をGoBGPに実装する」というテーマに取り組むことにしました。

事前準備

私自身、これまでBGP FlowSpecやGoBGPに触ったことがなかったので、これらに慣れることから始めました。 FlowSpecについては、日本語の解説記事がたくさんあったので理解しやすかったです。 特に、ゆるふわねっとわーく/Flowspecや、BBSakura Networks Blog/BGP Flow Specification を RFC8955 とパケットキャプチャから学習してみたが参考になりました。

FlowSpecについてイメージができた後は、実際に手を動かしてみます。 ということで、tinetを用いてGoBGPのGetting Startedを構築して、FlowSpecを実行してみました。 GoBGPでFlowSpecを使うための基本的な操作方法はドキュメント化されており、非常に助かりました。

▶︎spec.yaml

postinit:
  - cmds:
    - cmd: docker cp R1.conf R1:/etc/gobgpd.conf
    - cmd: docker cp R2.conf R2:/etc/gobgpd.conf

nodes:
  - name: R1
    image: yykzm/gobgp:dev # GoBGPがinstallされている適当なimageを使用してください
    interfaces:
      - { name: net0, type: direct, args: R2#net0 }
  - name: R2
    image: yykzm/gobgp:dev
    interfaces:
      - { name: net0, type: direct, args: R1#net0 }

node_configs:
  - name: R1
    cmds:
      - cmd: ip addr add 10.0.255.1/24 dev net0
      - cmd: gobgpd -f /etc/gobgpd.conf &
  - name: R2
    cmds:
      - cmd: ip addr add 10.0.255.2/24 dev net0
      - cmd: gobgpd -f /etc/gobgpd.conf &

▶︎R1.conf

[global.config]
    as = 65002
    router-id = "1.1.1.1"

[[neighbors]]
    [neighbors.config]
        neighbor-address = "10.0.255.2"
        peer-as = 65002

    [[neighbors.afi-safis]]
        [neighbors.afi-safis.config]
            afi-safi-name = "ipv4-flowspec

▶︎R2.conf

[global.config]
    as = 65002
    router-id = "2.2.2.2"

[[neighbors]]
    [neighbors.config]
        neighbor-address = "10.0.255.1"
        peer-as = 65002

    [[neighbors.afi-safis]]
        [neighbors.afi-safis.config]
            afi-safi-name = "ipv4-flowspec"

I-Dの理解

続いて、今回取り組むdraft-ietf-idr-ts-flowspec-srv6-policy-05を読みます。 すごく簡単に私の理解をまとめると、このI-Dでは以下のことが提案されています。

  • BGP FlowSpecを用いて配布されるフロー情報を特定のSR Policyへ誘導する方法を提案
    • フロー情報の条件と、SR Policyを識別するための情報を一緒に広告してあげる(SR Policy自体を配布するわけではない)

SR Policyは、どのノードへ送るかを指定する Endpoint、経路を指定する Segment List、属性である Color の 3 つの要素から構成されます(参考)。 このうち、tupple(Endpoint, Color)が一致するSR Policyへ、条件に合うフローを誘導します。 EndpointとColorは次のようにして広告することが提案されています。

SR Policy自体は別の方法を用いて広告されており、すでにHeadendでインスタンス化されていることが想定されるため、Segment Listは広告する必要はありません。 また、Redirect to IPv6を再利用するということから、本来のリダイレクトアクションとはどう区別するのかと疑問に思いましたが、きちんとドラフトに記載がありました。 単純に、Color Extended Communityの有無で判断するようです。

When the FlowSpec IPv6 extended community exists but the color extended community is missing, the FlowSpec steering falls back to the redirect-IP action.

FlowSpec IPv6拡張コミュニティは存在するが、カラー拡張コミュニティが見つからない場合、FlowSpecステアリングはredirect-IPアクションにフォールバックする。

本手法をSRv6へ適用する例が2つ紹介されています。

  • Option 1: フィルタルール(条件)、リダイレクト(Endpoint)、 カラー、SRv6 SID(Service_id_x)をHeadendに通知する
    • TailendでVPN等が実行されている場合に、そのサービスを通知できる
  • Option 1: フィルタルール(条件)、リダイレクト(Endpoint)、 カラーをHeadendに通知する
    • SR PolicyのSegment Listの最後のSIDがUSDフレーバーである場合、明示的なサービスの通知は不要

実装

続いて、実装に取り掛かります。 まずは、GoBGPをライブラリとして利用し、理想のBGPパケットを構築してみます。 今回作りたいのは、FlowSpecNLRI + Color Extended Community + Flow-spec Redirect to IPv6 Extended Community (+ BGP Prefix-SID)が含まれるBGPパケットです。

GoBGPには、これらそれぞれのAttributeを構築するための実装はすでに存在しています。 そのため、GoBGP自体に実装を追加することなくパケットを構築できました。

▶︎main.go(試行錯誤した痕跡がそのままですが、供養も兼ねて掲載しておきます。)

package main

import (
        "context"
        "time"

        "github.com/sirupsen/logrus"
        apb "google.golang.org/protobuf/types/known/anypb"

        api "github.com/osrg/gobgp/v3/api"
        "github.com/osrg/gobgp/v3/pkg/log"
        "github.com/osrg/gobgp/v3/pkg/server"
)

func main() {
        log := logrus.New()

        s := server.NewBgpServer(server.LoggerOption(&myLogger{logger: log}))
        go s.Serve()

        // global configuration
        if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
                Global: &api.Global{
                        Asn:        65002,
                        RouterId:   "1.1.1.1",
                        ListenPort: -1,
                },
        }); err != nil {
                log.Fatal(err)
        }

        // monitor the change of the peer state
        if err := s.WatchEvent(context.Background(), &api.WatchEventRequest{Peer: &api.WatchEventRequest_Peer{}}, func(r *api.WatchEventResponse) {
                if p := r.GetPeer(); p != nil && p.Type == api.WatchEventResponse_PeerEvent_STATE {
                        log.Info(p)
                }
        }); err != nil {
                log.Fatal(err)
        }

        // neighbor configuration
        n := &api.Peer{
                Conf: &api.PeerConf{
                        NeighborAddress: "10.0.255.2",
                        PeerAsn:         65002,
                },
                AfiSafis: []*api.AfiSafi{
                        {
                                Config: &api.AfiSafiConfig{
                                        // enable IPv4 flow-spec unicast or VPNv4 flow-spec unicast
                                        Family: &api.Family{
                                                Afi:  api.Family_AFI_IP,
                                                Safi: api.Family_SAFI_FLOW_SPEC_UNICAST,
                                        },
                                        Enabled: true,
                                },
                        },
                },
        }

        if err := s.AddPeer(context.Background(), &api.AddPeerRequest{
                Peer: n,
        }); err != nil {
                log.Fatal(err)
        }

        /*******************************/

        // IPv4 flow-spec rule
        rule := &api.FlowSpecIPPrefix{
                Type:      1, // 1: destination
                PrefixLen: 24,
                Prefix:    "192.168.1.0",
        }
        rules := make([]*apb.Any, 0, 1)
        anyRule, _ := apb.New(rule)
        rules = append(rules, anyRule)
        // IPv4 flow-spec NLRI
        nlri, _ := apb.New(&api.FlowSpecNLRI{
                Rules: rules,
        })

        // origin attribute
        a1, _ := apb.New(&api.OriginAttribute{
                Origin: 0,
        })

        // next-hop attribute
        a3, _ := apb.New(&api.NextHopAttribute{
                NextHop: "10.0.255.10",
        })

        // IPv6 flowspec redirect extended community
        // $ gobgp global rib -a ipv4-flowspec add match destination 192.168.1.0/24 then redirect 2001:db8::1:100
        // wireshark can't decode this community
        // Sub-Type: 0x800b
        community := &api.RedirectIPv6AddressSpecificExtended{
                Address:    "2001:db8::1",
                LocalAdmin: 100,
        }
        // community := &api.RedirectIPv4AddressSpecificExtended{
        //      Address:    "10.255.255.1",
        //      LocalAdmin: 100,
        // }
        communities := make([]*apb.Any, 0, 1)
        anyComm, _ := apb.New(community)
        communities = append(communities, anyComm)

        color := &api.ColorExtended{
                Color: 99,
        }
        colors := make([]*apb.Any, 0, 1)
        anyColor, _ := apb.New(color)
        colors = append(colors, anyColor)
        // communities = append(communities, anyColor)

        // IPv6 address specific extended communitiy attribute
        a4, _ := apb.New(&api.IP6ExtendedCommunitiesAttribute{
                Communities: communities,
        })
        a5, _ := apb.New(&api.ExtendedCommunitiesAttribute{
                Communities: colors,
                //Communities: communities,
        })

        // BGP Prefix-SID attribute
        a6, _ := apb.New(&api.PrefixSID{})

        attrs := []*apb.Any{a1, a3, a4, a5, a6}

        family := &api.Family{
                Afi:  api.Family_AFI_IP,
                Safi: api.Family_SAFI_FLOW_SPEC_UNICAST,
        }

        paths := []*api.AddPathRequest{
                {
                        Path: &api.Path{
                                Family: family,
                                Nlri:   nlri,
                                Pattrs: attrs,
                        },
                },
        }
        for _, pathReq := range paths {
                if _, err := s.AddPath(context.Background(), pathReq); err != nil {
                        log.Fatal(err)
                }
        }

        s.ListPath(context.Background(), &api.ListPathRequest{Family: family}, func(p *api.Destination) {
                log.Info(p)
        })

        // do something useful here instead of exiting
        time.Sleep(time.Minute * 3)
}

// implement github.com/osrg/gobgp/v3/pkg/log/Logger interface
type myLogger struct {
        logger *logrus.Logger
}

func (l *myLogger) Panic(msg string, fields log.Fields) {
        l.logger.WithFields(logrus.Fields(fields)).Panic(msg)
}

func (l *myLogger) Fatal(msg string, fields log.Fields) {
        l.logger.WithFields(logrus.Fields(fields)).Fatal(msg)
}

func (l *myLogger) Error(msg string, fields log.Fields) {
        l.logger.WithFields(logrus.Fields(fields)).Error(msg)
}

func (l *myLogger) Warn(msg string, fields log.Fields) {
        l.logger.WithFields(logrus.Fields(fields)).Warn(msg)
}

func (l *myLogger) Info(msg string, fields log.Fields) {
        l.logger.WithFields(logrus.Fields(fields)).Info(msg)
}

func (l *myLogger) Debug(msg string, fields log.Fields) {
        l.logger.WithFields(logrus.Fields(fields)).Debug(msg)
}

func (l *myLogger) SetLevel(level log.LogLevel) {
        l.logger.SetLevel(logrus.Level(level))
}

func (l *myLogger) GetLevel() log.LogLevel {
        return log.LogLevel(l.logger.GetLevel())
}

理想のBGPパケットを構築できることが分かったので、cmd/gobgpに新たなサブコマンドを追加して、スタンドアローンで実行できるようにしてみます。 以下のようなコマンドを追加します。(各値に意味はないです。)

// for Option 1 
$ gobgp global rib -a ipv4-flowspec add match destination 192.168.1.0/24 then redirect fd00:1::1:0 color 100 prefix 2001:db8:2:2::/64 locator-node-length 24 function-length 16 behavior END_DT6 
// for Option 2
$ gobgp global rib -a ipv4-flowspec add match destination 192.168.1.0/24 then redirect fd00:1::1:0 color 100

gobgp global ... redirect fd00:1::1:0までは、既存のリダイレクトアクションとして実装が存在しています。 これをそのまま活用しますが、今回はEndpointのことを表しています。 fd00:1::1:0の末尾:0は、アクションをコピーしたトラフィックにのみ適用するかどうかを表すFlagになっています(0または1)。 これは、draft-ietf-idr-flowspec-redirect-ip-03で定義されています。

既存のリダイレクトアクションでも、with IPv6 address specific RTとしてredirect 2001:db8::1:100というフォーマットが存在しており、この形のパースが実装されています。 ということで、これについても既存の実装を再利用できそうです。

後は、cmd/gobgp/global.goを読み漁ってなんとなくで実装してみました。 BBSakura Networks Blog/GoBGPにBGP Extensions for the Mobile User Plane (MUP) SAFIを実装した話はロードマップとしてとても参考になります。(今回はここまで大規模な実装は不要でしたが..)

実行結果

パケットキャプチャ

Wireshark*1では、Flow-spec Redirect to IPv6 Extended Community(Sub-Type: 0x800b)のパースはできないようですが、きちんとredirect fd00:1::1:0の値が格納されていることが確認できます。

さいごに

ここまで読んでいただき、ありがとうございます。 「IETF発 ! 技術ハッカソンをやってみよう」に参加してきたので、取り組んだ内容を紹介してみました。 1週間という短い期間での取り組みでしたが、I-Dの理解から実装までできたので非常に良かったです。 とはいえ、今回はgobgpdに追加の実装は行っていないので作業量は比較的少なく済んだ気がします。 GoBGPがサポートしている機能の多さに驚きました。 特に、SR関連の新しい機能をサポートしているOSSは非常に貴重だと感じました。

期間中、相談に乗っていただいた講師の三島さん、貴重な機会を提供していただいたたくあんさんをはじめとする運営のみなさん、ありがとうございました!!!

後日談

その後、GoBGP に PR を出し、無事にマージされています。

github.com

*1:Version 4.0.5 (v4.0.5-0-ge556162d8da3).

LISP(Locator/ID Separation Protocol)を動かしたい!

本記事の内容について、間違いや解釈違いを見つけた場合はXアカウント@yy_kzmでお知らせいただけると助かります。

LISPとは

LISP(Locator/ID Separation Protocol)とは、名前の通りルーティングのためのネットワーク識別子(Locator)とノードを一意に識別するためのエンドポイント識別子(ID)を分離するプロトコルです。現在のインターネットアーキテクチャでは、これら2つ情報は1つのIPアドレスとして表現されます。LISPでは2つのIPアドレスを利用して、LocatorとIDを別々の論理空間として定義し、それらのマッピングカプセル化技術を使うことで通信を実現します。LISPは、ホストのプロトコルスタックに変更を加える必要はなく、既存のIPネットワークに段階的に導入可能です。

本記事は、LISPOSSで動かしてみたいというモチベーションで執筆しているため、基本的な動作説明は省略し、動作確認のみを説明します。LISPの基本的な動作については、 ゆるふわねっとわーく - LISP 概要/基本動作Sig9 Memo v4.0 - はじめての LISP (Location/Identifier Separation Protocol)が非常にわかりやすく、参考になります。日本語でLISPについてここまで丁寧に説明してくれている記事は少なく、とても貴重です。

用語説明

まずはじめに、以降で利用するLISPに関連する用語を簡単に説明します。

【参考: RFC 9300 - The Locator/ID Separation Protocol (LISP)

【参考: RFC 9301 - Locator/ID Separation Protocol (LISP) Control Plane

  • Map-Server (MS): ETRからマッピング情報を受け取り、データベースとして保存するサーバです。
  • Map-Resolver (MR): ITRからEncapsulated Map-Requestを受け取り、宛先EIDがMap-Serverの管理対象に該当するかを確認するサーバです。該当しない場合は、Negative Map-Replyと呼ばれる空のRLOCをを含むメッセージをITRへ返します。該当する場合は、Map-Serverのデータベース参照し、適切なRLOCを発見します。Map-ServerとMap-Resolverは単一のサーバで実装することができ、多くの場合はそのように配置されます。
  • Map-Registerメッセージ: ETRがMap-Serverに自身のRLOCと接続されているLISPサイトのEID-Prefixとのマッピング情報を登録するためのメッセージです。
  • (Encapsulated) Map-Requestメッセージ: EIDにマッピングされているRLOCを解決(ルックアップ)するための要求メッセージです。(ITR→MS/MR→ETR)
  • Map-Replyメッセージ: 解決されたRLOCを知らせるための応答メッセージです。(ETR→ITR)
  • Map-Notifyメッセージ: Map-ServerからETRへマッピング情報が正常に登録されたことを通知するメッセージです。ETRは、Map-Registerメッセージの「Want-Map-Notify」フラグ(Mビット)を設定することにより、Map-Notifyを返すことを要求します。

Open Overlay Router

Open Overlay Router(OOR: double-O R)は、C言語で実装されたプログラマブルなオーバーレイネットワークを構築するためのオープンソースルータです。OORはRFC6830(Experimental)で標準化されたLISPを実装しています。ただし、2019年/2020年辺りから更新がないので現在アクティブなプロジェクトなのかどうかはよくわかりません。OpenWrt、AndroidiOS、VPPなど様々なプラットフォームやデータプレーンに対応しているようなので開発当時の本気度が伺えます。また、IPv6もサポートしています。非常に貴重なLISPOSS実装ということで、今回はOORを使ってLISPの基本的な動作を確認したいと思います。

動作確認

まずは、OORのソースコードをcloneします。

$ git clone https://github.com/OpenOverlayRouter/oor.git

OORはDockerをサポートしており、ハンズオンラボが用意されています。docker-compose-2-xtr-1ms.ymlを立ち上げると以下のようなネットワークが構築されます。

$ cd oor/Docker/compose-examples/2xtrs-1msmr
$ ls
docker-compose-2-xtr-1ms.yml  oor.msmr.conf  oor.xtr1.conf  oor.xtr2.conf  scenario.sh
$ sudo ./scenario.sh UP

▶︎ oor.msmr.conf

debug                  = 3
map-request-retries    = 2
log-file               = /var/log/oor.log
ipv6-scope             = SITE
operating-mode         = MS
control-iface          = eth0

# xTR1
lisp-site {
    eid-prefix            = 192.168.1.0/24
    key-type              = 1
    key                   = password
    iid                   = 0
    accept-more-specifics = true
}

lisp-site {
    eid-prefix            = fd00:1::/64
    key-type              = 1
    key                   = password
    iid                   = 0
    accept-more-specifics = true
}

# xTR2
lisp-site {
    eid-prefix            = 192.168.2.0/24
    key-type              = 1
    key                   = password
    iid                   = 0
    accept-more-specifics = true
}

lisp-site {
    eid-prefix            = fd00:2::/64
    key-type              = 1
    key                   = password
    iid                   = 0
    accept-more-specifics = true
}

▶︎ oor.xtr1.conf

debug                  = 1
map-request-retries    = 2
log-file               = /var/log/oor.log
ipv6-scope             = SITE
operating-mode         = xTR
encapsulation          = LISP
nat_traversal_support  = off
rloc-probing {
    rloc-probe-interval = 0
}
map-resolver = {
    10.0.0.2
}
map-server {
    address        = 10.0.0.2
    key-type       = 1
    key            = password
    proxy-reply    = off
}
database-mapping {
    eid-prefix     = 192.168.1.0/24
    iid            = 0
    ttl            = 10
    rloc-address {
        address    = 10.0.0.3
        priority   = 1
        weight     = 100
    }
}
database-mapping {
    eid-prefix     = fd00:1::/64
    iid            = 0
    ttl            = 10
    rloc-address {
        address    = 10.0.0.3
        priority   = 1
        weight     = 100
    }
}

▶︎ oor.xtr2.conf

debug                  = 1
map-request-retries    = 2
log-file               = /var/log/oor.log
ipv6-scope             = SITE
operating-mode         = xTR
encapsulation          = LISP
nat_traversal_support  = off
rloc-probing {
    rloc-probe-interval = 0
}
map-resolver = {
    10.0.0.2
}
map-server {
    address        = 10.0.0.2
    key-type       = 1
    key            = password
    proxy-reply    = off
}
database-mapping {
    eid-prefix     = 192.168.2.0/24
    iid            = 0
    ttl            = 10
    rloc-address {
        address    = 10.0.0.4
        priority   = 1
        weight     = 10
    }
}
database-mapping {
    eid-prefix     = fd00:2::/64
    iid            = 0
    ttl            = 10
    rloc-address {
        address    = 10.0.0.4
        priority   = 1
        weight     = 10
    }
}

立ち上げ完了後、client1からclient2のEIDにpingを打ってみます。すると、最初の数回は失敗しますが、その後は疎通が確認できます。

$ docker exec -it client1 ping 192.168.2.3 
PING 192.168.2.3 (192.168.2.3): 56 data bytes
64 bytes from 192.168.2.3: seq=2 ttl=61 time=0.889 ms
64 bytes from 192.168.2.3: seq=3 ttl=61 time=0.815 ms
64 bytes from 192.168.2.3: seq=4 ttl=61 time=0.667 ms
64 bytes from 192.168.2.3: seq=5 ttl=61 time=0.804 ms
^C
--- 192.168.2.3 ping statistics ---
6 packets transmitted, 4 packets received, 33% packet loss
round-trip min/avg/max = 0.667/0.793/0.889 ms

このとき、LISPがどのような通信をしているのかを確認します。

xtr2からMap-Server(msmr)へMap-Registerを送信し、マッピング情報を登録します(xtr1も同様)。このとき、「Want-Map-Notify」フラグ(Mビット)を設定することにより、Map-Notifyを返すことを要求します。

xtr2からmsmrへのMap-Register

マッピング情報の登録処理が完了すると、Map-Server(msmr)からMap-Notifyメッセージが返されます。

msmrからxtr2へのMap-Nortify

このときのMap-Server(msmr)では次のようなログが確認できます。

$ docker exec -it oor-msmr oor
...
[2024/6/18 16:42:48] DEBUG: Received Map-Register -> flags:pirM record-count: 1 nonce 6bf9f9775cdff663, IP: 10.0.0.4 -> 10.0.0.2, UDP: 4342 -> 4342
[2024/6/18 16:42:48] DEBUG:   Mapping-record -> ttl: 10 loc-count: 1 action: no-action auth: 1 map-version: 0 eid: 192.168.2.0/24
[2024/6/18 16:42:48] DEBUG:     Locator-record -> flags: L=1,p=0,R=1, p/w: 1/10 255/0, addr: 10.0.0.4
...
[2024/6/18 16:42:48] DEBUG-2: The map cache entry of EID 192.168.2.0/24 will expire in 180 seconds.
[2024/6/18 16:42:48] DEBUG-3: **************** MS registered sites ******************
[2024/6/18 16:42:48] DEBUG-3: EID: 192.168.2.0/24, ttl: 10, loc-count: 1, action: no-action, auth: 1
  RLOC: 10.0.0.4, Up, L:0, R:1, 1/10, 255/0
[2024/6/18 16:42:48] DEBUG-3: *******************************************************
[2024/6/18 16:42:48] DEBUG: Map-Notify-> flags:ir, record-count: 1, nonce 6bf9f9775cdff663, IP: 10.0.0.2 -> 10.0.0.4, UDP: 4342 -> 4342
[2024/6/18 16:42:48] DEBUG: Sent control message IP: 10.0.0.2 -> 10.0.0.4 UDP: 4342 -> 4342
...

client1からclient2のEID(192.168.2.3)へpingを実行します。

このパケットを受け取ったxtr1は、宛先EIDに対応するRLOCをルックアップします。自身が持つEID-to-RLOCキャッシュに該当するエントリーがなければMap-Resolver(msmr)へMap-Requestを送信します。

xtr1からmsmrへのMap-Request

Map-Requestを受信したMap-Resolver(msmr)は、宛先EIDを確認し、Map-Serverへ転送します。今回はこれらは共存しているのでこのフェーズは図示していません。Map-Serverはデータベースを参照し、EIDに紐づけられたRLOCへMap-Requestを転送します。

msmrからxtr2へのMap-Request

Map-Requestを受信したxtr2は、xtr1へEID-PrefixとRLOCを含むMap-Replyを返します。

なぜ、Map-Serverが直接ITR(xtr1)にMap-Replyを返さないのだろうと思ったりしましたが、おそらくLISP-DDTと呼ばれるようなEID-Prefixを集約してツリー構造で管理するケースが想定されており、この辺りの構成が関係していそうです。

xtr2からxtr1へのMap-Reply

client1からclient2のEID(192.168.2.3)へping疎通が確認できます。

client1からclient2へのICMP Echo Request

おわりに

本記事では、LISPOSS実装であるOORを使ってLISPの基本動作を確認しました。LISPは関連RFCが多く、複雑なものだと思い込んでいましたが、基本動作はとてもシンプルなものだとわかりました。Locator/ID分離の考え方はLISP以外のプロトコルでも採用されていることがあり、この概念に対する理解が進んだのでよかったです。

デバッグ

動作確認にあたって、詰まった点があるので紹介します。(完全には解決していない..) xtr1で、msmrやxtr2のRLOCと通信しようとした際、ICMP Redirectを頻繁に確認しました。xtr1のルーティングテーブルとルーティングポリシーを見てみると以下のようになっていました。

root@6bd6d3d52bf9:/oor# ip r
default via 10.0.0.1 dev eth0
10.0.0.0/24 dev eth0  proto kernel  scope link  src 10.0.0.3
192.168.1.0/24 dev eth1  proto kernel  scope link  src 192.168.1.2
root@6bd6d3d52bf9:/oor# ip rule
0:      from all lookup local
99:     from all to 192.168.1.0/24 lookup main
100:    from 192.168.1.0/24 lookup 100
408:    from 10.0.0.3 lookup 152
32766:  from all lookup main
32767:  from all lookup default

408: from 10.0.0.3 lookup 152に着目して、テーブル152のルーティングテーブルを確認すると、以下のようになっており、msmrやxtr2のRLOCの通信が一旦10.0.0.1(0rlocのBridge)へ転送されてしまい、Redirectが発生していました。そもそも408: from 10.0.0.3 lookup 152のエントリーが何のためにあるのかわかりませんでした。(RLOCを複数保持する場合など?)

root@6bd6d3d52bf9:/oor# ip r s t 152
default via 10.0.0.1 dev eth0  proto static  metric 100

IPv6-mostlyネットワークを構築してみた

はじめに、本記事は私なりの解釈を含んでおり、情報の正確性を保証するものではありません。 もし、間違いや解釈違いを見つけた場合はXアカウント@yy_kzmまでご連絡いただけると大変助かります。

IPv6-onlyネットワークにおける問題点

NAT64/DNS64によるIPv6-onlyネットワークは比較的うまく動作しますが、次のような問題点があります。

NAT64/DNS6環境では、Pref64とIPv4アドレスの合成はDNS64がしてくれるため、ping 203.0.113.1のように"文字通りの"IPv4アドレスを使った通信ができません。 また、レガシーなソフトウェアはIPv4-onlyソケットを使うこともあるためそのようなソフトウェアはNAT64/DNS6環境では動作しません。

  • DNS64はAAAAレコードを偽造しているとも取れる[3]

DNS64はAレコードしか持たない宛先に対してAAAAレコードを偽装するため、DNSSECによる検証ができなくなるらしいです。 詳細は[1]などをご覧ください。

  • Dual Stackネットワークからの移行ハードルが高い[2]

ネットワークの運用者は、デバイスやアプリケーションを制御でもできない限りIPv4を完全に削除してIPv6-onlyへ移行することは難しいといえます。 そのため、段階的にIPv6-onlyへ移行するための方法が必要となります。

IPv6-mostlyネットワークとは

IPv6-mostlyネットワークとは、IPv6-onlyで問題なく動作するホスト*1IPv4を無効にし、そうでないホストには今まで通りIPv4アドレスを配布するようなネットワークのことです。 つまり、IPv4アドレスをオンデマンドで提供するということです。 これにより、単なるDual Stackネットワークよりも使用するIPv4アドレスプールを節約できます。

ここで言う「IPv6-onlyで問題なく動作するホスト」に関して厳密な定義はありませんが、CLATが実装されているホストのことと思って問題なさそうです。 macOS(13 以降)、iOSAndroid最新バージョンなどがこれに該当するそうです[3]。 これらのホストはホスト内でNAT46(CLAT)して、ゲートウェイでNAT64(PLAT)するような464XLAT構成を取ることになります。

  • IPv4-onlyホスト: DHCPv4でIPv4アドレスを取得
  • Dual Stackホスト(IPv6-onlyは未サポート): SLAAC/DHCPv6でIPv6アドレスを取得、DHCPv4でIPv4アドレスを取得
  • IPv6-onlyサポートホスト: SLAAC/DHCPv6でIPv6アドレスを取得、DHCPv4サーバに対するDiscoverのRequet ListにOption 108 (IPv6-Only Preferred)[6]を含めることでIPv4アドレスの要求を破棄

IPv6-onlyサポートホストがIPv4-onlyな宛先にアクセスする際、CLATを実行することになりますが、そのためにはPref64をホストが把握する必要があります。 そこで、Router Advertisementを用いてPref64を広告する仕組みがあります[7]。 これにより、IPv6-mostlyネットワークではすべてのIPv6-onlyサポートホストがCLATを実装していればDNS64は必要ありません。 IPv6-mostlyネットワークはIPv6-onlyネットワークへ段階的に移行するためのベストプラクティスと言えそうです。

IPv6-mostlyネットワークの構築

続いて、実際にIPv6-mostlyネットワークを構築してみます。 使用する機器とソフトウェアは次の通りです。 各種ソフトウェアのインストール手順は省略します。

  • 使用機器: Raspberry pi 4 Model B
  • OS: Ubuntu 22.04.4 LTS
  • NAT64: Jool 4.1.7.0
  • RA: radvd 2.20_rc1
    • リリース版にはRFC8781(Pref64 Option)が入っていないのでソースからビルドする必要があります。
  • DHCPv4: kea-dhcp4 2.0.2

ネットワーク構成は以下の通りです。今回構築した環境はアップリンクIPv4-onlyなのでIPv6インターネットへの到達性はありません。(自宅にIPv6環境がないのでご了承ください...)

今回はiptablesによるNAT44やunboundを用いたDNSを有効にしています*2が、単なるIPv4-onlyのための設定なので省略します。

ということで、まずはkea-dhcp4の設定を行います。 "name": "v6-only-preferred"でRFC8925(IPv6-Only Preferred)を有効にします。 これにより、このオプションをRequest Listに含むDiscoverを受け取った場合はV6ONLY_WAITをセットしてOfferを返します。 このとき、IPv4アドレスプールからアドレスは払い出しません。 クライアントはこのV6ONLY_WAIT秒間IPv4スタックを停止します。

ubuntu@raspi:~$ cat /etc/kea/kea-dhcp4.conf
{
# DHCPv4 configuration starts on the next line
"Dhcp4": {

    "valid-lifetime": 4000,
    "renew-timer": 1000,
    "rebind-timer": 2000,

    "interfaces-config": {
        "interfaces": [ "wlan0" ]
    },

    "lease-database": {
        "type": "memfile",
        "persist": true,
        "name": "/var/lib/kea/dhcp4.leases"
    },

    "subnet4": [
        {
            "subnet": "172.16.100.0/24",
            "pools": [
                {
                    "pool": "172.16.100.1 - 172.16.100.200"
                }
            ],
            "option-data": [
            {
                "name": "routers",
                "data": "172.16.100.1"
            },
            {
                // This will make the v6-only capable devices to disable their
                // v4 stack for half an hour and then try again
                "name": "v6-only-preferred",
                "data": "1800"
            },
            {
                "name": "domain-name-servers",
                "data": "172.16.100.1"
            }
            ]
        }
    ]
    # DHCPv4 configuration ends with the next line
}

}
ubuntu@raspi:~$ sudo keactrl start

続いてJoolによるNAT64の設定を行います。 Pref64としてfd64:ff9b::/96を指定します。

$ sudo jool instance add "example" --netfilter --pool6 fd64:ff9b::/96

続いてradvdの設定を行います。 Pref64としてfd64:ff9b::/96を広告します。 上記でも記述しましたが、nat64prefixはリリース版では使用できないので注意が必要です。

ubuntu@raspi:~$ cat /etc/radvd.conf
interface wlan0 {
  AdvSendAdvert on;
  MinRtrAdvInterval 3;
  MaxRtrAdvInterval 10;
  AdvManagedFlag off;
  AdvOtherConfigFlag off;

  prefix fc00:cafe:dead:beef::/64 {
    AdvOnLink on;
    AdvAutonomous on;
    AdvRouterAddr off;
  };
  RDNSS fc00:cafe:dead:beef::1 {
    AdvRDNSSLifetime 30;
  };
  nat64prefix fd64:ff9b::/96 {
    AdvValidLifetime 1800;
  };
};
$ sudo radvd -C /etc/radvd.conf

動作確認

以上で構築は完了です。さっそくクライアント端末を繋いでみましょう。 IPv6-onlyサポートホストとしてMacbook AirmacOS 13.5.1)を繋いでみます。接続後のインターフェース情報を確認してみます。 clat46と書かれたIPv6アドレスが確認できます。また、192.0.0.2/32というアドレスも確認できます。 このIPv4アドレスはIPv4リテラルと通信するためのダミーアドレスだと思います。

en0: flags=88e3<UP,BROADCAST,SMART,RUNNING,NOARP,SIMPLEX,MULTICAST> mtu 1500
        options=6463<RXCSUM,TXCSUM,TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
        ether c4:35:d9:80:77:d3
        inet6 fe80::1cc1:3b33:5a01:1050%en0 prefixlen 64 secured scopeid 0xd
        inet6 fc00:cafe:dead:beef:cd4:228e:d23b:77b1 prefixlen 64 autoconf secured
        inet 192.0.0.2 netmask 0xffffffff broadcast 192.0.0.2
        inet6 fc00:cafe:dead:beef:402:19d6:9299:559c prefixlen 64 clat46
        nat64 prefix fd64:ff9b:: prefixlen 96
        nd6 options=201<PERFORMNUD,DAD>
        media: autoselect
        status: active

実際にIPv4リテラルを使って通信してみます。pingが通ることが確認できました(感動)。 IPv4リテラルと通信する際にはclat46と書かれたIPv6アドレスがネットワーク上を流れるようになっています。

$ ping -c3 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=54 time=18.284 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=54 time=19.731 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=54 time=17.255 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 17.255/18.423/19.731/1.016 ms

IPv4リテラルと通信した際のキャプチャ

DHCP DiscoverとOfferのやりとりを確認してみると、MacbookからのDiscoverのRequest ListにOption 108(IPv6-Only Preferred)が含まれていることがわかります。 また、それに対するkea-dhcp4からのOfferにも同様のオプションが確認できます。ValueV6ONLY_WAITと思われます。 Offerの宛先はIPv4アドレスプールのものになっていますが、このOfferに対してRequestを返すこともしてないですし、リースを確認しても払い出しは行われていないようだったので正常な動作なのだと思います(たぶん)。

DHCP Discover(左)とOffer(右)

Router Advertisementも確認してみましょう。Pref64 Optionでfd64:ff9b::/96が広告されているのが確認できます。

Router Advertisement

問題点として、192.0.0.2/32がネットワークに流出しまっている点が挙げられます。 これは[3]でも言及されていました。

192.0.0.2がマルチキャストの送信元アドレスとして使われてしまっている

さいごに

IPv6-mostlyネットワークについて調べて実際に構築してみたので記事にしました。 ここまで読んでいただきありがとうございます。 今回は、IPv6-onlyサポートホストでのみ動作確認を行いました。 WindowsLinuxも接続してみてDual Stackでも問題なく動作するのかも確認していきたいと思います。

参考

  1. Let’s talk about IPv6 DNS64 & DNSSEC
  2. IPv6-Mostly Networks: Deployment and Operations Considerations
  3. Experimental IPv6-only network at APRICOT 2024
  4. FIRST EXPERIENCES WITH DEPLOYING IPV6-MOSTLY
  5. Deploying IPv6-mostly access networks
  6. RFC8925: IPv6-Only Preferred Option for DHCPv4
  7. RFC8781: Discovering PREF64 in Router Advertisements

*1:[2]ではエンドポイントと定義されていますが、わかりやすさのためホストと表現します。

*2:DNS64は不要なので無効にしています。

tcpreplay を使って Raw IP Pcap ファイルに任意の Ethernet Frame を追加する

「とあるインターネットトレースされた Pcap ファイルを再送してインターネットトラフィックを模す」ということをする必要が出たのですが、元の Pcap ファイルはもちろん加工されており、Ethernet Frame と Payload が削除された状態でした。

$ tcpdump -evXnnr sample.pcap
reading from file sample.pcap, link-type RAW (Raw IP)
14:12:02.795288 ip: (tos 0x10, ttl 64, id 11762, offset 0, flags [DF], proto TCP (6), length 1088)
    10.20.22.132.20222 > 10.20.22.15.62585: Flags [P.], seq 3211:4247, ack 3682, win 501, options [nop,nop,TS val 1205061784 ecr 1681210238], length 1036
        0x0000:  4510 0440 2df2 4000 4006 c7fb 0a14 1684  E..@-.@.@.......
        0x0010:  0a14 160f 4efe f479 54ed cb7f 4024 f5da  ....N..yT...@$..
        0x0020:  8018 01f5 44ed 0000 0101 080a 47d3 c898  ....D.......G...
        0x0030:  6435 3b7e 
...

これを tcpreplay で再送すると、Raw IP のまま送信されてしまいます。そこで、以下のことをしたのでその備忘録です。

  • 偽の Ethernet Frame を追加する
  • IPアドレスを任意のものに変更する
  • Header の Length フィールドに従ってパケットの末尾に Padding を追加する

tcpreplay をインストールする

まずは tcpreplay をインストールします。現時点での最新はv4.4.4ですが、このバージョンでは Ethernet Frame 追加の動作が期待通り動かなかったのでv4.3.4を使います。

$ wget https://github.com/appneta/tcpreplay/releases/download/v4.3.4/tcpreplay-4.3.4.tar.gz
$ tar xzvf tcpreplay-4.3.4.tar.gz
$ cd tcpreplay-4.3.4
$ ./configure
$ make
$ sudo make install
$ tcpreplay -V
Warning: May need to run as root to get access to all network interfaces.
tcpreplay version: 4.3.4 (build git:v4.3.4)
Copyright 2013-2018 by Fred Klassen <tcpreplay at appneta dot com> - AppNeta
Copyright 2000-2012 by Aaron Turner <aturner at synfin dot net>
The entire Tcpreplay Suite is licensed under the GPLv3
Cache file supported: 04
Not compiled with libdnet.
Compiled against libpcap: 1.9.1
64 bit packet counters: enabled
Verbose printing via tcpdump: enabled
Packet editing: disabled
Fragroute engine: disabled
Injection method: PF_PACKET send()
Not compiled with netmap

高速に Pcap を再送したい場合などは、より高度なインストールも可能だそうです。

偽の Ethernet Frame を追加する

$ sudo tcprewrite --dlt=enet --enet-smac=55:44:33:22:11:00 --enet-dmac=00:11:22:33:44:55 --infile=sample.pcap --outfile=sample_add_frame.pcap

Data Link Types: DLT が Ethernet になっており、指定した MAC アドレスを含む Ethernet Frame が追加されていることが確認できます。

$ tcpdump -er sample_add_frame.pcap
reading from file sample_add_frame.pcap, link-type EN10MB (Ethernet)
14:12:02.795288 55:44:33:22:11:00 > 00:11:22:33:44:55, ethertype IPv4 (0x0800), length 1102: (tos 0x10, ttl 64, id 11762, offset 0, flags [DF], proto TCP (6), length 1088)
    10.20.22.132.20222 > 10.20.22.15.62585: Flags [P.], seq 3211:4247, ack 3682, win 501, options [nop,nop,TS val 1205061784 ecr 1681210238], length 1036
        0x0000:  4510 0440 2df2 4000 4006 c7fb 0a14 1684  E..@-.@.@.......
        0x0010:  0a14 160f 4efe f479 54ed cb7f 4024 f5da  ....N..yT...@$..
        0x0020:  8018 01f5 44ed 0000 0101 080a 47d3 c898  ....D.......G...
        0x0030:  6435 3b7e                                d5;~
...

IPアドレスを任意のものに変更する

$ tcpprep --auto=first --pcap=sample_add_frame.pcap --cachefile=sample_add_frame.cache
$ tcprewrite --endpoints=192.0.2.254:203.0.113.254 --cachefile=sample_add_frame.cache --infile=sample_add_frame.pcap --outfile=sample_add_frame_rewrite_ip.pcap

IP アドレスが書き換わっていることが確認できます。また、チェックサムは自動で再計算してくれます。

$ tcpdump -evXnnr sample_add_frame_rewrite_ip.pcap
reading from file sample_add_frame_rewrite_ip.pcap, link-type EN10MB (Ethernet)
14:12:02.795288 55:44:33:22:11:00 > 00:11:22:33:44:55, ethertype IPv4 (0x0800), length 1102: (tos 0x10, ttl 64, id 11762, offset 0, flags [DF], proto TCP (6), length 1088)
    192.0.2.254.20222 > 203.0.113.254.62585: Flags [P.], seq 3211:4247, ack 3682, win 501, options [nop,nop,TS val 1205061784 ecr 1681210238], length 1036
        0x0000:  4510 0440 2df2 4000 4006 08b9 c000 02fe  E..@-.@.@.......
        0x0010:  cb00 71fe 4efe f479 54ed cb7f 4024 f5da  ..q.N..yT...@$..
        0x0020:  8018 01f5 85aa 0000 0101 080a 47d3 c898  ............G...
        0x0030:  6435 3b7e  
...

IPv6 の場合は、--endpoints=[2001:db8::dead:beef]:[::ffff:0:0:ac:f:0:2]のように指定することができます。

Header の Length フィールドに従ってパケットの末尾に Padding を追加する

$ tcprewrite --fixlen=pad --infile=sample_add_frame_rewrite_ip.pcap --outfile=sample_add_frame_rewrite_ip_pad.pca

0x00 の Padding が追加されていることが確認できます。

$ tcpdump -evXnnr sample_add_frame_rewrite_ip_pad.pcap
reading from file sample_add_frame_rewrite_ip_pad.pcap, link-type EN10MB (Ethernet)
14:12:02.795288 55:44:33:22:11:00 > 00:11:22:33:44:55, ethertype IPv4 (0x0800), length 1102: (tos 0x10, ttl 64, id 11762, offset 0, flags [DF], proto TCP (6), length 1088)
    192.0.2.254.20222 > 203.0.113.254.62585: Flags [P.], cksum 0x26b3 (correct), seq 3211:4247, ack 3682, win 501, options [nop,nop,TS val 1205061784 ecr 1681210238], length 1036
        0x0000:  4510 0440 2df2 4000 4006 08b9 c000 02fe  E..@-.@.@.......
        0x0010:  cb00 71fe 4efe f479 54ed cb7f 4024 f5da  ..q.N..yT...@$..
        0x0020:  8018 01f5 26b3 0000 0101 080a 47d3 c898  ....&.......G...
        0x0030:  6435 3b7e 0000 0000 0000 0000 0000 0000  d5;~............
        0x0040:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0050:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0060:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0070:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0080:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0090:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x00a0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x00b0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x00c0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x00d0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x00e0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x00f0:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0100:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0110:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0120:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0130:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0140:  0000 0000 0000 0000 0000 0000 0000 0000  ................
        0x0150:  0000 0000 0000 0000 0000 0000 0000 0000  ................
...

JANOG50 NETCON 問題解説 Level2-1, Level2-5

Level2-1

問題文

デフォルトゲートウェイ冗長化すべくVRRPを導入したが,MasterのGi0/1に障害が発生した場合に経路が切り替わってくれないようです。この場合でも経路が切り替わるように設定をしてください。

確認方法

  • Clientから192.168.2.2にpingを打ち続ける
  • MasterのGi0/1をshutdownする
  • 経路が切り替わってpingが継続して届いていれば確認完了

解答・解説

オブジェクトトラッキングを設定する問題です。
初期状態はVRRPを有効にし,VIPとPriorityの設定をした状態です。MasterのPriorityを200,BackupのPriorityはデフォルト値(100)に設定しています。
この状態では,ダウンリンク側の障害に対しては経路が切り替わってくれますが,アップリンク側の障害には経路が切り替わってくれません。
そこでオブジェクトトラッキングの設定をしてあげることにより,アップリンク側に障害が発生した場合にダウンリンク側のPriorityを意図的に下げてあげます。これによりアップリンク側に障害が発生した場合でも経路が切り替わってくれます。
設定における注意点として,Backup側のPriorityはデフォルト値(100)でMaster側のPriorityは200なので,オブジェクトトラッキングによるdecrement値は100より大きくする必要があります。

!
hostname Master
!
track 1 interface GigabitEthernet0/1 line-protocol     ## 追加設定(1)
!
interface GigabitEthernet0/0
 ip address 191.168.1.2 255.255.255.0
 vrrp 1 ip 192.168.1.1
 vrrp 1 priority 200
 vrrp 1 track 1 decrement 150     ## 追加設定(2)
 no shutdown
!
interface GigabitEthernet0/1
 ip address 192.168.2.1 255.255.255.0
 no shutdown
!

Level2-5

問題文

iBGPフルメッシュを避けるためにBGPルートレフレクタを導入しました。(RR1,RR2)
しかしながらRouter1,Router2から10.200.1.0/24のネットワークにpingを打つと不安定な現象が見られました。
何が起きているのかを簡単に説明し,iBGPのピアを正しく設定してください。
なお,初期状態ではRouter2,Router3はRR1のクライアントで,Router1,Router4はRR2のクライアントです。また,RR1とRR2はiBGPピアを張っています。

参考:

解答・解説

ルートリフレクタに対するクライアントが不適切なため,Router1 <-> Router2でルーティングループが発生しています。
Router1はRR2とピアを張っているため,10.200.1.0/24への経路として10.100.255.4をBGPから学習しています。

Router1#sh ip route 10.200.1.2
Routing entry for 10.200.1.0/24
  Known via "bgp 100", distance 200, metric 0
  Tag 200, type internal
  Last update from 10.100.255.4 01:00:43 ago
  Routing Descriptor Blocks:
  * 10.100.255.4, from 10.100.255.22, 01:00:43 ago
      Route metric is 0, traffic share count is 1
      AS Hops 1
      Route tag 200
      MPLS label: none

IGPからは10.100.255.4への経路として10.100.5.2と10.100.1.1を学習し,マルチパスとして保持しています。
これはRouter2においても同様です。

Router1#sh ip route 10.100.255.4
Routing entry for 10.100.255.4/32
  Known via "ospf 1", distance 110, metric 4, type intra area
  Last update from 10.100.1.1 on GigabitEthernet0/1, 01:01:50 ago
  Routing Descriptor Blocks:
  * 10.100.5.2, from 10.100.255.4, 01:01:50 ago, via GigabitEthernet0/0
      Route metric is 4, traffic share count is 1
    10.100.1.1, from 10.100.255.4, 01:01:50 ago, via GigabitEthernet0/1
      Route metric is 4, traffic share count is 1

Router1で例えば10.200.1.2にpingを実行すると,まず初めに10.100.5.2か10.100.1.2が選択されます。ここで10.100.5.2が選択された場合,同様にRouter2でも10.100.5.1か10.100.2.1が選択されますが,さらにここで10.100.5.1が選択された場合,ルーティングループが発生します。
OSPFのマルチパスによってロードバランシングされているため,ループするパケットとしないパケットが発生しています。

Router1#ping 10.200.1.1 repeat 100
Type escape sequence to abort.
Sending 100, 100-byte ICMP Echos to 10.200.1.1, timeout is 2 seconds:
!!.!.!..!!.!.!..!!.!.!..!!.!.!..!!.!.!..!!.!.!..!!.!.!..!!.!.!..!!.!.!
..!!.!.!..!!.!.!..!!.!.!..!!.!

この現象は,Router1をRR1のクライアント,Router2をRR2のクライアントとすることで解決できます。

# Router1
router bgp 100
 bgp router-id 10.100.255.1
 neighbor 10.100.255.11 remote-as 100
 neighbor 10.100.255.11 update-source Loopback0
# Router2
router bgp 100
 bgp router-id 10.100.255.2
 neighbor 10.100.255.22 remote-as 100
 neighbor 10.100.255.22 update-source Loopback0
# RR1
router bgp 100
 bgp router-id 10.100.255.11
 neighbor 10.100.255.1 remote-as 100
 neighbor 10.100.255.1 update-source Loopback0
 neighbor 10.100.255.3 remote-as 100
 neighbor 10.100.255.3 update-source Loopback0
 neighbor 10.100.255.22 remote-as 100
 neighbor 10.100.255.22 update-source Loopback0
 !
 address-family ipv4
  neighbor 10.100.255.1 activate
  neighbor 10.100.255.1 route-reflector-client
  neighbor 10.100.255.3 activate
  neighbor 10.100.255.3 route-reflector-client
  neighbor 10.100.255.22 activate
 exit-address-family
!
# RR2
router bgp 100
 bgp router-id 10.100.255.22
 neighbor 10.100.255.2 remote-as 100
 neighbor 10.100.255.2 update-source Loopback0
 neighbor 10.100.255.4 remote-as 100
 neighbor 10.100.255.4 update-source Loopback0
 neighbor 10.100.255.11 remote-as 100
 neighbor 10.100.255.11 update-source Loopback0
 !
 address-family ipv4
  neighbor 10.100.255.2 activate
  neighbor 10.100.255.2 route-reflector-client
  neighbor 10.100.255.4 activate
  neighbor 10.100.255.4 route-reflector-client
  neighbor 10.100.255.11 activate
 exit-address-family
!

作問振り返り

リンクパススルー(LPT)

Level2-1作問後のフィードバックで,「物理配線だとMasterのGi0/1をShutdownすると対向であるBackupのGi0/1のLinkもDownするためPingは送くれなくなる」と指摘がありました。
今回は仮想環境でBridgeしているためそうはならないとのことなので,このまま出題しました。

ループが発生したりしなかったり

Level2-5問題を作問するにあたって,初めは安定してループを発生させるために,OSPF Multi-Path Maximumを1にすることで全てのパケットをRouter2へ向かわせようとしていました。
しかしながら,Router1で10.200.255.4へのNexthopとしてGi0/1(Router2側)が選択されてほしかったにも関わらず,Gi0/0(RR1側)が選択されてしまうことがあるという現象に見舞われました。
これは等コストなパスがあったとき,最初に追加されるパスがそのままベストパスになるために発生した現象でした。
フィードバックをいただき,Multi-Pathを2にすることで必ず一定数はRouter2へ向かうようになるため,安定したループを再現することができました。