Проблема: Apple закрыла доступ к Wi-Fi данным
Если ты писал скрипты на macOS, которые читают информацию о Wi-Fi сети — SSID, BSSID, мощность сигнала, канал — последние пару лет стали жестокими.
До недавнего времени все полагались на утилиту airport, встроенную в систему. Но в macOS Sonoma 14.4 Apple её удалила. Вместо неё предложили wdutil, но с подвохом: он требует sudo и всё равно возвращает <redacted> вместо реальных данных.
Это было частью намеренной политики, начавшейся в macOS 14.5 — Apple классифицировала Wi-Fi метаданные как данные о геолокации и заблокировала доступ.
Другие попытки:
networksetup -getairportnetwork— работает только для SSID на macOS 13–14, в версии 15 отключенаioreg— больше не показывает нужные значенияsystem_profiler SPAirPortDataType— даёт уровень сигнала, но не соседние сети
Единственный одобренный путь: CoreWLAN
Apple признаёт только один способ — фреймворк CoreWLAN с методом scanForNetworks(...):
- (NSSet<CWNetwork *> *)scanForNetworksWithName:(NSString *)networkName
includeHidden:(BOOL)includeHidden
error:(NSError * _Nullable *)error;
Но здесь два подвоха:
- Нужно вызывать из Objective-C или Swift — Go не может этого делать напрямую
- Метод возвращает реальные BSSIDs только приложениям, которые имеют разрешение на Location Services и подписаны стабильным Developer ID сертификатом
Вторая подвох — это капец
Вот что говорит сама Apple (DTS team):
TCC, подсистема macOS, которая управляет привилегиями в System Preferences > Security & Privacy > Privacy, плохо работает со скриптами. Для надёжной работы код должен быть подписан сертификатом, который TCC может отследить от сборки к сборке. Не unsigned, не ad-hoc signed.
Проблема в том, что Go-бинарники по умолчанию неподписанные и меняют хеш с каждой сборкой. TCC никогда не будет постоянно разрешать доступ неизменяющемуся приложению.
Решение: Helper App Bundle с правильной подписью
Лазейка в том, что TCC понимает отдельный подписанный app bundle со стабильным идентификатором. Вот паттерн:
- Твой Go-бинарник (неподписанный — это нормально) содержит малюсенький companion app bundle
- Bundle подписан один раз Developer ID Application сертификатом и нотаризован через Apple Notary Service
- При запуске Go-программа распаковывает bundle в temp-директорию и запускает его через
open -W - Bundle запрашивает разрешение Location Services. У него стабильная подпись — TCC с радостью отслеживает авторизацию
- Bundle делает вызов CoreWLAN, сериализует результат и отправляет его обратно на Go-сторону через loopback сокет
Когда пользователь один раз одобрит Permission для Location Services, все последующие вызовы Scan() вернут реальные BSSIDs.
Библиотека macwifi
Этот паттерн упакован в Go-библиотеку macwifi. Helper bundle встроен в пакет через go:embed:
package main
import (
"context"
"fmt"
"github.com/jaisonerick/macwifi"
)
func main() {
nets, err := macwifi.Scan(context.Background())
if err != nil {
panic(err)
}
for _, n := range nets {
fmt.Printf("%-32s %s %d dBm ch %d %s\n",
n.SSID, n.BSSID, n.RSSI, n.Channel, n.Security)
}
}
Вывод (после первого одобрения Permission):
Office Wi-Fi aa:bb:cc:dd:ee:ff -52 dBm ch 149 WPA2
Guest 11:22:33:44:55:66 -71 dBm ch 36 WPA2
Conference Room 77:88:99:aa:bb:cc -68 dBm ch 6 WPA2
Итог
Apple закрывает доступ? Не проблема — нужно просто играть по её правилам, но более хитро. Правильно подписанный helper app — и Wi-Fi сканирование из Go работает как часики.