├── .gitignore ├── README.md ├── __init__.py ├── iw_parse ├── iw_parse.py └── license.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python 2 | # Edit at https://www.gitignore.io/?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # celery beat schedule file 89 | celerybeat-schedule 90 | 91 | # SageMath parsed files 92 | *.sage.py 93 | 94 | # Environments 95 | .env 96 | .venv 97 | env/ 98 | venv/ 99 | ENV/ 100 | env.bak/ 101 | venv.bak/ 102 | 103 | # Spyder project settings 104 | .spyderproject 105 | .spyproject 106 | 107 | # Rope project settings 108 | .ropeproject 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ 115 | .dmypy.json 116 | dmypy.json 117 | 118 | # Pyre type checker 119 | .pyre/ 120 | 121 | ### Python Patch ### 122 | .venv/ 123 | 124 | # End of https://www.gitignore.io/api/python 125 | 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iw_parse 2 | ======== 3 | 4 | Parse the output of iwlist scan to get the name, address, quality, channel, and encryption type of all networks broadcasting within your Wireless NIC's reach. 5 | 6 | Dependencies 7 | ------------ 8 | 9 | * [pip](http://www.pip-installer.org/en/latest/installing.html "pip installation guide") - If you don't have pip installed, followed the link. 10 | 11 | 12 | Installation 13 | ------------ 14 | 15 | ```bash 16 | pip install iw_parse 17 | ``` 18 | 19 | Usage 20 | ----- 21 | 22 | ```bash 23 | iwlist scan | iw_parse 24 | ``` 25 | 26 | Replace `` with the system name for your wireless NIC. It is usually something like `wlan0`. The command `iwconfig` will list all of your network interfaces. 27 | 28 | Example: 29 | 30 | ```bash 31 | iwlist wlan0 scan | iw_parse 32 | ``` 33 | 34 | The result should look something like: 35 | 36 | ``` 37 | Name Address Quality Channel Encryption 38 | wireless1 20:AA:4B:34:2C:F5 100 % 11 WEP 39 | wireless2 00:26:F2:1E:FC:03 84 % 1 WPA v.1 40 | wireless3 00:1D:D3:6A:3C:60 66 % 6 WEP 41 | wireless4 20:10:7A:E5:02:98 64 % 1 WEP 42 | wireless5 CC:A4:62:B7:D2:B0 54 % 8 WPA v.1 43 | wireless6 30:46:9A:53:3C:76 47 % 11 WPA v.1 44 | wireless7 A0:21:B7:5F:84:B0 44 % 11 WEP 45 | wireless8 04:A1:51:18:E8:E0 41 % 6 WPA v.1 46 | ``` 47 | 48 | Example from Python shell: 49 | 50 | ```python 51 | >>> import iw_parse 52 | >>> networks = iw_parse.get_interfaces(interface='wlan0') 53 | >>> print networks 54 | [{'Address': 'F8:1E:DF:F9:B0:0B', 55 | 'Channel': '3', 56 | 'Encryption': 'WEP', 57 | 'Name': 'Francis', 58 | 'Bit Rates': '144 Mb/s', 59 | 'Signal Level': '42', 60 | 'Name': 'Francis', 61 | 'Quality': '100'}, 62 | {'Address': '86:1B:5E:33:17:D4', 63 | 'Channel': '6', 64 | 'Encryption': 'Open', 65 | 'Bit Rates': '54 Mb/s', 66 | 'Signal Level': '72', 67 | 'Name': 'optimumwifi', 68 | 'Quality': '100'}, 69 | ... 70 | ``` 71 | 72 | Acknowledgements 73 | ---------------- 74 | 75 | * The vast majority of iw_parse was written by Hugo Chargois. 76 | 77 | License 78 | ------- 79 | 80 | iw_parse is free--as in BSD. Hack your heart out, hackers. 81 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .iw_parse import * 2 | -------------------------------------------------------------------------------- /iw_parse: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import sys 4 | 5 | from iw_parse import get_parsed_cells, print_cells 6 | 7 | def main(): 8 | """ Pretty prints the output of iwlist scan into a table. """ 9 | parsed_cells = get_parsed_cells(sys.stdin) 10 | 11 | # You can choose which columns to display here, and most importantly 12 | # in what order. Of course, they must exist as keys in the dict rules. 13 | columns = [ 14 | "Name", 15 | "Address", 16 | "Quality", 17 | "Channel", 18 | "Signal Level", 19 | "Encryption" 20 | ] 21 | 22 | print_cells(parsed_cells, columns) 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /iw_parse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Hugo Chargois - 17 jan. 2010 - v.0.1 4 | # Parses the output of iwlist scan into a table 5 | 6 | # You can add or change the functions to parse the properties 7 | # of each AP (cell) below. They take one argument, the bunch of text 8 | # describing one cell in iwlist scan and return a property of that cell. 9 | 10 | import re 11 | import subprocess 12 | 13 | VERSION_RGX = re.compile("version\s+\d+", re.IGNORECASE) 14 | 15 | def get_name(cell): 16 | """ Gets the name / essid of a network / cell. 17 | @param string cell 18 | A network / cell from iwlist scan. 19 | 20 | @return string 21 | The name / essid of the network. 22 | """ 23 | 24 | essid = matching_line(cell, "ESSID:") 25 | if not essid: 26 | return "" 27 | return essid[1:-1] 28 | 29 | def get_quality(cell): 30 | """ Gets the quality of a network / cell. 31 | @param string cell 32 | A network / cell from iwlist scan. 33 | 34 | @return string 35 | The quality of the network. 36 | """ 37 | 38 | quality = matching_line(cell, "Quality=") 39 | if quality is None: 40 | return "" 41 | quality = quality.split()[0].split("/") 42 | quality = matching_line(cell, "Quality=").split()[0].split("/") 43 | return str(int(round(float(quality[0]) / float(quality[1]) * 100))) 44 | 45 | def get_signal_level(cell): 46 | """ Gets the signal level of a network / cell. 47 | @param string cell 48 | A network / cell from iwlist scan. 49 | 50 | @return string 51 | The signal level of the network. 52 | """ 53 | 54 | signal = matching_line(cell, "Signal level=") 55 | if signal is None: 56 | return "" 57 | signal = signal.split("=")[1].split("/") 58 | if len(signal) == 2: 59 | return str(int(round(float(signal[0]) / float(signal[1]) * 100))) 60 | elif len(signal) == 1: 61 | return signal[0].split(' ')[0] 62 | else: 63 | return "" 64 | 65 | def get_noise_level(cell): 66 | """ Gets the noise level of a network / cell. 67 | @param string cell 68 | A network / cell from iwlist scan. 69 | 70 | @return string 71 | The noise level of the network. 72 | """ 73 | 74 | noise = matching_line(cell, "Noise level=") 75 | if noise is None: 76 | return "" 77 | noise = noise.split("=")[1] 78 | return noise.split(' ')[0] 79 | 80 | def get_channel(cell): 81 | """ Gets the channel of a network / cell. 82 | @param string cell 83 | A network / cell from iwlist scan. 84 | 85 | @return string 86 | The channel of the network. 87 | """ 88 | 89 | channel = matching_line(cell, "Channel:") 90 | if channel: 91 | return channel 92 | frequency = matching_line(cell, "Frequency:") 93 | channel = re.sub(r".*\(Channel\s(\d{1,3})\).*", r"\1", frequency) 94 | return channel 95 | 96 | def get_frequency(cell): 97 | """ Gets the frequency of a network / cell. 98 | @param string cell 99 | A network / cell from iwlist scan. 100 | 101 | @return string 102 | The frequency of the network. 103 | """ 104 | 105 | frequency = matching_line(cell, "Frequency:") 106 | if frequency is None: 107 | return "" 108 | return frequency.split()[0] 109 | 110 | def get_encryption(cell, emit_version=False): 111 | """ Gets the encryption type of a network / cell. 112 | @param string cell 113 | A network / cell from iwlist scan. 114 | 115 | @return string 116 | The encryption type of the network. 117 | """ 118 | 119 | enc = "" 120 | if matching_line(cell, "Encryption key:") == "off": 121 | enc = "Open" 122 | else: 123 | for line in cell: 124 | matching = match(line,"IE:") 125 | if matching == None: 126 | continue 127 | 128 | wpa = match(matching,"WPA") 129 | if wpa == None: 130 | continue 131 | 132 | version_matches = VERSION_RGX.search(wpa) 133 | if len(version_matches.regs) == 1: 134 | version = version_matches \ 135 | .group(0) \ 136 | .lower() \ 137 | .replace("version", "") \ 138 | .strip() 139 | wpa = wpa.replace(version_matches.group(0), "").strip() 140 | if wpa == "": 141 | wpa = "WPA" 142 | if emit_version: 143 | enc = "{0} v.{1}".format(wpa, version) 144 | else: 145 | enc = wpa 146 | if wpa == "WPA2": 147 | return enc 148 | else: 149 | enc = wpa 150 | if enc == "": 151 | enc = "WEP" 152 | return enc 153 | 154 | def get_mode(cell): 155 | """ Gets the mode of a network / cell. 156 | @param string cell 157 | A network / cell from iwlist scan. 158 | 159 | @return string 160 | The IEEE 802.11 mode of the network. 161 | """ 162 | 163 | mode = matching_line(cell, "Extra:ieee_mode=") 164 | if mode is None: 165 | return "" 166 | return mode 167 | 168 | def get_address(cell): 169 | """ Gets the address of a network / cell. 170 | @param string cell 171 | A network / cell from iwlist scan. 172 | 173 | @return string 174 | The address of the network. 175 | """ 176 | 177 | return matching_line(cell, "Address: ") 178 | 179 | def get_bit_rates(cell): 180 | """ Gets the bit rate of a network / cell. 181 | @param string cell 182 | A network / cell from iwlist scan. 183 | 184 | @return string 185 | The bit rate of the network. 186 | """ 187 | 188 | return matching_line(cell, "Bit Rates:") 189 | 190 | # Here you can choose the way of sorting the table. sortby should be a key of 191 | # the dictionary rules. 192 | 193 | def sort_cells(cells): 194 | sortby = "Quality" 195 | reverse = True 196 | cells.sort(key=lambda el:el[sortby], reverse=reverse) 197 | 198 | 199 | # Below here goes the boring stuff. You shouldn't have to edit anything below 200 | # this point 201 | 202 | def matching_line(lines, keyword): 203 | """ Returns the first matching line in a list of lines. 204 | @see match() 205 | """ 206 | for line in lines: 207 | matching = match(line,keyword) 208 | if matching != None: 209 | return matching 210 | return None 211 | 212 | def match(line, keyword): 213 | """ If the first part of line (modulo blanks) matches keyword, 214 | returns the end of that line. Otherwise checks if keyword is 215 | anywhere in the line and returns that section, else returns None""" 216 | 217 | line = line.lstrip() 218 | length = len(keyword) 219 | if line[:length] == keyword: 220 | return line[length:] 221 | else: 222 | if keyword in line: 223 | return line[line.index(keyword):] 224 | else: 225 | return None 226 | 227 | def parse_cell(cell, rules): 228 | """ Applies the rules to the bunch of text describing a cell. 229 | @param string cell 230 | A network / cell from iwlist scan. 231 | @param dictionary rules 232 | A dictionary of parse rules. 233 | 234 | @return dictionary 235 | parsed networks. """ 236 | 237 | parsed_cell = {} 238 | for key in rules: 239 | rule = rules[key] 240 | parsed_cell.update({key: rule(cell)}) 241 | return parsed_cell 242 | 243 | def print_table(table): 244 | # Functional black magic. 245 | widths = list(map(max, map(lambda l: map(len, l), zip(*table)))) 246 | 247 | justified_table = [] 248 | for line in table: 249 | justified_line = [] 250 | for i, el in enumerate(line): 251 | justified_line.append(el.ljust(widths[i] + 2)) 252 | justified_table.append(justified_line) 253 | 254 | for line in justified_table: 255 | print("\t".join(line)) 256 | 257 | def print_cells(cells, columns): 258 | table = [columns] 259 | for cell in cells: 260 | cell_properties = [] 261 | for column in columns: 262 | if column == 'Quality': 263 | # make print nicer 264 | cell[column] = cell[column].rjust(3) + " %" 265 | cell_properties.append(cell[column]) 266 | table.append(cell_properties) 267 | print_table(table) 268 | 269 | def get_parsed_cells(iw_data, rules=None): 270 | """ Parses iwlist output into a list of networks. 271 | @param list iw_data 272 | Output from iwlist scan. 273 | A list of strings. 274 | 275 | @return list 276 | properties: Name, Address, Quality, Channel, Frequency, Encryption, Signal Level, Noise Level, Bit Rates, Mode. 277 | """ 278 | 279 | # Here's a dictionary of rules that will be applied to the description 280 | # of each cell. The key will be the name of the column in the table. 281 | # The value is a function defined above. 282 | rules = rules or { 283 | "Name": get_name, 284 | "Quality": get_quality, 285 | "Channel": get_channel, 286 | "Frequency": get_frequency, 287 | "Encryption": get_encryption, 288 | "Address": get_address, 289 | "Signal Level": get_signal_level, 290 | "Noise Level": get_noise_level, 291 | "Bit Rates": get_bit_rates, 292 | "Mode": get_mode, 293 | } 294 | 295 | cells = [[]] 296 | parsed_cells = [] 297 | 298 | for line in iw_data: 299 | cell_line = match(line, "Cell ") 300 | if cell_line != None: 301 | cells.append([]) 302 | line = cell_line[-27:] 303 | cells[-1].append(line.rstrip()) 304 | 305 | cells = cells[1:] 306 | 307 | for cell in cells: 308 | parsed_cells.append(parse_cell(cell, rules)) 309 | 310 | sort_cells(parsed_cells) 311 | return parsed_cells 312 | 313 | def call_iwlist(interface='wlan0'): 314 | """ Get iwlist output via subprocess 315 | @param string interface 316 | interface to scan 317 | default is wlan0 318 | 319 | @return string 320 | properties: iwlist output 321 | """ 322 | return subprocess.check_output(['iwlist', interface, 'scanning']) 323 | 324 | def get_interfaces(interface="wlan0"): 325 | """ Get parsed iwlist output 326 | @param string interface 327 | interface to scan 328 | default is wlan0 329 | 330 | @param list columns 331 | default data attributes to return 332 | 333 | @return dict 334 | properties: dictionary of iwlist attributes 335 | """ 336 | return get_parsed_cells(call_iwlist(interface).split('\n')) 337 | 338 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Cuzzo Yahn 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | --------------------------------------------------------------------------------