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. :-))
I’m not sure what’s causing the main error here, but let’s start with endpoints, and specifically my NWEndpoint History and Advice post. This explains the general landscape.
NEAppProxyUDPFlow has read and write methods that use Network.NWEndpoint type. See here and here.
These are the Swift async versions; there are also equivalent completion handler versions.
In terms of how to handle endpoints, it’s best to approach this from the perspective of the client, that is, the DNS client that’s issuing DNS requests to resolve queries. And specifically a BSD Sockets client, which will:
- Open a socket.
- Optionally connect the socket to an endpoint (aka address).
- Send a datagram. If it connected the socket, it can call one of the send routines that doesn’t take an endpoint. If it didn’t connect the socket, it must supply an endpoint at this point.
- Receive a datagram, along with the source of that datagram.
From your perspective steps 1 and 2 result in a new flow. If you want to know what endpoint the client connected to, implement the handleNewUDPFlow(_:initialRemoteFlowEndpoint:) method from the NEAppProxyUDPFlowHandling protocol [1]. However, be aware that the client might not be connecting to an endpoint, in which case the system calls the vanilla handleNewFlow(_:) method.
You then connect via your underlying infrastructure and, when you’re done, call the open(withLocalFlowEndpoint:) method to:
- Tell the flow that you’re ready, and
- If appropriate, override the local endpoint.
This local endpoint is what the client gets when it calls getsockname, or the currentPath property if it’s using Network framework.
Your provider then sets up a datagram read. This completes with the series of datagrams and endpoints, which are the datagrams and endpoints supplied by the client in step 3. The endpoints may or may not match the initial remote endpoint you got previously. Remember that, in the BSD Sockets case, each datagram can have its own destination endpoint.
You should remember the endpoints for each request and write each reply with its corresponding endpoint. Again, think about this from the client’s perspective. When it sends a DNS request to address X, it expects to receive the reply from host X. If it gets a reply from some other host, it’ll likely throw it away.
I think that should be enough for you to sort out your endpoint concerns. Please apply these changes and let me know how you get along.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] In this and other cases I’m linking to the new methods, just to demonstrate that the Network framework NWEndpoint APIs do actually exist. These have poor documentation. For better docs, switch over to the legacy method.