40 KiB
VPNセルフホスト設計相談
User: Soma Nakamura (namamurasub@gmail.com)
Created: 1/19/2026 21:48:40
Updated: 1/20/2026 0:41:25
Exported: 1/20/2026 0:51:17
Link: https://chatgpt.com/c/696e2828-1440-8330-b0b7-14ed22c5f05c
Prompt:
簡単にセルフホスト可能なVPNソフトウェアを作るに当たり、以下のようなものを考えている。
- 普通のクライアント同士は、なるべく直接p2p接続を行う。
- グローバルIPがある場所に「サーバー」を置く。そいつが設定の管理や鍵の管理、TURNっぽいもの(NAT配下から抜けるために)やTURNS(ファイヤウォール突破のため。この場合のみこのサーバーがリレーを行う?)をやったりする。NAT46みたいなことができるようになれば、サーバーはv6グローバルだけあれば良くなるので、更における場所が広がる。また、これをexit nodeにするような設定もできると良い。あと、このサーバー上の設定として、各クライアントに名前をつけてDNS広報などもできるようになると良い。 こういうものを作るにあたり、どういうふうにしたらよいか以下の質問に答えてほしい。
- 鍵の管理などはtincみたいに分散させるべきか、集中していたほうが(グローバルがある場所にあったほうが)良いか。
- 新しいクライアントの参加時には、どのような手法で行うべきか
- TURNとか、様々なNAT越えの手法があるが、どれを使うとよいか(どんな環境においてもHTTPSさえ通れば絶対に疎通できることが望ましい。)
- 各クライアントがp2p接続する際のプロトコルはwireguardでよいか?適当にやると(フルメッシュにしたいわけで)無数のwireguardクライアントが生えて困りそうだが、多分カスタムのインターフェースを作ってそこに集約するのか?
Response:
Thought for 11m 26s
あなたが考えている構成は、方向性としてはかなり「現代的なMesh VPN(= Control planeは集中、Data planeはP2P、無理ならHTTPSリレー)」で、実装方針も立てやすいです。結論から言うと、
- 鍵“そのもの”(秘密鍵)は各クライアントに保持させる(分散)
- ただし 鍵配布・ACL・名前/DNS・到達性(NAT越えの候補交換)といった“調停”は中央サーバ(集中)
- NAT越えは ICE/STUN/TURN の考え方を借り、最終フォールバックは 443/TLS 上のリレー(DERP的)
- P2Pの暗号化・仮想L3は WireGuard が最適。ただし「WireGuard単体」ではなく、それを動的にオーケストレーションする常駐デーモンが要る
…という設計が、要件(セルフホスト容易・P2P優先・HTTPSさえ通れば極力なんとかする)に一番素直です。Tailscale/Headscale系がまさにこの分離で動いていて、「Control planeは鍵(公開鍵)とポリシーを配るだけ、Data planeはWireGuardのメッシュ」という整理が定番になっています。 Tailscale+2GitHub+2
以下、質問に順に答えます。
1. 鍵の管理は分散(tinc的)か集中か
まず「何の鍵をどこで管理するか」を分解した方がよい
Mesh VPNでは鍵が最低でも2種類あります。
- データプレーン鍵(実際にトラフィックを暗号化する鍵。WireGuardなら各ノードの秘密鍵)
- コントロールプレーンの認証・承認(誰がネットワークに参加できて、誰と通信できるか、という権限)
このうち、
- **(1) データプレーンの秘密鍵は“絶対にクライアントから出さない”**のがベストです。
そうすると中央サーバが侵害されても「盗聴」まではされにくくなります(ただし後述の“鍵すり替え”リスクは残る)。Tailscaleも「公開鍵だけ交換し、秘密鍵は端末から出ない」と明言しています。 Tailscale+1 - **(2) 参加管理/ACL/DNS名/経路広告/到達性(候補交換)**は、**集中サーバ(coordination server)**に置く方が「簡単にセルフホスト」要件に合います。 Tailscale+1
「分散(tinc的)」のメリット/デメリット(あなたの要件に照らす)
分散のメリット
- 中央が落ちても(理想的には)参加/鍵配布が継続できる
- 中央を完全に信頼しない設計に寄せられる
分散の現実的デメリット
- そもそも NAT越えの“候補交換/到達性観測”のために結局ランデブーが要る(完全分散は難しい)
- オンボーディングが複雑化(誰が新規ノードを承認するのか)
- 「HTTPSさえ通れば」の最終フォールバックに 443リレーが必要 → 結局“中央サービス”が必要
推奨(結論)
- **鍵管理は「秘密鍵は分散(端末保持)」「公開鍵とポリシー配布は集中(サーバ)」**が最適解です。 Tailscale+1
- さらに“中央を信頼しない”を少しでもやるなら、オプションで
(a) 管理者署名によるノード鍵承認、または (b) 透明性ログ(Key Transparency的) を入れると「サーバ侵害→鍵すり替えMITM」耐性が上がります(実装コストは上がります)。
2. 新しいクライアント参加はどう設計すべきか
要件が「簡単にセルフホスト」なので、現実的に安全で運用しやすいブートストラップに寄せるのが良いです。
推奨フロー:短命の招待トークン(Enrollment / Auth Key)方式
- 管理者がサーバで 招待トークンを発行
- TTL(例: 10分〜24時間)
- 使い捨て or N回まで
- 付与するタグ/グループ(例:
dev,admin,no-exit)
- クライアントは初回起動で
- **デバイス識別鍵(例: Ed25519)**を生成(長期)
- **WireGuard鍵(X25519)**を生成(ローテーション可能)
- クライアントは
https://control.example.comに接続し、招待トークンを提示して登録 - サーバは
- ノードID発行
- VPN内IP(v4 /32 と v6 /128)割当
- ACL適用
- “通信してよい相手の公開鍵一覧 + ルート + DNS情報” を配布(以後は差分Push)
この「中央は公開鍵と設定を配るだけ」の形は、Headscaleの説明(WireGuard公開鍵の交換点、IP割当、ルート広告)とも一致します。 GitHub
追加で入れると強い安全策
- 承認待ちモード:トークンで「仮登録」→ 管理者がUI/CLIで承認したら有効化
- トークンのスコープ:
- 「このトークンで参加した端末は exit node になれない」
- 「このトークンは特定ユーザ/特定タグにのみ」
- サーバ真正性:TLS(Let’s Encrypt等)+ 可能なら 初回にサーバ公開鍵ピン留め(TOFUでも可)
“社内Proxyで証明書差し替え”環境もあるので、その場合の扱い(明示的にCAを追加する等)は設計で決めておく。
3. TURNやNAT越えはどれを使うべきか(HTTPSさえ通れば絶対が理想)
結論:考え方は ICE(STUN+TURN)を採用、実装は「段階的フォールバック」が現実的
- **ICE(RFC 8445)**は「NAT越えのために候補(アドレス/経路)を集め、疎通チェックして最適経路を選ぶ」枠組みです。ICEはSTUNとTURNを利用する、とRFC自体が書いています。 IETF Datatracker+2IETF Datatracker+2
- STUN(RFC 8489)は、NAT越しに「外から見える自分のIP:port」を知ったり、NATバインディング維持のkeepaliveにも使えます。 IETF Datatracker+1
- TURN(RFC 5766 / 更新版RFC 8656)は、どうしても直接が無理なときに **中継(リレー)**を提供します。 RFC Editor+1
ただし「VPN用途」だと TURNフル実装が重い
TURNは汎用中継として標準ですが、VPNの“データプレーン”のためにTURNサーバ/クライアントを完全実装するのは工数が増えがちです。
この場合、現実解としては2つあります:
A) 標準準拠でいく:STUN + TURN(必要ならTCP拡張)
- UDPが通る: STUNで外部アドレス取得 → UDP hole punching
- UDPが塞がれる: TURN over TCP/TLS を検討
TURNのTCP割当拡張はRFC 6062が定義しています。 IETF Datatracker+1
B) “HTTPSさえ通れば”を最優先:DERP的な「443/TLSリレー」を自作(おすすめ)
「HTTPS(443/TCP)が通ればとにかく繋ぐ」を狙うなら、TURNより **DERP(TCP/TLSで暗号化済みパケットを盲目的に転送するリレー)**の方が設計が単純です。
- Tailscaleは「DERPはTLS上(多くは443)で動くTCPリレーで、直接繋がらないときに暗号化済みパケットを転送する」と説明しています。 Tailscale+2Tailscale+2
- そしてファイアウォール観点でも「制御サーバ/DERPリレーはHTTPSで443を使う」とドキュメントにあります。 Tailscale
- HeadscaleのDERP解説も、DERP relayが HTTPS tcp/443、STUNが udp/3478 という“最低限ポート”の切り方になっています。 headscale.net
あなたの要件に寄せた “段階的フォールバック” のおすすめ順はこうです:
- IPv6直(両者がグローバルv6なら最強。NAT越え問題が激減)
- UDP直(v4/v6)
- STUNで各自の外部アドレスを把握(server-reflexive candidate)
- rendezvous(あなたの中央サーバ)で候補交換
- 同時送信でhole punching(WireGuardのハンドシェイク自体を“疎通パケット”にしても良い)
- (任意)UPnP/PCP/NAT-PMPでポートマッピング要求(家ルータでは効くことがあるが期待しすぎない)
- 最後の砦:443/TLSリレー(DERP的)
- WebSocket/HTTP2ストリーミングなど“HTTPSっぽい見え方”にして通りやすくする
「HTTPSさえ通れば絶対」についての注意(設計上の現実)
“絶対”は難しいです。理由は、ネットワークによっては
- HTTPプロキシ必須(CONNECTが要る、認証が要る)
- TLSインスペクション(証明書差し替え)
- SNI/宛先制限(特定ドメイン以外禁止)
- キャプティブポータル
などがあるためです。
ただし、**「443/TCPで外向きTLSが通る」**という前提が満たされる範囲では、DERP的リレーは非常に強いアプローチです。 Tailscale+1
4. P2PのプロトコルはWireGuardでよいか?フルメッシュで無数に増えないか?
結論:WireGuardで良い。増えるのは「インターフェース」ではなく「peerエントリ」
WireGuardは「インターフェース(例: wg0)1つ」に対して、複数peerをぶら下げるモデルです。
WireGuard公式のQuick Startでもインターフェースを作ってIPを付ける流れが説明されています。 WireGuard
またWireGuardはNoiseフレームワーク等の現代的暗号を使う、という説明とホワイトペーパーもあります。 WireGuard+1
フルメッシュでの基本設計(典型)
- 各ノードに VPN内アドレスを1つだけ割り当てる
- IPv4: /32(例: 100.64.0.10/32)
- IPv6: /128(例: fd7a:115c:a1e0::10/128)
- 各peerのAllowedIPsは「そのノードの/32(/128)だけ」にする
→ AllowedIPsが重ならないのでルーティングが破綻しない - WireGuardのAllowedIPsは「送信時の宛先IPからpeerを選ぶ/受信時のソースIPをフィルタする」用途を兼ねる、という説明がホワイトペーパーにあります。 WireGuard
この形だと、フルメッシュでも「wg0 1つ + peerがN-1個」です。
ただし WireGuard“単体”ではNAT越え・経路切替ができない
WireGuard自体は「UDPで相手のEndpointへ投げる」だけで、
- STUNして外部アドレスを発見
- 経路が変わったらEndpointを更新
- UDPが無理なら443/TLSに逃がす
みたいなことは“外側のソフト”がやる必要があります。
なのであなたが言っている「カスタムのインターフェースを作って集約するのか?」はだいたい合っていて、実装の形は2択です:
方式A:カーネルWireGuard + 制御デーモン(netlinkでpeer更新)
- パフォーマンス良い
- でも **“TCP/TLSリレーでWireGuardパケットを運ぶ”**の統合が難しくなる(WGは基本UDP)
方式B:userspace WireGuard(wireguard-go相当)を内蔵し、送受信ソケットを自前で差し替える(おすすめ)
- 「UDP直」と「443/TLSリレー」を **同一の“パケット送受信抽象”**で切り替えやすい
- Tailscaleの
magicsockはまさに「通信経路を動的に変えられるソケット」を提供する、という説明があります。 Go Packages+1 - 443/TLSリレーは「WireGuardで暗号化済みのデータをリレーが盲目的に転送する」形にしやすいです。 Tailscale+1
あなたの「HTTPSさえ通れば」要件が強いなら、方式B寄りが実装コストと到達性のバランスが良いと思います。
追加:あなたの「v6グローバルだけでサーバを置きたい(NAT46的)」について
ここは誤解が起きやすいので先に釘を刺します。
- IPv4しかないクライアントは、IPv6-onlyのサーバへは基本的に到達できません(間に“IPv4を受けられる翻訳/プロキシ装置”が必要)。
- NAT46は「IPv4クライアント→IPv6サーバ」を実現する翻訳の一種ですが、翻訳ゲートウェイが“受け口としてIPv4側”を持つ必要があり、サーバが純IPv6だけだとどこか別の場所に翻訳器が要ります。 Palo Alto Networks TechDocs+1
「IPv6だけで置ける場所を増やしたい」という狙いは理解できますが、“誰でもどこでも(IPv4-only含む)”を満たすなら、結局どこかにIPv4の到達点が必要です。
一方で日本だとIPv6普及は高いので、実用上は「v6優先 + v4はリレーで吸う」でも十分成立するケースは多いです(ただし“絶対”は言えない)。
まとめ(あなたの4問への短い指針)
- 鍵管理:
- 秘密鍵は端末保持(分散)
- **公開鍵配布・ACL・設定・名前は中央(集中)**が最も簡単で強い。 Tailscale+2Tailscale+2
- 参加手法:
- 短命/制約付きの招待トークン + 端末生成鍵 +(任意で)管理者承認が運用しやすい。 GitHub
- NAT越え:
- 枠組みは ICE(STUN+TURN) が教科書。 IETF Datatracker+2IETF Datatracker+2
- ただし「HTTPSさえ通れば」重視なら **最終フォールバックは 443/TLSリレー(DERP的)**が現実解。 Tailscale+2Tailscale+2
- P2Pプロトコル:
- WireGuardでOK。インターフェースは基本1つでpeerを増やす。 WireGuard+1
- “経路切替・NAT越え・リレー”のために WireGuardの外側に常駐オーケストレータが必須。 Go Packages+1
必要なら次に、あなたの要件に合わせて **最小MVP(サーバ1台・ポート最小・クライアント実装の部品分割)**を、
- プロトコル境界(Control API、NetMap差分、Relay framing)
- データ構造(ノードID、鍵、ACL、DNS名、経路広告)
- 推奨ポート設計(443/tcp、3478/udp 等)
まで具体化して提案できます。
Prompt:
あと、L2的にするか、L3的にするかという問題(tailscaleのSubnet Routingみたいなものをやるかどうか?)もあります。
Response:
Thought for 13m 4s
L2/L3は「見た目がそれっぽい」以上に、アーキテクチャ全体(到達性・スケール・運用・セキュリティ)を決める分岐なので、ここは最初に方針を固めた方がいいです。結論としては、あなたの要件(セルフホスト容易 / P2P優先 / 最悪はHTTPSで通す)だと、
- 基本は L3(TUN)で作る
- “LANをまるごと繋ぐ”は L3の「Subnet Routing(経路広告)」で実現
- L2(TAP/ブリッジ)は「どうしても必要なユースケースだけ」のオプション(MVPでは避ける)
が一番きれいです。
- まず整理:Subnet Routingは「L2にするか?」とは別問題(L3の拡張)
TailscaleのSubnet routersは、やっていることは L3の経路広告で、L2ブリッジではありません。
- Subnet router側で IP forwardingを有効化して、
--advertise-routes=192.0.2.0/24,...みたいに 到達させたいプレフィックスを広告する(=Control planeに「このサブネットはこのノード経由で届く」と登録)。 Tailscale - 管理コンソール(またはautoApprovers)で その経路を承認して有効化する。 Tailscale
- 複数広告がある場合は Longest Prefix Matchで選ぶ(/16より/24が優先、みたいな)。 Tailscale+1
- デフォルトルート
0.0.0.0/0/::/0を流すなら “exit node” にせよ、という整理。 Tailscale+1
つまり **Subnet Routingを実装するかどうかは「L3でルーティング機能を足すか」**の話で、L2ブリッジ(同一ブロードキャストドメイン化)とは別です。
- L3(TUN)で作る場合の性質:WireGuardと相性が良い
あなたが前に挙げたWireGuardを採るなら、L3ベースは自然です。
- WireGuard自体が Layer 3のトンネルとして設計されている(公式ペーパーのAbstractで明言)。 WireGuard
- Tailscaleも「主としてネットワーク層のソリューションで、WireGuardを使う」と整理しています。 Tailscale
L3(TUN)Mesh + Subnet Routingのメリット
- スケールが楽:各ノード/32(/128)で、ルーティングは「宛先IP→次ホップpeer」になり、ブロードキャストの洪水が起きにくい
- フルメッシュでも管理可能:WireGuard peerは増えるが、基本は
wg01つにpeerをぶら下げる設計で済む(あなたの前提とも整合) - セキュリティ境界が作りやすい:ACLはL3/4(IP/Port)で表現しやすい
- “HTTPSさえ通れば”の最終フォールバック(443/TLSリレー)も、L3パケット単位で運びやすい
※Tailscaleは「UDPが無理ならTCP(HTTPS)で暗号化トラフィックをリレーする」と説明しています。 Tailscale
L3のデメリット(ここが「L2が欲しくなる」ポイント)
- L2ブロードキャスト/マルチキャストに依存する発見系が素直に動かない
例:mDNS/Bonjour、SSDP、NetBIOS Name Service、ゲームのLAN探索、など。
これは(Tailscaleでも)課題として継続的に話題になります。 GitHub+1
- L2(TAP/仮想Ethernet)にすると何が起きるか
L2にする=「全ノードが同一のEthernetセグメントに繋がっている」ように見せるです。
L2のメリット
- DHCP・ARP・mDNS・SSDPなど、L2に依存するプロトコルが“そのまま”動く可能性が高い
- “VPN越しに同一LANっぽく”という体験は作りやすい
ただし、あなたの狙い(簡単にセルフホスト、P2P優先、HTTPSフォールバック)と衝突しやすい
L2はインターネット越しにやると、現実のつらさが出ます。
- ブロードキャスト/ARPの扱い
フルメッシュで“同一セグメント”をやると、結局「ブロードキャストを全peerへ複製」する必要がある
→ ノードが増えるほど雑に増える(O(N²)寄りになりがち)
→ P2P優先のメリットを削りやすい - ループ/ストーム/MTU問題
L2はSTP相当の設計や、フレームの洪水対策をちゃんとやらないと事故りやすい - アドレス空間の衝突が地雷
“同一L2”を複数拠点の既存LANに伸ばすと、同じRFC1918が被って終わる、が典型 - セキュリティ境界が曖昧になりやすい(L2は攻撃面が増えやすい)
参考として、ZeroTierは「Virtual Layer 2をVXLAN的に作る」と説明していますが、これはそれ相応の設計領域を背負う、ということでもあります。 Tailscale
- あなたのプロダクトとしての推奨方針
推奨:L3をコアにして、Subnet Routingを“必須級のオプション”にする
MVPとして一番強いのはこれです。
- 通常ノード:/32(/128)だけを持つ
- Subnet routerノード:
- Exit nodeノード:
Subnet Routing実装でハマる要点(ここを仕様に落とすと良い)
Tailscaleのドキュメントが現実に寄り添っていて、あなたの実装にもそのまま使えます:
- ルータ側は IP forwarding必須、firewallは転送拒否がデフォルトの前提で説明されている Tailscale
- SNATの扱いが超重要
- 「L2っぽさ」が欲しい時の現実的な落としどころ
L2に振り切る前に、L3のまま“困りがちポイントだけ”解決する手があります。
A. 発見系(mDNS/SSDPなど)だけを中継する
- いわゆる mDNSリフレクタ/ゲートウェイ(224.0.0.251)や SSDPプロキシ(239.255.255.250)を、サブネットルータ上で動かす
- VPN全体をL2にせず、必要なプロトコルだけを中継
- セキュリティポリシー(誰がどの発見情報を見ていいか)が書きやすい
B. “名前で解決”に寄せる(DNSの作り込み)
あなたが元々言っていた「名前つけてDNS広報」は、L3モデルの弱点をかなり潰します。
Subnet Routingの文脈でも「内部DNSへのルーティング」やsplit DNSの話が出ます。 Tailscale+1
- それでもL2が必要なら:どう設計するのがマシか
もし「L2がないと成立しない」ユースケースが明確にあるなら、僕ならこうします:
- コアはL3のまま(WireGuard/TUN)
- L2は “L2セグメント”という別機能として提供(デフォルトOFF)
- 例:特定グループだけが参加する“仮想スイッチVNI”を作り、そのVNI参加ノード間だけEthernetフレームを運ぶ
- 実装は VXLAN/Geneve 的な発想(ただしメンバ管理・フラッディング制御・MTU・暗黙のブロードキャスト複製など、考えることは増える)
- さらに、HTTPSフォールバック上でL2を流すなら、TCP-over-TCPの性能劣化やフレーム断片化に注意が必要(ここは設計で割り切る)
これを最初からやると重いので、MVPでは避けるのが無難です。
まとめ:あなたのプロジェクトに対する実装優先度
- L3 Mesh(WireGuard)をコアにする(最も自然・簡単・堅い) WireGuard+1
- Subnet Routing(経路広告+承認+SNAT制御)を実装(“VPNを実用にする”機能) Tailscale+1
- Exit node(デフォルトルート)を実装(必要なら) Tailscale
- それでも足りないときだけ
- 発見系の中継(mDNS/SSDP)
- さらに必要なら L2セグメント(オプション)
必要なら次は、あなたのControl plane設計に合わせて、
- 「経路広告(subnet/exit)をnetmapにどう持つか」
- 「クライアント側でAllowedIPsとOSルートをどう同期するか」
- 「SNATあり/なしの両モードのユーザ体験(設定手順)をどう分岐させるか」
を、擬似コード/データ構造まで落として提案します。
Prompt:
L3にしようと思います。もう一つの問題は、クライアントソフトウェアの設計です。個人的には、一つのクライアントのマシンがいくつかのネットワークに接続できたほうが望ましいと思っている(tailscaleはこれができない!)のですが、どうでしょうか。ただ、問題は適当にやるとIP帯がかぶるところですね。
Response:
Thought for 14m 19s
L3で行くなら、「1台の端末が複数ネットワークに同時参加できる」はかなり価値のある機能です(仕事用・個人用・検証用・顧客環境など)。実際、Tailscaleも“複数アカウントを素早く切り替える”機能はありますが、同時に複数tailnetへパケット送信はできないと明記しています。 Tailscale
(なお組織が複数tailnetを持てる機能も出ていますが、これは主に管理側の話で、クライアントが同時接続できる話とは別です。 Tailscale+1)
あなたが作るなら、同時参加を最初から設計に入れるのはアリです。ただし、言う通り「IP帯がかぶる」問題が出るので、そこを仕様で潰すのがポイントになります。
以下、設計のおすすめを「現実に運用できる線」でまとめます。
- まず分けて考えるべき「かぶるIP」の種類
A. オーバレイ内(VPN端末同士)のIP
これはあなたのVPNが配るアドレスなので、設計でほぼ完全に回避できます。
B. Subnet Routingで流す“向こう側LAN”のIP
こっちは現実世界のLANが 192.168.0.0/16 とかで乱立するので、高確率で衝突します。
同時に2つ以上のネットワークへ参加するなら、衝突が起きたときの挙動が実質“仕様の核”になります。
- 「複数ネットワーク同時参加」を成立させるクライアント設計の基本方針
結論(おすすめ)
- ネットワークごとに“独立したルーティングドメイン”を持つ発想で作る
- ただしOSの通常ルーティングテーブルは“宛先IPだけ”で決めるので、
衝突する経路を“そのまま”同居はできない
→ だから、衝突を避ける(オーバレイ)+衝突したら方針を決める(サブネット) が必要
- 実装アーキテクチャ:一番ラクなのは「マルチインスタンス」
あなたのソフトを「同時に複数ネットワーク参加可能」にする最短ルートはこれです。
方式A:1ネットワーク = 1デーモン(推奨)
clientd --profile netAがtun-netAを作るclientd --profile netBがtun-netBを作る- UI/CLIだけ “1つのクライアント” に見せれば良い
メリット
- 実装が単純(状態・鍵・netmap・ルート注入が分離)
- バグっても片方に閉じ込めやすい
- “このネットワークだけ落とす/再接続” が容易
デメリット
- DNS統合(後述)が少し面倒
- OSごとにTUN複数の扱いが違う(Windows/macOS周りは要検証)
方式B:1デーモンで複数ネットワーク管理
- 中長期ではUXが良いけど、最初は事故りやすい(状態機械が重なる)
MVPは方式Aが堅いです。Tailscaleでも“複数同時接続したい”需要は昔からIssueになっていて、回避として複数デーモンを立てる話が出ます。 GitHub
- オーバレイIPの衝突を「設計で潰す」方法
ここはあなたが自由に決められるので、衝突が起きない割当にしてしまうのが最強です。
推奨:ネットワークIDからプレフィックスを決定する
ネットワークには必ず一意な NetworkID(例:コントロールサーバの公開鍵、またはネットワーク作成時に生成するUUID相当)を持たせて、そこから
- IPv6:ULA /48(RFC 4193の範囲
fd00::/8) IETF Datatracker - IPv4:Shared Address Space
100.64.0.0/10(RFC 6598)から /24 など IETF Datatracker
を決定的(deterministic)に生成します。
例(イメージ)
overlay_v6_prefix = fd00::/8 + H(NetworkID)の先頭40bit → /48overlay_v4_prefix = 100.64.0.0/10 の中から H(NetworkID)で /24 を選ぶ
これにより、別ネットワーク同士でもオーバレイがかぶりにくい(実質“ほぼ起きない”)状態を作れます。
補足:IPv6 ULAは「ローカルで使うがグローバル一意性を期待できる」用途として定義されています。 IETF Datatracker
IPv4の100.64/10はCGN向けの共有アドレス空間としてIETFが予約したブロックです。 IETF Datatracker
ルート注入も衝突しにくくする
- 各ネットワークのオーバレイは そのネットワーク固有のプレフィックスだけを
tun-netXに向ける - 端末同士は /32(/128)でもいいし、プレフィックスまとめても良い(規模次第)
WireGuard自体もL3トンネルとして整理されているので、この方向は素直です。 WireGuard+1
- 本題:Subnet Routingがあると“衝突が必ず起きる”問題
ここは「何を保証するか」を決めないといけません。おすすめは段階的です。
レベル1(推奨デフォルト):衝突したサブネット経路は“入れない”
クライアントが受け取った advertised routes を、OSのルートテーブルへ入れる前に
- 既存ルート(ローカルLAN含む)
- 他のVPNネットワークから入っているルート
と照合して、プレフィックスが重なるものは拒否します。
ユーザ体験としては:
- “netB の
192.168.1.0/24は netA(またはローカル)と衝突するので無効化しました” - どのルートが衝突してるか、解決策(後述)を提示
これで **「同時参加はできるが、衝突サブネットは使えない」**という一貫した挙動になります。
MVPとしては非常に現実的です。
レベル2:手動優先度(メトリクス)でどちらか一方を採用
- 衝突した2つの
192.168.1.0/24のうち、片方だけを採用 - UIで “この経路はnetA優先/netB優先” を選ばせる
ただし、同時に両方は使えません(OSルーティングは宛先IPで決まるため)。
レベル3(上級者向け):サブネットのアドレス変換(route translation / netmap)
「同時に両方の 192.168.1.0/24 を使いたい」を満たす現実解はこれです。
発想
- netBの
192.168.1.0/24を、そのネットワーク内では 仮想プレフィックス172.31.200.0/24にマッピングして提供する - クライアントは
172.31.200.0/24宛に送る - Subnet Router(netB側)が 1:1のプレフィックス変換で
172.31.200.x ⇔ 192.168.1.xを行う
こうすると、
- ローカルLANの
192.168.1.0/24 - netAの
192.168.1.0/24 - netBの(変換後)
172.31.200.0/24
が同時に共存できます。
ただし仕様の重さ
- 変換後のアドレスをユーザにどう見せるか(DNSで変換後を返す、など)まで決める必要がある
- ルータだけでなく、クライアント側も「この経路は変換モード」を理解する必要がある(実装は可能)
MVPではレベル1(拒否)で十分、要望が出たらレベル3を足すのが良いです。
レベル4(Linux限定の最終手段):VRF / network namespace
- OS側でルーティングテーブルを分離して“同じ宛先プレフィックス”を共存させる
- ただし「そのネットワークを使うアプリ」を名前空間に入れる必要が出て、透明性が落ちます
- クロスプラットフォームでは厳しい
- “同時に複数ネットワーク参加”を入れるなら、追加で決めるべき仕様
(a) Exit node(デフォルトルート)は同時に1つ、が基本
複数ネットワークで同時に 0.0.0.0/0 を入れるのは混乱のもとです。
- 「どのネットワークのexitを使うか」を明示選択
- もう一方はexit機能をOFFにする(またはアプリ単位の選択にする)
(b) DNS名の衝突
netAにもnetBにも db みたいな名前があると衝突します。
解決策は素直に
- ネットワークごとにDNSサフィックスを持つ(例:
db.netA.vpn,db.netB.vpn) - OSへはsplit DNS(or ローカルスタブDNSで集約)
が堅いです。
(c) ネットワーク間の“意図しない中継”を防ぐ
同じホストが2ネットワークに入ると、事故でルーティング(転送)してしまう可能性があります。
- デフォルトで IP forwardを有効化しない
- 「サブネットルータ/exit node」は明示的に有効化+承認制
まとめ:あなたの状況へのおすすめ
あなたが「複数ネットワーク同時接続」を売りにしたいなら、こう決めると破綻しにくいです。
- クライアントはマルチインスタンス(1ネットワーク=1デーモン)でMVP
- オーバレイIPは “NetworkIDから決定的に生成”
- IPv6はULA /48(RFC 4193) IETF Datatracker
- IPv4は100.64/10内(RFC 6598) IETF Datatracker
- Subnet Routingは衝突検出して、衝突した経路はデフォルト拒否(レベル1)
- どうしても必要になったら **“サブネット変換(レベル3)”**を追加
もしよければ、次は「衝突検出ロジック」と「ネットワークIDからのプレフィックス生成」の具体案(ビット割り当て、衝突時のリトライ規則、サーバ側のネットワーク作成API仕様、クライアントのルート適用順序)を、実装に落とせる粒度で提案できます。