NEPacketTunnelProvider doesn't keep the DNS packets flowing on iOS

I have a network extension setup on my app using the NEPacketTunnelProvider Network Extension.

I have set its DNS settings like so, to override all system DNS settings:

let dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "8.8.4.4"])
dnsSettings.matchDomains = [""]
tunnelNetworkSettings.dnsSettings = dnsSettings

After that, I am catching all DNS network packets (aka UDP, port 53) using

tunnelProvider.packetFlow.readPackets { ... }

When the packets are incoming, I am stripping them of their headers and sending their payload (DNS Query) to the DNS servers using

currentUDPSession.session.writeMultipleDatagrams(packetPayloadsToSend) { ... }

And receiving back the answers using the callback on NWUDPSession

currentUDPSession.session.setReadHandler { ... }

Then, on the received datagram payloads (DNS Answer) I am rebuilding the UDP headers (whether it's IPv4 or IPv6, I'm supporting both), and writing them back to the TUN interface like so:

// Examples of the data I'm passing. I'm populating it with VALID rebuilt packets beforehand
let rebuiltPackets = [Data]()
let rebuiltPacketsProtocols = [NSNumber]()

tunnelProvider.packetFlow.writePackets(rebuiltPackets, withProtocols: rebuiltPacketsProtocols)

On paper, everything looks great and sniffing those packets using Wireshark shows that everything should work. But here's the thing, It doesn't!

When trying to access any website on the iPhone I am getting:

Safari could not open the page because the server stopped responding.

Here's a screenshot of the Wireshark PCAP for trying to access twitter.com:

IP address 10.0.0.43 is my tunnel, while 172.20.100.155 is my iPhone's local network IP address. Not sure why it shows both tunnel and OS DNS packets, but someone told me it's regular iOS behavior.

Any ideas? Thanks!

It looks like you’re creating a packet tunnel provider solely to capture DNS traffic. That’s not something we support because, to quote TN3120 Expected use cases for Network Extension packet tunnel providers:

Avoiding the unsupported scenarios will save your project many edges cases and bugs during the development and deployment process.

If you want to capture DNS traffic, use a DNS proxy provider.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

If you want to capture DNS traffic, use a DNS proxy provider.

But isn't using the DNS proxy provider requires a special permission from Apple? I'm developing an SDK that will be used as by 3rd party clients. This is why I decided to go with the Packet Tunnel provider instead.

But isn't using the DNS proxy provider requires a special permission from Apple?

Yes. This was a deliberate policy choice…

This is why I decided to go with the Packet Tunnel provider instead.

… which you’re trying to subvert.

One of the things about supporting an API is that there has to be an escalation path. If you ask DTS a question and we don’t know the answer, we have to be able to ask the NE engineering team. And if something doesn’t work, we have to be able to suggest that you file a bug. Neither of these is possible in this case because you’re building a product that’s counter to the overall goals of the system. And that’s why DTS doesn’t support this, and why we wrote TN3120.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

One thing I don't understand here, though, is that if you don't set up DNS settings in the packet tunnel provider, the packet tunnel provider simply doesn't work. No traffic goes through the packet flow, hence you can't route the packets through the tunnel. Try to make an http request and you get an immediate fail with "the internet connection appears to be offline", no packet seen in the flow. If you do set up the DNS settings, you will start to see DNS requests going through the packet tunnel provider, and that is specifically against TN3120. I would love not having to deal with DNS requests, but I can't find a way to set up the packet tunnel provider in a way that is transparent to them. Adding the DNS you set up in the excluded routes does nothing to prevent this to happen. The only way I see packets running through the packet tunnel provider without setting up DNS routes is if I set up a local proxy (and not just by putting bogus proxy settings, I mean actually having a proxy running), but the same TN3120 says you can't put a proxy in the packet tunnel provider. What's more puzzling, is that both Proxyman, Charles and Surge clearly do have a proxy running in the packet tunnel provider, Proxyman by own admission of the developer on StackOverflow.

What gives?

@MarcoCarandenteDCL I wish the modes of operation were documented better. I think you're misunderstanding how the tunnel works.

If you operate in destination IP mode, then any packets with a destination IP covered by one of your tunnel's routes will appear to your packet tunnel provider, regardless of DNS settings. And that might be the default route (all packets which don't match a more specific route).

If you add dns servers and set the matchDomains on the NEDNSSettings to include [""], which I guess means "match all", then your configured dns servers will be tried first, and the system-configured DNS failing that. The presumption here would be that your tunnel services a network that includes the configured DNS servers addresses.

It is not against TN3120 for DNS to go over the tunnel. The expectation is that you just encapsulate those packets in your protocol and send them to the remote network(s) on the other end of the tunnel(s). I believe the spirit of what TN3120 is saying is that you shan't run a DNS proxy server locally in the packet tunnel provider. Should you wish to proxy DNS, thou shalt implement a DNSProxyProvider, (or a general [transparent] proxy provider on macOS) and perform any local proxying logic there.

What the network dev tools you mention do, running a proxy in the packet tunnel provider, is technically possible, yes. It requires a userspace network stack, though. And it's not a setup or usage of the API that Apple can officially support, based on engineer comments on these forums. You are in "here be dragons" land and you can pretty easily mess up the network connectivity on the device, cause a really poor UX, and find yourself in edge cases that haven't been designed for. But where there's a will there's quite often a way (:

If you're primarily targeting macOS, then check out the NETransparentProxyProvider. It is what mitmproxy (https://mitmproxy.org) uses to proxy all device traffic on macOS in as far as I can tell an Apple-supported/official way. Notably this is not supported on iOS and I have no idea if that is by design or not.

NEPacketTunnelProvider doesn't keep the DNS packets flowing on iOS
 
 
Q