NEVPNManager não salva? A ordem é carregar, ativar, salvar, conectar
Os códigos 4 e 5 geralmente significam que o perfil foi editado antes de ser carregado ou salvo enquanto estava desativado. Corrija a ordem das chamadas e depois verifique o entitlement.
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.