0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KVM仮想マシンにWireGuard VPNサーバーを構築する【iptables落とし穴あり】

0
Posted at

KVM仮想マシンにWireGuard VPNサーバーを構築する【iptables落とし穴あり】

はじめに

自宅サーバーのUbuntu 22.04上にKVM仮想マシンを作り、そこにWireGuard VPNサーバーを構築しました。
外出先からAndroidスマホ・WindowsノートPCで自宅ネットワークに安全にアクセスするのが目的です。

構築中にiptablesのFORWARDチェーンでVPN接続が通らないという落とし穴にハマったので、その調査・解決方法も合わせて記録します。

環境

ホストPC(Ubuntu 22.04)

項目 詳細
OS Ubuntu 22.04.5 LTS
CPU Intel Core i7-9700K(8コア)
RAM 40GB
ネットワーク管理 NetworkManager
物理NIC enp5s0(192.168.0.10/24)

VPNサーバーVM(ubuntu-server)

項目 詳細
VM管理 QEMU/KVM(virsh)
OS Ubuntu Server(Noble)
NIC enp1s0
固定IP 192.168.0.11/24

クライアント

  • Androidスマホ
  • Windowsノート PC

最終的なネットワーク構成

インターネット
      ↓ 51820/UDP
ルーター(192.168.0.1)
      ↓ ポートフォワード → 192.168.0.11(ubuntu-server直接)
br0(192.168.0.10)← ホストOS
      ├── enp5s0(物理NIC・スレーブ)
      └── ubuntu-server(192.168.0.11)← LAN直接参加
                └── WireGuard(wg0: 10.0.0.1)
                      ├── Android  (10.0.0.2)
                      └── Windows  (10.0.0.3)

Step 1: ブリッジ(br0)の作成

KVMのデフォルトNAT構成ではVMがLANに直接参加できません。
ブリッジ(br0)を作成して、VMをLAN上の1台として扱えるようにします。

補足: enp5s0 は筆者の環境でのNIC名です。
ip a コマンドで自分の環境のNIC名を確認してから置き換えてください。

# ブリッジ作成
sudo nmcli con add type bridge ifname br0 con-name br0

# 物理NICをブリッジに参加
sudo nmcli con add type bridge-slave ifname enp5s0 master br0

# IPアドレス設定(enp5s0のIPをbr0に移す)
sudo nmcli con modify br0 ipv4.addresses 192.168.0.10/24
sudo nmcli con modify br0 ipv4.gateway 192.168.0.1
sudo nmcli con modify br0 ipv4.dns 192.168.0.1
sudo nmcli con modify br0 ipv4.method manual

# 旧接続を削除してbr0を有効化(一瞬ネットワーク切断あり)
sudo nmcli con delete "有線接続 1" && sudo nmcli con up br0
sudo nmcli con up bridge-slave-enp5s0

注意: sudo nmcli con delete "有線接続 1" を先に実行してから br0 を有効化することで、IP競合を防げます。順序を逆にすると不安定になります。


Step 2: VMのネットワークをブリッジモードに変更

virsh edit ubuntu-server

以下のように変更します:

<!-- 変更前 -->
<interface type='network'>
  <source network='default'/>

<!-- 変更後 -->
<interface type='bridge'>
  <source bridge='br0'/>

変更を反映:

virsh destroy ubuntu-server && virsh start ubuntu-server

Step 3: VMのIPアドレスを固定

VM内で /etc/netplan/50-cloud-init.yaml を編集します:

network:
  version: 2
  ethernets:
    enp1s0:
      dhcp4: false
      addresses:
        - 192.168.0.11/24
      routes:
        - to: default
          via: 192.168.0.1
      nameservers:
        addresses:
          - 192.168.0.1
          - 8.8.8.8
sudo netplan apply

ルーターのDHCP配布範囲から 192.168.0.11 を除外しておくことも推奨します。


Step 4: WireGuardのインストール

VM内で実行:

sudo apt update && sudo apt install wireguard -y

確認:

wg --version
# wireguard-tools v1.0.20210914

Step 5: 鍵ペアの生成

# サーバー用鍵ペア
wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key
sudo chmod 600 /etc/wireguard/server_private.key

# Android用鍵ペア
wg genkey | tee ~/android_private.key | wg pubkey > ~/android_public.key

# Windows用鍵ペア
wg genkey | tee ~/windows_private.key | wg pubkey > ~/windows_public.key

生成されるファイル:

ファイル 用途 管理
/etc/wireguard/server_private.key サーバー秘密鍵 絶対に外部へ漏らさない
/etc/wireguard/server_public.key サーバー公開鍵 クライアント設定に使用
~/android_private.key Android秘密鍵 クライアント設定に記載
~/android_public.key Android公開鍵 サーバー設定に記載
~/windows_private.key Windows秘密鍵 クライアント設定に記載
~/windows_public.key Windows公開鍵 サーバー設定に記載

Step 6: サーバー設定ファイルの作成

/etc/wireguard/wg0.conf を作成:

[Interface]
PrivateKey = <サーバー秘密鍵(server_private.keyの内容)>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp1s0 -j MASQUERADE

[Peer]
# Android
PublicKey = <android_public.keyの内容>
AllowedIPs = 10.0.0.2/32

[Peer]
# Windows
PublicKey = <windows_public.keyの内容>
AllowedIPs = 10.0.0.3/32

VPNトンネル内のIPアドレス構成:

ノード VPN IP
サーバー(ubuntu-server) 10.0.0.1
Android 10.0.0.2
Windows 10.0.0.3

Step 7: WireGuard起動・自動起動設定

# 起動
sudo wg-quick up wg0

# OS起動時に自動起動
sudo systemctl enable wg-quick@wg0

# 動作確認
sudo wg show
sudo systemctl status wg-quick@wg0

Step 8: ルーターのポート開放

ルーターの管理画面でポートフォワーディングを設定します:

設定項目
プロトコル UDP
外部ポート 51820
転送先IP 192.168.0.11(ubuntu-server)
転送先ポート 51820

Step 9: クライアント設定

各端末にWireGuardアプリをインストールして、以下の設定を行います。

Android

[Interface]
PrivateKey = <android_private.keyの内容>
Address = 10.0.0.2/24
DNS = 192.168.0.1

[Peer]
PublicKey = <server_public.keyの内容>
Endpoint = <DDNSドメインまたはグローバルIP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Windows

[Interface]
PrivateKey = <windows_private.keyの内容>
Address = 10.0.0.3/24
DNS = 192.168.0.1

[Peer]
PublicKey = <server_public.keyの内容>
Endpoint = <DDNSドメインまたはグローバルIP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

AllowedIPs = 0.0.0.0/0 はフルトンネル(全通信をVPN経由)です。
自宅ネットワークのみに限定したい場合は 192.168.0.0/24, 10.0.0.0/24 を指定してください(スプリットトンネル)。


ハマりポイント:iptablesのFORWARDチェーン競合

上記の手順が完了してクライアントからVPN接続を試みましたが、ハンドシェイクが確立されませんでした。

症状

sudo wg show を確認しても latest handshake が表示されない。

調査

# ホストまでパケットが届いているか確認
sudo tcpdump -n -i any udp port 51820
# → enp5s0には届いていた(ルーター・DDNS・ポートフォワーディングは正常)

# VMへ転送されているか確認
sudo tcpdump -n -i br0 udp port 51820
# → 何も届いていなかった

パケットはホストまで届いているのにVMに届いていない。FORWARDチェーンの問題と判断。

sudo iptables -L FORWARD --line-numbers -n
# FORWARDチェーンの状態
1: DOCKER-USER
2: DOCKER-FORWARD
3: LIBVIRT_FWX
4: LIBVIRT_FWI   ← ここで新規接続(NEW)を全てREJECT ★問題箇所
5: LIBVIRT_FWO
6: ACCEPT udp → 192.168.0.11:51820  ← ここに到達できない

LIBVIRT_FWI チェーンの中身を確認:

sudo iptables -L LIBVIRT_FWI -n
ACCEPT  all  --  0.0.0.0/0  192.168.122.0/24  RELATED,ESTABLISHED
REJECT  all  --  0.0.0.0/0  192.168.122.0/24  reject-with icmp-port-unreachable

libvirtd が RELATED,ESTABLISHED のみを通過させ、それ以外を REJECT していました。
WireGuardのハンドシェイク(新規接続 = NEW)がここで弾かれていたのが原因です。

解決策

ACCEPTルールを LIBVIRT_FWX より前に移動します:

# 旧ルール削除
sudo iptables -D FORWARD 6

# LIBVIRT_FWXより前(3番)に挿入
sudo iptables -I FORWARD 3 -p udp -d 192.168.0.11 --dport 51820 -j ACCEPT

# 永続化
sudo apt install netfilter-persistent iptables-persistent -y
sudo netfilter-persistent save

変更後の順序:

1: DOCKER-USER
2: DOCKER-FORWARD
3: ACCEPT udp → 192.168.0.11:51820  ← LIBVIRTより前でACCEPT ✅
4: LIBVIRT_FWX
5: LIBVIRT_FWI
6: LIBVIRT_FWO

確認

sudo wg show
# latest handshake: 17 seconds ago
# transfer: 180 B received, 92 B sent

ハンドシェイク成立・VPN接続確認済み。


おまけ:CPUパススルーで暗号処理を高速化

デフォルトのKVM仮想CPU(qemu64)ではホストCPUのAES-NI・AVX2が使えず、WireGuardの暗号処理が遅くなります。
host-passthrough に変更することでホストCPUの命令セットをそのまま利用できます。

virt-xml ubuntu-server --edit --cpu host-passthrough
virsh destroy ubuntu-server && virsh start ubuntu-server

VM内で確認:

grep -o 'aes\|avx2\|avx' /proc/cpuinfo | sort -u
# → aes / avx / avx2 の3つが有効に
命令 用途
aes AES-NI(AES暗号のハードウェア処理)
avx 256bit SIMD(数値演算の並列処理)
avx2 WireGuardのChaCha20暗号高速化に効く

iperf3による速度計測結果(スプリットトンネル)

項目
送信(クライアント → VPN) 9.65 Mbps
受信(VPNサーバー側) 8.23 Mbps
パケット再送(Retr) 0(ロスなし)

フルトンネルでも問題なく接続・通信できました。


セキュリティ上の注意事項

  • 秘密鍵(*_private.key)は絶対に外部に漏らさない
  • クライアント端末を紛失した場合は、サーバーの wg0.conf から該当Peerの PublicKey 行を削除し、sudo systemctl restart wg-quick@wg0 で即座に無効化する
  • /etc/wireguard/server_private.key には chmod 600 が設定済みであることを確認する
ls -la /etc/wireguard/
# -rw------- 1 root root ... server_private.key  ← 600であること

まとめ

  • KVM仮想マシン上にWireGuardサーバーを構築し、Android・Windowsからの接続に成功
  • ブリッジ(br0)を作成することでVMをLANに直接参加させ、ルーターのポートフォワーディングで外部公開
  • libvirtd が管理するiptablesのFORWARDチェーンとの競合が最大のハマりポイント
  • CPUパススルーでAES-NI/AVX2を有効化し、暗号処理を高速化

WireGuard自体のセットアップは非常にシンプルですが、KVMとlibvirtdの組み合わせ特有のiptables問題に注意が必要です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?