API to open a socket on a specific interface

Does CFNetwork provide API to open a socket on a particular interface? This post suggests

using SCNetworkInterfaceCopyAll and SCNetworkInterfaceGetInterfaceType to find an interface with a desired type. But I get an error saying SCNetworkInterfaceCopyAll is unavailable in Xcode 8.3.2.


If SCNetworkInterfaceCopyAll is unavailable, how would you search the result of getifaddrs to find an interface with a type (e.g. kSCNetworkInterfaceTypeIEEE80211 (wifi) vs kSCNetworkInterfaceTypeWWAN (cell)).


Thanks!

Answered by DTS Engineer in 226101022

I'm working on iOS.

OK then, the System Configuration framework APIs you’re referencing are only available on macOS.

This issue breaks down as follows:

  • Binding a connection to an interface

  • Selecting an interface.

I’ll discuss each in turn.

If you’re working with low-level APIs — those that let you get at the socket (in the BSD Sockets sense) for the connection — you can force the connection to run over a specific interface in two ways:

  • By using

    bind
    to bind the source address of the socket to the interface’s IP address
  • Using the

    IP_BOUND_IF
    socket option

This works well for BSD Sockets and things tightly tied to it (like CFSocket and GCD). It does not work at all for higher-level APIs, like NSURLSession. You can bodgy it into working for CFSocketStream, but that has some serious caveats.

With regards identifying an interface, iOS doesn’t have any really good options on that front. You can call

getifaddrs
to get an interface list and then, for
AF_LINK
interfaces, muddle around in
struct if_data
, but there are some serious drawbacks:
  • The constants needed to test

    ifi_type
    are not part of the iOS SDK, and hence not officially supported
  • Even when you identify the interface type, you’ll find that there are often multiple active interfaces of each type and there’s no supported way to pick between them

What’s your high-level goal here? Specific subsystems within iOS have supported ways to run connections over a specific interface, so there might be a subsystem-specific mechanism that can help you out. For example, captive network apps can force the connection to run over the captive Wi-Fi interface by calling

-bindToHotspotHelperCommand:
.

Share and Enjoy

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

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

Does CFNetwork provide API to open a socket on a particular interface?

What platform are you working on?

Share and Enjoy

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

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

Hi Quinn,


I'm working on iOS.


Thanks for your response!

Accepted Answer

I'm working on iOS.

OK then, the System Configuration framework APIs you’re referencing are only available on macOS.

This issue breaks down as follows:

  • Binding a connection to an interface

  • Selecting an interface.

I’ll discuss each in turn.

If you’re working with low-level APIs — those that let you get at the socket (in the BSD Sockets sense) for the connection — you can force the connection to run over a specific interface in two ways:

  • By using

    bind
    to bind the source address of the socket to the interface’s IP address
  • Using the

    IP_BOUND_IF
    socket option

This works well for BSD Sockets and things tightly tied to it (like CFSocket and GCD). It does not work at all for higher-level APIs, like NSURLSession. You can bodgy it into working for CFSocketStream, but that has some serious caveats.

With regards identifying an interface, iOS doesn’t have any really good options on that front. You can call

getifaddrs
to get an interface list and then, for
AF_LINK
interfaces, muddle around in
struct if_data
, but there are some serious drawbacks:
  • The constants needed to test

    ifi_type
    are not part of the iOS SDK, and hence not officially supported
  • Even when you identify the interface type, you’ll find that there are often multiple active interfaces of each type and there’s no supported way to pick between them

What’s your high-level goal here? Specific subsystems within iOS have supported ways to run connections over a specific interface, so there might be a subsystem-specific mechanism that can help you out. For example, captive network apps can force the connection to run over the captive Wi-Fi interface by calling

-bindToHotspotHelperCommand:
.

Share and Enjoy

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

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

Thanks for your response!


Our goal is the ability to connect to an endpoint via wifi and/or cell based on internally calculated connection metrics, over a VPN using a custom protocol. The ideal, high-level API would be something like a property on an instance of NWTCPConnection that specified the type of interface used by that connection.


Since that’s not available, is it possible for a custom NEPacketTunnelProvider to create a TCP connection through a VPN tunnel by using CFSocket or GCD, instead of by using a NWTCPConnection from createTCPConnectionThroughTunnel? That seems to be the only way something like this could work.


If there were a way to ask the system return a NWTCPConnection when any other path were available, not just when a better path were available as determined by the system, then we wouldn’t need to use lower-level APIs.


Thanks!

Our goal is the ability to connect to an endpoint via wifi and/or cell based on internally calculated connection metrics, over a VPN using a custom protocol.

I’m confused by this. It seems to me that the VPN tunnel would be running over one interface or the other, so if you connect through the tunnel you’ll inherent the interface choice from there.

Or are you planning to have two tunnels, one over WWAN and one over Wi-Fi, and then choose between them?

Share and Enjoy

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

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

Yes, we want to have two tunnels, one over WWAN and one over Wi-Fi, and then choose between them.

Yes, we want to have two tunnels, one over WWAN and one over Wi-Fi, and then choose between them.

OK. The bit that confused me earlier was this:

… instead of by using a NWTCPConnection from createTCPConnectionThroughTunnel?

The ‘through tunnel’ APIs create a connection through the tunnel. As a packet tunnel provider, if you want to create a connection for the tunnel, you use

-createTCPConnectionToEndpoint:***
(not
-createTCPConnectionThroughTunnelToEndpoint:***
).

Having said that, the use of NWTCPConnection for the packet tunnel is not mandatory. It’s fine to use other APIs, including BSD Sockets, for this.

The gotcha here isn’t getting the TCP connection to run over a specific interface, which you can do with BSD Sockets and other low-level APIs, it’s identifying the correct interface to use.

Share and Enjoy

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

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

I see quite a few interfaces on the device I’m using: en0—en2, pdp_ip0—pdp_ip4, ap1, awdl0, ipsec0—ipsec4, utun0, lo0.


A common approach people take to identifying interfaces on iOS is to assume that the interface named en0 is the preferred wifi interface, and the interface named pdp_ip0 is the preferred cellular interface. Do you have any advice about that approach and/or how to more reliably identify the correct interface to use?


Thanks!

A common approach people take to identifying interfaces on iOS is to assume that the interface named

en0
is the preferred wifi interface, and the interface named
pdp_ip0
is the preferred cellular interface.

Hardwiring interface names is a really bad idea.

Do you have any advice about that approach and/or how to more reliably identify the correct interface to use?

No. I’m going to quote myself here, namely, my 28 Apr 2017 post on this thread:

With regards identifying an interface, iOS doesn’t have any really good options on that front.

As to what the best option is, that’s somewhat context sensitive, as I’ve discussed above.

Share and Enjoy

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

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

To follow this, if it hasn't been said recently enough, if the undocumented nature of the interfaces (naming or otherwise) is a problem, please file feature requests so the Apple staff have numbers to justify the work.


After all, how the heck are you supposed to know what "awdl" or "utun" is, let alone what "en" is? That sort of stuff is supposed to be documented by the POSIX distrubtion. 😉

I too am trying to concurrently direct traffic out through either wifi or cellular (based on some policies/business logic similar to the OP) but running into problems.


The approaches described here (and similar others here and here) to bind a BSD socket to cellular - via bind(ip of cellular) or setsockopt(IP_BOUND_IF, cellular i/f) - work great when performed within an app but do not work when performed from within my Packet Tunnel provider. The binding itself succeeds, but then any attempts to connect on the cellular-bound socket always fail with EADDRNOTAVAIL.


I ran some additional experiments and found:

  1. The connect works perfectly fine when done from an app (instead of within my PT provider). I also tried this as a separate app while my PT provider was actively running at the same time and had no problems.
  2. I can connect() on the cellular-bound socket from within my packet tunnel provider as long as it is before I call the startTunnel completion handler (i.e. before VPN tunnel is brought up by the system). IOW, the only time the connect() fails is when I try to do it from within my PT provider and only after my tunnel is started (after I call the completion handler).
  3. The same EADDRNOTAVAIL occurs with both TCP and UDP sockets.
  4. The EADDRNOTAVAIL doesn't seem to be related to the port already being used (which is the typical cause), since I'm calling bind() with 0 for the port.


So, I'm wondering:

  1. Am I doing something wrong, i.e. is there something subtlely different that needs to be done within a PT provider that isn't typically required in an app?
  2. Is there a known issue with connecting cellular-bound sockets from within a tunnel provider?
  3. Is there some other method I can use to send some subset of my traffic over cellular, while still concurrently sending most of my traffic over wifi? I'm running a single full tunnel (not split) and finding that createTCPConnectionToEndpoint only allows me to send out over the current default network (not selectively and concurrently out either cellular or wifi when both are available).


Any help would be greatly appreciated. Thanks!

… do not work when performed from within my Packet Tunnel provider.

That’s not a huge surprise. Packet tunnel providers operate in a restricted networking environment that results in a bunch of oddities. For example, we specifically go out of our way to ensure that traffic from the packet tunnel provider does not go through any other VPN interface [1].

Unfortunately I don’t have any easy answers for you here. I still don’t have a solid handle on how these restrictions get applied, and thus how they might interact with your WWAN requirements. My only suggestion is that you open a DTS tech support incident so that I can allocate the time to research this properly.

Share and Enjoy

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

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

[1] Although, even more strangely, that restriction is not currently being enforced by macOS (r. 39974413).

Have you got any solution for this.

I am also trying the same but no luck.

Any update or solution for this. I am also trying the same

Any update or solution for this. I am also trying the same

This thread has covered a lot of ground. Which specific issue is troubling you?

Share and Enjoy

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

API to open a socket on a specific interface
 
 
Q