├── 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 | ![](https://img.shields.io/pypi/pyversions/pywifi.svg) 4 | [![Build Status](https://travis-ci.com/awkman/pywifi.svg?branch=master)](https://travis-ci.com/awkman/pywifi) 5 | [![PyPI version](https://badge.fury.io/py/pywifi.svg)](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 | --------------------------------------------------------------------------------