Swift Network.framework: get NWInterface for ethernet interface without IP connectivity

1.4k views Asked by At

The "advances in networking" talks for WWDC2019 had this example of using NWEthernetChannel to monitor a custom (non-IP) protocol. This is for MacOS.

import Foundation
import Network
let path = NWPathMonitor(requiredInterfaceType: .wiredEthernet).currentPath
guard let interface = path.availableInterfaces.first else {
  fatalError("not connected to Internet")
}
let channel = NWEthernetChannel(on: interface, etherType: 0xB26E)

For my application I need to use a NWEthernetChannel to monitor a custom protocol (actually Cisco Discovery Protocol and/or Link-layer discovery protocol) on an Ethernet link which does not have IP Internet connectivity (but it does have physical link to a switch). NWPath appears to only give me a NWInterface struct if it is a valid path to the Internet.

How can I get a list of NWInterface Structs on the Mac without having a valid Internet path?

In my particular use case I'm only interested in .wiredEthernet.

Something as simple as getting a full array of all NWInterfaces on a box would be sufficient, but so far the only way to "vend" NWInterfaces I've found is with NWPathMonitor, which seems to require IP connectivity.

1

There are 1 answers

2
David Nadoba On

You can get a NWIntferface if you know its corresponding BSD name. The documentation of IPv4Address.init(_:) says that you can specify the name of an interface after the IP Address, separated by %.

/// Create an IP address from an address literal string.
/// If the string contains '%' to indicate an interface, the interface will be
/// associated with the address, such as "::1%lo0" being associated with the loopback
/// interface.
/// This function does not perform host name to address resolution. This is the same as calling getaddrinfo
/// and using AI_NUMERICHOST.

You can only find this documentation in the generated swift interface, not on the website.

The SystemConfiguration framework provides the functionality to get a list of all interfaces with their corresponding BSD names.

import Foundation
import Network
import SystemConfiguration

// get all interfaces
let interfaces = SCNetworkInterfaceCopyAll() as? Array<SCNetworkInterface> ?? []
// convert to NWInterface
let nwInterfaces = interfaces.compactMap { interface -> NWInterface? in
    guard let bsdName = SCNetworkInterfaceGetBSDName(interface) else { return nil }
    return IPv4Address("127.0.0.1%\(bsdName)")?.interface
}

print(interfaces)

This works well but it feels like a workaround. I wish Network.framework would provide a better option to get all interfaces.