仙石浩明の日記

2025年6月13日

OpenVPN クライアントをアクティブ・アクティブ構成で使えるようにしてみた

同じ機能を持つ複数の系統を用意しておく冗長化において、 主系統以外を待機させておき、 主系統に障害が起きたとき別の待機系統へ切り替えるのが、 アクティブ・スタンバイ (active-stanby) 構成。 待機系統は、ふだん動いていないわけで、 いざというときに本当に動くのか?という懸念がある。 実際、待機系統への切り替えがうまくいかず、 せっかく冗長化したのに役に立たなかった、 という話も良く聞く。

これに対しアクティブ・アクティブ (active-active) 構成は、 主系統だけでなく全系統を常に動かしておく。 いわば全ての系統が「主系統」。 アクティブ・スタンバイに比べるとシステムが複雑になりがちだが、 全系統の動作確認を常に行っていると言えるわけで、 冗長化方法としてはより望ましい。

私の自宅の LAN では、 昔から LAN とインターネットをつなぐ NAT ルータを冗長化している。 ルータと言いつつ実体は普通の Linux サーバであり、 タグVLAN 間のルーティングを担っている。 普通の Linux サーバなので、 ルータであると同時に DMZ サーバとしても利用できる。 OpenVPN サーバを動かして、 他拠点 LAN と VPN 接続を行っていた。

ところが、 自宅のマンションに対して全戸一括でインターネット接続を提供している 「UCOM光 レジデンス」が、 グローバルIPアドレスを止めてプライベートアドレスに移行するという。 これでは、 せっかくの DMZ サーバが外部からアクセスできなくなってしまう。 もちろんプライベートアドレスであっても外部からアクセスする方法はいくらでもある (例えば ssh トンネル) が、 肝心の OpenVPN サーバはパフォーマンスの観点から直接インターネットから接続できることが望ましい。

幸い、 他の 2拠点で利用しているプロバイダ (インターネット接続サービス) は、 どちらもグローバルIPアドレスを利用できるので、 OpenVPN のクライアント/サーバの関係を逆転させることにした。 つまり自宅のルータ (という名の Linux サーバ) で OpenVPN クライアントを動かし、 他拠点で新たに立ち上げた OpenVPN サーバへ接続する。

自宅には asao, senri, esaka, hattori という 4台の Linux サーバがあり、 いずれも NAT ルータとして機能している。 この全てで OpenVPN クライアントを動かし、 川崎にある拠点の Linux サーバ takatsu へ接続する。 takatsu の OpenVPN サーバの設定ファイルはこんな感じ:

proto udp
dev tun
ca /etc/openvpn/cacert.pem
cert /usr/ssl/certs/kawagw.gcd.org.pem
key /usr/ssl/private/kawagw.gcd.org-nopass.pem
dh /etc/openvpn/dh2048.pem
server 192.168.90.96 255.255.255.224
topology subnet
client-config-dir /etc/openvpn/ccd_kawagw
client-to-client
push "route 192.168.52.0 255.255.254.0"
route 192.168.18.0 255.255.255.0

この設定ファイルに加えて、 自宅ルータ asao から接続するときのための設定ファイル /etc/openvpn/ccd_kawagw/asao.gcd.org を takatsu 上に用意する:

iroute 192.168.18.0 255.255.255.0

つまり自宅LAN のセグメント 192.168.18.0/24 と、 川崎の拠点の LAN のセグメント 192.168.52.0/24 が、 VPN セグメント 192.168.90.96/27 を介して接続される。

いっぽうクライアントの asao の OpenVPN の設定ファイルはこんな感じ:

ca /etc/openvpn/cacert.pem
cert /usr/ssl/certs/asao.gcd.org.pem
key /usr/ssl/private/asao.gcd.org-nopass.pem
client
tls-client
verify-x509-name 'C=JP, ST=Kanagawa, L=Kawasaki, O=GCD, OU=Hiroaki Sengoku, CN=kawagw.gcd.org, emailAddress=sengoku@gcd.org'
nobind
dev tun
remote gcd.tplinkdns.com 41194 udp4
mssfix 1392

takatsu はグローバルではあるが固定アドレスではないので、 ダイナミックDNS を利用している。 takatsu は IPv6 アドレスも持っているが、 自宅側の UCOM光 レジデンスが (いまだに) IPv6 非対応なので、 設定ファイルでプロトコルを IPv4 に限定している。

他の自宅ルータ senri, esaka, hattori についても同様に設定ファイルを作り、 4台の自宅ルータから takatsu の OpenVPN サーバへ接続した。 以上で自宅から川崎の拠点へ VPN 接続できるようになったが、 一つ問題があった。

それは、 川崎側から自宅LAN のサーバへアクセスできないことがある点。 自宅の 4台の OpenVPN クライアントは、 いずれも 「iroute 192.168.18.0 255.255.255.0」 という設定になっている。 4台とも同じ LAN セグメント 192.168.18.0/24 にあるのだから当然だが、 OpenVPN ではこのような使い方を想定していないらしい。 具体的に言うと 4台のうち最後に接続した OpenVPN クライアントの iroute 設定のみが有効になり、 他は無視されてしまう。 例えば esaka が最後だと、

takatsu:/ # cat /var/openvpn/openvpn-status.log
OpenVPN CLIENT LIST
Updated,2025-06-13 02:24:17
Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
esaka.gcd.org,58.4.226.100:59773,49021,53152,2025-06-13 00:06:37
asao.gcd.org,58.4.224.2:53602,1633380,763773,2025-06-13 00:06:37
senri.gcd.org,58.4.226.214:53857,48875,49703,2025-06-13 00:06:31
ROUTING TABLE
Virtual Address,Common Name,Real Address,Last Ref
192.168.18.0/24,esaka.gcd.org,58.4.226.100:59773,2025-06-13 00:06:37
192.168.90.98,senri.gcd.org,58.4.226.214:53857,2025-06-13 00:06:31
192.168.90.100,esaka.gcd.org,58.4.226.100:59773,2025-06-13 00:06:37
192.168.90.99,asao.gcd.org,58.4.224.2:53602,2025-06-13 02:24:14
GLOBAL STATS
Max bcast/mcast queue length,3
END

という感じになり、 VPN 内では 192.168.18.0/24 宛は esaka に送られる。 このとき takatsu から esaka 以外、 例えば asao に対して ping を打つと、 パケット (icmp echo request) は VPN を通って esaka(192.168.90.100) に達し、 VPN を出て esaka(192.168.18.15) から自宅 LAN 192.168.18.0/24 に入り、 asao(192.168.18.9) に届く。

ping パケットを受けた asao でも OpenVPN クライアントが動いているので、 帰り (icmp echo reply) は asao(192.168.90.99) から直接 VPN 192.168.90.96/27 に入って takatsu に戻ってくる。 ちゃんと戻るのだから大丈夫だろうと思っていたのだが、 落とし穴があった。 takatsu の OpenVPN サーバが、 せっかく戻ってきた ping reply パケットを捨ててしまうのだ。 takatsu のログを見ると、

2025-06-13 02:43:04 us=728265 MULTI: Learn: 192.168.18.9 -> esaka.gcd.org/58.4.226.100:59773
2025-06-13 02:43:04 us=734855 asao.gcd.org/58.4.224.2:53602 MULTI: bad source address from client [192.168.18.9], packet dropped

つまり takatsu の OpenVPN サーバは、 192.168.18.9 が esaka の担当だと学習した (Learn) のに、 esaka じゃない asao から届いたのでパケットを捨てたと言っている。 送信元アドレスを偽装したパケットだと疑われたわけだ。

確かに OpenVPN クライアントが、 同じ VPN に属する他のクライアントが担当するパケットを送信してはマズイだろう。 VPN でなく普通の LAN セグメントで言えば、 送信元IPアドレスがセグメントの範囲外であるようなパケットを、 送り出すようなものだからだ。

しかしこれではアクティブ・アクティブ構成を実現できない。 パケットは、どのアクティブな OpenVPN クライアントからも VPN へ入ってくる可能性があるからだ。 そこでクライアント同士が通信できる (client-to-client) 場合に限り、 送信元チェックを無効にするパッチを書いてみた:

diff -ur openvpn-2.6.14.org/src/openvpn/multi.c openvpn-2.6.14/src/openvpn/multi.c
--- openvpn-2.6.14.org/src/openvpn/multi.c	2025-04-02 15:53:10.000000000 +0900
+++ openvpn-2.6.14/src/openvpn/multi.c	2025-06-13 08:56:28.816434691 +0900
@@ -3424,7 +3424,7 @@
                     c->c2.to_tun.len = 0;
                 }
                 /* make sure that source address is associated with this client */
-                else if (multi_get_instance_by_virtual_addr(m, &src, true) != m->pending)
+                else if (!m->enable_c2c && multi_get_instance_by_virtual_addr(m, &src, true) != m->pending)
                 {
                     /* IPv6 link-local address (fe80::xxx)? */
                     if ( (src.type & MR_ADDR_MASK) == MR_ADDR_IPV6
@@ -3491,7 +3491,7 @@
 
                 if (mroute_flags & MROUTE_EXTRACT_SUCCEEDED)
                 {
-                    if (multi_learn_addr(m, m->pending, &src, 0) == m->pending)
+                    if (m->enable_c2c || multi_learn_addr(m, m->pending, &src, 0) == m->pending)
                     {
                         /* check for broadcast */
                         if (m->enable_c2c)

client-to-client の場合 (m->enable_c2c) は、 multi_learn_addr チェックを行わないようにしている。 これで川崎側からも自宅 LAN の任意のホストに対して VPN 経由でアクセスできるようになった。

Filed under: システム構築・運用 — hiroaki_sengoku @ 20:27

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment