Dois códigos de erro explicam a maioria das primeiras tentativas com uma VPN Network Extension no iOS: NEVPNErrorDomain Code=4 e Code=5. Nenhum deles é uma reclamação do servidor. Um aparece quando o app grava ajustes num objeto que nunca foi lido do disco, e o outro quando a própria gravação falha, quase sempre porque o perfil é salvo enquanto ainda está desativado. Ambos se corrigem mudando a ordem de quatro chamadas, não mexendo nos parâmetros do IKEv2.

Carregue primeiro, ou o salvamento não tem com o que comparar

O gerenciador mantém uma cópia do perfil armazenado, e o objeto que você edita não é essa cópia até você lê-la. Chame loadFromPreferences primeiro, edite dentro do handler de conclusão e depois salve. Se pular a leitura, o framework vê uma divergência entre o que tem e o que você escreve. No Swift a forma é a mesma em cada etapa, e cada etapa pertence ao seu próprio closure:

let mgr = NEVPNManager.shared()
mgr.loadFromPreferences { loadErr in
    guard loadErr == nil else { return }
    let proto = NEVPNProtocolIKEv2()
    proto.serverAddress = "vpn.example.net"
    proto.username = "alice"
    proto.useExtendedAuthentication = true
    mgr.protocolConfiguration = proto
    mgr.isEnabled = true
    mgr.saveToPreferences { saveErr in
        // do not start here; reload, then connect
    }
}

Iniciar o túnel de dentro do closure de salvamento é o mesmo erro em outro lugar. Isso roda antes de o perfil ser lido novamente, então a chamada de início vê o estado que você montou e não o estado que foi salvo. O closure de salvamento termina com um segundo loadFromPreferences, e só esse handler de conclusão chama startVPNTunnel.

O sinalizador de ativação faz parte do salvamento

Um perfil salvo é inerte até ser ligado. Defina isEnabled = true na mesma gravação, ou o perfil chega ao disco desativado e startVPNTunnel recusa mesmo que Ajustes mostre uma configuração que parece completa.

As edições externas são a outra metade. Se o usuário mudar algo em Ajustes, ou o dispositivo se deslocar entre redes, leia novamente antes de confiar nos valores que ainda mantém na memória. O objeto não observa essas mudanças por você. Uma cópia desatualizada é como um app grava um perfil que desfaz silenciosamente a edição do usuário. Dois campos adjacentes causam problema silencioso no mesmo momento: as regras on-demand devem ser um array de objetos de regra e não um marcador vazio, e disconnectOnSleep deve refletir o comportamento que você escolheu e não um padrão copiado.

Apenas dois protocolos estão disponíveis para apps de terceiros

Network Extension dá aos apps IPsec e IKEv2. L2TP e PPTP ficam dentro do sistema, então um app que precise deles não pode tê-los, e as respostas realistas são um gateway compatível com IKEv2 ou um perfil de configuração gerenciado.

As credenciais seguem o mesmo padrão. Coloque a senha no Keychain e passe uma referência persistente em vez da string. Uma referência nula ao salvar geralmente significa que a busca rodou antes de o item ser gravado, não que o gerenciador esteja quebrado.

Ambas são decisões de projeto e não soluções improvisadas. Um app que guarda a senha nas próprias preferências escolhe um caminho que o framework desaconselha, e a falha que produz é lida como um erro de salvamento.

Descarte o entitlement antes de reescrever código

Quando loadFromPreferences nunca chama de volta, a causa provável é uma capacidade Network Extension ausente ou um perfil de provisionamento que não a carrega. Essa falha trava em vez de dar erro, e por isso código que funciona é reescrito por horas. Verifique o entitlement na build assinada que você pretende enviar, porque uma execução no simulador e uma no dispositivo podem diferir e o alvo que importa é o dispositivo. Abra as configurações de assinatura do alvo e confirme que a capacidade Network Extension está presente para exatamente aquela build.

Um perfil de provisionamento sem o entitlement falha do mesmo jeito no hardware, enquanto a mesma build pode carregar normalmente no simulador. Teste o arquivo assinado num dispositivo real antes de reescrever a ordem das chamadas.

Observe as mudanças de estado, não as suposições

Observe NEVPNStatusDidChangeNotification e registre manager.connection.status depois de cada etapa. Um estado que nunca sai de invalid significa que o perfil não foi salvo ou que o entitlement está ausente. Uma conexão que chega a connecting e nunca conecta aponta para a proposta ou para as credenciais, que é outra investigação com outras evidências.

Confirme que o perfil aparece em Ajustes, Geral, VPN, e registre o erro de cada closure. Esses dois fatos separam um bug de ordem de um bug de permissão sem adivinhação.

Artigos relacionados