systemd が VPN サービスを停止する:Type= をクライアントに合わせる
起動時の VPN ユニットは、Type= がクライアントの寿命と合わないと非アクティブになる。種類を合わせ、所有権を直し、トンネルの後にサービスを並べる。
ユニットは正しく見え、systemctl start が成功を報告しても、十秒後にはサービスが非アクティブのまま座っている。あるいは起動制限に止められるまで三十秒ごとに再起動する。どちらの挙動もネットワーク障害ではない:ユニットが記述しているプロセスの寿命が、クライアントの実際の動作と合っていないのだ。クライアントは fork して終了するか、セッションを構成してすぐ終了する。
起動が成功した数秒後にユニットが非アクティブになる理由
systemd は自分が起動したプロセスから状態を推測するので、Type= はそのプロセスについての主張であり、説明書ではない。Type=simple ではコマンドが動いている間ユニットはアクティブと数えられ、戻ると非アクティブになる。接続をヘルパーに渡して終了するクライアントがクラッシュのように見えるのはそのためで、トンネルが上がっていても同じだ。するとジャーナルには起動と停止のパターンが残り、壊れたバイナリのように読める。
ネットワーク設定に触る前に、クライアントの挙動と並べてユニットの種類を読むこと。
クライアントに合う種類を選ぶ
二つのパターンがほとんどのベンダー CLI を覆う:セッションを構成して終了するコマンドと、戻る前にヘルパーを fork するコマンドだ。それぞれ別のユニット形状が要る。
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/protonvpn-cli connect -f
ExecStop=/usr/bin/protonvpn-cli disconnectoneshot と RemainAfterExit=true を組み合わせると、コマンドが戻った後もユニットはアクティブのままで、構成して終了するパターンに合い、停止コマンドがサービスをきれいに終了させる。fork するクライアントは同じ形のままだが、systemd が子を追うために Type=forking が要る。Restart=on-failure、RestartSec=30、そして有限の StartLimitBurst を設定し、プロバイダの障害がエラーを埋める再起動ループにならないようにする。
監視下のクライアントをデーモン化させてはならない
systemd の外の監視ツールも同じ壁に当たる。デーモン化したクライアントを起動する runit の実行スクリプトはすぐに監視ツールへ戻り、監視ツールはその戻りをクラッシュと読んでもう一つ複製を起動する。どの監視ツールも前面のプロセスを所有することを前提にしており、sshd -D が前に居続けるのと同じだ。監視下のコマンドではデーモンのフラグを外し、背後は監視ツールに任せる。トンネルができる前に戻るラッパースクリプトも同じ混乱を招く。
自ら切り離すプロセスは、それを見張るはずだった何かから見えない。
セッションが終わるとトンネルも一緒に消える
デスクトップセッションは自分が起動したクライアントを所有する。ログインを終えるとセッションバスが消えてトンネルも一緒に消え、最後のウィンドウを閉じるとグラフィカルなクライアントの接続がよく切れるのはそのためだ。サーバーは自分で上がるシステムユニットか NetworkManager プロファイルを使うべきだ。ユーザーユニットが意味を持つのはトンネルがログインと共に終わるべき場合だけなので、グラフィカルセッションのターゲットに結び付け、その旨を説明に書く。
サービスのユーザーは同じ問題のもう半分だ。root が所有しモード 0600 の鍵は別のアカウントで動くユニットからは読めず、シェルで sudo が何を成し遂げていても変わらない。
systemctl cat openvpn-server@work
sudo chown -R openvpn:network /etc/openvpn/server
sudo find /etc/openvpn -type f -perm /077 -lsアドレスができてから依存側を並べる
まだ作られていないトンネルアドレスにデーモンが束縛されると、Cannot assign requested address で終了する。直し方は再試行ループではなく、明示的な順序付けだ。
[Unit]
After=network.target [email protected]
Requires=sys-devices-virtual-net-wg0.deviceAfter= はジョブを順序付け、デバイスユニットの Requires= は依存側をインターフェースの存在に結び付けるが、これはネットワークターゲットだけを待つより強い。編集後にマネージャを再読み込みして依存サービスを再起動し、順序が効いたと決めつけずにジャーナルを読む。クライアントの出力は StandardOutput=journal やログオプションでジャーナルに送る(誰も開かないファイルに書く監視下のクライアントは、一度も起動しなかったものと全く同じに見えるからだ)。
その後で一度再起動し、トンネルデバイスが現れた後にユニットがアクティブであることを確認する。systemctl start だけで確かめた修正は、直すはずだった起動順序を試していない。