OpenVPN 훅 이벤트: 이름과 타임스탬프가 키가 아닌 이유
연결 해제 훅은 연결 시각을 보고하지, 해제 시각을 보고하지 않습니다. 사용자 이름과 함께 쓰이면 잘못된 세션을 종료할 수 있습니다. 대신 식별자를 소유하세요.
같은 이름으로 두 번째 연결이 이미 시작된 뒤에 해제 기록이 도착하면, 해당 사용자의 최신 행을 갱신하는 핸들러는 잘못된 세션을 닫습니다. 실수는 타임스탬프 비교 자체가 아닙니다. 이름에 정수 초 하나를 더하면 하나의 세션을 식별한다는 가정이 문제입니다. 훅 이벤트는 연결을 설명하며, 연결에는 오직 그 연결에 속한 키가 필요합니다.
해제 변수가 실제로 무엇을 뜻하는지부터 시작하세요. 그때에야 식별자 선택이 의미를 갖습니다.
해제 훅이 실제로 전달하는 것
Community OpenVPN 2.6에서 훅 참조 문서는 time_unix를 클라이언트가 연결한 순간으로 정의하며, 이 값은 연결 훅이 실행되기 전에 설정되고 해제 훅이 나중에 발동해도 다시 정의되지 않습니다. time_duration는 해당 세션의 길이를 초 단위로 제공하므로 끝은 대략 연결 시각에 기간을 더한 값입니다. 둘 다 정수 초이며, 어느 것도 데이터베이스가 이벤트를 처리한 순간이 아닙니다. 이 세 시각을 분리해 두는 것이 상관관계를 테스트 중에만 맞아 보이게 하는 대신 실제로 유지되게 합니다.
이 산술은 훅 자체가 제공하는 유일한 종료 시각이기 때문에 기록할 가치가 있습니다. 처리 지연과 호스트 간 시계 차이는 여전히 그 바깥에 있습니다.
뻔한 조합이 유일하지 않은 이유
이름을 연결 시각과 짝지으면 검색 범위는 좁아지지만 여전히 세션을 식별하지 못합니다. 클라이언트는 같은 초 안에 다시 연결할 수 있으므로 두 세션이 두 값을 모두 공유할 수 있습니다. 두 데몬 인스턴스나 같은 구성을 유지하는 서버 교체가 서로 다른 프로세스에서 그 조합을 만들어낼 수 있습니다. 터널 주소도 같은 이유로 재활용되므로 세션의 식별자가 아니라 속성이 됩니다.
실패 방식은 조용합니다. 첫 세션의 늦은 해제가 최신 행과 일치해 두 번째 세션이 아직 쓰는 할당을 종료하므로, 사용자는 자신이 아무것도 하지 않았는데 무작위로 접근을 잃습니다. 나중에 맞아 보이는 행 수가 문제를 발견하기 어렵게 만듭니다.
안정적인 식별자는 어디서 오는가
관리 인터페이스는 각 클라이언트에 클라이언트 인스턴스 식별자를 부여하며, 그 식별자는 이를 발급한 데몬 인스턴스로 범위가 제한됩니다. 쓸 만한 외부 키는 안정적인 서버 식별자, 데몬이 시작할 때 바뀌는 에포크, 그리고 그 클라이언트 ID를 결합합니다. 에포크가 중요한 이유는 식별자가 프로세스와 함께 다시 시작되며, 맨 프로세스 ID는 나중에 무관한 무언가에 의해 재사용될 수 있기 때문입니다.
자식 연결 스크립트 안에서 설정한 환경 변수는 그 스크립트들이 자체 환경을 가진 별도 프로세스로 실행되기 때문에 부모 데몬의 이후 해제 스크립트로 전달되지 않습니다. 따라서 연결 시 UUID를 생성하는 것은 절반의 해결책입니다. 해제 쪽이 같은 UUID를 지명할 수 있어야 하며, 이는 식별자가 두 이벤트가 모두 도달할 수 있는 곳에 살아야 함을 뜻합니다.
해제를 조건부이고 멱등하게 만드세요
사용자가 아니라 세션 소유자로 해제하세요. 이름에 현재 속한 할당을 무엇이든 삭제하는 핸들러는 결국 잘못된 것을 삭제하며, 유일한 방어는 제거되는 레코드가 이 이벤트가 설명하는 레코드인지 확인하는 것입니다.
관리 인터페이스는 한 번에 하나의 연결된 관리 클라이언트만 받으므로, 같은 소켓을 두고 경쟁하는 두 번째 관찰자를 붙이는 대신 이미 그것을 소유한 쪽과 통합하세요.
반복되는 이벤트를 결과를 두 번 바꾸지 않고 처리하고, 짝이 없는 이벤트는 버리지 말고 조정을 위해 보관하세요. 해제 콜백은 문서화된 수명 주기 조건을 가지며 충돌이나 강제 중지 후에는 영영 오지 않을 수 있으므로, 설계에는 종료 이벤트가 없는 세션을 표현할 방법이 필요합니다.
일 초 안의 재연결 두 번, 데몬 인스턴스 두 개, 처리가 실패한 연결, 세션 도중의 데몬 재시작 같은 까다로운 경우를 일부러 시험하세요. 이 네 가지를 모두 견디는 키만이 신뢰할 만한 키입니다.