🐡

VNet peering された 2 つの Hub に、それぞれ ExpressRoute で接続された VNet 同士を、通信可能にする

2022/08/19に公開

TL;DR

  • オンプレミス #1 - [ER] -> VNet #1 -> VNet #2 -> [ER] - オンプレミス #2 という構成を作る
  • この構成を実現するための手段の 1 つとして VXLAN と FRRouting を使う
  • サンプルの Bicep ファイルもおつけしておきました

ぷりふぇーす

文字で表現するとなかなかわかりづらいと思いますが、こちらのアーキテクチャっぽいのものを作ります。

nva vxlan multi hub

インフラの準備

Bicep ファイルはこちらです。
以下のリソースが大体 1 時間もあれば作られるかと思います。

  • VNet x4
  • [existing] ExpressRoute circuit x2
  • ExpressRoute Gateway x4
  • Connection (for ExpressRoute) x4
  • Azure Bastion x4
  • Azure Route Server x2
  • Azure VM x4
main.bicep
main.bicep
param location01 string = 'eastasia'

param circuit01 object
param circuit02 object

param sshKeyRGName string

param isInitialDeploy bool = false
var useExisting = !isInitialDeploy

param publicKeyName string

resource public_key 'Microsoft.Compute/sshPublicKeys@2022-03-01' existing = {
  name: publicKeyName
  scope: resourceGroup(sshKeyRGName)
}

/* ****************************** hub00 ****************************** */

var hub00Suffix = 'hub00'

resource hub00 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-${hub00Suffix}'
  location: location01
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: '10.0.100.0/24'
        }
      }
      {
        name: 'GatewaySubnet'
        properties: {
          addressPrefix: '10.0.200.0/24'
        }
      }
      {
        name: 'RouteServerSubnet'
        properties: {
          addressPrefix: '10.0.210.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

var ergw00Name = 'ergw-${hub00Suffix}'
module ergw00 '../lib/ergw.bicep' = {
  name: ergw00Name
  params: {
    location: location01
    gatewayName: ergw00Name
    vnetName: hub00.name
    useExisting: useExisting
  }
}

resource conn_hub00 'Microsoft.Network/connections@2022-01-01' = {
  name: 'conn-${hub00Suffix}'
  location: location01
  properties: {
    connectionType: 'ExpressRoute'
    virtualNetworkGateway1: {
      id: ergw00.outputs.ergwId
    }
    peer: {
      id: circuit01.id
    }
    authorizationKey: circuit01.authorizationKey1
  }
}

module bast00 '../lib/bastion.bicep' = {
  name: 'bast-${hub00Suffix}'
  params: {
    location: location01
    vnetName: hub00.name
  }
}

var rs00Name = 'rs-${hub00Suffix}'
module rs00 '../lib/route-server.bicep' = {
  name: rs00Name
  params: {
    location: location01
    routeServerName: rs00Name
    vnetName: hub00.name
    bgpConnections: [
      {
        name: vm_hub00.name
        ip: vm_hub00.outputs.privateIP
        asn: '65001'
      }
    ]
    useExisting: useExisting
  }
  dependsOn: [
    conn_hub00
  ]
}

var vm00Name = 'vm-${hub00Suffix}'
module vm_hub00 '../lib/ubuntu2004.bicep' = {
  name: vm00Name
  params: {
    location: location01
    keyData: public_key.properties.publicKey
    subnetId: hub00::defaultsubnet.id
    vmName: vm00Name
    enableIPForwarding: true
  }
}

/* ****************************** hub10 ****************************** */

var hub10Suffix = 'hub10'

resource hub10 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-${hub10Suffix}'
  location: location01
  properties: {
    addressSpace: { 
      addressPrefixes: [
        '10.10.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.10.0.0/24'
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: '10.10.100.0/24'
        }
      }
      {
        name: 'GatewaySubnet'
        properties: {
          addressPrefix: '10.10.200.0/24'
        }
      }
      {
        name: 'RouteServerSubnet'
        properties: {
          addressPrefix: '10.10.210.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

module peering_hub0010 '../lib/vnet-peering.bicep' = {
  name: 'peering-${hub00Suffix}-${hub10Suffix}'
  params: {
    vnet01Name: hub00.name
    vnet02Name: hub10.name
  }
}

var ergw10Name = 'ergw-${hub10Suffix}'
module ergw10 '../lib/ergw.bicep' = {
  name: ergw10Name
  params: {
    location: location01
    gatewayName: ergw10Name
    vnetName: hub10.name
    useExisting: useExisting
  }
}

resource conn_hub10 'Microsoft.Network/connections@2022-01-01' = {
  name: 'conn-${hub10Suffix}'
  location: location01
  properties: {
    connectionType: 'ExpressRoute'
    virtualNetworkGateway1: {
      id: ergw10.outputs.ergwId
    }
    peer: {
      id: circuit02.id
    }
    authorizationKey: circuit02.authorizationKey1
  }
}

module bast10 '../lib/bastion.bicep' = {
  name: 'bast-${hub10Suffix}'
  params: {
    location: location01
    vnetName: hub10.name
  }
}

var rs10Name = 'rs-${hub10Suffix}'
module rs10 '../lib/route-server.bicep' = {
  name: rs10Name
  params: {
    location: location01
    routeServerName: rs10Name
    vnetName: hub10.name
    bgpConnections: [
      {
        name: vm_hub10.name
        ip: vm_hub10.outputs.privateIP
        asn: '65001'
      }
    ]
    useExisting: useExisting
  }
  dependsOn: [
    conn_hub10
  ]
}

var vm10Name = 'vm-${hub10Suffix}'
module vm_hub10 '../lib/ubuntu2004.bicep' = {
  name: vm10Name
  params: {
    location: location01
    keyData: public_key.properties.publicKey
    subnetId: hub10::defaultsubnet.id
    vmName: vm10Name
    enableIPForwarding: true
  }
}

/* ****************************** hub100 ****************************** */

var hub100Suffix = 'hub100'

resource hub100 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-${hub100Suffix}'
  location: location01
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.100.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.100.0.0/24'
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: '10.100.100.0/24'
        }
      }
      {
        name: 'GatewaySubnet'
        properties: {
          addressPrefix: '10.100.200.0/24'
        }
      }
      {
        name: 'RouteServerSubnet'
        properties: {
          addressPrefix: '10.100.210.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

var ergw100Name = 'ergw-${hub100Suffix}'
module ergw100 '../lib/ergw.bicep' = {
  name: ergw100Name
  params: {
    location: location01
    gatewayName: ergw100Name
    vnetName: hub100.name
    useExisting: useExisting
  }
}

resource conn_hub100 'Microsoft.Network/connections@2022-01-01' = {
  name: 'conn-${hub100Suffix}'
  location: location01
  properties: {
    connectionType: 'ExpressRoute'
    virtualNetworkGateway1: {
      id: ergw100.outputs.ergwId
    }
    peer: {
      id: circuit01.id
    }
    authorizationKey: circuit01.authorizationKey2
  }
  dependsOn: [
    conn_hub00
  ]
}

module bast100 '../lib/bastion.bicep' = {
  name: 'bast-${hub100Suffix}'
  params: {
    location: location01
    vnetName: hub100.name
  }
}

var vm100Name = 'vm-${hub100Suffix}'
module vm_hub100 '../lib/ubuntu2004.bicep' = {
  name: vm100Name
  params: {
    location: location01
    keyData: public_key.properties.publicKey
    subnetId: hub100::defaultsubnet.id
    vmName: vm100Name
    enableIPForwarding: true
  }
}

/* ****************************** hub110 ****************************** */

var hub110Suffix = 'hub110'

resource hub110 'Microsoft.Network/virtualNetworks@2022-01-01' = {
  name: 'vnet-${hub110Suffix}'
  location: location01
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.110.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '10.110.0.0/24'
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: '10.110.100.0/24'
        }
      }
      {
        name: 'GatewaySubnet'
        properties: {
          addressPrefix: '10.110.200.0/24'
        }
      }
      {
        name: 'RouteServerSubnet'
        properties: {
          addressPrefix: '10.110.210.0/24'
        }
      }
    ]
  }

  resource defaultsubnet 'subnets' existing = {
    name: 'default'
  }
}

var ergw110Name = 'ergw-${hub110Suffix}'
module ergw110 '../lib/ergw.bicep' = {
  name: ergw110Name
  params: {
    location: location01
    gatewayName: ergw110Name
    vnetName: hub110.name
    useExisting: useExisting
  }
}

resource conn_hub110 'Microsoft.Network/connections@2022-01-01' = {
  name: 'conn-${hub110Suffix}'
  location: location01
  properties: {
    connectionType: 'ExpressRoute'
    virtualNetworkGateway1: {
      id: ergw110.outputs.ergwId
    }
    peer: {
      id: circuit02.id
    }
    authorizationKey: circuit02.authorizationKey2
  }
  dependsOn: [
    conn_hub10
  ]
}

module bast110 '../lib/bastion.bicep' = {
  name: 'bast-${hub110Suffix}'
  params: {
    location: location01
    vnetName: hub110.name
  }
}

var vm110Name = 'vm-${hub110Suffix}'
module vm_hub110 '../lib/ubuntu2004.bicep' = {
  name: vm110Name
  params: {
    location: location01
    keyData: public_key.properties.publicKey
    subnetId: hub110::defaultsubnet.id
    vmName: vm110Name
    enableIPForwarding: true
  }
}

Ubuntu Server 20.04 の NVA 化 (VXLAN)

BGP については FRRouting を利用しますが、その前提として VXLAN の設定しておきます。
VXLAN の設定についてはこちらの記事が前提にあります。

https://zenn.dev/skmkzyk/articles/ubuntu-2004-vxlan

  • NVA #1 は以下の設定が必要 (10.0.0.4 → 10.10.0.4)
ip link add vxlan0 type vxlan id 77 remote 10.10.0.4 dstport 4789 dev eth0
ip link set up vxlan0
ip address add 169.254.0.1/24 dev vxlan0
  • NVA #2 は以下の設定が必要 (10.10.0.4 → 10.0.0.4)
ip link add vxlan0 type vxlan id 77 remote 10.0.0.4 dstport 4789 dev eth0
ip link set up vxlan0
ip address add 169.254.0.2/24 dev vxlan0

上記記事に沿って、これらの script (をちょっと修正したもの) を /etc/network/if-post-up.d/vxlan に配置します。
VM を一度再起動し、ip linkip addrvxlan0 が生えていること、および ping で疎通が取れることを確認します。

$ ip -d link show vxlan0
3: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether 9a:5f:72:27:62:4b brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
    vxlan id 77 remote 10.10.0.4 dev eth0 srcport 0 0 dstport 4789 ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 62780 gso_max_segs 65535
$ ip addr show vxlan0
3: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 9a:5f:72:27:62:4b brd ff:ff:ff:ff:ff:ff
    inet 169.254.0.1/24 scope global vxlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::985f:72ff:fe27:624b/64 scope link
       valid_lft forever preferred_lft forever
$ ping -c 4 169.254.0.2
PING 169.254.0.2 (169.254.0.2) 56(84) bytes of data.
64 bytes from 169.254.0.2: icmp_seq=1 ttl=64 time=2.26 ms
64 bytes from 169.254.0.2: icmp_seq=2 ttl=64 time=0.915 ms
64 bytes from 169.254.0.2: icmp_seq=3 ttl=64 time=1.22 ms
64 bytes from 169.254.0.2: icmp_seq=4 ttl=64 time=0.999 ms

--- 169.254.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3010ms
rtt min/avg/max/mdev = 0.915/1.348/2.259/0.537 ms

Ubuntu Server 20.04 の NVA 化 (FRRouting)

FRRouting の config は以下のような感じです。[1]

vm-hub00# show run
Building configuration...

Current configuration:
!
frr version 8.3
frr defaults traditional
hostname vm-hub00
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
ip route 10.0.210.0/24 10.0.0.1
!
router bgp 65001
 neighbor 10.0.210.4 remote-as 65515
 neighbor 10.0.210.4 ebgp-multihop 255
 neighbor 10.0.210.5 remote-as 65515
 neighbor 10.0.210.5 ebgp-multihop 255
 neighbor 169.254.0.2 remote-as 65001
 !
 address-family ipv4 unicast
  neighbor 10.0.210.4 soft-reconfiguration inbound
  neighbor 10.0.210.4 prefix-list rs-hub00 out
  neighbor 10.0.210.4 route-map rmap-bogon-asns in
  neighbor 10.0.210.4 route-map rmap-azure-asns out
  neighbor 10.0.210.5 soft-reconfiguration inbound
  neighbor 10.0.210.5 prefix-list rs-hub00 out
  neighbor 10.0.210.5 route-map rmap-bogon-asns in
  neighbor 10.0.210.5 route-map rmap-azure-asns out
  neighbor 169.254.0.2 next-hop-self
  neighbor 169.254.0.2 soft-reconfiguration inbound
  neighbor 169.254.0.2 prefix-list vm-hub10 out
 exit-address-family
exit
!
ip prefix-list vm-hub10 seq 5 deny 10.0.0.0/16 ge 16
ip prefix-list vm-hub10 seq 10 permit any
ip prefix-list rs-hub00 seq 5 deny 10.0.0.0/16
ip prefix-list rs-hub00 seq 10 deny 10.100.0.0/16
ip prefix-list rs-hub00 seq 15 permit any
!
bgp as-path access-list azure-asns seq 5 permit _65515_
bgp as-path access-list bogon-asns seq 5 permit _0_
bgp as-path access-list bogon-asns seq 10 permit _23456_
bgp as-path access-list bogon-asns seq 15 permit _1310[0-6][0-9]_|_13107[0-1]_
bgp as-path access-list bogon-asns seq 20 deny _65515_
bgp as-path access-list bogon-asns seq 25 permit ^65
!
route-map rmap-bogon-asns deny 5
 match as-path bogon-asns
exit
!
route-map rmap-bogon-asns permit 10
exit
!
route-map rmap-azure-asns permit 5
 match as-path azure-asns
 set as-path replace any
exit
!
route-map rmap-azure-asns deny 10
exit
!
end

簡易的ですが説明を入れます。

  • ip route 10.0.210.0/24 10.0.0.1
    この仮想マシンとしては 10.0.0.0/16 ではなく 10.0.0.0/24 として connected を認識しており、RouteServerSubnet が見えていないため static route を入れます。

  • neighbor 10.0.210.4 remote-as 65515neighbor 10.0.210.4 ebgp-multihop 255
    BGP の peer の設定および、上記 static route で 1 hop 先の peer との接続となるため ebgp-multihop を入れておきます

  • neighbor 169.254.0.2 remote-as 65001
    こちらは VXLAN を使った connected となるため ebgp-multihop は不要です

  • neighbor 10.0.210.4 soft-reconfiguration inbound
    これを入れると show ip bgp nei x.x.x.x received-routes が叩けるようになりトラブルシュートに便利です

  • neighbor 10.0.210.4 prefix-list rs-hub00 out
    Azure Route Server に広報する経路を絞るための prefix-list です
    prefix をべた書きしているので BGP Community などを使って置き換えてもいいかなとは思っています

  • neighbor 10.0.210.4 route-map rmap-bogon-asns in
    Bogon な AS-PATH を含む経路を受け入れないための route-map です
    実際には Azure Route Server で使っている AS65515 は除外しています

  • neighbor 10.0.210.4 route-map rmap-azure-asns out
    AS65515 なものだけを経路広報する、かつ、AS_PATH をすべて置き換える route-map を適用しています

  • neighbor 169.254.0.2 next-hop-self
    169.254.0.2 との peer は iBGP になるのですが、connected を OSPF などで回しておらず next-hop が解決できないため、next-hop-self で上書きしています。
    受け取った経路の next-hop が VXLAN の IP アドレスとなるため、2 つの NVA の間の通信が VXLAN でトンネルされます。

参考までにもう片方の config も張り付けておきます。
こちらの NVA の IP アドレスは 10.10.0.4 (VNet は 10.10.0.0/16) です。

show running-config
vm-hub10# show run
Building configuration...

Current configuration:
!
frr version 8.3
frr defaults traditional
hostname vm-hub10
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
ip route 10.10.210.0/24 10.10.0.1
!
router bgp 65001
 neighbor 10.10.210.4 remote-as 65515
 neighbor 10.10.210.4 ebgp-multihop 255
 neighbor 10.10.210.5 remote-as 65515
 neighbor 10.10.210.5 ebgp-multihop 255
 neighbor 169.254.0.1 remote-as 65001
 !
 address-family ipv4 unicast
  neighbor 10.10.210.4 soft-reconfiguration inbound
  neighbor 10.10.210.4 prefix-list rs-hub10 out
  neighbor 10.10.210.4 route-map rmap-bogon-asns in
  neighbor 10.10.210.4 route-map rmap-azure-asns out
  neighbor 10.10.210.5 soft-reconfiguration inbound
  neighbor 10.10.210.5 prefix-list rs-hub10 out
  neighbor 10.10.210.5 route-map rmap-bogon-asns in
  neighbor 10.10.210.5 route-map rmap-azure-asns out
  neighbor 169.254.0.1 next-hop-self
  neighbor 169.254.0.1 soft-reconfiguration inbound
  neighbor 169.254.0.1 prefix-list vm-hub00 out
 exit-address-family
exit
!
ip prefix-list vm-hub00 seq 5 deny 10.10.0.0/16
ip prefix-list vm-hub00 seq 10 permit any
ip prefix-list rs-hub10 seq 5 deny 10.10.0.0/16
ip prefix-list rs-hub10 seq 10 deny 10.110.0.0/16
ip prefix-list rs-hub10 seq 15 permit any
!
bgp as-path access-list azure-asns seq 5 permit _65515_
bgp as-path access-list bogon-asns seq 5 permit _0_
bgp as-path access-list bogon-asns seq 10 permit _23456_
bgp as-path access-list bogon-asns seq 15 permit _1310[0-6][0-9]_|_13107[0-1]_
bgp as-path access-list bogon-asns seq 20 deny _65515_
bgp as-path access-list bogon-asns seq 25 permit ^65
!
route-map rmap-bogon-asns deny 5
 match as-path bogon-asns
exit
!
route-map rmap-bogon-asns permit 10
exit
!
route-map rmap-azure-asns permit 5
 match as-path azure-asns
 set as-path replace any
exit
!
route-map rmap-azure-asns deny 10
exit
!
end

動作確認 (microsoft/ethr)

以上の設定が完了すれば、左上と左下の VNet の間で通信が可能になっているはずです。
今回の main.bicep では 10.100.0.0/24 と 10.110.0.0/24 に該当します。

お気に入りの microsoft/ethr で TCP レベルの traceroute (宛先は SSH のポート = 22/tcp) を試します。
間に入っている ??? は Virtual network gateway とかだと思いますが、経路がないんだか ICMP Time Exceeded を送ってくれないんだかでとりあえず見えません。

ikko@vm-hub100:~$ sudo ./ethr -c 10.110.0.4 -t tcp --port 22 -t tr

Ethr: Comprehensive Network Performance Measurement Tool (Version: v1.0.0)
Maintainer: Pankaj Garg (ipankajg @ LinkedIn | GitHub | Gmail | Twitter)

Using destination: 10.110.0.4, ip: 10.110.0.4, port: 22
Tracing route to 10.110.0.4 over 30 hops:
 1.|--???
 2.|--10.0.0.4 []                                                            109.137ms
 3.|--???
 4.|--???
 5.|--10.110.0.4 []                                                          218.825ms
Ethr done, measurement complete.

逆方向ももちろん通ります。

ikko@vm-hub110:~$ sudo ./ethr -c 10.100.0.4 -p tcp --port 22 -t tr

Ethr: Comprehensive Network Performance Measurement Tool (Version: v1.0.0)
Maintainer: Pankaj Garg (ipankajg @ LinkedIn | GitHub | Gmail | Twitter)

Using destination: 10.100.0.4, ip: 10.100.0.4, port: 22
Tracing route to 10.100.0.4 over 30 hops:
 1.|--???
 2.|--10.10.0.4 []                                                           110.862ms
 3.|--???
 4.|--???
 5.|--10.100.0.4 []                                                          226.166ms
Ethr done, measurement complete.

動作確認 (tcpdump)

NVA にあたる vm-hub00 (中央上の VNet にあたります) で tcpdump を仕掛けてみます。
まずは port 22 and host 10.100.0.4 で見てみます。

root@vm-hub00:~# tcpdump -nni eth0 port 22 and host 10.100.0.4
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:52:53.056601 IP 10.100.0.4.8890 > 10.110.0.4.22: Flags [S], seq 2349978517, win 64240, options [mss 1376,sackOK,TS val 1386299511 ecr 0,nop,wscale 7], length 0
16:52:53.172351 IP 10.100.0.4.8891 > 10.110.0.4.22: Flags [S], seq 3804810079, win 64240, options [mss 1376,sackOK,TS val 1386299630 ecr 0,nop,wscale 7], length 0
16:52:55.177477 IP 10.100.0.4.8892 > 10.110.0.4.22: Flags [S], seq 892564222, win 64240, options [mss 1376,sackOK,TS val 1386301631 ecr 0,nop,wscale 7], length 0
16:52:57.174257 IP 10.100.0.4.8893 > 10.110.0.4.22: Flags [S], seq 365349981, win 64240, options [mss 1376,sackOK,TS val 1386303632 ecr 0,nop,wscale 7], length 0
16:52:57.286751 IP 10.110.0.4.22 > 10.100.0.4.8893: Flags [S.], seq 1119812311, ack 365349982, win 65160, options [mss 1376,sackOK,TS val 3924610454 ecr 1386303632,nop,wscale 7], length 0
16:52:57.392118 IP 10.100.0.4.8893 > 10.110.0.4.22: Flags [.], ack 1, win 502, options [nop,nop,TS val 1386303850 ecr 3924610454], length 0
16:52:57.392118 IP 10.100.0.4.8893 > 10.110.0.4.22: Flags [R.], seq 1, ack 1, win 502, options [nop,nop,TS val 1386303851 ecr 3924610454], length 0

port 4789 だと VXLAN のトンネルの内容が見れます。
ちょっと見づらいかもしれませんが、SSH とか ARP などの VXLAN の中でしゃべっている内容もちゃんと表示されますね、すごい。
VXLAN の世界では普通に 10.0.0.4 と 10.10.0.4 の間でパケットをやり取りしているだけ、というのも確認できます。

root@vm-hub00:~# tcpdump -nni eth0 port 4789
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:54:54.186454 IP 10.0.0.4.45068 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 10.100.0.4.8891 > 10.110.0.4.22: Flags [S], seq 1400684226, win 64240, options [mss 1376,sackOK,TS val 1386420644 ecr 0,nop,wscale 7], length 0
16:54:54.187906 IP 10.10.0.4.49610 > 10.0.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 169.254.0.2 > 10.100.0.4: ICMP time exceeded in-transit, length 68
16:54:56.191046 IP 10.0.0.4.53467 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 10.100.0.4.8892 > 10.110.0.4.22: Flags [S], seq 2783408859, win 64240, options [mss 1376,sackOK,TS val 1386422645 ecr 0,nop,wscale 7], length 0
16:54:58.188230 IP 10.0.0.4.51090 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 10.100.0.4.8893 > 10.110.0.4.22: Flags [S], seq 2256194720, win 64240, options [mss 1376,sackOK,TS val 1386424646 ecr 0,nop,wscale 7], length 0
16:54:58.300305 IP 10.10.0.4.47245 > 10.0.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 10.110.0.4.22 > 10.100.0.4.8893: Flags [S.], seq 3010654816, ack 2256194721, win 65160, options [mss 1376,sackOK,TS val 3924731468 ecr 1386424646,nop,wscale 7], length 0
16:54:58.405096 IP 10.0.0.4.51090 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 10.100.0.4.8893 > 10.110.0.4.22: Flags [.], ack 1, win 502, options [nop,nop,TS val 1386424864 ecr 3924731468], length 0
16:54:58.405120 IP 10.0.0.4.51090 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 10.100.0.4.8893 > 10.110.0.4.22: Flags [R.], seq 1, ack 1, win 502, options [nop,nop,TS val 1386424864 ecr 3924731468], length 0
16:54:59.220729 IP 10.0.0.4.34769 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
ARP, Request who-has 169.254.0.2 tell 169.254.0.1, length 28
16:54:59.222175 IP 10.10.0.4.48365 > 10.0.0.4.4789: VXLAN, flags [I] (0x08), vni 77
ARP, Reply 169.254.0.2 is-at 7e:ce:db:b2:9f:00, length 28
16:54:59.285062 IP 10.10.0.4.48365 > 10.0.0.4.4789: VXLAN, flags [I] (0x08), vni 77
ARP, Request who-has 169.254.0.1 tell 169.254.0.2, length 28
16:54:59.285134 IP 10.0.0.4.34769 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
ARP, Reply 169.254.0.1 is-at 66:fb:f6:dc:2d:03, length 28
16:55:03.871583 IP 10.10.0.4.34442 > 10.0.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 169.254.0.2.36366 > 169.254.0.1.179: Flags [P.], seq 1704704179:1704704198, ack 2112209324, win 507, options [nop,nop,TS val 3231400303 ecr 1129369912], length 19: BGP
16:55:03.871858 IP 10.0.0.4.42559 > 10.10.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 169.254.0.1.179 > 169.254.0.2.36366: Flags [P.], seq 1:20, ack 19, win 502, options [nop,nop,TS val 1129429912 ecr 3231400303], length 19: BGP
16:55:03.872991 IP 10.10.0.4.34442 > 10.0.0.4.4789: VXLAN, flags [I] (0x08), vni 77
IP 169.254.0.2.36366 > 169.254.0.1.179: Flags [.], ack 20, win 507, options [nop,nop,TS val 3231400304 ecr 1129429912], length 0

動作確認 (Effective routes)

左上の Azure VM (10.100.0.0/16 にあたります) での Effective routes は以下のとおりです。
ExpressRoute 経由で 10.0.0.0/16 (中央上の VNet) と 10.110.0.0/16 (左下の VNet) のアドレス空間が聞こえてきています。
10.110.0.4 宛ては Next Hop が Virtual network gateway となっており、これは ExpressRoute 経由ということと考えられます。

Source State Address Prefixes Next Hop Type Next Hop IP Address User Defined Route Name
Default Active 10.100.0.0/16 Virtual network - -
Virtual network gateway Active 10.0.0.0/16 Virtual network gateway 10.2.146.76 -
Virtual network gateway Active 10.0.0.0/16 Virtual network gateway 10.2.146.77 -
Virtual network gateway Active 10.110.0.0/16 Virtual network gateway 10.2.146.76 -
Virtual network gateway Active 10.110.0.0/16 Virtual network gateway 10.2.146.77 -

中央上の VNet (10.0.0.0/16) にある NVA における Effective routes は以下のとおりです。
ほんとにマジで大事なことなんですが、ここでは 10.100.0.0/16 (左下の VNet) の Next Hop IP Address は 10.0.0.4 (中央上の VNet) に向いています。

Source State Address Prefixes Next Hop Type Next Hop IP Address User Defined Route Name
Default Active 10.0.0.0/16 Virtual network - -
Default Active 10.10.0.0/16 VNet peering - -
Virtual network gateway Active 10.100.0.0/16 Virtual network gateway 10.2.146.77 -
Virtual network gateway Active 10.100.0.0/16 Virtual network gateway 10.2.146.76 -
Virtual network gateway Active 10.110.0.0/16 Virtual network gateway 10.0.0.4 -

これだけをみるとパケットがループするように見えます。

  • 左上 (10.100.0.4) から左下 (10.110.0.4) に向かってパケットを飛ばしたい
  • 10.100.0.4 の Effective route を見ると、Virtual network gateway に送ればよさそう (ExpressRoute を経由して中央上の VNet にくる)
  • 中央上の VNet にパケットが入ってきて、10.110.0.4 宛てのパケットは 10.0.0.4 (中央上の NVA) に送ればいい
  • NVA の Effective routes を見るに、10.110.0.4 あてのパケットは 10.0.0.4 に再度送ることになる (ループする)

ここで重要なのが NVA で VXLAN でトンネルを張っていることです。
中央上の NVA の FRRouting での show ip route の結果は以下のとおりです。

vm-hub00# show 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

K>* 0.0.0.0/0 [0/100] via 10.0.0.1, eth0, src 10.0.0.4, 00:36:35
B>  10.0.0.0/16 [20/0] via 10.0.210.4 (recursive), weight 1, 00:23:27
  *                      via 10.0.0.1, eth0, weight 1, 00:23:27
                       via 10.0.210.5 (recursive), weight 1, 00:23:27
                         via 10.0.0.1, eth0, weight 1, 00:23:27
C>* 10.0.0.0/24 is directly connected, eth0, 00:36:35
S>* 10.0.210.0/24 [1/0] via 10.0.0.1, eth0, weight 1, 00:36:35
B>  10.100.0.0/16 [20/0] via 10.0.210.4 (recursive), weight 1, 00:08:26
  *                        via 10.0.0.1, eth0, weight 1, 00:08:26
                         via 10.0.210.5 (recursive), weight 1, 00:08:26
                           via 10.0.0.1, eth0, weight 1, 00:08:26
B>* 10.110.0.0/16 [200/0] via 169.254.0.2, vxlan0, weight 1, 00:08:25
K>* 168.63.129.16/32 [0/100] via 10.0.0.1, eth0, src 10.0.0.4, 00:36:35
C>* 169.254.0.0/24 is directly connected, vxlan0, 00:36:35
K>* 169.254.169.254/32 [0/100] via 10.0.0.1, eth0, src 10.0.0.4, 00:36:35

OS 側に戻って ip route を叩いても大体同じような結果が見えてきます。

# ip route
default via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.4
10.0.0.0/16 nhid 19 via 10.0.0.1 dev eth0 proto bgp metric 20
10.0.210.0/24 nhid 10 via 10.0.0.1 dev eth0 proto static metric 20
10.100.0.0/16 nhid 19 via 10.0.0.1 dev eth0 proto bgp metric 20
10.110.0.0/16 nhid 25 via 169.254.0.2 dev vxlan0 proto bgp metric 20
168.63.129.16 via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100
169.254.0.0/24 dev vxlan0 proto kernel scope link src 169.254.0.1
169.254.169.254 via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100

これを見ると、10.110.0.0/16 の next-hop は 169.254.0.2、つまり VXLAN になっています。
つまり、NVA に入ってきた 10.110.0.4 宛てのパケットは、VXLAN にトンネルされ、Azure 側ではいったん宛先 10.10.0.4 宛てのパケットして見えるようになります。

  • 左上 (10.100.0.4) から左下 (10.110.0.4) に向かってパケットを飛ばしたい
  • 10.100.0.4 の Effective route を見ると、Virtual network gateway に送ればよさそう (ExpressRoute を経由して中央上の VNet にくる)
  • 中央上の VNet にパケットが入ってきて、10.110.0.4 宛てのパケットは 10.0.0.4 (中央上の NVA) に送ればいい
  • NVA の内部で VXLAN にカプセル化され、Azure の世界上ではいったん 10.10.0.4 宛てのパケットに見える (VNet Peering で送れる)
  • もう片方の NVA でカプセル化をほどき、そのあとは Azure 側に任せてパケットを転送する

中央下 (10.10.0.0/16) のほうの NVA での Effective routes は以下のとおりです。
2 行出ていないのがややおかしい気がしますが、10.110.0.0/16 は ExpressRoute 経由となっています。
これにより、結果として ExpressRoute 経由で中央下の VNet から左下の VNet へとパケットが運ばれていきます。

Source State Address Prefixes Next Hop Type Next Hop IP Address User Defined Route Name
Default Active 10.10.0.0/16 Virtual network - -
Default Active 10.0.0.0/16 VNet peering - -
Virtual network gateway Active 10.110.0.0/16 Virtual network gateway 10.2.146.62 -
Virtual network gateway Active 10.100.0.0/16 Virtual network gateway 10.10.0.4 -

ぽすとふぇーす

ずーーーーーーーーっと VXLAN の検証をやりたかったんですがやっとできました。
Ubuntu Server でやる分にはかなりシンプルな設定でした。
ただ、それを永続化させる部分や FRRouting の設定にはかなりてこずりました。

あとはこれを Cisco でできないか考えたいところです。
また、本格的に考える場合には NVA の冗長化も考えるため、NVA を 2 台構成にして前に Load Balancer を置いて、、NEXT_HOP 属性を使いつつ、という感じになりそうですね。

参考

  • そういえば Hub-Spoke が 2 つあるときの構成例も参考になるかも

https://zenn.dev/microsoft/articles/multiple-hub-spoke-designs


Update log

  • 全体を # (見出し1) から ## (見出し2) に変更 - 2023/12/18
  • 参考リンクの追加 - 2023/12/18
脚注
  1. 今回の検証の中で今まで書いてた config あまりよくないのに気付いたので後で書きなおしたい ↩︎

Microsoft (有志)

Discussion