Connecting to a protected WiFi from Python on Linux
Asked Answered
M

1

13

I'm creating a software for Ubuntu Linux that needs to connect to a WiFi AP. The WiFi network is not predefined and can change several times during a single run of the software (the user is the one who orders the change). The idea is this: given a set of SSIDs and their WPA or WEP passphrases, the software should be able to switch between the networks on the whim, without the need to change any configuration files anywhere in the system.

The huge problem, as it seems, is to pass the passphrase to the connection. Here's what I've been operating with so far:

  • Ubuntu 12.10 machine equipped with a WiFi dongle.
  • Python, which runs the software, and which will be used to request the connections
  • connman 0.79
  • wpa_supplicant v1.0
  • d-bus

At first I thought it would be possible to pass the passphrase to connman through d-bus, but neither this version of connman, nor 1.11, seem to expose any method for that. Then I found out that it's possible to dump a service_<SSID>.conf file to the /var/lib/connman/ directory. Contents of the file are very simple and look like this:

[service_SSID]
Type=wifi
Name=Network-SSID
Passphrase=here-goes-the-passphrase

Once this file is created, connecting to the network requires a simple call to net.connman.Service.Connect() method in appropriate service. The problem is that connman won't parse the config file unless it's restarted. This requires sudo privileges, additional time, and raises risk for all the "what can go wrong now" things to occur. Then I figured that passphrase could be somehow passed to the wpa_supplicant d-bus API, but I failed to find anything.

Google searches have failed me too. It is as if no one ever tried to do this before.

Command sudo iwconfig wlan0 essid <SSID> key s:<PASSPHRASE> results in a SET failed on device wlan0 ; Invalid argument. error. Also, it requires sudo which I would like to avoid.

I tried to figure out how the wpa_gui program does its magic. First of all I discovered that it also requires sudo, and that it sends a bunch of commands directly to /var/run/wpa_supplicant/wlan0. Replicating this behavior would be a last resort for me if I don't figure out anything simplier.

So, the big question is this: How to use Python in order to connect to a WEP/WPA protected WiFi network?
I'm also wondering if using connman is a good approach here and if I shouldn't revert to the Network Manager, which is Ubuntu's default.

Maser answered 21/2, 2013 at 14:52 Comment(0)
M
11

This shows how to do this for WPA.

First of all, ditch connman and use NetworkManager. The example script below shows how to enable wireless support, check if network with given SSID is available, connect to that SSID using a WPA passphrase, and then disconnect from the network and disable wireless. I'm fairly sure that this script can be improved, but the current version serves enough as an example:

#!/usr/bin/python

# This script shows how to connect to a WPA protected WiFi network
# by communicating through D-Bus to NetworkManager 0.9.
#
# Reference URLs:
# http://projects.gnome.org/NetworkManager/developers/
# http://projects.gnome.org/NetworkManager/developers/settings-spec-08.html

import dbus
import time

SEEKED_SSID = "skynet"
SEEKED_PASSPHRASE = "qwertyuiop"

if __name__ == "__main__":
    bus = dbus.SystemBus()
    # Obtain handles to manager objects.
    manager_bus_object = bus.get_object("org.freedesktop.NetworkManager",
                                        "/org/freedesktop/NetworkManager")
    manager = dbus.Interface(manager_bus_object,
                             "org.freedesktop.NetworkManager")
    manager_props = dbus.Interface(manager_bus_object,
                                   "org.freedesktop.DBus.Properties")

    # Enable Wireless. If Wireless is already enabled, this does nothing.
    was_wifi_enabled = manager_props.Get("org.freedesktop.NetworkManager",
                                         "WirelessEnabled")
    if not was_wifi_enabled:
        print "Enabling WiFi and sleeping for 10 seconds ..."
        manager_props.Set("org.freedesktop.NetworkManager", "WirelessEnabled",
                          True)
        # Give the WiFi adapter some time to scan for APs. This is absolutely
        # the wrong way to do it, and the program should listen for
        # AccessPointAdded() signals, but it will do.
        time.sleep(10)

    # Get path to the 'wlan0' device. If you're uncertain whether your WiFi
    # device is wlan0 or something else, you may utilize manager.GetDevices()
    # method to obtain a list of all devices, and then iterate over these
    # devices to check if DeviceType property equals NM_DEVICE_TYPE_WIFI (2).
    device_path = manager.GetDeviceByIpIface("wlan0")
    print "wlan0 path: ", device_path

    # Connect to the device's Wireless interface and obtain list of access
    # points.
    device = dbus.Interface(bus.get_object("org.freedesktop.NetworkManager",
                                           device_path),
                            "org.freedesktop.NetworkManager.Device.Wireless")
    accesspoints_paths_list = device.GetAccessPoints()

    # Identify our access point. We do this by comparing our desired SSID
    # to the SSID reported by the AP.
    our_ap_path = None
    for ap_path in accesspoints_paths_list:
        ap_props = dbus.Interface(
            bus.get_object("org.freedesktop.NetworkManager", ap_path),
            "org.freedesktop.DBus.Properties")
        ap_ssid = ap_props.Get("org.freedesktop.NetworkManager.AccessPoint",
                               "Ssid")
        # Returned SSID is a list of ASCII values. Let's convert it to a proper
        # string.
        str_ap_ssid = "".join(chr(i) for i in ap_ssid)
        print ap_path, ": SSID =", str_ap_ssid
        if str_ap_ssid == SEEKED_SSID:
            our_ap_path = ap_path
            break

    if not our_ap_path:
        print "AP not found :("
        exit(2)
    print "Our AP: ", our_ap_path

    # At this point we have all the data we need. Let's prepare our connection
    # parameters so that we can tell the NetworkManager what is the passphrase.
    connection_params = {
        "802-11-wireless": {
            "security": "802-11-wireless-security",
        },
        "802-11-wireless-security": {
            "key-mgmt": "wpa-psk",
            "psk": SEEKED_PASSPHRASE
        },
    }

    # Establish the connection.
    settings_path, connection_path = manager.AddAndActivateConnection(
        connection_params, device_path, our_ap_path)
    print "settings_path =", settings_path
    print "connection_path =", connection_path

    # Wait until connection is established. This may take a few seconds.
    NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2
    print """Waiting for connection to reach """ \
          """NM_ACTIVE_CONNECTION_STATE_ACTIVATED state ..."""
    connection_props = dbus.Interface(
        bus.get_object("org.freedesktop.NetworkManager", connection_path),
        "org.freedesktop.DBus.Properties")
    state = 0
    while True:
        # Loop forever until desired state is detected.
        #
        # A timeout should be implemented here, otherwise the program will
        # get stuck if connection fails.
        #
        # IF PASSWORD IS BAD, NETWORK MANAGER WILL DISPLAY A QUERY DIALOG!
        # This is something that should be avoided, but I don't know how, yet.
        #
        # Also, if connection is disconnected at this point, the Get()
        # method will raise an org.freedesktop.DBus.Error.UnknownMethod
        # exception. This should also be anticipated.
        state = connection_props.Get(
            "org.freedesktop.NetworkManager.Connection.Active", "State")
        if state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
            break
        time.sleep(0.001)
    print "Connection established!"

    #
    # Connection is established. Do whatever is necessary.
    # ...
    #
    print "Sleeping for 5 seconds ..."
    time.sleep(5)
    print "Disconnecting ..."

    # Clean up: disconnect and delete connection settings. If program crashes
    # before this point is reached then connection settings will be stored
    # forever.
    # Some pre-init cleanup feature should be devised to deal with this problem,
    # but this is an issue for another topic.
    manager.DeactivateConnection(connection_path)
    settings = dbus.Interface(
        bus.get_object("org.freedesktop.NetworkManager", settings_path),
        "org.freedesktop.NetworkManager.Settings.Connection")
    settings.Delete()

    # Disable Wireless (optional step)
    if not was_wifi_enabled:
        manager_props.Set("org.freedesktop.NetworkManager", "WirelessEnabled",
                          False)
    print "DONE!"
    exit(0)
Maser answered 22/2, 2013 at 10:36 Comment(5)
Could you tell me what would be the security dict for WEP?Incretion
Sorry, never needed to investigate how to do this with WEP, so I can't help you here. I don't imagine it would be much different, though.Maser
@Maser I know it is almost a year since last reply, but according to this link you don't even need to enter the type of security(WEP, WPA2, etc), you just need to provide the password(psk). Citation for the first parameter of AddAndActivateConnection: Connection settings and properties; if incomplete missing settings will be automatically completed using the given device and specific object. So I removed security and key-mgmt from connection_params and it connected successfully. :)Labannah
@Labannah Good to know. Glad to hear that this SO entry is helping other people, as the plans changed for me and I didn't use it in any real solution anywhere.Maser
Adding additional information for future readers. A full list of the connection settings and properties can be found here. You will primarily want to look into the 802-11-wireless-security settings.Corallite

© 2022 - 2024 — McMap. All rights reserved.