Transparent Proxy Provider and .write

Because it may be quicker to ask: with a TPP, readData() gets a data size of 0 if the process has finished writing to the network. However, there seems to be no way to find out if it has finished reading from the network, other than to do a .write() and see if you get an error. (I filed a FB about this, for whatever that's worth.)

Since the API is flow-based, not socket, it's not possible to tell if the app has set its own timeout. Or exited. So one question I have is: if I do flow.write(Data(count:0)) -- is that a possible way to determine if it's still around? Or will it be interpreted as read(2) returning 0?

(Putting this in for testing is difficult, but not impossible -- as I said, this might be the quickest way to find out.)

Replies

I filed a FB about this, for whatever that's worth.

What was that bug number?

Share and Enjoy

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

I know I filed this, but I can't find it. So I filed a new one, FB13796015

I don’t really understand your issue. A transparent proxy has top and bottom sides:

  • The top side connects to the app via the various subclasses of NEAppProxyFlow.

  • The bottom side connects to the network [1], via your networking API of choice.

It sounds like you’re focused on the top side, and specifically interested in TCP, and thus you’re working with NEAppProxyTCPFlow. Is that correct?

If so, what case are you trying to handle?

Share and Enjoy

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

[1] Usually. There’s actually no constraints on how the proxy handles flow, so it could connect to a file, a serial port, or whatever. But I’m assuming the standard case here.

Just using TCP, since it's the simplest and more common one for us anyway:

The TPP has two things to do related to each flow:

  • Read data from the process, and send it to the thing that does the proxying; this is done by looping with flow.read(), which indicates that the process has closed the write-to-network side by returning a Data object of size 0. Just like POSIX read!
  • Get data from the thing that is doing the proxying, and write it to the process; this is done by calling flow.write() each time data is presented to the TPP from the proxying thingy.

Each of those sides is distinct -- processes can, and often do, close the write-to-network side (aka flow.read()) before they are done with reading from the network. However, if a process does close the read-from-network side, the only way to tell this is to try to send data to it (aka flow.write()) and get an error.

Now consider a case where you've got a connection to an internet server (process -> TPP -> proxying thing -> server , and server -> proxying thing -> TPP -> process). The process closes the read-from-internet side, but the server does not close its end. This means that the connection is still open, but no data ever comes.

There is no information given to the TPP, in that case, that the process has closed the read-from-network side.

Thanks for the explanation.

So my question is: How does the case you’re concerned about differ from the non-transparent proxy case?

Consider this test project:

import Foundation
import System

func main() throws {
    let s = try FileDescriptor.socket(AF_INET, SOCK_STREAM, 0)
    try s.connect("127.0.0.1", 12345)
    _ = try s.write(data: Data("Hello Cruel World!\n".utf8))
    print("will sleep")
    sleep(3)
    print("will shutdown read")
    try s.shutdown(SHUT_RD)
    print("will sleep again")
    sleep(3)
    print("will close")
    try! s.close()
}

try main()

Note This is using the helpers from Calling BSD Sockets from Swift.

If I run this against a local server like so:

% nc -l 12345

then the fact that the client shuts down the read side of its socket (SHUT_RD) has no visible effect on a quiscent server. Specifically:

  • If the server does nothing, the TCP connection stays in place until the will close log point.

  • If, between the will shutdown read and will close, the server sends data, that triggers a connection close.

Share and Enjoy

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

Well, that's just another aspect of the same problem; that's why I asked if there was a way to tell if it was still valid. I could possibly try to watch for the process to exit, but that wouldn't get all of the cases although it might be better than now. Or what happens if I do a flow.write(Data(count:0)) -- would that return success/error depending on whether it was still open, or would it be treated as telling the process that there was no more data coming?

Setting a timeout is of course one way to go, but it's not necessarily the valid way to go -- there are pretty good reasons to keep connections open, but idling, for hours. But TCP at least has KEEPALIVE, which lets one end know if the other has gone away for some reason.

If not sure that your transparent proxy should strive to solve a problem that affects BSD Sockets in general. Which brings us to TCP keepalives. AFAIK there’s no way for the transparent proxy to get the TCP keepalive state set by the app, but I think that’d make a reasonable enhancement request.

Share and Enjoy

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

I filed a FB a while back about getting some socket options visible (mostly for UDP, mind you).

But in this case, I asked what happens if I do a flow.write(Data(count:0)) -- or, alternately, if there is a way to non-destructively determine if the write-to-app side of the flow is opened or closed.

But in this case, I asked what happens if I do a flow.write(Data(count:0))

I’ve no idea. I think this is implementation-defined behaviour, that is, I don’t think there’s any documentation that specifies what’ll happen if you write zero-length data to a TCP flow.

Share and Enjoy

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

So there is no way for a proxy provider to tell that a process is done receiving data? Alas.

NB I filed FB13796015 earlier about this.