背景・狙い
BGP の Local Preference、MED、AS_PATH prepend といった経路制御の属性は、仕様を読めば「どちらの経路が選ばれるか」は分かります。ただ、実際に設定を入れて経路やトラフィックがどう変わるかを自分の手で確かめないと、なかなか腹落ちしませんでした。
そこで、AWS Site-to-Site VPN の BGP 相手を自分で立てた仮想ルーターにして、次のことを手元で試せる環境を作りました。(作成には多いに生成AIの支援を受けました・・・)
- BGP で広報した経路が、AWS 側のルートテーブルにどう反映されるか
- prefix-list で広報を絞ると何が起きるか
- Local Preference / AS_PATH prepend / MED で、行きと戻りの経路がそれぞれどう変わるか
物理機器を用意しなくても、terraform apply で作って、試し終わったら terraform destroy で消せるようにしています。(短時間の検証であれば、お金はほとんどかかりません)
コードは GitHub で公開しています。
構成図
一般的な書き方ではありませんが、ルートテーブルにつながる部分や模擬セグメント向けには点線で経路イメージを記載しています。
VGW (AS64512) ══ IPsec tunnel1/2 + eBGP ══ onpre-edge (AS65000) ══ GRE + eBGP ══ onpre-core (AS65001) ── onpre-sv
└ dummy IF: 模擬 LAN セグメント
- cloud(us-east-1):Virtual Private Gateway(VGW)とテスト用サーバ(cloud-sv)。cloud のルートテーブルは VGW のルート伝播だけにしてあり、onpre から BGP で広報した経路がそのまま現れます。
-
onpre(us-east-2):オンプレミスに見立てた VPC です。
- onpre-edge:VPN を終端するルーター(Customer Gateway)。VGW と2本のトンネルそれぞれで eBGP を張ります。
- onpre-core:LAN 側のルーター。dummy インターフェースで模擬 LAN セグメント(172.16.10.0/24、172.16.20.0/24、192.168.100.0/24)を持ち、edge に広報します。
- onpre-sv:LAN 上のホストです。
- edge と core は GRE トンネルで結び、その上で eBGP を張っています。VPC のルートテーブルは BGP に従わないので、VPC のルーティングを経由しない経路を作る必要があるためです。
アドレスと AS 番号
| 項目 | 値 |
|---|---|
| cloud VPC | 10.1.0.0/16 |
| onpre VPC | 10.2.0.0/16(edge サブネット 10.2.0.0/24、lan サブネット 10.2.1.0/24) |
| edge / core / sv | 10.2.0.10 / 10.2.1.20 / 10.2.1.10 |
| GRE(edge⇔core) | 10.255.0.0/30 |
| AS 番号 | VGW 64512 / edge 65000 / core 65001 |
ソフトウェア構成
| 分類 | ソフトウェア | 用途 |
|---|---|---|
| IaC | Terraform 1.16(1.3 以上で動作)、AWS Provider 5.x | AWS リソースの構築と、ルーター設定ファイルの生成 |
| OS | Ubuntu 22.04 LTS(全インスタンス) | |
| IPsec | strongSwan 5.9.5 | VGW との IPsec(ルートベース VPN / VTI) |
| ルーティング | FRRouting 8.1(Ubuntu 標準パッケージ) | bgpd / staticd / zebra |
| ネットワーク設定 | systemd-networkd | GRE と dummy IF を永続化 |
| 操作端末 | Windows + PowerShell 7、OpenSSH、Windows Terminal | 設定の転送・反映と、各サーバへのログイン |
IPsec は IKEv2 / AES256 / SHA2-256 / DH Group 14 で、AWS 側の VPN 設定にも同じ暗号スイートを明示しています。
工夫した点
1. ルーターの設定ファイルを Terraform の templatefile で生成する
VPN の設定で面倒なのは、トンネルの外部 IP、内側の IP(169.254.x.x/30)、事前共有鍵(PSK)など、AWS 側で VPN を作るまで決まらない値をルーターの設定に書き写す作業です。AWS からダウンロードできる設定ファイルを手で編集してもよいのですが、作り直すたびに値が変わるので手作業は避けたいところです。
そこで、aws_vpn_connection リソースの属性(tunnel1_address、tunnel1_vgw_inside_address、tunnel1_preshared_key など)をそのままテンプレートに渡し、strongSwan と FRR の設定ファイルを Terraform で生成するようにしました。
locals {
vpn = aws_vpn_connection.cloud_onpre_vpn
vpn_tunnels = [
{
name = "cloud-t1"
outside_address = local.vpn.tunnel1_address
vgw_inside_address = local.vpn.tunnel1_vgw_inside_address
# ...
},
# tunnel2 も同様
]
}
resource "local_file" "edge_frr_conf" {
filename = "${local.generated_dir}/edge/frr.conf"
content = templatefile("${path.module}/templates/frr-edge.conf.tftpl", {
edge_asn = var.edge_asn
tunnels = local.edge_bgp_tunnels
to_aws_prefix_list = var.to_aws_prefix_list
# ...
})
}
テンプレートの中では、トンネルの数だけネイバーや route-map を展開します。
%{ for t in tunnels ~}
neighbor ${t.vgw_inside_address} remote-as ${amazon_asn}
neighbor ${t.vgw_inside_address} description ${t.name}
%{ endfor ~}
経路制御の実験も同じ仕組みに乗せました。terraform.tfvars にトンネルごとの属性を書くと、edge の route-map に反映されます。
tunnel_policy = {
tunnel1 = { local_preference = 200 } # 行き(onpre → AWS)は tunnel1 から出す
tunnel2 = { med = 101 } # 戻り(AWS → onpre)は tunnel1 を選ばせる
}
route-map RM-FROM-AWS-T1 permit 10
set local-preference 200
!
route-map RM-TO-AWS-T2 permit 10
match ip address prefix-list PL-TO-AWS
set metric 101
生成したファイルは、PowerShell のスクリプトで各ルーターへ scp し、ルーター上の反映スクリプトで配置・再起動します。
terraform apply
.\scripts\deploy.ps1 -Target edge
PSK はコードに書かず、AWS に自動生成させた値を Terraform の state から取り出しています。生成したファイルを置く generated/ は git の管理外にしています。
2. 大まかな設計で気をつけたこと
-
送信元アドレス:VTI や GRE を経由する経路をそのまま使うと、ルーター自身が送るパケットの送信元が 169.254.x.x などの内側アドレスになってしまいます。FRR の
ip protocol bgp route-mapでset srcを指定し、送信元をルーターの実 IP にしています。 - GRE のループ防止:edge と core は互いのサブネットを BGP で学習するので、GRE の相手の実 IP まで GRE 経由になってループします。相手の実 IP(/32)だけを静的ルートで VPC に向けています。
-
ログインの手間:確認のためにログインするサーバが4台あるので、Terraform で
ssh_configを生成し、Windows Terminal で全サーバのタブを一度に開くスクリプトを用意しました。
検証方法と結果
1. BGP の確立とルート広報
edge 上で BGP の状態を確認します。
$ sudo vtysh -c 'show bgp summary'
Neighbor V AS ... Up/Down State/PfxRcd PfxSnt Desc
169.254.x.x 4 64512 ... 00:00:22 1 4 cloud-t2
169.254.y.y 4 64512 ... 00:00:19 1 4 cloud-t1
10.255.0.2 4 65001 ... 00:00:31 4 6 onpre-core
VGW とは2本とも、core とも BGP が確立しています。AWS からは 10.1.0.0/16 を1経路受信し、AWS へは4経路を送信しています。
2. 送信フィルタ(prefix-list)
core は 10.2.1.0/24 と模擬 LAN の3つを広報していますが、edge では次の prefix-list で AWS に送る経路を絞っています。
ip prefix-list PL-TO-AWS seq 10 permit 10.2.0.0/16 le 24
ip prefix-list PL-TO-AWS seq 20 permit 172.16.0.0/16 le 24
cloud 側のルートテーブルを確認すると、伝播ルート(EnableVgwRoutePropagation)は次の4経路で、192.168.100.0/24 だけが届いていないことが分かります。
| 宛先 | ターゲット | Origin |
|---|---|---|
| 10.2.0.0/24 | vgw-xxxx | EnableVgwRoutePropagation |
| 10.2.1.0/24 | vgw-xxxx | EnableVgwRoutePropagation |
| 172.16.10.0/24 | vgw-xxxx | EnableVgwRoutePropagation |
| 172.16.20.0/24 | vgw-xxxx | EnableVgwRoutePropagation |
core の dummy IF を送信元にして ping すると、結果もそのとおりになります。
$ ping -c 2 -I 172.16.10.1 <cloud-sv> # 2 packets transmitted, 2 received
$ ping -c 2 -I 192.168.100.1 <cloud-sv> # 2 packets transmitted, 0 received
3. Local Preference(行きの経路)
tunnel1 = { local_preference = 200 } を設定すると、edge が AWS から受信した 10.1.0.0/16 のうち、tunnel1 側の経路が best になります。
$ sudo vtysh -c 'show ip bgp 10.1.0.0/16'
Paths: (2 available, best #1, table default)
64512
169.254.y.y from 169.254.y.y
Origin IGP, metric 100, localpref 200, valid, external, best (Local Pref)
64512
169.254.x.x from 169.254.x.x
...
Local Preference は自分の AS の中だけで使う属性で、eBGP の相手(AWS)には伝わりません。そのため、これで制御できるのは onpre → AWS の行きの経路だけです。
4. MED(戻りの経路)
戻り(AWS → onpre)の経路を制御するには、AWS へ送る経路に属性を付けます。tunnel2 = { med = 101 } を設定すると、tunnel2 へ送る経路にだけ MED 101 が付きます。
$ sudo vtysh -c 'show ip bgp neighbors <tunnel2 の VGW> advertised-routes'
Network Next Hop Metric LocPrf Weight Path
*> 10.2.1.0/24 0.0.0.0 101 0 65001 i
*> 172.16.10.0/24 0.0.0.0 101 0 65001 i
...
VGW は BGP の属性も、どの経路を選んだかも見せてくれません。そこで、実際のトラフィックがどちらのトンネルを流れるかで確認しました。cloud-sv から onpre-sv へ ping を20回打ち、その前後で edge の各トンネルのパケット数を比べています。
cat /sys/class/net/tunnel{1,2}/statistics/rx_packets
| トンネル | RX(AWS → onpre) | TX(onpre → AWS) |
|---|---|---|
| tunnel1 | +21 | +22 |
| tunnel2 | +1 | +2(BGP の keepalive) |
行き(Local Preference)も戻り(MED)も、tunnel1 を流れていることが確認できました。
AWS は何も設定しなくても tunnel1 を選ぶことがあります。属性の効果だと言い切るには、MED を反対側のトンネルに付け替えて、トラフィックが移ることを確かめる対照実験が必要です(今回は未実施)。
分かったこと
- 行きの経路は自分の側で選ぶので、Local Preference で制御できる
- 戻りの経路は相手(AWS)が選ぶので、AS_PATH prepend か MED で「選んでほしい経路」を伝える必要がある
- VGW の経路選択は「最長一致 → 静的ルート → AS_PATH の短い方 → MED の低い方」の順なので、AS_PATH の長さが違うと MED は比較されない
- BGP コミュニティで AWS 側の Local Preference を変える機能(
7224:7100など)は Direct Connect 専用で、Site-to-Site VPN では使えない
コスト・まとめ
コスト
起動している間、主に次の料金がかかります(us-east-1 / us-east-2 の場合の目安)。
| 項目 | 数量 | 目安(1 時間あたり) |
|---|---|---|
| Site-to-Site VPN 接続 | 1 | 約 0.05 USD |
| パブリック IPv4 アドレス(EIP ×2、cloud-sv) | 3 | 約 0.015 USD |
| EC2(t3.micro ×2、t3.nano ×2) | 4 | 約 0.03 USD |
| EC2 Instance Connect Endpoint | 1 | 無料 |
合計で 1 時間あたり約 0.1 USD です。数時間触って terraform destroy すれば、数十円程度で済みます。消し忘れには注意してください。
まとめ
- AWS Site-to-Site VPN の BGP 相手を FRR で立てると、経路制御の属性を気軽に試せる
- 設定ファイルを Terraform の templatefile で生成すれば、作り直すたびに変わるトンネルの値を手で書き写さずに済む
- Local Preference は行き、AS_PATH / MED は戻り。この違いを、ルーター上の BGP テーブルとトンネルのトラフィック量で確かめられた
