Connecting to a protected WiFi from Python on Linux

ZalewaPL picture ZalewaPL · Feb 21, 2013 · Viewed 10.3k times · Source

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.

Answer

ZalewaPL picture ZalewaPL · Feb 22, 2013

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)