I'm implementing a NEDNSProxyProvider on macOS 15.x and macOS 26.x. The flow works correctly up to the last step — returning the DNS response to the client via writeDatagrams.
Environment:
- macOS 15.x, 26.x
- Xcode 26.x
- NEDNSProxyProvider with NEAppProxyUDPFlow
What I'm doing:
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
guard let udpFlow = flow as? NEAppProxyUDPFlow else { return false }
udpFlow.readDatagrams { datagrams, endpoints, error in
// 1. Read DNS request from client
// 2. Forward to upstream DNS server via TCP
// 3. Receive response from upstream
// 4. Try to return response to client:
udpFlow.writeDatagrams([responseData], sentBy: [endpoints.first!]) { error in
// Always fails: "The datagram was too large"
// responseData is 50-200 bytes — well within UDP limits
}
}
return true
}
Investigation:
I added logging to check the type of endpoints.first :
// On macOS 15.0 and 26.3.1:
// type(of: endpoints.first) → NWAddressEndpoint
// Not NWHostEndpoint as expected
On both macOS 15.4 and 26.3.1, readDatagrams returns [NWEndpoint] where each endpoint appears to be NWAddressEndpoint — a type that is not publicly documented.
When I try to create NWHostEndpoint manually from hostname and port, and pass it to writeDatagrams, the error "The datagram was too large" still occurs in some cases.
Questions:
- What is the correct endpoint type to pass to
writeDatagramson macOS 15.x, 26.x? - Should we pass the exact same
NWEndpointobjects returned byreadDatagrams,or create new ones? - NWEndpoint, NWHostEndpoint, and writeDatagrams are all deprecated in macOS 15. Is there a replacement API for NEAppProxyUDPFlow that works with nw_endpoint_t from the Network framework?
- Is the error "The datagram was too large" actually about the endpoint type rather than the data size?
Any guidance would be appreciated. :-))
Interesting.
Honestly, I’m skeptical that the specific flavour of API you’re using will affect this. The APIs you call are just wrappers that convert the old flavour of endpoint to the new flavour, or vice versa. For example, the -writeDatagrams:sentByFlowEndpoints:completionHandler: method is marked as ‘refined for Swift’ [1] and then there’s a Swift version that looks like this:
func writeDatagrams(_ array: [(Data, Network.NWEndpoint)], completionHandler: @escaping (Error?) -> Void) {
let datagrams = array.map { $0.0 }
let endpoints = array.map { oldEndpointFromNewEndpoint($0.1) }
__writeDatagrams(datagrams, sentByFlow: addresses, completionHandler: completionHandler)
}
where:
oldEndpointFromNewEndpoint(…)is a function that converts from the NW to the NE endpoint flavour, and__writeDatagrams(…)is the ‘hidden’ version of the original Objective-C method.
But that leaves me at a loss to explain the behaviour you’re seeing. I had a look at the original of the The datagram was too large string, and it’s tied to the NEAppProxyFlowErrorDatagramTooLarge error. That error is only raised in very specific circumstances, namely, where the size of the data is larger than the max datagram size of the flow. That max size is set up when the flow ‘connects’.
Are you absolutely sure that the flow has finished opening, and finished successfully, at the time you start writing datagrams? If the flow hasn’t opened yet, or failed to open, the max datagram size would default to 0 and you’d see this error.
Specifically, you want to make sure that -openWithLocalFlowEndpoint:completionHandler: [2] has completed and with the error parameter being set to nil [3].
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] In the Swift 6 section of NetworkExtension.apinotes, it’s labeled as SwiftPrivate.
[2] Or the older -openWithLocalEndpoint:completionHandler:, or the various Swift equivalents.
[3] Alternatively, if you’re calling the Swift async function variants then you want to make sure that it’s returned without throwing an error.