FRR BGPdのbestpath selectionの3番までを動かして検証する

BGPでは複数のneighborから同じprefixに対する経路を受け取ることがあります.
それに対して bestpath selection というalgorithmを適用し,
実際にFIBにinstallする経路を決定します.
ここではFRRを用いてそれを検証し,
BGPのお気持ちを頑張って理解してみます.
FRRのalgorithm はこちらに記載されています.
今回はこのうち3番,Local Route Checkまでを検証してみます.

恐らくですが全部のruleを検証するように,後々書きかえると思います.
もっといいtopoに変更したり,ちゃんとpacapして出すと思います.
それまでの暫定的な記事だと思っていただければ.

今回用いるtinet configは,
こちら に置いてあります.

実行環境も載せておきます.

/images/frr-bestpath/archey.png

まずは経路につけるweightを制御することでbestpathにどう影響が出るのか見ていきます.
まずはequal weightで経路広報してみて,
その後weightをつけるとどうなるか,というようにして見てみます.

次のようなnetworkを考えてみます.
ここで,R2, R3からは redistribute connected を設定しています.
configを見るとわかりますがprefix-listでroute filteringを行っているので,
R2, R3は 10.0.0.0/16 , 10.0.1.0/24 のみ広報します.
upperからdefault routeを広報し neighbor PEER default-originate を設定するというのもやってみたかったですが.
対応するtinet configはこちらに.

           +-----------------------------------+
           |                                   |
           |                                   |
           |              R1                   |
           |           AS65001                 |
           |                                   |
           |                                   |
           |         .1                   .2   |
           +----------+-------------------+----+
                      |    10.0.0.0/16    |
                      |                   |
+---------------------+---+    +----------+---------------+
|                    .251 |    |          .252            |
|           R2            |    |           R3             |
|         AS65002         |    |         AS65003          |
|                         |    |                          |
|                         |    |                          |
|              .1         |    |          .2              |
+-------------+-----------+    +---------+----------------+
              |          10.0.1.0/24     |
           +--+--------------------------+------+
           |   .251                      .252   |
           |                                    |
           |              C1                    |
           |           AS65004                  |
           |                                    |
           |                                    |
           |                                    |
           +------------------------------------+

一例として,R1 configを見てみましょう.

$ docker exec R1 vtysh -c 'sh run'
Building configuration...

Current configuration:
!
frr version 8.0
frr defaults traditional
hostname R1
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
interface net1
 ip address 10.0.0.1/16
!
router bgp 65001
 bgp router-id 1.1.1.1
 bgp bestpath as-path multipath-relax
 neighbor 10.0.0.251 remote-as 65002
 neighbor 10.0.0.252 remote-as 65003
 !
 address-family ipv4 unicast
  neighbor 10.0.0.251 route-map RMAP_LOWER in
  neighbor 10.0.0.252 route-map RMAP_LOWER in
 exit-address-family
!
ip prefix-list PLIST_LOWER seq 5 permit 10.0.1.0/24
!
route-map RMAP_LOWER permit 10
 match ip address prefix-list PLIST_LOWER
!
line vty
!
end

RMAP_LOWER は C1が接続するnetwork prefixだけをpermitし,
R2, R3からはその経路しかもらわないようにfilteringします.
R2, R3は実際には 10.0.0.0/16 にも接続していますが,
同じくR1も接続していてその経路はいらないので弾くイメージです.

今度はC1でrib/fibを見てみましょう.

$ docker exec C1 vtysh -c 'sh bgp ipv4 unicast'
BGP table version is 2, local router ID is 4.4.4.4, vrf id 0
Default local pref 100, local AS 65004
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*= 10.0.0.0/16      10.0.1.2                 0             0 65003 ?
*>                  10.0.1.1                 0             0 65002 ?

Displayed  1 routes and 2 total paths
$ docker exec C1 vtysh -c 'sh ip route'
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

B>* 10.0.0.0/16 [20/0] via 10.0.1.1, net2, weight 1, 00:00:14
  *                    via 10.0.1.2, net2, weight 1, 00:00:14
C>* 10.0.1.0/24 is directly connected, net2, 00:00:16

いい感じにmultipathが広報されていますね.
それでは本題です.
C1のroute-mapで set weight をかけてみましょう.

$ docker exec C1 vtysh -c 'sh run'
Building configuration...

Current configuration:
!
frr version 8.0
frr defaults traditional
hostname C1
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
interface net2
 ip address 10.0.1.254/24
!
router bgp 65004
 bgp router-id 4.4.4.4
 bgp bestpath as-path multipath-relax
 neighbor 10.0.1.1 remote-as 65002
 neighbor 10.0.1.2 remote-as 65003
 !
 address-family ipv4 unicast
  neighbor 10.0.1.1 route-map RMAP_UPPER1 in
  neighbor 10.0.1.2 route-map RMAP_UPPER2 in
 exit-address-family
!
ip prefix-list PLIST_UPPER seq 5 permit 10.0.0.0/16
!
route-map RMAP_UPPER1 permit 10
 match ip address prefix-list PLIST_UPPER
 set weight 10
!
route-map RMAP_UPPER2 permit 10
 match ip address prefix-list PLIST_UPPER
 set weight 20
!
line vty
!
end

この状態でC1のrib/fibを見ると変化を確認できます.

$ docker exec C1 vtysh -c 'sh bgp ipv4 unicast'
BGP table version is 2, local router ID is 4.4.4.4, vrf id 0
Default local pref 100, local AS 65004
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.0.0/16      10.0.1.2                 0            20 65003 ?
*                   10.0.1.1                 0            10 65002 ?

Displayed  1 routes and 2 total paths
$ docker exec C1 vtysh -c 'sh ip route'
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

B>* 10.0.0.0/16 [20/0] via 10.0.1.2, net2, weight 1, 00:01:10
C>* 10.0.1.0/24 is directly connected, net2, 00:01:12

これが Weight Check の挙動です.
同じprefixの経路が複数広報されたとき,
weightの高い方を優先してbestpathを選定します.
weight以外の項目に変化がないのが重要です.
10.0.0.0/16 の経路はこのように重み付けされるが,
10.0.1.0/24 は普通にmultipathである点に注意してください.
(route-mapを適用しているのは経路を受け取っているC1本人なので)

つづいてlocal preferenceです.
これはiBGP内で用いられるpath attributeの LOCAL_PREF に関わってきます.

ここで対象とするものは,先程のnetworkからほとんど変わっていません.
loでpeeringする点くらい?
loのaddressはIGPとかを使わずにstatic routeで横着しています.
対応するtinet configはこちらに.

記事では解説しませんが,repositoryにはequal-localpref.yaml も置いてあるので参考にしてください.
先ほどと同じくmultipathがselectされるだけですが.

さて,ここではR2, R3のconfigから,C1に適用するroute-mapを見てみます.
LOCAL_PREF path attributeはAS内のすべてのiBGP peerを流れるので,
R2で設定するとそれがC1に届くまで維持されます.

まず,R2では set local-preference 200 を設定しています.

$ docker exec R2 vtysh -c 'sh route-map RMAP_UPPER'
ZEBRA:
route-map: RMAP_UPPER Invoked: 0 Optimization: enabled Processed Change: false
 permit, sequence 10 Invoked 0
  Match clauses:
    ip address prefix-list PLIST_UPPER
  Set clauses:
  Call clause:
  Action:
    Exit routemap
BGP:
route-map: RMAP_UPPER Invoked: 12 Optimization: enabled Processed Change: false
 permit, sequence 10 Invoked 6
  Match clauses:
    ip address prefix-list PLIST_UPPER
  Set clauses:
    local-preference 200
  Call clause:
  Action:
    Exit routemap

次に,R3では set local-preference 400 を設定しています.

$ docker exec R3 vtysh -c 'sh route-map RMAP_UPPER'
ZEBRA:
route-map: RMAP_UPPER Invoked: 0 Optimization: enabled Processed Change: false
 permit, sequence 10 Invoked 0
  Match clauses:
    ip address prefix-list PLIST_UPPER
  Set clauses:
  Call clause:
  Action:
    Exit routemap
BGP:
route-map: RMAP_UPPER Invoked: 13 Optimization: enabled Processed Change: false
 permit, sequence 10 Invoked 7
  Match clauses:
    ip address prefix-list PLIST_UPPER
  Set clauses:
    local-preference 400
  Call clause:
  Action:
    Exit routemap

この状態で 10.0.0.0/16 の経路が広報されるとき,
UPDATE messageのLOCAL_PREF attributeが期待する値に書き換わり, C1でのbestpath selectionに影響を与えるはずです.

$ docker exec C1 vtysh -c 'sh bgp ipv4 unicast'
BGP table version is 1, local router ID is 4.4.4.4, vrf id 0
Default local pref 100, local AS 65002
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*>i10.0.0.0/16      10.0.255.3               0    400      0 ?
* i                 10.0.255.2               0    200      0 ?

Displayed  1 routes and 2 total paths
$ docker exec C1 vtysh -c 'sh ip route'
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

B>  10.0.0.0/16 [200/0] via 10.0.255.3 (recursive), weight 1, 00:00:19
  *                       via 10.0.1.2, net2, weight 1, 00:00:19
C>* 10.0.1.0/24 is directly connected, net2, 00:00:21
S>* 10.0.255.2/32 [1/0] via 10.0.1.1, net2, weight 1, 00:00:21
S>* 10.0.255.3/32 [1/0] via 10.0.1.2, net2, weight 1, 00:00:21
C>* 10.0.255.4/32 is directly connected, lo, 00:00:21

想定どおりの挙動ですね.
LOCAL_PREF attributeに格納された値が大きい方をbestpathとして優先します.
ところで NEXT_HOP attributeのrecursive lookupに全然詳しくないことがわかったので,
これはいずれ勉強して記事にしなければ.

最後にLocal Route Checkですが,
これはsimpleにstatic/aggregates/redistributed routeを優先する,というものです.
実はこのruleはすでに確認しています.
Local Preference Check のR2でribを見てみましょう.

$ docker exec R2 vtysh -c 'sh bgp ipv4 unicast'
BGP table version is 3, local router ID is 2.2.2.2, vrf id 0
Default local pref 100, local AS 65002
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
* i10.0.0.0/16      10.0.255.3               0    400      0 ?
*>                  0.0.0.0                  0         32768 ?
*> 10.0.1.0/24      0.0.0.0                  0         32768 ?
*> 10.0.255.2/32    0.0.0.0                  0         32768 ?

Displayed  3 routes and 4 total paths

ここで,R2は 10.0.0.0/16 に関する2つの経路を持っていることがわかります.

  • R3とiBGP peerを形成して,R3からもらった経路
  • そもそものconnected route

bestpathに選ばれている経路を見ると,
上記でいう2つ目,connected routeが対応していそうですね.
というのがLocal Route Checkの挙動です.simple.

逆にいうと,
static routeよりも高いweight( FRRなら 32768 より上 ) でneighborを設定すれば,
static routeよりもらってきた経路を優先することができるわけです.

ここでは簡単にFRRでBGPd bestpath selectionを感じながら色々動かしてみました.
他にも,

  • AS_PATH Length Check
  • Route Origin Check
  • MED Check
  • External Check
  • IGP Cost Check

を含む多くの項目が存在しますが,
multipathよりも優先される項目は全部検証しておいても良いかな?と思っています.

というか,BGP何もわかっていない…