背景
複数拠点(A, B, Cなど)を OpenVPN 経由で接続し、 **2系統のVPNトンネル(10.8.0.0/24 と 10.10.0.0/24)**を経由して経路冗長化したい。(openvpnサーバは独立した2マシン) そんなニーズから検証を始めました。
各拠点はそれぞれローカルLAN(例: 192.168.1.0/24, 192.168.2.0/24, 192.168.3.0/24)を持ち、 それらを OSPF (FRRouting) で自動経路学習させて、 VPNトンネル越しに相互通信できるようにしたい、という構成です。
やりたかったこと
- OpenVPNで各拠点を接続(多拠点site-to-site VPN)
- FRR (OSPF) を使って動的ルーティング
- トンネルごとにコストを設定して経路選択
- できれば route-nopull で手動ルーティングを制御 目標は「OpenVPNトンネルを2本走らせて、FRRで経路選択を任せる」ことでした。
OpenVPNの仕組みを理解してぶつかった壁
結論から言うと、 OpenVPNではOSレベルのルーティングテーブルがVPN内部で使われない という根本的な構造問題にぶつかります。
OpenVPNのルーティングの構造
OpenVPNはユーザランドで動作し、/dev/net/tun デバイスを通じて通信します。
- Linuxカーネルはtunデバイスにパケットを出す
- そこから先は**OpenVPNプロセス(ユーザランド)**が受け取り、独自の内部ルーティングテーブルで転送を行う つまり、Linuxのルーティングテーブル(ip route)とは別世界で動いています。
push と route-nopull の関係
OpenVPNサーバはクライアントに対して push “route …” を送ることで、 クライアントのOSにもユーザランドにもルート情報を注入します。
一方、クライアント設定で route-nopull を有効にすると、 その push 情報をすべて拒否します。 これがまずひとつの落とし穴です。
FRR(OSPF)を組み合わせても解決しない理由
FRRでOSPFを走らせると、 VPNトンネル越しに相手拠点のLAN情報(例: 192.168.x.x/24)が学習されます。 これにより、OSレベルのルーティングテーブルは更新されます。
しかし!
OpenVPNはそのOSルートを全く参照しません。 OpenVPNプロセスは独自に「このLANはどのクライアントの背後にあるか」を iroute で知っていなければ、パケットをどこに転送すべきか判断できません。
OpenVPNの根本的な設計思想
これはOpenVPNが「1:多アクセスVPN」を想定して作られたから。
- クライアントは基本的に1台のPC想定(背後LANなし)
- 経路はすべてサーバからpushされる
- クライアントが動的に経路広告する設計ではない
→ だから「クライアント背後のLAN」や「OSPF経由の経路広告」は OpenVPN内部では無視されます。
実際のFRR設定例
log syslog informational
frr version 9.x
frr defaults traditional
hostname site-a
service integrated-vtysh-config
!
interface lan0
ip ospf passive
!
interface tun0
ip ospf network point-to-multipoint
ip ospf cost 10 ! プライマリ(ECMPなら削る or tun1と同コスト)
!
interface tun1
ip ospf network point-to-multipoint
ip ospf cost 20 ! バックアップ(ECMPならtun0と同コスト)
!
router ospf
router-id 2.2.2.2
passive-interface lan0
network 192.168.10.0/24 area 0
network 10.8.0.0/24 area 0
network 10.10.0.0/24 area 0
! maximum-paths 2 ! 負荷分散したいときに有効化
!
line vty
この設定でOSPFは正しく学習・経路交換できるものの、 OpenVPN内部では転送されません。
なぜ通信が失敗するのか(技術的因果)
- sanatorium(192.168.40.0/24)が kong(192.168.10.0/24)宛にパケットを出す
- FRRにより via 10.10.0.3 dev tun1 のルートが選ばれる
- sanatoriumのカーネルがパケットをtun1に投げる
- OpenVPNが受け取るが、「192.168.10.0/24 は誰の背後ネットワークか」を知らないため DROP
→ 結果、通信失敗。
これを解決するための唯一の方法
OpenVPNに「どのLANがどのクライアントの背後にあるか」を明示的に教える必要があります。
サーバ側設定例 /etc/openvpn/server.conf にて:
client-config-dir /etc/openvpn/ccd
client-to-client
topology subnet
route 192.168.10.0 255.255.255.0
route 192.168.40.0 255.255.255.0
しかし、frrを導入する目的が、openvpnサーバから静的にルーティング情報を受け取らなくてもいい感じにルーティング情報を学習してくれることだったので、 これでは本末転倒ですね。😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅
代替解決策:WireGuard + FRR
WireGuardは完全にカーネル内で動作するVPN。 ユーザランドではなくカーネルレベルで暗号化・転送を行うため、 FRRやOSPFがそのまま機能します。
つまり: 「ルーティングはOSがやる、暗号化はWireGuardがやる」 → OpenVPNのような二重構造が存在しない。
動的ルーティングと経路学習をシームレスに使いたい場合、 WireGuard + FRR(OSPF) が現時点で最適解です。
ということで、WireGuard + FRRで、真に冗長化された完璧な俺俺バックボーンネットワークを作りますかね。 インターネットくん、気前よすぎ。
🧾 証拠パック(根拠リンクつき)
🔹 OpenVPN の内部ルーティング(iroute / CCD が必須)
サーバは「どのクライアントの背後にどのサブネットがあるか」を
iroute
で把握する。iroute
は CCD(client-config-dir) 内のクライアント別ファイルで指定し、OpenVPN の内部ルーティングを決める(OS ルーティングとは別)。
→ HowTo: explain CCDs and “iroute” (OpenVPN Forum)サーバ設定
route
と CCD のiroute
はセットで機能し、LAN 間通信(クライアント背後サブネット到達)を成立させる一般手順として各種ドキュメント/ナレッジで示されている。
→ Routing through OpenVPN clients (UI Community)OpenVPN ステータスファイルの
ROUTING_TABLE
は、どのネットワークがどのクライアントに紐づいたかを示す(=サーバ内部ルーティングの実体)。記事中でこれをスクショ/抜粋すると説得力が増す。
→ Mastering OpenVPN (O’Reilly Media)
🔹 client-to-client / topology の意味
--client-to-client
は、サーバが内部でクライアント同士のパケットをルーティングする挙動を有効化するオプション(TUN/TAP に全部押し出すのではなく、OpenVPN デーモン内で処理)。
→ Server Fault: How does client-to-client work?topology subnet
(サブネット型トポロジ)では、TUN デバイスが「ブロードキャスト的な」アドレス/マスクを持ち、単一サブネット内で IP を配布する方式になる(/30 の net30 とは異なる)。
→ OpenVPN Manual: Topology subnet explained
🔹 route-push と route-nopull の挙動
サーバからの
push "route …"
は、通常クライアント側の OS ルーティングにも OpenVPN 内部にも反映される。クライアントは--pull
でそれを受け取る。
一方で--route-nopull
を指定すると「プッシュされたルートを受け取らない」ため、クライアントのルーティングテーブルにも反映されない。
→ OpenVPN Manual (route-nopull Option)route-nopull
はクライアント設定の末尾に置く等の実装注意点がコミュニティでも共有されている(オプションの上書き事故を防ぐ)。
→ OpenVPN Forum Discussion on route-nopull
🔹 「OS のルートは見えても OpenVPN は別宇宙」
TUN デバイスはカーネル側の仮想IFだが、OpenVPN はユーザ空間で動き、TUN/TAP を経由して L3 パケットを読み書きする。
つまり OpenVPN の転送判断はユーザランド内部のテーブルに依存する(だからiroute
が必要)。
→ Medium: Understanding TUN/TAP and OpenVPN Routing実務でも OpenVPN×OSPF/FRR を組み合わせる場合、OpenVPN にも経路(
iroute
)を教える必要があるという知見が共有されている。
OSPF は OpenVPN の内部ルーティングを更新しない。
→ Netgate Forum: OSPF with OpenVPN dynamic routing discussion
→ Open Maniak Blog: FRR OSPF over OpenVPN
🔹 WireGuard を推す根拠(カーネル空間・FRR と親和)
WireGuard は Linux カーネル内で動作する L3 VPN(=暗号化された仮想インターフェイス)。
カーネルルーティングと直結するため、FRR/OSPF の経路がそのまま実効経路になる。
→ WireGuard Official Paper (PDF)公式も「Linux カーネル実装(最大性能)」を明言。必要に応じてユーザ空間実装もあるが、基本はカーネル側で完結。
→ WireGuard Official Website
🔹 記事内で使える “引用しやすい一文” テンプレ
OpenVPN の
iroute
は OS のルーティングではなく、OpenVPN デーモン内部のルーティングを教えるためのもの(CCD でクライアント別に設定)。
→ OpenVPN Forum Discussion on irouteroute-nopull
を入れたクライアントは、サーバからのルート push を受け取らない(OS にも入らない)。
→ OpenVPN Manual: route-nopullclient-to-client
は OpenVPN サーバがクライアント間の転送を内部で処理するスイッチ。
→ Server Fault: client-to-client option explanationtopology subnet
は単一サブネット配布の挙動(net30と違う)。
→ OpenVPN Topology ModesWireGuard はカーネル内実装の L3 トンネルで、ルーティングはそのままカーネル/FRR と整合する。
→ WireGuard Paper (kernel implementation)