Different behaviour for IP packets when establishing connections from different targets.

We have a PacketTunnelProvider in a SystemExtension with split tunneling. We are configuring a private IP address range of 240.0.0.1/10 as included routes and specifying a few matching domains using NEPacketTunnelNetworkSettings.

Once TunnelNetworkSettings has been applied successfully, a new utunx interface is created, and it includes routes for the 240.0.0.1/10 IP range. In our case, the interface name is utun3, where 'x' represents an integer value.

According to our business logic, we need to establish connections with some IPs from this range. To achieve this, we are utilizing the NWConnection class API to create connections with IP addresses

Like this

func establishConnection() {
        // Specify the destination host and port
        let host = "240.0.0.19"
        let port = 80 

        // Create an NWHostEndpoint
        let endpoint = NWHostEndpoint(hostname: host, port: "\(port)")

        // Create an NWConnection
        let connection = NWConnection(to: endpoint, using: .tcp)
        connection.start(queue: .global())
}

For the above code, we have observed different behaviour for IP packets when creating connections from different targets.

In the first case, when we create a connection to the IP address 240.0.0.19 from the Main app target using the provided code, the IP packets correctly go through the utun3 interface because this address falls within the 240.0.0/10 range, which is part of the included routes.

However, in the second case, when we use the same code to create a connection to 240.0.0.19 from the Extension target, the IP packets go through the primary interface en0 rather than the utun3 interface.

**Question : **

  1. Why do we have different behaviour for the same code?
  2. How can we achieve the same behaviour as the Main app target in the System Extension target?

-- Thanks

Answered by DTS Engineer in 767017022

Why do we have different behaviour for the same code?

Because Apple platforms have a subsystem called NECP that, amongst other things, works to prevent VPN loops. See A Peek Behind the NECP Curtain.

How can we achieve the same behaviour as the Main app target in the System Extension target?

The canonical way to achieve this goal is to use the legacy in-provider networking API and create a TCP connection using the createTCPConnectionThroughTunnel(to:enableTLS:tlsParameters:delegate:) method.

This is annoying, because the in-provider networking API is not one of our preferred networking APIs. See TN3151 Choosing the right networking API for more on that. We already have a bug on file to provide a nice way to do this with NWConnection (r. 100151093).

In the meantime, I think you’ll be able to set this up by explicitly binding your NWConnection to your VPN interface with the requiredInterface property.

I must admit to having never tried this myself )-:

Share and Enjoy

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

Accepted Answer

Why do we have different behaviour for the same code?

Because Apple platforms have a subsystem called NECP that, amongst other things, works to prevent VPN loops. See A Peek Behind the NECP Curtain.

How can we achieve the same behaviour as the Main app target in the System Extension target?

The canonical way to achieve this goal is to use the legacy in-provider networking API and create a TCP connection using the createTCPConnectionThroughTunnel(to:enableTLS:tlsParameters:delegate:) method.

This is annoying, because the in-provider networking API is not one of our preferred networking APIs. See TN3151 Choosing the right networking API for more on that. We already have a bug on file to provide a nice way to do this with NWConnection (r. 100151093).

In the meantime, I think you’ll be able to set this up by explicitly binding your NWConnection to your VPN interface with the requiredInterface property.

I must admit to having never tried this myself )-:

Share and Enjoy

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

Thanks @eskimo! One more question: Is there a way to create / retrieve NWInterface using the interface name?

Thanks

Is there a way to create / retrieve NWInterface using the interface name?

Yeah, I knew that question was coming (-:

There isn’t a good way to create an NWInterface from a BSD interface name, like en0. Having said that, it’s not clear how that’d help you. How do you plan to get the BSD interface name to start with?

Share and Enjoy

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

Thanks @eskimo! Actually we have NEAppProxyTCPFlow object, from which we can get corresponding nw_interface_t (networkInterface), and then use nw_interface_get_name (description is also seems to work) to get the BSD interface name

Thanks, Arkadi

I think there is a way to get local ip address via getifaddrs , then create IPV4Address / IPV6Address and get interface from it

Actually we have NEAppProxyTCPFlow object

I’m confused. Right at the top of this thread you said:

We have a PacketTunnelProvider

How are you getting app proxy flows in a packet tunnel provider?

Share and Enjoy

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

Hello  @eskimo  ,

I apologize for any confusion.

At the beginning, I mentioned the use of PacketTunnelProvider, and you responded with a suggestion to use the createTCPConnectionThroughTunnel method, which works perfectly for PacketTunnelProvider.

However, we also have the NETransparentProxyProvider in the same target, which is why  @arkadit mentioned NEAppProxyTCPFlow in his question.

we need to establish connections with some IPs from this range from NETransparentProxyProvider subclass as well. Unfortunately, the createTCPConnectionThroughTunnel method is specific to the PacketTunnelProvider class and cannot be used in the NETransparentProxyProvider class.

As a solution for NETransparentProxyProvider, we are planning to explicitly bind our NWConnection to the VPN interface using the requiredInterface property.

Thanks

Thanks for the explanation.

As a solution for NETransparentProxyProvider, we are planning to explicitly bind our NWConnection to the VPN interface using the requiredInterface property.

OK. I don’t see any why that won’t work but please lemme know how it pans out.

Share and Enjoy

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

Thanks 🙏 @eskimo

With the help of the NWPathMonitor API, I am able to get all available interfaces and bind the VPN interface to NWConnection object with NWParameters's requiredInterface property, and everything is working as expected

Different behaviour for IP packets when establishing connections from different targets.
 
 
Q