How cam I bind a socket to an interface to send data through it

Hello,

In a simple macOS tool, I'm trying to bind a socket to the default interface like "en0" or a VPN interface like "utun5" to make the socket send the data only through that interface.

int idx = (int)if_nametoindex("utun5"); if (setsockopt(sock, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)) == -1) { perror("setsockopt"); exit(1); }

The method setsockopt succeeds however the data doesn't go through the bound interface, but it goes through the default interface like if there has been no binding at all.

I tried also with libcurl, which is based on sockets, but the data goes through the default interface like before, even if the method succeed.

if (curl_easy_setopt(curlH, CURLOPT_INTERFACE, "utun5") != CURLE_OK) { perror("CURLOPT_INTERFACE"); exit(1); }

May someone suggest how to bind an interface to a socket in a way that makes all the data transmitted by that socket go only through the bound interface?

Any constructive suggestion is very welcome. Thank you!

Best regards,

Luca Severini

IP_BOUND_IF is the right option here.

And this is working for me. Consider the following program:

func test(_ interfaceName: String) throws {
    let sock = try FileDescriptor.socket(AF_INET, SOCK_STREAM, 0)
    defer { try! sock.close() }
    let interface = if_nametoindex(interfaceName)
    assert(interface != 0)
    try sock.setSocketOption(IPPROTO_IP, IP_BOUND_IF, interface)
    try sock.connect("93.184.216.34", 80)
    print(interfaceName, try sock.getSockName())
}

try test("en0")
try test("utun3")

Note This uses the helpers from Calling BSD Sockets from Swift.

On my Mac en0 is the Wi-Fi interface that acts as the default route, while utun3 is a VPN to Apple. The latter has a 17.0.0.0/8 address, as you’d expect for Apple.

If I run the program as is, I see this:

en0 (address: "192.168.1.141", port: 56079)
utun3 (address: "17.NNN.NNN.NNN", port: 57606)

If I comment out the setsockopt(…) call I see this:

en0 (address: "192.168.1.141", port: 58475)
utun3 (address: "192.168.1.141", port: 63329)

So, the connection to 93.184.216.34, aka example.com, goes through en0 by default, but setting IP_BOUND_IF forces it to go through utun3.

This is an macOS 13.5, but this things have worked this way for decades.

Share and Enjoy

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

How cam I bind a socket to an interface to send data through it
 
 
Q