Our macOS application (running as a LaunchDaemon) has been able to report the current Wi-Fi SSID and BSSID (if connected) using the airport
command. Since airport
has been removed from macOS, we have not been able to collect BSSID information.
First, I demonstrate that the BSSID exists: I can option-click the Wi-Fi status menu icon and see the following:
Wi-Fi
Interface Name: en0
Address: a8:8f:d9:52:10:7d
* * *
Enable Wi-Fi Logging
Create Diagnostics Report...
Open Wireless Diagnostics...
* * *
Known Network
polymorphic
IP Address: 192.168.86.50
Router: 192.168.86.1
Security: WPA2 Personal
BSSID: 88:3d:24:ba:36:81
Channel: 149 (5 GHz, 80 MHZ)
Country Code: US
RSSI: -60 dBm
Noise: -89 dBm
Tx Rate: 520 Mbps
PHY Mode: 802.11ac
MCS Index: 5
NSS: 2
* * *
Other Networks
* * *
Wi-Fi Settings...
This says to me that:
- The WiFi router I am connected to has SSID =
polymorphic
. - The WiFi router I am connected to has BSSID =
88:3d:24:ba:36:81
. - My computer's Wi-Fi hardware has MAC address =
a8:8f:d9:52:10:7d
. - My computer's Wi-Fi interface name =
en0
.
To get this information now (from within an application), I have attempted to run:
/usr/sbin/networksetup -listallhardwareports
The output of that command includes the following
Hardware Port: Wi-Fi
Device: en0
Ethernet Address: a8:8f:d9:52:10:7d
To get the SSID, I can then execute:
$ /usr/sbin/networksetup -getairportnetwork en0
Current Wi-Fi Network: polymorphic
But I still can't get the router's BSSID.
So I try
$/usr/sbin/networksetup -getinfo 'Wi-Fi'
DHCP Configuration
IP address: 192.168.86.50
Subnet mask: 255.255.255.0
Router: 192.168.86.1
Client ID:
IPv6: Automatic
IPv6 IP address: none
IPv6 Router: none
Wi-Fi ID: a8:8f:d9:52:10:7d
Still no new information.
$ /usr/sbin/networksetup -getmacaddress en0
Ethernet Address: a8:8f:d9:52:10:7d (Device: en0)
This is not helpful either.
Let's try another approach:
$ /usr/sbin/netstat -nr -f inet | grep ^default
default 192.168.86.1 UGScg en0
This tells me that my router's IP address is 192.168.86.1.
The arp
tool should be able to translate
$ /usr/sbin/arp -a -n | grep "(192.168.86.1)"
? (192.168.86.1) at 88:3d:24:ba:36:7f on en0 ifscope [ethernet]
This tells me that the router's MAC address is "88:3d:24:ba:36:7f", but it is not the same value as the router's BSSID, which we know to be 88:3d:24:ba:36:81
!
Another approach. I wrote the following Swift program:
import CoreWLAN
let c : CWWiFiClient = CWWiFiClient.shared()
if let ifs : [CWInterface] = c.interfaces() {
for i in ifs {
print(
i.interfaceName ?? "<nil>",
i.powerOn(),
i.ssid() ?? "<nil>",
i.bssid() ?? "<nil>")
}
}
When executing it with swift
, I got:
en0 true polymorphic <nil>
So for some reason, the CoreWLAN API is hiding the BSSID, but not the SSID.
When I use swiftc
to compile before executing, I get:
en0 true <nil> <nil>
Why is the CoreWLAN API now hiding the SSID as well?
I even tried an Objective-C program:
// Link with:
// -framework Foundation
// -framework CoreWLAN
#include <stdio.h>
#include <CoreWLAN/CoreWLAN.h>
void printWifi() {
NSArray<CWInterface*>* ifs = [[CWWiFiClient sharedWiFiClient] interfaces];
for (CWInterface* i in ifs) {
printf("%s %s %s %s\n",
[i.interfaceName UTF8String],
[i powerOn] ? "true" : "false",
[[i ssid] UTF8String],
[[i bssid] UTF8String]);
}
}
int main() {
printWifi();
return 0;
}
It prints out:
en0 true (null) (null)
Based on <https://developer.apple.com/forums/thread/131636>, I tried
// Link with:
// -framework Foundation
// -framework CoreWLAN
// -framework CoreLocation
#include <stdio.h>
#include <CoreWLAN/CoreWLAN.h>
#include <CoreLocation/CoreLocation.h>
void printWifi() {
NSArray<CWInterface*>* ifs = [[CWWiFiClient sharedWiFiClient] interfaces];
for (CWInterface* i in ifs) {
printf("%s %s %s %s\n",
[i.interfaceName UTF8String],
[i powerOn] ? "true" : "false",
[[i ssid] UTF8String],
[[i bssid] UTF8String]);
}
}
CLLocationManager* startCoreLocation() {
CLLocationManager* mgr = [[CLLocationManager alloc] init];
[mgr requestAlwaysAuthorization];
[mgr startUpdatingLocation];
return mgr;
}
int main() {
CLLocationManager* locMgr = startCoreLocation();
printWifi();
return 0;
}
That change did not seem to make a difference.
After more work, I found that I can not even figure out CLLocationManager authorization. So I attempted to create a minimal program that can get that: <https://github.com/HalCanary/location>.
I am not sure how to proceed here. What is wrong with my location code? Will our application need to get the com.apple.security.personal-information.location
entitlement in order to get the BSSID?
Our macOS … LaunchDaemon … has been able to report the current Wi-Fi SSID and BSSID
That’s not easy on recent versions of macOS because:
-
macOS now gates access to SSID and BSSID information on the System Settings > Privacy & Security > Location privilege.
-
It’s not possible for a daemon to gain the Location privilege )-:
This restricted rolled out a while back and in recently macOS 14.x software updates we’ve been closing various ways to bypass it (such as the airport
tool).
Will our application need to get the com.apple.security.personal-information.location entitlement in order to get the BSSID?
That won’t help. It’s an App Sandbox entitlement, and so is only relevant if you’re in a sandbox. And that’s not the issue here, but rather that a daemon can’t get the Location privilege.
The only approach that will work is to split this functionality out of your launchd
daemon into a launchd
agent. An agent runs in a user context and can gain the Location privilege. And once it has that, it can use Core WLAN to get Wi-Fi information.
There are, however, significant drawbacks to this approach:
-
It’s substantially more complicated.
-
It only works if at least one user is logged in. If all users log out, there are no GUI login sessions and hence no
launchd
agents running.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"