DNS settings not work for tunnel

Hello,


I'm trying to set DNS servers for my packet tunnel but it looks like OS ignores them. Here is how I set DNS servers:


// configrations is a POJO with some tunnel information
NEPacketTunnelNetworkSettings *settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:configuration.remoteAddr];
settings.IPv4Settings = [[NEIPv4Settings alloc] initWithAddresses:@[configuration.localAddr] subnetMasks:@[@"255.255.255.0"]];

BOOL isFullTunnel = YES;
if (configuration.routes.count > 0) {
    // create routes and add them
    settings.IPv4Settings.includedRoutes = routes;
    isFullTunnel = NO;
} else {
    settings.IPv4Settings.includedRoutes = @[[NEIPv4Route defaultRoute]];
}

if (configuration.dnsServers.count > 0) {
    settings.DNSSettings = [[NEDNSSettings alloc] initWithServers:configuration.dnsServers];
}

[self setTunnelNetworkSettings:settings completionHandler:^(NSError * _Nullable error) {
    // some next actions
}];


When I start packet tunnel, it works fine and OS route networks packets right, but DNS requests go to defaul server (from network interface I guess), not to configured DNS servers.


Here is how it looks like in tcpdump:

375 105.231826 192.168.2.2 192.168.2.1 DNS 72 Standard query 0xf1d6 A web.test


IP address 192.168.2.2 is my iOS device with started packet tunnel and 192.168.1.1 is IP address of device, I connected to by Wi-Fi (Wi-Fi hotspot) and "web.test" is hostname I want to get IP address for. Why OS ignores my DNS servers settings?

Replies

I met the same issue. Did you find a workaround for it? I think that we need to submit a bug report to Apple.

So it seems that it only works if [NEIPv4Route defaultRoute] is in includedRoutes. I haven't found better workaround then add [NEIPv4Route defaultRoute] into included routes. And everything else, except your virtual network into exluded routes.

My vpn uses 10.0.0.0/8, so that what I did:

self.settings.IPv4Settings.includedRoutes = @[[NEIPv4Route defaultRoute]];
self.settings.IPv4Settings.excludedRoutes = @[
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"0.0.0.0" subnetMask:@"248.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"8.0.0.0" subnetMask:@"255.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"9.0.0.0" subnetMask:@"255.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"11.0.0.0" subnetMask:@"255.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"12.0.0.0" subnetMask:@"252.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"16.0.0.0" subnetMask:@"240.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"32.0.0.0" subnetMask:@"240.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"48.0.0.0" subnetMask:@"240.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"64.0.0.0" subnetMask:@"192.0.0.0"],
                                                       [[NEIPv4Route alloc] initWithDestinationAddress:@"128.0.0.0" subnetMask:@"128.0.0.0"],
                                                       ];

I’ve been researching this as part of various DTS incidents.

So it seems that it only works if [NEIPv4Route defaultRoute] is in includedRoutes.

The above is correct as far as it goes. In general iOS uses the DNS servers for the primary interface so, when your VPN claims the default route, it becomes the primary interface and thus its DNS servers are used.

If you don’t want to become the default route (that is, you want to create a split tunnel rather than a full tunnel) you have two options:

  • You can configure specific match domains (via NEDNSSettings’s

    matchDomains
    property. When you do this, iOS will use your tunnel’s DNS server for any server inside those domains.

    For example, the VPN for Waffle Varnishing Inc might list

    waffle-varnishing.com
    ,
    waffle-varnishing.some-cdn.com
    ,
    waffle-varnishing.co.uk
    , and so on in its match domains.

    [Oooh,

    waffle-varnishing.com
    isn’t registered, maybe I should register it!}
  • If you add an empty match domain (that is, set the property to an array containing an empty string), your DNS server is consulted before the primary interface’s DNS server.

So, you normally set up a VPN in one of three ways:

  • full tunnel — The VPN is the primary interface and its DNS server is used by default.

  • split tunnel, match domains — The VPN is not the primary interface and the system consults its VPN server for just the specified match domains.

  • split tunnel, wildcard match domains — The VPN is not the primary interface and the system consults its DNS server for all domains first.

Share and Enjoy

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

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

And as already checked, this


If you add an empty match domain (that is, set the property to an array containing an empty string), your DNS server is consulted before the primary interface’s DNS server.


Doesn't work for some cases too.

I can confirm that adding an empty match domain doesnt help.


    self.settings.IPv4Settings.includedRoutes = @[[[NEIPv4Route alloc] initWithDestinationAddress:@"10.0.0.0" subnetMask:@"255.0.0.0"]];
    self.settings.IPv4Settings.excludedRoutes = @[[NEIPv4Route defaultRoute]];
    self.settings.DNSSettings = [[NEDNSSettings alloc] initWithServers:@[@"10.9.0.2"]];
    self.settings.DNSSettings.matchDomains = @[@""];

One bit of data that I can share is that you do need to make sure that the IP of your DNS server is included in the routes that are associated with your VPN tunnel.

But that's what I did in my code. Didn't I?

My dns is 10.9.0.2, included routes are 10.0.0.0/8.

I created simple program for DNS test:


- (IBAction)sendQuery:(id)sender
{
    [self printDNSServers];

    CFHostRef host = CFHostCreateWithName(NULL, (CFStringRef)@"web.mobile");

    CFStreamError error;
    if (!CFHostStartInfoResolution(host, kCFHostAddresses, &error))
    {
        NSLog(@"can't start resolution: %ld %d", error.domain, (int)error.error);
        return;
    }

    Boolean resolved;
    CFArrayRef addresses = CFHostGetAddressing(host, &resolved);
    if (addresses != NULL)
    {
        NSLog(@"resolved: %d", resolved);
    }
}

- (void)printDNSServers
{
    res_state res = malloc(sizeof(struct __res_state));
    int result = res_ninit(res);
    if (result == 0)
    {
        NSLog(@"number of name servers: %d", res->nscount);
        for (int i = 0; i < res->nscount; i++)
        {
            NSLog(@"name server IP: %@", [NSString stringWithUTF8String: inet_ntoa(res->nsaddr_list[i].sin_addr)]);
        }
    }
    free(res);
}


When I run app without my VPN app, I see following:


DNSTestApp: number of name servers: 1

DNSTestApp: name server IP: 192.168.1.1

DNSTestApp: can't start resolution: 12 8


So everything is correct. I connected to Wi-Fi spot with IP "192.168.1.1" to Internet and there is no such custom hostname "web.mobile" in public DSN servers. Now I start my VPN app and push button again:


DNSTestApp: number of name servers: 1

DNSTestApp: name server IP: 100.1.1.3

DNSTestApp: can't start resolution: 12 8


Test app shows correct IP address of my VPN DNS server "100.1.1.3" but for some reason still can't resolve custom hostname to IP address. Also I can't find any description about error with domain 12 and code 8.

since ios 9.3


split tunnel, wildcard match domains — The VPN is not the primary interface and the system consults its VPN server for all domains first.


seems not to be true anymore if the entry exist in the public DNS is takes this one and doesn't start the VPN tunnel. If it doesn't exist it take the DNS from the VPN and connects to the VPN

Hi, i do facing the same problem. did you resolve this issue?

Hi eskimo, what if I want to use full tunnel

(full tunnel — The VPN is the primary interface and its DNS server is used by default), but I want to decide for each query, by the query's domain which DNS server to use (the VPN or the system). Is it doable? And can I do it via Packet tunnel provider, or only with NEDNSProxyProvider (which is problematic for me because it's limited only to supervised devices).

Hello, Eskimo


Thank you for explaination!


I have tested that all you said is perfectly match the device packet-tunnel behavior, but per-app packet tunnel seems doesn't follow what you said and has different behavior on my MacBook and my customer test MacBook (both are v10.14.6) when tested with Chrome browser app.


In my MacBook, all DNS requests go to device DNS via device primary interface (not VPN) no matter of wether matchdomains is empty string or "mydomain.com". But in my customer test MacBook, all DNS requests go to the VPN DNS no matter of wether matchdomains is empty string or "hisdomain.org;hisdoman.biz".


I also tried define IPv4 routes (10.0.0.0/8) for per-app packet-tunnel even though i know it does work for TCP/UDP traffic routing, but I just want to help for DNS resolving because the customer VPN DNS can only resolve their own internal FQDN, not public FQDN.


Is that only device VPN is the solution for this packet-tunnel situation? Is the DNSProxy NE can be another solution co-working with packet-tunnel on BYOD devices (not only the supervised devices)?


Thank you!


Paul Ling

This works for me. Tested on iOS 13.2.3.


settings.DNSSettings = [[NEDNSSettings alloc]initWithServers: @[@"8.8.8.8"]];
settings.DNSSettings.matchDomains = @[@""];


And IPv4Settings.includedRoutes should include 8.8.8.8 for sure.


There are some tested but not working cases for DNSSettings.matchDomains below:

nil
@[]
@[@"*"]
@[@"."]


Hope this helps.