NEPacketTunnelFlow: large UDP DNS responses (~893 bytes) silently dropped despite writePacketObjects() returning success

I'm using NEPacketTunnelProvider to intercept DNS queries, forward them upstream,
and inject the responses back via writePacketObjects().

This works correctly for responses under ~500 bytes. For larger responses (~893
bytes, e.g. DNS CERT records), writePacketObjects() returns without error but
mDNSResponder never receives the packet — it retries 3–4 times and then times out.

What I have verified:

  • IP and UDP checksums are correct
  • UDP length and IP total length fields are correct
  • Maximum packet size (MTU) set to 1500 in NEIPv4Settings/NEIPv6Settings

Approaches tried:

  • Injecting the full 921-byte packet — writePacketObjects() succeeds but the packet never reaches mDNSResponder
  • IP-level fragmentation — fragments appear to be silently dropped
  • Setting the TC (truncation) bit — mDNSResponder does not retry over TCP
  • Truncating the response to ≤512 bytes — the packet arrives but the data is incomplete

Questions:

  1. Is there a supported way to deliver a DNS response larger than 512 bytes through NEPacketTunnelFlow?
  2. Does NEPacketTunnelProvider impose an undocumented packet size limit below the configured MTU?
  3. Does mDNSResponder silently discard responses larger than 512 bytes when the original query had no EDNS0 OPT record? Is there a way to signal that larger responses are supported?
  4. Are IP-level fragments reliably delivered through writePacketObjects()?

Tested on iOS 26.3, physical device.

Answered by DTS Engineer in 878085022
I'm using NEPacketTunnelProvider to intercept DNS queries, forward them upstream

What does “upstream” mean in this context?

Most VPN products don’t need to mess with DNS. Rather, the packet tunnel provider forwards DNS packets to the VPN server which forwards them to their destination, just like any other packet. And the same process works in reverse.

If you want to intercept just DNS traffic, a DNS proxy is a much better option. DNS proxies receive flows rather than packets. And in the case of DNS over UDP, the flow works in terms of datagrams rather than packets.

I see a lot of folks try to use a packet tunnel provider for things other than VPN, for example, as an ersatz DNS proxy. This isn’t something that DTS supports. See TN3120 Expected use cases for Network Extension packet tunnel providers.

Share and Enjoy

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

I'm using NEPacketTunnelProvider to intercept DNS queries, forward them upstream

What does “upstream” mean in this context?

Most VPN products don’t need to mess with DNS. Rather, the packet tunnel provider forwards DNS packets to the VPN server which forwards them to their destination, just like any other packet. And the same process works in reverse.

If you want to intercept just DNS traffic, a DNS proxy is a much better option. DNS proxies receive flows rather than packets. And in the case of DNS over UDP, the flow works in terms of datagrams rather than packets.

I see a lot of folks try to use a packet tunnel provider for things other than VPN, for example, as an ersatz DNS proxy. This isn’t something that DTS supports. See TN3120 Expected use cases for Network Extension packet tunnel providers.

Share and Enjoy

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

Thanks for the response. By 'upstream' I mean the public DNS resolver we forward queries to (e.g. Cloudflare 1.1.1.1).

I'm aware of NEDNSProxyProvider, but per Apple's own documentation it requires supervised devices (iOS 11–15) or managed devices (iOS 16+), making it unavailable for consumer App Store distribution. NEPacketTunnelProvider is the only viable option for our use case.

The specific issue: when the public DNS resolver returns a large response (~893 bytes for CERT records), injecting it via writePacketObjects() results in mDNSResponder receiving the packet but not delivering the record to the application. Responses under 512 bytes work correctly. Is there a supported way to deliver DNS responses larger than 512 bytes through NEPacketTunnelFlow?"

NEPacketTunnelFlow: large UDP DNS responses (~893 bytes) silently dropped despite writePacketObjects() returning success
 
 
Q