Looking up Apple Devices in a Local Network
- #macOS
• 7 min read
Introduction
A local area network (LAN) is a computer network that interconnects computers within a limited area. Ethernet and Wi-Fi are the two most common technologies for local area networks. Each workstation in the network has its own internet protocol (IP) address, so these addresses can be utilized to distinguish devices in the network.
Address resolution protocol
The easiest way to scan the local network to detect which IP addresses are already taken is to use the address resolution protocol, ARP, which maps local IP addresses to the physical MAC addresses of devices.
On macOS, there is a command-line utility called arp
:
arp -a
It gives the mapping table which shows local IP address assignations to physical MAC addresses.
Example result:
$ arp -a
? (10.0.1.0) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
? (10.0.1.1) at 4c:32:75:c3:3:ce on en0 ifscope [ethernet]
? (10.0.1.2) at (incomplete) on en0 ifscope [ethernet]
? (10.0.1.3) at 3c:22:fb:19:8e:20 on en0 ifscope [ethernet]
? (10.0.1.4) at 58:d3:49:5a:53:c2 on en0 ifscope [ethernet]
? (10.0.1.5) at 18:3e:ef:e6:8b:a6 on en0 ifscope [ethernet]
? (10.0.1.6) at (incomplete) on en0 ifscope [ethernet]
? (10.0.1.7) at ac:bc:32:5d:27:5 on en0 ifscope [ethernet]
? (10.0.1.8) at (incomplete) on en0 ifscope [ethernet]
? (10.0.1.9) at 60:5b:b4:8e:f0:f3 on en0 ifscope [ethernet]
? (10.0.1.10) at 26:58:32:f4:c:d6 on en0 ifscope [ethernet]
? (10.0.1.11) at d4:90:9c:e6:d4:f5 on en0 ifscope [ethernet]
? (10.0.1.12) at (incomplete) on en0 ifscope [ethernet]
? (10.0.1.13) at (incomplete) on en0 ifscope [ethernet]
? (10.0.1.14) at f8:4d:89:7f:d7:2f on en0 ifscope permanent [ethernet]
? (10.0.1.15) at 50:ec:50:4:b:e7 on en0 ifscope [ethernet]
Note: the
arp
tool is open sourced.
MAC address
At the point when we have the MAC addresses list, we can look up the device's manufacturer.
A MAC Address is a 12-digit hexadecimal number (6-Byte binary number), most often written in the colon-hexadecimal notation. The first 6 digits (say 00:40:96) of a MAC address identify the manufacturer and constitute the organizational unique identifier or OUI. IEEE Registration Authority Committee assigns these MAC prefixes to its registered vendors.
Apple's OUI Examples:
...
F8:4E:73
F8:4D:89
F8:38:80
4C:32:75
4C:6B:E8
...
However, even if we have a device manufacturer, we still can't get the device type and model. Some interprocess communication should be done on top to discover device information.
Bonjour
One of the widely used technologies for networking and discoverability is Bonjour from Apple.
Bonjour, also known as zero-configuration networking, enables the automatic discovery of devices and services on a local network using industry-standard IP protocols. Bonjour makes it easy to discover, publish, and resolve network services with a sophisticated, easy-to-use programming interface that is accessible from Cocoa, Ruby, Python, and other languages.
Apple devices publish corresponding Bonjour services to the network so that others can discover and distinguish them by service type. Bonjour locates devices such as printers, and other computers, and the services that those devices offer on a local network using multicast Domain Name System (mDNS) service records.
Prototype
We will implement a prototype in Swift because Foundation has a top-level API for discovering and monitoring Bonjour services. This API collection is deprecated but is still suitable for demonstrating purposes and is backward compatible.
One can achieve the same result using the new Network framework.
For the prototype, we will use the following pattern:
- Browse for the determined list of the services to set up the devices list.
- Monitor informational device services for TXT records updates.
Browse needed services
For our purposes, we will set up service discovery of several types: _companion-link._tcp
, _rdlink._tcp.
, _airport._tcp.
.
_companion-link._tcp
Companion Link is an undocumented Apple service for communication between Apple devices; however, almost all devices tested are discoverable by it.
_rdlink._tcp.
Remote Active Queue Management service. Used for iOS devices discovering and distinguishing.
_airport._tcp.
Admin Airport Utility service. Used for discovering Airport Base Stations.
Once we have several services to discover, we need to have an array of NetServiceBrowser
:
let servicesToBrowse = ["_companion-link._tcp.", "_rdlink._tcp.", "_airport._tcp."]
servicesToBrowse.forEach {
let browser = NetServiceBrowser()
browser.delegate = self
browser.searchForServices(ofType: $0, inDomain: "local.")
}
NetServiceBrowserDelegate
delegate methods will be invoked in case of services discovered:
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
servicesToMonitor
.map { type -> NetService in
let service = NetService(domain: "local.", type: type, name: service.name)
service.delegate = self
return service
}.forEach {
$0.startMonitoring()
services.append($0)
}
}
In the function above we start monitoring the discovered services to get their TXT records. We use several services type there:
"_device-info._tcp.", "_airplay._tcp."
_device-info._tcp.
Device Info Service. Returns basic device information with TXT record.
_airplay._tcp.
Airplay Info Service. Returns basic device information with TXT record. Used for AirPlay-based devices, like HomePods.
NetServiceDelegate
delegate methods will be invoked in case of TXT records updated:
func netService(_ sender: NetService, didUpdateTXTRecord data: Data) {
guard let deviceModel = self.extractModelFromTXTRecord(recordData: data) else {
return
}
if let existingDevice = self.devices.first(where: { $0.serviceName == sender.name }) {
existingDevice.updateWithDeviceModel(model: deviceModel)
} else {
let device = Device(serviceName: sender.name, model: deviceModel)
devices.append(device)
}
}
private func extractModelFromTXTRecord(recordData: Data) -> String? {
let txtDictionary = NetService.dictionary(fromTXTRecord: recordData)
//Check for other keys here
guard let modelData = txtDictionary["model"], let model = String(data: modelData, encoding: .utf8) else {
return nil
}
return model
}
TXT dictionaries from above contain the "model" key, corresponding to the device model in the value.
For example, MacBookPro18,3
for MacBook Pro (14-inch, 2021) or j210ap
for iPad mini (5th generation).
Public "model to marketing names" databases could be used here to get human-readable names, or we could also use the private Launch Services function.
Launch Services device type identifier
Launch Services framework provides a private function to translate a device model into a device type identifier to use it with public NSWorkspace
API to get marketing titles and images.
extern NSString* _LSCreateDeviceTypeIdentifierWithModelCode(NSString *);
@implementation ANLaunchService
+ (NSString *)deviceTypeIdentifierFromModelCode:(NSString *)modelCode
{
return _LSCreateDeviceTypeIdentifierWithModelCode(modelCode);
}
@end
...
let workspace = NSWorkspace.shared
let typeIdentifier = ANLaunchService.deviceTypeIdentifier(fromModelCode: model)
let deviceDescription = workspace.localizedDescription(forType: typeIdentifier) ?? ""
let deviceIcon = workspace.icon(forFileType: typeIdentifier)
With the combination of the above APIs, we get the complete list of available Apple devices in a local network, along with their device types and marketing information.
Conclusion
With the given set of available macOS APIs, it is possible to build a fully functional list of Apple devices in the local network.
Potential integrations
Knowledge of the local network environment can be beneficial for building appropriate advertisement targeting. It can also provide possibilities for monitoring local network devices for further manipulation. We could examine and use the local network to suggest software installation to specific workstations with appropriate hardware.
Links
This is an independent publication and it has not been authorized, sponsored, or otherwise approved by Apple Inc.