Hi Quinn, thanks for answering and sorry for the delay.
> As written, it looks like you’re doing this all synchronously. That’s quite unusual, because the I/O APIs are asynchronous. Is this just because you’re writing pseudo code? Or do you have some sort of ‘synthetic synchronous’ thing going on?
The actual code isn't synchronous because it uses all the block-based APIs from `NEAppProxyUDPFlow` which are inherently async. For example, the `open()` call from the pseudo code actually looks like this:
private func open() {
// The endpoint is ignored. From the NEAppProxyFlow.h header: "If the source application already specifed a local endpoint by binding the socket then this parameter is ignored."
let bogusEndpoint = NWHostEndpoint(hostname: "0.0.0.0", port: "0")
udpFlow.open(withLocalEndpoint: bogusEndpoint) { [unowned self] (error) in
Logger.fileLog("\(self.logIdentifier) - opened with error: \(error.debugDescription)")
if let error = error {
Logger.fileLog("*** \(self.logIdentifier) - error: \(error)")
} else {
self.handleMoreData()
}
}
}
You can see the entire project in the radar I filed: rdar://48495244 .
> In this code, is read() reading from an NEAppProxyUDPFlow? And is write() writing to an NEAppProxyUDPFlow? If so, have you just elided the code that sends the request out on the wire?
Yes, read() is in fact `NEAppProxyUDPFlow.readDatagrams` and write() is `NEAppProxyUDPFlow.writeDatagrams`. I'm not sure about your second question but here's some more detail.
For each incoming `NEAppProxyUDPFlow`, we:
while(!done)
udpFlow.readDatagrams()
Add some provisioning info to the EDNS0 part of each datagram
Send the DNS request to our own DNS server
Take the response data from that request and feed it to
udpFlow.writeDatagrams()
And continue that sequence until udpFlow.readDatagrams() returns 0 datagrams (or an error). Here's the actual code from that loop:
private func handleMoreData() {
udpFlow.readDatagrams() { [unowned self] (datagrams, endpoints, error) in
if let error = error {
self.finish(error: error, logStr: "*** Error: \(error). Closing flow for read and write.")
return
}
if let datagrams = datagrams, let endpoints = endpoints, !datagrams.isEmpty {
let modifiedData = self.addEDNS0(datagrams)
if modifiedData.isEmpty {
self.finish(error: nil, logStr: "*** Modified data was empty. Closing flow and bailing.")
return
}
let responseData = self.send(modifiedData)
if responseData.isEmpty {
self.finish(error: nil, logStr: "*** Response data was empty. Closing flow and bailing.")
return
}
self.writeDNSResponse(responseData, endpoints) { (error) in
if let error = error {
self.finish(error: error, logStr: "*** Write failed")
} else {
self.handleMoreData()
}
}
} else {
self.finish(error: nil, logStr: "No more data. Closing flow.")
}
}
}
Where we see the error (and this happens about 50% of the time) is in `writeDNSResponse` which looks like:
private func writeDNSResponse(_ datagrams: [Data], _ endpoints: [NWEndpoint], completion: @escaping(_ error: Error?)->Void) {
udpFlow.writeDatagrams(datagrams, sentBy: endpoints) { (error) in
if let error = error {
Logger.fileLog("*** \(self.logIdentifier) - error: \(error)")
}
completion(error)
}
}
Does that help pinpoint the problem at all?