This is an interesting edge case. I had a chat with the Network framework team about this and there is no direct way to do what you want. This kinda makes sense given Network framework’s original focus on transport protocols, but it’s clearly no longer sufficient. We would appreciate you filing a bug about this, explaining what you’re doing and where you got stuck. Please post your bug number, just for the record.
In the meantime, they suggested a workaround that seems viable. On Apple platforms all interfaces get a link-local IPv6 address. If you create an UDP connection (
NWConnection
) to that address, the resulting path contain’s the interface object you need.
Pasted in below is some code that shows this in action. I tested it as follows:
I started with a MacBook running 10.15.2.
I connected the Wi-Fi interface to the wider Internet.
I attached a Thunderbolt Ethernet dongle. This became
en7
.I connected the Ethernet cable to the Ethernet port on another Mac. The other Mac isn’t relevant here, I just needed an active port that would bring up the link but not route any traffic.
I ran the program below. As you see, the UDP connection’s path update handler is called with an interface object for
en7
. You should be able to use that for your NWEthernetChannel
.
Please try this out and let me know how you get along.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
import Foundation
import Network
func firstV6AddrForInterface(name: String) -> String? {
var addrList: UnsafeMutablePointer<ifaddrs>? = nil
guard
getifaddrs(&addrList) == 0,
let first = addrList
else { return nil }
defer { freeifaddrs(addrList) }
return sequence(first: first, next: { $0.pointee.ifa_next })
.first { addr -> Bool in
guard
let sa = addr.pointee.ifa_addr,
sa.pointee.sa_family == AF_INET6,
String(cString: addr.pointee.ifa_name) == name
else {
return false
}
return true
}
.flatMap { addr -> String? in
var name = [CChar](repeating: 0, count: Int(NI_MAXHOST))
let err = getnameinfo(
addr.pointee.ifa_addr,
socklen_t(addr.pointee.ifa_addr.pointee.sa_len),
&name, socklen_t(name.count),
nil,
0,
NI_NUMERICHOST | NI_NUMERICSERV
)
guard err == 0 else {
return nil
}
return String(cString: name)
}
}
func main() {
guard let en7Addr = firstV6AddrForInterface(name: "en7") else {
fatalError()
}
print("starting with \(en7Addr)…")
let conn = NWConnection(host: .init(en7Addr), port: 12345, using: .udp)
conn.pathUpdateHandler = { path in
print(path.availableInterfaces) // prints: [en7]
}
conn.start(queue: .main)
dispatchMain()
}
main()