前書き

通常はWireGuardを使用してセキュリティと速度を両立したネットワークで快適に過ごしているが、WireGuardが通れないネットワークがたまに存在する。

具体的には、次のようなものが該当する。

  • DNSとTCP80とTCP443以外を遮断しているネットワーク
  • MTUが小さいネットワーク

前者は飛行機の機内Wi-Fiサービス(2024年2月頃以降)、後者は新幹線車内Wi-Fi。

飛行機の機内Wi-Fiサービス2023年10月頃はWireGuardが通れたのだが、この4ヶ月くらいの間で仕様変更があった模様。

新幹線Wi-FiはMTUが小さく、1294バイト。WireGuardは設定可能な内側MTUの最低値が1280で、そこに必要なヘッダーがーついて、外側のMTUとしては1360バイトくらい必要。UDPなので、MTUが大きすぎる場合はパケットが破棄されてしまう。

こうしたネットワークを通過するために、最初はSoftehterVPNを選定した。しかし、内側がIPv6シングルスタックという環境故、バージョン4系は通信が成立せず、バージョン5系はSoftehterVPNプロトコルならつながるものの、OpenVPN互換機能ではDHCPサーバーが必須なようで、これがない場合にユーザー認証失敗扱いで接続できない。DHCPサーバーがないことによるユーザー認証失敗と、本当のユーザー認証失敗時は、認証処理にかかる時間が異なるので分かっていれば判別可能だが、当初はユーザー認証失敗という事で、結構ハマった。

今回、OpenVPNを使って外側TCP443デュアルスタック、内側IPv6シングルスタックというVPNを実現できたので、設定をここに記す。

内側で使うIPv6アドレスはISPから配布されているグローバルIPv6アドレスの内、/64を使う。設定ファイルにハードコートする。動的に変わる場合は参考資料のとこに乗せたサイトを参考にしてほしい。なお当方では未確認。

このOpenVPNサーバーに接続するクライアントは、Android端末とWindows端末。それぞれ公式の専用アプリをインストーする。

参考資料

[OpenWrt Wiki] OpenVPN server
[OpenWrt Wiki] OpenVPN extras
[OpenWrt Wiki] OpenVPN server with dynamic IPv6 GUA prefix

セットアップ

基本的には公式ドキュメントに従えばサーバーが動く。ただ、内側がIPv6シングルスタックという特殊要件故に調整が必要だった。導入方法一式を含めて書いていく。

uhttpd ポート番号変更

IPv4だけなら外側から内側に入れるときにPort Forwardしたらいいのだが、IPv6で待ち受けしたい場合、IPv6に対してPort Forwardが有効にならなかった。ので、OpenVPNサーバーに443を使わせる。もともと設定画面がこのポートを使用しているので、まずはこのポートから8443へ引っ越ししてもらう。

vi /etc/config/uhttpd
        list listen_http '0.0.0.0:80'
        list listen_http '[::]:80'
        list listen_https '0.0.0.0:8443'
        list listen_https '[::]:8443'
service uhttpd restart

パッケージのインストール

これは公式に案内されているものと同様

opkg update
opkg install openvpn-openssl openvpn-easy-rsa

環境変数の定義

パラメーターを入れる。これらはコンソールに直接入れる。

VPN_POOL6とVPN_DNS6は各自の環境に合わせて読み替えてほしい。

# Configuration parameters
VPN_DIR="/etc/openvpn"
VPN_PKI="/etc/easy-rsa/pki"
VPN_PORT="443"
VPN_PROTO="tcp"
VPN_POOL6="2001:0DB8::/64"
VPN_DNS6="${VPN_POOL6%:*}:1"
VPN_DN="$(uci -q get dhcp.@dnsmasq[0].domain)"

# Fetch server address
NET_FQDN="$(uci -q get ddns.@service[0].lookup_host)"
. /lib/functions/network.sh
network_flush_cache
network_find_wan NET_IF
network_get_ipaddr NET_ADDR "${NET_IF}"
if [ -n "${NET_FQDN}" ]
then VPN_SERV="${NET_FQDN}"
else VPN_SERV="${NET_ADDR}"
fi

鍵管理

# Work around EasyRSA issues
wget -U "" -O /tmp/easyrsa.tar.gz https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.7/EasyRSA-3.1.7.tgz
tar -z -x -f /tmp/easyrsa.tar.gz

# Configuration parameters
cat << EOF > /etc/profile.d/easy-rsa.sh
export EASYRSA_PKI="${VPN_PKI}"
export EASYRSA_TEMP_DIR="/tmp"
export EASYRSA_CERT_EXPIRE="3650"
export EASYRSA_BATCH="1"
alias easyrsa="/root/EasyRSA-3.1.7/easyrsa"
EOF
. /etc/profile.d/easy-rsa.sh

# Remove and re-initialize PKI directory
easyrsa init-pki

# Generate DH parameters
easyrsa gen-dh

# Create a new CA
easyrsa build-ca nopass

# Generate server keys and certificate
easyrsa build-server-full server nopass
openvpn --genkey tls-crypt-v2-server ${EASYRSA_PKI}/private/server.pem

# Generate a CRL
easyrsa gen-crl

クライアントの証明書を作成

CLIENT1を任意の名前に変更してほしい。名前を変えて複数回実行すると、その分の証明書を作成できる。接続する端末毎に作成する。

# Generate client keys and certificate and Add more client
easyrsa build-client-full CLIENT1 nopass
openvpn --tls-crypt-v2 ${EASYRSA_PKI}/private/server.pem --genkey tls-crypt-v2-client ${EASYRSA_PKI}/private/CLIENT1.pem

ファイアーウォール

# Configure firewall
uci rename firewall.@zone[0]="lan"
uci rename firewall.@zone[1]="wan"
uci del_list firewall.lan.device="tun+"
uci add_list firewall.lan.device="tun+"
uci -q delete firewall.ovpn
uci set firewall.ovpn="rule"
uci set firewall.ovpn.name="Allow-OpenVPN"
uci set firewall.ovpn.src="wan"
uci set firewall.ovpn.dest_port="${VPN_PORT}"
uci set firewall.ovpn.proto="${VPN_PROTO}"
uci set firewall.ovpn.target="ACCEPT"
uci commit firewall
service firewall restart

VPN サービスの構成ファイル生成

# Configure VPN service and generate client profiles
umask go=
VPN_DH="$(cat ${VPN_PKI}/dh.pem)"
VPN_CA="$(openssl x509 -in ${VPN_PKI}/ca.crt)"
ls ${VPN_PKI}/issued \
| sed -e "s/\.\w*$//" \
| while read -r VPN_ID
do
VPN_TC="$(cat ${VPN_PKI}/private/${VPN_ID}.pem)"
VPN_KEY="$(cat ${VPN_PKI}/private/${VPN_ID}.key)"
VPN_CERT="$(openssl x509 -in ${VPN_PKI}/issued/${VPN_ID}.crt)"
VPN_EKU="$(echo "${VPN_CERT}" | openssl x509 -noout -purpose)"
case ${VPN_EKU} in
(*"SSL server : Yes"*)
VPN_CONF="${VPN_DIR}/${VPN_ID}.conf"
cat << EOF > ${VPN_CONF} ;;
user nobody
group nogroup
dev tun
port ${VPN_PORT}
proto ${VPN_PROTO}-server
proto ${VPN_PROTO}6-server
server-ipv6 ${VPN_POOL6}
topology subnet
client-to-client
keepalive 10 60
persist-tun
persist-key
push "dhcp-option DNS ${VPN_DNS6}"
push "dhcp-option DOMAIN ${VPN_DN}"
push "redirect-gateway ipv6"
push "persist-tun"
push "persist-key"
<dh>
${VPN_DH}
</dh>
EOF
(*"SSL client : Yes"*)
VPN_CONF="${VPN_DIR}/${VPN_ID}.ovpn"
cat << EOF > ${VPN_CONF} ;;
user nobody
group nogroup
dev tun
nobind
client
remote ${VPN_SERV} ${VPN_PORT} ${VPN_PROTO}
auth-nocache
block-outside-dns
remote-cert-tls server
EOF
esac
cat << EOF >> ${VPN_CONF}
<tls-crypt-v2>
${VPN_TC}
</tls-crypt-v2>
<key>
${VPN_KEY}
</key>
<cert>
${VPN_CERT}
</cert>
<ca>
${VPN_CA}
</ca>
EOF
done
service openvpn restart
ls ${VPN_DIR}/*.ovpn

lsコマンドにより、生成されたovpnファイルが表示される。各接続端末に配置してアプリで読み込むと設定が完了。

# Enable CRL verification
VPN_PKI="/etc/easy-rsa/pki"
VPN_CRL="$(cat ${VPN_PKI}/crl.pem)"
cat << EOF >> /etc/openvpn/server.conf
<crl-verify>
${VPN_CRL}
</crl-verify>
EOF

クライアント証明書の無効化

証明書を失効させることができる。ただし、当方未確認。

RevokeCLIENTのところを失効させたいクライアントの名前に置き換えてほしい。

# Revoke client certificate
easyrsa revoke RevokeCLIENT