Packet Tunnel Provider - route traffic via tunnel at the Extension app

In related to my other question "Packet Tunnel Provider + libcurl" https://forums.developer.apple.com/message/285308

I have a VPN app with Packet Tunnel Provider, and the VPN is connected and works as expeceted.

Now I'm trying to add some code at the Extension app, which should access the internet.

I see that the requests doesn't go via the tunnel, so I'm guessing that traffic made at the extension doesn't reach to the packetFlow object.


Am I correct / wrong? Is it a bug or this is the expected behavior? How can I force the traffic at the extension to go through the tunnel?

Accepted Reply

Only traffic from the Extension itself behaves differently.

Ah, I though you were talking about code outside of the extension. Code within the extension is subject to different rules.

Within the extension you can send data through the tunnel using the following

NEPacketTunnelProvider
methods:
  • -createTCPConnectionThroughTunnelToEndpoint:enableTLS:TLSParameters:delegate:
  • -createUDPSessionThroughTunnelToEndpoint:fromEndpoint:

I’m not sure if there’s a way to get high-level APIs, like

NSURLSession
, to go through the tunnel. I suspect not, but if you need a definitive answer I recommend that you open a DTS tech support incident and I can investigate this properly.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

I see that the requests doesn't go via the tunnel, so I'm guessing that traffic made at the extension doesn't reach to the packetFlow object.

I’ve seen behaviour like this, but only in the context of per-app VPN. Is your packet tunnel provider running in source app (

NETunnelProviderRoutingMethodSourceApplication
) or destination IP address (
NETunnelProviderRoutingMethodDestinationIP
) mode?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

NETunnelProviderRoutingMethodDestinationIP (which is the default, I didn't set routingMethod).

In destination IP mode traffic is routed via IP address. Despite the name, the source IP address is also considered. The general rule is something like this:

  1. If the app binds the socket to a specific interface (either by calling

    bind
    with the interface’s source IP address or by
    IP_BOUND_IF
    ), it goes via that interface.
  2. If the destination address is on a network defined by the interface, it goes via that interface.

  3. If there’s an explicitly entry in the routing table, it honours that.

  4. Finally, it goes via the default route.

Your packet tunnel provider can control the last three steps:

  • For step 2 you set the interface’s networks via the

    addresses
    and
    subnetMasks
    properties of the
    NEIPv4Settings
    object you use to configure the interface (and similarly for IPv6).
  • For step 3 you can explicitly opt in to and out of networks via the

    includedRoutes
    and
    excludedRoutes
    . Also, you can automatically opted out of the address in the
    tunnelRemoteAddress
    property of the
    NEPacketTunnelNetworkSettings
    object.
  • For step 4 you claim the default route (and thus become the primary interface) by including

    +[NEIPv4Route defaultRoute]
    in the
    includedRoutes
    property.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

That exactly what I thought, but still - I'm not sure it's working that way:

Just for example, I wrote this test:

  let url = URL(string: "https:/ /   somewebsite.com"")
                    let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
                        if data != nil {
                            let res = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
                            DDLogInfo("res:\(res!)")
                        } else {
                            DDLogInfo("data is null")
                        }
                    }
                    task.resume()


When I'm running it at the Extension (after the tunnel is open, and the VPN is connected) - this reqeust doesn't goes via the tunnel.

But if I'm writing this code at the containing app (after the VPN is connected), the request will go via the tunnel.


In addition to what you said:

I did include defaultRoute (step 4), so the tunnel supposed to become the primary interface as you said

I did not excluded anything (step 3)

I'm not using bind

There's no explicit entry at the routing table


In general - everything else is working good and goes via the tunnel.

Only traffic from the Extension itself behaves differently.

Only traffic from the Extension itself behaves differently.

Ah, I though you were talking about code outside of the extension. Code within the extension is subject to different rules.

Within the extension you can send data through the tunnel using the following

NEPacketTunnelProvider
methods:
  • -createTCPConnectionThroughTunnelToEndpoint:enableTLS:TLSParameters:delegate:
  • -createUDPSessionThroughTunnelToEndpoint:fromEndpoint:

I’m not sure if there’s a way to get high-level APIs, like

NSURLSession
, to go through the tunnel. I suspect not, but if you need a definitive answer I recommend that you open a DTS tech support incident and I can investigate this properly.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Well, I suspected that it's not possible. I guess I'll have to create UDP packets and sent them via the tunnel.

Thanks Eskimo!

Regarding this again, just to be sure - I thought about it and maybe I wasn't clear enough, so last quesion regarding this topic:

Is it possible, to send traffic at the Extension, while the VPN is connected, so that the OS would pass this traffic at the packetFlow object (and from there I would be able to send it at the tunnel) ?


(for example: send traffic from Extension - > traffic is not on tunnel so the OS catpure it and pass it via packetFlow -> I read the packetFlow and send this traffic on the tunnel)

The methods I mentioned above are only available within the extension (they are instances methods on

NEPacketTunnelProvider
, and that class can’t be instantiated outside of the extension context). If they create a connection through the tunnel then, by definition, the data on that connection must come through your packet flow.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks, but that not exactly what I've asked -

I want to send traffic at the Extension (without those methods), and I want the OS to detect this traffic and instead of ending it- the OS would pass it via the packetFlow, and from there I'll take those packets and resend them via the above methods (so this time they would be sent via the tunnel).

I want to send traffic at the Extension (without those methods), and I want the OS to detect this traffic and instead of ending it- the OS would pass it via the packetFlow

In the above, what do you mean by “ending it”?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Sorry, I meant "instead of sending it"


So when I try to send traffic from the extension not on the tunnel, the OS won't send it but would pass it to packetFlow (as long as I didn't excluded this route/not on interface network etc...)

But that’s exactly what the

createXxxThroughTunnelXxx
methods do (per my post on 3 Jan). Are they not working for you?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"