En este tutorial vamos a crear una pequeña aplicación para macOS capaz de escanear las redes Wi-Fi cercanas y mostrar información como SSID, BSSID/MAC, RSSI, canal, banda, ancho de canal, país y beacon.
La aplicación se construirá directamente desde Terminal, sin necesidad de Xcode.
Nota: el BSSID es la dirección MAC del punto de acceso Wi-Fi. El programa no obtiene las MAC de los dispositivos conectados a redes de terceros.
1. ¿Qué vamos a construir?
El resultado será una aplicación:
WiFiRadar.app
que podrá obtener información similar a:
==============================================
RADAR WI-FI
Redes encontradas: 12
==============================================
SSID : MiRed
BSSID : AA:BB:CC:DD:EE:FF
RSSI : -42 dBm
Ruido : -91 dBm
Canal : 6
Banda : 2.4 GHz
Ancho : 20 MHz
País : ES
Beacon : 100 ms
----------------------------------------------
La tecnología utilizada será CoreWLAN, el framework de Apple para trabajar con interfaces Wi-Fi en macOS.
2. Requisitos
Necesitamos:
- macOS
- Wi-Fi integrado o adaptador compatible
- Terminal
- Swift, incluido en las herramientas de desarrollo de Apple
Podemos comprobar que Swift está disponible:
swift --version
Por ejemplo:
swift-driver version ...
Apple Swift version ...
No necesitamos Python para esta versión.
3. Crear la estructura de la aplicación
Una aplicación macOS tiene una estructura determinada.
Vamos a crear:
WiFiRadar.app
└── Contents
├── MacOS
│ └── WiFiRadar
└── Info.plist
Ejecutamos:
rm -rf ~/WiFiRadar.app
mkdir -p ~/WiFiRadar.app/Contents/MacOS
Podemos comprobarlo:
find ~/WiFiRadar.app -type d
4. Crear el código Swift
Creamos el archivo:
nano ~/WiFiRadar.swift
Introducimos:
import Cocoa
import CoreWLAN
import CoreLocation
class LocationDelegate: NSObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
func locationManagerDidChangeAuthorization(
_ manager: CLLocationManager
) {
print(
"Estado de Localización:",
manager.authorizationStatus.rawValue
)
if manager.authorizationStatus == .authorized {
escanearWiFi()
}
}
func escanearWiFi() {
let wifi = CWWiFiClient.shared()
guard let interface = wifi.interface() else {
print("No se encontró la interfaz Wi-Fi")
esperar()
return
}
do {
let redes = try interface.scanForNetworks(
withName: nil,
includeHidden: true
)
print("")
print("==============================================")
print(" RADAR WI-FI")
print(" Redes encontradas: \(redes.count)")
print("==============================================")
for red in redes.sorted(
by: { $0.rssiValue > $1.rssiValue }
) {
print("")
print(
"SSID : \(red.ssid ?? "<OCULTO>")"
)
print(
"BSSID : \(red.bssid ?? "<NO DISPONIBLE>")"
)
print(
"RSSI : \(red.rssiValue) dBm"
)
print(
"Ruido : \(red.noiseMeasurement) dBm"
)
if let canal = red.wlanChannel {
print(
"Canal : \(canal.channelNumber)"
)
print(
"Banda : \(canal.channelBand.rawValue)"
)
print(
"Ancho : \(canal.channelWidth.rawValue)"
)
}
print(
"País : \(red.countryCode ?? "?")"
)
print(
"Beacon : \(red.beaconInterval) ms"
)
print(
"IBSS : \(red.ibss)"
)
print("----------------------------------------------")
}
} catch {
print("ERROR:", error)
}
esperar()
}
func esperar() {
print("")
print("Pulsa ENTER para salir...")
_ = readLine()
NSApp.terminate(nil)
}
}
let app = NSApplication.shared
app.setActivationPolicy(.regular)
let delegate = LocationDelegate()
app.run()
Guardamos con:
Ctrl + O
pulsamos Enter y salimos:
Ctrl + X
5. ¿Qué hace el código?
La aplicación utiliza:
import CoreWLAN
para acceder a la interfaz Wi-Fi.
Y:
import CoreLocation
para solicitar autorización de Localización.
Esta parte:
locationManager.requestWhenInUseAuthorization()
solicita el permiso necesario.
Después:
interface.scanForNetworks(
withName: nil,
includeHidden: true
)
realiza el escaneo.
Cada resultado es un objeto CWNetwork.
De él obtenemos:
red.ssid
red.bssid
red.rssiValue
red.noiseMeasurement
red.wlanChannel
red.countryCode
red.beaconInterval
6. Crear Info.plist
Este paso es muy importante.
macOS necesita saber por qué la aplicación solicita acceso a Localización.
Creamos:
nano ~/WiFiRadar.app/Contents/Info.plist
Introducimos:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC
"-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>WiFiRadar</string>
<key>CFBundleIdentifier</key>
<string>com.jesusn.wifiradar</string>
<key>CFBundleName</key>
<string>WiFiRadar</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>
WiFiRadar necesita acceso a la localización
para identificar las redes Wi-Fi cercanas.
</string>
</dict>
</plist>
Guardamos:
Ctrl + O
Enter
Ctrl + X
7. Compilar la aplicación
Ahora compilamos el código Swift.
Es importante que la carpeta de destino exista:
mkdir -p ~/WiFiRadar.app/Contents/MacOS
Después:
swiftc ~/WiFiRadar.swift -o ~/WiFiRadar.app/Contents/MacOS/WiFiRadar -framework Cocoa -framework CoreWLAN -framework CoreLocation
Si no aparece ningún mensaje de error, la compilación ha terminado correctamente.
Podemos comprobarlo:
ls -lh ~/WiFiRadar.app/Contents/MacOS/WiFiRadar
Deberíamos obtener algo similar a:
-rwxr-xr-x ... WiFiRadar
8. Ejecutar la aplicación
Podemos ejecutarla directamente desde Terminal:
~/WiFiRadar.app/Contents/MacOS/WiFiRadar
Esta forma es especialmente útil durante el desarrollo porque podremos ver todos los mensajes.
La primera vez macOS puede solicitar autorización para utilizar la Localización.
9. Dar permiso de Localización
En macOS vamos a:
Ajustes del Sistema → Privacidad y seguridad → Localización
Comprobamos que:
Servicios de localización
está activado.
La aplicación WiFiRadar debería aparecer después de solicitar autorización.
También podemos comprobar posteriormente:
Ajustes del Sistema → Privacidad y seguridad → Localización → WiFiRadar
y verificar que tiene permiso.
Esto es importante porque, sin la autorización adecuada, CoreWLAN puede detectar las redes pero no proporcionar determinados identificadores como SSID o BSSID.
10. Ejecutar como aplicación
Una vez compilada podemos abrirla con:
open ~/WiFiRadar.app
Sin embargo, durante las pruebas recomiendo:
~/WiFiRadar.app/Contents/MacOS/WiFiRadar
porque podremos ver inmediatamente los resultados y posibles errores.
11. Ejemplo de resultado
Si todo funciona correctamente tendremos algo parecido a:
==============================================
RADAR WI-FI
Redes encontradas: 14
==============================================
SSID : MiWifi
BSSID : AA:BB:CC:DD:EE:FF
RSSI : -42 dBm
Ruido : -91 dBm
Canal : 6
Banda : 2
Ancho : 20
País : ES
Beacon : 100 ms
IBSS : false
----------------------------------------------
SSID : OtraRed
BSSID : 11:22:33:44:55:66
RSSI : -58 dBm
Ruido : -90 dBm
Canal : 36
Banda : 5
Ancho : 80
País : ES
Beacon : 100 ms
IBSS : false
----------------------------------------------
Las redes se ordenan por RSSI:
redes.sorted(by: { $0.rssiValue > $1.rssiValue })
Por tanto, las señales más fuertes aparecen primero.
12. ¿Qué significa RSSI?
RSSI representa aproximadamente la intensidad de la señal recibida.
Por ejemplo:
| RSSI | Interpretación aproximada |
|---|---|
| -30 dBm | Muy fuerte |
| -40 dBm | Fuerte |
| -50 dBm | Buena |
| -60 dBm | Media |
| -70 dBm | Débil |
| -80 dBm | Muy débil |
| -90 dBm | Muy débil |
No debemos interpretar RSSI como una distancia exacta.
Por ejemplo:
RSSI = -40 dBm
no significa necesariamente que el punto de acceso esté a una distancia concreta.
La potencia depende de paredes, antenas, obstáculos, interferencias, orientación y otros factores.
13. ¿Qué es el BSSID?
El BSSID identifica normalmente la interfaz inalámbrica del punto de acceso.
Por ejemplo:
BSSID: AA:BB:CC:DD:EE:FF
Tiene el formato de una dirección MAC.
Es diferente del SSID:
SSID → nombre de la red
BSSID → identificador MAC del punto de acceso
Por ejemplo:
SSID : MiCasa
BSSID : AA:BB:CC:DD:EE:FF