Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CoreWLAN returning None for SSID/BSSID #600

Open
phenotypic opened this issue Apr 4, 2024 · 5 comments
Open

CoreWLAN returning None for SSID/BSSID #600

phenotypic opened this issue Apr 4, 2024 · 5 comments
Labels
bug Something isn't working

Comments

@phenotypic
Copy link

Hi there! In my project, WiFiCrackPy, several people have opened issues stating that CoreWLAN is returning None for the SSIDs and/or BSSIDs of networks (see here).

I am aware that an issue was opened in 2022 (#484) where a similar problem was occurring, and I see that this was resolved by granting Python location services permissions.

What is strange is that I do not experience this issue, but people with the same macOS versions and Python versions as me do. They have confirmed they have granted Python location services permissions. I am using Python 3.12.2 installed via Homebrew, macOS 14.4.1, and PyObjC 10.2.

Someone has noticed that the issue does not occur when running the script with the system-native version of Python, but it is not clear why there is an inconsistency with the Homebrew-installed version of Python.

As a note, there is a post in the Apple Developer forums where people have highlighted the same issue and underscored the inconsistency across the same macOS/Python/PyObjC versions.

Here is an excerpt of my WiFiCrackPy script which contains just the location authorisation and network scanning sections. As mentioned, this works fine on my Mac but you can see examples from other people in the issue I linked above where the SSIDs and BSSIDs are shown as None:

import re, time, CoreWLAN, CoreLocation
from prettytable import PrettyTable

# Initialise CoreLocation
print('\nObtaining authorisation for location services (required for WiFi scanning)...\n')
location_manager = CoreLocation.CLLocationManager.alloc().init()
location_manager.startUpdatingLocation()

# Wait for location services to be authorised
max_wait = 60
for i in range(1, max_wait):
    authorization_status = location_manager.authorizationStatus()
    if authorization_status == 3 or authorization_status == 4:
        print('Received authorisation, continuing...\n')
        break
    if i == max_wait-1:
        exit('Unable to obtain authorisation, exiting...\n')
    time.sleep(1)

# Get the default WiFi interface
cwlan_interface = CoreWLAN.CWInterface.interface()

# Scan for networks
print('Scanning for networks...\n')
scan_result, _ = cwlan_interface.scanForNetworksWithName_error_(None, None)

# Parse scan results and display in a table
table = PrettyTable(['Number', 'Name', 'BSSID', 'RSSI', 'Channel', 'Security'])

# Iterate through each network in the scan results
for i, net in enumerate(scan_result):
    # Extract security type from the CWNetwork description
    security = re.search(r'security=(.*?)(,|$)', str(net)).group(1)

    # Add the network to the table
    table.add_row([i + 1, net.ssid(), net.bssid(), net.rssiValue(), net.channel(), security])

# Display the table
print(table)

It has been difficult for me to troubleshoot the issue as I have not experienced the problem.

I recently tried to manually load the frameworks using objc rather than importing CoreWLAN and CoreLocation directly (see here), but that does not seem to have fixed the issue.

Thank you in advance for any help or guidance you can give!

@ronaldoussoren
Copy link
Owner

I can see this behaviour on my main machine:

Obtaining authorisation for location services (required for WiFi scanning)...

Scanning for networks...

+--------+------+-------+------+---------+--------------------+
| Number | Name | BSSID | RSSI | Channel |      Security      |
+--------+------+-------+------+---------+--------------------+
|   1    | None |  None | -85  |    36   |   WPA2 Personal    |
|   2    | None |  None | -68  |    1    |   WPA2 Personal    |
|   3    | None |  None | -85  |    6    | WPA2/WPA3 Personal |
|   4    | None |  None | -89  |    40   |   WPA2 Personal    |
|   5    | None |  None | -90  |    44   |   WPA2 Personal    |
|   6    | None |  None | -90  |    40   |   WPA2 Personal    |
|   7    | None |  None | -78  |    8    |        Open        |
|   8    | None |  None | -84  |    36   |   WPA2 Personal    |
|   9    | None |  None | -70  |    4    | WPA/WPA2 Personal  |
|   10   | None |  None | -63  |    40   | WPA2/WPA3 Personal |
|   11   | None |  None | -85  |    36   |   WPA2 Personal    |
|   12   | None |  None | -55  |   108   | WPA2/WPA3 Personal |
|   13   | None |  None | -91  |    36   |   WPA2 Personal    |
|   14   | None |  None | -64  |    7    | WPA2/WPA3 Personal |
|   15   | None |  None | -80  |    44   |   WPA2 Personal    |
+--------+------+-------+------+---------+--------------------+

Python 3.12.3, PyObjC 10.2, macOS 14.4.1 on an M1 laptop.

One thing I noticed: On the first try it took a long time to obtain authorisation, no problems from the second run onwards. This is in a virtualenv.

@ronaldoussoren
Copy link
Owner

ronaldoussoren commented Apr 13, 2024

I get the same behaviour with an ObjC program, but... note how I disabled the location authorisation check which was needed because the loop always failed:

#import <Foundation/Foundation.h>
#import <CoreWLAN/CoreWLAN.h>
#import <CoreLocation/CoreLocation.h>

#include <time.h>

int main(void)
{
    // Initialise CoreLocation
    NSLog(@"Obtaining authorisation for location services (required for WiFi scanning)...");
    CLLocationManager* location_manager = [[CLLocationManager alloc] init];
    [location_manager startUpdatingLocation];

#if 0
    // Wait for location services to be authorised
    const int max_wait = 10;
    for(int i = 0; i < max_wait; i++) {
        CLAuthorizationStatus authorization_status = location_manager.authorizationStatus;
        NSLog(@"status %ld", (long)authorization_status);
        switch (authorization_status) {
        case kCLAuthorizationStatusAuthorized:
        //case kCLAuthorizationStatusAuthorizedAlways:
        //case kCLAuthorizationStatusAuthorizedWhenInUse:
            NSLog(@"Received authorization, continuing...");
            break;
        case kCLAuthorizationStatusRestricted:
        case kCLAuthorizationStatusDenied:
            NSLog(@"No authorization, aborting...");
            return 1;
        default:
            if (i == max_wait - 1) {
                NSLog(@"Unable to obtain authorisation, exiting...");
                return 1;
            }
        }

        sleep(1);
    }
#endif


    // Get the default WiFi interface
    CWInterface* cwlan_interface = [CWInterface interface];

    // Scan for networks
    NSLog(@"Scanning for networks...");
    NSError* error;

    NSSet<CWNetwork*>* scan_result = [cwlan_interface scanForNetworksWithName:nil error:&error];
    if (scan_result == nil) {
        NSLog(@"scan failed %@", error);
        return 1;
    }

    for (CWNetwork* net in scan_result) {
        NSLog(@"net = %@", net);
    }
        /* 
    # Iterate through each network in the scan results
    for i, net in enumerate(scan_result):
        # Extract security type from the CWNetwork description
        security = re.search(r'security=(.*?)(,|$)', str(net)).group(1)

        # Add the network to the table
        table.add_row([i + 1, net.ssid(), net.bssid(), net.rssiValue(), net.channel(), security])

    # Display the table
    print(table)
    */
    return 0;
    }

Noteworthy: the +[CWInterface interface] API is deprecated as of macOS 10.10:

repro.m:43:49: warning: 'interface' is deprecated: first deprecated in macOS 10.10 - Use -[CWWiFiClient interface] instead [-Wdeprecated-declarations]
    CWInterface* cwlan_interface = [CWInterface interface];

That's a red herring though, the modern way to do the same way gives the same result:

    CWInterface* cwlan_interface = [[CWWiFiClient sharedWiFiClient] interface];

UPDATE:
The failure happens both with a command-line tool and when running the code in an app bundle with the required Info.plist keys.

@ronaldoussoren
Copy link
Owner

I cannot reproduce this after all, just noticed that I disabled the check in the python version as well and had disabled location services for python. After reenabling the code and location services I got the expected result with names and BSSIDs (not sharing the output for obvious reasons).

import re, time, CoreWLAN, CoreLocation, Foundation
from prettytable import PrettyTable

# Initialise CoreLocation
print('\nObtaining authorisation for location services (required for WiFi scanning)...\n')
location_manager = CoreLocation.CLLocationManager.alloc().init()
#location_manager.requestWhenInUseAuthorization()

location_manager.startUpdatingLocation()

loop = Foundation.NSRunLoop.currentRunLoop()


# Wait for location services to be authorised
max_wait = 10
for i in range(1, max_wait):
    authorization_status = location_manager.authorizationStatus()
    print(f"{authorization_status=}")
    if authorization_status == 3 or authorization_status == 4:
        print('Received authorisation, continuing...\n')
        break
    #loop.runUntilDate_(Foundation.NSDate.dateWithTimeIntervalSinceNow_(1))
    time.sleep(1)
else:
    exit('Unable to obtain authorisation, exiting...\n')
# Get the default WiFi interface
cwlan_interface = CoreWLAN.CWInterface.interface()

# Scan for networks
print('Scanning for networks...\n')
scan_result, _ = cwlan_interface.scanForNetworksWithName_error_(None, None)

# Parse scan results and display in a table
table = PrettyTable(['Number', 'Name', 'BSSID', 'RSSI', 'Channel', 'Security'])

# Iterate through each network in the scan results
for i, net in enumerate(scan_result):
    # Extract security type from the CWNetwork description
    security = re.search(r'security=(.*?)(,|$)', str(net)).group(1)

    # Add the network to the table
    table.add_row([i + 1, net.ssid(), net.bssid(), net.rssiValue(), net.channel(), security])

# Display the table
print(table)

@phenotypic
Copy link
Author

Thank you for the response!

Just to confirm, you were able to get the code to function as expected simply by disabling and re-enabling Python's location services permission in System Settings?

Either way, it's strange that your code ran in the first instance with the correct permissions, but returned 'None' for the SSID and BSSID. Indeed, CoreLocation will have had to return 1 or 2 for the authorization_status for the code to have proceeded with network scanning.

Why did CoreWLAN fail to return the correct SSID/BSSID values despite having correct permissions? And why did disabling/re-enabling Python's location services remedy this issue for you? Is there some additional requirement past location services authorisation for CoreWLAN to return the expected SSID/BSSID values?

@waknin
Copy link

waknin commented May 13, 2024

Same error! Location permitted!

image

MBP M1 Pro
Sonoma 14.4.1
python installed via homebrew 3.12.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants