ohmu
·
2026-10-15
1#include "wifi.h"
2#include "common/percent.h"
3#include "common/processing.h"
4#include "common/strutil.h"
5#include "common/apple/cf_helpers.h"
6
7#import <CoreWLAN/CoreWLAN.h>
8#import <IOKit/IOKitLib.h>
9#import <IOKit/kext/KextManager.h>
10
11@interface CWNetworkProfile ()
12@property (readonly, retain, nullable) NSArray<NSDictionary*>* bssidList;
13@end
14
15const char* ffDetectWifi(FFlist* result) {
16 NSArray<CWInterface*>* interfaces = CWWiFiClient.sharedWiFiClient.interfaces;
17 if (!interfaces) {
18 return "CWWiFiClient.sharedWiFiClient.interfaces is nil";
19 }
20
21 for (CWInterface* inf in interfaces) {
22 FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
23 ffStrbufInit(&item->inf.description);
24 ffStrbufInit(&item->inf.status);
25 ffStrbufInit(&item->conn.status);
26 ffStrbufInit(&item->conn.ssid);
27 ffStrbufInit(&item->conn.bssid);
28 ffStrbufInit(&item->conn.protocol);
29 ffStrbufInit(&item->conn.security);
30 item->conn.signalQuality = -DBL_MAX;
31 item->conn.rxRate = -DBL_MAX;
32 item->conn.txRate = -DBL_MAX;
33 item->conn.channel = 0;
34 item->conn.channelWidth = 0;
35 item->conn.frequency = 0;
36
37 ffStrbufSetS(&item->inf.description, inf.interfaceName.UTF8String);
38 ffStrbufSetStatic(&item->inf.status, inf.powerOn ? "Power On" : "Power Off");
39
40 if (!inf.powerOn) {
41 continue;
42 }
43
44 ffStrbufSetStatic(&item->conn.status, inf.interfaceMode != kCWInterfaceModeNone ? "Active" : "Inactive");
45 if (inf.interfaceMode == kCWInterfaceModeNone) {
46 continue;
47 }
48
49 FF_STRBUF_AUTO_DESTROY ipconfig = ffStrbufCreate();
50
51 CWNetworkProfile* networkProfile = inf.configuration.networkProfiles.firstObject;
52
53 if (inf.ssid) { // https://developer.apple.com/forums/thread/732431
54 ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String);
55 } else if (networkProfile.ssid) {
56 ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String);
57 } else {
58 ffStrbufSetStatic(&item->conn.ssid, "<redacted>"); // https://developer.apple.com/forums/thread/732431
59 }
60
61 if (inf.bssid) {
62 ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);
63 } else if (networkProfile.bssidList) {
64 ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]);
65 } else {
66 ffStrbufSetStatic(&item->conn.bssid, "<redacted>");
67 }
68
69 switch (inf.activePHYMode) {
70 case kCWPHYModeNone:
71 ffStrbufSetStatic(&item->conn.protocol, "none");
72 break;
73 case kCWPHYMode11a:
74 ffStrbufSetStatic(&item->conn.protocol, "802.11a");
75 break;
76 case kCWPHYMode11b:
77 ffStrbufSetStatic(&item->conn.protocol, "802.11b");
78 break;
79 case kCWPHYMode11g:
80 ffStrbufSetStatic(&item->conn.protocol, "802.11g");
81 break;
82 case kCWPHYMode11n:
83 ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)");
84 break;
85 case kCWPHYMode11ac:
86 ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)");
87 break;
88 case 6 /*kCWPHYMode11ax*/:
89 ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)");
90 break;
91 case 7 /*kCWPHYMode11be?*/:
92 ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)");
93 break;
94 default:
95 if (inf.activePHYMode < 8) {
96 ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode);
97 }
98 break;
99 }
100 item->conn.signalQuality = ffRssiToSignalQuality((int) inf.rssiValue);
101 item->conn.txRate = inf.transmitRate;
102
103 switch (inf.security) {
104 case kCWSecurityNone:
105 ffStrbufSetStatic(&item->conn.security, "Insecure");
106 break;
107 case kCWSecurityWEP:
108 ffStrbufSetStatic(&item->conn.security, "WEP");
109 break;
110 case kCWSecurityWPAPersonal:
111 ffStrbufSetStatic(&item->conn.security, "WPA Personal");
112 break;
113 case kCWSecurityWPAPersonalMixed:
114 ffStrbufSetStatic(&item->conn.security, "WPA Personal Mixed");
115 break;
116 case kCWSecurityWPA2Personal:
117 ffStrbufSetStatic(&item->conn.security, "WPA2 Personal");
118 break;
119 case kCWSecurityPersonal:
120 ffStrbufSetStatic(&item->conn.security, "Personal");
121 break;
122 case kCWSecurityDynamicWEP:
123 ffStrbufSetStatic(&item->conn.security, "Dynamic WEP");
124 break;
125 case kCWSecurityWPAEnterprise:
126 ffStrbufSetStatic(&item->conn.security, "WPA Enterprise");
127 break;
128 case kCWSecurityWPAEnterpriseMixed:
129 ffStrbufSetStatic(&item->conn.security, "WPA Enterprise Mixed");
130 break;
131 case kCWSecurityWPA2Enterprise:
132 ffStrbufSetStatic(&item->conn.security, "WPA2 Enterprise");
133 break;
134 case kCWSecurityEnterprise:
135 ffStrbufSetStatic(&item->conn.security, "Enterprise");
136 break;
137 case 11 /*kCWSecurityWPA3Personal*/:
138 ffStrbufSetStatic(&item->conn.security, "WPA3 Personal");
139 break;
140 case 12 /*kCWSecurityWPA3Enterprise*/:
141 ffStrbufSetStatic(&item->conn.security, "WPA3 Enterprise");
142 break;
143 case 13 /*kCWSecurityWPA3Transition*/:
144 ffStrbufSetStatic(&item->conn.security, "WPA3 Transition");
145 break;
146 case 14 /*kCWSecurityOWE*/:
147 ffStrbufSetStatic(&item->conn.security, "OWE");
148 break;
149 case 15 /*kCWSecurityOWETransition*/:
150 ffStrbufSetStatic(&item->conn.security, "OWE Transition");
151 break;
152 default:
153 ffStrbufAppendF(&item->conn.security, "Unknown (%ld)", inf.security);
154 break;
155 }
156
157 item->conn.channel = (uint16_t) inf.wlanChannel.channelNumber;
158 switch (inf.wlanChannel.channelWidth) {
159 case kCWChannelWidth20MHz: item->conn.channelWidth = 20; break;
160 case kCWChannelWidth40MHz: item->conn.channelWidth = 40; break;
161 case kCWChannelWidth80MHz: item->conn.channelWidth = 80; break;
162 case kCWChannelWidth160MHz: item->conn.channelWidth = 160; break;
163 default: item->conn.channelWidth = 0; break;
164 }
165
166 FF_IOOBJECT_AUTO_RELEASE io_object_t service = IOServiceGetMatchingService(MACH_PORT_NULL, IOBSDNameMatching(MACH_PORT_NULL, 0, item->inf.description.chars));
167 FF_CFTYPE_AUTO_RELEASE CFNumberRef frequency;
168 // Note: IO80211ChannelFrequency is only available when the interface is connected
169 while (!(frequency = IORegistryEntryCreateCFProperty(service, CFSTR("IO80211ChannelFrequency"), nullptr, kNilOptions))) {
170 io_object_t parentService = IO_OBJECT_NULL;
171 if (IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService) == KERN_SUCCESS) {
172 IOObjectRelease(service);
173 service = parentService;
174 } else {
175 IOObjectRelease(service);
176 service = IO_OBJECT_NULL;
177 break;
178 }
179 }
180 if (frequency) {
181 int64_t value;
182 if (ffCfNumGetInt64(frequency, &value) == nil) {
183 item->conn.frequency = (uint16_t) value;
184 }
185 }
186 }
187
188 return nullptr;
189}