NEVPNManager DNS Problem

Hi,


I'm working on an app that configures a NEVPNProtocolIKEv2 VPN tunnel. The configuration appears to work and the tunnel is started, however dns resolution happens via the default dns servers. How to I get dns resolution to happen via the VPN's dns servers?


In contrast to setting up the vpn tunnel in code, if I create and deploy a configuration profile with the exact same settings then dns resolution happens using the vpn's dns.


Thanks

Alan

Accepted Reply

To start, I want to stress that getting the system DNS servers via these legacy BIND APIs is problematic because these APIs can’t represent the full complexity of iOS’s DNS configuration. You can learn more about it in this thread.

The code is just for debugging …

And that’s a perfectly valid technique, you just have to understand the limitations involved.

Coming back to your main issue, to start I recommend that you read this post, which explains the fundamentals of iOS DNS configuration.

I believe the disparity you’re seeing here is that:

  • In the configuration profile case you’ve set

    OverridePrimary
    , which forces the VPN interface to become primary.
  • In the

    NEVPNManager
    case there is no equivalent of
    OverridePrimary
    so you’re relying on the VPN server to claim the default route (setting up a full tunnel). Doing this will cause it to become the primary interface, and thus make its DNS settings the primary DNS settings.

How you proceed from here really depends on your goals. Configuring your server to set up a full tunnel is one obvious and easy solution to this problem, one that’s equivalent to your current configuration profile setup. If you’re trying to avoid setting up a full tunnel then you have a couple of options:

  • You can use a configuration profile to set up a split tunnel, match domains configuration, or a split tunnel, wildcard match domains configuration (terms in bold are defined in the post I referenced earlier)

  • You can investigate whether it’s possible to set up those configurations by configuring your VPN server

With regards the last point, this isn’t something I can help you with. DTS supports VPN APIs but not VPN configuration (that’s supported by AppleCare), so my understanding of that side of things is rather limited.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

if I create and deploy a configuration profile with the exact same settings then dns resolution happens using the vpn's dns.

Interesting. Most VPN configuration problems I see are reproducible in both

NEVPNManager
and a configuration profile. It’s very unusual to see an issue that’s only reproducible with
NEVPNManager
.

Please post an example of how you’ve configured DNS in

NEVPNManager
and the matching snippets from your configuration profile.

Also, what platform are you working on here?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn,


I'm working on iOS (currently testing on 11.2.5) on iPad and iPhone. Here is how I am installing the VPN:


    private func installVPN(server: String, identityData: Data, identityDataPassword: String, localIdentifier: String) {
        let vpnProtocol = NEVPNProtocolIKEv2()
        let onDemandRule = NEOnDemandRuleConnect()
     
        onDemandRule.interfaceTypeMatch = NEOnDemandRuleInterfaceType.any
     
        vpnProtocol.serverAddress = server
        vpnProtocol.disconnectOnSleep = false
        vpnProtocol.remoteIdentifier = server
        vpnProtocol.localIdentifier = localIdentifier
        vpnProtocol.authenticationMethod = NEVPNIKEAuthenticationMethod.certificate
        vpnProtocol.serverCertificateCommonName = server
        vpnProtocol.enablePFS = true
        vpnProtocol.deadPeerDetectionRate = .medium
        vpnProtocol.certificateType = .RSA
        vpnProtocol.identityDataPassword = identityDataPassword
        vpnProtocol.identityData = identityData
        vpnProtocol.ikeSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group14
        vpnProtocol.ikeSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128
        vpnProtocol.ikeSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA96
        vpnProtocol.ikeSecurityAssociationParameters.lifetimeMinutes = 1440
        vpnProtocol.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group14
        vpnProtocol.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128
        vpnProtocol.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA96
        vpnProtocol.childSecurityAssociationParameters.lifetimeMinutes = 1440
     
        manager.localizedDescription = "Test VPN"
        manager.isOnDemandEnabled = true
        manager.onDemandRules = [onDemandRule]
        manager.protocolConfiguration = vpnProtocol
        manager.isEnabled = true
        manager.saveToPreferences { (error) in
            if error != nil {
                print(error!)
            }
        }
    }


This works ok, in that it installs and starts the VPN but when I check in code what the DNS servers are it returns the local ones. The following profile looks to be configured the same way (I've removed the cert details, server etc) but when installed it uses the VPN DNS servers. The problem is that we are using DNS to configure access etc.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>PayloadContent</key>
  <array>
  <dict>
  <key>DNS</key>
  <dict>
  <key>SearchDomains</key>
  <array>
  <string></string>
  </array>
  <key>SupplementalMatchDomains</key>
  <array>
  <string></string>
  </array>
  </dict>
  <key>IKEv2</key>
  <dict>
  <key>AuthenticationMethod</key>
  <string>Certificate</string>
  <key>ChildSecurityAssociationParameters</key>
  <dict>
  <key>DiffieHellmanGroup</key>
  <integer>14</integer>
  <key>EncryptionAlgorithm</key>
  <string>AES-128</string>
  <key>IntegrityAlgorithm</key>
  <string>SHA1-96</string>
  <key>LifeTimeInMinutes</key>
  <integer>1440</integer>
  </dict>
  <key>DeadPeerDetectionRate</key>
  <string>Medium</string>
  <key>DisableMOBIKE</key>
  <integer>0</integer>
  <key>DisableRedirect</key>
  <integer>0</integer>
  <key>EnableCertificateRevocationCheck</key>
  <integer>0</integer>
  <key>EnablePFS</key>
  <integer>1</integer>
  <key>IKESecurityAssociationParameters</key>
  <dict>
  <key>DiffieHellmanGroup</key>
  <integer>14</integer>
  <key>EncryptionAlgorithm</key>
  <string>AES-128</string>
  <key>IntegrityAlgorithm</key>
  <string>SHA1-96</string>
  <key>LifeTimeInMinutes</key>
  <integer>1440</integer>
  </dict>
  <key>LocalIdentifier</key>
  <string>local id</string>
  <key>OnDemandEnabled</key>
  <integer>1</integer>
  <key>OnDemandRules</key>
  <array>
  <dict>
  <key>Action</key>
  <string>Connect</string>
  <key>InterfaceTypeMatch</key>
  <string>WiFi</string>
  </dict>
  <dict>
  <key>Action</key>
  <string>Disconnect</string>
  </dict>
  </array>
  <key>PayloadCertificateUUID</key>
  <string>dc785ab8-81dd-4d1e-aee1-4f0ff7aae26f</string>
  <key>RemoteAddress</key>
  <string>remote-address</string>
  <key>RemoteIdentifier</key>
  <string>remote-identifier</string>
  <key>ServerCertificateCommonName</key>
  <string>cert common name</string>
  </dict>
  <key>IPv4</key>
  <dict>
  <key>OverridePrimary</key>
  <integer>1</integer>
  </dict>
  <key>PayloadDisplayName</key>
  <string>Test VPN</string>
  <key>PayloadIdentifier</key>
  <string>com.apple.vpn.managed.56c86f12-3a81-44c2-80a6-6e555c7026c7</string>
  <key>PayloadType</key>
  <string>com.apple.vpn.managed</string>
  <key>PayloadUUID</key>
  <string>56c86f12-3a81-44c2-80a6-6e555c7026c7</string>
  <key>PayloadVersion</key>
  <integer>1</integer>
  <key>Proxies</key>
  <dict>
  <key>HTTPEnable</key>
  <integer>0</integer>
  <key>HTTPSEnable</key>
  <integer>0</integer>
  </dict>
  <key>UserDefinedName</key>
  <string>Test</string>
  <key>VPNType</key>
  <string>IKEv2</string>
  </dict>
  <dict>
  <key>Password</key>
  <string>cert password</string>
  <key>PayloadCertificateFileName</key>
  <string>33059064-c0d2-5dc1-a667-68d9cd9fa01e.p12</string>
  <key>PayloadContent</key>
  <data>


  </data>
  <key>PayloadDescription</key>
  <string>Adds a PKCS#12-formatted certificate</string>
  <key>PayloadDisplayName</key>
  <string>PKCS12 Certificate</string>
  <key>PayloadIdentifier</key>
  <string>com.apple.security.pkcs12.dc785ab8-81dd-4d1e-aee1-4f0ff7aae26f</string>
  <key>PayloadType</key>
  <string>com.apple.security.pkcs12</string>
  <key>PayloadUUID</key>
  <string>dc785ab8-81dd-4d1e-aee1-4f0ff7aae26f</string>
  <key>PayloadVersion</key>
  <integer>1</integer>
  </dict>
  </array>
  <key>PayloadDescription</key>
  <string>This profile is part of the Test service. It helps protect your device when on Wi-Fi.</string>
  <key>PayloadDisplayName</key>
  <string>Test VPN</string>
  <key>PayloadIdentifier</key>
  <string>33059064-c0d2-5dc1-a667-68d9cd9fa01e</string>
  <key>PayloadOrganization</key>
  <string>Test</string>
  <key>PayloadRemovalDisallowed</key>
  <false/>
  <key>PayloadType</key>
  <string>Configuration</string>
  <key>PayloadUUID</key>
  <string>47F3A3E6-1FE9-48B8-A078-A743C448C141</string>
  <key>PayloadVersion</key>
  <integer>1</integer>
</dict>
</plist>


Thanks!

Thanks for all the info. I have some feedback on it but before we go there I want to clarify one thing. You wrote:

… when I check in code what the DNS servers are it returns the local ones.

What do you mean by this?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn


I'm using the following code:


- (NSString *)getDNSAddressesStr {
    NSMutableString *addressStr = [[NSMutableString alloc]initWithString:@"DNS Addresses \n"];
   
    res_state res = malloc(sizeof(struct __res_state));
   
    int result = res_ninit(res);
   
    if ( result == 0 )
    {
        for ( int i = 0; i < res->nscount; i++ )
        {
            NSString *s = [NSString stringWithUTF8String :  inet_ntoa(res->nsaddr_list[i].sin_addr)];
            [addressStr appendFormat:@"%@\n",s];
            NSLog(@"%@",s);
        }
    }
    else
        [addressStr appendString:@" res_init result != 0"];
   
    return addressStr;
}


When connected to the VPN that is setup in code this returns the default servers that are associated with the WiFi connection but when connected to the VPN that is configured via the profile it returns the DNS servers from the VPN. The code is just for debugging, I can tell that the correct DNS servers are not being used because certain sites that are supposed to blocked via DNS are not when using the programmatic VPN.


Thanks

Alan

To start, I want to stress that getting the system DNS servers via these legacy BIND APIs is problematic because these APIs can’t represent the full complexity of iOS’s DNS configuration. You can learn more about it in this thread.

The code is just for debugging …

And that’s a perfectly valid technique, you just have to understand the limitations involved.

Coming back to your main issue, to start I recommend that you read this post, which explains the fundamentals of iOS DNS configuration.

I believe the disparity you’re seeing here is that:

  • In the configuration profile case you’ve set

    OverridePrimary
    , which forces the VPN interface to become primary.
  • In the

    NEVPNManager
    case there is no equivalent of
    OverridePrimary
    so you’re relying on the VPN server to claim the default route (setting up a full tunnel). Doing this will cause it to become the primary interface, and thus make its DNS settings the primary DNS settings.

How you proceed from here really depends on your goals. Configuring your server to set up a full tunnel is one obvious and easy solution to this problem, one that’s equivalent to your current configuration profile setup. If you’re trying to avoid setting up a full tunnel then you have a couple of options:

  • You can use a configuration profile to set up a split tunnel, match domains configuration, or a split tunnel, wildcard match domains configuration (terms in bold are defined in the post I referenced earlier)

  • You can investigate whether it’s possible to set up those configurations by configuring your VPN server

With regards the last point, this isn’t something I can help you with. DTS supports VPN APIs but not VPN configuration (that’s supported by AppleCare), so my understanding of that side of things is rather limited.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Ok, thanks Quinn. Will look in to the full tunnel option.


Cheers

Alan