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).
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"