OpenVPN Serverをdocker composeで動かす

January 31, 2023

linuxAlpineLinux

目次

はじめに

筆者はリモートから自宅LANに接続するために自宅LAN内にvpnサーバを立てて運用しています。自宅LANがフレッツ光のPPPoE接続の場合は、

この記事の通りSoftether VPNにより容易にL2TP/IPsec方式のvpnサーバを構築することができました。L2TP/IPsec方式はクライアントOSに標準で実装されているので、クライアントの設定も超簡単でした。

自宅LANがIPoE接続の場合は、UDPポート500番と4500番を利用するL2TP/IPsec方式は稼働させることができず、代わりにOpenVPNによりvpnサーバを立ち上げる必要がありました。この顛末は以下の記事にまとめています。

これで大体うまくいったのですが、vpnを介した内線電話がうまく機能していません。フレッツ光のHGW(ホームゲートウェイ)にはひかり電話のSIPサーバが内蔵されています。SIPクライアントを用意すれば固定電話の子機として外線受発信したり、SIPクライアント同士の内線通話が利用できるのですが、これはSIPサーバと同一サブネット内にあるSIPクライアントで可能な機能なのです。

OpenVPNクライアントでvpnサーバに接続した場合、クライアントのネットワークはvpnサーバとは別のサブネットになってしまうため、SIPサーバとの接続がうまくいきません。クライアントのサブネットからSIPサーバのサブネットへルーティングしてやればよいはずなのですが、Softether VPN内蔵のOpenVPNではうまくいきません。出来るのかもしれませんが、やり方を見つけきれていないのが現状です。

そこで、Softether VPNを諦めて素のOpenVPNサーバをdocker composeで立ち上げて、SIPクライアントも利用可能なvpn環境を構築してみようというのが本記事の目的です。

docker composeによるOpenVPNサーバのインストール

以下の記事を参考にしました。

【参考】
https://vild.hatenablog.com/entry/2019/06/02/141153
DockerでOpenVPNサーバを動かす

https://qiita.com/nbhr/items/52da5651e3bfcf2eab60
IPv6環境でひかり電話を外出先から使う(by RPi + WireGuard or OpenVPN)

コンテナ内でカーネルモジュールnf_nat_sipを利用しているため、、母艦とコンテナが同じAlpineLinux3.9で動いていることを前提としています。母艦とコンテナが別のlinuxでは、このままでは意図した動作にならない可能性があります。nf_nat_sipはフレッツ光の内線電話SIPプロトコルをNATを介して動作させるためのものという理解です。

証明書の生成

以下のOpenVPN初期設定用Dockerfile, docker-compose.ymlを用意します。これらは証明書の生成時のみ使用します。証明書が出来たら後はほかしてもらってもかまいません。

Dockerfile
FROM alpine:3.9
RUN apk --update --no-cache --no-progress add openvpn openssl easy-rsa && \
    rm -rf /var/cache/apk/*
docker-compose.yml
version: '3.2'
services:
  openvpn:
    build: .
    image: openvpn
    cap_add:
      - NET_ADMIN
    ports:
      - "55505:1194/udp"
    volumes:
      - ./easy-rsa:/opt/easy-rsa
    working_dir: /opt/easy-rsa
    environment:
      - OVPN_PORT=1194
    restart: always
    tty: true

OpenVPN初期設定用コンテナを起動。

$ docker compose up -d --build

コンテナを稼働した状態で、マウントしている/opt/easy-rsa内でeasyrsaで証明書とかを生成します。 コンテナ内に入り、/opt/easy-rsa内に入り作業することになります。

$ docker compose exec openvpn sh

# cd /opt/easy-rsa
# /usr/share/easy-rsa/easyrsa init-pki
# /usr/share/easy-rsa/easyrsa build-ca nopass
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:larkbox
# /usr/share/easy-rsa/easyrsa gen-dh
# /usr/share/easy-rsa/easyrsa build-server-full server nopass
# openvpn --genkey --secret pki/ta.key
# /usr/share/easy-rsa/easyrsa gen-crl
# /usr/share/easy-rsa/easyrsa build-client-full client nopass
# ^D

以上でコンテナから抜けると、以下のファイルが出来ています。

./easy-rsa/pki/ca.crt
               dh.pem
               ta.key
               issued/client.crt
                      server.crt
               private/ca.key
                       client.key
                       server.key

./easy-rsa/以下のファイルはrootパーミッションですので、参照する場合はsudoで。

./easy-rsa/以下が出来ていたらコンテナを破棄。

$ docker compose down

OpenVPNサーバ稼働

OpenVPNサーバは192.168.1.xで稼働し、クライアントは192.168.30.xで稼働するようにしています。

以下のOpenVPNサーバ稼働用Dockerfile, docker-compose.yml, run.sh, server.confを用意します。

Dockerfile
FROM alpine:3.9
RUN apk --update --no-cache --no-progress add openvpn openssl easy-rsa && \
    rm -rf /var/cache/apk/*
COPY run.sh server.conf /
CMD [ "/run.sh" ]

コンテナ上ではOpenVPNサーバを標準の1194/udpポートを使用して動かしていますが、外部からは55505ポートでアクセスする設定にしています。このポート番号はIPoE接続の場合IPv4 over IPv6の利用可能ポート番号からお好みで選択してください。PPPoE接続の場合は1194のままでも大丈夫です。

docker-compose.yml
version: '3.2'
services:
  openvpn:
    build: .
    image: openvpn
    cap_add:
      - NET_ADMIN
    ports:
      - "55505:1194/udp"
    volumes:
      - ./easy-rsa:/opt/easy-rsa
      - /dev:/dev
      - /lib/modules:/lib/modules:ro
    working_dir: /opt/easy-rsa
    environment:
      - OVPN_PORT=1194
    restart: always
    sysctls:
      net.ipv4.ip_forward: 1
      net.netfilter.nf_conntrack_helper: 1
    privileged: true

OpenVPNサーバ起動スクリプト

run.sh
#!/bin/sh

# ERROR: Cannot open TUN/TAP dev /dev/net/tun: No such file or directory (errno=2)が出ないようにするため
mkdir -p /dev/net
if [ ! -c /dev/net/tun ]; then
    mknod /dev/net/tun c 10 200
fi

# クライアント用のネットワーク
OVPN_SERVER=${OVPN_SERVER:-192.168.30.0}

# `ip addr`コマンドの結果からネットワークデバイス名を抽出する
OVPN_NATDEVICE=$(ip addr | awk 'match($0, /global [[:alnum:]]+/) {print substr($0, RSTART+7, RLENGTH)}')
if [ -z "${OVPN_NATDEVICE}" ]; then
    ip addr
    echo "Failed to extract OVPN_NATDEVICE."
    exit 1
fi

# iptablesの設定
iptables -t nat -A POSTROUTING -s ${OVPN_SERVER}/24 -o ${OVPN_NATDEVICE} -j MASQUERADE

# OpenVPNサーバの起動
/usr/sbin/openvpn --config /server.conf

OpenVPNサーバ設定ファイル

server.conf
port 1194
proto udp
dev tun
ca /opt/easy-rsa/pki/ca.crt
cert /opt/easy-rsa/pki/issued/server.crt
key /opt/easy-rsa/pki/private/server.key  # This file should be kept secret
dh /opt/easy-rsa/pki/dh.pem
tls-auth /opt/easy-rsa/pki/ta.key 0 # This file is secret
server 192.168.30.0 255.255.255.0
#push "route 192.168.1.0 255.255.255.0"
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 192.168.1.1"
user nobody
group nobody
keepalive 10 120
persist-key
persist-tun
status openvpn-status.log
verb 3
compress lz4
explicit-exit-notify 1
tun-mtu 1500
mssfix 1460

OpenVPNサーバコンテナの起動

$ docker compose up -d --build

OpenVPNクライアント設定ファイルの作成

接続先のvpnサーバのアドレスとポート番号はクライアント設定ファイルのremote行で設定します。

remote vpn999999999.v4.softether.net 55505

vpnサーバのアドレスはSoftetherVPNを起動して取得したダイナミックDNSのアドレスを指定しています。自宅LANのグローバルアドレスが何かしらの理由で変更になっても常にこの名前でアクセスできます。SoftetherVPNのダイナミックDNSのアドレスの取得方法はこちらの記事を参照してください。 実験するだけだったら、HGWで受け取ったWAN側のグローバルIPv4アドレスを直書きしても大丈夫。

vpnサーバと接続するポート番号(ここでは55505)はdocker-compose.ymlで指定したポート番号と同じ値です。

クライアント設定ファイルのファイル名は何でも良いのですが、クライアントからvpnサーバを選択する際の名前になりますので、わかりやすい名前にしておけばいいでしょう(例えばdocker_openvpn_v4.ovpn)。

docker_openvpn_v4.ovpn
client
dev tun
proto udp
remote vpn999999999.v4.softether.net 55505
resolv-retry infinite
nobind
persist-key
persist-tun
auth-nocache
verb 3
key-direction 1
compress lz4
tun-mtu 1500
mssfix 1460
<ca>
-----BEGIN CERTIFICATE-----
MIIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
./easy-rsa/pki/ca.crtの内容をコピペする
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXX/Y7mg==
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
MIIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
./easy-rsa/pki/issued/client.crtの内容をコピペする
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXGqJfz
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
MIIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
./easy-rsa/pki/private/client.keyの内容をコピペする
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXX1sL4=
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
./easy-rsa/pki/ta.keyの内容をコピペする
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END OpenVPN Static key V1-----
</tls-auth>

このファイルをvpnクライアントに持っていって、OpenVPNで接続します。

母艦側の設定

本記事の目的はSIPクライアントも利用可能なvpn環境の構築です。そのためには、OpenVPNサーバコンテナを稼働させる母艦側にも設定が必要です。母艦側でもカーネルモジュールnf_nat_sipを有効にして、フレッツ光の内線電話SIPプロトコルをHGWに繋いであげるためです。

母艦側に以下の設定を追加し、母艦を再起動すると、nf_nat_sipが有効になります。

/etc/modulesに以下の1行を追加。

/etc/modules
nf_nat_sip

本来なら/etc/sysctl.confに以下の2行を追加すべきですが、今回はdocker-compose.ymlの中で設定しているのでここでは不要。

/etc/sysctl.conf
net.ipv4.ip_forward=1
net.netfilter.nf_conntrack_helper = 1

本来なら/etc/rc.localに以下の1行を追加すべきですが、今回はrun.shの中で設定しているのでここでは不要。

/etc/rc.local
iptables -t nat -A POSTROUTING -s 192.168.30.0/24 -o eth0 -j MASQUERADE

HGW(ホームゲートウェイ)の設定

NTT西日本のHGW(ホームゲートウェイ) RT-500KIを例に、IPoE(IPV4 over IPv6)環境でOpenVPNを動かすための設定方法をまとめます。

IPV4 over IPv6に関する設定は以下のurlで開くページで指定します。この例ではHGWのアドレスをデフォルトのまま192.168.1.1としています。

http://192.168.1.1:8888/t/

hgw_1.png

「IPoE IPv4設定」をクリック、設定のトップページが開きます。

hgw_2.png

自宅サイトのグローバルIPv4アドレスと利用可能ポートがわかります。PPPoEでは利用可能な全ポートの1/64程度しか利用できません。IPoEの場合、1つのグローバルIPv4アドレスを64サイトで共用しているということなんでしょう。

OpenVPNをIPoE(IPV4 over IPv6, ポート55505/udpを使用)で利用するため、「静的IPマスカレード設定」で55505/udpを開けます。左サイドメニューの「静的IPマスカレード設定」をクリックし、1番目のエントリーを「編集」します。

hgw_3.png

筆者の場合、55505は利用可能ポートですが、利用可能ポートはサイト毎に異なります。利用可能ポートにあわせて適宜変更してください。

「LAN側宛先IPアドレス」に自宅LAN内のvpnサーバのプライベートIPv4アドレスを指定します。

以上でHGW(ホームゲートウェイ)の設定は完了。

MacOSクライアントの設定

MacOSからOpenVPNサーバーに接続するには、今回は「Tunnelblick」を使用しました。

https://tunnelblick.net/downloads.html
Tunnelblickダウンロードページ

ダウンロードページからインストーラーの.dmgファイルをダウンロードし、マウントしてTunnelblick.appをダブルクリックするだけでインストール完了です。

tunnelblick_1.png

Tunnelblickをインストールするとメニューバーに梵鐘のようなアイコンが追加されるので、最初にそこから「VPNの詳細…」を選択。

tunnelblick_2.png

接続先設定タブを選択。最初は左側の接続先一覧が空の状態ですが、ここにOpenVPNクライアント設定ファイルの取得で説明したクライアント設定ファイルをドラッグします。クライアント設定ファイルは大事なファイルなのでTunnelblickにドラッグした後、Mac上に放置しないように注意!

クライアント設定ファイルが受け入れられたら「接続」ボタンをクリック。

tunnelblick_3.png

認証に成功するとデスクトップ右上に接続ウィンドウが表示されます。

tunnelblick_5.png

2回目以降の操作は梵鐘アイコンからどうぞ。

tunnelblick_2.png

簡単ではありますが、OS標準搭載のvpn設定が使えないのが残念なところです。

蛇足ですが、接続実験を行う場合は、当然のことながらvpnクライアントとvpnサーバは別のネットワークに接続されている必要があります。

Windows11クライアントの設定

https://www.openvpn.jp/download/
OpenVPNダウンロードサイト

ダウンロードサイトからWindowsインストーラをダウンロードして、

OpenVPNのインストールはダウンロードしたインストーラを起動して「Install Now」をクリックするだけ、特に設定しなければならないこともありません。

openvpn_for_windows_1.png

インストールを終えたら、

C:\Users\自分のアカウント\OpenVPN\config

このフォルダーにOpenVPNクライアント設定ファイルの取得で説明したクライアント設定ファイルを入れます。

OpenVPNに関する操作は全てタスクトレイから行います。図の最下行の右側のアイコンを右クリック。

openvpn_for_windows_2.png

以下のメニューがポップアップします。

openvpn_for_windows_4.png

vpn接続する場合はメニューの「接続」を選択。接続に成功すると、アイコンが緑色に変わります。

openvpn_for_windows_3.png

接続の状況はメニューの「ステータスの表示」で確認できます。

openvpn_for_windows_5.png

簡単ではありますが、OS標準搭載のvpn設定が使えないのが残念なところです。

蛇足ですが、接続実験を行う場合は、当然のことながらvpnクライアントとvpnサーバは別のネットワークに接続されている必要があります。

iOSクライアントの設定

App Storeから「OpenVPN Connect」をインストールします。

openvpn_for_ios_1.png

次にOpenVPNクライアント設定ファイルの取得で説明したクライアント設定ファイルを自分宛てのメールに添付し、自分にメールします。このメールを受信し、添付ファイル(拡張子が.ovpnのファイル)をタップし、処理するアプリから「OpenVPN Connect」を選択します。

尚、この方法は簡単ではありますが、セキュリティ的には雑な方法ですので、使った後の.ovpnファイルが添付されたメールは確実に消去する等を忘れないように。

.ovpnファイルをタップすると、「OpenVPN Connect」から設定ファイルを追加する旨のメッセージが表示されるので、「Add」を選択します。

openvpn_for_ios_2.png

設定ファイルが追加されると、

openvpn_for_ios_3.png

左側のトグルスイッチアイコンをタップするとvpnに接続します。

openvpn_for_ios_4.png

筆者の場合iOS版OpenVPNでは、デフォルトの設定では接続はできたものの、Webサイトの閲覧とかはうまくいきませんでした。そこで、設定を次のように変更します。

イゲタメニューから「Settings」を選択

openvpn_for_ios_5.png

設定ページを一番下までスクロールすると、「ADVANCED SETTINGS」が見えてくるのでこれをタップし、

openvpn_for_ios_6.png

「Allow Compression(insecure)」を「FULL」に変更する。

openvpn_for_ios_7.png

これで正しく動作するようになりました。

簡単ではありますが、OpenVPNではOS標準搭載のvpn設定が使えないのが残念なところです。

蛇足ですが、自宅で接続実験を行う場合は、vpnクライアントとvpnサーバは別のネットワークに接続されている必要があるので、スマホのWifiは切ってモバイルネットワーク通信で。

自宅のひかり固定電話をvpn経由でモバイルで利用する

SoftetherVPNでもOpenVPN接続可能なのにわざわざdocker composeでOpenVPNサーバを立てた理由は、vpn経由で自宅の固定電話を利用したかったからに他なりません。

とはいうものの、いまや固定電話にはクズ業者からのセールス電話しかかかってこないので、受話に関してはあまりメリットはないかも。発話に関しては、筆者の場合ひかり電話はモバイルと比べ通話料がお安いというメリットはあるものの、モバイルのカケホーダイが一般的になった今、万人にメリットがあるとは言い難い。

それはさておき、早速試してみます。

通話アプリはSIP対応のものなら何でもいいのですが、NTTお墨付きのAGEphoneを使うことにします。

HGW(ホームゲートウェイ)の設定

まずはHGW(ホームゲートウェイ)の設定から。

NTT西日本のHGW(ホームゲートウェイ) RT-500KIを例に説明します。

まず、ブラウザでHGWにアクセス。

http://192.168.1.1/

アカウントとパスワードを入力後、

電話設定>内線設定

を開く。

sip_server_1.png

ここで、クライアント(電話子機)として利用する端末(PC, Mac, iPhone等)の内線番号とMACアドレスを指定していきます。

sip_server_2.png

ここで注意する点は、WindowsのAGEphoneを使う場合は、「内線番号」と「ユーザID」を同じにすることです。例えば、内線番号「3」のデフォルトのユーザIDは「003」になっていますが、これを「3」に書き換える必要があります。WindowsのAGEphoneのバグかも?

macを子機にする

App Storeで「AGEphone」を検索し、インストール。

agephone_for_mac_1.png

SIPドメインにHGWのIPアドレスを設定し、HGWに設定した内線番号、ユーザID、パスワードを電話番号、認証ID、パスワード欄にそれぞれ設定するだけで繋がる。

agephone_for_mac_3.png

macがHGWと同じサブネットにWifiで接続している時もvpn接続しているときもふつうに内線電話として使用できます。もちろん外線発受話もできます。出先で家の電話が使えるのは便利なこともあろうかと。

筆者宅では内線発信するとなぜかメインのアナログ電話も呼鈴してしまいうるさくてしょうがない。

agephone_for_mac_2.png

Windows PCを子機にする

公式ダウンロードサイトから「AGEphone for windows無償版」をダウンロードする

https://www.ageet.com/agephone
AGEphone公式ダウンロードサイト

設定項目はコレだけしかない!HGWで「内線番号」と「ユーザID」を同じにしておかなければならない原因がコレです。

agephone_for_windows_2.png

電話のGUIは昔のiPhone風。Windows PCがHGWと同じサブネットにWifiで接続している時もvpn接続しているときもふつうに内線電話として使用できます。もちろん外線発受話もできます。出先で家の電話が使えるのは便利なこともあろうかと。

agephone_for_windows_1.png

iPhoneを子機にする

App Storeで「AGEphone」を検索し、インストール。

agephone_for_ios_1.png

iOS版のAGEphoneにはやたら設定項目が多い。なぜ?

agephone_for_ios_3.png

これでvpn経由で出先からiPhoneを内線電話として使用できます。もちろん外線発受話もできます。これは便利!

agephone_for_ios_2.png

まとめ

OpenVPNサーバをdocker composeで立ち上げて、IPoE環境(IPv4 over IPv6)でvpnを利用する方法を解説しました。IPoE環境ではipv4接続時にはポートが自由に使えないのでvpnは鬼門です。それでもOpenVPNを使うことにより、PPPoE接続の時のようにvpnを使えるようにできました。ルーティング設定を行うことにより、サブネットが異なるvpnクライアントからでもSIPによる内線電話受発話を可能にしました。


Written by questions6768 who lives in Uji, Kyoto.