iOS VPN이라는 이름에 세 가지 다른 제품이 응답하며, 이들은 같은 API를 공유하지 않습니다. Personal VPN은 시스템에 내장된 IPsec과 IKEv2 전송을 이용합니다. Custom tunnel은 자체 프로토콜을 network extension 안에서 실행합니다. Configuration profile은 둘 중 하나를 구성하고 on-demand 동작을 추가할 수 있습니다. 먼저 컴파일된 자습서로 고르는 것보다 반드시 말해야 하는 전송으로 고르는 편이 더 빠릅니다.

각 단계는 이전 단계가 유효한 구성을 만들었다고 가정하므로 아래 점검을 순서대로 실행하세요.

1단계: 전송으로 고르기

서버가 IKEv2이거나 Cisco 스타일 IPsec이면 NEVPNManager로 충분하며 시스템 전송이 무료로 따라옵니다. 서버가 OpenVPN, WireGuard 또는 다른 것을 말한다면 NEVPNManager는 도울 수 없습니다: 서드파티 프로토콜을 위한 연결 고리가 없기 때문입니다. 그런 경우 프로토콜을 구현하는 packet tunnel provider와 함께 NETunnelProviderManager가 필요합니다. 프로필은 구성만 배포할 뿐이며 운영 체제에 없는 전송을 추가할 수는 없습니다.

PPTP는 현재 시스템에서 대상이 아닙니다. Shared-secret IPsec은 여전히 remoteIdentifier의 그룹 이름, 시크릿을 보관하는 keychain 항목, 서버가 사용자 이름을 요청할 때의 확장 인증을 기대합니다. 빠진 조각은 모두 동일한 일반 구성 오류로 나타나므로 다른 것을 디버깅하기 전에 전부 추가하세요.

2단계: 저장, 로드, 시작 순서를 지키기

NEVPNManager는 preferences에 쓰고 이를 다시 읽은 다음에야 시작합니다. 프로토콜과 manager 객체를 채우고 saveToPreferences를 호출하고 loadFromPreferences를 호출하며 load completion handler에서 startVPNTunnel을 호출하세요. 로드가 돌아오기 전에 시작하면 그 외에는 올바른 구성에서 configuration-invalid 오류, NEVPNErrorDomain code 1이 발생합니다.

형태가 짧아서 한곳에 두기에 충분합니다:

manager.protocolConfiguration = proto
manager.isEnabled = true
manager.saveToPreferences { }
manager.loadFromPreferences { manager.connection.startVPNTunnel() }

startVPNTunnel은 요청이 수락될 때 돌아오며, 터널이 올라올 때 돌아오는 것이 아닙니다. NEVPNStatusDidChange를 관찰하고 실제 상태는 connection.status를 읽으세요. 호출 지점에서 성공을 가정하는 앱은 extension이 아직 협상 중인데도 VPN이 연결되었다고 보고합니다. connecting 상태는 잠깐이면 정상이지만, 그 상태에서 절대 벗어나지 않는 것이 기록할 가치가 있는 실패입니다.

3단계: provider 프로세스에 필요한 것을 주기

Custom transport는 자체 bundle identifier와 자체 entitlement를 가진 별도 프로세스에서 실행됩니다. container 앱과 provider 모두 서명 시 network-extension entitlement가 필요하며, App Store 밖의 Developer ID 빌드도 provisioning profile에 그것을 담아야 합니다. entitlement가 빠지면 extension이 그냥 시작되지 않기 때문에 프로토콜 버그처럼 보입니다.

빌려 쓸 공용 socket descriptor는 없습니다. NEPacketTunnelProvider는 read, write, cancel이 있는 NEPacketTunnelFlow를 주며, 비공개 키 경로에 손을 뻗는 코드는 릴리스 사이에서 깨집니다. 앱이 백그라운드에 있어도 provider는 계속 실행되므로 container 앱에는 이를 위한 백그라운드 모드가 필요 없습니다.

4단계: on-demand는 규칙 집합이지 스위치가 아님

isOnDemandEnabled는 규칙을 무장시킬 뿐이고, 터널이 언제 올라올지는 NEOnDemandRuleConnect와 그 형제들이 결정합니다. probe URL이 있는 evaluate-connection 규칙은 그 URL만큼만 좋습니다. probe가 터널 없이 응답하면 규칙은 사설 네트워크에 이미 도달할 수 있다고 판단하고 절대 연결하지 않으므로, 내부에서만 응답하는 대상으로 지정하세요.

Always-on은 별개의 약속입니다. 감독되는 기기와 관리되는 프로필이 필요하며, 감독되지 않는 사용자는 프로필을 언제든 제거할 수 있습니다. 함대가 감독되지 않는 한 always-on에 의존하는 설계를 출하하지 마세요.

5단계: 라벨이 아니라 경로를 증명하기

tun, tap, ppp 또는 ipsec 인터페이스를 스캔하는 것은 VPN이 구성되었다는 증거일 뿐, 당신의 요청이 그것을 사용했다는 증거가 아닙니다. usesInterfaceType(.other)를 사용하는 NWPathMonitor는 가상 인터페이스가 사용 가능하다고 말하며, viability handler는 호스트 하나를 테스트할 수 있습니다. 어느 것도 출구 경로를 증명하지 않으므로 터널만 도달할 수 있는 리소스에 대한 요청과 결합하세요. 인터페이스 이름은 진단 보조 수단이며, 애플리케이션 로직이 분기해야 할 게이트가 아닙니다.

현장 점검을 위해서는 터널이 있을 때와 없을 때의 공인 주소를 비교하고 내부 전용 페이지를 가져오세요. status 호출은 provider가 협상하는 동안 연결됨으로 보고할 수 있으므로 리소스 결과가 결정하게 하고 status는 보조 증거로 취급하세요.

관련 글