When to close NEAppProxyUDPFlow?

I have been writing a custom subclass of NETransparentProxyProvider. Here is what I do to handle NEAppProxyUDPFlow.

(1) Return true in method handleNewUDPFlow(_:initialRemoteEndpoint:) and retain flow object

(2) Open flow

open(withLocalEndpoint:completionHandler:)

(3) Read datagrams

 readDatagrams(completionHandler: @escaping ([Data]?, [NWEndpoint]?, Error?) -> Void)

(4) Create NWConnection object wait for it to be in ready state

(5) Send data from step 3

send(content: Data?, contentContext: NWConnection.ContentContext = .defaultMessage, isComplete: Bool = true, completion: NWConnection.SendCompletion)

(6) Listen for the response

receiveMessage(completion: @escaping (Data?, NWConnection.ContentContext?, Bool, NWError?) -> Void)

(7) Write a response to the flow

writeDatagrams(_ datagrams: [Data], sentBy remoteEndpoints: [NWEndpoint], completionHandler: @escaping (Error?) -> Void)

The scheme above works.

One of the questions I have is when to close the flow? The first case is when the datagrams and remoteEndpoints arrays are non-nil but are empty in readDatagrams callback

But how about the UDP server response? rfc768 spec is pretty short https://datatracker.ietf.org/doc/html/rfc768 And there is no response as such in UDP. The server extracts the source port and source address from the packet and may or may not send data to that socket. Theoretically, it can send multiple replies to the same socket How can I know that no more data is expected to be received in NWConnection to close the connection and release the flow? The receive message callback can only tell us that that one datagram has been delivered Can I not close the flow at all?

How can I know that no more data is expected to be received in NWConnection to close the connection and release the flow?

  1. If an error is thrown then it is time to tear down the connection.
  2. If you read data from the remote side of the connection and there is no error, but the data is also empty, then in this case you could consider closing the connection at that point.
Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Thanks for the quick reply Matt, On practical tests, I have never seen scenario number 2. Is this some agreement that an empty UDP payload is considered as EOF?

Is this some agreement that an empty UDP payload is considered as EOF?

I wish I could say that is the case, but unfortunately UDP is not as organized as TCP. This is just an option you could try because there as essentially no guarantees here when dealing with UDP.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Accepted Answer

So, I think I understood how things work. Since the transport layer(my transparent proxy) can't decide if more data is coming from the remote side of the connection or not it should not do that. And rely on the app layer which can.

From practical tests, readDatagrams callback will not return empty the datagrams and remote endpoints arrays(EOF sign) until the app that opened socket(flow) is satisfied with the number of datagrams received from the remote side.

So, to summarize:

  1. Close connection and flow in case of errors
  2. Close flow as per documentation to readDatagrams function:
If the datagrams and remoteEndpoints arrays are non-nil but are empty, then no more datagrams can be subsequently read from the flow.

Close flow as per documentation to readDatagrams function: If the datagrams and remoteEndpoints arrays are non-nil but are empty, then no more datagrams can be subsequently read from the flow.

Right.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

So normally if you're a process doing UDP I/O, you use a timeout of some sort (usually with recvfrom, or a read with an alarm signal or something).

How is a network extension supposed to know that? Or is it supposed to assume that if a process signals done-with-writing, that it should treat both directions as closed?

How is a network extension supposed to know that? 

If you're using readDatagrams you'll get a completion handler and then you can call the function that receives more datagrams again until complete. See the documentation note for more.

Sorry I wasn't clear enough there -- I'm talking about when the application is doing read or recvfrom or other on a UDP socket. Most applications that use UDP, as I said, will either use a timeout of some sort, or a non-blocking socket. (And, of course, they may also be using kqueue to find the same information out.)

So how should a Transparent Proxy Provider, which does not know if the socket was set to be non-blocking, or has a timeout, deal with a UDP flow? Or should the TPP instead always assume that if the write-to-internet side (the readDatagrams method) gets closed, the read-from-internet side is also closed?

So how should a Transparent Proxy Provider, which does not know if the socket was set to be non-blocking, or has a timeout, deal with a UDP flow? Or should the TPP instead always assume that if the write-to-internet side (the readDatagrams method) gets closed, the read-from-internet side is also closed?

If the write side is closed then I'm assuming it's also safe to close the read side because there will be nothing more coming in. From the read side though with a NETransparentProxyProvider, if you're using NWConnection then you can also use receiveMessage that will deliver your proxy an error to close the connection or a final empty read that should be considered as complete.

When to close NEAppProxyUDPFlow?
 
 
Q