iOS listen on all interfaces?

Is it possible to listen on all interfaces on iOS, in a way equivalent to listening on 0.0.0.0 for ipv4 on linux/windows/mac? I tried both 0.0.0.0 and the ipv6 equivalent and they do not seem to work on iOS.

I tried both 0.0.0.0 and the ipv6 equivalent and they do not seem to work on iOS.

These do work in general, with the semantics you’re looking for. What API are you using? And what is the failure mode?

Share and Enjoy

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

Good to know--thanks! I'm using pretty standard TCP listeners in Rust (https://docs.rs/smol/latest/smol/net/struct.TcpListener.html); the app is a thin iOS wrapper around a Rust library.

Failure mode: when I start a server listening on 0.0.0.0:9001, I cannot connect to it from localhost (on the loopback interface). I just tested with another computer on the same network and that can connect to the server just fine. So it seems that only access over the loopback interface is broken.

So it seems that only access over the loopback interface is broken.

… in the Rust library you’re using (-:

Presumably that library is based on BSD Sockets. If you call BSD Sockets directly from C, this works. Specifically, the code included below worked on iOS 15.6.1, printing:

client did connect, conn: 4
server did accept, conn: 5

AFAIK this should work on all Apple platforms in all situations.

It’s hard to say for sure what’s going on in your case but I recommend that you:

  1. Run my code in your environment. If that works, it rules out some environmental issue, leaving the Rust library as the most likely culpritt.

  2. Escalate this via the support channel for that library.

Share and Enjoy

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


- (void)startClient {
    [NSThread detachNewThreadWithBlock:^{
        int conn = socket(AF_INET, SOCK_STREAM, 0);

        struct sockaddr_in addr = {};
        addr.sin_len = sizeof(addr);
        addr.sin_family = AF_INET;
        addr.sin_port = htons(9000);
        addr.sin_addr.s_addr = htonl(0x7f000001);
        BOOL success = connect(conn, (const struct sockaddr *) &addr, sizeof(addr)) >= 0;
        assert(success);
        
        fprintf(stderr, "client did connect, conn: %d\n", conn);
    }];
}

- (void)startServer {
    [NSThread detachNewThreadWithBlock:^{
        int listener = socket(AF_INET, SOCK_STREAM, 0);

        struct sockaddr_in addr = {};
        addr.sin_len = sizeof(addr);
        addr.sin_family = AF_INET;
        addr.sin_port = htons(9000);
        addr.sin_addr.s_addr = INADDR_ANY;
        BOOL success = bind(listener, (const struct sockaddr *) &addr, sizeof(addr)) >= 0;
        assert(success);
        
        success = listen(listener, 5) >= 0;
        assert(success);
        
        [self startClient];
        
        int conn = accept(listener, NULL, NULL);
        fprintf(stderr, "server did accept, conn: %d\n", conn);
    }];
}

I looked into the Rust standard TCP functions, and they seem to be simple wrappers around the C functions, and I'm not sure why things could be wrong.

If it helps, I am running the code inside a network extension (a PacketTunnelProvider); is the network stack in some weird state inside network extensions that can cause listening to 0.0.0.0 to fail?

If it helps, I am running the code inside a network extension (a PacketTunnelProvider); is the network stack in some weird state inside network extensions that can cause listening to 0.0.0.0 to fail?

Yes.

Blah, I really need to write this up once and for all but…

Apple platforms have a sophisticated network interface access control mechanism known as NECP (Network Extension Control Protocol). It’s used for a variety of tasks. For example, it’s this technology that underlies the WWAN access control visible to the user on iOS in Settings > Mobile Data.

As the name suggest, NECP originated to support the Network Extension framework and one of its prime goals is to prevent VPN loops, that is, prevent traffic generated by a tunnel provider from being routed via the tunnel. This creates all sorts of odd behaviours, and so I’m not surprised you’re hitting this problem in this context.

What are you trying to do loopback within your packet tunnel provider?

Share and Enjoy

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

My app, which is an anticensorship tool that combines a VPN and SOCKS5 proxy, has an option to expose the SOCKS5 proxy on 0.0.0.0 so that other computers on the same LAN can use the tunnel. Currently, that breaks applications on the same phone that try to connect to the SOCKS5 proxy through localhost (admittedly, these apps are rare, but Telegram is an example that can be configured to use a localhost proxy). All of my interesting business logic is within the network extension itself.

On preventing VPN loops, currently I am using rather complicated hacks to whitelist every IP that the VPN might want to connect to, in the VPN routing configuration. Configuring this correctly to avoid missing everything has been a huge hassle, as the IP addresses that my tool contacts continually change. Furthermore, for correct functionality, the VPN contacts certain IP addresses that other apps would also need to contact, and this other traffic must go through the VPN --- I've just given up on that and leak traffic outside the VPN for those cases.

Are you saying that iOS magically avoids VPN loops through e.g. avoiding routing any network-extension-originated traffic through a network extension? If that's the case it'll save me massive amounts of time working with bug-prone hacks :)

Are you saying that iOS magically avoids VPN loops through e.g. avoiding routing any network-extension-originated traffic through a network extension?

Yes. Well, it’s not magic, it’s NECP, but yes.

But yeah, don’t trust me, try it out and see what you get.

Share and Enjoy

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

I tried out the NECP, and sure enough it worked like a charm :). Does the same nice VPN loopback-prevention functionality exist on Mac?

Does the same nice VPN loopback-prevention functionality exist on Mac?

Yes. Assuming you’re using the NE provider architecture. Some Mac VPN products are implemented using various ad hoc techniques [1], and those don’t benefit from this protection.

Share and Enjoy

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

[1] Because they originated before the NE provider architecture was added to macOS. In contrast, all iOS VPN products use the NE provider architecture.

iOS listen on all interfaces?
 
 
Q