├── MANIFEST.in
├── etc
├── nose.cfg
└── coverage.cfg
├── docs
├── source
│ ├── usage_cli.rst
│ ├── usage_library.rst
│ ├── api.rst
│ ├── tips_and_tricks.rst
│ ├── index.rst
│ └── conf.py
├── Makefile
└── make.bat
├── dev-requirements.txt
├── run_tests.sh
├── wpa_supplicant
├── test
│ ├── test_cli.py
│ ├── __init__.py
│ ├── mocks.py
│ └── test_core.py
├── __init__.py
├── examples
│ ├── example_scan.py
│ └── example_txdbus.py
├── cli.py
└── core.py
├── tox.ini
├── .travis.yml
├── CHANGELOG.md
├── .codeclimate.yml
├── .gitignore
├── HACKING.md
├── feature_map.csv
├── toxtest.sh
├── setup.py
├── README.md
└── LICENSE
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include README.rst
3 |
--------------------------------------------------------------------------------
/etc/nose.cfg:
--------------------------------------------------------------------------------
1 | [nosetests]
2 | tests = wpa_supplicant/test/
--------------------------------------------------------------------------------
/docs/source/usage_cli.rst:
--------------------------------------------------------------------------------
1 | Using the wpa_supplicant CLI
2 | ============================
3 |
4 | TODO
5 |
--------------------------------------------------------------------------------
/dev-requirements.txt:
--------------------------------------------------------------------------------
1 | --editable .
2 | nose
3 | mock
4 | coverage
5 | nose-cov
6 | sphinx
7 | pyandoc
8 | tox
9 |
--------------------------------------------------------------------------------
/docs/source/usage_library.rst:
--------------------------------------------------------------------------------
1 | Using the wpa_supplicant Library
2 | ================================
3 |
4 | TODO
5 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | nosetests --config=etc/nose.cfg --with-cov --cov=wpa_supplicant --cov-config=etc/coverage.cfg --cov-report=html
4 |
--------------------------------------------------------------------------------
/wpa_supplicant/test/test_cli.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 |
4 | class TestCLI(unittest.TestCase):
5 | def test_cli_import(self):
6 | from wpa_supplicant import cli
7 |
--------------------------------------------------------------------------------
/wpa_supplicant/__init__.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
--------------------------------------------------------------------------------
/wpa_supplicant/test/__init__.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py27,py33,py34,pypy
3 |
4 | [testenv]
5 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
6 | deps=
7 | -rdev-requirements.txt
8 | commands=nosetests
9 |
10 | [testenv:coverage]
11 | deps=
12 | {[testenv]deps}
13 | python-coveralls
14 | commands =
15 | coverage run --branch --omit={envdir}/* {envbindir}/nosetests
16 | coveralls
17 |
--------------------------------------------------------------------------------
/etc/coverage.cfg:
--------------------------------------------------------------------------------
1 | [report]
2 | omit = *cli*
3 | exclude_lines =
4 | # Have to re-enable the standard pragma
5 | pragma: no cover
6 |
7 | # Don't complain about missing debug-only code:
8 | def __repr__
9 | def __str__
10 | if self\.debug
11 |
12 | # Don't complain if tests don't hit defensive assertion code:
13 | raise AssertionError
14 | raise NotImplementedError
15 |
16 | # Don't complain if non-runnable code isn't run:
17 | if 0:
18 | if __name__ == .__main__.:
19 |
20 | [html]
21 | directory = cover
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | # this version of python is only used to run tox - the version specified by TOX_ENV
4 | # is used to install and run tests
5 | python: 2.7
6 | env:
7 | - TOX_ENV=py27
8 | - TOX_ENV=py33
9 | - TOX_ENV=py34
10 | - TOX_ENV=pypy
11 | - TOX_ENV=coverage
12 |
13 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
14 | install:
15 | - pip install tox
16 | - pip install -r dev-requirements.txt
17 |
18 | # command to run tests, e.g. python setup.py test
19 | script:
20 | - tox -e $TOX_ENV
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Python wpa_supplicant Library Changelog
2 |
3 | ### 0.2 / 2015-06-03
4 | [Full Changelog](https://github.com/digidotcom/python-wpa-supplicant/0.1...0.2)
5 |
6 | Enhancements:
7 |
8 | * Python 3.3+ Support added in addition to Python 2.7 support.
9 | * Minor documentation updates
10 | * Better continuous integration and testing support in the library.
11 |
12 | ### 0.1 / 2015-06-03
13 |
14 | Initial Release of the library with a large chunk of initial
15 | functionality based on real project work. Documentation present but
16 | somewhat limited and only supports Python 2.7.
17 |
--------------------------------------------------------------------------------
/docs/source/api.rst:
--------------------------------------------------------------------------------
1 | API Documentation
2 | =================
3 |
4 | WpaSupplicantDriver
5 | -------------------
6 |
7 | .. autoclass:: wpa_supplicant.core.WpaSupplicantDriver
8 | :members:
9 |
10 | WpaSupplicant
11 | -------------
12 |
13 | .. autoclass:: wpa_supplicant.core.WpaSupplicant
14 | :members:
15 |
16 | Interface
17 | ---------
18 |
19 | .. autoclass:: wpa_supplicant.core.Interface
20 | :members:
21 |
22 | Network
23 | -------
24 |
25 | .. autoclass:: wpa_supplicant.core.Network
26 | :members:
27 |
28 | BSS
29 | ---
30 |
31 | .. autoclass:: wpa_supplicant.core.BSS
32 | :members:
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | #
2 | # ---Choose Your Languages---
3 | # To disable analysis for a certain language, set the language to `false`.
4 | # For help setting your languages:
5 | # http://docs.codeclimate.com/article/169-configuring-analysis-languages
6 | #
7 | languages:
8 | Ruby: false
9 | Javascript: false
10 | PHP: false
11 | Python: true
12 |
13 | #
14 | # ---Exclude Files or Directories---
15 | # List the files or directories you would like excluded from analysis.
16 | # For help setting your exclude paths:
17 | # http://docs.codeclimate.com/article/166-excluding-files-folders
18 | #
19 | exclude_paths:
20 | - wpa_supplicant/test/**
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | env2/
13 | env3/
14 | .toxenv/
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | README.rst
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 | cover/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 |
59 | # Sphinx documentation
60 | docs/_build/
61 |
62 | # PyBuilder
63 | target/
64 |
65 | # PyCharm
66 | .idea/
--------------------------------------------------------------------------------
/HACKING.md:
--------------------------------------------------------------------------------
1 | Developer's Guide
2 | =================
3 |
4 | Welcome to the developer's guide for the Python wpa_supplicant library.
5 |
6 | Setup Development Environment
7 | -----------------------------
8 |
9 | Use `virtualenv` to create a Python environment.
10 |
11 | ```sh
12 | $ virtualenv -p /usr/bin/python2 env2
13 | ```
14 |
15 | Activate one of the environments.
16 |
17 | ```sh
18 | $ source env2/bin/activate
19 | ```
20 |
21 | Then, install the development dependencies.
22 |
23 | ```sh
24 | (env2) $ pip install -r dev-requirements.txt
25 | ```
26 |
27 |
28 | Running tests
29 | -------------
30 |
31 | This project uses `nose` for finding and executing tests and `coverage` for generating
32 | HTML based coverage reports.
33 |
34 | Running the tests can be as easy as:
35 |
36 | ```sh
37 | $ nosetests
38 | ```
39 |
40 | However, running the tests is highly configurable and for this project it is easiest
41 | to run a script which makes use of config files under `etc/`.
42 |
43 | ```sh
44 | $ ./run_tests.sh
45 | ```
46 |
47 | Check out the coverage reports:
48 |
49 | ```sh
50 | $ firefox cover/index.html
51 | ```
52 |
--------------------------------------------------------------------------------
/wpa_supplicant/examples/example_scan.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
7 | from wpa_supplicant.core import WpaSupplicantDriver
8 | from twisted.internet.selectreactor import SelectReactor
9 | import threading
10 | import time
11 |
12 | # Start a simple Twisted SelectReactor
13 | reactor = SelectReactor()
14 | threading.Thread(target=reactor.run, kwargs={'installSignalHandlers': 0}).start()
15 | time.sleep(0.1) # let reactor start
16 |
17 | # Start Driver
18 | driver = WpaSupplicantDriver(reactor)
19 |
20 | # Connect to the supplicant, which returns the "root" D-Bus object for wpa_supplicant
21 | supplicant = driver.connect()
22 |
23 | # Register an interface w/ the supplicant, this can raise an error if the supplicant
24 | # already knows about this interface
25 | interface = supplicant.create_interface('wlan0')
26 |
27 | # Issue the scan
28 | scan_results = interface.scan(block=True)
29 | for bss in scan_results:
30 | print bss.get_ssid()
31 |
--------------------------------------------------------------------------------
/docs/source/tips_and_tricks.rst:
--------------------------------------------------------------------------------
1 | Development Tips and Tricks
2 | ===========================
3 |
4 | D-Bus Configuration
5 | -------------------
6 |
7 | Give your user permissions to use the D-Bus system bus:
8 |
9 | Add the following lines to the following files:
10 |
11 | **/etc/dbus-1/system.d/org.freedesktop.ModemManager1.conf**
12 |
13 | .. code-block:: shell
14 |
15 |
16 |
17 |
18 |
19 |
20 | **/etc/dbus-1/system.d/wpa_supplicant.conf**
21 |
22 | .. code-block:: shell
23 |
24 |
25 |
26 |
27 |
28 |
29 | Then, reset dbus:
30 |
31 | .. code-block:: shell
32 |
33 | $ /etc/init.d/dbus stop
34 | $ /etc/init.d/dbus start
35 |
36 |
37 | Disabling NetworkManager on Ubuntu
38 | ----------------------------------
39 |
40 | It is often desired to disable NetworkMananger on your dev box because the WDNU-II
41 | application is essentially the same thing and they likely do not play nicely together.
42 | To do so while keeping your DHCP ethernet connection intact (interwebs), do the following:
43 |
44 | .. code-block:: shell
45 |
46 | $ sudo service network-manager stop
47 | $ sudo ifconfig eth0 up (make sure your interface is actually eth0!!)
48 | $ sudo dhclient eth0
49 |
--------------------------------------------------------------------------------
/feature_map.csv:
--------------------------------------------------------------------------------
1 | Object,Method,Supported
2 | root,CreateInterface,X
3 | ,RemoveInterface,X
4 | ,GetInterface,X
5 | ,Get (properties),X
6 | ,Set (properties),X
7 | ,Register (signal),X
8 | Interface,Scan,X
9 | ,Disconnect,X
10 | ,AddNetwork,X
11 | ,RemoveNetwork,X
12 | ,RemoveAllNetworks,X
13 | ,SelectNetwork,X
14 | ,Reassociate,
15 | ,Reattach,
16 | ,AddBlob,
17 | ,RemoveBlob,
18 | ,GetBlob,
19 | ,AutoScan,
20 | ,TDLSDiscover,
21 | ,TDLSSetup,
22 | ,TDLSStatus,
23 | ,TDLSTeardown,
24 | ,EAPLogoff,
25 | ,EAPLogon,
26 | ,NetworkReply,
27 | ,SetPKCS11EngineAndModulePath,
28 | ,SignalPoll,
29 | ,FlushBSS,
30 | ,SubscribeProbReq,
31 | ,UnsubscribeProbReq,
32 | ,Get (properties),X
33 | ,Set (properties),X
34 | ,Register (signal),X
35 | Interface.WPS,Start,
36 | ,Get (properties),
37 | ,Set (properties),
38 | ,Register (signal),
39 | Interface.P2PDevice,Find,
40 | ,StopFind,
41 | ,Listen,
42 | ,ExtendedListen,
43 | ,PresenceRequest,
44 | ,ProvisionDiscoveryRequest,
45 | ,Connect,
46 | ,GroupAdd,
47 | ,Invite,
48 | ,Disconnect,
49 | ,RejectPeer,
50 | ,Flush,
51 | ,AddService,
52 | ,DeleteService,
53 | ,FlushService,
54 | ,ServiceDiscoveryRequest,
55 | ,ServiceDiscoveryResponse,
56 | ,ServiceDiscoveryCancelRequest,
57 | ,ServiceUpdate,
58 | ,Register (signal),
59 | BSS,Get (properties),X
60 | ,Set (properties),X
61 | ,Register (signal),X
62 | Network,Get (properties),X
63 | ,Set (properties),X
64 | ,Register (signal),X
65 | Peer,Get (properties),
66 | ,Set (properties),
67 | ,Register (signal),
68 | Group,Get (properties),
69 | ,Set (properties),
70 | ,Register (signal),
71 | PersistentGroup,Get (properties),
72 | ,Set (properties),
73 | ,Register (signal),
74 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. python-wpa-supplicant documentation master file, created by
2 | sphinx-quickstart on Wed Jun 3 14:30:47 2015.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to python-wpa-supplicant's documentation!
7 | =================================================
8 |
9 | This library provides an interface to the wpa_supplicant D-Bus interface. As of now,
10 | there is really no good option for interfacing with wpa_supplicant from Python and
11 | to go a step further, the existing D-Bus libraries are difficult to work with. This
12 | library abstracts all of that away into a very clean API based on the
13 | `wpa_supplicant D-Bus documentation `_
14 |
15 | In addition to the Python library, this package also includes a wpa_supplicant CLI tool
16 | which provides access to the entire library via command line!
17 |
18 |
19 | Documentation Contents
20 | ----------------------
21 |
22 | The main documentation seeks to provide a broad overview showing how to use
23 | the library and CLI at a high level but without going into all the minute
24 | details.
25 |
26 | .. toctree::
27 | :maxdepth: 2
28 |
29 | usage_cli
30 | usage_library
31 | tips_and_tricks
32 |
33 | API Reference
34 | -------------
35 |
36 | The API reference includes automatically generated documentation
37 | that covers the details of individual classes, methods, and
38 | members.
39 |
40 | .. toctree::
41 | :maxdepth: 2
42 |
43 | api
44 |
45 |
46 | Indices and tables
47 | ==================
48 |
49 | * :ref:`genindex`
50 | * :ref:`modindex`
51 | * :ref:`search`
52 |
53 |
--------------------------------------------------------------------------------
/toxtest.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Script that will try to test this codebase against as many
4 | # python versions as is possible. It does this using a combination
5 | # of pyenv (for building various interpreters) and tox for
6 | # testing using each of those interpreters.
7 | #
8 |
9 | pyversions=(2.7.7
10 | 3.3.5
11 | 3.4.3
12 | pypy-2.3.1)
13 |
14 | # parse options
15 | for i in "$@"
16 | do
17 | case $i in
18 | --fast)
19 | FAST=YES
20 | ;;
21 | esac
22 | done
23 |
24 | if [ ! FAST="YES" ]; then
25 | # first make sure that pyenv is installed
26 | if [ ! -s "$HOME/.pyenv/bin/pyenv" ]; then
27 | curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash
28 | fi
29 | fi
30 |
31 | # Update pyenv (required for new python versions to be available)
32 | (cd $HOME/.pyenv && git pull)
33 |
34 | # add pyenv to our path and initialize (if this has not already been done)
35 | export PATH="$HOME/.pyenv/bin:$PATH"
36 | eval "$(pyenv init -)"
37 |
38 | # install each python version that we want to test with
39 | for pyversion in ${pyversions[*]};
40 | do
41 | pyenv install -s ${pyversion}
42 | done
43 | pyenv rehash
44 |
45 | # This is required
46 | pyenv global ${pyversions[*]}
47 |
48 | # Now, run the tests after sourcing venv for tox install/use
49 | if [ "$FAST" != "YES" ]; then
50 | virtualenv -q .toxenv
51 | fi
52 | source .toxenv/bin/activate
53 | if [ "$FAST" != "YES" ]; then
54 | pip install -q -r dev-requirements.txt
55 | fi
56 |
57 | if [ "$FAST" == "YES" ]; then
58 | tox
59 | else
60 | # will ensure all depencies are pulled in
61 | tox --recreate
62 | fi
63 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
7 | from setuptools import setup, find_packages
8 | import os
9 |
10 | install_requires = [
11 | 'txdbus>=1.0.1',
12 | 'click',
13 | 'six',
14 | ]
15 |
16 |
17 | def get_long_description():
18 | long_description = open('README.md').read()
19 | try:
20 | import subprocess
21 | import pandoc
22 |
23 | process = subprocess.Popen(
24 | ['which pandoc'],
25 | shell=True,
26 | stdout=subprocess.PIPE,
27 | universal_newlines=True)
28 |
29 | pandoc_path = process.communicate()[0]
30 | pandoc_path = pandoc_path.strip('\n')
31 |
32 | pandoc.core.PANDOC_PATH = pandoc_path
33 |
34 | doc = pandoc.Document()
35 | doc.markdown = long_description
36 | long_description = doc.rst
37 | open("README.rst", "w").write(doc.rst)
38 | except:
39 | if os.path.exists("README.rst"):
40 | long_description = open("README.rst").read()
41 | else:
42 | print("Could not find pandoc or convert properly")
43 | print(" make sure you have pandoc (system) and pyandoc (python module) installed")
44 |
45 | return long_description
46 |
47 |
48 | setup(
49 | name='wpa_supplicant',
50 | version='0.2',
51 | description='WPA Supplicant wrapper for Python',
52 | long_description=get_long_description(),
53 | author="Stephen Stack",
54 | author_email="Stephen.Stack@digi.com",
55 | install_requires=install_requires,
56 | packages=find_packages(),
57 | entry_points={
58 | 'console_scripts': ['wpa=wpa_supplicant.cli:run']
59 | },
60 | classifiers=[
61 | "Development Status :: 3 - Alpha",
62 | "Intended Audience :: Developers",
63 | "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
64 | "Programming Language :: Python :: 2.7",
65 | "Topic :: Software Development :: Libraries",
66 | "Operating System :: POSIX :: Linux",
67 | ],
68 | )
69 |
--------------------------------------------------------------------------------
/wpa_supplicant/examples/example_txdbus.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
7 | """Example of using the raw txdbus APIs
8 |
9 | Note: There are some hardcoded values in this example and should not be expected
10 | to run on every PC but serves as a reference.
11 | """
12 |
13 | from twisted.internet import reactor, defer
14 | from txdbus import client, error
15 | from txdbus.interface import DBusInterface, Method, Signal
16 | import time
17 |
18 | dbus_name = 'fi.w1.wpa_supplicant1'
19 | root_iface_str = 'fi.w1.wpa_supplicant1'
20 | root_iface_obj_path = "/" + root_iface_str.replace('.', '/')
21 |
22 | interface_iface_str = root_iface_str + ".Interface"
23 | interface_iface = DBusInterface(interface_iface_str,
24 | Signal('ScanDone', 'b'),
25 | Method('Scan', arguments='a{sv}'),
26 | Method('AddNetwork', arguments='a{sv}', returns='o'),
27 | Method('RemoveNetwork', arguments='o'),
28 | Method('SelectNetwork', arguments='o'))
29 |
30 |
31 | @defer.inlineCallbacks
32 | def set_mode_example():
33 | cli = yield client.connect(reactor, busAddress='system')
34 |
35 | root_obj = yield cli.getRemoteObject(dbus_name, root_iface_obj_path)
36 | print "Root Object: %s" % root_obj
37 |
38 | wlan0_obj_path = yield root_obj.callRemote('GetInterface', 'wlan1')
39 | print "WLAN0 Object Path: %s" % wlan0_obj_path
40 |
41 | wlan0_obj = yield cli.getRemoteObject(dbus_name, wlan0_obj_path, interface_iface)
42 | print "WLAN0 Object: %s" % wlan0_obj
43 |
44 | # wpa_supplicant will validate keys, but does not validate values.
45 | # There are a bunch of additional configuration options for 801.11ac.
46 | network_settings = {
47 | "disabled": 0, # Not disabled
48 | # "id_str": "ExtScriptID",
49 | "ssid": "funny-ssid", # SSID
50 | # "frequency": 2412, # When IBSS only, ignored in mode=0
51 | # "proto": "WPA",
52 | # "bssid": "Somebss",
53 | "mode": 0, # 0 = managed(default), 1 = IBSS (Ad-hoc, p2p), 2: AP (access point)
54 | # "freq_list": None, #
55 | # "bgscan": "simple:30:-45:300",
56 | # "proto": "WPA",
57 | "key_mgmt": "WPA-PSK",
58 | # "ieee80211w": 0,
59 | "auth_alg": "OPEN",
60 | "pairwise": "CCMP",
61 | # "group": None,
62 | "psk": "funnypsk",
63 | # "eapol_flags": 3,
64 | # "macsec_policy": 0,
65 | # "mixed_cell": 0,
66 | # "proactive_key_caching": 0,
67 | # "wep_key0": None,
68 | # "wep_key1": None,
69 | # "wep_key2": None,
70 | # "wep_key3": None,
71 | # "wep_tx_keyidx": None,
72 | # "peerkey": 0,
73 | # "wpa_ptk_rekey": 100000,
74 | # "eap": "MD5 MSCHAPV2 OTP GTC TLS PEAP TTLS",
75 | # "identity": "Some one", # EAP identity
76 | # "anonymous_identity": "Anonymous one", # EAP anonymous
77 | # "password": "Some one's password",
78 | # "ca_cert": "file path to CA certificates",
79 | # "ca_path": "directory path for CA certificate files (PEM)",
80 | # "client_cert": "file path to client certificate file",
81 | # "private_key": "file path to private key",
82 | # "private_key_passwd": "password for private key",
83 | # "dh_file": "file path to DH/DSA params",
84 | # "subject_match": "x",
85 | # "altsubject_match": "alt",
86 | # "phase1": "peapver=1", # See wpa_supplicant.conf for phase1 TLS details
87 | # "phase2": "auth=MSCHAPV2", # See wpa_supplicant.conf for phase2 TLS details
88 | # "ca_cert2": "file path to phase2 CA certificates",
89 | # "ca_path2": "directory path for phase2 CA certificate files (PEM)",
90 | # "client_cert2": "file path to phase2 client certificate",
91 | # "private_key2": "file path to phase2 client private key",
92 | # "private_key2_passwd": "password for phase2 private key",
93 | # "dh_file2": "file path to phase2 DH/DSA params",
94 | # "subject_match2": "x",
95 | # "altsubject_match2": "alt2",
96 | # "fragment_size": "1398",
97 | # "ocsp": 0,
98 | # "ap_max_inactivity": 300, # Seconds
99 | # "disable_ht": "0", # High Throughput 802.11n enabled
100 | # "disable_ht40": 0, # High Throughput 802.11n enabled (40 MHz channel)
101 | # "disable_sgi": 0, # short guard interval enabled
102 | # "disable_ldpc": 0, # low density parity check enabled
103 | # "ht40_intolerant": 0, # HT-40 tolerated
104 | # "ht_mcs": "", # Use all available
105 | # "disable_max_amsdu": -1,
106 | # "ampdu_factor": 3,
107 | # "ampdu_density": -1,
108 | # "disable_vht": 0, # Very High Throughput 802.11ac enabled
109 | }
110 |
111 | network_obj = yield wlan0_obj.callRemote('AddNetwork', network_settings)
112 | print "Network Object Path: %s" % network_obj
113 | yield wlan0_obj.callRemote('SelectNetwork', network_obj)
114 | time.sleep(20)
115 | yield wlan0_obj.callRemote('RemoveNetwork', network_obj)
116 | reactor.stop()
117 |
118 |
119 | reactor.callWhenRunning(set_mode_example)
120 | reactor.run()
121 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " applehelp to make an Apple Help Book"
34 | @echo " devhelp to make HTML files and a Devhelp project"
35 | @echo " epub to make an epub"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 |
51 | clean:
52 | rm -rf $(BUILDDIR)/*
53 |
54 | html:
55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
56 | @echo
57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
58 |
59 | dirhtml:
60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
61 | @echo
62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
63 |
64 | singlehtml:
65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
66 | @echo
67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
68 |
69 | pickle:
70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
71 | @echo
72 | @echo "Build finished; now you can process the pickle files."
73 |
74 | json:
75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
76 | @echo
77 | @echo "Build finished; now you can process the JSON files."
78 |
79 | htmlhelp:
80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
81 | @echo
82 | @echo "Build finished; now you can run HTML Help Workshop with the" \
83 | ".hhp project file in $(BUILDDIR)/htmlhelp."
84 |
85 | qthelp:
86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
87 | @echo
88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-wpa-supplicant.qhcp"
91 | @echo "To view the help file:"
92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-wpa-supplicant.qhc"
93 |
94 | applehelp:
95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
96 | @echo
97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
98 | @echo "N.B. You won't be able to view it unless you put it in" \
99 | "~/Library/Documentation/Help or install it in your application" \
100 | "bundle."
101 |
102 | devhelp:
103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
104 | @echo
105 | @echo "Build finished."
106 | @echo "To view the help file:"
107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-wpa-supplicant"
108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-wpa-supplicant"
109 | @echo "# devhelp"
110 |
111 | epub:
112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
113 | @echo
114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
115 |
116 | latex:
117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
118 | @echo
119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
121 | "(use \`make latexpdf' here to do that automatically)."
122 |
123 | latexpdf:
124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
125 | @echo "Running LaTeX files through pdflatex..."
126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
128 |
129 | latexpdfja:
130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
131 | @echo "Running LaTeX files through platex and dvipdfmx..."
132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
134 |
135 | text:
136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
137 | @echo
138 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
139 |
140 | man:
141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
142 | @echo
143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
144 |
145 | texinfo:
146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
147 | @echo
148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
149 | @echo "Run \`make' in that directory to run these through makeinfo" \
150 | "(use \`make info' here to do that automatically)."
151 |
152 | info:
153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
154 | @echo "Running Texinfo files through makeinfo..."
155 | make -C $(BUILDDIR)/texinfo info
156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
157 |
158 | gettext:
159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
160 | @echo
161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
162 |
163 | changes:
164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
165 | @echo
166 | @echo "The overview file is in $(BUILDDIR)/changes."
167 |
168 | linkcheck:
169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
170 | @echo
171 | @echo "Link check complete; look for any errors in the above output " \
172 | "or in $(BUILDDIR)/linkcheck/output.txt."
173 |
174 | doctest:
175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
176 | @echo "Testing of doctests in the sources finished, look at the " \
177 | "results in $(BUILDDIR)/doctest/output.txt."
178 |
179 | coverage:
180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
181 | @echo "Testing of coverage in the sources finished, look at the " \
182 | "results in $(BUILDDIR)/coverage/python.txt."
183 |
184 | xml:
185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
186 | @echo
187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
188 |
189 | pseudoxml:
190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
191 | @echo
192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
193 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | echo. coverage to run coverage check of the documentation if enabled
41 | goto end
42 | )
43 |
44 | if "%1" == "clean" (
45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
46 | del /q /s %BUILDDIR%\*
47 | goto end
48 | )
49 |
50 |
51 | REM Check if sphinx-build is available and fallback to Python version if any
52 | %SPHINXBUILD% 2> nul
53 | if errorlevel 9009 goto sphinx_python
54 | goto sphinx_ok
55 |
56 | :sphinx_python
57 |
58 | set SPHINXBUILD=python -m sphinx.__init__
59 | %SPHINXBUILD% 2> nul
60 | if errorlevel 9009 (
61 | echo.
62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
63 | echo.installed, then set the SPHINXBUILD environment variable to point
64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
65 | echo.may add the Sphinx directory to PATH.
66 | echo.
67 | echo.If you don't have Sphinx installed, grab it from
68 | echo.http://sphinx-doc.org/
69 | exit /b 1
70 | )
71 |
72 | :sphinx_ok
73 |
74 |
75 | if "%1" == "html" (
76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
80 | goto end
81 | )
82 |
83 | if "%1" == "dirhtml" (
84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
88 | goto end
89 | )
90 |
91 | if "%1" == "singlehtml" (
92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
93 | if errorlevel 1 exit /b 1
94 | echo.
95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
96 | goto end
97 | )
98 |
99 | if "%1" == "pickle" (
100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
101 | if errorlevel 1 exit /b 1
102 | echo.
103 | echo.Build finished; now you can process the pickle files.
104 | goto end
105 | )
106 |
107 | if "%1" == "json" (
108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
109 | if errorlevel 1 exit /b 1
110 | echo.
111 | echo.Build finished; now you can process the JSON files.
112 | goto end
113 | )
114 |
115 | if "%1" == "htmlhelp" (
116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
117 | if errorlevel 1 exit /b 1
118 | echo.
119 | echo.Build finished; now you can run HTML Help Workshop with the ^
120 | .hhp project file in %BUILDDIR%/htmlhelp.
121 | goto end
122 | )
123 |
124 | if "%1" == "qthelp" (
125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
129 | .qhcp project file in %BUILDDIR%/qthelp, like this:
130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-wpa-supplicant.qhcp
131 | echo.To view the help file:
132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-wpa-supplicant.ghc
133 | goto end
134 | )
135 |
136 | if "%1" == "devhelp" (
137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished.
141 | goto end
142 | )
143 |
144 | if "%1" == "epub" (
145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
149 | goto end
150 | )
151 |
152 | if "%1" == "latex" (
153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
157 | goto end
158 | )
159 |
160 | if "%1" == "latexpdf" (
161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
162 | cd %BUILDDIR%/latex
163 | make all-pdf
164 | cd %~dp0
165 | echo.
166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdfja" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf-ja
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "text" (
181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
182 | if errorlevel 1 exit /b 1
183 | echo.
184 | echo.Build finished. The text files are in %BUILDDIR%/text.
185 | goto end
186 | )
187 |
188 | if "%1" == "man" (
189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
190 | if errorlevel 1 exit /b 1
191 | echo.
192 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
193 | goto end
194 | )
195 |
196 | if "%1" == "texinfo" (
197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
198 | if errorlevel 1 exit /b 1
199 | echo.
200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
201 | goto end
202 | )
203 |
204 | if "%1" == "gettext" (
205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
206 | if errorlevel 1 exit /b 1
207 | echo.
208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
209 | goto end
210 | )
211 |
212 | if "%1" == "changes" (
213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
214 | if errorlevel 1 exit /b 1
215 | echo.
216 | echo.The overview file is in %BUILDDIR%/changes.
217 | goto end
218 | )
219 |
220 | if "%1" == "linkcheck" (
221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
222 | if errorlevel 1 exit /b 1
223 | echo.
224 | echo.Link check complete; look for any errors in the above output ^
225 | or in %BUILDDIR%/linkcheck/output.txt.
226 | goto end
227 | )
228 |
229 | if "%1" == "doctest" (
230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
231 | if errorlevel 1 exit /b 1
232 | echo.
233 | echo.Testing of doctests in the sources finished, look at the ^
234 | results in %BUILDDIR%/doctest/output.txt.
235 | goto end
236 | )
237 |
238 | if "%1" == "coverage" (
239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
240 | if errorlevel 1 exit /b 1
241 | echo.
242 | echo.Testing of coverage in the sources finished, look at the ^
243 | results in %BUILDDIR%/coverage/python.txt.
244 | goto end
245 | )
246 |
247 | if "%1" == "xml" (
248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
249 | if errorlevel 1 exit /b 1
250 | echo.
251 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
252 | goto end
253 | )
254 |
255 | if "%1" == "pseudoxml" (
256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
257 | if errorlevel 1 exit /b 1
258 | echo.
259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
260 | goto end
261 | )
262 |
263 | :end
264 |
--------------------------------------------------------------------------------
/wpa_supplicant/cli.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
7 | # TODO: There is no good way to accept array type arguments
8 |
9 | import click
10 | from contextlib import contextmanager
11 | import threading
12 | from twisted.internet.selectreactor import SelectReactor
13 | import time
14 | from wpa_supplicant.core import WpaSupplicantDriver, BSS
15 | import pprint
16 | import sys
17 |
18 |
19 | #
20 | # Helpers
21 | #
22 | @contextmanager
23 | def supplicant():
24 | """Run a reactor and provide access to the supplicant driver"""
25 | reactor = SelectReactor()
26 | t = threading.Thread(target=reactor.run, kwargs={'installSignalHandlers': 0})
27 | t.start()
28 | time.sleep(0.1) # let reactor start
29 | driver = WpaSupplicantDriver(reactor)
30 | supplicant = driver.connect()
31 | try:
32 | yield supplicant
33 | except Exception as e:
34 | print('FAIL - {}'.format(e))
35 | else:
36 | print('OK')
37 | reactor.disconnectAll()
38 | reactor.sigTerm()
39 | t.join()
40 |
41 |
42 | def add_optional_args(args, *optionals):
43 | for opt in optionals:
44 | if opt is not None:
45 | args.append(opt)
46 | return args
47 |
48 |
49 | def ctx_get(ctx, key, default=None):
50 | """Iterate up context tree and return the value at `key` if found"""
51 |
52 | while not getattr(ctx, 'is_root', False):
53 | value = getattr(ctx, key, None)
54 | if value is not None:
55 | return value
56 | ctx = ctx.parent
57 | return default
58 |
59 |
60 | #
61 | # The following CLI "groups" follow the natural hierarchy of the D-Bus
62 | # object system:
63 | #
64 | # * fi.w1.wpa_supplicant1
65 | # * fi.w1.wpa_supplicant1.Interface
66 | # * fi.w1.wpa_supplicant1.Interface.WPS
67 | # * fi.w1.wpa_supplicant1.Interface.P2PDevice
68 | # * fi.w1.wpa_supplicant1.BSS
69 | # * fi.w1.wpa_supplicant1.Network
70 | # * fi.w1.wpa_supplicant1.Peer
71 | # * fi.w1.wpa_supplicant1.Group
72 | # * fi.w1.wpa_supplicant1.PersistentGroup
73 | #
74 |
75 | @click.group()
76 | @click.option('--debug/--no-debug', default=False, help="Show log debug on stdout")
77 | @click.pass_context
78 | def root(ctx, debug):
79 | """Command line interface for wpa_supplicant D-Bus"""
80 | ctx.is_root = True
81 |
82 |
83 | @root.group()
84 | @click.argument('ifname', 'e.g. wlan0')
85 | @click.pass_context
86 | def interface(ctx, ifname):
87 | """Access fi.w1.wpa_supplicant1.Interface object"""
88 | ctx.ifname = ifname
89 |
90 |
91 | @interface.group(name='wps')
92 | def interface_wps():
93 | """Access fi.w1.wpa_supplicant1.Interface.WPS object"""
94 | raise NotImplemented
95 |
96 |
97 | @interface.group(name='p2p_device')
98 | def interface_p2p_device():
99 | """Access fi.w1.wpa_supplicant1.Interface.P2PDevice object"""
100 | raise NotImplemented
101 |
102 |
103 | @root.group()
104 | @click.argument('ifname', 'e.g. wlan0')
105 | @click.option('--ssid', default=None, help='Look at scan results for BSS examples')
106 | @click.option('--bssid', default=None, help='Look at scan results for BSS examples')
107 | @click.pass_context
108 | def bss(ctx, ifname, ssid, bssid):
109 | """Access fi.w1.wpa_supplicant1.BSS object"""
110 | ctx.ifname = ifname
111 | ctx.ssid = None
112 | ctx.bssid = None
113 | at_least_one_option = False
114 | if ssid is not None:
115 | ctx.ssid = ssid
116 | at_least_one_option = True
117 | if bssid is not None:
118 | ctx.bssid = bssid
119 | at_least_one_option = True
120 | if not at_least_one_option:
121 | print('BSS sub-commands require a valid ssid or bssid option')
122 | sys.exit(1)
123 |
124 |
125 | @root.group()
126 | def network():
127 | """Access fi.w1.wpa_supplicant1.Network object"""
128 |
129 |
130 | @root.group()
131 | def peer():
132 | """Access fi.w1.wpa_supplicant1.Peer object"""
133 | raise NotImplemented
134 |
135 |
136 | @root.group()
137 | def group():
138 | """Access fi.w1.wpa_supplicant1.Group object"""
139 | raise NotImplemented
140 |
141 |
142 | @root.group()
143 | def persistent_group():
144 | """Access fi.w1.wpa_supplicant1.PersistentGroup object"""
145 | raise NotImplemented
146 |
147 |
148 | #
149 | # fi.w1.wpa_supplicant1 API
150 | #
151 | @root.command()
152 | @click.argument('ifname', 'e.g. wlan0')
153 | @click.option('--bridge_if_name', default=None, help='Bridge to control, e.g., br0')
154 | @click.option('--driver', default=None, help='e.g. nl80211')
155 | @click.option('--config_file', default=None, help='Config file path')
156 | def create_interface(ifname, bridge_if_name, driver, config_file):
157 | """Method: Registers a wireless interface in wpa_supplicant"""
158 | args = add_optional_args([ifname, ], bridge_if_name, driver, config_file)
159 | with supplicant() as supp:
160 | pprint.pprint(supp.create_interface(*args))
161 |
162 |
163 | @root.command()
164 | @click.argument('ifname', 'e.g. wlan0')
165 | def remove_interface(ifname):
166 | """Method: Deregisters a wireless interface from wpa_supplicant"""
167 | with supplicant() as supp:
168 | iface = supp.get_interface(ifname)
169 | supp.remove_interface(iface.get_path())
170 |
171 |
172 | @root.command()
173 | @click.argument('ifname', 'e.g. wlan0')
174 | def get_interface(ifname):
175 | """Method: Returns a D-Bus path to an object related to an interface which wpa_supplicant already controls"""
176 | with supplicant() as supp:
177 | pprint.pprint(supp.get_interface(ifname))
178 |
179 |
180 | @root.command(name='get')
181 | @click.argument('name', 'Name of property (case sensitive)')
182 | def root_get(name):
183 | """Method: Get Property (case sensitive)"""
184 | with supplicant() as supp:
185 | pprint.pprint(supp.get(name))
186 |
187 |
188 | @root.command(name='set')
189 | @click.argument('name', 'Name of property (case sensitive)')
190 | @click.argument('value', 'Value to be set')
191 | def root_set(name, value):
192 | """Method: Set Property (case sensitive)"""
193 | with supplicant() as supp:
194 | pprint.pprint(supp.set(name, value))
195 |
196 |
197 | #
198 | # fi.w1.wpa_supplicant1.Interface API
199 | #
200 | @interface.command()
201 | @click.option('--scan_type', default='active', help='Active or Passive')
202 | @click.pass_context
203 | def scan(ctx, scan_type):
204 | """Method: Trigger a scan and block for results"""
205 | with supplicant() as supp:
206 | iface = supp.get_interface(ctx_get(ctx, 'ifname'))
207 | pprint.pprint(iface.scan(type=scan_type, block=True))
208 |
209 |
210 | @interface.command()
211 | @click.pass_context
212 | def disconnect(ctx):
213 | """Method: Disassociates the interface from current network"""
214 | with supplicant() as supp:
215 | iface = supp.get_interface(ctx_get(ctx, 'ifname'))
216 | iface.disconnect()
217 |
218 |
219 | @interface.command(name='get')
220 | @click.argument('name', 'Name of property (case sensitive)')
221 | @click.pass_context
222 | def interface_get(ctx, name):
223 | """Method: Get Property (case sensitive)"""
224 | with supplicant() as supp:
225 | iface = supp.get_interface(ctx_get(ctx, 'ifname'))
226 | pprint.pprint(iface.get(name))
227 |
228 |
229 | @interface.command(name='set')
230 | @click.argument('name', 'Name of property (case sensitive)')
231 | @click.argument('value', 'Value to be set')
232 | @click.pass_context
233 | def interface_set(ctx, name, value):
234 | """Method: Set Property (case sensitive)"""
235 | with supplicant() as supp:
236 | iface = supp.get_interface(ctx_get(ctx, 'ifname'))
237 | pprint.pprint(iface.set(name, value))
238 |
239 |
240 | #
241 | # fi.w1.wpa_supplicant1.BSS API
242 | #
243 | @bss.command(name='get')
244 | @click.argument('name', 'Name of property (case sensitive)')
245 | @click.pass_context
246 | def bss_get(ctx, name):
247 | """Method: Get Property (case sensitive)"""
248 | with supplicant() as supp:
249 | iface = supp.get_interface(ctx_get(ctx, 'ifname'))
250 | scan_results = iface.scan(block=True)
251 | for result in scan_results:
252 | if result.get_ssid() == ctx_get(ctx, 'ssid') or \
253 | result.get_bssid() == ctx_get(ctx, 'bssid'):
254 | bss = result
255 | break
256 | else:
257 | print('No BSS found')
258 | sys.exit(1)
259 |
260 | pprint.pprint(bss.get(name))
261 |
262 |
263 | @bss.command(name='set')
264 | @click.argument('name', 'Name of property (case sensitive)')
265 | @click.argument('value', 'Value to be set')
266 | @click.pass_context
267 | def bss_set(ctx, name, value):
268 | """Method: Set Property (case sensitive)"""
269 | raise NotImplemented
270 |
271 |
272 | def run():
273 | root()
274 |
275 |
276 | if __name__ == '__main__':
277 | run()
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # python-wpa-supplicant documentation build configuration file, created by
4 | # sphinx-quickstart on Wed Jun 3 14:30:47 2015.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys
16 | import os
17 | import shlex
18 |
19 | # If extensions (or modules to document with autodoc) are in another directory,
20 | # add these directories to sys.path here. If the directory is relative to the
21 | # documentation root, use os.path.abspath to make it absolute, like shown here.
22 | #sys.path.insert(0, os.path.abspath('.'))
23 |
24 | # -- General configuration ------------------------------------------------
25 |
26 | # If your documentation needs a minimal Sphinx version, state it here.
27 | #needs_sphinx = '1.0'
28 |
29 | # Add any Sphinx extension module names here, as strings. They can be
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 | # ones.
32 | extensions = [
33 | 'sphinx.ext.autodoc',
34 | 'sphinx.ext.todo',
35 | 'sphinx.ext.coverage',
36 | 'sphinx.ext.pngmath',
37 | ]
38 |
39 | # Add any paths that contain templates here, relative to this directory.
40 | templates_path = ['_templates']
41 |
42 | # The suffix(es) of source filenames.
43 | # You can specify multiple suffix as a list of string:
44 | # source_suffix = ['.rst', '.md']
45 | source_suffix = '.rst'
46 |
47 | # The encoding of source files.
48 | #source_encoding = 'utf-8-sig'
49 |
50 | # The master toctree document.
51 | master_doc = 'index'
52 |
53 | # General information about the project.
54 | project = u'python-wpa-supplicant'
55 | copyright = u'2015, Stephen Stack'
56 | author = u'Stephen Stack'
57 |
58 | # The version info for the project you're documenting, acts as replacement for
59 | # |version| and |release|, also used in various other places throughout the
60 | # built documents.
61 | #
62 | # The short X.Y version.
63 | version = '0.1'
64 | # The full version, including alpha/beta/rc tags.
65 | release = '0.1'
66 |
67 | # The language for content autogenerated by Sphinx. Refer to documentation
68 | # for a list of supported languages.
69 | #
70 | # This is also used if you do content translation via gettext catalogs.
71 | # Usually you set "language" from the command line for these cases.
72 | language = None
73 |
74 | # There are two options for replacing |today|: either, you set today to some
75 | # non-false value, then it is used:
76 | #today = ''
77 | # Else, today_fmt is used as the format for a strftime call.
78 | #today_fmt = '%B %d, %Y'
79 |
80 | # List of patterns, relative to source directory, that match files and
81 | # directories to ignore when looking for source files.
82 | exclude_patterns = []
83 |
84 | # The reST default role (used for this markup: `text`) to use for all
85 | # documents.
86 | #default_role = None
87 |
88 | # If true, '()' will be appended to :func: etc. cross-reference text.
89 | #add_function_parentheses = True
90 |
91 | # If true, the current module name will be prepended to all description
92 | # unit titles (such as .. function::).
93 | #add_module_names = True
94 |
95 | # If true, sectionauthor and moduleauthor directives will be shown in the
96 | # output. They are ignored by default.
97 | #show_authors = False
98 |
99 | # The name of the Pygments (syntax highlighting) style to use.
100 | pygments_style = 'sphinx'
101 |
102 | # A list of ignored prefixes for module index sorting.
103 | #modindex_common_prefix = []
104 |
105 | # If true, keep warnings as "system message" paragraphs in the built documents.
106 | #keep_warnings = False
107 |
108 | # If true, `todo` and `todoList` produce output, else they produce nothing.
109 | todo_include_todos = True
110 |
111 |
112 | # -- Options for HTML output ----------------------------------------------
113 |
114 | # The theme to use for HTML and HTML Help pages. See the documentation for
115 | # a list of builtin themes.
116 | html_theme = 'alabaster'
117 |
118 | # Theme options are theme-specific and customize the look and feel of a theme
119 | # further. For a list of options available for each theme, see the
120 | # documentation.
121 | #html_theme_options = {}
122 |
123 | # Add any paths that contain custom themes here, relative to this directory.
124 | #html_theme_path = []
125 |
126 | # The name for this set of Sphinx documents. If None, it defaults to
127 | # " v documentation".
128 | #html_title = None
129 |
130 | # A shorter title for the navigation bar. Default is the same as html_title.
131 | #html_short_title = None
132 |
133 | # The name of an image file (relative to this directory) to place at the top
134 | # of the sidebar.
135 | #html_logo = None
136 |
137 | # The name of an image file (within the static path) to use as favicon of the
138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
139 | # pixels large.
140 | #html_favicon = None
141 |
142 | # Add any paths that contain custom static files (such as style sheets) here,
143 | # relative to this directory. They are copied after the builtin static files,
144 | # so a file named "default.css" will overwrite the builtin "default.css".
145 | html_static_path = ['_static']
146 |
147 | # Add any extra paths that contain custom files (such as robots.txt or
148 | # .htaccess) here, relative to this directory. These files are copied
149 | # directly to the root of the documentation.
150 | #html_extra_path = []
151 |
152 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
153 | # using the given strftime format.
154 | #html_last_updated_fmt = '%b %d, %Y'
155 |
156 | # If true, SmartyPants will be used to convert quotes and dashes to
157 | # typographically correct entities.
158 | #html_use_smartypants = True
159 |
160 | # Custom sidebar templates, maps document names to template names.
161 | #html_sidebars = {}
162 |
163 | # Additional templates that should be rendered to pages, maps page names to
164 | # template names.
165 | #html_additional_pages = {}
166 |
167 | # If false, no module index is generated.
168 | #html_domain_indices = True
169 |
170 | # If false, no index is generated.
171 | #html_use_index = True
172 |
173 | # If true, the index is split into individual pages for each letter.
174 | #html_split_index = False
175 |
176 | # If true, links to the reST sources are added to the pages.
177 | #html_show_sourcelink = True
178 |
179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
180 | #html_show_sphinx = True
181 |
182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
183 | #html_show_copyright = True
184 |
185 | # If true, an OpenSearch description file will be output, and all pages will
186 | # contain a tag referring to it. The value of this option must be the
187 | # base URL from which the finished HTML is served.
188 | #html_use_opensearch = ''
189 |
190 | # This is the file name suffix for HTML files (e.g. ".xhtml").
191 | #html_file_suffix = None
192 |
193 | # Language to be used for generating the HTML full-text search index.
194 | # Sphinx supports the following languages:
195 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
196 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
197 | #html_search_language = 'en'
198 |
199 | # A dictionary with options for the search language support, empty by default.
200 | # Now only 'ja' uses this config value
201 | #html_search_options = {'type': 'default'}
202 |
203 | # The name of a javascript file (relative to the configuration directory) that
204 | # implements a search results scorer. If empty, the default will be used.
205 | #html_search_scorer = 'scorer.js'
206 |
207 | # Output file base name for HTML help builder.
208 | htmlhelp_basename = 'python-wpa-supplicantdoc'
209 |
210 | # -- Options for LaTeX output ---------------------------------------------
211 |
212 | latex_elements = {
213 | # The paper size ('letterpaper' or 'a4paper').
214 | #'papersize': 'letterpaper',
215 |
216 | # The font size ('10pt', '11pt' or '12pt').
217 | #'pointsize': '10pt',
218 |
219 | # Additional stuff for the LaTeX preamble.
220 | #'preamble': '',
221 |
222 | # Latex figure (float) alignment
223 | #'figure_align': 'htbp',
224 | }
225 |
226 | # Grouping the document tree into LaTeX files. List of tuples
227 | # (source start file, target name, title,
228 | # author, documentclass [howto, manual, or own class]).
229 | latex_documents = [
230 | (master_doc, 'python-wpa-supplicant.tex', u'python-wpa-supplicant Documentation',
231 | u'Stephen Stack', 'manual'),
232 | ]
233 |
234 | # The name of an image file (relative to this directory) to place at the top of
235 | # the title page.
236 | #latex_logo = None
237 |
238 | # For "manual" documents, if this is true, then toplevel headings are parts,
239 | # not chapters.
240 | #latex_use_parts = False
241 |
242 | # If true, show page references after internal links.
243 | #latex_show_pagerefs = False
244 |
245 | # If true, show URL addresses after external links.
246 | #latex_show_urls = False
247 |
248 | # Documents to append as an appendix to all manuals.
249 | #latex_appendices = []
250 |
251 | # If false, no module index is generated.
252 | #latex_domain_indices = True
253 |
254 |
255 | # -- Options for manual page output ---------------------------------------
256 |
257 | # One entry per manual page. List of tuples
258 | # (source start file, name, description, authors, manual section).
259 | man_pages = [
260 | (master_doc, 'python-wpa-supplicant', u'python-wpa-supplicant Documentation',
261 | [author], 1)
262 | ]
263 |
264 | # If true, show URL addresses after external links.
265 | #man_show_urls = False
266 |
267 |
268 | # -- Options for Texinfo output -------------------------------------------
269 |
270 | # Grouping the document tree into Texinfo files. List of tuples
271 | # (source start file, target name, title, author,
272 | # dir menu entry, description, category)
273 | texinfo_documents = [
274 | (master_doc, 'python-wpa-supplicant', u'python-wpa-supplicant Documentation',
275 | author, 'python-wpa-supplicant', 'One line description of project.',
276 | 'Miscellaneous'),
277 | ]
278 |
279 | # Documents to append as an appendix to all manuals.
280 | #texinfo_appendices = []
281 |
282 | # If false, no module index is generated.
283 | #texinfo_domain_indices = True
284 |
285 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
286 | #texinfo_show_urls = 'footnote'
287 |
288 | # If true, do not generate a @detailmenu in the "Top" node's menu.
289 | #texinfo_no_detailmenu = False
290 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Python wpa_supplicant Library
2 | =============================
3 |
4 | [](https://travis-ci.org/digidotcom/python-wpa-supplicant)
5 | [](https://coveralls.io/r/digidotcom/python-wpa-supplicant)
6 | [](https://codeclimate.com/github/digidotcom/python-wpa-supplicant)
7 | [](https://pypi.python.org/pypi/wpa_supplicant/)
8 | [](https://github.com/digidotcom/python-wpa-supplicant/blob/master/LICENSE)
9 |
10 | * [Full Documentation](http://digidotcom.github.io/python-wpa-supplicant/)
11 |
12 | Motivation
13 | ----------
14 |
15 | This library provides an interface to the wpa_supplicant D-Bus interface. As of now,
16 | there is really no good option for interfacing with wpa_supplicant from Python and
17 | to go a step further, the existing D-Bus libraries are difficult to work with. This
18 | library abstracts all of that away into a very clean API based on [wpa_supplicant
19 | D-Bus documentation](http://w1.fi/wpa_supplicant/devel/dbus.html).
20 |
21 |
22 | Overview
23 | --------
24 |
25 | This library is more than just a library that can be used programmatically, it also
26 | serves as a command-line tool for easily using wpa_supplicant and exercising the library.
27 |
28 | Here is a quick example of creating an wireless interface (wlan0) and performing a scan.
29 |
30 | With the library:
31 |
32 | ```py
33 | from wpa_supplicant.core import WpaSupplicantDriver
34 | from twisted.internet.selectreactor import SelectReactor
35 | import threading
36 | import time
37 |
38 | # Start a simple Twisted SelectReactor
39 | reactor = SelectReactor()
40 | threading.Thread(target=reactor.run, kwargs={'installSignalHandlers': 0}).start()
41 | time.sleep(0.1) # let reactor start
42 |
43 | # Start Driver
44 | driver = WpaSupplicantDriver(reactor)
45 |
46 | # Connect to the supplicant, which returns the "root" D-Bus object for wpa_supplicant
47 | supplicant = driver.connect()
48 |
49 | # Register an interface w/ the supplicant, this can raise an error if the supplicant
50 | # already knows about this interface
51 | interface = supplicant.create_interface('wlan0')
52 |
53 | # Issue the scan
54 | scan_results = interface.scan(block=True)
55 | for bss in scan_results:
56 | print bss.get_ssid()
57 | ```
58 |
59 | With the CLI, you can do it in 2 commands:
60 |
61 | ```
62 | $ wpa create_interface wlan0
63 | Interface(Path: /fi/w1/wpa_supplicant1/Interfaces/5, Name: wlan0, State: disconnected)
64 | OK
65 | $ wpa interface wlan0 scan
66 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/13, SSID: WINKHUB-107, BSSID: B4:79:A7:17:38:B5, Signal: -75dBm),
67 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/20, SSID: Stage, BSSID: 04:18:D6:67:2A:9C, Signal: -67dBm),
68 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/22, SSID: DAP-GUEST, BSSID: 00:07:7D:34:DB:BD, Signal: -73dBm),
69 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/24, SSID: HaberHive, BSSID: E0:1C:41:B5:6F:D5, Signal: -71dBm),
70 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/25, SSID: CMLS Guest WiFi, BSSID: 02:18:4A:91:E9:50, Signal: -79dBm),
71 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/26, SSID: Shout Public, BSSID: 0A:18:D6:67:27:9E, Signal: -77dBm),
72 | BSS(Path: /fi/w1/wpa_supplicant1/Interfaces/3/BSSs/29, SSID: ThinkTank, BSSID: 00:12:5F:03:AD:B4, Signal: -79dBm)]
73 | OK
74 | ```
75 |
76 |
77 | Installation
78 | ------------
79 |
80 | ```sh
81 | $ pip install wpa_supplicant
82 | ```
83 |
84 |
85 | Feature Coverage Map
86 | --------------------
87 |
88 | Often times adding a command or method to the library is very trivial however due to
89 | the finicky-ness of D-Bus in my experience, it is better to have an explicit method
90 | in the library that maps to a method in the D-Bus interface, even if the implementation
91 | is very cookie-cutter.
92 |
93 | Here is the current feature coverage:
94 |
95 |
96 | |Object | Method | Supported|
97 | |---------------------|---------------------------------|-----------|
98 | |root | CreateInterface | Yes |
99 | | | RemoveInterface | Yes |
100 | | | GetInterface | Yes |
101 | | | Get (properties) | Yes |
102 | | | Set (properties) | Yes |
103 | | | Register (signal) | Yes |
104 | |Interface | Scan | Yes |
105 | | | Disconnect | Yes |
106 | | | AddNetwork | Yes |
107 | | | RemoveNetwork | Yes |
108 | | | RemoveAllNetworks | Yes |
109 | | | SelectNetwork | Yes |
110 | | | Reassociate | * |
111 | | | Reattach | * |
112 | | | AddBlob | * |
113 | | | RemoveBlob | * |
114 | | | GetBlob | * |
115 | | | AutoScan | * |
116 | | | TDLSDiscover | * |
117 | | | TDLSSetup | * |
118 | | | TDLSStatus | * |
119 | | | TDLSTeardown | * |
120 | | | EAPLogoff | * |
121 | | | EAPLogon | * |
122 | | | NetworkReply | * |
123 | | | SetPKCS11EngineAndModulePath | * |
124 | | | SignalPoll | * |
125 | | | FlushBSS | * |
126 | | | SubscribeProbReq | * |
127 | | | UnsubscribeProbReq | * |
128 | | | Get (properties) | Yes |
129 | | | Set (properties) | Yes |
130 | | | Register (signal) | Yes |
131 | |Interface.WPS | Start | * |
132 | | | Get (properties) | * |
133 | | | Set (properties) | * |
134 | | | Register (signal) | * |
135 | |Interface.P2PDevice | Find | * |
136 | | | StopFind | * |
137 | | | Listen | * |
138 | | | ExtendedListen | * |
139 | | | PresenceRequest | * |
140 | | | ProvisionDiscoveryRequest | * |
141 | | | Connect | * |
142 | | | GroupAdd | * |
143 | | | Invite | * |
144 | | | Disconnect | * |
145 | | | RejectPeer | * |
146 | | | Flush | * |
147 | | | AddService | * |
148 | | | DeleteService | * |
149 | | | FlushService | * |
150 | | | ServiceDiscoveryRequest | * |
151 | | | ServiceDiscoveryResponse | * |
152 | | | ServiceDiscoveryCancelRequest | * |
153 | | | ServiceUpdate | * |
154 | | | Register (signal) | * |
155 | |BSS | Get (properties) | Yes |
156 | | | Set (properties) | Yes |
157 | | | Register (signal) | Yes |
158 | |Network | Get (properties) | Yes |
159 | | | Set (properties) | Yes |
160 | | | Register (signal) | Yes |
161 | |Peer | Get (properties) | * |
162 | | | Set (properties) | * |
163 | | | Register (signal) | * |
164 | |Group | Get (properties) | * |
165 | | | Set (properties) | * |
166 | | | Register (signal) | * |
167 | |PersistentGroup | Get (properties) | * |
168 | | | Set (properties) | * |
169 | | | Register (signal) | * |
170 |
171 | License
172 | -------
173 |
174 | This software is open-source. Copyright (c), Digi International Inc., 2015.
175 |
176 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
177 | If a copy of the MPL was not distributed with this file, you can obtain one at
178 | http://mozilla.org/MPL/2.0/.
179 |
180 | Digi, Digi International, the Digi logo, the Digi website, and Digi
181 | Device Cloud are trademarks or registered trademarks of Digi
182 | International, Inc. in the United States and other countries
183 | worldwide. All other trademarks are the property of their respective
184 | owners.
185 |
186 | THE SOFTWARE AND RELATED TECHNICAL INFORMATION IS PROVIDED "AS IS"
187 | WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
188 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
189 | PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL DIGI OR ITS
190 | SUBSIDIARIES BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
191 | WHETHER IN AN ACTION IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
192 | OF OR IN CONNECTION WITH THE SOFTWARE AND TECHNICAL INFORMATION
193 | HEREIN, INCLUDING ALL SOURCE AND OBJECT CODES, IRRESPECTIVE OF HOW IT
194 | IS USED. YOU AGREE THAT YOU ARE NOT PROHIBITED FROM RECEIVING THIS
195 | SOFTWARE AND TECHNICAL INFORMATION UNDER UNITED STATES AND OTHER
196 | APPLICABLE COUNTRY EXPORT CONTROL LAWS AND REGULATIONS AND THAT YOU
197 | WILL COMPLY WITH ALL APPLICABLE UNITED STATES AND OTHER COUNTRY EXPORT
198 | LAWS AND REGULATIONS WITH REGARD TO USE AND EXPORT OR RE-EXPORT OF THE
199 | SOFTWARE AND TECHNICAL INFORMATION.
200 |
--------------------------------------------------------------------------------
/wpa_supplicant/test/mocks.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
7 | """This module provides classes for mocking out the txdbus layer at a low level
8 |
9 | Mocking out things at this layer means that some of the txdbus code is being tested
10 | in addition to the wpa_supplicant library code. Although a-typical of traditional unit
11 | testing this approach proved beneficial in practice.
12 | """
13 |
14 | from twisted.internet import defer
15 | from collections import OrderedDict
16 | from txdbus import client, error
17 | import mock
18 | import re
19 |
20 |
21 | def init():
22 | """Call at the beginning of ``testCase.setUp`` to mock D-Bus Layer"""
23 |
24 | conn = MockConnection()
25 |
26 | @defer.inlineCallbacks
27 | def mock_connect(reactor, busAddress):
28 | yield
29 | defer.returnValue(conn)
30 |
31 | client.connect = mock_connect
32 |
33 |
34 | class MockDBusObject(object):
35 | """Base class for mocked txdbus objects which implement D-Bus interfaces"""
36 |
37 | def __init__(self, *args, **kwargs):
38 | self.callRemote = mock.Mock(side_effect=self._callRemote)
39 | self.notifyOnSignal = mock.Mock(side_effect=self._notifyOnSignal)
40 | self.cancelSignalNotification = mock.Mock(
41 | side_effect=self._cancelSignalNotification)
42 |
43 | self._signals = dict()
44 |
45 | def _callRemote(self, method_name, *args):
46 | if method_name == 'Get':
47 | property_name = args[1]
48 | property_method = getattr(self, 'Get_%s' % property_name)
49 | if property_method:
50 | deferred = mock.Mock()
51 | deferred.result = property_method()
52 | deferred.called = True
53 | return deferred
54 | else:
55 | return None
56 |
57 | elif method_name == 'Set':
58 | return NotImplementedError()
59 |
60 | else:
61 | native_method = getattr(self, method_name)
62 | if native_method:
63 | deferred = mock.Mock()
64 | deferred.result = native_method(*args)
65 | deferred.called = True
66 | return deferred
67 |
68 | def _notifyOnSignal(self, signal_name, callback, interface=None):
69 | d = defer.Deferred()
70 | d.addCallback(callback)
71 | self._signals.setdefault(signal_name, list()).append(d)
72 | return d
73 |
74 | def _cancelSignalNotification(self, rule_id):
75 | pass
76 |
77 | def fire_signal(self, signal_name, value):
78 | deferreds = self._signals.get(signal_name, None)
79 | if deferreds is not None:
80 | for d in deferreds:
81 | d.callback(value)
82 |
83 |
84 | class MockWpaSupplicant(MockDBusObject):
85 | """Mock wpa_supplicant `Root` object"""
86 |
87 | def __init__(self, *args, **kwargs):
88 | MockDBusObject.__init__(self, *args, **kwargs)
89 | self._valid_interfaces = {
90 | 'wlan0': '/fi/w1/wpa_supplicant1/Interfaces/3',
91 | 'sta0': '/fi/w1/wpa_supplicant1/Interfaces/4'
92 | }
93 | self._created_interfaces = []
94 |
95 | #
96 | # Methods
97 | #
98 | def GetInterface(self, interface_name):
99 | if interface_name in self._valid_interfaces:
100 | return self._valid_interfaces.get(interface_name)
101 | else:
102 | raise error.RemoteError('fi.w1.wpa_supplicant1.InterfaceUnknown')
103 |
104 | def RemoveInterface(self, interface_path):
105 | for i, path in enumerate(self._created_interfaces[:]):
106 | if path == interface_path:
107 | del self._created_interfaces[i]
108 | break
109 | else:
110 | raise error.RemoteError('fi.w1.wpa_supplicant1.InterfaceUnknown')
111 |
112 | def CreateInterface(self, cfg):
113 | if not isinstance(cfg, dict):
114 | raise error.RemoteError('fi.w1.wpa_supplicant1.InvalidArgs')
115 |
116 | interface_name = cfg.get('Ifname', None) # required argument
117 | if interface_name is None:
118 | raise error.RemoteError('fi.w1.wpa_supplicant1.InvalidArgs')
119 |
120 | iface_path = self._valid_interfaces.get(interface_name)
121 |
122 | if not iface_path:
123 | raise error.RemoteError('fi.w1.wpa_supplicant1.UnknownError')
124 |
125 | if iface_path in self._created_interfaces:
126 | raise error.RemoteError('fi.w1.wpa_supplicant1.InterfaceExists')
127 |
128 | self._created_interfaces.append(iface_path)
129 | return iface_path
130 |
131 | #
132 | # Properties
133 | #
134 | def Get_Interfaces(self):
135 | return [u'/fi/w1/wpa_supplicant1/Interfaces/7']
136 |
137 | def Get_EapMethods(self):
138 | return [u'MD5',
139 | u'TLS',
140 | u'MSCHAPV2',
141 | u'PEAP',
142 | u'TTLS',
143 | u'GTC',
144 | u'OTP',
145 | u'SIM',
146 | u'LEAP',
147 | u'PSK',
148 | u'AKA',
149 | u"AKA'",
150 | u'FAST',
151 | u'PAX',
152 | u'SAKE',
153 | u'GPSK',
154 | u'WSC',
155 | u'IKEV2',
156 | u'TNC',
157 | u'PWD']
158 |
159 | def Get_DebugLevel(self):
160 | return u'info'
161 |
162 | def Get_DebugShowKeys(self):
163 | return False
164 |
165 | def Get_DebugTimestamp(self):
166 | return False
167 |
168 |
169 | class MockInterfaceObject(MockDBusObject):
170 | """Mock wpa_supplicant `Interface` object"""
171 |
172 | def __init__(self, *args, **kwargs):
173 | MockDBusObject.__init__(self, *args, **kwargs)
174 |
175 | # Internal State
176 | self._network_counter = -1
177 | self._networks = dict()
178 | self._current_network = None
179 |
180 | #
181 | # Methods
182 | #
183 | def Scan(self, scan_config):
184 | return None
185 |
186 | def AddNetwork(self, cfg):
187 | self._network_counter += 1
188 | network_path = '/fi/w1/wpa_supplicant1/Networks/%s' % self._network_counter
189 | self._networks[network_path] = cfg
190 | return network_path
191 |
192 | def RemoveNetwork(self, network_path):
193 | if network_path in self._networks:
194 | del self._networks[network_path]
195 | else:
196 | raise error.RemoteError('fi.w1.wpa_supplicant1.NetworkUnknown')
197 |
198 | def SelectNetwork(self, network_path):
199 | self._current_network = network_path
200 |
201 | def Disconnect(self):
202 | if self._current_network is None:
203 | raise error.RemoteError('fi.w1.wpa_supplicant1.NotConnected')
204 | else:
205 | self._current_network = None
206 |
207 | #
208 | # Properties
209 | #
210 | def Get_Networks(self):
211 | return []
212 |
213 | def Get_FastReauth(self):
214 | return True
215 |
216 | def Get_ScanInterval(self):
217 | return 5
218 |
219 | def Get_CurrentNetwork(self):
220 | return self._current_network
221 |
222 | def Get_Ifname(self):
223 | return u'wlan0'
224 |
225 | def Get_BSSs(self):
226 | return ['/fi/w1/wpa_supplicant1/Interfaces/3/BSSs/1234', ]
227 |
228 | def Get_CurrentBSS(self):
229 | return '/fi/w1/wpa_supplicant1/Interfaces/3/BSSs/1234'
230 |
231 | def Get_ApScan(self):
232 | return 1
233 |
234 | def Get_Scanning(self):
235 | return False
236 |
237 | def Get_State(self):
238 | return u'inactive'
239 |
240 | def Get_Capabilities(self):
241 | return {u'AuthAlg': [u'open', u'shared', u'leap'],
242 | u'Group': [u'ccmp', u'tkip', u'wep104', u'wep40'],
243 | u'KeyMgmt': [u'none',
244 | u'ieee8021x',
245 | u'wpa-eap',
246 | u'wpa-ft-eap',
247 | u'wpa-eap-sha256',
248 | u'wpa-psk',
249 | u'wpa-ft-psk',
250 | u'wpa-psk-sha256',
251 | u'wps'],
252 | u'MaxScanSSID': 4,
253 | u'Modes': [u'infrastructure', u'ad-hoc', u'ap'],
254 | u'Pairwise': [u'ccmp', u'tkip'],
255 | u'Protocol': [u'rsn', u'wpa'],
256 | u'Scan': [u'active', u'passive', u'ssid']}
257 |
258 | def Get_Country(self):
259 | return u'US'
260 |
261 | def Get_BSSExpireAge(self):
262 | return 180
263 |
264 | def Get_BSSExpireCount(self):
265 | return 2
266 |
267 | def Get_BridgeIfname(self):
268 | return u''
269 |
270 | def Get_Driver(self):
271 | return u'nl80211'
272 |
273 |
274 | class MockBSSObject(MockDBusObject):
275 | """Mock txdbus/wpa_supplicant .fi.w1.wpa_supplicant1.BSS object"""
276 |
277 | #
278 | # Properties
279 | #
280 | def Get_BSSID(self):
281 | # This should return an array of bytes, for testing it is ideal
282 | # if the bytes fall within the ASCII range
283 | return [70, 71, 72, 73]
284 |
285 | def Get_SSID(self):
286 | # This should return an array of bytes, for testing it is ideal
287 | # if the bytes fall within the ASCII range
288 | return [70, 71, 72, 73]
289 |
290 | def Get_WPA(self):
291 | return {u'Group': u'tkip', u'KeyMgmt': [u'wpa-psk'], u'Pairwise': [u'tkip']}
292 |
293 | def Get_RSN(self):
294 | return {u'Group': u'', u'KeyMgmt': [], u'Pairwise': []}
295 |
296 | def Get_IEs(self):
297 | return [0,
298 | 9,
299 | 68,
300 | 65,
301 | 80,
302 | 150,
303 | 24,
304 | 36]
305 |
306 | def Get_Privacy(self):
307 | return True
308 |
309 | def Get_Mode(self):
310 | return u'infrastructure'
311 |
312 | def Get_Frequency(self):
313 | return 2462
314 |
315 | def Get_Rates(self):
316 | return [54000000, 48000000, 6000000]
317 |
318 | def Get_Signal(self):
319 | return -60
320 |
321 |
322 | class MockNetworkObject(MockDBusObject):
323 | """Mock txdbus/wpa_supplicant .fi.w1.wpa_supplicant1.Networks object"""
324 |
325 | def Get_Properties(self):
326 | return {
327 | u'ap_max_inactivity': u'0',
328 | u'beacon_int': u'0',
329 | u'bg_scan_period': u'-1',
330 | u'disabled': u'0',
331 | u'dtim_period': u'0',
332 | u'eap_workaround': u'-1',
333 | u'eapol_flags': u'3',
334 | u'engine': u'0',
335 | u'engine2': u'0',
336 | u'fragment_size': u'1398',
337 | u'frequency': u'0',
338 | u'group': u'CCMP TKIP WEP104 WEP40',
339 | u'ignore_broadcast_ssid': u'0',
340 | u'key_mgmt': u'WPA-PSK WPA-EAP',
341 | u'mixed_cell': u'0',
342 | u'mode': u'0',
343 | u'ocsp': u'0',
344 | u'pairwise': u'CCMP TKIP',
345 | u'peerkey': u'0',
346 | u'priority': u'0',
347 | u'proactive_key_caching': u'-1',
348 | u'proto': u'WPA RSN',
349 | u'scan_ssid': u'0',
350 | u'ssid': u'"wdnu-dvt1"',
351 | u'wep_tx_keyidx': u'0',
352 | u'wpa_ptk_rekey': u'0'
353 | }
354 |
355 |
356 | class MockConnection(object):
357 | """Mock txdbus client connection to a D-Bus server"""
358 |
359 | mock_objects = OrderedDict([
360 | ('/fi/w1/wpa_supplicant1/Interfaces/.+/BSSs/.+', MockBSSObject),
361 | ('/fi/w1/wpa_supplicant1/Networks/.+', MockNetworkObject),
362 | ('/fi/w1/wpa_supplicant1/Interfaces/.+', MockInterfaceObject),
363 | ('/fi/w1/wpa_supplicant1', MockWpaSupplicant)
364 | ])
365 |
366 | @defer.inlineCallbacks
367 | def getRemoteObject(self, busName, objectPath, interfaces=None):
368 | interface_object = None
369 | for opath, interface in self.mock_objects.items():
370 | match = re.match(opath, objectPath)
371 | if match:
372 | interface_object = interface()
373 | break
374 | yield
375 | defer.returnValue(interface_object)
376 |
--------------------------------------------------------------------------------
/wpa_supplicant/test/test_core.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 | import time
7 | import unittest
8 | import threading
9 |
10 | import six
11 | from six.moves.queue import Queue
12 | from wpa_supplicant.core import WpaSupplicantDriver, Interface, BSS, Network, \
13 | InterfaceUnknown, InterfaceExists, NotConnected, NetworkUnknown, WpaSupplicant
14 | from twisted.internet.selectreactor import SelectReactor
15 | import mock
16 | from wpa_supplicant.test import mocks
17 |
18 |
19 | class Task(object):
20 | def __init__(self, fn, *args, **kwargs):
21 | self._fn = fn
22 | self._args = args
23 | self._kwargs = kwargs
24 |
25 | def do(self):
26 | self._fn(*self._args, **self._kwargs)
27 |
28 |
29 | class ThreadedTaskRunner(threading.Thread):
30 | _QUIT = object()
31 |
32 | def __init__(self):
33 | threading.Thread.__init__(self)
34 | self.setDaemon(True)
35 |
36 | self._tasks = Queue()
37 |
38 | def run(self):
39 | while True:
40 | task = self._tasks.get()
41 | if task is self._QUIT:
42 | break
43 | else:
44 | if isinstance(task, Task):
45 | task.do()
46 |
47 | def stop(self):
48 | self._tasks.put(self._QUIT)
49 |
50 | def queue(self, task):
51 | self._tasks.put(task)
52 |
53 |
54 | class TestWpaSupplicant(unittest.TestCase):
55 | def setUp(self):
56 | mocks.init()
57 | self._taskrunner = ThreadedTaskRunner()
58 | self._taskrunner.start()
59 | self._reactor = SelectReactor()
60 | self._driver = WpaSupplicantDriver(self._reactor)
61 | self._reactor_thread = threading.Thread(target=self._reactor.run,
62 | kwargs={'installSignalHandlers': 0})
63 | self._reactor_thread.start()
64 | time.sleep(0.1)
65 | self._supplicant = self._driver.connect()
66 |
67 | def tearDown(self):
68 | self._reactor.disconnectAll()
69 | self._reactor.sigTerm()
70 | self._reactor_thread.join()
71 | self._taskrunner.stop()
72 |
73 | #
74 | # Helpers
75 | #
76 | def _get_interface(self, interface_name):
77 | return self._supplicant.get_interface(interface_name)
78 |
79 | def _get_any_bss(self):
80 | iface = self._get_interface('wlan0')
81 | return BSS('/fi/w1/wpa_supplicant1/Interfaces/1/BSSs/1',
82 | iface._conn,
83 | iface._reactor)
84 |
85 | #
86 | # Test Driver
87 | #
88 | def test_connect(self):
89 | supplicant = self._driver.connect()
90 | self.assertIsInstance(supplicant, WpaSupplicant)
91 |
92 | #
93 | # Test Base
94 | #
95 | def test_register_for_signal(self):
96 | cb = mock.Mock()
97 | self._supplicant.register_signal('mysig', cb)
98 | self._supplicant._introspection.fire_signal('mysig', True)
99 | cb.assert_called_once_with(True)
100 |
101 | def test_register_for_signal_once(self):
102 | def fire_signal(result):
103 | time.sleep(1)
104 | self._supplicant._introspection.fire_signal('mysig', result)
105 |
106 | deferred_queue = self._supplicant.register_signal_once('mysig')
107 | self._taskrunner.queue(Task(fire_signal, True))
108 | result = deferred_queue.get()
109 | self.assertEqual(result, True)
110 |
111 | #
112 | # Test Supplicant
113 | #
114 | def test_get_interface(self):
115 | interface = self._supplicant.get_interface('wlan0')
116 | self._supplicant._without_introspection.callRemote.assert_called_with(
117 | 'GetInterface', 'wlan0')
118 | self.assertTrue(isinstance(interface, Interface))
119 | self.assertEqual(interface.get_path(), '/fi/w1/wpa_supplicant1/Interfaces/3')
120 |
121 | def test_get_unknown_interface(self):
122 | self.assertRaises(InterfaceUnknown, self._supplicant.get_interface, 'wlan99')
123 |
124 | def test_create_interface(self):
125 | interface = self._supplicant.create_interface('wlan0')
126 | self._supplicant._without_introspection.callRemote.assert_called_with(
127 | 'CreateInterface',
128 | {'Ifname': 'wlan0',
129 | 'Driver': None})
130 | self.assertTrue(isinstance(interface, Interface))
131 | self.assertEqual(interface.get_path(), '/fi/w1/wpa_supplicant1/Interfaces/3')
132 |
133 | def test_create_interface_already_exists(self):
134 | self.test_create_interface()
135 | self.assertRaises(InterfaceExists, self._supplicant.create_interface, 'wlan0')
136 |
137 | def test_remove_interface(self):
138 | self._supplicant.create_interface('wlan0')
139 | returnval = self._supplicant.remove_interface(
140 | '/fi/w1/wpa_supplicant1/Interfaces/3')
141 | self._supplicant._without_introspection.callRemote.assert_called_with(
142 | 'RemoveInterface',
143 | '/fi/w1/wpa_supplicant1/Interfaces/3')
144 | self.assertEqual(returnval, None)
145 |
146 | def test_remove_unknown_interface(self):
147 | supplicant = self._driver.connect()
148 | self.assertRaises(InterfaceUnknown, supplicant.remove_interface, 'wlan99')
149 |
150 | def test_get_debug_level(self):
151 | supplicant = self._driver.connect()
152 | self.assertEqual(supplicant.get_debug_level(), six.u('info'))
153 |
154 | def test_get_debug_timestamp(self):
155 | supplicant = self._driver.connect()
156 | self.assertEqual(supplicant.get_debug_timestamp(), False)
157 |
158 | def test_get_debug_showkeys(self):
159 | supplicant = self._driver.connect()
160 |
161 | def test_get_interfaces(self):
162 | supplicant = self._driver.connect()
163 | self.assertEqual(supplicant.get_interfaces(),
164 | [u'/fi/w1/wpa_supplicant1/Interfaces/7'])
165 |
166 | def test_get_eap_methods(self):
167 | supplicant = self._driver.connect()
168 | self.assertEqual(supplicant.get_eap_methods(), [u'MD5',
169 | u'TLS',
170 | u'MSCHAPV2',
171 | u'PEAP',
172 | u'TTLS',
173 | u'GTC',
174 | u'OTP',
175 | u'SIM',
176 | u'LEAP',
177 | u'PSK',
178 | u'AKA',
179 | u"AKA'",
180 | u'FAST',
181 | u'PAX',
182 | u'SAKE',
183 | u'GPSK',
184 | u'WSC',
185 | u'IKEV2',
186 | u'TNC',
187 | u'PWD'])
188 |
189 | #
190 | # Test Interface
191 | #
192 | def test_interface_scan(self):
193 | interface = self._get_interface('wlan0')
194 | scan_results = interface.scan()
195 | self.assertEqual(scan_results, None)
196 |
197 | def test_interface_blocking_scan(self):
198 | interface = self._get_interface('wlan0')
199 |
200 | def fire_signal():
201 | time.sleep(1)
202 | interface._introspection.fire_signal('ScanDone', True)
203 |
204 | self._taskrunner.queue(Task(fire_signal))
205 | scan_results = interface.scan(block=True)
206 | for res in scan_results:
207 | self.assertTrue(isinstance(res, BSS))
208 | self.assertEqual(res.get_path(),
209 | '/fi/w1/wpa_supplicant1/Interfaces/3/BSSs/1234')
210 |
211 | def test_add_network(self):
212 | interface = self._get_interface('wlan0')
213 | network = interface.add_network({})
214 | self.assertTrue(isinstance(network, Network))
215 | self.assertEqual(network.get_path(), '/fi/w1/wpa_supplicant1/Networks/0')
216 |
217 | def test_remove_network(self):
218 | interface = self._get_interface('wlan0')
219 | network = interface.add_network({})
220 | result = interface.remove_network(network.get_path())
221 | self.assertEqual(result, None)
222 |
223 | def test_remove_unknown_network(self):
224 | interface = self._get_interface('wlan0')
225 | self.assertRaises(NetworkUnknown,
226 | interface.remove_network,
227 | '/fi/w1/wpa_supplicant1/Networks/44')
228 |
229 | def test_select_network(self):
230 | interface = self._get_interface('wlan0')
231 | network = interface.add_network({})
232 | interface.select_network(network.get_path())
233 | current_network = interface.get_current_network()
234 | self.assertEqual(current_network.get_path(), network.get_path())
235 |
236 | def test_get_ifname(self):
237 | interface = self._get_interface('wlan0')
238 | self.assertEqual(interface.get_ifname(), 'wlan0')
239 |
240 | def test_get_current_bss(self):
241 | interface = self._get_interface('wlan0')
242 | bss = interface.get_current_bss()
243 | self.assertTrue(isinstance(bss, BSS))
244 |
245 | def test_get_current_network(self):
246 | interface = self._get_interface('wlan0')
247 | net = interface.get_current_network()
248 | self.assertEqual(net, None)
249 |
250 | def test_network_disconnect(self):
251 | interface = self._get_interface('wlan0')
252 | network = interface.add_network({})
253 | interface.select_network(network.get_path())
254 | interface.disconnect_network()
255 | self.assertIsNone(interface.get_current_network())
256 |
257 | def test_network_disconnect_not_connected(self):
258 | interface = self._get_interface('wlan0')
259 | self.assertRaises(NotConnected, interface.disconnect_network)
260 |
261 | def test_get_networks(self):
262 | interface = self._get_interface('wlan0')
263 | self.assertEqual(interface.get_networks(), [])
264 |
265 | def test_get_state(self):
266 | interface = self._get_interface('wlan0')
267 | self.assertEqual(interface.get_state(), u'inactive')
268 |
269 | def test_get_scanning(self):
270 | interface = self._get_interface('wlan0')
271 | self.assertEqual(interface.get_scanning(), False)
272 |
273 | def test_get_scan_interval(self):
274 | interface = self._get_interface('wlan0')
275 | self.assertEqual(interface.get_scan_interval(), 5)
276 |
277 | def test_get_fast_reauth(self):
278 | interface = self._get_interface('wlan0')
279 | self.assertEqual(interface.get_fast_reauth(), True)
280 |
281 | def test_get_all_bss(self):
282 | interface = self._get_interface('wlan0')
283 | self.assertEqual(interface.get_all_bss(),
284 | ['/fi/w1/wpa_supplicant1/Interfaces/3/BSSs/1234', ])
285 |
286 | def test_get_driver(self):
287 | interface = self._get_interface('wlan0')
288 | self.assertEqual(interface.get_driver(), u'nl80211')
289 |
290 | def test_get_country(self):
291 | interface = self._get_interface('wlan0')
292 | self.assertEqual(interface.get_country(), u'US')
293 |
294 | def test_get_bridge_ifname(self):
295 | interface = self._get_interface('wlan0')
296 | self.assertEqual(interface.get_bridge_ifname(), u'')
297 |
298 | def test_get_bss_expire_age(self):
299 | interface = self._get_interface('wlan0')
300 | self.assertEqual(interface.get_bss_expire_age(), 180)
301 |
302 | def test_get_bss_expire_count(self):
303 | interface = self._get_interface('wlan0')
304 | self.assertEqual(interface.get_bss_expire_count(), 2)
305 |
306 | def test_get_ap_scan(self):
307 | interface = self._get_interface('wlan0')
308 | self.assertEqual(interface.get_ap_scan(), 1)
309 |
310 | #
311 | # Test BSS
312 | #
313 | def test_get_channel(self):
314 | # The "channel" is a conversion from frequency, so we need to test
315 | # the calculation being used
316 | bss = self._get_any_bss()
317 | self.assertEqual(bss.get_channel(), 11)
318 |
319 | def test_get_ssid(self):
320 | bss = self._get_any_bss()
321 | self.assertEqual(bss.get_ssid(), 'FGHI')
322 |
323 | def test_get_bssid(self):
324 | bss = self._get_any_bss()
325 | self.assertEqual(bss.get_bssid(), '46:47:48:49')
326 |
327 | def test_get_frequency(self):
328 | bss = self._get_any_bss()
329 | self.assertEqual(bss.get_frequency(), 2462)
330 |
331 | def test_get_wpa(self):
332 | bss = self._get_any_bss()
333 | self.assertEqual(bss.get_wpa(),
334 | {u'Group': u'tkip', u'KeyMgmt': [u'wpa-psk'], u'Pairwise': [u'tkip']})
335 |
336 | def test_get_rsn(self):
337 | bss = self._get_any_bss()
338 | self.assertEqual(bss.get_rsn(), {u'Group': u'', u'KeyMgmt': [], u'Pairwise': []})
339 |
340 | def test_get_ies(self):
341 | bss = self._get_any_bss()
342 | self.assertEqual(bss.get_ies(), [0, 9, 68, 65, 80, 150, 24, 36])
343 |
344 | def test_get_privacy(self):
345 | interface = self._get_interface('wlan0')
346 | bss = interface.get_current_bss()
347 | self.assertEqual(bss.get_privacy(), True)
348 |
349 | def test_get_mode(self):
350 | bss = self._get_any_bss()
351 | self.assertEqual(bss.get_mode(), u'infrastructure')
352 |
353 | def test_get_rates(self):
354 | bss = self._get_any_bss()
355 | self.assertEqual(bss.get_rates(), [54000000, 48000000, 6000000])
356 |
357 | def test_get_signal_dbm(self):
358 | interface = self._get_interface('wlan0')
359 | bss = interface.get_current_bss()
360 | self.assertEqual(-60, bss.get_signal_dbm())
361 |
362 | def test_get_signal_quality(self):
363 | interface = self._get_interface('wlan0')
364 | bss = interface.get_current_bss()
365 | self.assertEqual(80, bss.get_signal_quality())
366 |
367 | #
368 | # Test Network
369 | #
370 | def test_get_properties(self):
371 | interface = self._get_interface('wlan0')
372 | desired_network = interface.add_network({'ssid': 'foo', 'psk': 'bar'})
373 | interface.select_network(desired_network.get_path())
374 | curr_network = interface.get_current_network()
375 | props = curr_network.get_properties()
376 | self.assertEqual(props['ssid'], 'wdnu-dvt1')
377 |
378 | def test_get_enabled(self):
379 | pass
380 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
--------------------------------------------------------------------------------
/wpa_supplicant/core.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
6 |
7 | """
8 |
9 | This module (library) is an intuitive wrapper around wpa_supplicant's D-Bus API
10 |
11 | For the most part, the library aims to provide a one-to-one mapping into the D-Bus
12 | interfaces offered by wpa_supplicant. Certain abstractions or niceties have been made
13 | to make the usage of the API more intuitive.
14 |
15 | The libraries API was mainly derived from the following documentation:
16 |
17 | http://w1.fi/wpa_supplicant/devel/dbus.html
18 |
19 | """
20 | from twisted.internet import defer, threads
21 | from txdbus.interface import DBusInterface, Method, Signal
22 | from txdbus import client, error
23 | from functools import wraps
24 | import threading
25 | import logging
26 |
27 | #
28 | # Constants/Globals
29 | #
30 | BUS_NAME = 'fi.w1.wpa_supplicant1'
31 | logger = logging.getLogger('wpasupplicant')
32 |
33 |
34 | #
35 | # Helpers
36 | #
37 | def _catch_remote_errors(fn):
38 | """Decorator for catching and wrapping txdbus.error.RemoteError exceptions"""
39 | @wraps(fn)
40 | def closure(*args, **kwargs):
41 | try:
42 | return fn(*args, **kwargs)
43 | except error.RemoteError as ex:
44 | match = _REMOTE_EXCEPTIONS.get(str(ex.errName))
45 | if match is not None:
46 | raise match(ex.message)
47 | else:
48 | logger.warn("Unrecognized error: %s" % ex.errName)
49 | raise WpaSupplicantException(ex.message)
50 | return closure
51 |
52 |
53 | def _eval(deferred, reactor):
54 | """Evaluate a deferred on a given reactor and return the result
55 |
56 | This function is safe to call with a deferred that has already been evaluated.
57 | """
58 |
59 | @defer.inlineCallbacks
60 | def closure():
61 | if deferred.called:
62 | result = deferred.result
63 | else:
64 | result = yield deferred
65 |
66 | defer.returnValue(result)
67 |
68 | if threading.currentThread().getName() == reactor.thread_name:
69 | return closure()
70 | else:
71 | return threads.blockingCallFromThread(reactor, closure)
72 |
73 |
74 | #
75 | # Exceptions
76 | #
77 | class WpaSupplicantException(Exception):
78 | """Base class for all exception types raised from this lib"""
79 |
80 |
81 | class UnknownError(WpaSupplicantException):
82 | """Something failed for an unknown reason
83 |
84 | Possible sources:
85 | Basically everything in this library
86 | """
87 |
88 |
89 | class MethodTimeout(WpaSupplicantException):
90 | """Raised when a timeout occurs while waiting on DeferredQueue.get()
91 |
92 | Possible sources:
93 | :meth:`~Interface.scan` with block=True
94 | """
95 |
96 |
97 | class InterfaceExists(WpaSupplicantException):
98 | """wpa_supplicant already controls this interface
99 |
100 | Possible sources:
101 | :meth:`~WpaSupplicant.create_interface`
102 | """
103 |
104 |
105 | class InvalidArgs(WpaSupplicantException):
106 | """Invalid entries were found in the passed arguments
107 |
108 | Possible sources:
109 | :meth:`~WpaSupplicant.create_interface`
110 | :meth:`~Interface.scan`
111 | :meth:`~Interface.add_network`
112 | :meth:`~Interface.remove_network`
113 | :meth:`~Interface.select_network`
114 | """
115 |
116 |
117 | class InterfaceUnknown(WpaSupplicantException):
118 | """Object pointed by the path doesn't exist or doesn't represent an interface
119 |
120 | Possible sources:
121 | :meth:`~WpaSupplicant.get_interface`
122 | :meth:`~WpaSupplicant.remove_interface`
123 | """
124 |
125 |
126 | class NotConnected(WpaSupplicantException):
127 | """Interface is not connected to any network
128 |
129 | Possible sources:
130 | :meth:`~Interface.disconnect`
131 | """
132 |
133 |
134 | class NetworkUnknown(WpaSupplicantException):
135 | """A passed path doesn't point to any network object
136 |
137 | Possible sources:
138 | :meth:`~Interface.remove_network`
139 | :meth:`~Interface.select_network`
140 | """
141 |
142 |
143 | class ReactorNotRunning(WpaSupplicantException):
144 | """In order to connect to the WpaSupplicantDriver a reactor must be started"""
145 |
146 |
147 | # These are exceptions defined by the wpa_supplicant D-Bus API
148 | _REMOTE_EXCEPTIONS = {
149 | 'fi.w1.wpa_supplicant1.UnknownError': UnknownError,
150 | 'fi.w1.wpa_supplicant1.InvalidArgs': InvalidArgs,
151 | 'fi.w1.wpa_supplicant1.InterfaceExists': InterfaceExists,
152 | 'fi.w1.wpa_supplicant1.InterfaceUnknown': InterfaceUnknown,
153 | 'fi.w1.wpa_supplicant1.NotConnected': NotConnected,
154 | 'fi.w1.wpa_supplicant1.NetworkUnknown': NetworkUnknown,
155 | }
156 |
157 |
158 | #
159 | # Library Core
160 | #
161 | class TimeoutDeferredQueue(defer.DeferredQueue):
162 | """Deferred Queue implementation that provides .get() a timeout keyword arg"""
163 |
164 | def __init__(self, reactor):
165 | defer.DeferredQueue.__init__(self)
166 | self._reactor = reactor
167 |
168 | def _timed_out(self, deferred):
169 | """A timeout occurred while waiting for the deferred to be fired"""
170 |
171 | if not deferred.called:
172 | if deferred in self.waiting:
173 | self.waiting.remove(deferred)
174 | deferred.errback(MethodTimeout('Timed out waiting for response'))
175 |
176 | def _stop_timeout(self, result, call_id):
177 | """One of the callbacks in the deferreds callback-chain"""
178 |
179 | call_id.cancel()
180 | return result
181 |
182 | def get(self, timeout=None):
183 | """Synchronously get the result of :meth:`defer.DeferredQueue.get()`
184 |
185 | :param timeout: optional timeout parameter (integer # of sections)
186 | :returns: The result value that has been put onto the deferred queue
187 | :raises MethodTimeout: More time has elapsed than the specified `timeout`
188 | """
189 |
190 | deferred = defer.DeferredQueue.get(self)
191 |
192 | if timeout:
193 | call_id = self._reactor.callLater(timeout, self._timed_out, deferred)
194 | deferred.addCallback(self._stop_timeout, call_id)
195 |
196 | return _eval(deferred, self._reactor)
197 |
198 |
199 | class RemoteSignal(object):
200 | """Encapsulation of a signal object returned by signal registration methods"""
201 |
202 | def __init__(self, signal_name, callback, remote_object, remote_interface, reactor):
203 | self._signal_name = signal_name
204 | self._callback = callback
205 | self._remote_object = remote_object
206 | self._remote_interface = remote_interface
207 | self._reactor = reactor
208 | self._deferred = remote_object.notifyOnSignal(signal_name, callback, remote_interface)
209 |
210 | def get_signal_name(self):
211 | return self._signal_name
212 |
213 | def cancel(self):
214 | self._remote_object.cancelSignalNotification(_eval(self._deferred, self._reactor))
215 |
216 |
217 | class BaseIface(object):
218 | """Base class for all Interfaces defined in the wpa_supplicant D-Bus API"""
219 |
220 | def __init__(self, obj_path, conn, reactor):
221 | self._obj_path = obj_path
222 | self._conn = conn
223 | self._reactor = reactor
224 | self._introspection = _eval(self._conn.getRemoteObject(BUS_NAME, self._obj_path),
225 | self._reactor)
226 | self._without_introspection = _eval(self._conn.getRemoteObject(BUS_NAME, self._obj_path, self.iface),
227 | self._reactor)
228 |
229 | def get_path(self):
230 | """Return the full D-Bus object path that defines this interface
231 |
232 | :returns: String D-Bus object path, e.g. /w1/fi/wpa_supplicant1/Interfaces/0
233 | :rtype: str
234 | """
235 |
236 | return self._obj_path
237 |
238 | @_catch_remote_errors
239 | def _call_remote(self, method, *args):
240 | """Call a remote D-Bus interface method using introspection"""
241 |
242 | return _eval(self._introspection.callRemote(method, *args), self._reactor)
243 |
244 | @_catch_remote_errors
245 | def _call_remote_without_introspection(self, method, *args):
246 | """Call a remote D-Bus interface method without using introspection"""
247 |
248 | return _eval(self._without_introspection.callRemote(method, *args), self._reactor)
249 |
250 | @_catch_remote_errors
251 | def register_signal_once(self, signal_name):
252 | """Register a signal on an Interface object
253 |
254 | .. note::
255 |
256 | Since this de-registers itself after the signal fires the only real
257 | use for this function is code that wants to block and wait for a signal
258 | to fire, hence the queue it returns.
259 |
260 | :param signal_name: Case-sensitive name of the signal
261 | :returns: Object of type :class:`~EventSignal`
262 | """
263 |
264 | q = TimeoutDeferredQueue(self._reactor)
265 |
266 | signal = None
267 |
268 | def signal_callback(result):
269 | q.put(result)
270 | if signal is not None:
271 | signal.cancel()
272 |
273 | signal = RemoteSignal(signal_name,
274 | signal_callback,
275 | self._introspection,
276 | self.INTERFACE_PATH,
277 | self._reactor)
278 |
279 | return q
280 |
281 | @_catch_remote_errors
282 | def register_signal(self, signal_name, callback):
283 | """Register a callback when a signal fires
284 |
285 | :param signal_name: Case-sensitve name of the signal
286 | :param callback: Callable object
287 | :returns: ~`RemoteSignal`
288 | """
289 | if not callable(callback):
290 | raise WpaSupplicantException('callback must be callable')
291 |
292 | return RemoteSignal(signal_name,
293 | callback,
294 | self._introspection,
295 | self.INTERFACE_PATH,
296 | self._reactor)
297 |
298 | @_catch_remote_errors
299 | def get(self, property_name):
300 | """Get the value of a property defined by the interface
301 |
302 | :param property_name: Case-sensitive string of the property name
303 | :returns: Variant type of the properties value
304 | """
305 | return self._call_remote('Get', self.INTERFACE_PATH, property_name)
306 |
307 | @_catch_remote_errors
308 | def set(self, property_name, value):
309 | """Set the value of a property defined by the interface
310 |
311 | :param property_name: Case-sensitive string of the property name
312 | :param value: Variant type value to set for property
313 | :returns: `None`
314 | """
315 |
316 | logger.info('Setting `%s` -> `%s`', property_name, value)
317 | return self._call_remote('Set', self.INTERFACE_PATH, property_name, value)
318 |
319 |
320 | class WpaSupplicant(BaseIface):
321 | """Interface implemented by the main wpa_supplicant D-Bus object
322 |
323 | Registered in the bus as "fi.w1.wpa_supplicant1"
324 | """
325 |
326 | INTERFACE_PATH = 'fi.w1.wpa_supplicant1'
327 |
328 | iface = DBusInterface(
329 | INTERFACE_PATH,
330 | Method('CreateInterface', arguments='a{sv}', returns='o'),
331 | Method('GetInterface', arguments='s', returns='o'),
332 | Method('RemoveInterface', arguments='o'),
333 | Signal('InterfaceAdded', 'o,a{sv}'),
334 | Signal('InterfaceRemoved', 'o'),
335 | Signal('PropertiesChanged', 'a{sv}')
336 | )
337 |
338 | def __init__(self, *args, **kwargs):
339 | BaseIface.__init__(self, *args, **kwargs)
340 | self._interfaces_cache = dict()
341 |
342 | def __repr__(self):
343 | return str(self)
344 |
345 | def __str__(self):
346 | return "WpaSupplicant(Interfaces: %s)" % self.get_interfaces()
347 |
348 | #
349 | # Methods
350 | #
351 | def get_interface(self, interface_name):
352 | """Get D-Bus object related to an interface which wpa_supplicant already controls
353 |
354 | :returns: Interface object that implements the wpa_supplicant Interface API.
355 | :rtype: :class:`~Interface`
356 | :raises InterfaceUnknown: wpa_supplicant doesn't know anything about `interface_name`
357 | :raises UnknownError: An unknown error occurred
358 | """
359 |
360 | interface_path = self._call_remote_without_introspection('GetInterface',
361 | interface_name)
362 | interface = self._interfaces_cache.get(interface_path, None)
363 | if interface is not None:
364 | return interface
365 | else:
366 | interface = Interface(interface_path, self._conn, self._reactor)
367 | self._interfaces_cache[interface_path] = interface
368 | return interface
369 |
370 | def create_interface(self, interface_name, driver=None):
371 | """Registers a wireless interface in wpa_supplicant
372 |
373 | :returns: Interface object that implements the wpa_supplicant Interface API
374 | :raises InterfaceExists: The `interface_name` specified is already registered
375 | :raises UnknownError: An unknown error occurred
376 | """
377 |
378 | interface_path = self._call_remote_without_introspection('CreateInterface',
379 | {'Ifname': interface_name,
380 | 'Driver': driver})
381 | return Interface(interface_path, self._conn, self._reactor)
382 |
383 | def remove_interface(self, interface_path):
384 | """Deregisters a wireless interface from wpa_supplicant
385 |
386 | :param interface_path: D-Bus object path to the interface to be removed
387 | :returns: None
388 | :raises InterfaceUnknown: wpa_supplicant doesn't know anything about `interface_name`
389 | :raises UnknownError: An unknown error occurred
390 | """
391 |
392 | self._call_remote_without_introspection('RemoveInterface', interface_path)
393 |
394 | #
395 | # Properties
396 | #
397 | def get_debug_level(self):
398 | """Global wpa_supplicant debugging level
399 |
400 | :returns: Possible values: "msgdump" (verbose debugging)
401 | "debug" (debugging)
402 | "info" (informative)
403 | "warning" (warnings)
404 | "error" (errors)
405 | :rtype: str
406 | """
407 |
408 | return self.get('DebugLevel')
409 |
410 | def get_debug_timestamp(self):
411 | """ Determines if timestamps are shown in debug logs"""
412 |
413 | return self.get('DebugTimestamp')
414 |
415 | def get_debug_showkeys(self):
416 | """Determines if secrets are shown in debug logs"""
417 |
418 | return self.get('DebugShowKeys')
419 |
420 | def get_interfaces(self):
421 | """An array with paths to D-Bus objects representing controlled interfaces"""
422 |
423 | return self.get('Interfaces')
424 |
425 | def get_eap_methods(self):
426 | """An array with supported EAP methods names"""
427 |
428 | return self.get('EapMethods')
429 |
430 |
431 | class Interface(BaseIface):
432 | """Interface implemented by objects related to network interface added to wpa_supplicant"""
433 |
434 | INTERFACE_PATH = 'fi.w1.wpa_supplicant1.Interface'
435 |
436 | iface = DBusInterface(
437 | INTERFACE_PATH,
438 | Method('Scan', arguments='a{sv}'),
439 | Method('AddNetwork', arguments='a{sv}', returns='o'),
440 | Method('RemoveNetwork', arguments='o'),
441 | Method('SelectNetwork', arguments='o'),
442 | Method('Disconnect'),
443 | Method('SaveConfig'),
444 | Signal('ScanDone', 'b'),
445 | Signal('PropertiesChanged', 'a{sv}')
446 | )
447 |
448 | def __repr__(self):
449 | return str(self)
450 |
451 | def __str__(self):
452 | return "Interface(Path: %s, Name: %s, State: %s)" % (self.get_path(),
453 | self.get_ifname(),
454 | self.get_state())
455 |
456 | #
457 | # Methods
458 | #
459 | def scan(self, type='active', ssids=None, ies=None, channels=None, block=False):
460 | """Triggers a scan
461 |
462 | :returns: List of `BSS` objects if block=True else None
463 | :raises InvalidArgs: Invalid argument format
464 | :raises MethodTimeout: Scan has timed out (only if block=True)
465 | :raises UnknownError: An unknown error occurred
466 | """
467 |
468 | # TODO: Handle the other arguments
469 | scan_options = {
470 | 'Type': type
471 | }
472 |
473 | # If blocking, register for the ScanDone signal and return BSSs
474 | if block:
475 | deferred_queue = self.register_signal_once('ScanDone')
476 | self._call_remote('Scan', scan_options) # Trigger scan
477 | success = deferred_queue.get(timeout=10)
478 | if success:
479 | return [BSS(path, self._conn, self._reactor) for path in self.get_all_bss()]
480 | else:
481 | raise UnknownError('ScanDone signal received without success')
482 | else:
483 | self._call_remote('Scan', scan_options) # Trigger scan
484 |
485 | def add_network(self, network_cfg):
486 | """Adds a new network to the interface
487 |
488 | :param network_cfg: Dictionary of config, see wpa_supplicant.conf for k/v pairs
489 | :returns: `Network` object that was registered w/ wpa_supplicant
490 | :raises InvalidArgs: Invalid argument format
491 | :raises UnknownError: An unknown error occurred
492 | """
493 |
494 | network_path = self._call_remote('AddNetwork', network_cfg)
495 | return Network(network_path, self._conn, self._reactor)
496 |
497 | def remove_network(self, network_path):
498 | """Removes a configured network from the interface
499 |
500 | :param network_path: D-Bus object path to the desired network
501 | :returns: None
502 | :raises NetworkUnknown: The specified `network_path` is invalid
503 | :raises InvalidArgs: Invalid argument format
504 | :raises UnknownError: An unknown error occurred
505 | """
506 |
507 | self._call_remote('RemoveNetwork', network_path)
508 |
509 | def select_network(self, network_path):
510 | """Attempt association with a configured network
511 |
512 | :param network_path: D-Bus object path to the desired network
513 | :returns: None
514 | :raises NetworkUnknown: The specified `network_path` has not been added
515 | :raises InvalidArgs: Invalid argument format
516 | """
517 |
518 | self._call_remote('SelectNetwork', network_path)
519 |
520 | def disconnect_network(self):
521 | """Disassociates the interface from current network
522 |
523 | :returns: None
524 | :raises NotConnected: The interface is not currently connected to a network
525 | """
526 |
527 | self._call_remote('Disconnect')
528 |
529 | def save_config(self):
530 | """Save current configuration to disk
531 |
532 | :returns: None
533 | """
534 |
535 | self._call_remote('SaveConfig')
536 |
537 | #
538 | # Properties
539 | #
540 | def get_ifname(self):
541 | """Name of network interface controlled by the interface, e.g., wlan0"""
542 |
543 | return self.get('Ifname')
544 |
545 | def get_current_bss(self):
546 | """BSS object path which wpa_supplicant is associated with
547 |
548 | Returns "/" if is not associated at all
549 | """
550 |
551 | bss_path = self.get('CurrentBSS')
552 | if bss_path == '/' or bss_path is None:
553 | return None
554 | else:
555 | return BSS(bss_path, self._conn, self._reactor)
556 |
557 | def get_current_network(self):
558 | """The `Network` object which wpa_supplicant is associated with
559 |
560 | Returns `None` if is not associated at all
561 | """
562 |
563 | network_path = self.get('CurrentNetwork')
564 | if network_path == '/' or network_path is None:
565 | return None
566 | else:
567 | return Network(network_path, self._conn, self._reactor)
568 |
569 | def get_networks(self):
570 | """List of `Network` objects representing configured networks"""
571 |
572 | networks = list()
573 | paths = self.get('Networks')
574 | for network_path in paths:
575 | if network_path == '/':
576 | networks.append(None)
577 | else:
578 | networks.append(Network(network_path, self._conn, self._reactor))
579 | return networks
580 |
581 | def get_state(self):
582 | """A state of the interface.
583 |
584 | Possible values are: "disconnected"
585 | "inactive"
586 | "scanning"
587 | "authenticating"
588 | "associating"
589 | "associated"
590 | "4way_handshake"
591 | "group_handshake"
592 | "completed"
593 | "unknown"
594 | """
595 |
596 | return self.get('State')
597 |
598 | def get_scanning(self):
599 | """Determines if the interface is already scanning or not"""
600 |
601 | return self.get('Scanning')
602 |
603 | def get_scan_interval(self):
604 | """Time (in seconds) between scans for a suitable AP. Must be >= 0"""
605 |
606 | return self.get('ScanInterval')
607 |
608 | def get_fast_reauth(self):
609 | """Identical to fast_reauth entry in wpa_supplicant.conf"""
610 |
611 | return self.get('FastReauth')
612 |
613 | def get_all_bss(self):
614 | """List of D-Bus objects paths representing BSSs known to the interface"""
615 |
616 | return self.get('BSSs')
617 |
618 | def get_driver(self):
619 | """Name of driver used by the interface, e.g., nl80211"""
620 |
621 | return self.get('Driver')
622 |
623 | def get_country(self):
624 | """Identical to country entry in wpa_supplicant.conf"""
625 |
626 | return self.get('Country')
627 |
628 | def get_bridge_ifname(self):
629 | """Name of bridge network interface controlled by the interface, e.g., br0"""
630 |
631 | return self.get('BridgeIfname')
632 |
633 | def get_bss_expire_age(self):
634 | """Identical to bss_expiration_age entry in wpa_supplicant.conf file"""
635 |
636 | return self.get('BSSExpireAge')
637 |
638 | def get_bss_expire_count(self):
639 | """Identical to bss_expiration_scan_count entry in wpa_supplicant.conf file"""
640 |
641 | return self.get('BSSExpireCount')
642 |
643 | def get_ap_scan(self):
644 | """Identical to ap_scan entry in wpa_supplicant configuration file.
645 |
646 | Possible values are 0, 1 or 2.
647 | """
648 |
649 | return self.get('ApScan')
650 |
651 | def set_country(self, country_code):
652 | self.set('Country', country_code)
653 |
654 |
655 | class BSS(BaseIface):
656 | """Interface implemented by objects representing a scanned BSSs (scan results)"""
657 |
658 | INTERFACE_PATH = 'fi.w1.wpa_supplicant1.BSS'
659 |
660 | iface = DBusInterface(
661 | INTERFACE_PATH,
662 | Signal('PropertiesChanged', 'a{sv}')
663 | )
664 |
665 | def __repr__(self):
666 | return str(self)
667 |
668 | def __str__(self):
669 | return "BSS(Path: %s, SSID: %s, BSSID: %s, Signal: %sdBm)" % (self.get_path(),
670 | self.get_ssid(),
671 | self.get_bssid(),
672 | self.get_signal_dbm())
673 |
674 | def to_dict(self):
675 | """Dict representation of a BSS object"""
676 |
677 | elements = (
678 | ('ssid', self.get_ssid),
679 | ('rsn', self.get_rsn),
680 | ('channel', self.get_channel),
681 | ('privacy', self.get_privacy),
682 | ('wpa', self.get_wpa),
683 | ('signal_dbm', self.get_signal_dbm),
684 | ('signal_quality', self.get_signal_quality),
685 | ('network_type', self.get_network_type),
686 | ('privacy', self.get_privacy),
687 | )
688 | out = {}
689 | for k, v in elements:
690 | try:
691 | out[k] = v()
692 | except:
693 | logger.exception('Error while fetching BSS information')
694 | return out
695 |
696 | #
697 | # Properties
698 | #
699 | def get_channel(self):
700 | """Wi-Fi channel number (1-14)"""
701 | freq = self.get_frequency()
702 |
703 | if freq == 2484: # Handle channel 14
704 | return 14
705 | elif freq >= 2412 and freq <= 2472:
706 | return 1 + (freq - 2412) / 5
707 | elif freq >= 5180 and freq <= 5905:
708 | return 36 + (freq - 5180) / 5
709 | else:
710 | logger.warn('Unexpected frequency %s', freq)
711 | raise WpaSupplicantException('Unexpected frequency in WiFi connection.')
712 |
713 | def get_ssid(self):
714 | """SSID of the BSS in ASCII"""
715 |
716 | return "".join(chr(i) for i in self.get('SSID'))
717 |
718 | def get_bssid(self):
719 | """BSSID of the BSS as hex bytes delimited by a colon"""
720 |
721 | return ":".join(["{:02X}".format(i) for i in self.get('BSSID')])
722 |
723 | def get_frequency(self):
724 | """Frequency of the BSS in MHz"""
725 |
726 | return self.get('Frequency')
727 |
728 | def get_wpa(self):
729 | """WPA information of the BSS, empty dictionary indicates no WPA support
730 |
731 | Dictionaries are::
732 |
733 | {
734 | "KeyMgmt": ,
735 | "Pairwise": ,
736 | "Group": ,
737 | "MgmtGroup":
738 | }
739 | """
740 |
741 | return self.get('WPA')
742 |
743 | def get_rsn(self):
744 | """RSN/WPA2 information of the BSS, empty dictionary indicates no RSN support
745 |
746 | Dictionaries are::
747 |
748 | {
749 | "KeyMgmt": ,
750 | "Pairwise": ,
751 | "Group": ,
752 | "MgmtGroup": ,
753 | }
754 | """
755 |
756 | return self.get('RSN')
757 |
758 | def get_ies(self):
759 | """All IEs of the BSS as a chain of TLVs"""
760 |
761 | return self.get('IEs')
762 |
763 | def get_privacy(self):
764 | """Indicates if BSS supports privacy"""
765 |
766 | return self.get('Privacy')
767 |
768 | def get_mode(self):
769 | """Describes mode of the BSS
770 |
771 | Possible values are: "ad-hoc"
772 | "infrastructure"
773 |
774 | """
775 |
776 | return self.get('Mode')
777 |
778 | def get_rates(self):
779 | """Descending ordered array of rates supported by the BSS in bits per second"""
780 |
781 | return self.get('Rates')
782 |
783 | def get_signal_dbm(self):
784 | """Signal strength of the BSS in dBm"""
785 |
786 | return self.get('Signal')
787 |
788 | def get_signal_quality(self):
789 | """Signal strength of the BSS as a percentage (0-100)"""
790 |
791 | dbm = self.get_signal_dbm()
792 | if dbm <= -100:
793 | return 0
794 | elif dbm >= -50:
795 | return 100
796 | else:
797 | return 2 * (dbm + 100)
798 |
799 | def get_network_type(self):
800 | """Return the network type as a string
801 |
802 | Possible values are:
803 | 'WPA'
804 | 'WPA2'
805 | 'WEP'
806 | 'OPEN'
807 | """
808 |
809 | if self.get_privacy():
810 | rsn_key_mgmt = self.get_rsn().get('KeyMgmt')
811 | if rsn_key_mgmt:
812 | return 'WPA2'
813 |
814 | wpa_key_mgmt = self.get_wpa().get('KeyMgmt')
815 | if wpa_key_mgmt:
816 | return 'WPA'
817 |
818 | return 'WEP'
819 | else:
820 | return 'OPEN'
821 |
822 |
823 | class Network(BaseIface):
824 | """Interface implemented by objects representing configured networks"""
825 |
826 | INTERFACE_PATH = 'fi.w1.wpa_supplicant1.Network'
827 |
828 | iface = DBusInterface(
829 | INTERFACE_PATH,
830 | Signal('PropertiesChanged', 'a{sv}')
831 | )
832 |
833 | def __repr__(self):
834 | return str(self)
835 |
836 | def __str__(self):
837 | return "Network(Path: %s, Properties: %s)" % (self.get_path(),
838 | self.get_properties())
839 |
840 | #
841 | # Properties
842 | #
843 | def get_properties(self):
844 | """Properties of the configured network
845 |
846 | Dictionary contains entries from "network" block of wpa_supplicant.conf.
847 | All values are string type, e.g., frequency is "2437", not 2437.
848 | """
849 |
850 | network_properties = self.get('Properties')
851 | ssid = network_properties.get('ssid', '')
852 | if ssid:
853 | quote_chars = {'"', "'"}
854 | ssid = ssid[1:] if ssid[0] in quote_chars else ssid
855 | ssid = ssid[:-1] if ssid[-1] in quote_chars else ssid
856 | network_properties.update({
857 | 'ssid': ssid
858 | })
859 | return network_properties
860 |
861 | def get_enabled(self):
862 | """Determines if the configured network is enabled or not"""
863 |
864 | return self.get('Enabled')
865 |
866 |
867 | class WpaSupplicantDriver(object):
868 | """Driver object for starting, stopping and connecting to wpa_supplicant"""
869 |
870 | def __init__(self, reactor):
871 | self._reactor = reactor
872 |
873 | @_catch_remote_errors
874 | def connect(self):
875 | """Connect to wpa_supplicant over D-Bus
876 |
877 | :returns: Remote D-Bus proxy object of the root wpa_supplicant interface
878 | :rtype: :class:`~WpaSupplicant`
879 | """
880 |
881 | if not self._reactor.running:
882 | raise ReactorNotRunning('Twisted Reactor must be started (call .run())')
883 |
884 | @defer.inlineCallbacks
885 | def get_conn():
886 | self._reactor.thread_name = threading.currentThread().getName()
887 | conn = yield client.connect(self._reactor, busAddress='system')
888 | defer.returnValue(conn)
889 |
890 | conn = threads.blockingCallFromThread(self._reactor, get_conn)
891 | return WpaSupplicant('/fi/w1/wpa_supplicant1', conn, self._reactor, )
892 |
--------------------------------------------------------------------------------