├── tests
├── __init__.py
└── pywifi_test.py
├── .gitignore
├── CONTRIBUTORS.md
├── pywifi
├── __init__.py
├── const.py
├── profile.py
├── wifi.py
├── iface.py
├── _wifiutil_linux.py
├── .pylintrc
└── _wifiutil_win.py
├── .travis.yml
├── LICENSE
├── setup.py
├── README.md
└── DOC.md
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | build/
3 | dist/
4 | *.egg-info/
5 | *.pyc
6 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | pywifi is written and maintained by Jiang Sheng-Jhih and
2 | various contributors
3 |
4 | # Contributors
5 |
6 | - Thomas Bell https://github.com/bell345
7 | - William Gaylord https://github.com/chibill
8 | - Tucker Kern https://github.com/mill1000
9 | - oycillessen https://github.com/oycillessen
10 |
--------------------------------------------------------------------------------
/pywifi/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """
5 | pywifi - a cross-platform wifi library.
6 |
7 |
8 | This library is made for manipulating wifi device on varient platforms.
9 | """
10 |
11 | import logging
12 |
13 | from . import const
14 | from .profile import Profile
15 | from .wifi import PyWiFi
16 |
17 |
18 | def set_loglevel(level=logging.NOTSET):
19 |
20 | format_pattern = "%(name)s %(asctime)s %(levelname)s %(message)s"
21 | logging.basicConfig(format=format_pattern)
22 | logger = logging.getLogger('pywifi')
23 | logger.setLevel(level)
24 |
25 |
26 | set_loglevel()
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | install:
3 | pip install .
4 | jobs:
5 | include:
6 | - stage: test
7 | script: pytest -p no:cacheprovider
8 | python: '2.7'
9 | - stage: test
10 | script: pytest -p no:cacheprovider
11 | python: '3.4'
12 | - stage: test
13 | script: pytest -p no:cacheprovider
14 | python: '3.5'
15 | - stage: test
16 | script: pytest -p no:cacheprovider
17 | python: '3.6'
18 | - stage: test
19 | script: pytest -p no:cacheprovider
20 | python: '3.7'
21 | dist: xential
22 |
23 |
--------------------------------------------------------------------------------
/pywifi/const.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """Constants used in pywifi library define here."""
5 |
6 | # Define interface status.
7 | IFACE_DISCONNECTED = 0
8 | IFACE_SCANNING = 1
9 | IFACE_INACTIVE = 2
10 | IFACE_CONNECTING = 3
11 | IFACE_CONNECTED = 4
12 |
13 | # Define auth algorithms.
14 | AUTH_ALG_OPEN = 0
15 | AUTH_ALG_SHARED = 1
16 |
17 | # Define auth key mgmt types.
18 | AKM_TYPE_NONE = 0
19 | AKM_TYPE_WPA = 1
20 | AKM_TYPE_WPAPSK = 2
21 | AKM_TYPE_WPA2 = 3
22 | AKM_TYPE_WPA2PSK = 4
23 | AKM_TYPE_UNKNOWN = 5
24 |
25 | # Define ciphers.
26 | CIPHER_TYPE_NONE = 0
27 | CIPHER_TYPE_WEP = 1
28 | CIPHER_TYPE_TKIP = 2
29 | CIPHER_TYPE_CCMP = 3
30 | CIPHER_TYPE_UNKNOWN = 4
31 |
32 | KEY_TYPE_NETWORKKEY = 0
33 | KEY_TYPE_PASSPHRASE = 1
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Jiang Sheng-Jhih
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/pywifi/profile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """Define WiFi Profile."""
5 |
6 | from .const import *
7 |
8 |
9 | class Profile():
10 |
11 | def __init__(self):
12 |
13 | self.id = 0
14 | self.auth = AUTH_ALG_OPEN
15 | self.akm = [AKM_TYPE_NONE]
16 | self.cipher = CIPHER_TYPE_NONE
17 | self.ssid = None
18 | self.bssid = None
19 | self.key = None
20 |
21 | def process_akm(self):
22 |
23 | if len(self.akm) > 1:
24 | self.akm = self.akm[-1:]
25 |
26 | def __eq__(self, profile):
27 |
28 | if profile.ssid:
29 | if profile.ssid != self.ssid:
30 | return False
31 |
32 | if profile.bssid:
33 | if profile.bssid != self.bssid:
34 | return False
35 |
36 | if profile.auth:
37 | if profile.auth!= self.auth:
38 | return False
39 |
40 | if profile.cipher:
41 | if profile.cipher != self.cipher:
42 | return False
43 |
44 | if profile.akm:
45 | if set(profile.akm).isdisjoint(set(self.akm)):
46 | return False
47 |
48 | return True
49 |
--------------------------------------------------------------------------------
/pywifi/wifi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """
5 | wifi - entry module of pywifi libary.
6 |
7 | We put the fundamental implementation of wifi functions in each OS
8 | folder (e.g. linux, win, and osx). So, PyWiFi class is just the
9 | entry point to manipulate wifi devices.
10 | """
11 |
12 | import platform
13 | import logging
14 |
15 | from .iface import Interface
16 |
17 |
18 | if platform.system().lower() == 'windows':
19 | from . import _wifiutil_win as wifiutil
20 | elif platform.system().lower() == 'linux':
21 | from . import _wifiutil_linux as wifiutil
22 | else:
23 | raise NotImplementedError
24 |
25 |
26 | class PyWiFi:
27 | """PyWiFi provides operations to manipulate wifi devices."""
28 |
29 | _ifaces = []
30 | _logger = None
31 |
32 | def __init__(self):
33 |
34 | self._logger = logging.getLogger('pywifi')
35 |
36 | def interfaces(self):
37 | """Collect the available wlan interfaces."""
38 |
39 | self._ifaces = []
40 | wifi_ctrl = wifiutil.WifiUtil()
41 |
42 | for interface in wifi_ctrl.interfaces():
43 | iface = Interface(interface)
44 | self._ifaces.append(iface)
45 | self._logger.info("Get interface: %s", iface.name())
46 |
47 | if not self._ifaces:
48 | self._logger.error("Can't get wifi interface")
49 |
50 | return self._ifaces
51 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import platform
4 | import sys
5 | import os
6 | from setuptools import setup, find_packages
7 |
8 | # 'setup.py publish' shortcut.
9 | if sys.argv[-1].startswith('publish'):
10 | os.system('rm -rf build dist pywifi.egg-info')
11 | os.system('python setup.py sdist bdist_wheel')
12 | if 'test' in sys.argv[-1]:
13 | os.system('twine upload --repository-url '\
14 | 'https://test.pypi.org/legacy/ dist/*')
15 | else:
16 | os.system('twine upload dist/*')
17 | sys.exit()
18 |
19 | with open('README.md', 'r') as f:
20 | readme = f.read()
21 |
22 | if platform.system().lower() == 'windows':
23 | requires = ['comtypes']
24 | else:
25 | requires = []
26 |
27 | test_requires = [
28 | 'pytest>=3.3.0'
29 | ]
30 |
31 | setup(
32 | name='pywifi',
33 | version='1.1.12',
34 | author='Jiang Sheng-Jhih',
35 | author_email='shengjhih@gmail.com',
36 | description="A cross-platform module for manipulating WiFi devices.",
37 | long_description=readme,
38 | long_description_content_type="text/markdown",
39 | packages=find_packages(),
40 | install_requires=requires,
41 | python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
42 | url='https://github.com/awkman/pywifi',
43 | license='MIT',
44 | download_url='https://github.com/awkman/pywifi/archive/master.zip',
45 | classifiers=[
46 | 'Intended Audience :: Developers',
47 | 'Topic :: Utilities',
48 | 'License :: OSI Approved :: MIT License',
49 | 'Programming Language :: Python :: 2.7',
50 | 'Programming Language :: Python :: 3.4',
51 | 'Programming Language :: Python :: 3.5',
52 | 'Programming Language :: Python :: 3.6',
53 | 'Programming Language :: Python :: 3.7',
54 | ],
55 | keywords=['wifi', 'wireless', 'Linux', 'Windows'],
56 | )
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pywifi
2 |
3 | 
4 | [](https://travis-ci.com/awkman/pywifi)
5 | [](https://badge.fury.io/py/pywifi)
6 |
7 | pywifi provides a cross-platform Python module for manipulating wireless
8 | interfaces.
9 |
10 | * Easy to use
11 | * Supports Windows and Linux
12 |
13 | ## Prerequisites
14 |
15 | On Linux, you will need to run wpa_supplicant to manipulate the wifi devices,
16 | and then pywifi can communicate with wpa_supplicant through socket.
17 |
18 | On Windows, the [Native Wifi] component comes with Windows versions greater
19 | than Windows XP SP2.
20 |
21 | ## Installation
22 |
23 | After installing the prerequisites listed above for your platform, you can
24 | use pip to install from source:
25 |
26 | cd pywifi/
27 | pip install .
28 |
29 | ## Documentation
30 |
31 | For the details of pywifi, please refer to [Documentation].
32 |
33 | ## Example
34 |
35 | import time
36 | import pywifi
37 | from pywifi import const
38 |
39 | wifi = pywifi.PyWiFi()
40 |
41 | iface = wifi.interfaces()[0]
42 |
43 | iface.disconnect()
44 | time.sleep(1)
45 | assert iface.status() in\
46 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
47 |
48 | profile = pywifi.Profile()
49 | profile.ssid = 'testap'
50 | profile.auth = const.AUTH_ALG_OPEN
51 | profile.akm.append(const.AKM_TYPE_WPA2PSK)
52 | profile.cipher = const.CIPHER_TYPE_CCMP
53 | profile.key = '12345678'
54 |
55 | iface.remove_all_network_profiles()
56 | tmp_profile = iface.add_network_profile(profile)
57 |
58 | iface.connect(tmp_profile)
59 | time.sleep(30)
60 | assert iface.status() == const.IFACE_CONNECTED
61 |
62 | iface.disconnect()
63 | time.sleep(1)
64 | assert iface.status() in\
65 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
66 |
67 | (C) Jiang Sheng-Jhih 2019, [MIT License].
68 |
69 | [Native Wifi]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms706556.aspx
70 | [MIT License]: https://opensource.org/licenses/MIT
71 | [Documentation]: https://github.com/awkman/pywifi/blob/master/DOC.md
72 |
--------------------------------------------------------------------------------
/pywifi/iface.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """Implement Interface for manipulating wifi devies."""
5 |
6 | import platform
7 | import logging
8 |
9 |
10 | if platform.system().lower() == 'windows':
11 | from . import _wifiutil_win as wifiutil
12 | elif platform.system().lower() == 'linux':
13 | from . import _wifiutil_linux as wifiutil
14 | else:
15 | raise NotImplementedError
16 |
17 |
18 | class Interface:
19 | """Interface provides methods for manipulating wifi devices."""
20 |
21 | """
22 | For encapsulating OS dependent behavior, we declare _raw_obj here for
23 | storing some common attribute (e.g. name) and os attributes (e.g. dbus
24 | objects for linux)
25 | """
26 | _raw_obj = {}
27 | _wifi_ctrl = {}
28 | _logger = None
29 |
30 | def __init__(self, raw_obj):
31 |
32 | self._raw_obj = raw_obj
33 | self._wifi_ctrl = wifiutil.WifiUtil()
34 | self._logger = logging.getLogger('pywifi')
35 |
36 | def name(self):
37 | """"Get the name of the wifi interfacce."""
38 |
39 | return self._raw_obj['name']
40 |
41 | def scan(self):
42 | """Trigger the wifi interface to scan."""
43 |
44 | self._logger.info("iface '%s' scans", self.name())
45 |
46 | self._wifi_ctrl.scan(self._raw_obj)
47 |
48 | def scan_results(self):
49 | """Return the scan result."""
50 |
51 | bsses = self._wifi_ctrl.scan_results(self._raw_obj)
52 |
53 | if self._logger.isEnabledFor(logging.INFO):
54 | for bss in bsses:
55 | self._logger.info("Find bss:")
56 | self._logger.info("\tbssid: %s", bss.bssid)
57 | self._logger.info("\tssid: %s", bss.ssid)
58 | self._logger.info("\tfreq: %d", bss.freq)
59 | self._logger.info("\tauth: %s", bss.auth)
60 | self._logger.info("\takm: %s", bss.akm)
61 | self._logger.info("\tsignal: %d", bss.signal)
62 |
63 | return bsses
64 |
65 | def add_network_profile(self, params):
66 | """Add the info of the AP for connecting afterward."""
67 |
68 | return self._wifi_ctrl.add_network_profile(self._raw_obj, params)
69 |
70 | def remove_network_profile(self, params):
71 | """Remove the specified AP settings."""
72 |
73 | self._wifi_ctrl.remove_network_profile(self._raw_obj, params)
74 |
75 | def remove_all_network_profiles(self):
76 | """Remove all the AP settings."""
77 |
78 | self._wifi_ctrl.remove_all_network_profiles(self._raw_obj)
79 |
80 | def network_profiles(self):
81 | """Get all the AP profiles."""
82 |
83 | profiles = self._wifi_ctrl.network_profiles(self._raw_obj)
84 |
85 | if self._logger.isEnabledFor(logging.INFO):
86 | for profile in profiles:
87 | self._logger.info("Get profile:")
88 | self._logger.info("\tssid: %s", profile.ssid)
89 | self._logger.info("\tauth: %s", profile.auth)
90 | self._logger.info("\takm: %s", profile.akm)
91 | self._logger.info("\tcipher: %s", profile.cipher)
92 |
93 | return profiles
94 |
95 | def connect(self, params):
96 | """Connect to the specified AP."""
97 |
98 | self._logger.info("iface '%s' connects to AP: '%s'",
99 | self.name(), params.ssid)
100 |
101 | self._wifi_ctrl.connect(self._raw_obj, params)
102 |
103 | def disconnect(self):
104 | """Disconnect from the specified AP."""
105 |
106 | self._logger.info("iface '%s' disconnects", self.name())
107 |
108 | self._wifi_ctrl.disconnect(self._raw_obj)
109 |
110 | def status(self):
111 | """Get the status of the wifi interface."""
112 |
113 | return self._wifi_ctrl.status(self._raw_obj)
114 |
--------------------------------------------------------------------------------
/DOC.md:
--------------------------------------------------------------------------------
1 |
2 | # Documentation
3 |
4 | ## Constants
5 |
6 | Following constatns are defined in pywifi.
7 |
8 | Before using the constants, please remember to ```import pywifi```.
9 |
10 | ### Interface Status
11 |
12 | ```Interface.status()``` will return one of the status code below.
13 |
14 | ```
15 | const.IFACE_DISCONNECTED
16 | const.IFACE_SCANNING
17 | const.IFACE_INACTIVE
18 | const.IFACE_CONNECTING
19 | const.IFACE_CONNECTED
20 | ```
21 |
22 | ### Authentication Algorithms
23 |
24 | Authentication algorithm should be assined to a *Profile*.
25 | For normal case, almost all the APs use *open* algorithm.
26 |
27 | ```
28 | const.AUTH_OPEN
29 | const.AUTH_SHARED
30 | ```
31 |
32 | ### Key Management Type
33 |
34 | The key management type should be assigned to a *Profile*.
35 |
36 | For normal APs, if
37 | - an AP is no security setting, set the profile akm as ```AKM_TYPE_NONE```.
38 | - an AP is in WPA mode, set the profile akm as ```AKM_TYUPE_WPAPSK```.
39 | - an AP is in WPA2 mode, set the profile akm as ```AKM_TYUPE_WPA2PSK```.
40 |
41 | ```AKM_TYPE_WPA``` and ```AKM_TYPE_WPA2``` are used by the enterprise APs.
42 |
43 | ```
44 | const.AKM_TYPE_NONE
45 | const.AKM_TYPE_WPA
46 | const.AKM_TYPE_WPAPSK
47 | const.AKM_TYPE_WPA2
48 | const.AKM_TYPE_WPA2PSK
49 | ```
50 |
51 | ### Cipher Types
52 |
53 | The cipher type should be set to the *Profile* if the akm is not ```AKM_TYPE_NONE```.
54 | You can refer to the setting of the AP you want to connect to.
55 |
56 | ```
57 | const.CIPHER_TYPE_NONE
58 | const.CIPHER_TYPE_WEP
59 | const.CIPHER_TYPE_TKIP
60 | const.CIPHER_TYPE_CCMP
61 | ```
62 |
63 | ## Network Profile
64 |
65 | A **Profile** is the settings of the AP we want to connect to.
66 | The fields of an profile:
67 |
68 | - ```ssid``` - The ssid of the AP.
69 | - ```auth``` - The authentication algorithm of the AP.
70 | - ```akm``` - The key management type of the AP.
71 | - ```cipher``` - The cipher type of the AP.
72 | - ```key``` *(optinoal)* - The key of the AP.
73 | This should be set if the cipher is not ```CIPHER_TYPE_NONE```.
74 |
75 | Example:
76 |
77 | ```
78 | import pywifi
79 |
80 | profile = pywifi.Profile()
81 | profile.ssid = 'testap'
82 | profile.auth = const.AUTH_ALG_OPEN
83 | profile.akm.append(const.AKM_TYPE_WPA2PSK)
84 | profile.cipher = const.CIPHER_TYPE_CCMP
85 | profile.key = '12345678'
86 |
87 | wifi = pywifi.PyWiFi()
88 | iface = wifi.interfaces()[0]
89 | profile = iface.add_network_profile(profile)
90 | iface.connect(profile)
91 | ```
92 |
93 | ## Interface
94 |
95 | An **Interface** means the Wi-Fi interface which we use to perform
96 | Wi-Fi operations (e.g. scan, connect, disconnect, ...).
97 |
98 | ### Get interface information
99 |
100 | In general, there will be only one Wi-Fi interface in the platform.
101 | Thus, use index *0* to obtain the Wi-Fi interface.
102 |
103 | ```
104 | import pywifi
105 |
106 | wifi = pywifi.PyWiFi()
107 | iface = wifi.interfaces()[0]
108 | ```
109 |
110 | ### Interface.name()
111 |
112 | Get the name of the Wi-Fi interface.
113 |
114 | ### Interface.scan()
115 |
116 | Trigger the interface to scan APs.
117 |
118 | ### Interface.scan_results()
119 |
120 | Obtain the results of the previous triggerred scan.
121 | A **Profile** list will be returned.
122 |
123 | *Note.* Because the scan time for each Wi-Fi interface is variant.
124 | It is safer to call ```scan_results()``` 2 ~ 8 seconds later after
125 | calling ```scan()```.
126 |
127 | ### Interface.add_network_profile(*profile*)
128 |
129 | Add the AP profile for connecting to later.
130 |
131 | ### Interface.remove_all_network_profiles()
132 |
133 | Remove all the AP profiles.
134 |
135 | ### Interface.network_profiles()
136 |
137 | Obtain all the saved AP profiles by returning a **Profile** list.
138 |
139 | ### Interface.connect(*profile*)
140 |
141 | Connect to the specified AP by the given *profile*.
142 | *Note.* As current design, ```add_network_profile(profile)``` should be
143 | called before ```connect(profile)``` is called.
144 |
145 | ### Interface.disconnect()
146 |
147 | Disconnect current AP connection.
148 |
149 | ### Interface.status()
150 |
151 | Get the status of current status.
152 |
153 | (C) Jiang Sheng-Jhih 2017, [MIT License].
154 |
--------------------------------------------------------------------------------
/pywifi/_wifiutil_linux.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """Implementations of wifi functions of Linux."""
5 |
6 | import logging
7 | import socket
8 | import stat
9 | import os
10 |
11 | from .const import *
12 | from .profile import Profile
13 |
14 | CTRL_IFACE_DIR = '/var/run/wpa_supplicant'
15 | CTRL_IFACE_RETRY = 3
16 | REPLY_SIZE = 4096
17 |
18 | status_dict = {
19 | 'completed': IFACE_CONNECTED,
20 | 'inactive': IFACE_INACTIVE,
21 | 'authenticating': IFACE_CONNECTING,
22 | 'associating': IFACE_CONNECTING,
23 | 'associated': IFACE_CONNECTING,
24 | '4way_handshake': IFACE_CONNECTING,
25 | 'group_handshake': IFACE_CONNECTING,
26 | 'interface_disabled': IFACE_INACTIVE,
27 | 'disconnected': IFACE_DISCONNECTED,
28 | 'scanning': IFACE_SCANNING
29 | }
30 |
31 | key_mgmt_to_str = {
32 | AKM_TYPE_WPA: 'WPA-EAP',
33 | AKM_TYPE_WPAPSK: 'WPA-PSK',
34 | AKM_TYPE_WPA2: 'WPA-EAP',
35 | AKM_TYPE_WPA2PSK: 'WPA-PSK'
36 | }
37 |
38 | key_mgmt_to_proto_str = {
39 | AKM_TYPE_WPA: 'WPA',
40 | AKM_TYPE_WPAPSK: 'WPA',
41 | AKM_TYPE_WPA2: 'RSN',
42 | AKM_TYPE_WPA2PSK: 'RSN'
43 | }
44 |
45 | proto_to_key_mgmt_id = {
46 | 'WPA': AKM_TYPE_WPAPSK,
47 | 'RSN': AKM_TYPE_WPA2PSK
48 | }
49 |
50 | cipher_str_to_value = {
51 | 'TKIP': CIPHER_TYPE_TKIP,
52 | 'CCMP': CIPHER_TYPE_CCMP,
53 | }
54 |
55 | class WifiUtil():
56 | """WifiUtil implements the wifi functions in Linux."""
57 |
58 | _connections = {}
59 | _logger = logging.getLogger('pywifi')
60 |
61 | def scan(self, obj):
62 | """Trigger the wifi interface to scan."""
63 |
64 | self._send_cmd_to_wpas(obj['name'], 'SCAN')
65 |
66 | def scan_results(self, obj):
67 | """Get the AP list after scanning."""
68 |
69 | bsses = []
70 | bsses_summary = self._send_cmd_to_wpas(obj['name'], 'SCAN_RESULTS', True)
71 | bsses_summary = bsses_summary[:-1].split('\n')
72 | if len(bsses_summary) == 1:
73 | return bsses
74 |
75 | for l in bsses_summary[1:]:
76 | values = l.split('\t')
77 | bss = Profile()
78 | bss.bssid = values[0]
79 | bss.freq = int(values[1])
80 | bss.signal = int(values[2])
81 | bss.ssid = values[4]
82 | bss.akm = []
83 | if 'WPA-PSK' in values[3]:
84 | bss.akm.append(AKM_TYPE_WPAPSK)
85 | if 'WPA2-PSK' in values[3]:
86 | bss.akm.append(AKM_TYPE_WPA2PSK)
87 | if 'WPA-EAP' in values[3]:
88 | bss.akm.append(AKM_TYPE_WPA)
89 | if 'WPA2-EAP' in values[3]:
90 | bss.akm.append(AKM_TYPE_WPA2)
91 |
92 | bss.auth = AUTH_ALG_OPEN
93 |
94 | bsses.append(bss)
95 |
96 | return bsses
97 |
98 | def connect(self, obj, network):
99 | """Connect to the specified AP."""
100 |
101 | network_summary = self._send_cmd_to_wpas(
102 | obj['name'],
103 | 'LIST_NETWORKS',
104 | True)
105 | network_summary = network_summary[:-1].split('\n')
106 | if len(network_summary) == 1:
107 | return networks
108 |
109 | for l in network_summary[1:]:
110 | values = l.split('\t')
111 | if values[1] == network.ssid:
112 | network_summary = self._send_cmd_to_wpas(
113 | obj['name'],
114 | 'SELECT_NETWORK {}'.format(values[0]),
115 | True)
116 |
117 | def disconnect(self, obj):
118 | """Disconnect to the specified AP."""
119 |
120 | self._send_cmd_to_wpas(obj['name'], 'DISCONNECT')
121 |
122 | def add_network_profile(self, obj, params):
123 | """Add an AP profile for connecting to afterward."""
124 |
125 | network_id = self._send_cmd_to_wpas(obj['name'], 'ADD_NETWORK', True)
126 | network_id = network_id.strip()
127 |
128 | params.process_akm()
129 |
130 | self._send_cmd_to_wpas(
131 | obj['name'],
132 | 'SET_NETWORK {} ssid \"{}\"'.format(network_id, params.ssid))
133 |
134 | key_mgmt = ''
135 | if params.akm[-1] in [AKM_TYPE_WPAPSK, AKM_TYPE_WPA2PSK]:
136 | key_mgmt = 'WPA-PSK'
137 | elif params.akm[-1] in [AKM_TYPE_WPA, AKM_TYPE_WPA2]:
138 | key_mgmt = 'WPA-EAP'
139 | else:
140 | key_mgmt = 'NONE'
141 |
142 | if key_mgmt:
143 | self._send_cmd_to_wpas(
144 | obj['name'],
145 | 'SET_NETWORK {} key_mgmt {}'.format(
146 | network_id,
147 | key_mgmt))
148 |
149 | proto = ''
150 | if params.akm[-1] in [AKM_TYPE_WPAPSK, AKM_TYPE_WPA]:
151 | proto = 'WPA'
152 | elif params.akm[-1] in [AKM_TYPE_WPA2PSK, AKM_TYPE_WPA2]:
153 | proto = 'RSN'
154 |
155 | if proto:
156 | self._send_cmd_to_wpas(
157 | obj['name'],
158 | 'SET_NETWORK {} proto {}'.format(
159 | network_id,
160 | proto))
161 |
162 | if params.akm[-1] in [AKM_TYPE_WPAPSK, AKM_TYPE_WPA2PSK]:
163 | self._send_cmd_to_wpas(
164 | obj['name'],
165 | 'SET_NETWORK {} psk \"{}\"'.format(network_id, params.key))
166 |
167 | return params
168 |
169 | def network_profiles(self, obj):
170 | """Get AP profiles."""
171 |
172 | networks = []
173 | network_ids = []
174 | network_summary = self._send_cmd_to_wpas(
175 | obj['name'],
176 | 'LIST_NETWORKS',
177 | True)
178 | network_summary = network_summary[:-1].split('\n')
179 | if len(network_summary) == 1:
180 | return networks
181 |
182 | for l in network_summary[1:]:
183 | network_ids.append(l.split()[0])
184 |
185 | for network_id in network_ids:
186 | network = Profile()
187 |
188 | network.id = network_id
189 |
190 | ssid = self._send_cmd_to_wpas(
191 | obj['name'],
192 | 'GET_NETWORK {} ssid'.format(network_id), True)
193 | if ssid.upper().startswith('FAIL'):
194 | continue
195 | else:
196 | network.ssid = ssid[1:-1]
197 |
198 | key_mgmt = self._send_cmd_to_wpas(
199 | obj['name'],
200 | 'GET_NETWORK {} key_mgmt'.format(network_id),
201 | True)
202 |
203 | network.akm = []
204 | if key_mgmt.upper().startswith('FAIL'):
205 | continue
206 | else:
207 | if key_mgmt.upper() in ['WPA-PSK']:
208 | proto = self._send_cmd_to_wpas(
209 | obj['name'],
210 | 'GET_NETWORK {} proto'.format(network_id),
211 | True)
212 |
213 | if proto.upper() == 'RSN':
214 | network.akm.append(AKM_TYPE_WPA2PSK)
215 | else:
216 | network.akm.append(AKM_TYPE_WPAPSK)
217 | elif key_mgmt.upper() in ['WPA-EAP']:
218 | proto = self._send_cmd_to_wpas(
219 | obj['name'],
220 | 'GET_NETWORK {} proto'.format(network_id),
221 | True)
222 |
223 | if proto.upper() == 'RSN':
224 | network.akm.append(AKM_TYPE_WPA2)
225 | else:
226 | network.akm.append(AKM_TYPE_WPA)
227 |
228 | ciphers = self._send_cmd_to_wpas(
229 | obj['name'],
230 | 'GET_NETWORK {} pairwise'.format(network_id),
231 | True).split(' ')
232 |
233 | if ciphers[0].upper().startswith('FAIL'):
234 | continue
235 | else:
236 | # Assume the possible ciphers TKIP and CCMP
237 | if len(ciphers) == 1:
238 | network.cipher = cipher_str_to_value(ciphers[0].upper())
239 | elif 'CCMP' in ciphers:
240 | network.cipher = CIPHER_TYPE_CCMP
241 |
242 | networks.append(network)
243 |
244 | return networks
245 |
246 | def remove_network_profile(self, obj, params):
247 | """Remove the specified AP profiles"""
248 |
249 | network_id = -1
250 | profiles = self.network_profiles(obj)
251 |
252 | for profile in profiles:
253 | if profile == params:
254 | network_id = profile.id
255 |
256 | if network_id != -1:
257 | self._send_cmd_to_wpas(obj['name'],
258 | 'REMOVE_NETWORK {}'.format(network_id))
259 |
260 | def remove_all_network_profiles(self, obj):
261 | """Remove all the AP profiles."""
262 |
263 | self._send_cmd_to_wpas(obj['name'], 'REMOVE_NETWORK all')
264 |
265 | def status(self, obj):
266 | """Get the wifi interface status."""
267 |
268 | reply = self._send_cmd_to_wpas(obj['name'], 'STATUS', True)
269 | result = reply.split('\n')
270 |
271 | status = ''
272 | for l in result:
273 | if l.startswith('wpa_state='):
274 | status = l[10:]
275 | return status_dict[status.lower()]
276 |
277 | def interfaces(self):
278 | """Get the wifi interface lists."""
279 |
280 | ifaces = []
281 | for f in sorted(os.listdir(CTRL_IFACE_DIR)):
282 | sock_file = '/'.join([CTRL_IFACE_DIR, f])
283 | mode = os.stat(sock_file).st_mode
284 | if stat.S_ISSOCK(mode):
285 | iface = {}
286 | iface['name'] = f
287 | ifaces.append(iface)
288 | self._connect_to_wpa_s(f)
289 |
290 | return ifaces
291 |
292 | def _connect_to_wpa_s(self, iface):
293 |
294 | ctrl_iface = '/'.join([CTRL_IFACE_DIR, iface])
295 | if ctrl_iface in self._connections:
296 | self._logger.info(
297 | "Connection for iface '%s' aleady existed!",
298 | iface)
299 |
300 | sock_file = '{}/{}_{}'.format('/tmp', 'pywifi', iface)
301 | self._remove_existed_sock(sock_file)
302 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
303 | sock.bind(sock_file)
304 | sock.connect(ctrl_iface)
305 |
306 | send_len = sock.send(b'PING')
307 | retry = CTRL_IFACE_RETRY
308 | while retry >= 0:
309 | reply = sock.recv(REPLY_SIZE)
310 | if reply == b'':
311 | self._logger.error("Connection to '%s' is broken!", iface_ctrl)
312 | break
313 |
314 | if reply.startswith(b'PONG'):
315 | self._logger.info(
316 | "Connect to sock '%s' successfully!", ctrl_iface)
317 | self._connections[iface] = {
318 | 'sock': sock,
319 | 'sock_file': sock_file,
320 | 'ctrl_iface': ctrl_iface
321 | }
322 | break
323 | retry -= 1
324 |
325 | def _remove_existed_sock(self, sock_file):
326 |
327 | if os.path.exists(sock_file):
328 | mode = os.stat(sock_file).st_mode
329 | if stat.S_ISSOCK(mode):
330 | os.remove(sock_file)
331 |
332 | def _send_cmd_to_wpas(self, iface, cmd, get_reply=False):
333 |
334 | if 'psk' not in cmd:
335 | self._logger.info("Send cmd '%s' to wpa_s", cmd)
336 | sock = self._connections[iface]['sock']
337 |
338 | sock.send(bytearray(cmd, 'utf-8'))
339 | reply = sock.recv(REPLY_SIZE)
340 | if get_reply:
341 | return reply.decode('utf-8')
342 |
343 | if reply != b'OK\n':
344 | self._logger.error(
345 | "Unexpected resp '%s' for Command '%s'",
346 | reply.decode('utf-8'),
347 | cmd)
348 |
--------------------------------------------------------------------------------
/tests/pywifi_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """
5 | Test cases for pywifi.
6 | """
7 |
8 | import pytest
9 | import sys
10 | import time
11 | import platform
12 | import logging
13 |
14 | # For mocking
15 | import os
16 | import stat
17 | import socket
18 |
19 | import pywifi
20 | from pywifi import const
21 |
22 | pywifi.set_loglevel(logging.INFO)
23 |
24 |
25 | class SockMock:
26 |
27 | default_scan_results =\
28 | "bssid / frequency / signal level / flags / ssid\n"\
29 | "14:4d:67:14:1e:44\t2412\t-67\t[WPA2-PSK-CCMP][WPS][ESS]\tTOTOLINK N302RE\n"\
30 | "ac:9e:17:31:85:fc\t2437\t-63\t[WPA2-PSK-CCMP][WPS][ESS]\tEvan\n"\
31 | "0c:80:63:2b:0d:a8\t2417\t-79\t[WPA2-PSK-CCMP][WPS][ESS]\tKevin_H2\n"\
32 | "78:32:1b:63:96:05\t2422\t-91\t[WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS]\tjoyfulness\n"
33 |
34 | def __init__(self):
35 | self._last_cmd = None
36 | self._last_state = None
37 | self._network_profiles = []
38 |
39 | def bind(self, *args, **kwargs):
40 | pass
41 |
42 | def connect(self, *args, **kwargs):
43 | pass
44 |
45 | def recv(self, *args, **kwargs):
46 |
47 | if 'SCAN' == self._last_cmd:
48 | #print('mock sock get scan cmd')
49 |
50 | return b'OK\n'
51 | elif 'PING' == self._last_cmd:
52 | #print('mock sock get ping cmd')
53 |
54 | return b'PONG'
55 | elif 'SCAN_RESULTS' == self._last_cmd:
56 | #print('mock sock get scan_result cmd')
57 |
58 | return bytearray(self.default_scan_results, 'utf-8')
59 | elif 'DISCONNECT' == self._last_cmd:
60 | #print('mock sock get scan_result cmd')
61 |
62 | self._last_state = 0
63 |
64 | return b'OK\n'
65 | elif 'SELECT_NETWORK' == self._last_cmd[0:len('SELECT_NETWORK')]:
66 | #print('mock sock get scan_result cmd')
67 |
68 | self._last_state = 1
69 |
70 | return b'OK\n'
71 | elif 'STATUS' == self._last_cmd:
72 | #print('mock sock get scan_result cmd')
73 |
74 | if self._last_state == 0:
75 | status = 'wpa_state=DISCONNECTED'
76 | elif self._last_state == 1:
77 | status = 'wpa_state=COMPLETED'
78 |
79 | return bytearray(status, 'utf-8')
80 | elif 'REMOVE_NETWORK all' == self._last_cmd:
81 | #print('mock sock get remove_network_all cmd')
82 |
83 | self._network_profiles = self._network_profiles[:]
84 |
85 | return b'OK\n'
86 | elif 'REMOVE_NETWORK' == self._last_cmd[:len('REMOVE_NETWORK')]:
87 | #print('mock sock get remove_network cmd')
88 |
89 | network_id = int(self._last_cmd.split(' ')[1])
90 |
91 | for idx, network in enumerate(self._network_profiles):
92 | if network['id'] == network_id:
93 | del self._network_profiles[idx]
94 | break
95 |
96 | return b'OK\n'
97 | elif 'LIST_NETWORKS' == self._last_cmd:
98 | #print('mock sock get list_network cmd')
99 |
100 | networks = 'network id / ssid / bssid / flag\n'
101 |
102 | for network in self._network_profiles:
103 | networks += "{}\t{}\t{}\t[DISABLED]\n".format(
104 | network['id'], network['ssid'], network.get('bssid', 'None'))
105 |
106 | return bytearray(networks, 'utf-8')
107 | elif 'ADD_NETWORK' == self._last_cmd:
108 |
109 | if self._network_profiles:
110 | self._network_profiles.sort(key=lambda obj: obj['id'])
111 | network_id = self._network_profiles[-1]['id'] + 1
112 | else:
113 | network_id = 0
114 |
115 | network = {}
116 | network['id'] = network_id
117 | self._network_profiles.append(network)
118 |
119 | return bytearray(str(network_id), 'utf-8')
120 | elif 'SET_NETWORK' == self._last_cmd[:len('SET_NETWORK')]:
121 |
122 | network_id = int(self._last_cmd.split(' ')[1])
123 | field_name = self._last_cmd.split(' ')[2]
124 | val = self._last_cmd.split(' ')[3]
125 |
126 | for profile in self._network_profiles:
127 | if profile['id'] == network_id:
128 | network = profile
129 |
130 | if field_name == 'ssid':
131 | val = val[1:-1]
132 |
133 | network[field_name] = val
134 |
135 | return b'OK\n'
136 | elif 'GET_NETWORK' == self._last_cmd[:len('GET_NETWORK')]:
137 |
138 | network_id = int(self._last_cmd.split(' ')[1])
139 | field_name = self._last_cmd.split(' ')[2]
140 |
141 | for profile in self._network_profiles:
142 | if profile['id'] == network_id:
143 | network = profile
144 |
145 | if field_name == 'pairwise':
146 | val = 'CCMP TKIP'
147 | elif field_name == 'ssid':
148 | val = '"' + network[field_name] + '"'
149 | else:
150 | val = network[field_name]
151 |
152 | return bytearray(val, 'utf-8')
153 |
154 | def send(self, *args, **kwargs):
155 |
156 | self._last_cmd = args[0].decode('utf-8')
157 |
158 |
159 | class Mock:
160 |
161 |
162 | def __init__(self):
163 |
164 | self._dict = {}
165 |
166 | def __getattr__(self, field):
167 |
168 | return self._dict.get(field, None)
169 |
170 |
171 | def pywifi_test_patch(test_func):
172 |
173 | def core_patch(*args, **kwargs):
174 | os.stat = lambda *args: Mock()
175 | os.listdir = lambda *args: ['wlx000c433243ce']
176 | stat.S_ISSOCK = lambda *args: True
177 | socket.socket = lambda *args: SockMock()
178 | os.remove = lambda *args: True
179 |
180 | test_func(*args, **kwargs)
181 |
182 | return core_patch
183 |
184 | @pywifi_test_patch
185 | def test_interfaces():
186 |
187 | wifi = pywifi.PyWiFi()
188 |
189 | assert wifi.interfaces()
190 |
191 | if platform.system().lower() == 'windows':
192 | assert wifi.interfaces()[0].name() ==\
193 | 'Intel(R) Dual Band Wireless-AC 7260'
194 | elif platform.system().lower() == 'linux':
195 | assert wifi.interfaces()[0].name() == 'wlx000c433243ce'
196 |
197 | @pywifi_test_patch
198 | def test_scan():
199 |
200 | wifi = pywifi.PyWiFi()
201 |
202 | iface = wifi.interfaces()[0]
203 | iface.scan()
204 | time.sleep(5)
205 | bsses = iface.scan_results()
206 | assert bsses
207 |
208 | def test_profile_comparison():
209 |
210 | profile1 = pywifi.Profile()
211 | profile1.ssid = 'testap'
212 | profile1.auth = const.AUTH_ALG_OPEN
213 | profile1.akm.append(const.AKM_TYPE_WPA2PSK)
214 | profile1.cipher = const.CIPHER_TYPE_CCMP
215 | profile1.key = '12345678'
216 |
217 | profile2 = pywifi.Profile()
218 | profile2.ssid = 'testap'
219 | profile2.auth = const.AUTH_ALG_OPEN
220 | profile2.akm.append(const.AKM_TYPE_WPA2PSK)
221 | profile2.cipher = const.CIPHER_TYPE_CCMP
222 | profile2.key = '12345678'
223 |
224 | assert profile1 == profile2
225 |
226 | profile3 = pywifi.Profile()
227 | profile3.ssid = 'testap'
228 | profile3.auth = const.AUTH_ALG_OPEN
229 | profile3.akm.append(const.AKM_TYPE_WPAPSK)
230 | profile3.cipher = const.CIPHER_TYPE_CCMP
231 | profile3.key = '12345678'
232 |
233 | assert profile1 == profile3
234 |
235 | @pywifi_test_patch
236 | def test_add_network_profile():
237 |
238 | wifi = pywifi.PyWiFi()
239 |
240 | iface = wifi.interfaces()[0]
241 |
242 | profile = pywifi.Profile()
243 | profile.ssid = 'testap'
244 | profile.auth = const.AUTH_ALG_OPEN
245 | profile.akm.append(const.AKM_TYPE_WPA2PSK)
246 | profile.cipher = const.CIPHER_TYPE_CCMP
247 | profile.key = '12345678'
248 |
249 | iface.remove_all_network_profiles()
250 |
251 | assert len(iface.network_profiles()) == 0
252 |
253 | iface.add_network_profile(profile)
254 | profiles = iface.network_profiles()
255 |
256 | assert profiles is not None
257 | assert profiles[0].ssid == "testap"
258 | assert const.AKM_TYPE_WPA2PSK in profiles[0].akm
259 | assert const.AUTH_ALG_OPEN == profiles[0].auth
260 |
261 | @pywifi_test_patch
262 | def test_remove_network_profile():
263 |
264 | wifi = pywifi.PyWiFi()
265 |
266 | iface = wifi.interfaces()[0]
267 | iface.remove_all_network_profiles()
268 |
269 | assert len(iface.network_profiles()) == 0
270 |
271 | profile1 = pywifi.Profile()
272 | profile1.ssid = 'testap'
273 | profile1.auth = const.AUTH_ALG_OPEN
274 | profile1.akm.append(const.AKM_TYPE_WPA2PSK)
275 | profile1.cipher = const.CIPHER_TYPE_CCMP
276 | profile1.key = '12345678'
277 | iface.add_network_profile(profile1)
278 |
279 | profile2 = pywifi.Profile()
280 | profile2.ssid = 'testap2'
281 | profile2.auth = const.AUTH_ALG_OPEN
282 | profile2.akm.append(const.AKM_TYPE_WPA2PSK)
283 | profile2.cipher = const.CIPHER_TYPE_CCMP
284 | profile2.key = '12345678'
285 | iface.add_network_profile(profile2)
286 |
287 | profile3 = pywifi.Profile()
288 | profile3.ssid = 'testap3'
289 | profile3.auth = const.AUTH_ALG_OPEN
290 | profile3.akm.append(const.AKM_TYPE_WPAPSK)
291 | profile3.cipher = const.CIPHER_TYPE_TKIP
292 | profile3.key = '12345678'
293 | iface.add_network_profile(profile3)
294 |
295 | profiles = iface.network_profiles()
296 |
297 | assert len(profiles) == 3
298 |
299 | iface.remove_network_profile(profile2)
300 |
301 | profiles = iface.network_profiles()
302 |
303 | assert len(profiles) == 2
304 | assert profile2 not in profiles
305 |
306 | @pywifi_test_patch
307 | def test_status():
308 |
309 | wifi = pywifi.PyWiFi()
310 |
311 | iface = wifi.interfaces()[0]
312 | iface.disconnect()
313 | assert iface.status() in\
314 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
315 |
316 | @pywifi_test_patch
317 | def test_connect():
318 |
319 | wifi = pywifi.PyWiFi()
320 |
321 | iface = wifi.interfaces()[0]
322 |
323 | iface.disconnect()
324 | time.sleep(1)
325 | assert iface.status() in\
326 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
327 |
328 | profile = pywifi.Profile()
329 | profile.ssid = 'testap'
330 | profile.auth = const.AUTH_ALG_OPEN
331 | profile.akm.append(const.AKM_TYPE_WPA2PSK)
332 | profile.cipher = const.CIPHER_TYPE_CCMP
333 | profile.key = '12345678'
334 |
335 | iface.remove_all_network_profiles()
336 | tmp_profile = iface.add_network_profile(profile)
337 |
338 | iface.connect(tmp_profile)
339 | time.sleep(5)
340 | assert iface.status() == const.IFACE_CONNECTED
341 |
342 | iface.disconnect()
343 | time.sleep(1)
344 | assert iface.status() in\
345 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
346 |
347 | @pywifi_test_patch
348 | def test_connect_open():
349 |
350 | wifi = pywifi.PyWiFi()
351 |
352 | iface = wifi.interfaces()[0]
353 |
354 | iface.disconnect()
355 | time.sleep(1)
356 | assert iface.status() in\
357 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
358 |
359 | profile = pywifi.Profile()
360 | profile.ssid = 'testap'
361 | profile.auth = const.AUTH_ALG_OPEN
362 | profile.akm.append(const.AKM_TYPE_NONE)
363 |
364 | iface.remove_all_network_profiles()
365 | tmp_profile = iface.add_network_profile(profile)
366 |
367 | iface.connect(tmp_profile)
368 | time.sleep(5)
369 | assert iface.status() == const.IFACE_CONNECTED
370 |
371 | iface.disconnect()
372 | time.sleep(1)
373 | assert iface.status() in\
374 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
375 |
376 | @pywifi_test_patch
377 | def test_disconnect():
378 |
379 | wifi = pywifi.PyWiFi()
380 |
381 | iface = wifi.interfaces()[0]
382 | iface.disconnect()
383 |
384 | assert iface.status() in\
385 | [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]
386 |
--------------------------------------------------------------------------------
/pywifi/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | # Specify a configuration file.
4 | #rcfile=
5 |
6 | # Python code to execute, usually for sys.path manipulation such as
7 | # pygtk.require().
8 | #init-hook=
9 |
10 | # Add files or directories to the blacklist. They should be base names, not
11 | # paths.
12 | ignore=CVS
13 |
14 | # Pickle collected data for later comparisons.
15 | persistent=yes
16 |
17 | # List of plugins (as comma separated values of python modules names) to load,
18 | # usually to register additional checkers.
19 | load-plugins=
20 |
21 | # Use multiple processes to speed up Pylint.
22 | jobs=1
23 |
24 | # Allow loading of arbitrary C extensions. Extensions are imported into the
25 | # active Python interpreter and may run arbitrary code.
26 | unsafe-load-any-extension=no
27 |
28 | # A comma-separated list of package or module names from where C extensions may
29 | # be loaded. Extensions are loading into the active Python interpreter and may
30 | # run arbitrary code
31 | extension-pkg-whitelist=
32 |
33 | # Allow optimization of some AST trees. This will activate a peephole AST
34 | # optimizer, which will apply various small optimizations. For instance, it can
35 | # be used to obtain the result of joining multiple strings with the addition
36 | # operator. Joining a lot of strings can lead to a maximum recursion error in
37 | # Pylint and this flag can prevent that. It has one side effect, the resulting
38 | # AST will be different than the one from reality.
39 | optimize-ast=no
40 |
41 |
42 | [MESSAGES CONTROL]
43 |
44 | # Only show warnings with the listed confidence levels. Leave empty to show
45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
46 | confidence=
47 |
48 | # Enable the message, report, category or checker with the given id(s). You can
49 | # either give multiple identifier separated by comma (,) or put this option
50 | # multiple time. See also the "--disable" option for examples.
51 | #enable=
52 |
53 | # Disable the message, report, category or checker with the given id(s). You
54 | # can either give multiple identifiers separated by comma (,) or put this
55 | # option multiple times (only on the command line, not in the configuration
56 | # file where it should appear only once).You can also use "--disable=all" to
57 | # disable everything first and then reenable specific checks. For example, if
58 | # you want to run only the similarities checker, you can use "--disable=all
59 | # --enable=similarities". If you want to run only the classes checker, but have
60 | # no Warning level messages displayed, use"--disable=all --enable=classes
61 | # --disable=W"
62 | disable=file-builtin,next-method-called,delslice-method,old-division,standarderror-builtin,oct-method,dict-view-method,getslice-method,cmp-builtin,raw_input-builtin,backtick,input-builtin,reload-builtin,cmp-method,old-raise-syntax,nonzero-method,using-cmp-argument,unpacking-in-except,old-octal-literal,metaclass-assignment,hex-method,no-absolute-import,basestring-builtin,indexing-exception,round-builtin,suppressed-message,unicode-builtin,dict-iter-method,execfile-builtin,useless-suppression,xrange-builtin,setslice-method,long-suffix,old-ne-operator,print-statement,coerce-method,zip-builtin-not-iterating,unichr-builtin,reduce-builtin,coerce-builtin,apply-builtin,import-star-module-level,map-builtin-not-iterating,range-builtin-not-iterating,parameter-unpacking,filter-builtin-not-iterating,raising-string,long-builtin,intern-builtin,buffer-builtin
63 |
64 |
65 | [REPORTS]
66 |
67 | # Set the output format. Available formats are text, parseable, colorized, msvs
68 | # (visual studio) and html. You can also give a reporter class, eg
69 | # mypackage.mymodule.MyReporterClass.
70 | output-format=text
71 |
72 | # Put messages in a separate file for each module / package specified on the
73 | # command line instead of printing them on stdout. Reports (if any) will be
74 | # written in a file name "pylint_global.[txt|html]".
75 | files-output=no
76 |
77 | # Tells whether to display a full report or only the messages
78 | reports=yes
79 |
80 | # Python expression which should return a note less than 10 (10 is the highest
81 | # note). You have access to the variables errors warning, statement which
82 | # respectively contain the number of errors / warnings messages and the total
83 | # number of statements analyzed. This is used by the global evaluation report
84 | # (RP0004).
85 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
86 |
87 | # Template used to display messages. This is a python new-style format string
88 | # used to format the message information. See doc for all details
89 | #msg-template=
90 |
91 |
92 | [BASIC]
93 |
94 | # List of builtins function names that should not be used, separated by a comma
95 | bad-functions=map,filter
96 |
97 | # Good variable names which should always be accepted, separated by a comma
98 | good-names=i,j,k,ex,Run,_
99 |
100 | # Bad variable names which should always be refused, separated by a comma
101 | bad-names=foo,bar,baz,toto,tutu,tata
102 |
103 | # Colon-delimited sets of names that determine each other's naming style when
104 | # the name regexes allow several styles.
105 | name-group=
106 |
107 | # Include a hint for the correct naming format with invalid-name
108 | include-naming-hint=no
109 |
110 | # Regular expression matching correct class attribute names
111 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
112 |
113 | # Naming hint for class attribute names
114 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
115 |
116 | # Regular expression matching correct attribute names
117 | attr-rgx=[a-z_][a-z0-9_]{2,30}$
118 |
119 | # Naming hint for attribute names
120 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$
121 |
122 | # Regular expression matching correct variable names
123 | variable-rgx=[a-z_][a-z0-9_]{2,30}$
124 |
125 | # Naming hint for variable names
126 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$
127 |
128 | # Regular expression matching correct constant names
129 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
130 |
131 | # Naming hint for constant names
132 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
133 |
134 | # Regular expression matching correct module names
135 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
136 |
137 | # Naming hint for module names
138 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
139 |
140 | # Regular expression matching correct class names
141 | class-rgx=[A-Z_][a-zA-Z0-9]+$
142 |
143 | # Naming hint for class names
144 | class-name-hint=[A-Z_][a-zA-Z0-9]+$
145 |
146 | # Regular expression matching correct method names
147 | method-rgx=[a-z_][a-z0-9_]{2,30}$
148 |
149 | # Naming hint for method names
150 | method-name-hint=[a-z_][a-z0-9_]{2,30}$
151 |
152 | # Regular expression matching correct function names
153 | function-rgx=[a-z_][a-z0-9_]{2,30}$
154 |
155 | # Naming hint for function names
156 | function-name-hint=[a-z_][a-z0-9_]{2,30}$
157 |
158 | # Regular expression matching correct argument names
159 | argument-rgx=[a-z_][a-z0-9_]{2,30}$
160 |
161 | # Naming hint for argument names
162 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$
163 |
164 | # Regular expression matching correct inline iteration names
165 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
166 |
167 | # Naming hint for inline iteration names
168 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
169 |
170 | # Regular expression which should only match function or class names that do
171 | # not require a docstring.
172 | no-docstring-rgx=^_
173 |
174 | # Minimum line length for functions/classes that require docstrings, shorter
175 | # ones are exempt.
176 | docstring-min-length=-1
177 |
178 |
179 | [ELIF]
180 |
181 | # Maximum number of nested blocks for function / method body
182 | max-nested-blocks=5
183 |
184 |
185 | [LOGGING]
186 |
187 | # Logging modules to check that the string format arguments are in logging
188 | # function parameter format
189 | logging-modules=logging
190 |
191 |
192 | [SIMILARITIES]
193 |
194 | # Minimum lines number of a similarity.
195 | min-similarity-lines=4
196 |
197 | # Ignore comments when computing similarities.
198 | ignore-comments=yes
199 |
200 | # Ignore docstrings when computing similarities.
201 | ignore-docstrings=yes
202 |
203 | # Ignore imports when computing similarities.
204 | ignore-imports=no
205 |
206 |
207 | [TYPECHECK]
208 |
209 | # Tells whether missing members accessed in mixin class should be ignored. A
210 | # mixin class is detected if its name ends with "mixin" (case insensitive).
211 | ignore-mixin-members=yes
212 |
213 | # List of module names for which member attributes should not be checked
214 | # (useful for modules/projects where namespaces are manipulated during runtime
215 | # and thus existing member attributes cannot be deduced by static analysis. It
216 | # supports qualified module names, as well as Unix pattern matching.
217 | ignored-modules=
218 |
219 | # List of classes names for which member attributes should not be checked
220 | # (useful for classes with attributes dynamically set). This supports can work
221 | # with qualified names.
222 | ignored-classes=
223 |
224 | # List of members which are set dynamically and missed by pylint inference
225 | # system, and so shouldn't trigger E1101 when accessed. Python regular
226 | # expressions are accepted.
227 | generated-members=
228 |
229 |
230 | [VARIABLES]
231 |
232 | # Tells whether we should check for unused import in __init__ files.
233 | init-import=no
234 |
235 | # A regular expression matching the name of dummy variables (i.e. expectedly
236 | # not used).
237 | dummy-variables-rgx=_$|dummy
238 |
239 | # List of additional names supposed to be defined in builtins. Remember that
240 | # you should avoid to define new builtins when possible.
241 | additional-builtins=
242 |
243 | # List of strings which can identify a callback function by name. A callback
244 | # name must start or end with one of those strings.
245 | callbacks=cb_,_cb
246 |
247 |
248 | [FORMAT]
249 |
250 | # Maximum number of characters on a single line.
251 | max-line-length=100
252 |
253 | # Regexp for a line that is allowed to be longer than the limit.
254 | ignore-long-lines=^\s*(# )??$
255 |
256 | # Allow the body of an if to be on the same line as the test if there is no
257 | # else.
258 | single-line-if-stmt=no
259 |
260 | # List of optional constructs for which whitespace checking is disabled. `dict-
261 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
262 | # `trailing-comma` allows a space between comma and closing bracket: (a, ).
263 | # `empty-line` allows space-only lines.
264 | no-space-check=trailing-comma,dict-separator
265 |
266 | # Maximum number of lines in a module
267 | max-module-lines=1000
268 |
269 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
270 | # tab).
271 | indent-string=' '
272 |
273 | # Number of spaces of indent required inside a hanging or continued line.
274 | indent-after-paren=4
275 |
276 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
277 | expected-line-ending-format=
278 |
279 |
280 | [SPELLING]
281 |
282 | # Spelling dictionary name. Available dictionaries: none. To make it working
283 | # install python-enchant package.
284 | spelling-dict=
285 |
286 | # List of comma separated words that should not be checked.
287 | spelling-ignore-words=
288 |
289 | # A path to a file that contains private dictionary; one word per line.
290 | spelling-private-dict-file=
291 |
292 | # Tells whether to store unknown words to indicated private dictionary in
293 | # --spelling-private-dict-file option instead of raising a message.
294 | spelling-store-unknown-words=no
295 |
296 |
297 | [MISCELLANEOUS]
298 |
299 | # List of note tags to take in consideration, separated by a comma.
300 | notes=FIXME,XXX,TODO
301 |
302 |
303 | [DESIGN]
304 |
305 | # Maximum number of arguments for function / method
306 | max-args=5
307 |
308 | # Argument names that match this expression will be ignored. Default to name
309 | # with leading underscore
310 | ignored-argument-names=_.*
311 |
312 | # Maximum number of locals for function / method body
313 | max-locals=15
314 |
315 | # Maximum number of return / yield for function / method body
316 | max-returns=6
317 |
318 | # Maximum number of branch for function / method body
319 | max-branches=12
320 |
321 | # Maximum number of statements in function / method body
322 | max-statements=50
323 |
324 | # Maximum number of parents for a class (see R0901).
325 | max-parents=7
326 |
327 | # Maximum number of attributes for a class (see R0902).
328 | max-attributes=7
329 |
330 | # Minimum number of public methods for a class (see R0903).
331 | min-public-methods=2
332 |
333 | # Maximum number of public methods for a class (see R0904).
334 | max-public-methods=20
335 |
336 | # Maximum number of boolean expressions in a if statement
337 | max-bool-expr=5
338 |
339 |
340 | [CLASSES]
341 |
342 | # List of method names used to declare (i.e. assign) instance attributes.
343 | defining-attr-methods=__init__,__new__,setUp
344 |
345 | # List of valid names for the first argument in a class method.
346 | valid-classmethod-first-arg=cls
347 |
348 | # List of valid names for the first argument in a metaclass class method.
349 | valid-metaclass-classmethod-first-arg=mcs
350 |
351 | # List of member names, which should be excluded from the protected access
352 | # warning.
353 | exclude-protected=_asdict,_fields,_replace,_source,_make
354 |
355 |
356 | [IMPORTS]
357 |
358 | # Deprecated modules which should not be used, separated by a comma
359 | deprecated-modules=optparse
360 |
361 | # Create a graph of every (i.e. internal and external) dependencies in the
362 | # given file (report RP0402 must not be disabled)
363 | import-graph=
364 |
365 | # Create a graph of external dependencies in the given file (report RP0402 must
366 | # not be disabled)
367 | ext-import-graph=
368 |
369 | # Create a graph of internal dependencies in the given file (report RP0402 must
370 | # not be disabled)
371 | int-import-graph=
372 |
373 |
374 | [EXCEPTIONS]
375 |
376 | # Exceptions that will emit a warning when being caught. Defaults to
377 | # "Exception"
378 | overgeneral-exceptions=Exception
379 |
--------------------------------------------------------------------------------
/pywifi/_wifiutil_win.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # vim: set fileencoding=utf-8
3 |
4 | """Implementations of wifi functions of Linux."""
5 |
6 | import re
7 | import platform
8 | import time
9 | import logging
10 | from ctypes import *
11 | from ctypes.wintypes import *
12 | from comtypes import GUID
13 |
14 | from .const import *
15 | from .profile import Profile
16 |
17 |
18 | if platform.release().lower() == 'xp':
19 | if platform.win32_ver()[2].lower() in ['sp2', 'sp3']:
20 | CLIENT_VERSION = 1
21 | else:
22 | CLIENT_VERSION = 2
23 |
24 | """
25 | Some types does not exist in python2 ctypes.wintypes so we fake them
26 | using how its defined in python3 ctypes.wintypes.
27 | """
28 | if not "PDWORD" in dir():
29 | PDWORD = POINTER(DWORD)
30 |
31 | if not "PWCHAR" in dir():
32 | PWCHAR= POINTER(WCHAR)
33 |
34 | ERROR_SUCCESS = 0
35 | WLAN_MAX_PHY_TYPE_NUMBER = 8
36 | DOT11_MAC_ADDRESS = c_ubyte * 6
37 |
38 |
39 | native_wifi = windll.wlanapi
40 |
41 | status_dict = [
42 | IFACE_INACTIVE,
43 | IFACE_CONNECTED,
44 | IFACE_CONNECTED,
45 | IFACE_DISCONNECTED,
46 | IFACE_DISCONNECTED,
47 | IFACE_CONNECTING,
48 | IFACE_CONNECTING,
49 | IFACE_CONNECTING
50 | ]
51 |
52 | auth_value_to_str_dict = {
53 | AUTH_ALG_OPEN: 'open',
54 | AUTH_ALG_SHARED: 'shared'
55 | }
56 |
57 | auth_str_to_value_dict = {
58 | 'open': AUTH_ALG_OPEN,
59 | 'shared': AUTH_ALG_SHARED
60 | }
61 |
62 | akm_str_to_value_dict = {
63 | 'NONE': AKM_TYPE_NONE,
64 | 'WPA': AKM_TYPE_WPA,
65 | 'WPAPSK': AKM_TYPE_WPAPSK,
66 | 'WPA2': AKM_TYPE_WPA2,
67 | 'WPA2PSK': AKM_TYPE_WPA2PSK,
68 | 'OTHER': AKM_TYPE_UNKNOWN
69 | }
70 |
71 | akm_value_to_str_dict = {
72 | AKM_TYPE_NONE: 'NONE',
73 | AKM_TYPE_WPA: 'WPA',
74 | AKM_TYPE_WPAPSK: 'WPAPSK',
75 | AKM_TYPE_WPA2: 'WPA2',
76 | AKM_TYPE_WPA2PSK: 'WPA2PSK',
77 | AKM_TYPE_UNKNOWN: 'OTHER'
78 | }
79 |
80 | cipher_str_to_value_dict = {
81 | 'NONE': CIPHER_TYPE_NONE,
82 | 'WEP': CIPHER_TYPE_WEP,
83 | 'TKIP': CIPHER_TYPE_TKIP,
84 | 'AES': CIPHER_TYPE_CCMP,
85 | 'OTHER': CIPHER_TYPE_UNKNOWN
86 | }
87 |
88 | cipher_value_to_str_dict = {
89 | CIPHER_TYPE_NONE: 'NONE',
90 | CIPHER_TYPE_WEP: 'WEP',
91 | CIPHER_TYPE_TKIP: 'TKIP',
92 | CIPHER_TYPE_CCMP: 'AES',
93 | CIPHER_TYPE_UNKNOWN: 'UNKNOWN'
94 | }
95 |
96 | class WLAN_INTERFACE_INFO(Structure):
97 |
98 | _fields_ = [
99 | ("InterfaceGuid", GUID),
100 | ("strInterfaceDescription", c_wchar * 256),
101 | ("isState", c_uint)
102 | ]
103 |
104 |
105 | class WLAN_INTERFACE_INFO_LIST(Structure):
106 |
107 | _fields_ = [
108 | ("dwNumberOfItems", DWORD),
109 | ("dwIndex", DWORD),
110 | ("InterfaceInfo", WLAN_INTERFACE_INFO * 1)
111 | ]
112 |
113 |
114 | class DOT11_SSID(Structure):
115 |
116 | _fields_ = [("uSSIDLength", c_ulong),
117 | ("ucSSID", c_char * 32)]
118 |
119 |
120 | class WLAN_RATE_SET(Structure):
121 |
122 | _fields_ = [
123 | ("uRateSetLength", c_ulong),
124 | ("usRateSet", c_ushort * 126)
125 | ]
126 |
127 |
128 | class WLAN_RAW_DATA(Structure):
129 |
130 | _fields_ = [
131 | ("dwDataSize", DWORD),
132 | ("DataBlob", c_byte * 1)
133 | ]
134 |
135 |
136 | class WLAN_AVAILABLE_NETWORK(Structure):
137 |
138 | _fields_ = [
139 | ("strProfileName", c_wchar * 256),
140 | ("dot11Ssid", DOT11_SSID),
141 | ("dot11BssType", c_uint),
142 | ("uNumberOfBssids", c_ulong),
143 | ("bNetworkConnectable", c_bool),
144 | ("wlanNotConnectableReason", c_uint),
145 | ("uNumberOfPhyTypes", c_ulong * WLAN_MAX_PHY_TYPE_NUMBER),
146 | ("dot11PhyTypes", c_uint),
147 | ("bMorePhyTypes", c_bool),
148 | ("wlanSignalQuality", c_ulong),
149 | ("bSecurityEnabled", c_bool),
150 | ("dot11DefaultAuthAlgorithm", c_uint),
151 | ("dot11DefaultCipherAlgorithm", c_uint),
152 | ("dwFlags", DWORD),
153 | ("dwReserved", DWORD)
154 | ]
155 |
156 |
157 | class WLAN_AVAILABLE_NETWORK_LIST(Structure):
158 |
159 | _fields_ = [
160 | ("dwNumberOfItems", DWORD),
161 | ("dwIndex", DWORD),
162 | ("Network", WLAN_AVAILABLE_NETWORK * 1)
163 | ]
164 |
165 |
166 | class WLAN_BSS_ENTRY(Structure):
167 |
168 | _fields_ = [
169 | ("dot11Ssid", DOT11_SSID),
170 | ("uPhyId", c_ulong),
171 | ("dot11Bssid", DOT11_MAC_ADDRESS),
172 | ("dot11BssType", c_uint),
173 | ("dot11BssPhyType", c_uint),
174 | ("lRssi", c_long),
175 | ("uLinkQuality", c_ulong),
176 | ("bInRegDomain", c_bool),
177 | ("usBeaconPeriod", c_ushort),
178 | ("ullTimestamp", c_ulonglong),
179 | ("ullHostTimestamp", c_ulonglong),
180 | ("usCapabilityInformation", c_ushort),
181 | ("ulChCenterFrequency", c_ulong),
182 | ("wlanRateSet", WLAN_RATE_SET),
183 | ("ulIeOffset", c_ulong),
184 | ("ulIeSize", c_ulong)
185 | ]
186 |
187 |
188 | class WLAN_BSS_LIST(Structure):
189 |
190 | _fields_ = [
191 | ("dwTotalSize", DWORD),
192 | ("dwNumberOfItems", DWORD),
193 | ("wlanBssEntries", WLAN_BSS_ENTRY * 1)
194 | ]
195 |
196 |
197 | class NDIS_OBJECT_HEADER(Structure):
198 |
199 | _fields_ = [
200 | ("Type", c_ubyte),
201 | ("Revision", c_ubyte),
202 | ("Size", c_ushort)
203 | ]
204 |
205 |
206 | class DOT11_BSSID_LIST(Structure):
207 |
208 | _fields_ = [
209 | ("Header", NDIS_OBJECT_HEADER),
210 | ("uNumOfEntries", c_ulong),
211 | ("uTotalNumOfEntries", c_ulong),
212 | ("BSSIDs", DOT11_MAC_ADDRESS * 1)
213 | ]
214 |
215 |
216 | class WLAN_CONNECTION_PARAMETERS(Structure):
217 |
218 | _fields_ = [
219 | ("wlanConnectionMode", c_uint),
220 | ("strProfile", c_wchar_p),
221 | ("pDot11Ssid", POINTER(DOT11_SSID)),
222 | ("pDesiredBssidList", POINTER(DOT11_BSSID_LIST)),
223 | ("dot11BssType", c_uint),
224 | ("dwFlags", DWORD)
225 | ]
226 |
227 |
228 | class WLAN_PROFILE_INFO(Structure):
229 |
230 | _fields_ = [
231 | ("strProfileName", c_wchar * 256),
232 | ("dwFlags", DWORD)
233 | ]
234 |
235 |
236 | class WLAN_PROFILE_INFO_LIST(Structure):
237 |
238 | _fields_ = [
239 | ("dwNumberOfItems", DWORD),
240 | ("dwIndex", DWORD),
241 | ("ProfileInfo", WLAN_PROFILE_INFO * 1)
242 | ]
243 |
244 |
245 | class WifiUtil():
246 | """WifiUtil implements the wifi functions in Windows."""
247 |
248 | _nego_version = DWORD()
249 | _handle = HANDLE()
250 | _ifaces = pointer(WLAN_INTERFACE_INFO_LIST())
251 | _logger = logging.getLogger('pywifi')
252 |
253 | def scan(self, obj):
254 | """Trigger the wifi interface to scan."""
255 |
256 | self._wlan_scan(self._handle, byref(obj['guid']))
257 |
258 | def scan_results(self, obj):
259 | """Get the AP list after scanning."""
260 |
261 | avail_network_list = pointer(WLAN_AVAILABLE_NETWORK_LIST())
262 | self._wlan_get_available_network_list(self._handle,
263 | byref(obj['guid']), byref(avail_network_list))
264 | networks = cast(avail_network_list.contents.Network,
265 | POINTER(WLAN_AVAILABLE_NETWORK))
266 |
267 | self._logger.debug("Scan found %d networks.",
268 | avail_network_list.contents.dwNumberOfItems)
269 |
270 | network_list = []
271 | for i in range(avail_network_list.contents.dwNumberOfItems):
272 |
273 | if networks[i].dot11BssType == 1 and networks[i].bNetworkConnectable :
274 |
275 | ssid = ''
276 | for j in range(networks[i].dot11Ssid.uSSIDLength):
277 |
278 | if networks[i].dot11Ssid.ucSSID != b'':
279 | ssid += "%c" % networks[i].dot11Ssid.ucSSID[j]
280 |
281 | bss_list = pointer(WLAN_BSS_LIST())
282 | self._wlan_get_network_bss_list(self._handle,
283 | byref(obj['guid']), byref(bss_list), networks[i].dot11Ssid, networks[i].bSecurityEnabled)
284 | bsses = cast(bss_list.contents.wlanBssEntries,
285 | POINTER(WLAN_BSS_ENTRY))
286 |
287 | if networks[i].bSecurityEnabled:
288 | akm = self._get_akm(networks[i].dot11DefaultCipherAlgorithm)
289 | auth_alg = self._get_auth_alg(networks[i].dot11DefaultAuthAlgorithm)
290 | else:
291 | akm = [AKM_TYPE_NONE]
292 | auth_alg = [AUTH_ALG_OPEN]
293 |
294 | for j in range(bss_list.contents.dwNumberOfItems):
295 | network = Profile()
296 |
297 | network.ssid = ssid
298 |
299 | network.bssid = ''
300 | for k in range(6):
301 | network.bssid += "%02x:" % bsses[j].dot11Bssid[k]
302 |
303 | network.signal = bsses[j].lRssi
304 | network.freq = bsses[j].ulChCenterFrequency
305 | network.auth = auth_alg
306 | network.akm = akm
307 | network_list.append(network)
308 |
309 | return network_list
310 |
311 | def connect(self, obj, params):
312 | """Connect to the specified AP."""
313 |
314 | connect_params = WLAN_CONNECTION_PARAMETERS()
315 | connect_params.wlanConnectionMode = 0 # Profile
316 | connect_params.dot11BssType = 1 # infra
317 | profile_name = create_unicode_buffer(params.ssid)
318 |
319 | connect_params.strProfile = profile_name.value
320 | ret = self._wlan_connect(
321 | self._handle, obj['guid'], byref(connect_params))
322 | self._logger.debug('connect result: %d', ret)
323 |
324 | def disconnect(self, obj):
325 | """Disconnect to the specified AP."""
326 |
327 | self._wlan_disconnect(self._handle, obj['guid'])
328 |
329 | def add_network_profile(self, obj, params):
330 | """Add an AP profile for connecting to afterward."""
331 |
332 | reason_code = DWORD()
333 |
334 | params.process_akm()
335 | profile_data = {}
336 | profile_data['ssid'] = params.ssid
337 |
338 | if AKM_TYPE_NONE in params.akm:
339 | profile_data['auth'] = auth_value_to_str_dict[params.auth]
340 | profile_data['encrypt'] = "none"
341 | else:
342 | profile_data['auth'] = akm_value_to_str_dict[params.akm[-1]]
343 | profile_data['encrypt'] = cipher_value_to_str_dict[params.cipher]
344 |
345 | profile_data['key'] = params.key
346 |
347 | profile_data['protected'] = 'false'
348 | profile_data['profile_name'] = params.ssid
349 |
350 | xml = """
351 |
352 | {profile_name}
353 |
354 |
355 | {ssid}
356 |
357 |
358 | ESS
359 | manual
360 |
361 |
362 |
363 | {auth}
364 | {encrypt}
365 | false
366 |
367 | """
368 |
369 | if AKM_TYPE_NONE not in params.akm:
370 | xml += """
371 | passPhrase
372 | {protected}
373 | {key}
374 | """
375 |
376 | xml += """
377 |
378 | """
379 |
380 | xml += """
381 | false
382 |
383 |
384 | """
385 |
386 | xml = xml.format(**profile_data)
387 |
388 | status = self._wlan_set_profile(self._handle, obj['guid'], xml,
389 | True, byref(reason_code))
390 | if status != ERROR_SUCCESS:
391 | self._logger.debug("Status %d: Add profile failed", status)
392 |
393 | buf_size = DWORD(64)
394 | buf = create_unicode_buffer(64)
395 | self._wlan_reason_code_to_str(reason_code, buf_size, buf)
396 |
397 | return params
398 |
399 | def network_profile_name_list(self, obj):
400 | """Get AP profile names."""
401 |
402 | profile_list = pointer(WLAN_PROFILE_INFO_LIST())
403 | self._wlan_get_profile_list(self._handle,
404 | byref(obj['guid']),
405 | byref(profile_list))
406 | profiles = cast(profile_list.contents.ProfileInfo,
407 | POINTER(WLAN_PROFILE_INFO))
408 |
409 | profile_name_list = []
410 | for i in range(profile_list.contents.dwNumberOfItems):
411 | profile_name = ''
412 | for j in range(len(profiles[i].strProfileName)):
413 | profile_name += profiles[i].strProfileName[j]
414 | profile_name_list.append(profile_name)
415 |
416 | return profile_name_list
417 |
418 | def network_profiles(self, obj):
419 | """Get AP profiles."""
420 |
421 | profile_name_list = self.network_profile_name_list(obj)
422 |
423 | profile_list = []
424 | for profile_name in profile_name_list:
425 | profile = Profile()
426 | flags = DWORD()
427 | access = DWORD()
428 | xml = LPWSTR()
429 | self._wlan_get_profile(self._handle, obj['guid'],
430 | profile_name, byref(xml), byref(flags),
431 | byref(access))
432 | # fill profile info
433 | profile.ssid = re.search(r'(.*)', xml.value).group(1)
434 | auth = re.search(r'(.*)',
435 | xml.value).group(1).upper()
436 |
437 | profile.akm = []
438 | if auth not in akm_str_to_value_dict:
439 | if auth not in auth_str_to_value_dict:
440 | profile.auth = AUTH_ALG_OPEN
441 | else:
442 | profile.auth = auth_str_to_value_dict[auth]
443 | profile.akm.append(AKM_TYPE_NONE)
444 | else:
445 | profile.auth = AUTH_ALG_OPEN
446 | profile.akm.append(akm_str_to_value_dict[auth])
447 |
448 | profile_list.append(profile)
449 |
450 | return profile_list
451 |
452 | def remove_network_profile(self, obj, params):
453 | """Remove the specified AP profile."""
454 |
455 | self._logger.debug("delete profile: %s", params.ssid)
456 | str_buf = create_unicode_buffer(params.ssid)
457 | ret = self._wlan_delete_profile(self._handle, obj['guid'], str_buf)
458 | self._logger.debug("delete result %d", ret)
459 |
460 | def remove_all_network_profiles(self, obj):
461 | """Remove all the AP profiles."""
462 |
463 | profile_name_list = self.network_profile_name_list(obj)
464 |
465 | for profile_name in profile_name_list:
466 | self._logger.debug("delete profile: %s", profile_name)
467 | str_buf = create_unicode_buffer(profile_name)
468 | ret = self._wlan_delete_profile(self._handle, obj['guid'], str_buf)
469 | self._logger.debug("delete result %d", ret)
470 |
471 | def status(self, obj):
472 | """Get the wifi interface status."""
473 |
474 | data_size = DWORD()
475 | data = PDWORD()
476 | opcode_value_type = DWORD()
477 | self._wlan_query_interface(self._handle, obj['guid'], 6,
478 | byref(data_size), byref(data),
479 | byref(opcode_value_type))
480 |
481 | return status_dict[data.contents.value]
482 |
483 | def interfaces(self):
484 | """Get the wifi interface lists."""
485 |
486 | ifaces = []
487 |
488 | if self._wlan_open_handle(CLIENT_VERSION,
489 | byref(self._nego_version),
490 | byref(self._handle)) \
491 | is not ERROR_SUCCESS:
492 | self._logger.error("Open handle failed!")
493 |
494 | if self._wlan_enum_interfaces(self._handle, byref(self._ifaces)) \
495 | is not ERROR_SUCCESS:
496 | self._logger.error("Enum interface failed!")
497 |
498 | interfaces = cast(self._ifaces.contents.InterfaceInfo,
499 | POINTER(WLAN_INTERFACE_INFO))
500 | for i in range(0, self._ifaces.contents.dwNumberOfItems):
501 | iface = {}
502 | iface['guid'] = interfaces[i].InterfaceGuid
503 | iface['name'] = interfaces[i].strInterfaceDescription
504 | ifaces.append(iface)
505 |
506 | return ifaces
507 |
508 | def _wlan_open_handle(self, client_version, _nego_version, handle):
509 |
510 | func = native_wifi.WlanOpenHandle
511 | func.argtypes = [DWORD, c_void_p, POINTER(DWORD), POINTER(HANDLE)]
512 | func.restypes = [DWORD]
513 | return func(client_version, None, _nego_version, handle)
514 |
515 | def _wlan_close_handle(self, handle):
516 |
517 | func = native_wifi.WlanCloseHandle
518 | func.argtypes = [HANDLE, c_void_p]
519 | func.restypes = [DWORD]
520 | return func(handle, None)
521 |
522 | def _wlan_enum_interfaces(self, handle, ifaces):
523 |
524 | func = native_wifi.WlanEnumInterfaces
525 | func.argtypes = [HANDLE, c_void_p, POINTER(
526 | POINTER(WLAN_INTERFACE_INFO_LIST))]
527 | func.restypes = [DWORD]
528 | return func(handle, None, ifaces)
529 |
530 | def _wlan_get_available_network_list(self, handle,
531 | iface_guid,
532 | network_list):
533 |
534 | func = native_wifi.WlanGetAvailableNetworkList
535 | func.argtypes = [HANDLE, POINTER(GUID), DWORD, c_void_p, POINTER(
536 | POINTER(WLAN_AVAILABLE_NETWORK_LIST))]
537 | func.restypes = [DWORD]
538 | return func(handle, iface_guid, 2, None, network_list)
539 |
540 | def _wlan_get_network_bss_list(self, handle, iface_guid, bss_list, ssid = None, security = False):
541 |
542 | func = native_wifi.WlanGetNetworkBssList
543 | func.argtypes = [HANDLE, POINTER(GUID), POINTER(
544 | DOT11_SSID), c_uint, c_bool, c_void_p, POINTER(POINTER(WLAN_BSS_LIST))]
545 | func.restypes = [DWORD]
546 | return func(handle, iface_guid, ssid, 1, security, None, bss_list)
547 |
548 | def _wlan_scan(self, handle, iface_guid):
549 |
550 | func = native_wifi.WlanScan
551 | func.argtypes = [HANDLE, POINTER(GUID), POINTER(
552 | DOT11_SSID), POINTER(WLAN_RAW_DATA), c_void_p]
553 | func.restypes = [DWORD]
554 | return func(handle, iface_guid, None, None, None)
555 |
556 | def _wlan_connect(self, handle, iface_guid, params):
557 |
558 | func = native_wifi.WlanConnect
559 | func.argtypes = [HANDLE, POINTER(GUID), POINTER(
560 | WLAN_CONNECTION_PARAMETERS), c_void_p]
561 | func.restypes = [DWORD]
562 | return func(handle, iface_guid, params, None)
563 |
564 | def _wlan_set_profile(self, handle, iface_guid, xml, overwrite, reason_code):
565 |
566 | func = native_wifi.WlanSetProfile
567 | func.argtypes = [HANDLE, POINTER(
568 | GUID), DWORD, c_wchar_p, c_wchar_p, c_bool, c_void_p, POINTER(DWORD)]
569 | func.restypes = [DWORD]
570 | return func(handle, iface_guid, 2, xml, None, overwrite, None, reason_code)
571 |
572 | def _wlan_reason_code_to_str(self, reason_code, buf_size, buf):
573 |
574 | func = native_wifi.WlanReasonCodeToString
575 | func.argtypes = [DWORD, DWORD, PWCHAR, c_void_p]
576 | func.restypes = [DWORD]
577 | return func(reason_code, buf_size, buf, None)
578 |
579 | def _wlan_get_profile_list(self, handle, iface_guid, profile_list):
580 |
581 | func = native_wifi.WlanGetProfileList
582 | func.argtypes = [HANDLE, POINTER(GUID), c_void_p, POINTER(
583 | POINTER(WLAN_PROFILE_INFO_LIST))]
584 | func.restypes = [DWORD]
585 | return func(handle, iface_guid, None, profile_list)
586 |
587 | def _wlan_get_profile(self, handle, iface_guid, profile_name, xml, flags, access):
588 |
589 | func = native_wifi.WlanGetProfile
590 | func.argtypes = [HANDLE, POINTER(GUID), c_wchar_p, c_void_p, POINTER(
591 | c_wchar_p), POINTER(DWORD), POINTER(DWORD)]
592 | func.restypes = [DWORD]
593 | return func(handle, iface_guid, profile_name, None, xml, flags, access)
594 |
595 | def _wlan_delete_profile(self, handle, iface_guid, profile_name):
596 |
597 | func = native_wifi.WlanDeleteProfile
598 | func.argtypes = [HANDLE, POINTER(GUID), c_wchar_p, c_void_p]
599 | func.restypes = [DWORD]
600 | return func(handle, iface_guid, profile_name, None)
601 |
602 | def _wlan_query_interface(self, handle, iface_guid, opcode, data_size, data, opcode_value_type):
603 |
604 | func = native_wifi.WlanQueryInterface
605 | func.argtypes = [HANDLE, POINTER(GUID), DWORD, c_void_p, POINTER(
606 | DWORD), POINTER(POINTER(DWORD)), POINTER(DWORD)]
607 | func.restypes = [DWORD]
608 | return func(handle, iface_guid, opcode, None, data_size, data, opcode_value_type)
609 |
610 | def _wlan_disconnect(self, handle, iface_guid):
611 |
612 | func = native_wifi.WlanDisconnect
613 | func.argtypes = [HANDLE, POINTER(GUID), c_void_p]
614 | func.restypes = [DWORD]
615 | return func(handle, iface_guid, None)
616 |
617 | def _get_auth_alg(self, auth_val):
618 |
619 | auth_alg = []
620 | if auth_val in [1, 3, 4, 6, 7]:
621 | auth_alg.append(AUTH_ALG_OPEN)
622 | elif auth_val == 2:
623 | auth_alg.append(AUTH_ALG_SHARED)
624 |
625 | return auth_alg
626 |
627 | def _get_akm(self, akm_val):
628 |
629 | akm = []
630 | if akm_val == 2:
631 | akm.append(AKM_TYPE_WPAPSK)
632 | elif akm_val == 4:
633 | akm.append(AKM_TYPE_WPA2PSK)
634 |
635 | return akm
636 |
--------------------------------------------------------------------------------