トンネルが再接続するたびにリセットされる上限は制限ではなく、耐久性のある状態を置く場所なしにバイトカウンタをVPNサーバーへボルト留めすると、まさにそうなる。カウンタはセッションに属し、上限は人またはデバイス契約に属する。この機能の難しさはすべてこの不一致から来るので、以下の確認は状態がどこに住み、どの層が決定してよいかに向けられている。

上限はトンネルではなくアカウントに固定する

まず上限の主体を名づける。一人が三つのデバイス証明書を持つことがあり、別々に認証するという理由だけでそれぞれが独立した月間予算を得るべきではない。再接続、新しいトンネルアドレス、二台目のゲートウェイへの移転を生き延びる安定したアカウント識別子だけが、上限が使える唯一の錨である。証明書のコモンネームがその鍵として機能するのは、発行ポリシーが名前ごとに一つのアカウントを保証する場合に限る。

割り当てられたトンネルアドレスが答えになることは決してない。アドレスは再利用され、プールは共有され、同じアカウントが同じ分に二つのゲートウェイへ二度現れうる。台帳がアドレスを鍵にするなら、切断して再接続するだけでタンクは再び満たされる。

計算を監査可能にするため、セッションごとにアカウント、請求期間、ゲートウェイのインスタンス、セッションの識別子、最後に観測したカウンタ、確定済みの合計を十分に記録する。セッション識別子にはデーモン起動の世代を含めるべきで、それは再起動したサーバーがクライアントIDを再利用するため、素朴な照合では前の基準線を引き継いでしまうからである。

単位、方向、リセットの境界を定める

収集を始める前に四つの定義を文書で固めなければならない。上限はクライアントが送るバイトか、受け取るバイトか、その合計か。ギガバイトは10^9バイトか2^30か。どの現地時刻、どのタイムゾーンで期間は切り替わるのか。そしてカウンタは正確に何を測るのか、トンネルのオーバーヘッドと再送は含まれるが、アプリケーションのファイルサイズやプロバイダの請求書は含まれない。

どの選択も誤りではないが、実装と顧客が見る数字は一致しなければならない。同じシステムの二つの半分がGiBで、あるいは現地時刻ではなくUTCの真夜中に切り替わることで食い違うと、上限全体への信頼を損なう種類のチケットがちょうど生まれる。

バイトの合計を一つの課金差分に変える

管理インターフェースは、クライアントIDと各方向の累積バイト合計を運ぶ定期的なBYTECOUNT_CLI行を報告し、bytecountコマンドがその到着頻度を決める。サーバーから見ると、受信の数字はクライアントが送ったバイトを数え、送信の数字はクライアントへ届けたバイトを数える。間隔は台帳が吸収できるものに設定すべきで、非常に短い間隔は実際には得られない精度を買うだけである。

一つの制約が設計全体を形づくる。管理ソケットは制御接続を一つしか受け付けないため、監視用エクスポーター、GUI、そしてあなたのコントローラがそれぞれ自前の接続を開くことはできない。どのプロセスがチャネルを所有するかを決め、他はそこから状態を読むようにするべきで、このインターフェースは管理操作を与えるものだから、そもそもホストの外へ公開すべきではない。

rx_delta = sample.rx_now - session.prev_rx
tx_delta = sample.tx_now - session.prev_tx
charge  = rx_delta + tx_delta
# same transaction:
ledger.total += charge
session.prev_rx, session.prev_tx = sample.rx_now, sample.tx_now

新しい基準線と使用量の増分を一緒に確定し、サンプルの再生が二重課金ではなく何も課金しないようにする。後ろへ動くカウンタは負債ではなく信号であり、セッションが置き換えられたかデーモンが再起動したという意味なので、正しい対応は負の額を計上することではなく、セッション識別子を突き合わせることだ。

切断の経路にも同じ注意が要る。最終イベントがセッション終了時の合計を運ぶなら、最後に確定したサンプルとの差だけを課金する。定期サンプリングで既に課金した後に全合計を足すのは、自作の上限システムで最も多い二重計上である。期間の境界をまたぐセッションにも規則が要るのは、一つの最終合計が、バイトがいつ真夜中を越えたかの情報を持たないからだ。

枯渇はトンネルを切るだけでなく接続を拒否させる

アカウントが上限に達したら、既存のセッションを終了させ、新しいものは拒否しなければならない。管理プロトコルはクライアントIDによるclient-killを提供し、アカウントの生きたセッションをすべて数え上げる限り、たまたま気づいた一つのアドレスを落とすのではなく前半を処理できる。

後半こそ上限設計が失敗する場所だ。殺すだけではクライアントの再接続ループとの競争になり、数秒後に喜んで戻ってくる。接続受付は共有台帳を参照しなければならず、複数ゲートウェイの配備ではその確認はマシンごとのファイルではありえない。ログインを受け付けうるすべてのインスタンスが、同じ枯渇状態を見なければならない。

トークンによる再接続は独自の試験に値する。OpenVPN 2.6はauth-gen-tokenでセッショントークンを内部検証でき、コントローラが上限を強制するために使っていた外部認証呼び出しを飛ばす。したがってまだ有効なトークンが証明するのは身元であって残りの上限ではなく、新しいクライアントインスタンスは依然として台帳を参照しなければならない。代わりにmanagement-client-authを有効にすると、コントローラが認証者になり、セッション状態を自ら解析して期限切れや無効なトークンを拒否する義務を負う。

サンプリングが生む超過を受け入れる

定期観測と切断は、既知の許容差を持つサンプリングされた上限を与える。クライアントはおよそ一つの報告間隔にコントローラが動くまでの時間を加えた分だけ超過でき、一つのアカウントの同時セッションはその窓を何倍にもする。提供できない最終バイトでの正確な遮断を宣伝するのではなく、それをインターフェースで伝える。

本当の厳格な天井は別の工学的問題であり、転送経路の内部で、セッションをまたいで同じアカウントに結びつけて強制する必要がある。カウンタを速く読むことは同じではなく、単により多くのトンネルを開くクライアントには耐えられない。

公開前に、繰り返しの再接続、同時に二台、コントローラとゲートウェイの再起動、失われた切断イベント、セッション途中の期間切り替え、枯渇直後のトークン再接続の試行といった、素朴な実装を壊す受け入れケースを走らせる。そのうえで会計の停止に対する故障モードを決める。耐久性のある台帳は記録した使用量を守るが、決して届かなかった観測を蘇らせることはできず、顧客は自分がそのどちらの状況にいるのかを知るに値する。

関連記事