Skip to content

Commit 1ed804d

Browse files
committed
Wifi (macOS): use sudo wdutil info on Tahoe
sudo, you know it.
1 parent 0e8b9be commit 1ed804d

1 file changed

Lines changed: 208 additions & 4 deletions

File tree

src/detection/wifi/wifi_apple.m

Lines changed: 208 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@
44

55
#import <CoreWLAN/CoreWLAN.h>
66

7+
static inline double rssiToSignalQuality(int rssi)
8+
{
9+
return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2);
10+
}
11+
712
static bool queryIpconfig(const char* ifName, FFstrbuf* result)
813
{
14+
if (@available(macOS 26.0, *))
15+
{
16+
// ipconfig no longer work in Tahoe
17+
return false;
18+
}
19+
920
return ffProcessAppendStdOut(result, (char* const[]) {
1021
"/usr/sbin/ipconfig",
1122
"getsummary",
@@ -41,15 +52,194 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb
4152
return true;
4253
}
4354

44-
const char* ffDetectWifi(FFlist* result)
55+
static const char* detectByWdutil(FFlist* result)
56+
{
57+
FF_STRBUF_AUTO_DESTROY wdutil = ffStrbufCreate();
58+
59+
if (geteuid() != 0)
60+
return "wdutil requires root privileges to run";
61+
62+
bool ok = ffProcessAppendStdOut(&wdutil, (char* const[]) {
63+
"/usr/bin/wdutil",
64+
"info",
65+
NULL
66+
}) == NULL;
67+
if (!ok) return "Failed to run wdutil info command";
68+
69+
// ffStrbufSetS(&wdutil, "————————————————————————————————————————————————————————————————————\nNETWORK\n————————————————————————————————————————————————————————————————————\n Primary IPv4 : en0 (Wi-Fi / 37C2D473-40B7-4AFB-93D2-49C588580986)\n : 192.168.48.96\n Primary IPv6 : en0 (Wi-Fi / 37C2D473-40B7-4AFB-93D2-49C588580986)\n : 240a:42a3:5200:1092:18c7:87a2:c8df:ad5a\n : 240a:42a3:5200:1092:5837:b618:deb0:b60c\n DNS Addresses : 240a:42a3:5200:1092::1b\n : 192.168.48.210\n Apple : Reachable\n————————————————————————————————————————————————————————————————————\nWIFI\n————————————————————————————————————————————————————————————————————\n MAC Address : 9e:c4:0a:4b:fe:ab (hw=f4:d4:88:70:f3:39)\n Interface Name : en0\n Power : On [On]\n Op Mode : STA\n SSID : iQOO 13\n BSSID : 92:ad:bb:24:51:bb\n RSSI : -24 dBm\n CCA : 36 %\n Noise : -83 dBm\n Tx Rate : 229.0 Mbps\n Security : WPA2 Personal\n PHY Mode : 11ax\n MCS Index : 9\n Guard Interval : 800\n NSS : 2\n Channel : 2g6/20\n Country Code : CN\n Scan Cache Count : 37\n NetworkServiceID : 37C2D473-40B7-4AFB-93D2-49C588580986\n IPv4 Config Method : DHCP\n IPv4 Address : 192.168.48.96\n IPv4 Router : 192.168.48.210\n IPv6 Config Method : Automatic\n IPv6 Address : 240a:42a3:5200:1092:18c7:87a2:c8df:ad5a\n : 240a:42a3:5200:1092:5837:b618:deb0:b60c\n IPv6 Router : fe80::90ad:bbff:fe24:51bb\n DNS : 192.168.48.210\n : 240a:42a3:5200:1092::1b\n BTC Mode : Off\n Desense :\n Chain Ack : []\n BTC Profile 2.4GHz : Disabled\n BTC Profile 5GHz : Disabled\n Sniffer Supported : YES\n Supports 6e : No\n Supported Channels : 2g1/20,2g2/20,2g3/20,2g4/20,2g5/20,2g6/20,2g7/20,2g8/20,2g9/20,2g10/20,2g11/20,2g12/20,2g13/20,5g36/20,5g40/20,5g44/20,5g48/20,5g52/20,5g56/20,5g60/20,5g64/20,5g149/20,5g153/20,5g157/20,5g161/20,5g165/20,5g36/40,5g40/40,5g44/40,5g48/40,5g52/40,5g56/40,5g60/40,5g64/40,5g149/40,5g153/40,5g157/40,5g161/40,5g36/80,5g40/80,5g44/80,5g48/80,5g52/80,5g56/80,5g60/80,5g64/80,5g149/80,5g153/80,5g157/80,5g161/80\n————————————————————————————————————————————————————————————————————\nBLUETOOTH\n————————————————————————————————————————————————————————————————————\n Power : On\n Address : f4:d4:88:78:91:4a\n Discoverable : No\n Connectable : Yes\n Scanning : No\n Devices : 12 (paired=5 cloud=1 connected=0)\n\n iQOO Wireless Active\n Address : 20:18:5b:3a:fe:93\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n ERAZER N500\n Address : c1:35:45:fb:16:ac\n Paired : No\n CloudPaired : No\n Connected : No\n\n M585/M590\n Address : c9:7a:ff:82:3b:fe\n Paired : No\n CloudPaired : No\n Connected : No\n\n Xiaomi Earphones Explore\n Address : e0:08:71:78:75:0d\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n WH-1000XM5\n Address : 88:c9:e8:61:fa:76\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n ERAZER G501\n Address : d1:00:0f:08:ae:42\n Paired : No\n CloudPaired : No\n Connected : No\n\n MSI BluetoothMouse\n Address : e0:10:9c:40:73:c5\n Paired : No\n CloudPaired : No\n Connected : No\n\n Bluetooth Mouse M336/M337/M535\n Address : 34:88:5d:8f:c0:fe\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n MacBook Pro\n Address : 3c:15:c2:e5:00:b7\n Paired : No\n CloudPaired : Yes\n Connected : No\n\n\n Address : e4:57:68:8e:bb:28\n Paired : No\n CloudPaired : No\n Connected : No\n\n ERAZER N500\n Address : c1:35:45:fb:16:aa\n Paired : No\n CloudPaired : No\n Connected : No\n\n Pro Controller\n Address : 5c:52:1e:88:7e:70\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n————————————————————————————————————————————————————————————————————\nAWDL\n————————————————————————————————————————————————————————————————————\n MAC Address : 46:3a:d9:c1:b0:2e (hw=46:3a:d9:c1:b0:2e)\n AirDrop Disc Mode : Everyone\n AWDL Enabled : No\n Interface Name : awdl0\n Power : On\n IPv6 Address : fe80::443a:d9ff:fec1:b02e\n Schedule State : n/a\n Channel Sequence : n/a\n Op Mode : n/a\n Real Time Mode : No\n Sync State : n/a\n Sync Params : n/a\n Master Channel : n/a\n Election Params : n/a\n————————————————————————————————————————————————————————————————————\nPOWER\n————————————————————————————————————————————————————————————————————\n Power Source : AC\n Battery Warning Level: None\n System Caps : FullWake:cpu disk net aud vid\n————————————————————————————————————————————————————————————————————\nWIFI FAULTS LAST HOUR\n————————————————————————————————————————————————————————————————————\n None\n————————————————————————————————————————————————————————————————————\nWIFI RECOVERIES LAST HOUR\n————————————————————————————————————————————————————————————————————\n None\n————————————————————————————————————————————————————————————————————\nWIFI LINK TESTS LAST HOUR\n————————————————————————————————————————————————————————————————————\n None");
70+
71+
// ...
72+
// ————————————————————————————————————————————————————————————————————
73+
// WIFI
74+
// ————————————————————————————————————————————————————————————————————
75+
// <WIFI INFO>
76+
// ————————————————————————————————————————————————————————————————————
77+
// ...
78+
79+
{
80+
// Remove unrelated lines
81+
uint32_t start = ffStrbufFirstIndexS(&wdutil, "\nWIFI\n");
82+
if (start >= wdutil.length)
83+
return "wdutil info command did not return WIFI section (1)";
84+
85+
start += 6; // Skip "\nWIFI\n"
86+
start = ffStrbufNextIndexC(&wdutil, start, '\n');
87+
if (start >= wdutil.length)
88+
return "wdutil info command did not return WIFI section (2)";
89+
start++;
90+
91+
uint32_t end = ffStrbufNextIndexS(&wdutil, start, "\n——————————");
92+
93+
ffStrbufSubstr(&wdutil, start, end);
94+
}
95+
96+
97+
// `wdutil info <interface>` returns a string like this:
98+
// MAC Address : xx:xx:xx:xx:xx:xx (hw=xx:xx:xx:xx:xx:xx)
99+
// Interface Name : en0
100+
// Power : On [On]
101+
// Op Mode : STA
102+
// SSID : XXX-XXX
103+
// BSSID : xx:xx:xx:xx:xx:xx
104+
// RSSI : -58 dBm
105+
// CCA : 29 %
106+
// Noise : -96 dBm
107+
// Tx Rate : 173.0 Mbps
108+
// Security : WPA2 Enterprise
109+
// 802.1X Mode : User
110+
// 802.1X Supplicant : Authenticated
111+
// PHY Mode : 11ac
112+
// MCS Index : 8
113+
// Guard Interval : 400
114+
// NSS : 2
115+
// Channel : 5g165/20
116+
// Country Code : CN
117+
// Scan Cache Count : 28
118+
// NetworkServiceID : XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
119+
// IPv4 Config Method : DHCP
120+
// IPv4 Address : xx.xx.xx.xx
121+
// IPv4 Router : xx.xx.xx.x
122+
// IPv6 Config Method : Automatic
123+
// IPv6 Address : xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
124+
// IPv6 Router : None
125+
// DNS : xxx.xxx.xxx.xxx
126+
// : xxx.xxx.xxx.xxx
127+
// BTC Mode : Off
128+
// Desense :
129+
// Chain Ack : []
130+
// BTC Profile 2.4GHz : Disabled
131+
// BTC Profile 5GHz : Disabled
132+
// Sniffer Supported : YES
133+
// Supports 6e : No
134+
// Supported Channels : 2g1/20,2g2/20,2g3/20,2g4/20,2g5/20,2g6/20,2g7/20,2g8/20,2g9/20,2g10/20,2g11/20,2g12/20,2g13/20,5g36/20,5g40/20,5g44/20,5g48/20,5g52/20,5g56/20,5g60/20,5g64/20,5g149/20,5g153/20,5g157/20,5g161/20,5g165/20,5g36/40,5g40/40,5g44/40,5g48/40,5g52/40,5g56/40,5g60/40,5g64/40,5g149/40,5g153/40,5g157/40,5g161/40,5g36/80,5g40/80,5g44/80,5g48/80,5g52/80,5g56/80,5g60/80,5g64/80,5g149/80,5g153/80,5g157/80,5g161/80
135+
136+
FFWifiResult* item = (FFWifiResult*) ffListAdd(result);
137+
ffStrbufInit(&item->inf.description);
138+
ffStrbufInit(&item->inf.status);
139+
ffStrbufInit(&item->conn.status);
140+
ffStrbufInit(&item->conn.ssid);
141+
ffStrbufInit(&item->conn.bssid);
142+
ffStrbufInit(&item->conn.protocol);
143+
ffStrbufInit(&item->conn.security);
144+
item->conn.signalQuality = 0.0/0.0;
145+
item->conn.rxRate = 0.0/0.0;
146+
item->conn.txRate = 0.0/0.0;
147+
item->conn.channel = 0;
148+
item->conn.frequency = 0;
149+
150+
char* line = NULL;
151+
size_t len = 0;
152+
while (ffStrbufGetline(&line, &len, &wdutil))
153+
{
154+
const char* key = line + 4; // Skip " "
155+
const char* value = key + strlen("MAC Address : ");
156+
switch (key[0] << 24 | key[1] << 16 | key[2] << 8 | key[3])
157+
{
158+
case 'Inte': // Interface Name
159+
ffStrbufAppendS(&item->inf.description, value);
160+
break;
161+
case 'Powe': // Power
162+
if (ffStrStartsWith(value, "On "))
163+
ffStrbufSetStatic(&item->inf.status, "Power On");
164+
else
165+
ffStrbufSetStatic(&item->inf.status, "Power Off");
166+
break;
167+
case 'SSID': // SSID
168+
ffStrbufAppendS(&item->conn.ssid, value);
169+
break;
170+
case 'BSSI': // BSSID
171+
if (ffStrEquals(value, "None") && ffStrbufEqualS(&item->conn.ssid, "None"))
172+
{
173+
ffStrbufSetStatic(&item->conn.status, "Inactive");
174+
ffStrbufClear(&item->conn.ssid); // None
175+
return NULL;
176+
}
177+
ffStrbufSetStatic(&item->conn.status, "Active");
178+
ffStrbufAppendS(&item->conn.bssid, value);
179+
break;
180+
case 'RSSI': // RSSI
181+
item->conn.signalQuality = rssiToSignalQuality((int) strtol(value, NULL, 10));
182+
break;
183+
case 'Tx R': // Tx Rate
184+
item->conn.txRate = strtod(value, NULL);
185+
break;
186+
case 'Secu': // Security
187+
if (ffStrEquals(value, "None"))
188+
ffStrbufSetStatic(&item->conn.security, "Insecure");
189+
else
190+
ffStrbufAppendS(&item->conn.security, value);
191+
break;
192+
case 'PHY ': // PHY Mode
193+
if (ffStrEquals(value, "None"))
194+
ffStrbufSetStatic(&item->conn.protocol, "none");
195+
else if (ffStrEquals(value, "11a"))
196+
ffStrbufSetStatic(&item->conn.protocol, "802.11a");
197+
else if (ffStrEquals(value, "11b"))
198+
ffStrbufSetStatic(&item->conn.protocol, "802.11b");
199+
else if (ffStrEquals(value, "11g"))
200+
ffStrbufSetStatic(&item->conn.protocol, "802.11g");
201+
else if (ffStrEquals(value, "11n"))
202+
ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)");
203+
else if (ffStrEquals(value, "11ac"))
204+
ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)");
205+
else if (ffStrEquals(value, "11ax"))
206+
ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)");
207+
else if (ffStrEquals(value, "11be"))
208+
ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)");
209+
else
210+
ffStrbufAppendS(&item->conn.protocol, value);
211+
break;
212+
case 'Chan': // Channel
213+
{
214+
int band, channel;
215+
if (sscanf(value, "%dg%d", &band, &channel) == 2)
216+
{
217+
item->conn.channel = (uint16_t) channel;
218+
switch (band)
219+
{
220+
case 2: item->conn.frequency = 2400; break;
221+
case 5: item->conn.frequency = 5400; break;
222+
case 6: item->conn.frequency = 6400; break;
223+
default: item->conn.frequency = 0; break;
224+
}
225+
}
226+
break;
227+
}
228+
}
229+
}
230+
231+
return NULL;
232+
}
233+
234+
static const char* detectByCoreWlan(FFlist* result)
45235
{
46236
NSArray<CWInterface*>* interfaces = CWWiFiClient.sharedWiFiClient.interfaces;
47237
if (!interfaces)
48238
return "CWWiFiClient.sharedWiFiClient.interfaces is nil";
49239

50240
for (CWInterface* inf in interfaces)
51241
{
52-
FFWifiResult* item = (FFWifiResult*)ffListAdd(result);
242+
FFWifiResult* item = (FFWifiResult*) ffListAdd(result);
53243
ffStrbufInit(&item->inf.description);
54244
ffStrbufInit(&item->inf.status);
55245
ffStrbufInit(&item->conn.status);
@@ -79,12 +269,14 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb
79269
else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig)))
80270
getWifiInfoByIpconfig(&ipconfig, "\n SSID : ", &item->conn.ssid);
81271
else
82-
ffStrbufSetStatic(&item->conn.ssid, "<unknown ssid>"); // https://developer.apple.com/forums/thread/732431
272+
ffStrbufSetStatic(&item->conn.ssid, "<redacted>"); // https://developer.apple.com/forums/thread/732431
83273

84274
if (inf.bssid)
85275
ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);
86276
else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig)))
87277
getWifiInfoByIpconfig(&ipconfig, "\n BSSID : ", &item->conn.bssid);
278+
else
279+
ffStrbufSetStatic(&item->conn.bssid, "<redacted>");
88280

89281
switch(inf.activePHYMode)
90282
{
@@ -117,7 +309,7 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb
117309
ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode);
118310
break;
119311
}
120-
item->conn.signalQuality = (double) (inf.rssiValue >= -50 ? 100 : inf.rssiValue <= -100 ? 0 : (inf.rssiValue + 100) * 2);
312+
item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue);
121313
item->conn.txRate = inf.transmitRate;
122314

123315
switch(inf.security)
@@ -189,5 +381,17 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb
189381
default: item->conn.frequency = 0; break;
190382
}
191383
}
384+
192385
return NULL;
193386
}
387+
388+
const char* ffDetectWifi(FFlist* result)
389+
{
390+
if (@available(macOS 26.0, *))
391+
{
392+
if (detectByWdutil(result) == NULL)
393+
return NULL;
394+
}
395+
396+
return detectByCoreWlan(result);
397+
}

0 commit comments

Comments
 (0)