훅 스크립트가 터널 반대편에 도달할 수 있는지는 어떤 이벤트에 연결되어 있는지, 스크립트가 실행될 때 OpenVPN이 아직 시작 순서 안에 있는지, 그리고 그 작업이 순서에서 분리되었는지라는 세 가지 조건에 달려 있다. 대부분의 스크립트는 세 번째에서 실패한다. 잠을 더하는 것은 수명 주기 문제를 타이밍 문제로 다루는 것이며, 오해를 부를 만큼만 작동한다.

먼저 이벤트를 정하고, 다음으로 작업을 지연하는 메커니즘을, 마지막으로 지연된 작업이 실행되는 장소를 정한다. 이 세 결정은 독립적이며, 그것을 섞는 것이 두 줄짜리 훅을 반나절로 만드는 방법이다.

훅이 자신을 기다리는 터널을 기다리는 이유

OpenVPN패킷을 옮기는 같은 프로세스 안에서 훅을 동기적으로 호출한다. up 은 tun 또는 tap 장치가 생긴 뒤지만 어떤 경로도 추가되기 전에 발생하고, route-up 은 경로 뒤에 발생한다. 두 순간 중 어느 것도 상대가 응답한다고 약속하지 않으며, 어느 훅의 스크립트든 아직 실행 중이면 데몬은 전달하지 않으므로 트래픽이 터널을 지나기를 기대하는 명령은 그 명령을 기다리는 터널을 기다린다. 이 순환 대기가 수동 실행은 통과하는데 훅은 시간 초과되는 이유다.

명백한 처방인 몇 초의 잠은 때때로 작동하지만, 그것은 먼 쪽이 스스로 올라왔기 때문이며 잠이 무언가를 고쳤기 때문이 아니다. 준비 상태를 묻는 도우미는 다른 답으로, 훅은 돌아오고 전달이 시작되며 도우미는 그것이 실제로 있을 때 필요한 것을 얻는다.

편의가 아니라 이벤트로 훅을 고른다

up 은 장치만 필요한 작업에, route-up 은 경로가 필요한 작업에 연결하고, client-connect 과 client-disconnect 은서버 쪽의 클라이언트별 작업을 위해 남겨 둔다. 연결 해제 시 정리는, 지속 인터페이스를 통한 재연결이 완전한 재시작과 같은 순서를 따르지 않기 때문에, 두 번 실행되어도 견뎌야 한다. 서버에서는 client-connect 이 데몬이 시작할 때가 아니라 클라이언트가 인증할 때 발생하며, 논리를 그쪽으로 옮기기 전에 확인할 가치가 있다. 작업이 단일 연결이 아니라 서비스의 수명 주기에 속한다면 systemd 유닛이 어떤 훅보다 나은 자리다.

훅 명령은 데몬이 직접 실행하며 그 사이에 셸이 없으므로, up 줄의 ifconfig_remote 은 문자 그대로의 텍스트로 도착한다. 명령을 명시적 셸로 감싸거나 논리를 환경을 읽는 스크립트에 둔다. ipchange 은 자체 인수를 더하는 하나의 지시문으로, 주소와 포트를 명령에 넘기므로 어느 것도 염두에 두지 않은 스크립트는 예상치 못한 두 개를 본다.

작업을 분리하는 세 가지 방법

첫 번째 선택지는 훅 안에서 도우미를 백그라운드로 보내 setsid 또는 systemd-run --scope 로 시작하고, 상속된 디스크립터를 닫고, 즉시 성공 상태로 종료하는 것이다. 두 번째는 훅이 작은 상태 파일을 쓰고 systemd 타이머나 경로 유닛이 그에 따라 행동하게 하는 것으로, 훅은 사소하게 유지되고 재시도 정책은 로그가 있는 곳에 놓인다. 세 번째는 훅을 완전히 버리고 작업을 VPN 서비스 뒤에 정렬된 유닛으로 옮기는 것으로, 작업이 애초에 연결별이 아닐 때 옳은 답이다. 셋 모두 훅이 터널이 필요해지기 전에 돌아와야 한다는 한 가지 규칙을 공유한다.

무엇을 고르든 도우미를 멱등적이고 관찰 가능하게 만든다. 겹치는 두 호출이 같은 되돌릴 수 없는 동작을 두 번 실행해서는 안 되고, 실패하는 도우미는 사람이 읽을 수 있는 것을 남겨야 한다.

샌드박스 실패와 수명 주기 실패를 구분한다

스크립트가 root 프롬프트에서 실행되어 서비스로 실패할 때, 원인은 보통 네트워크가 아니라 샌드박스다. 제한적인 ProtectHome, 읽기 전용 ProtectSystem, 또는존재하지 않는 작업 디렉터리는 스크립트나 그 데이터를 닿을 수 없는 곳에 둘 수 있고, SELinux 호스트에서는 파일에 실행을 허용하는 컨텍스트도 필요하다. 유닛은 systemctl cat openvpn-server@server 로, 저널은 journalctl -u openvpn-server@server -n 80 로 읽고, 연결 시간 초과가 아니라 권한이나 컨텍스트 오류를 찾는다. 샌드박스를 끄는 것은 첫 번째가 아니라 마지막 수단이어야 한다.

수명 주기 실패는 다르게 보이는데, 훅은 시작되고, 도우미는 돌고, 소켓은 그저 아직 없을 뿐이다. 훅 자체에서 타임스탬프가 있는 줄을 쓰고, 연결을 끊었다 다시 연결하고, 이벤트를 순서대로 읽는다. 도우미가 정말 터널을 필요로 한다면 고정된 시간 동안 자는 대신 준비 상태를 묻게 한다.

세 항목, 즉 어느 훅이 실행되었는지, 어느 계정을 사용했는지, 어디서 멈췄는지가 다음 보고를 유용하게 만든다. 그것이 없으면 이후의 모든 조사는 처음부터 다시 시작된다.

관련 글