Dos códigos de error explican la mayoría de los primeros intentos con una VPN Network Extension en iOS: NEVPNErrorDomain Code=4 y Code=5. Ninguno es una queja del servidor. Uno aparece cuando la app escribe ajustes en un objeto que nunca se leyó del disco, y el otro cuando falla la propia escritura, casi siempre porque el perfil se guarda mientras sigue desactivado. Ambos se corrigen cambiando el orden de cuatro llamadas, no tocando los parámetros de IKEv2.

Cargue primero, o el guardado no tiene con qué comparar

El gestor guarda una copia del perfil almacenado, y el objeto que edita no es esa copia hasta que la lee. Llame primero a loadFromPreferences, edite dentro de su manejador de finalización y luego guarde. Si se salta la lectura, el marco ve una discrepancia entre lo que tiene y lo que escribe. En Swift la forma es la misma en cada paso, y cada paso pertenece a su propio 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 el túnel desde dentro del closure de guardado es el mismo error en otro sitio. Se ejecuta antes de que el perfil se haya vuelto a leer, así que la llamada de inicio ve el estado que construyó y no el estado que se guardó. El closure de guardado termina con un segundo loadFromPreferences, y solo ese manejador de finalización llama a startVPNTunnel.

El indicador de activación forma parte del guardado

Un perfil guardado es inerte hasta que se enciende. Defina isEnabled = true en la misma escritura, o el perfil llega al disco desactivado y startVPNTunnel se niega aunque Ajustes muestre una configuración que parece completa.

Las ediciones externas son la otra mitad. Si el usuario cambia algo en Ajustes, o el dispositivo se mueve entre redes, vuelva a leer antes de fiarse de los valores que aún mantiene en memoria. El objeto no observa esos cambios por usted. Una copia obsoleta es como una app escribe un perfil que revierte en silencio la edición del usuario. Dos campos adyacentes causan problemas silenciosos al mismo tiempo: las reglas on-demand deben ser una matriz de objetos de regla y no un marcador vacío, y disconnectOnSleep debe reflejar el comportamiento que eligió y no un valor predeterminado copiado.

Solo dos protocolos están disponibles para apps de terceros

Network Extension da a las apps IPsec e IKEv2. L2TP y PPTP se quedan dentro del sistema, así que una app que los necesite no puede tenerlos, y las respuestas realistas son una puerta de enlace compatible con IKEv2 o un perfil de configuración gestionado.

Las credenciales siguen el mismo patrón. Ponga la contraseña en el Keychain y pase una referencia persistente en lugar de la cadena. Una referencia nula al guardar suele significar que la búsqueda se ejecutó antes de escribir el elemento, no que el gestor esté roto.

Ambas son decisiones de diseño y no soluciones improvisadas. Una app que guarda la contraseña en sus propias preferencias elige un camino que el marco desaconseja, y el fallo que produce se lee como un error de guardado.

Descarte el entitlement antes de reescribir código

Cuando loadFromPreferences no devuelve nunca la llamada, la causa probable es una capacidad Network Extension ausente o un perfil de aprovisionamiento que no la lleva. Ese fallo se cuelga en lugar de dar error, y por eso se reescribe código que funciona durante horas. Revise el entitlement en la compilación firmada que piensa entregar, porque una ejecución en el simulador y otra en el dispositivo pueden diferir y el objetivo que importa es el dispositivo. Abra los ajustes de firma del objetivo y confirme que la capacidad Network Extension está presente para esa compilación exacta.

Un perfil de aprovisionamiento sin el entitlement falla igual en hardware, mientras que la misma compilación puede cargarse con normalidad en el simulador. Pruebe el archivo firmado en un dispositivo real antes de reescribir el orden de las llamadas.

Observe los cambios de estado, no las suposiciones

Observe NEVPNStatusDidChangeNotification y registre manager.connection.status después de cada paso. Un estado que nunca sale de invalid significa que el perfil no se guardó o que falta el entitlement. Una conexión que llega a connecting y nunca conecta apunta a la propuesta o a las credenciales, que es otra investigación con otras pruebas.

Confirme que el perfil aparece en Ajustes, General, VPN, y registre el error de cada closure. Esos dos hechos separan un error de orden de un error de permisos sin adivinar.

Artículos relacionados