터널이 다시 연결될 때마다 초기화되는 한도는 제한이 아니며, 지속 상태를 둘 곳 없이 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를 켜면 컨트롤러가 인증자가 되어 세션 상태를 스스로 분석하고 만료되거나 유효하지 않은 토큰을 직접 거부할 의무를 물려받는다.

표본 추출이 만드는 초과를 받아들여라

주기적 관측과 연결 해제는 알려진 허용 오차를 가진 표본화된 한도를 준다. 클라이언트는 대략 하나의 보고 간격에 컨트롤러가 행동하는 데 걸리는 시간을 더한 만큼 초과할 수 있고, 한 계정의 동시 세션은 그 창을 몇 배로 늘린다. 제공할 수 없는 마지막 바이트에서의 정확한 차단을 광고하는 대신 그것을 인터페이스에서 말하라.

진짜 엄격한 상한은 다른 공학 문제로, 전달 경로 내부에서 세션을 가로질러 같은 계정에 묶인 강제다. 카운터를 더 빨리 읽는 것은 같은 일이 아니며, 단지 더 많은 터널을 여는 클라이언트를 견디지 못한다.

출시 전에 반복되는 재연결, 동시에 두 기기, 컨트롤러와 게이트웨이 재시작, 잃어버린 연결 해제 이벤트, 세션 중간의 기간 전환, 소진 직후의 토큰 재연결 시도처럼 순진한 구현을 깨뜨리는 인수 사례를 돌려라. 그다음 회계 중단에 대한 실패 모드를 정하라. 지속 장부는 기록한 사용량을 보호하지만 결코 도착하지 않은 관측을 되살릴 수는 없으며, 고객은 자신이 그 두 상황 중 어디에 있는지 알 자격이 있다.

관련 글