From 07b6092cd59992f2efbf32defdc04f41ccba2ec7 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 13 Feb 2020 23:57:18 +0000 Subject: [PATCH 001/126] Add tox configuration file --- tox.ini | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ad19358 --- /dev/null +++ b/tox.ini @@ -0,0 +1,13 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py34, py35, py36, py37 + +[testenv] +deps = + pylint +commands = + python -m unittest discover From d5ad9a4233da58c7a79785284d3f1e4302539a77 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 00:01:34 +0000 Subject: [PATCH 002/126] Add flake8 to tox --- tox.ini | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ad19358..e7752a7 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,15 @@ # and then run "tox" from this directory. [tox] -envlist = py34, py35, py36, py37 +envlist = py34, py35, py36, py37, flake8 [testenv] deps = pylint commands = python -m unittest discover + +[testenv:flake8] +description = "Check ProbeQuest code style & quality" +deps = flake8 +commands = flake8 probequest/ From aa798882e6481cc6a9356f7d6df30faa2fd16515 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 00:07:41 +0000 Subject: [PATCH 003/126] Skip the missing versions of Python in tox --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index e7752a7..b7fe6ee 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ [tox] envlist = py34, py35, py36, py37, flake8 +skip_missing_interpreters = true [testenv] deps = From 2e4da759d9dd3a4eedbbd37d0fc4269d273cac99 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 00:08:40 +0000 Subject: [PATCH 004/126] Set the minimum version of tox required --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index b7fe6ee..ab54a7c 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ [tox] envlist = py34, py35, py36, py37, flake8 skip_missing_interpreters = true +minversion = 3.0 [testenv] deps = From e2f37f723e3dd9fab1d77e95cdba389150c3ae2e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 00:12:07 +0000 Subject: [PATCH 005/126] Add description to 'testenv' in 'tox.ini' --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index ab54a7c..f2eeb34 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ skip_missing_interpreters = true minversion = 3.0 [testenv] +description = "ProbeQuest unit tests" deps = pylint commands = From 8a26e4d93a382d2432ffe8deda10d68d43f5cbcd Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 00:39:15 +0000 Subject: [PATCH 006/126] Run Pylint via tox --- test/test.py | 22 ---------------------- tox.ini | 12 +++++++++--- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/test/test.py b/test/test.py index ba5d3ac..45dcc28 100644 --- a/test/test.py +++ b/test/test.py @@ -7,7 +7,6 @@ from queue import Queue import unittest -import pylint.lint from netaddr.core import AddrFormatError from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt @@ -429,24 +428,3 @@ def test_fuzz_packets(self): for i in range(0, 1000): packet = RadioTap()/fuzz(Dot11()/Dot11ProbeReq()/Dot11Elt()) ProbeRequestParser.parse(packet) - - -class TestLinter(unittest.TestCase): - """ - Unit tests for Python linters. - """ - - # Some linting errors will be fixed while - # refactoring the code. - @unittest.expectedFailure - def test_pylint(self): - """ - Executes Pylint. - """ - - # pylint: disable=no-self-use - - pylint.lint.Run([ - "probequest", - "test" - ]) diff --git a/tox.ini b/tox.ini index f2eeb34..46a2d60 100644 --- a/tox.ini +++ b/tox.ini @@ -4,18 +4,24 @@ # and then run "tox" from this directory. [tox] -envlist = py34, py35, py36, py37, flake8 +envlist = py34, py35, py36, py37, flake8, pylint skip_missing_interpreters = true minversion = 3.0 [testenv] description = "ProbeQuest unit tests" -deps = - pylint commands = python -m unittest discover [testenv:flake8] description = "Check ProbeQuest code style & quality" +basepython = python3.7 deps = flake8 commands = flake8 probequest/ + +[testenv:pylint] +description = "Check ProbeQuest for programming errors" +basepython = python3.7 +deps = pylint +changedir = test +commands = pylint probequest/ From 9ad1b71c766624a93ac2a3a789480ee93bc12c8b Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 00:57:18 +0000 Subject: [PATCH 007/126] Use tox instead of 'python3 setup.py test' --- .travis.yml | 4 ++-- docs/development.rst | 6 ++++-- setup.py | 4 ---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index f60bf94..ceae15a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ sudo: false language: python -install: +install: pip install tox-travis -script: python setup.py test +script: tox jobs: include: diff --git a/docs/development.rst b/docs/development.rst index d7a9a2c..a072c80 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -5,11 +5,11 @@ Development Running the unit tests ---------------------- -To run the unit tests: +`tox`_ is used to run the unit tests: :: - python3 setup.py test + tox Releasing a new version @@ -29,3 +29,5 @@ Below are the different steps to do before releasing a new version: After having pushed the new release: - Edit the release note on GitHub + +.. _tox: https://tox.readthedocs.io diff --git a/setup.py b/setup.py index 26d604c..a17bc44 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,6 @@ ], packages=find_packages(), scripts=['bin/probequest'], - test_suite='test', install_requires=[ 'argparse >= 1.4.0', 'faker_wifi_essid', @@ -53,9 +52,6 @@ 'scapy >= 2.4.3', 'urwid>= 2.0.1', ], - tests_require=[ - 'pylint' - ], extras_require={ 'docs': [ 'sphinx >= 1.4.0', From 20f2c1b3aab144e1dc6d748a0027259d34b0ab06 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 01:06:54 +0000 Subject: [PATCH 008/126] Add Python 3.8 to the Travis CI build matrix --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ceae15a..aff3003 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ jobs: - python: 3.6 - python: 3.7 dist: xenial + - python: 3.8 + dist: bionic - stage: PyPI release python: 3.7 deploy: From cc9e4aac62a6f7228e2f847770b5ece91a907309 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 01:10:12 +0000 Subject: [PATCH 009/126] Add Python 3.8 to the classifiers --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a17bc44..d98425f 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', ], packages=find_packages(), From 1d3bbab969df30557bd512f089b21f7af0659ca2 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 14 Feb 2020 01:13:23 +0000 Subject: [PATCH 010/126] Add Python 3.8 to tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 46a2d60..c411a65 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py34, py35, py36, py37, flake8, pylint +envlist = py34, py35, py36, py37, py38, flake8, pylint skip_missing_interpreters = true minversion = 3.0 From 93334a4617fee0153f20d47cb3bc39ba143e600e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 17 Feb 2020 23:02:08 +0000 Subject: [PATCH 011/126] Use Python 3 for Flake8 and Pylint via tox --- tox.ini | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index c411a65..8a954df 100644 --- a/tox.ini +++ b/tox.ini @@ -11,17 +11,19 @@ minversion = 3.0 [testenv] description = "ProbeQuest unit tests" commands = - python -m unittest discover + {envpython} -m unittest discover [testenv:flake8] description = "Check ProbeQuest code style & quality" -basepython = python3.7 +basepython = python3 deps = flake8 -commands = flake8 probequest/ +commands = + {envpython} -m flake8 probequest/ [testenv:pylint] description = "Check ProbeQuest for programming errors" -basepython = python3.7 +basepython = python3 deps = pylint changedir = test -commands = pylint probequest/ +commands = + {envpython} -m pylint probequest/ From 0c01e92906ebf17b01d4fd9ccd3d227f82fb44a8 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 17 Feb 2020 23:34:54 +0000 Subject: [PATCH 012/126] Fix some Pylint errors --- probequest/fake_packet_sniffer.py | 3 +-- probequest/ui/raw.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/probequest/fake_packet_sniffer.py b/probequest/fake_packet_sniffer.py index cdc6e71..47fbe9d 100644 --- a/probequest/fake_packet_sniffer.py +++ b/probequest/fake_packet_sniffer.py @@ -2,6 +2,7 @@ Fake packet sniffer module. """ +from time import sleep from threading import Thread, Event from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt @@ -29,8 +30,6 @@ def __init__(self, config, new_packets): self.fake.add_provider(WifiESSID) def run(self): - from time import sleep - while not self.stop_sniffer.isSet(): sleep(1) self.new_packet() diff --git a/probequest/ui/raw.py b/probequest/ui/raw.py index d8ddf02..924fd1f 100644 --- a/probequest/ui/raw.py +++ b/probequest/ui/raw.py @@ -5,6 +5,8 @@ Raw probe request viewer. """ +from csv import writer + from probequest.probe_request_sniffer import ProbeRequestSniffer @@ -17,8 +19,6 @@ def __init__(self, config): self.output = config.output_file if self.output is not None: - from csv import writer - outfile = writer(self.output, delimiter=";") def write_csv(probe_req): From fbe07e09940d4477cbd82ec84f0adcf4648a04cd Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 17 Feb 2020 23:57:52 +0000 Subject: [PATCH 013/126] Fix Pylint command called by tox causing error The ending '/' character in the Pylint command in the tox configuration has been removed because it caused the following error: C0103: Module name "probequest/" doesn't conform to snake_case naming style (invalid-name) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8a954df..f14b3a3 100644 --- a/tox.ini +++ b/tox.ini @@ -26,4 +26,4 @@ basepython = python3 deps = pylint changedir = test commands = - {envpython} -m pylint probequest/ + {envpython} -m pylint probequest From c199b588270564fa1372a5fe7c591160aa3942bf Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 18 Feb 2020 00:07:39 +0000 Subject: [PATCH 014/126] Set what env tox will run on Travis CI The environments 'flake8' and 'pylint' will run under Python 3.7 on Travis CI. See https://tox-travis.readthedocs.io/en/stable/envlist.html for further information. --- tox.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tox.ini b/tox.ini index f14b3a3..fcf9a55 100644 --- a/tox.ini +++ b/tox.ini @@ -27,3 +27,11 @@ deps = pylint changedir = test commands = {envpython} -m pylint probequest + +[travis] +python = + 3.4: py34 + 3.5: py35 + 3.6: py36 + 3.7: py37, flake8, pylint + 3.8: py38 From ded6b5e2ce56be7ecab7cec245effabd564f9caa Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 18 Feb 2020 00:18:33 +0000 Subject: [PATCH 015/126] Add description to 'travis' in 'tox.ini' --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index fcf9a55..ab0500b 100644 --- a/tox.ini +++ b/tox.ini @@ -29,6 +29,7 @@ commands = {envpython} -m pylint probequest [travis] +description = "tox configuration when running on Travis CI" python = 3.4: py34 3.5: py35 From a0a072f3ffb0c59010cf4836f963d481628a8c57 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 12 Feb 2020 23:19:26 +0000 Subject: [PATCH 016/126] Use double quotes in 'setup.py' --- setup.py | 60 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/setup.py b/setup.py index d98425f..68975af 100755 --- a/setup.py +++ b/setup.py @@ -16,49 +16,49 @@ DIR = dirname(abspath(__file__)) -with fopen(join(DIR, 'README.rst'), encoding='utf-8') as f: +with fopen(join(DIR, "README.rst"), encoding="utf-8") as f: LONG_DESCRIPTION = f.read() setup( - name='probequest', + name="probequest", version=VERSION, - description='Toolkit for Playing with Wi-Fi Probe Requests', + description="Toolkit for Playing with Wi-Fi Probe Requests", long_description=LONG_DESCRIPTION, - license='GPLv3', - keywords='wifi wireless security sniffer', - author='Paul-Emmanuel Raoul', - author_email='skyper@skyplabs.net', - url='https://github.com/SkypLabs/probequest', - download_url='https://github.com/SkypLabs/probequest/archive/v{0}.zip' + license="GPLv3", + keywords="wifi wireless security sniffer", + author="Paul-Emmanuel Raoul", + author_email="skyper@skyplabs.net", + url="https://github.com/SkypLabs/probequest", + download_url="https://github.com/SkypLabs/probequest/archive/v{0}.zip" .format(VERSION), classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", ], packages=find_packages(), - scripts=['bin/probequest'], + scripts=["bin/probequest"], install_requires=[ - 'argparse >= 1.4.0', - 'faker_wifi_essid', - 'netaddr >= 0.7.19', - 'scapy >= 2.4.3', - 'urwid>= 2.0.1', + "argparse >= 1.4.0", + "faker_wifi_essid", + "netaddr >= 0.7.19", + "scapy >= 2.4.3", + "urwid>= 2.0.1", ], extras_require={ - 'docs': [ - 'sphinx >= 1.4.0', - 'sphinxcontrib-seqdiag >= 0.8.5', - 'sphinx-argparse >= 0.2.2', - 'sphinx_rtd_theme', + "docs": [ + "sphinx >= 1.4.0", + "sphinxcontrib-seqdiag >= 0.8.5", + "sphinx-argparse >= 0.2.2", + "sphinx_rtd_theme", ], }, ) From 8a7d65c83fcdfe57341be824e1890e6a740414eb Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 12 Feb 2020 23:25:55 +0000 Subject: [PATCH 017/126] Update classifiers in 'setup.py' --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 68975af..62e30eb 100755 --- a/setup.py +++ b/setup.py @@ -34,8 +34,10 @@ .format(VERSION), classifiers=[ "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries :: Python Modules", + "Environment :: Console", + "Intended Audience :: Information Technology", + "Natural Language :: English", + "Topic :: Security", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", From 2cf7b2cedca8be5b89dd6145edd936582db737be Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 12 Feb 2020 23:34:01 +0000 Subject: [PATCH 018/126] Test if Setuptools is installed --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 62e30eb..09299bc 100755 --- a/setup.py +++ b/setup.py @@ -8,9 +8,14 @@ See https://setuptools.readthedocs.io. """ +try: + from setuptools import setup, find_packages +except Exception: + raise ImportError("Setuptools is required to install ProbeQuest!") + + from codecs import open as fopen from os.path import dirname, abspath, join -from setuptools import setup, find_packages from probequest.version import VERSION From 7a58f683a8cbddb0a2534f155ca4bf6f6288a55f Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 12 Feb 2020 23:38:08 +0000 Subject: [PATCH 019/126] Add some project URLs in 'setup.py' --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 09299bc..7eb7599 100755 --- a/setup.py +++ b/setup.py @@ -37,6 +37,10 @@ url="https://github.com/SkypLabs/probequest", download_url="https://github.com/SkypLabs/probequest/archive/v{0}.zip" .format(VERSION), + project_urls={ + "Documentation": "https://probequest.readthedocs.io", + "Source Code": "https://github.com/SkypLabs/probequest", + }, classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", From d3b05963b598bc1c1cf1c74e879974fb7a116b12 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 12 Feb 2020 23:41:30 +0000 Subject: [PATCH 020/126] Add 'python_requires' to 'setup.py' --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7eb7599..d64b722 100755 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ ], packages=find_packages(), scripts=["bin/probequest"], + python_requires=">=3.4, <4", install_requires=[ "argparse >= 1.4.0", "faker_wifi_essid", From 58bac9e214917083fcb8f7e9405741862d586923 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 12 Feb 2020 23:43:17 +0000 Subject: [PATCH 021/126] Add topics to the classifiers --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index d64b722..f6677e9 100755 --- a/setup.py +++ b/setup.py @@ -47,6 +47,8 @@ "Intended Audience :: Information Technology", "Natural Language :: English", "Topic :: Security", + "Topic :: System :: Networking", + "Topic :: System :: Networking :: Monitoring", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", From 4f6049d18ec73a565d237645b3d2d0a5d9d9f9f0 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 13 Mar 2020 15:09:06 +0000 Subject: [PATCH 022/126] Add Ko-fi, PayPal and BuyMeACoffee sponsor links --- .github/FUNDING.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 5d517c8..3eb3cd0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ -custom: https://blog.skyplabs.net/support/ +ko_fi: skyplabs +custom: ["https://blog.skyplabs.net/support/", "https://www.buymeacoffee.com/skyplabs", "https://paypal.me/skyplabs"] From a895ae441797fc9fc96831906a342a7fb82678b3 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 4 Apr 2020 23:19:40 +0100 Subject: [PATCH 023/126] Add GitHub Sponsors link --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 3eb3cd0..0538874 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ +github: SkypLabs ko_fi: skyplabs custom: ["https://blog.skyplabs.net/support/", "https://www.buymeacoffee.com/skyplabs", "https://paypal.me/skyplabs"] From e6b9566bb6621252cbac8242213d958fb069fa25 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 25 Sep 2020 18:56:43 +0100 Subject: [PATCH 024/126] Fix 'import-outside-toplevel' pylint issues --- bin/probequest | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/bin/probequest b/bin/probequest index a2ae32e..e474dfa 100755 --- a/bin/probequest +++ b/bin/probequest @@ -9,9 +9,14 @@ your wireless interface. # pylint: disable=no-name-in-module from argparse import ArgumentParser, FileType +from os import geteuid +from sys import exit as sys_exit +from time import sleep from probequest.config import Config, Mode from probequest.version import VERSION +from probequest.ui.pnl import PNLViewer +from probequest.ui.raw import RawProbeRequestViewer def get_arg_parser(): @@ -95,9 +100,6 @@ def main(): Main function. """ - from os import geteuid - from sys import exit as sys_exit - config = Config() get_arg_parser().parse_args(namespace=config) @@ -106,9 +108,6 @@ def main(): # Default mode. if config.mode == Mode.RAW: - from time import sleep - from probequest.ui.raw import RawProbeRequestViewer - try: print("[*] Start sniffing probe requests...") raw_viewer = RawProbeRequestViewer(config) @@ -128,8 +127,6 @@ def main(): raw_viewer.stop() print("[*] Bye!") elif config.mode == Mode.PNL: - from probequest.ui.pnl import PNLViewer - try: pnl_viewer = PNLViewer(config) pnl_viewer.main() From 83bb173fe4d6c6dcf026350990baff9b60a141de Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 25 Sep 2020 19:42:46 +0100 Subject: [PATCH 025/126] Use an entry point to generate the CLI tool See the entry points specification: https://packaging.python.org/specifications/entry-points/. --- bin/probequest => probequest/main.py | 11 ++++------- setup.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) rename bin/probequest => probequest/main.py (94%) mode change 100755 => 100644 diff --git a/bin/probequest b/probequest/main.py old mode 100755 new mode 100644 similarity index 94% rename from bin/probequest rename to probequest/main.py index e474dfa..9065e36 --- a/bin/probequest +++ b/probequest/main.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - """ Toolkit allowing to sniff and display the Wi-Fi probe requests passing nearby your wireless interface. @@ -13,10 +10,10 @@ from sys import exit as sys_exit from time import sleep -from probequest.config import Config, Mode -from probequest.version import VERSION -from probequest.ui.pnl import PNLViewer -from probequest.ui.raw import RawProbeRequestViewer +from .config import Config, Mode +from .ui.pnl import PNLViewer +from .ui.raw import RawProbeRequestViewer +from .version import VERSION def get_arg_parser(): diff --git a/setup.py b/setup.py index f6677e9..5d0967c 100755 --- a/setup.py +++ b/setup.py @@ -10,8 +10,10 @@ try: from setuptools import setup, find_packages -except Exception: - raise ImportError("Setuptools is required to install ProbeQuest!") +except Exception as setuptools_not_present: + raise ImportError( + "Setuptools is required to install ProbeQuest!" + ) from setuptools_not_present from codecs import open as fopen @@ -58,7 +60,11 @@ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", ], packages=find_packages(), - scripts=["bin/probequest"], + entry_points={ + "console_scripts": [ + "probequest = probequest.main:main", + ] + }, python_requires=">=3.4, <4", install_requires=[ "argparse >= 1.4.0", From 6f2d8bebebe2859a93c55688231db3bd695cf044 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 25 Sep 2020 22:47:51 +0100 Subject: [PATCH 026/126] Add top-level module See https://docs.python.org/3/library/__main__.html. --- probequest/__main__.py | 9 +++++++++ probequest/main.py | 11 ++--------- 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 probequest/__main__.py diff --git a/probequest/__main__.py b/probequest/__main__.py new file mode 100644 index 0000000..a27cf2c --- /dev/null +++ b/probequest/__main__.py @@ -0,0 +1,9 @@ +""" +Top-level module. + +Executes the command-line tool when run as a script or with 'python -m'. +""" + +from probequest.main import main + +main() diff --git a/probequest/main.py b/probequest/main.py index 9065e36..13f19d1 100644 --- a/probequest/main.py +++ b/probequest/main.py @@ -1,10 +1,7 @@ """ -Toolkit allowing to sniff and display the Wi-Fi probe requests passing nearby -your wireless interface. +Main module. """ -# pylint: disable=no-name-in-module - from argparse import ArgumentParser, FileType from os import geteuid from sys import exit as sys_exit @@ -94,7 +91,7 @@ def get_arg_parser(): def main(): """ - Main function. + Entry point of the command-line tool. """ config = Config() @@ -137,7 +134,3 @@ def main(): pnl_viewer.sniffer.stop() else: sys_exit("[x] Invalid mode") - - -if __name__ == "__main__": - main() From 643a544eeb4cc59bcac4349d205e2f79d7b5cc5a Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 25 Sep 2020 22:59:37 +0100 Subject: [PATCH 027/126] Fix a linting issue (E0307) --- probequest/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/probequest/config.py b/probequest/config.py index 60d173c..cb2a440 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -19,7 +19,7 @@ class Mode(Enum): PNL = "pnl" def __str__(self): - return self.value + return str(self.value) class Config: From e4df31d14c9e42f07a0a75ec043b4bfb1bc92cf8 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 25 Sep 2020 23:14:37 +0100 Subject: [PATCH 028/126] Drop support for Python 3.4 "Python 3.4 has reached end-of-life. Python 3.4.10 is the final release of 3.4." https://www.python.org/downloads/release/python-3410/ Furthermore: "Starting from version 4.0.0, Faker dropped support for Python 2 and only supports Python 3.5 and above." https://github.com/joke2k/faker#compatibility --- .travis.yml | 1 - setup.py | 3 +-- tox.ini | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index aff3003..8987521 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ script: tox jobs: include: - - python: 3.4 - python: 3.5 - python: 3.6 - python: 3.7 diff --git a/setup.py b/setup.py index 5d0967c..6a1a787 100755 --- a/setup.py +++ b/setup.py @@ -52,7 +52,6 @@ "Topic :: System :: Networking", "Topic :: System :: Networking :: Monitoring", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", @@ -65,7 +64,7 @@ "probequest = probequest.main:main", ] }, - python_requires=">=3.4, <4", + python_requires=">=3.5, <4", install_requires=[ "argparse >= 1.4.0", "faker_wifi_essid", diff --git a/tox.ini b/tox.ini index ab0500b..2858278 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py34, py35, py36, py37, py38, flake8, pylint +envlist = py35, py36, py37, py38, flake8, pylint skip_missing_interpreters = true minversion = 3.0 @@ -31,7 +31,6 @@ commands = [travis] description = "tox configuration when running on Travis CI" python = - 3.4: py34 3.5: py35 3.6: py36 3.7: py37, flake8, pylint From 0462d0918e543041a86633674d960787900bc125 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 25 Sep 2020 23:41:19 +0100 Subject: [PATCH 029/126] Add 'ci/cd' issue tag to GitHub --- .github/settings.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/settings.yml b/.github/settings.yml index 91eef3b..b3efe09 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -84,6 +84,10 @@ labels: description: Documentation-related issue color: 2d2de2 + - name: ci/cd + description: CI/CD-related issue + color: e85733 + - name: good first issue description: Good first issue color: 7057ff From 9c3d4219ce8d6504b93b44fb8bb9504d2d3ce298 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 00:43:49 +0100 Subject: [PATCH 030/126] Include stage 'PyPI release' only if tag on master --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8987521..4796253 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ jobs: - python: 3.8 dist: bionic - stage: PyPI release + if: (branch = master) AND (tag IS present) python: 3.7 deploy: provider: pypi From c7656a002d2a25c556354a26cf76983c65f749e7 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 00:49:31 +0100 Subject: [PATCH 031/126] Remove the deprecated 'sudo' attribute https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4796253..5624207 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ -sudo: false - language: python install: pip install tox-travis From f6e64d049c88d1546b247e29e3a47fc50417f489 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 11:15:53 +0100 Subject: [PATCH 032/126] Rename 'optimisation' issue label to 'enhancement' --- .github/settings.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index b3efe09..e262eaa 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -17,11 +17,11 @@ repository: labels: - name: feature - oldname: enhancement description: New feature color: 84b6eb - - name: optimisation - description: Optimisation + - name: enhancement + oldname: optimisation + description: Enhancement color: 84b6eb - name: refactor description: Refactoring From b9da66dc8a7da1051786df633207afd4a7b9062b Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 11:30:09 +0100 Subject: [PATCH 033/126] Add 'testing' GH issue label --- .github/settings.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/settings.yml b/.github/settings.yml index e262eaa..26d5bf6 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -84,6 +84,10 @@ labels: description: Documentation-related issue color: 2d2de2 + - name: testing + description: Related to software testing + color: efa5ef + - name: ci/cd description: CI/CD-related issue color: e85733 From 7430c1322f2a7082e3041bf49e1572441590c7fa Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 12:04:06 +0100 Subject: [PATCH 034/126] Add 'packaging' GitHub issue label --- .github/settings.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/settings.yml b/.github/settings.yml index 26d5bf6..13eaec0 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -84,6 +84,10 @@ labels: description: Documentation-related issue color: 2d2de2 + - name: packaging + description: Related to software packaging + color: 31f427 + - name: testing description: Related to software testing color: efa5ef From b72e6bfc0bff6007a03da4f1c6da0880feb07c43 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 12:05:15 +0100 Subject: [PATCH 035/126] Remove 'oldname' attribute from 'enhancement' --- .github/settings.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/settings.yml b/.github/settings.yml index 13eaec0..e475e8c 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -20,7 +20,6 @@ labels: description: New feature color: 84b6eb - name: enhancement - oldname: optimisation description: Enhancement color: 84b6eb - name: refactor From 740f27e00505f695076b97075c11a686baf294c3 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 12:07:25 +0100 Subject: [PATCH 036/126] Add the 'exporters' GitHub issue label --- .github/settings.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/settings.yml b/.github/settings.yml index e475e8c..de1ae5c 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -35,6 +35,9 @@ labels: - name: ui description: Related to the user interface color: 1d76db + - name: exporters + description: Related to the exporters + color: 1d76db - name: android description: Android platform support issues From f7a2f5477df473ecb3e096e333566cd0343d240a Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 12:08:57 +0100 Subject: [PATCH 037/126] Update some label descriptions --- .github/settings.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index de1ae5c..8c79ffd 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -83,7 +83,7 @@ labels: color: 33aa3f - name: documentation - description: Documentation-related issue + description: Related to the documentation color: 2d2de2 - name: packaging @@ -95,7 +95,7 @@ labels: color: efa5ef - name: ci/cd - description: CI/CD-related issue + description: Related to CI/CD color: e85733 - name: good first issue From 3dabe036743fd2fbea1b4c2eab181c4a45695716 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 12:27:19 +0100 Subject: [PATCH 038/126] Add the 'cli' GitHub issue label --- .github/settings.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/settings.yml b/.github/settings.yml index 8c79ffd..71b84a5 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -38,6 +38,9 @@ labels: - name: exporters description: Related to the exporters color: 1d76db + - name: cli + description: Related to the CLI tool + color: 1d76db - name: android description: Android platform support issues From 6bb5687d4962fd550a0e0e4b53ee344abb63026e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 12:37:42 +0100 Subject: [PATCH 039/126] Add 'Documentation' stage to Travis CI --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5624207..f1ab6bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,11 @@ jobs: dist: xenial - python: 3.8 dist: bionic - - stage: PyPI release + - stage: "Documentation" + python: 3.8 + install: pip install .[docs] + script: cd docs; make html + - stage: "PyPI release" if: (branch = master) AND (tag IS present) python: 3.7 deploy: From e6bfaa06e92ab645d1f222134728aff8b61779fe Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 13:41:54 +0100 Subject: [PATCH 040/126] Add name to stage 'Unit Tests' --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f1ab6bd..fcd52cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ script: tox jobs: include: + - stage: "Unit Tests" - python: 3.5 - python: 3.6 - python: 3.7 From be00f4706996d16cdcc631d95be56f0905e73b59 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 14:00:28 +0100 Subject: [PATCH 041/126] Fix call to argparse extension in documentation --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 859eace..2cea12d 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -49,7 +49,7 @@ Command line arguments ---------------------- .. argparse:: - :filename: ../bin/probequest + :module: probequest.main :func: get_arg_parser :prog: probequest From e092795fc0caa8b49d885e7149093f08952a1a1d Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 14:17:44 +0100 Subject: [PATCH 042/126] Remove extra unnecessary build in Travis CI A mistake in the Travis CI configuration resulted in an extra unnecessary build in the "Unit Tests" stage. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fcd52cb..cdee499 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ script: tox jobs: include: - stage: "Unit Tests" - - python: 3.5 + python: 3.5 - python: 3.6 - python: 3.7 dist: xenial From 9dd78745a6b0eed7478188c8cccf5b366c8e2ae9 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 14:48:56 +0100 Subject: [PATCH 043/126] Use default distribution in Travis CI --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cdee499..aecc19d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,7 @@ jobs: python: 3.5 - python: 3.6 - python: 3.7 - dist: xenial - python: 3.8 - dist: bionic - stage: "Documentation" python: 3.8 install: pip install .[docs] From 0a92ffa9e48a803218abc58d4543687065bb8d03 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 15:01:44 +0100 Subject: [PATCH 044/126] Use Python 3.8 in 'PyPI Release' --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index aecc19d..5f43995 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,15 +15,15 @@ jobs: python: 3.8 install: pip install .[docs] script: cd docs; make html - - stage: "PyPI release" + - stage: "PyPI Release" if: (branch = master) AND (tag IS present) - python: 3.7 + python: 3.8 deploy: provider: pypi distributions: "sdist bdist_wheel" user: skyplabs password: - secure: AAdbZ/WxjoCcGV9BAvphrxNp48D3n6yt1FEWZwA2V1BK2SnVV6YwQX/r7ryat2uoYrTONnGf/P5QgXc2wThl5mzPiRMaaONOIkyrmi2ZCD86yHYy2QQPR1gO1QOYyIsx545FRB6PaGXBC99Hw30vh3HPQaCKoQ8/PY+u3QbVn47BSBPuIdBi/FLl81uYk0ZE1457TxHkYrSDheRA3JVs72oD+izeoS+JJ+1YX+2zreukk9xuNfuwtcXPAn5B8LH8yYkFp1dd2pRsQzYMB4aH3rz7QEzSZ0mLBr3/J2bFCldmT8NToRijIjZNk04ik8XlGQ7xmpYG9rYIAkWwSBYSsZbfeLBxoxxIBcUy9xVxveZSaNl7TcRchCsyVO8leuL9aLmKz3wuKhWCRxJQSUDLlo83LYoBaTifqstUO85gC8IxR/Y/yqkW8wfSfVgVaDi9ET3/7UgSZEQJFEqfiYGdnD6/IkAy2tUCRO5xNsXsOyVJ5A0CsDTTtvlEfGxf1UtQyt0BmRSGYLMTnDBStW1Oua2QfVcTKIJOdfEOyL/VWnn/f0RCJQiUkRo9OFmSywoFjgSC9Arejwsff5smEd5i/jTKk6rOoHgIMnAGxn+75BjF3vQ7usAJeEOlLzHB5puc5dKeCpn5rwxOHha1lfmr6kDs1ec5XhcgQKgvulYfjVQ= + secure: "AAdbZ/WxjoCcGV9BAvphrxNp48D3n6yt1FEWZwA2V1BK2SnVV6YwQX/r7ryat2uoYrTONnGf/P5QgXc2wThl5mzPiRMaaONOIkyrmi2ZCD86yHYy2QQPR1gO1QOYyIsx545FRB6PaGXBC99Hw30vh3HPQaCKoQ8/PY+u3QbVn47BSBPuIdBi/FLl81uYk0ZE1457TxHkYrSDheRA3JVs72oD+izeoS+JJ+1YX+2zreukk9xuNfuwtcXPAn5B8LH8yYkFp1dd2pRsQzYMB4aH3rz7QEzSZ0mLBr3/J2bFCldmT8NToRijIjZNk04ik8XlGQ7xmpYG9rYIAkWwSBYSsZbfeLBxoxxIBcUy9xVxveZSaNl7TcRchCsyVO8leuL9aLmKz3wuKhWCRxJQSUDLlo83LYoBaTifqstUO85gC8IxR/Y/yqkW8wfSfVgVaDi9ET3/7UgSZEQJFEqfiYGdnD6/IkAy2tUCRO5xNsXsOyVJ5A0CsDTTtvlEfGxf1UtQyt0BmRSGYLMTnDBStW1Oua2QfVcTKIJOdfEOyL/VWnn/f0RCJQiUkRo9OFmSywoFjgSC9Arejwsff5smEd5i/jTKk6rOoHgIMnAGxn+75BjF3vQ7usAJeEOlLzHB5puc5dKeCpn5rwxOHha1lfmr6kDs1ec5XhcgQKgvulYfjVQ=" on: branch: master tags: true From 29fd01c17e8dacebe1123ff39743372c5abb0c0e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 22:55:54 +0100 Subject: [PATCH 045/126] Split unit tests into multiple files --- test/test.py | 430 ------------------------ test/unit/__init__.py | 0 test/unit/test_config.py | 111 ++++++ test/unit/test_fake_packet_sniffer.py | 64 ++++ test/unit/test_packet_sniffer.py | 71 ++++ test/unit/test_probe_request.py | 94 ++++++ test/unit/test_probe_request_parser.py | 66 ++++ test/unit/test_probe_request_sniffer.py | 56 +++ 8 files changed, 462 insertions(+), 430 deletions(-) delete mode 100644 test/test.py create mode 100644 test/unit/__init__.py create mode 100644 test/unit/test_config.py create mode 100644 test/unit/test_fake_packet_sniffer.py create mode 100644 test/unit/test_packet_sniffer.py create mode 100644 test/unit/test_probe_request.py create mode 100644 test/unit/test_probe_request_parser.py create mode 100644 test/unit/test_probe_request_sniffer.py diff --git a/test/test.py b/test/test.py deleted file mode 100644 index 45dcc28..0000000 --- a/test/test.py +++ /dev/null @@ -1,430 +0,0 @@ -""" -Unit tests written with the 'unittest' module. -""" - -# pylint: disable=import-error -# pylint: disable=unused-variable - -from queue import Queue -import unittest -from netaddr.core import AddrFormatError - -from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt -from scapy.packet import fuzz -from scapy.error import Scapy_Exception - -from probequest.config import Config -from probequest.probe_request import ProbeRequest -from probequest.probe_request_sniffer import ProbeRequestSniffer -from probequest.packet_sniffer import PacketSniffer -from probequest.fake_packet_sniffer import FakePacketSniffer -from probequest.probe_request_parser import ProbeRequestParser - - -class TestProbeRequest(unittest.TestCase): - """ - Unit tests for the 'ProbeRequest' class. - """ - - def test_without_parameters(self): - """ - Initialises a 'ProbeRequest' object without any parameter. - """ - - # pylint: disable=no-value-for-parameter - - with self.assertRaises(TypeError): - probe_req = ProbeRequest() # noqa: F841 - - def test_with_only_one_parameter(self): - """ - Initialises a 'ProbeRequest' object with only one parameter. - """ - - # pylint: disable=no-value-for-parameter - - timestamp = 1517872027.0 - - with self.assertRaises(TypeError): - probe_req = ProbeRequest(timestamp) # noqa: F841 - - def test_with_only_two_parameters(self): - """ - Initialises a 'ProbeRequest' object with only two parameters. - """ - - # pylint: disable=no-value-for-parameter - - timestamp = 1517872027.0 - s_mac = "aa:bb:cc:dd:ee:ff" - - with self.assertRaises(TypeError): - probe_req = ProbeRequest(timestamp, s_mac) # noqa: F841 - - def test_create_a_probe_request(self): - """ - Creates a new 'ProbeRequest' with all the required parameters. - """ - - # pylint: disable=no-self-use - - timestamp = 1517872027.0 - s_mac = "aa:bb:cc:dd:ee:ff" - essid = "Test ESSID" - - probe_req = ProbeRequest(timestamp, s_mac, essid) # noqa: F841 - - def test_bad_mac_address(self): - """ - Initialises a 'ProbeRequest' object with a malformed MAC address. - """ - - timestamp = 1517872027.0 - s_mac = "aa:bb:cc:dd:ee" - essid = "Test ESSID" - - with self.assertRaises(AddrFormatError): - probe_req = ProbeRequest(timestamp, s_mac, essid) # noqa: F841 - - def test_print_a_probe_request(self): - """ - Initialises a 'ProbeRequest' object and prints it. - """ - - timestamp = 1517872027.0 - s_mac = "aa:bb:cc:dd:ee:ff" - essid = "Test ESSID" - - probe_req = ProbeRequest(timestamp, s_mac, essid) - - self.assertNotEqual( - str(probe_req).find("Mon, 05 Feb 2018 23:07:07"), - -1 - ) - self.assertNotEqual( - str(probe_req).find("aa:bb:cc:dd:ee:ff (None) -> Test ESSID"), - -1 - ) - - -class TestConfig(unittest.TestCase): - """ - Unit tests for the 'Config' class. - """ - - def test_bad_display_function(self): - """ - Assigns a non-callable object to the display callback function. - """ - - with self.assertRaises(TypeError): - config = Config() - config.display_func = "test" - - def test_bad_storage_function(self): - """ - Assigns a non-callable object to the storage callback function. - """ - - with self.assertRaises(TypeError): - config = Config() - config.storage_func = "test" - - def test_default_frame_filter(self): - """ - Tests the default frame filter. - """ - - config = Config() - frame_filter = config.generate_frame_filter() - - self.assertEqual( - frame_filter, - "type mgt subtype probe-req" - ) - - def test_frame_filter_with_mac_filtering(self): - """ - Tests the frame filter when some MAC addresses need to be filtered. - """ - - config = Config() - config.mac_filters = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] - frame_filter = config.generate_frame_filter() - - self.assertEqual( - frame_filter, - "type mgt subtype probe-req" + - " and (ether src host a4:77:33:9a:73:5c" + - "|| ether src host b0:05:94:5d:5a:4d)" - ) - - def test_frame_filter_with_mac_exclusion(self): - """ - Tests the frame filter when some MAC addresses need to be excluded. - """ - - config = Config() - config.mac_exclusions = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] - frame_filter = config.generate_frame_filter() - - self.assertEqual( - frame_filter, - "type mgt subtype probe-req" + - " and not (ether src host a4:77:33:9a:73:5c" + - "|| ether src host b0:05:94:5d:5a:4d)" - ) - - def test_compile_essid_regex_with_an_empty_regex(self): - """ - Tests 'complile_essid_regex' with an empty regex. - """ - - config = Config() - compiled_regex = config.complile_essid_regex() - - self.assertEqual(compiled_regex, None) - - def test_compile_essid_regex_with_a_case_sensitive_regex(self): - """ - Tests 'complile_essid_regex' with a case-sensitive regex. - """ - - from re import compile as rcompile - - config = Config() - config.essid_regex = "Free Wi-Fi" - compiled_regex = config.complile_essid_regex() - - self.assertEqual(compiled_regex, rcompile(config.essid_regex)) - - def test_compile_essid_regex_with_a_case_insensitive_regex(self): - """ - Tests 'complile_essid_regex' with a case-insensitive regex. - """ - - from re import compile as rcompile, IGNORECASE - - config = Config() - config.essid_regex = "Free Wi-Fi" - config.ignore_case = True - compiled_regex = config.complile_essid_regex() - - self.assertEqual(compiled_regex, rcompile( - config.essid_regex, IGNORECASE)) - - -class TestProbeRequestSniffer(unittest.TestCase): - """ - Unit tests for the 'ProbeRequestSniffer' class. - """ - - def test_without_parameters(self): - """ - Initialises a 'ProbeRequestSniffer' object without parameters. - """ - - # pylint: disable=no-value-for-parameter - - with self.assertRaises(TypeError): - sniffer = ProbeRequestSniffer() # noqa: F841 - - def test_bad_parameter(self): - """ - Initialises a 'ProbeRequestSniffer' object with a bad parameter. - """ - - # pylint: disable=no-value-for-parameter - - with self.assertRaises(AttributeError): - sniffer = ProbeRequestSniffer("test") # noqa: F841 - - def test_create_sniffer(self): - """ - Creates a 'ProbeRequestSniffer' object with the correct parameter. - """ - - # pylint: disable=no-self-use - - config = Config() - sniffer = ProbeRequestSniffer(config) # noqa: F841 - - def test_stop_before_start(self): - """ - Creates a 'ProbeRequestSniffer' object and stops the sniffer before - starting it. - """ - - # pylint: disable=no-self-use - - config = Config() - sniffer = ProbeRequestSniffer(config) - sniffer.stop() - - -class TestPacketSniffer(unittest.TestCase): - """ - Unit tests for the 'PacketSniffer' class. - """ - - def test_new_packet(self): - """ - Tests the 'new_packet' method. - """ - - config = Config() - new_packets = Queue() - sniffer = PacketSniffer(config, new_packets) - - self.assertEqual(sniffer.new_packets.qsize(), 0) - - packet = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2="aa:bb:cc:11:22:33", - addr3="dd:ee:ff:11:22:33" - ) \ - / Dot11ProbeReq() \ - / Dot11Elt( - info="Test" - ) - - sniffer.new_packet(packet) - self.assertEqual(sniffer.new_packets.qsize(), 1) - - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - - def test_stop_before_start(self): - """ - Creates a 'PacketSniffer' object and stops the sniffer before starting - it. - """ - - config = Config() - new_packets = Queue() - sniffer = PacketSniffer(config, new_packets) - - with self.assertRaises(Scapy_Exception): - sniffer.stop() - - def test_is_running_before_start(self): - """ - Creates a 'PacketSniffer' object and runs 'is_running' before starting - the sniffer. - """ - - config = Config() - new_packets = Queue() - sniffer = PacketSniffer(config, new_packets) - - self.assertFalse(sniffer.is_running()) - - -class TestFakePacketSniffer(unittest.TestCase): - """ - Unit tests for the 'FakePacketSniffer' class. - """ - - def test_new_packet(self): - """ - Tests the 'new_packet' method. - """ - - config = Config() - new_packets = Queue() - sniffer = FakePacketSniffer(config, new_packets) - - self.assertEqual(sniffer.new_packets.qsize(), 0) - - sniffer.new_packet() - self.assertEqual(sniffer.new_packets.qsize(), 1) - sniffer.new_packet() - self.assertEqual(sniffer.new_packets.qsize(), 2) - sniffer.new_packet() - self.assertEqual(sniffer.new_packets.qsize(), 3) - - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - - def test_stop_before_start(self): - """ - Creates a 'FakePacketSniffer' object and stops the sniffer before - starting it. - """ - - config = Config() - new_packets = Queue() - sniffer = FakePacketSniffer(config, new_packets) - - with self.assertRaises(RuntimeError): - sniffer.stop() - - def test_stop_before_start_using_join(self): - """ - Creates a 'FakePacketSniffer' object and stops the sniffer before - starting it. - """ - - config = Config() - new_packets = Queue() - sniffer = FakePacketSniffer(config, new_packets) - - with self.assertRaises(RuntimeError): - sniffer.join() - - -class TestProbeRequestParser(unittest.TestCase): - """ - Unit tests for the 'ProbeRequestParser' class. - """ - - def test_no_probe_request_layer(self): - """ - Creates a non-probe-request Wi-Fi packet and parses it with the - 'ProbeRequestParser.parse()' function. - """ - - # pylint: disable=no-self-use - - packet = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2="aa:bb:cc:11:22:33", - addr3="dd:ee:ff:11:22:33" - ) - - ProbeRequestParser.parse(packet) - - def test_empty_essid(self): - """ - Creates a probe request packet with an empty ESSID field and parses - it with the 'ProbeRequestParser.parse()' function. - """ - - # pylint: disable=no-self-use - - packet = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2="aa:bb:cc:11:22:33", - addr3="dd:ee:ff:11:22:33" - ) \ - / Dot11ProbeReq() \ - / Dot11Elt( - info="" - ) - - ProbeRequestParser.parse(packet) - - def test_fuzz_packets(self): - """ - Parses 1000 randomly-generated probe requests with the - 'ProbeRequestParser.parse()' function. - """ - - # pylint: disable=no-self-use - - for i in range(0, 1000): - packet = RadioTap()/fuzz(Dot11()/Dot11ProbeReq()/Dot11Elt()) - ProbeRequestParser.parse(packet) diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/test_config.py b/test/unit/test_config.py new file mode 100644 index 0000000..2f1ae4c --- /dev/null +++ b/test/unit/test_config.py @@ -0,0 +1,111 @@ +""" +Unit tests for the configuration module. +""" + +import unittest +from re import compile as rcompile, IGNORECASE + +from probequest.config import Config + + +class TestConfig(unittest.TestCase): + """ + Unit tests for the 'Config' class. + """ + + def test_bad_display_function(self): + """ + Assigns a non-callable object to the display callback function. + """ + + with self.assertRaises(TypeError): + config = Config() + config.display_func = "test" + + def test_bad_storage_function(self): + """ + Assigns a non-callable object to the storage callback function. + """ + + with self.assertRaises(TypeError): + config = Config() + config.storage_func = "test" + + def test_default_frame_filter(self): + """ + Tests the default frame filter. + """ + + config = Config() + frame_filter = config.generate_frame_filter() + + self.assertEqual( + frame_filter, + "type mgt subtype probe-req" + ) + + def test_frame_filter_with_mac_filtering(self): + """ + Tests the frame filter when some MAC addresses need to be filtered. + """ + + config = Config() + config.mac_filters = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] + frame_filter = config.generate_frame_filter() + + self.assertEqual( + frame_filter, + "type mgt subtype probe-req" + + " and (ether src host a4:77:33:9a:73:5c" + + "|| ether src host b0:05:94:5d:5a:4d)" + ) + + def test_frame_filter_with_mac_exclusion(self): + """ + Tests the frame filter when some MAC addresses need to be excluded. + """ + + config = Config() + config.mac_exclusions = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] + frame_filter = config.generate_frame_filter() + + self.assertEqual( + frame_filter, + "type mgt subtype probe-req" + + " and not (ether src host a4:77:33:9a:73:5c" + + "|| ether src host b0:05:94:5d:5a:4d)" + ) + + def test_compile_essid_regex_with_an_empty_regex(self): + """ + Tests 'complile_essid_regex' with an empty regex. + """ + + config = Config() + compiled_regex = config.complile_essid_regex() + + self.assertEqual(compiled_regex, None) + + def test_compile_essid_regex_with_a_case_sensitive_regex(self): + """ + Tests 'complile_essid_regex' with a case-sensitive regex. + """ + + config = Config() + config.essid_regex = "Free Wi-Fi" + compiled_regex = config.complile_essid_regex() + + self.assertEqual(compiled_regex, rcompile(config.essid_regex)) + + def test_compile_essid_regex_with_a_case_insensitive_regex(self): + """ + Tests 'complile_essid_regex' with a case-insensitive regex. + """ + + config = Config() + config.essid_regex = "Free Wi-Fi" + config.ignore_case = True + compiled_regex = config.complile_essid_regex() + + self.assertEqual(compiled_regex, rcompile( + config.essid_regex, IGNORECASE)) diff --git a/test/unit/test_fake_packet_sniffer.py b/test/unit/test_fake_packet_sniffer.py new file mode 100644 index 0000000..19d88a5 --- /dev/null +++ b/test/unit/test_fake_packet_sniffer.py @@ -0,0 +1,64 @@ +""" +Unit tests for the fake packet sniffer module. +""" + +import unittest +from queue import Queue + +from probequest.config import Config +from probequest.fake_packet_sniffer import FakePacketSniffer +from probequest.probe_request_parser import ProbeRequestParser + + +class TestFakePacketSniffer(unittest.TestCase): + """ + Unit tests for the 'FakePacketSniffer' class. + """ + + def test_new_packet(self): + """ + Tests the 'new_packet' method. + """ + + config = Config() + new_packets = Queue() + sniffer = FakePacketSniffer(config, new_packets) + + self.assertEqual(sniffer.new_packets.qsize(), 0) + + sniffer.new_packet() + self.assertEqual(sniffer.new_packets.qsize(), 1) + sniffer.new_packet() + self.assertEqual(sniffer.new_packets.qsize(), 2) + sniffer.new_packet() + self.assertEqual(sniffer.new_packets.qsize(), 3) + + ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) + ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) + ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) + + def test_stop_before_start(self): + """ + Creates a 'FakePacketSniffer' object and stops the sniffer before + starting it. + """ + + config = Config() + new_packets = Queue() + sniffer = FakePacketSniffer(config, new_packets) + + with self.assertRaises(RuntimeError): + sniffer.stop() + + def test_stop_before_start_using_join(self): + """ + Creates a 'FakePacketSniffer' object and stops the sniffer before + starting it. + """ + + config = Config() + new_packets = Queue() + sniffer = FakePacketSniffer(config, new_packets) + + with self.assertRaises(RuntimeError): + sniffer.join() diff --git a/test/unit/test_packet_sniffer.py b/test/unit/test_packet_sniffer.py new file mode 100644 index 0000000..f0adc87 --- /dev/null +++ b/test/unit/test_packet_sniffer.py @@ -0,0 +1,71 @@ +""" +Unit tests for the packet sniffer module. +""" + +import unittest +from queue import Queue + +from scapy.error import Scapy_Exception +from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt + +from probequest.config import Config +from probequest.packet_sniffer import PacketSniffer +from probequest.probe_request_parser import ProbeRequestParser + + +class TestPacketSniffer(unittest.TestCase): + """ + Unit tests for the 'PacketSniffer' class. + """ + + def test_new_packet(self): + """ + Tests the 'new_packet' method. + """ + + config = Config() + new_packets = Queue() + sniffer = PacketSniffer(config, new_packets) + + self.assertEqual(sniffer.new_packets.qsize(), 0) + + packet = RadioTap() \ + / Dot11( + addr1="ff:ff:ff:ff:ff:ff", + addr2="aa:bb:cc:11:22:33", + addr3="dd:ee:ff:11:22:33" + ) \ + / Dot11ProbeReq() \ + / Dot11Elt( + info="Test" + ) + + sniffer.new_packet(packet) + self.assertEqual(sniffer.new_packets.qsize(), 1) + + ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) + + def test_stop_before_start(self): + """ + Creates a 'PacketSniffer' object and stops the sniffer before starting + it. + """ + + config = Config() + new_packets = Queue() + sniffer = PacketSniffer(config, new_packets) + + with self.assertRaises(Scapy_Exception): + sniffer.stop() + + def test_is_running_before_start(self): + """ + Creates a 'PacketSniffer' object and runs 'is_running' before starting + the sniffer. + """ + + config = Config() + new_packets = Queue() + sniffer = PacketSniffer(config, new_packets) + + self.assertFalse(sniffer.is_running()) diff --git a/test/unit/test_probe_request.py b/test/unit/test_probe_request.py new file mode 100644 index 0000000..52921e8 --- /dev/null +++ b/test/unit/test_probe_request.py @@ -0,0 +1,94 @@ +""" +Unit tests for the probe request module. +""" + +import unittest +from netaddr.core import AddrFormatError + +from probequest.probe_request import ProbeRequest + + +class TestProbeRequest(unittest.TestCase): + """ + Unit tests for the 'ProbeRequest' class. + """ + + def test_without_parameters(self): + """ + Initialises a 'ProbeRequest' object without any parameter. + """ + + # pylint: disable=no-value-for-parameter + + with self.assertRaises(TypeError): + _ = ProbeRequest() + + def test_with_only_one_parameter(self): + """ + Initialises a 'ProbeRequest' object with only one parameter. + """ + + # pylint: disable=no-value-for-parameter + + timestamp = 1517872027.0 + + with self.assertRaises(TypeError): + _ = ProbeRequest(timestamp) + + def test_with_only_two_parameters(self): + """ + Initialises a 'ProbeRequest' object with only two parameters. + """ + + # pylint: disable=no-value-for-parameter + + timestamp = 1517872027.0 + s_mac = "aa:bb:cc:dd:ee:ff" + + with self.assertRaises(TypeError): + _ = ProbeRequest(timestamp, s_mac) + + def test_create_a_probe_request(self): + """ + Creates a new 'ProbeRequest' with all the required parameters. + """ + + # pylint: disable=no-self-use + + timestamp = 1517872027.0 + s_mac = "aa:bb:cc:dd:ee:ff" + essid = "Test ESSID" + + _ = ProbeRequest(timestamp, s_mac, essid) + + def test_bad_mac_address(self): + """ + Initialises a 'ProbeRequest' object with a malformed MAC address. + """ + + timestamp = 1517872027.0 + s_mac = "aa:bb:cc:dd:ee" + essid = "Test ESSID" + + with self.assertRaises(AddrFormatError): + _ = ProbeRequest(timestamp, s_mac, essid) + + def test_print_a_probe_request(self): + """ + Initialises a 'ProbeRequest' object and prints it. + """ + + timestamp = 1517872027.0 + s_mac = "aa:bb:cc:dd:ee:ff" + essid = "Test ESSID" + + probe_req = ProbeRequest(timestamp, s_mac, essid) + + self.assertNotEqual( + str(probe_req).find("Mon, 05 Feb 2018 23:07:07"), + -1 + ) + self.assertNotEqual( + str(probe_req).find("aa:bb:cc:dd:ee:ff (None) -> Test ESSID"), + -1 + ) diff --git a/test/unit/test_probe_request_parser.py b/test/unit/test_probe_request_parser.py new file mode 100644 index 0000000..fc61b09 --- /dev/null +++ b/test/unit/test_probe_request_parser.py @@ -0,0 +1,66 @@ +""" +Unit tests for the probe request parser module. +""" + +import unittest + +from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt +from scapy.packet import fuzz + +from probequest.probe_request_parser import ProbeRequestParser + + +class TestProbeRequestParser(unittest.TestCase): + """ + Unit tests for the 'ProbeRequestParser' class. + """ + + def test_no_probe_request_layer(self): + """ + Creates a non-probe-request Wi-Fi packet and parses it with the + 'ProbeRequestParser.parse()' function. + """ + + # pylint: disable=no-self-use + + packet = RadioTap() \ + / Dot11( + addr1="ff:ff:ff:ff:ff:ff", + addr2="aa:bb:cc:11:22:33", + addr3="dd:ee:ff:11:22:33" + ) + + ProbeRequestParser.parse(packet) + + def test_empty_essid(self): + """ + Creates a probe request packet with an empty ESSID field and parses + it with the 'ProbeRequestParser.parse()' function. + """ + + # pylint: disable=no-self-use + + packet = RadioTap() \ + / Dot11( + addr1="ff:ff:ff:ff:ff:ff", + addr2="aa:bb:cc:11:22:33", + addr3="dd:ee:ff:11:22:33" + ) \ + / Dot11ProbeReq() \ + / Dot11Elt( + info="" + ) + + ProbeRequestParser.parse(packet) + + def test_fuzz_packets(self): + """ + Parses 1000 randomly-generated probe requests with the + 'ProbeRequestParser.parse()' function. + """ + + # pylint: disable=no-self-use + + for _ in range(0, 1000): + packet = RadioTap()/fuzz(Dot11()/Dot11ProbeReq()/Dot11Elt()) + ProbeRequestParser.parse(packet) diff --git a/test/unit/test_probe_request_sniffer.py b/test/unit/test_probe_request_sniffer.py new file mode 100644 index 0000000..9be36fe --- /dev/null +++ b/test/unit/test_probe_request_sniffer.py @@ -0,0 +1,56 @@ +""" +Unit tests for the probe request sniffer module. +""" + +import unittest + +from probequest.config import Config +from probequest.probe_request_sniffer import ProbeRequestSniffer + + +class TestProbeRequestSniffer(unittest.TestCase): + """ + Unit tests for the 'ProbeRequestSniffer' class. + """ + + def test_without_parameters(self): + """ + Initialises a 'ProbeRequestSniffer' object without parameters. + """ + + # pylint: disable=no-value-for-parameter + + with self.assertRaises(TypeError): + _ = ProbeRequestSniffer() + + def test_bad_parameter(self): + """ + Initialises a 'ProbeRequestSniffer' object with a bad parameter. + """ + + # pylint: disable=no-value-for-parameter + + with self.assertRaises(AttributeError): + _ = ProbeRequestSniffer("test") + + def test_create_sniffer(self): + """ + Creates a 'ProbeRequestSniffer' object with the correct parameter. + """ + + # pylint: disable=no-self-use + + config = Config() + _ = ProbeRequestSniffer(config) + + def test_stop_before_start(self): + """ + Creates a 'ProbeRequestSniffer' object and stops the sniffer before + starting it. + """ + + # pylint: disable=no-self-use + + config = Config() + sniffer = ProbeRequestSniffer(config) + sniffer.stop() From 8cdd33c9209130331c020fcca8b274193279f212 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 23:11:27 +0100 Subject: [PATCH 046/126] Add '__version__' attribute to package See https://www.python.org/dev/peps/pep-0396/. --- probequest/__init__.py | 7 +++++++ probequest/main.py | 2 +- probequest/version.py | 5 ----- setup.py | 6 ++---- 4 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 probequest/version.py diff --git a/probequest/__init__.py b/probequest/__init__.py index e69de29..1144f29 100644 --- a/probequest/__init__.py +++ b/probequest/__init__.py @@ -0,0 +1,7 @@ +""" +ProbeQuest package. +""" + +from pkg_resources import get_distribution + +__version__ = get_distribution("probequest").version diff --git a/probequest/main.py b/probequest/main.py index 13f19d1..c8add47 100644 --- a/probequest/main.py +++ b/probequest/main.py @@ -7,10 +7,10 @@ from sys import exit as sys_exit from time import sleep +from . import __version__ as VERSION from .config import Config, Mode from .ui.pnl import PNLViewer from .ui.raw import RawProbeRequestViewer -from .version import VERSION def get_arg_parser(): diff --git a/probequest/version.py b/probequest/version.py deleted file mode 100644 index a815ff8..0000000 --- a/probequest/version.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Actual version number of ProbeQuest. -""" - -VERSION = "0.7.2" diff --git a/setup.py b/setup.py index 6a1a787..35f2490 100755 --- a/setup.py +++ b/setup.py @@ -15,18 +15,16 @@ "Setuptools is required to install ProbeQuest!" ) from setuptools_not_present - from codecs import open as fopen from os.path import dirname, abspath, join -from probequest.version import VERSION - DIR = dirname(abspath(__file__)) +VERSION = "0.7.2" + with fopen(join(DIR, "README.rst"), encoding="utf-8") as f: LONG_DESCRIPTION = f.read() - setup( name="probequest", version=VERSION, From 5b08ae4af6514e09d5386ab6996987f0bb30f2d8 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 26 Sep 2020 23:37:53 +0100 Subject: [PATCH 047/126] Fix import of ProbeQuest version by Sphinx --- docs/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 86724fa..2be8f2c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,8 @@ import os import sys -from probequest.version import VERSION + +from probequest import __version__ as VERSION sys.path.insert(0, os.path.abspath('..')) From 5fb69e0f444d0420d53e5c96998befa68497c7dc Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 27 Sep 2020 00:32:48 +0100 Subject: [PATCH 048/126] Remove useless shebangs Only executables need to have a shebang. --- probequest/config.py | 3 --- probequest/ui/pnl.py | 3 --- probequest/ui/raw.py | 3 --- 3 files changed, 9 deletions(-) diff --git a/probequest/config.py b/probequest/config.py index cb2a440..37fcdaf 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - """ ProbeQuest configuration. """ diff --git a/probequest/ui/pnl.py b/probequest/ui/pnl.py index 04d61a4..0094541 100644 --- a/probequest/ui/pnl.py +++ b/probequest/ui/pnl.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - """ Preferred network list viewer. """ diff --git a/probequest/ui/raw.py b/probequest/ui/raw.py index 924fd1f..57bba39 100644 --- a/probequest/ui/raw.py +++ b/probequest/ui/raw.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - """ Raw probe request viewer. """ From 0df081d4e1b3b7fcb5390c6cbd2a6378cb40bc4e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 27 Sep 2020 14:12:31 +0100 Subject: [PATCH 049/126] Add unit tests for the argparse --- test/unit/test_main.py | 228 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 test/unit/test_main.py diff --git a/test/unit/test_main.py b/test/unit/test_main.py new file mode 100644 index 0000000..eda4da3 --- /dev/null +++ b/test/unit/test_main.py @@ -0,0 +1,228 @@ +""" +Unit tests for the main module. +""" + +import unittest + +from argparse import Namespace +from contextlib import redirect_stdout, redirect_stderr +from io import StringIO, TextIOWrapper + +from probequest import __version__ as VERSION +from probequest.main import get_arg_parser +from probequest.config import Mode + + +class TestArgParse(unittest.TestCase): + """ + Tests the argument parser. + """ + + def setUp(self): + """ + Instanciates a new argument parser. + """ + + self.arg_parser = get_arg_parser() + + def test_without_parameters(self): + """ + Calls the argument parser with an emtpy input. + """ + + with self.assertRaises(SystemExit) as error_code: + error_output = StringIO() + + with redirect_stderr(error_output): + self.arg_parser.parse_args([]) + + self.assertEqual(error_code.exception.code, 2) + + def test_short_help_option(self): + """ + Calls the argument parser with the '-h' option. + """ + + with self.assertRaises(SystemExit) as error_code: + output = StringIO() + + with redirect_stdout(output): + self.arg_parser.parse_args(["-h"]) + + self.assertEqual(error_code.exception.code, 0) + + def test_long_help_option(self): + """ + Calls the argument parser with the '--help' option. + """ + + with self.assertRaises(SystemExit) as error_code: + output = StringIO() + + with redirect_stdout(output): + self.arg_parser.parse_args(["--help"]) + + self.assertEqual(error_code.exception.code, 0) + + def test_version_option(self): + """ + Calls the argument parser with the '--version' option. + """ + + with self.assertRaises(SystemExit) as error_code: + output = StringIO() + + with redirect_stdout(output): + self.arg_parser.parse_args(["--version"]) + + self.assertEqual(error_code.exception.code, 0) + self.assertEqual(output.getvalue(), VERSION + "\n") + + def test_default_values(self): + """ + Calls the argument parser with an empty input and tests the default + values in the configuration namespace. + """ + + # pylint: disable=no-member + + with self.assertRaises(SystemExit) as error_code: + error_output = StringIO() + + with redirect_stderr(error_output): + config = Namespace() + self.arg_parser.parse_args( + [], namespace=config + ) + + self.assertEqual(error_code.exception.code, 2) + + self.assertIsNone(config.interface) + self.assertIsNone(config.essid_filters) + self.assertIsNone(config.essid_regex) + self.assertFalse(config.ignore_case) + self.assertIsNone(config.mac_exclusions) + self.assertIsNone(config.mac_filters) + self.assertIsNone(config.output_file) + self.assertEqual(config.mode, Mode.RAW) + self.assertFalse(config.fake) + self.assertFalse(config.debug) + + def test_short_interface_option(self): + """ + Calls the argument parser with the '-i' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0" + ], namespace=config) + + self.assertEqual(config.interface, "wlan0") + + def test_long_interface_option(self): + """ + Calls the argument parser with the '--interface' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "--interface", "wlan0" + ], namespace=config) + + self.assertEqual(config.interface, "wlan0") + + def test_without_interface_option(self): + """ + Calls the argument parser with some options but not the required + interface one. + """ + + # pylint: disable=no-member + + with self.assertRaises(SystemExit) as error_code: + error_output = StringIO() + + with redirect_stderr(error_output): + config = Namespace() + self.arg_parser.parse_args([ + "--debug", "--fake" + ], namespace=config) + + self.assertEqual(error_code.exception.code, 2) + + def test_debug_option(self): + """ + Calls the argument parser with the '--debug' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--debug" + ], namespace=config) + + self.assertTrue(config.debug) + + def test_fake_option(self): + """ + Calls the argument parser with the '--fake' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--fake" + ], namespace=config) + + self.assertTrue(config.fake) + + def test_ignore_case_option(self): + """ + Calls the argument parser with the '--ignore-case' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--ignore-case" + ], namespace=config) + + self.assertTrue(config.ignore_case) + + def test_short_output_option(self): + """ + Calls the argument parser with the '-o' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "-o", "output.txt" + ], namespace=config) + + self.assertIsInstance(config.output_file, TextIOWrapper) + config.output_file.close() + + def test_long_output_option(self): + """ + Calls the argument parser with the '--output' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--output", "output.txt" + ], namespace=config) + + self.assertIsInstance(config.output_file, TextIOWrapper) + config.output_file.close() From 3af5848f2763dbeb32da82d519dba83f0c3af2e9 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 27 Sep 2020 19:45:20 +0100 Subject: [PATCH 050/126] Add more unit tests for the argument parser --- test/unit/test_main.py | 157 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/test/unit/test_main.py b/test/unit/test_main.py index eda4da3..218f134 100644 --- a/test/unit/test_main.py +++ b/test/unit/test_main.py @@ -18,6 +18,8 @@ class TestArgParse(unittest.TestCase): Tests the argument parser. """ + # pylint: disable=too-many-public-methods + def setUp(self): """ Instanciates a new argument parser. @@ -226,3 +228,158 @@ def test_long_output_option(self): self.assertIsInstance(config.output_file, TextIOWrapper) config.output_file.close() + + def test_short_essid_option(self): + """ + Calls the argument parser with the '-e' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "-e", "essid_1", "essid_2", "essid_3" + ], namespace=config) + + self.assertListEqual(config.essid_filters, [ + "essid_1", "essid_2", "essid_3" + ]) + + def test_long_essid_option(self): + """ + Calls the argument parser with the '--essid' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--essid", "essid_1", "essid_2", "essid_3" + ], namespace=config) + + self.assertListEqual(config.essid_filters, [ + "essid_1", "essid_2", "essid_3" + ]) + + def test_short_regex_option(self): + """ + Calls the argument parser with the '-r' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "-r", "test_regex" + ], namespace=config) + + self.assertEqual(config.essid_regex, "test_regex") + + def test_long_regex_option(self): + """ + Calls the argument parser with the '--regex' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--regex", "test_regex" + ], namespace=config) + + self.assertEqual(config.essid_regex, "test_regex") + + def test_essid_regex_mutual_exclusivity(self): + """ + Calls the argument parser with both '--essid' and '--regex' options, + which must fail as they are in the same mutually exclusive group. + """ + + # pylint: disable=no-member + + with self.assertRaises(SystemExit) as error_code: + error_output = StringIO() + + with redirect_stderr(error_output): + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--essid", "essid_1", + "--regex", "test_regex" + ], namespace=config) + + self.assertEqual(error_code.exception.code, 2) + + def test_exclude_option(self): + """ + Calls the argument parser with the '--exclude' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--exclude", "aa:bb:cc:dd:ee:ff", + "ff:ee:dd:cc:bb:aa" + ], namespace=config) + + self.assertListEqual(config.mac_exclusions, [ + "aa:bb:cc:dd:ee:ff", + "ff:ee:dd:cc:bb:aa", + ]) + + def test_short_station_option(self): + """ + Calls the argument parser with the '-s' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "-s", "aa:bb:cc:dd:ee:ff", + "ff:ee:dd:cc:bb:aa" + ], namespace=config) + + self.assertListEqual(config.mac_filters, [ + "aa:bb:cc:dd:ee:ff", + "ff:ee:dd:cc:bb:aa", + ]) + + def test_long_station_option(self): + """ + Calls the argument parser with the '--station' option. + """ + + # pylint: disable=no-member + + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--station", "aa:bb:cc:dd:ee:ff", + "ff:ee:dd:cc:bb:aa" + ], namespace=config) + + self.assertListEqual(config.mac_filters, [ + "aa:bb:cc:dd:ee:ff", + "ff:ee:dd:cc:bb:aa", + ]) + + def test_exclude_station_mutual_exclusivity(self): + """ + Calls the argument parser with both '--exclude' and '--station' + options, which must fail as they are in the same mutually exclusive + group. + """ + + # pylint: disable=no-member + + with self.assertRaises(SystemExit) as error_code: + error_output = StringIO() + + with redirect_stderr(error_output): + config = Namespace() + self.arg_parser.parse_args([ + "-i", "wlan0", "--exclude", "aa:bb:cc:dd:ee:ff", + "--station", "ff:ee:dd:cc:bb:aa" + ], namespace=config) + + self.assertEqual(error_code.exception.code, 2) From 29c03fe0b179127c6be4c3bdd51ecf1064ce0a85 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 27 Sep 2020 19:58:02 +0100 Subject: [PATCH 051/126] Clean test files --- test/unit/test_main.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/unit/test_main.py b/test/unit/test_main.py index 218f134..cafb8d1 100644 --- a/test/unit/test_main.py +++ b/test/unit/test_main.py @@ -7,6 +7,8 @@ from argparse import Namespace from contextlib import redirect_stdout, redirect_stderr from io import StringIO, TextIOWrapper +from os import remove +from os.path import isfile from probequest import __version__ as VERSION from probequest.main import get_arg_parser @@ -20,6 +22,8 @@ class TestArgParse(unittest.TestCase): # pylint: disable=too-many-public-methods + output_test_file = "probequest_test_output.txt" + def setUp(self): """ Instanciates a new argument parser. @@ -27,6 +31,14 @@ def setUp(self): self.arg_parser = get_arg_parser() + def tearDown(self): + """ + Removes test files if any. + """ + + if isfile(self.output_test_file): + remove(self.output_test_file) + def test_without_parameters(self): """ Calls the argument parser with an emtpy input. @@ -208,7 +220,7 @@ def test_short_output_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "-o", "output.txt" + "-i", "wlan0", "-o", self.output_test_file ], namespace=config) self.assertIsInstance(config.output_file, TextIOWrapper) @@ -223,7 +235,7 @@ def test_long_output_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--output", "output.txt" + "-i", "wlan0", "--output", self.output_test_file ], namespace=config) self.assertIsInstance(config.output_file, TextIOWrapper) From 26f3bddd6cea326a9b544e55059a10e57a6b8700 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 27 Sep 2020 23:20:41 +0100 Subject: [PATCH 052/126] Rename 'main.py' to 'cli.py' --- docs/usage.rst | 2 +- probequest/__main__.py | 2 +- probequest/{main.py => cli.py} | 2 +- setup.py | 2 +- test/unit/{test_main.py => test_cli.py} | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) rename probequest/{main.py => cli.py} (99%) rename test/unit/{test_main.py => test_cli.py} (99%) diff --git a/docs/usage.rst b/docs/usage.rst index 2cea12d..ede7b0f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -49,7 +49,7 @@ Command line arguments ---------------------- .. argparse:: - :module: probequest.main + :module: probequest.cli :func: get_arg_parser :prog: probequest diff --git a/probequest/__main__.py b/probequest/__main__.py index a27cf2c..1e0431f 100644 --- a/probequest/__main__.py +++ b/probequest/__main__.py @@ -4,6 +4,6 @@ Executes the command-line tool when run as a script or with 'python -m'. """ -from probequest.main import main +from probequest.cli import main main() diff --git a/probequest/main.py b/probequest/cli.py similarity index 99% rename from probequest/main.py rename to probequest/cli.py index c8add47..aa4020d 100644 --- a/probequest/main.py +++ b/probequest/cli.py @@ -1,5 +1,5 @@ """ -Main module. +CLI module. """ from argparse import ArgumentParser, FileType diff --git a/setup.py b/setup.py index 35f2490..e313a46 100755 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ packages=find_packages(), entry_points={ "console_scripts": [ - "probequest = probequest.main:main", + "probequest = probequest.cli:main", ] }, python_requires=">=3.5, <4", diff --git a/test/unit/test_main.py b/test/unit/test_cli.py similarity index 99% rename from test/unit/test_main.py rename to test/unit/test_cli.py index cafb8d1..143ad73 100644 --- a/test/unit/test_main.py +++ b/test/unit/test_cli.py @@ -1,5 +1,5 @@ """ -Unit tests for the main module. +Unit tests for the cli module. """ import unittest @@ -11,7 +11,7 @@ from os.path import isfile from probequest import __version__ as VERSION -from probequest.main import get_arg_parser +from probequest.cli import get_arg_parser from probequest.config import Mode From 472a74b0a4550349eea8935831f1b91efc6e96f3 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 28 Sep 2020 00:38:35 +0100 Subject: [PATCH 053/126] Use relative imports when relevant --- probequest/probe_request_parser.py | 2 +- probequest/probe_request_sniffer.py | 6 +++--- probequest/ui/pnl.py | 2 +- probequest/ui/raw.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/probequest/probe_request_parser.py b/probequest/probe_request_parser.py index 1e9a4d6..11e9a4f 100644 --- a/probequest/probe_request_parser.py +++ b/probequest/probe_request_parser.py @@ -8,7 +8,7 @@ from scapy.layers.dot11 import RadioTap, Dot11ProbeReq -from probequest.probe_request import ProbeRequest +from .probe_request import ProbeRequest class ProbeRequestParser(Thread): diff --git a/probequest/probe_request_sniffer.py b/probequest/probe_request_sniffer.py index c6ba9ec..6b053e5 100644 --- a/probequest/probe_request_sniffer.py +++ b/probequest/probe_request_sniffer.py @@ -7,9 +7,9 @@ from scapy.arch import get_if_hwaddr from scapy.error import Scapy_Exception -from probequest.packet_sniffer import PacketSniffer -from probequest.fake_packet_sniffer import FakePacketSniffer -from probequest.probe_request_parser import ProbeRequestParser +from .packet_sniffer import PacketSniffer +from .fake_packet_sniffer import FakePacketSniffer +from .probe_request_parser import ProbeRequestParser class ProbeRequestSniffer: diff --git a/probequest/ui/pnl.py b/probequest/ui/pnl.py index 0094541..c3e11c7 100644 --- a/probequest/ui/pnl.py +++ b/probequest/ui/pnl.py @@ -4,7 +4,7 @@ import urwid -from probequest.probe_request_sniffer import ProbeRequestSniffer +from ..probe_request_sniffer import ProbeRequestSniffer class PNLViewer: diff --git a/probequest/ui/raw.py b/probequest/ui/raw.py index 57bba39..50cb8cf 100644 --- a/probequest/ui/raw.py +++ b/probequest/ui/raw.py @@ -4,7 +4,7 @@ from csv import writer -from probequest.probe_request_sniffer import ProbeRequestSniffer +from ..probe_request_sniffer import ProbeRequestSniffer class RawProbeRequestViewer: From 9be618dd14445071a86a93ef47e9855487c36062 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 28 Sep 2020 00:43:29 +0100 Subject: [PATCH 054/126] Use relative imports in 'probequest/__main__.py' --- probequest/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/probequest/__main__.py b/probequest/__main__.py index 1e0431f..571eea3 100644 --- a/probequest/__main__.py +++ b/probequest/__main__.py @@ -4,6 +4,6 @@ Executes the command-line tool when run as a script or with 'python -m'. """ -from probequest.cli import main +from .cli import main main() From 762e04dbe1f8e0367e01089d2f4e18e4ef533ba1 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 28 Sep 2020 01:05:24 +0100 Subject: [PATCH 055/126] Rename 'test' folder to 'tests' --- {test => tests}/__init__.py | 0 {test => tests}/unit/__init__.py | 0 {test => tests}/unit/test_cli.py | 0 {test => tests}/unit/test_config.py | 0 {test => tests}/unit/test_fake_packet_sniffer.py | 0 {test => tests}/unit/test_packet_sniffer.py | 0 {test => tests}/unit/test_probe_request.py | 0 {test => tests}/unit/test_probe_request_parser.py | 0 {test => tests}/unit/test_probe_request_sniffer.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {test => tests}/__init__.py (100%) rename {test => tests}/unit/__init__.py (100%) rename {test => tests}/unit/test_cli.py (100%) rename {test => tests}/unit/test_config.py (100%) rename {test => tests}/unit/test_fake_packet_sniffer.py (100%) rename {test => tests}/unit/test_packet_sniffer.py (100%) rename {test => tests}/unit/test_probe_request.py (100%) rename {test => tests}/unit/test_probe_request_parser.py (100%) rename {test => tests}/unit/test_probe_request_sniffer.py (100%) diff --git a/test/__init__.py b/tests/__init__.py similarity index 100% rename from test/__init__.py rename to tests/__init__.py diff --git a/test/unit/__init__.py b/tests/unit/__init__.py similarity index 100% rename from test/unit/__init__.py rename to tests/unit/__init__.py diff --git a/test/unit/test_cli.py b/tests/unit/test_cli.py similarity index 100% rename from test/unit/test_cli.py rename to tests/unit/test_cli.py diff --git a/test/unit/test_config.py b/tests/unit/test_config.py similarity index 100% rename from test/unit/test_config.py rename to tests/unit/test_config.py diff --git a/test/unit/test_fake_packet_sniffer.py b/tests/unit/test_fake_packet_sniffer.py similarity index 100% rename from test/unit/test_fake_packet_sniffer.py rename to tests/unit/test_fake_packet_sniffer.py diff --git a/test/unit/test_packet_sniffer.py b/tests/unit/test_packet_sniffer.py similarity index 100% rename from test/unit/test_packet_sniffer.py rename to tests/unit/test_packet_sniffer.py diff --git a/test/unit/test_probe_request.py b/tests/unit/test_probe_request.py similarity index 100% rename from test/unit/test_probe_request.py rename to tests/unit/test_probe_request.py diff --git a/test/unit/test_probe_request_parser.py b/tests/unit/test_probe_request_parser.py similarity index 100% rename from test/unit/test_probe_request_parser.py rename to tests/unit/test_probe_request_parser.py diff --git a/test/unit/test_probe_request_sniffer.py b/tests/unit/test_probe_request_sniffer.py similarity index 100% rename from test/unit/test_probe_request_sniffer.py rename to tests/unit/test_probe_request_sniffer.py From 9d3c8bfe98b75e51872a9e15e187bd913e5c7774 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 28 Sep 2020 01:26:47 +0100 Subject: [PATCH 056/126] Add extra dependency group 'tests' --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index e313a46..dfa310a 100755 --- a/setup.py +++ b/setup.py @@ -71,6 +71,11 @@ "urwid>= 2.0.1", ], extras_require={ + "tests": [ + "flake8", + "pylint", + "tox" + ], "docs": [ "sphinx >= 1.4.0", "sphinxcontrib-seqdiag >= 0.8.5", From 6f31e8f3236a392b90d74d899175a0714d82be30 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 28 Sep 2020 11:00:16 +0100 Subject: [PATCH 057/126] Run Flake8 and Pylint on the test files with tox --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 2858278..fa5d271 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ description = "Check ProbeQuest code style & quality" basepython = python3 deps = flake8 commands = - {envpython} -m flake8 probequest/ + {envpython} -m flake8 probequest tests [testenv:pylint] description = "Check ProbeQuest for programming errors" @@ -26,7 +26,7 @@ basepython = python3 deps = pylint changedir = test commands = - {envpython} -m pylint probequest + {envpython} -m pylint probequest tests [travis] description = "tox configuration when running on Travis CI" From 600cbdbb32ada3c8ac9cbe936e760a84ddfeff8f Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 28 Sep 2020 11:02:26 +0100 Subject: [PATCH 058/126] Fix a linting issue --- tests/unit/test_probe_request_parser.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/tests/unit/test_probe_request_parser.py b/tests/unit/test_probe_request_parser.py index fc61b09..cbdf80f 100644 --- a/tests/unit/test_probe_request_parser.py +++ b/tests/unit/test_probe_request_parser.py @@ -15,21 +15,19 @@ class TestProbeRequestParser(unittest.TestCase): Unit tests for the 'ProbeRequestParser' class. """ + dot11_layer = Dot11( + addr1="ff:ff:ff:ff:ff:ff", + addr2="aa:bb:cc:11:22:33", + addr3="dd:ee:ff:11:22:33", + ) + def test_no_probe_request_layer(self): """ Creates a non-probe-request Wi-Fi packet and parses it with the 'ProbeRequestParser.parse()' function. """ - # pylint: disable=no-self-use - - packet = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2="aa:bb:cc:11:22:33", - addr3="dd:ee:ff:11:22:33" - ) - + packet = RadioTap() / self.dot11_layer ProbeRequestParser.parse(packet) def test_empty_essid(self): @@ -38,14 +36,8 @@ def test_empty_essid(self): it with the 'ProbeRequestParser.parse()' function. """ - # pylint: disable=no-self-use - packet = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2="aa:bb:cc:11:22:33", - addr3="dd:ee:ff:11:22:33" - ) \ + / self.dot11_layer \ / Dot11ProbeReq() \ / Dot11Elt( info="" From 2a5bea0fa30b09c9104b760708a71e9a9ca598b0 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 29 Sep 2020 10:56:22 +0100 Subject: [PATCH 059/126] Update Sphinx configuration --- docs/conf.py | 145 +++++---------------------------------------------- 1 file changed, 13 insertions(+), 132 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 2be8f2c..0d3d79e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,10 +1,10 @@ -# -*- coding: utf-8 -*- -# # Configuration file for the Sphinx documentation builder. # -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# pylint: skip-file # -- Path setup -------------------------------------------------------------- @@ -12,41 +12,30 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # - -# pylint: skip-file - -import os -import sys +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) from probequest import __version__ as VERSION -sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = 'ProbeQuest' -copyright = '2019, Paul-Emmanuel Raoul' +copyright = '2020, Paul-Emmanuel Raoul' author = 'Paul-Emmanuel Raoul' -# The short X.Y version -version = ".".join(VERSION.split(".")[:2]) # The full version, including alpha/beta/rc tags release = VERSION # -- General configuration --------------------------------------------------- -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.graphviz', - 'sphinx.ext.mathjax', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinxarg.ext', @@ -56,30 +45,14 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - # The master toctree document. master_doc = 'index' -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . +# This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - # -- Options for HTML output ------------------------------------------------- @@ -88,105 +61,11 @@ # html_theme = 'sphinx_rtd_theme' -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'ProbeQuestdoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'ProbeQuest.tex', 'ProbeQuest Documentation', - 'Paul-Emmanuel Raoul', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'probequest', 'ProbeQuest Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'ProbeQuest', 'ProbeQuest Documentation', - author, 'ProbeQuest', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - # -- Extension configuration ------------------------------------------------- @@ -195,17 +74,19 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True + # -- Options for sphinxcontrib-seqdiag extension ----------------------------- # Fontpath for seqdiag (truetype font). seqdiag_fontpath = '/usr/share/fonts/truetype/ipafont/ipagp.ttf' + # -- Options for GitHub integration ------------------------------------------ html_context = { 'display_github': True, # Integrate GitHub 'github_user': 'SkypLabs', # Username 'github_repo': 'probequest', # Repo name - 'github_version': 'master', # Version + 'github_version': 'develop', # Version 'conf_py_path': '/docs/', # Path in the checkout to the docs root } From abc05e2dde69e35768a48a95e948b9ae2b365dcf Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 29 Sep 2020 10:59:35 +0100 Subject: [PATCH 060/126] Update versions required in 'docs' dependencies --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index dfa310a..c1db6ae 100755 --- a/setup.py +++ b/setup.py @@ -77,10 +77,10 @@ "tox" ], "docs": [ - "sphinx >= 1.4.0", - "sphinxcontrib-seqdiag >= 0.8.5", + "sphinx >= 3.2.0", + "sphinxcontrib-seqdiag >= 2.0.0", "sphinx-argparse >= 0.2.2", - "sphinx_rtd_theme", + "sphinx_rtd_theme >= 0.5.0", ], }, ) From 8e1a4b8888628b98156b8701f26406f2603b7183 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 29 Sep 2020 15:57:12 +0100 Subject: [PATCH 061/126] Use the logging package in 'probequest.cli' --- probequest/__init__.py | 14 ++++ probequest/cli.py | 146 +++++++++++++++++++++++++++++++++-------- 2 files changed, 131 insertions(+), 29 deletions(-) diff --git a/probequest/__init__.py b/probequest/__init__.py index 1144f29..da264d1 100644 --- a/probequest/__init__.py +++ b/probequest/__init__.py @@ -2,6 +2,20 @@ ProbeQuest package. """ +import logging from pkg_resources import get_distribution __version__ = get_distribution("probequest").version + + +def set_up_package_logger(): + """ + Sets up the package logger. + """ + + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.NullHandler()) + + +set_up_package_logger() diff --git a/probequest/cli.py b/probequest/cli.py index aa4020d..e7c476e 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -2,7 +2,9 @@ CLI module. """ +import logging from argparse import ArgumentParser, FileType +from logging.handlers import MemoryHandler from os import geteuid from sys import exit as sys_exit from time import sleep @@ -12,6 +14,11 @@ from .ui.pnl import PNLViewer from .ui.raw import RawProbeRequestViewer +# Used to specify the capacity of the memory handler which will store the logs +# in memory until the argument parser is called to know whether they need to be +# flushed to the console (see "--debug" option) or not. +MEMORY_LOGGER_CAPACITY = 50 + def get_arg_parser(): """ @@ -89,48 +96,129 @@ def get_arg_parser(): return arg_parser +def set_up_root_logger(level=logging.DEBUG): + """ + Sets up the root logger. + + Returns a tuple containing the root logger, the memory handler and the + console handler. + """ + + root_logger = logging.getLogger("") + root_logger.setLevel(level) + + console = logging.StreamHandler() + + console_formatter = \ + logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s") + console.setFormatter(console_formatter) + + memory_handler = MemoryHandler(MEMORY_LOGGER_CAPACITY) + root_logger.addHandler(memory_handler) + + return (root_logger, memory_handler, console) + + def main(): """ Entry point of the command-line tool. """ + # pylint: disable=too-many-statements + + root_logger, memory_handler, console = set_up_root_logger() + + logger = logging.getLogger(__name__) + + logger.info("Program started") + + # -------------------------------------------------- # + # CLI configuration + # -------------------------------------------------- # + logger.debug("Creating configuration object") config = Config() + + # -------------------------------------------------- # + # Parsing arguments + # -------------------------------------------------- # + logger.debug("Parsing arguments") get_arg_parser().parse_args(namespace=config) + # -------------------------------------------------- # + # Debug mode + # -------------------------------------------------- # + # If the "--debug" option is present, flush the log buffer to the console, + # remove the memory handler from the root logger and add the console + # handler directly to the root logger. + if config.debug: + logger.debug("Setting the console as target of the memory handler") + memory_handler.setTarget(console) + + logger.debug("Removing the memory handler from the root logger") + # The buffer is flushed to the console at close time. + memory_handler.close() + root_logger.removeHandler(memory_handler) + + root_logger.addHandler(console) + logger.debug("Console handler added to the root logger") + # If the "--debug" option is absent (default), close the memory handler + # without flushing anything to the console. + else: + memory_handler.flushOnClose = False + memory_handler.close() + logger.debug("Memory handler closed") + + # -------------------------------------------------- # + # Checking privileges + # -------------------------------------------------- # if not geteuid() == 0: + logger.critical("User needs to be root to sniff the traffic") sys_exit("[!] You must be root") - # Default mode. - if config.mode == Mode.RAW: - try: + # -------------------------------------------------- # + # Mode selection + # -------------------------------------------------- # + try: + # Default mode. + if config.mode == Mode.RAW: + logger.info("Raw mode selected") + print("[*] Start sniffing probe requests...") - raw_viewer = RawProbeRequestViewer(config) - raw_viewer.start() + + logger.debug("Configuring the raw viewer") + viewer = RawProbeRequestViewer(config) + + logger.info("Starting the raw viewer") + viewer.start() while True: sleep(100) - except OSError: - raw_viewer.stop() - sys_exit( - "[!] Interface {interface} doesn't exist".format( - interface=config.interface - ) - ) - except KeyboardInterrupt: - print("[*] Stopping the threads...") - raw_viewer.stop() - print("[*] Bye!") - elif config.mode == Mode.PNL: - try: - pnl_viewer = PNLViewer(config) - pnl_viewer.main() - except OSError: - sys_exit( - "[!] Interface {interface} doesn't exist".format( - interface=config.interface - ) + elif config.mode == Mode.PNL: + logger.info("PNL mode selected") + + logger.debug("Configuring the PNL viewer") + viewer = PNLViewer(config) + + logger.info("Starting the PNL viewer") + viewer.main() + else: + logger.critical("Invalid mode: %s", config.mode) + sys_exit("[x] Invalid mode") + except OSError as err: + logger.debug("Stopping the viewer") + viewer.stop() + + logger.critical(err, exc_info=True) + sys_exit( + "[!] Interface {interface} doesn't exist".format( + interface=config.interface ) - except KeyboardInterrupt: - pnl_viewer.sniffer.stop() - else: - sys_exit("[x] Invalid mode") + ) + except KeyboardInterrupt: + logger.info("Keyboard interrupt received") + logger.info("Stopping the viewer") + print("[*] Stopping the threads...") + viewer.stop() + finally: + print("[*] Bye!") + logger.info("Program ended") From 764e7f03e74e8d1231b658061bf4cb013ce965c1 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 5 Oct 2020 12:06:00 +0100 Subject: [PATCH 062/126] Use logging package in 'probequest.config' --- probequest/config.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/probequest/config.py b/probequest/config.py index 37fcdaf..051af97 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -2,6 +2,7 @@ ProbeQuest configuration. """ +import logging from enum import Enum from re import compile as rcompile, IGNORECASE @@ -42,6 +43,9 @@ class Config: _display_func = lambda *args: None # noqa: E731 _storage_func = lambda *args: None # noqa: E731 + def __init__(self): + self.logger = logging.getLogger(__name__) + @property def display_func(self): """ @@ -66,6 +70,7 @@ def display_func(self, func): ) self._display_func = func + self.logger.debug("Display function set") @storage_func.setter def storage_func(self, func): @@ -75,6 +80,7 @@ def storage_func(self, func): ) self._storage_func = func + self.logger.debug("Storage function set") def generate_frame_filter(self): """ @@ -110,6 +116,8 @@ def generate_frame_filter(self): frame_filter += ")" + self.logger.debug("Frame filter: %s", frame_filter) + return frame_filter def complile_essid_regex(self): @@ -118,7 +126,11 @@ def complile_essid_regex(self): """ if self.essid_regex is not None: + self.logger.debug("Compiling ESSID regex") + if self.ignore_case: + self.logger.debug("Ignoring case in ESSID regex") + return rcompile( self.essid_regex, IGNORECASE From 803aa9099d0c8173401f043897f8d1d95f675855 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 5 Oct 2020 13:49:40 +0100 Subject: [PATCH 063/126] Add debugging messages to most modules --- probequest/fake_packet_sniffer.py | 9 +++++++++ probequest/packet_sniffer.py | 6 ++++++ probequest/probe_request_parser.py | 14 ++++++++------ probequest/probe_request_sniffer.py | 11 +++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/probequest/fake_packet_sniffer.py b/probequest/fake_packet_sniffer.py index 47fbe9d..200a78d 100644 --- a/probequest/fake_packet_sniffer.py +++ b/probequest/fake_packet_sniffer.py @@ -2,6 +2,7 @@ Fake packet sniffer module. """ +import logging from time import sleep from threading import Thread, Event @@ -19,8 +20,12 @@ class FakePacketSniffer(Thread): """ def __init__(self, config, new_packets): + # pylint: disable=duplicate-code + super().__init__() + self.logger = logging.getLogger(__name__) + self.config = config self.new_packets = new_packets @@ -30,6 +35,8 @@ def __init__(self, config, new_packets): self.fake.add_provider(WifiESSID) def run(self): + self.logger.debug("Starting the fake probe request sniffer") + while not self.stop_sniffer.isSet(): sleep(1) self.new_packet() @@ -39,6 +46,8 @@ def join(self, timeout=None): Stops the fake packet sniffer. """ + self.logger.debug("Stopping the fake probe request sniffer") + self.stop_sniffer.set() super().join(timeout) diff --git a/probequest/packet_sniffer.py b/probequest/packet_sniffer.py index 3938460..49d13a8 100644 --- a/probequest/packet_sniffer.py +++ b/probequest/packet_sniffer.py @@ -2,6 +2,8 @@ Packet sniffer module. """ +import logging + from scapy.sendrecv import AsyncSniffer @@ -11,6 +13,8 @@ class PacketSniffer: """ def __init__(self, config, new_packets): + self.logger = logging.getLogger(__name__) + self.config = config self.new_packets = new_packets @@ -26,6 +30,7 @@ def start(self): Starts the packet sniffer. """ + self.logger.debug("Starting the packet sniffer") self.sniffer.start() def stop(self): @@ -33,6 +38,7 @@ def stop(self): Stops the packet sniffer. """ + self.logger.debug("Stopping the packet sniffer") self.sniffer.stop() def is_running(self): diff --git a/probequest/probe_request_parser.py b/probequest/probe_request_parser.py index 11e9a4f..a0a0f01 100644 --- a/probequest/probe_request_parser.py +++ b/probequest/probe_request_parser.py @@ -2,9 +2,10 @@ Probe request parser module. """ +import logging from queue import Empty -from threading import Thread, Event from re import match +from threading import Thread, Event from scapy.layers.dot11 import RadioTap, Dot11ProbeReq @@ -19,6 +20,8 @@ class ProbeRequestParser(Thread): def __init__(self, config, new_packets): super().__init__() + self.logger = logging.getLogger(__name__) + self.config = config self.new_packets = new_packets @@ -26,12 +29,9 @@ def __init__(self, config, new_packets): self.stop_parser = Event() - if self.config.debug: - print("[!] ESSID filters: " + str(self.config.essid_filters)) - print("[!] ESSID regex: " + str(self.config.essid_regex)) - print("[!] Ignore case: " + str(self.config.ignore_case)) - def run(self): + self.logger.debug("Starting the probe request parser") + # The parser continues to do its job even after the call of the # join method if the queue is not empty. while not self.stop_parser.isSet() or not self.new_packets.empty(): @@ -67,6 +67,8 @@ def join(self, timeout=None): Stops the probe request parsing thread. """ + self.logger.debug("Stopping the probe request parser") + self.stop_parser.set() super().join(timeout) diff --git a/probequest/probe_request_sniffer.py b/probequest/probe_request_sniffer.py index 6b053e5..23193d3 100644 --- a/probequest/probe_request_sniffer.py +++ b/probequest/probe_request_sniffer.py @@ -2,6 +2,7 @@ Wi-Fi probe request sniffer. """ +import logging from queue import Queue from scapy.arch import get_if_hwaddr @@ -21,6 +22,8 @@ class ProbeRequestSniffer: """ def __init__(self, config): + self.logger = logging.getLogger(__name__) + self.config = config self.new_packets = Queue() @@ -34,6 +37,8 @@ def start(self): This method will start the sniffing and parsing threads. """ + self.logger.debug("Starting the probe request sniffer") + try: # Test if the interface exists. get_if_hwaddr(self.config.interface) @@ -55,6 +60,8 @@ def stop(self): This method will stop the sniffing and parsing threads. """ + self.logger.debug("Stopping the probe request sniffer") + try: self.sniffer.stop() except Scapy_Exception: @@ -75,11 +82,13 @@ def new_sniffer(self): """ if self.config.fake: + self.logger.debug("Creating a new fake probe request sniffer") self.sniffer = FakePacketSniffer( self.config, self.new_packets ) else: + self.logger.debug("Creating a new probe request sniffer") self.sniffer = PacketSniffer( self.config, self.new_packets @@ -90,6 +99,8 @@ def new_parser(self): Creates a new parsing thread. """ + self.logger.debug("Creating a new probe request parser") + self.parser = ProbeRequestParser( self.config, self.new_packets From 0d5d36dbe2237e04b2c00dcc45d68f7264221651 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 5 Oct 2020 13:52:01 +0100 Subject: [PATCH 064/126] Double-quote frame filter in debug message --- probequest/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/probequest/config.py b/probequest/config.py index 051af97..2fa04e1 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -116,7 +116,7 @@ def generate_frame_filter(self): frame_filter += ")" - self.logger.debug("Frame filter: %s", frame_filter) + self.logger.debug("Frame filter: \"%s\"", frame_filter) return frame_filter From 1dac651a84b9283307afb5383e37b032d3dc975e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 8 Oct 2020 19:20:57 +0100 Subject: [PATCH 065/126] Add log messages to 'Config' --- probequest/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/probequest/config.py b/probequest/config.py index 2fa04e1..df785bd 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -65,8 +65,9 @@ def storage_func(self): @display_func.setter def display_func(self, func): if not hasattr(func, "__call__"): + self.logger.error("Not a callable object: %s", func) raise TypeError( - "The display function property is not a callable object" + "The display function property must be a callable object" ) self._display_func = func @@ -75,8 +76,9 @@ def display_func(self, func): @storage_func.setter def storage_func(self, func): if not hasattr(func, "__call__"): + self.logger.error("Not a callable object: %s", func) raise TypeError( - "The storage function property is not a callable object" + "The storage function property must be a callable object" ) self._storage_func = func From 3ff76b54eaacd0a65c60fb242aedf3ca8ab790e6 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 8 Oct 2020 23:07:23 +0100 Subject: [PATCH 066/126] Verify the presence of log messages in tests --- tests/unit/test_config.py | 43 ++++++++++++++++++------ tests/unit/test_fake_packet_sniffer.py | 19 ++++++++--- tests/unit/test_packet_sniffer.py | 16 +++++++-- tests/unit/test_probe_request_sniffer.py | 13 ++++++- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 2f1ae4c..c3662ca 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -2,6 +2,7 @@ Unit tests for the configuration module. """ +import logging import unittest from re import compile as rcompile, IGNORECASE @@ -13,23 +14,33 @@ class TestConfig(unittest.TestCase): Unit tests for the 'Config' class. """ + def setUp(self): + """ + Creates a fake package logger. + """ + + self.logger = logging.getLogger("probequest") + self.logger.setLevel(logging.DEBUG) + def test_bad_display_function(self): """ Assigns a non-callable object to the display callback function. """ - with self.assertRaises(TypeError): - config = Config() - config.display_func = "test" + with self.assertLogs(self.logger, level=logging.ERROR): + with self.assertRaises(TypeError): + config = Config() + config.display_func = "test" def test_bad_storage_function(self): """ Assigns a non-callable object to the storage callback function. """ - with self.assertRaises(TypeError): - config = Config() - config.storage_func = "test" + with self.assertLogs(self.logger, level=logging.ERROR): + with self.assertRaises(TypeError): + config = Config() + config.storage_func = "test" def test_default_frame_filter(self): """ @@ -37,7 +48,9 @@ def test_default_frame_filter(self): """ config = Config() - frame_filter = config.generate_frame_filter() + + with self.assertLogs(self.logger, level=logging.DEBUG): + frame_filter = config.generate_frame_filter() self.assertEqual( frame_filter, @@ -51,7 +64,9 @@ def test_frame_filter_with_mac_filtering(self): config = Config() config.mac_filters = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] - frame_filter = config.generate_frame_filter() + + with self.assertLogs(self.logger, level=logging.DEBUG): + frame_filter = config.generate_frame_filter() self.assertEqual( frame_filter, @@ -67,7 +82,9 @@ def test_frame_filter_with_mac_exclusion(self): config = Config() config.mac_exclusions = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] - frame_filter = config.generate_frame_filter() + + with self.assertLogs(self.logger, level=logging.DEBUG): + frame_filter = config.generate_frame_filter() self.assertEqual( frame_filter, @@ -93,7 +110,9 @@ def test_compile_essid_regex_with_a_case_sensitive_regex(self): config = Config() config.essid_regex = "Free Wi-Fi" - compiled_regex = config.complile_essid_regex() + + with self.assertLogs(self.logger, level=logging.DEBUG): + compiled_regex = config.complile_essid_regex() self.assertEqual(compiled_regex, rcompile(config.essid_regex)) @@ -105,7 +124,9 @@ def test_compile_essid_regex_with_a_case_insensitive_regex(self): config = Config() config.essid_regex = "Free Wi-Fi" config.ignore_case = True - compiled_regex = config.complile_essid_regex() + + with self.assertLogs(self.logger, level=logging.DEBUG): + compiled_regex = config.complile_essid_regex() self.assertEqual(compiled_regex, rcompile( config.essid_regex, IGNORECASE)) diff --git a/tests/unit/test_fake_packet_sniffer.py b/tests/unit/test_fake_packet_sniffer.py index 19d88a5..9dbd69b 100644 --- a/tests/unit/test_fake_packet_sniffer.py +++ b/tests/unit/test_fake_packet_sniffer.py @@ -2,6 +2,7 @@ Unit tests for the fake packet sniffer module. """ +import logging import unittest from queue import Queue @@ -15,6 +16,14 @@ class TestFakePacketSniffer(unittest.TestCase): Unit tests for the 'FakePacketSniffer' class. """ + def setUp(self): + """ + Creates a fake package logger. + """ + + self.logger = logging.getLogger("probequest") + self.logger.setLevel(logging.DEBUG) + def test_new_packet(self): """ Tests the 'new_packet' method. @@ -47,8 +56,9 @@ def test_stop_before_start(self): new_packets = Queue() sniffer = FakePacketSniffer(config, new_packets) - with self.assertRaises(RuntimeError): - sniffer.stop() + with self.assertLogs(self.logger, level=logging.DEBUG): + with self.assertRaises(RuntimeError): + sniffer.stop() def test_stop_before_start_using_join(self): """ @@ -60,5 +70,6 @@ def test_stop_before_start_using_join(self): new_packets = Queue() sniffer = FakePacketSniffer(config, new_packets) - with self.assertRaises(RuntimeError): - sniffer.join() + with self.assertLogs(self.logger, level=logging.DEBUG): + with self.assertRaises(RuntimeError): + sniffer.join() diff --git a/tests/unit/test_packet_sniffer.py b/tests/unit/test_packet_sniffer.py index f0adc87..4246092 100644 --- a/tests/unit/test_packet_sniffer.py +++ b/tests/unit/test_packet_sniffer.py @@ -2,6 +2,7 @@ Unit tests for the packet sniffer module. """ +import logging import unittest from queue import Queue @@ -18,13 +19,21 @@ class TestPacketSniffer(unittest.TestCase): Unit tests for the 'PacketSniffer' class. """ + def setUp(self): + """ + Creates a fake package logger. + """ + + self.logger = logging.getLogger("probequest") + self.logger.setLevel(logging.DEBUG) + def test_new_packet(self): """ Tests the 'new_packet' method. """ - config = Config() new_packets = Queue() + config = Config() sniffer = PacketSniffer(config, new_packets) self.assertEqual(sniffer.new_packets.qsize(), 0) @@ -55,8 +64,9 @@ def test_stop_before_start(self): new_packets = Queue() sniffer = PacketSniffer(config, new_packets) - with self.assertRaises(Scapy_Exception): - sniffer.stop() + with self.assertLogs(self.logger, level=logging.DEBUG): + with self.assertRaises(Scapy_Exception): + sniffer.stop() def test_is_running_before_start(self): """ diff --git a/tests/unit/test_probe_request_sniffer.py b/tests/unit/test_probe_request_sniffer.py index 9be36fe..fa6a583 100644 --- a/tests/unit/test_probe_request_sniffer.py +++ b/tests/unit/test_probe_request_sniffer.py @@ -2,6 +2,7 @@ Unit tests for the probe request sniffer module. """ +import logging import unittest from probequest.config import Config @@ -13,6 +14,14 @@ class TestProbeRequestSniffer(unittest.TestCase): Unit tests for the 'ProbeRequestSniffer' class. """ + def setUp(self): + """ + Creates a fake package logger. + """ + + self.logger = logging.getLogger("probequest") + self.logger.setLevel(logging.DEBUG) + def test_without_parameters(self): """ Initialises a 'ProbeRequestSniffer' object without parameters. @@ -53,4 +62,6 @@ def test_stop_before_start(self): config = Config() sniffer = ProbeRequestSniffer(config) - sniffer.stop() + + with self.assertLogs(self.logger, level=logging.DEBUG): + sniffer.stop() From b5d76fd4887d7379c457281f6be3f9c4e62cf633 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 8 Oct 2020 23:13:23 +0100 Subject: [PATCH 067/126] Update a comment --- probequest/__main__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/probequest/__main__.py b/probequest/__main__.py index 571eea3..b40b87f 100644 --- a/probequest/__main__.py +++ b/probequest/__main__.py @@ -1,6 +1,4 @@ """ -Top-level module. - Executes the command-line tool when run as a script or with 'python -m'. """ From 8883a057c4b9d28ce8623848b713a397826c8335 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 9 Oct 2020 00:20:25 +0100 Subject: [PATCH 068/126] Cache the MAC address' OUI in 'ProbeRequest' --- probequest/probe_request.py | 24 +++++++++++++++--------- tests/unit/test_probe_request.py | 7 +++++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/probequest/probe_request.py b/probequest/probe_request.py index 6fd480d..535a5c3 100644 --- a/probequest/probe_request.py +++ b/probequest/probe_request.py @@ -16,27 +16,33 @@ def __init__(self, timestamp, s_mac, essid): self.s_mac = str(s_mac) self.essid = str(essid) - self.s_mac_oui = self.get_mac_organisation() + self._s_mac_oui = None def __str__(self): - return "{timestamp} - {s_mac} ({mac_org}) -> {essid}".format( + return "{timestamp} - {s_mac} ({s_mac_oui}) -> {essid}".format( timestamp=strftime( "%a, %d %b %Y %H:%M:%S %Z", localtime(self.timestamp) ), s_mac=self.s_mac, - mac_org=self.s_mac_oui, + s_mac_oui=self.s_mac_oui, essid=self.essid ) - def get_mac_organisation(self): + @property + def s_mac_oui(self): """ - Returns the OUI of the MAC address as a string. + OUI of the station's MAC address as a string. + + The value is cached once already computed. """ # pylint: disable=no-member - try: - return EUI(self.s_mac).oui.registration().org - except NotRegisteredError: - return None + if self._s_mac_oui is None: + try: + self._s_mac_oui = EUI(self.s_mac).oui.registration().org + except NotRegisteredError: + self._s_mac_oui = "Unknown OUI" + + return self._s_mac_oui diff --git a/tests/unit/test_probe_request.py b/tests/unit/test_probe_request.py index 52921e8..276cf8e 100644 --- a/tests/unit/test_probe_request.py +++ b/tests/unit/test_probe_request.py @@ -71,7 +71,8 @@ def test_bad_mac_address(self): essid = "Test ESSID" with self.assertRaises(AddrFormatError): - _ = ProbeRequest(timestamp, s_mac, essid) + probe_req = ProbeRequest(timestamp, s_mac, essid) + _ = probe_req.s_mac_oui def test_print_a_probe_request(self): """ @@ -89,6 +90,8 @@ def test_print_a_probe_request(self): -1 ) self.assertNotEqual( - str(probe_req).find("aa:bb:cc:dd:ee:ff (None) -> Test ESSID"), + str(probe_req).find( + "aa:bb:cc:dd:ee:ff (Unknown OUI) -> Test ESSID" + ), -1 ) From c3b6d1e2c423fca91ef457e58cce9d3357f888dc Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 9 Oct 2020 01:31:29 +0100 Subject: [PATCH 069/126] Test default values of 'Config' --- tests/unit/test_config.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index c3662ca..0257821 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -6,7 +6,7 @@ import unittest from re import compile as rcompile, IGNORECASE -from probequest.config import Config +from probequest.config import Config, Mode class TestConfig(unittest.TestCase): @@ -22,6 +22,31 @@ def setUp(self): self.logger = logging.getLogger("probequest") self.logger.setLevel(logging.DEBUG) + def test_default_values(self): + """ + Tests the default values. + """ + + config = Config() + + self.assertIsNone(config.interface) + + self.assertIsNone(config.essid_filters) + self.assertIsNone(config.essid_regex) + self.assertFalse(config.ignore_case) + + self.assertIsNone(config.mac_exclusions) + self.assertIsNone(config.mac_filters) + + self.assertIsNone(config.output_file) + + self.assertEqual(config.mode, Mode.RAW) + self.assertFalse(config.fake) + self.assertFalse(config.debug) + + self.assertTrue(hasattr(config.display_func, "__call__")) + self.assertTrue(hasattr(config.storage_func, "__call__")) + def test_bad_display_function(self): """ Assigns a non-callable object to the display callback function. From 9a81416bb64520307463e0faa77805ba9dbcb856 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 9 Oct 2020 01:48:54 +0100 Subject: [PATCH 070/126] Cache the frame filter in 'Config' once computed --- probequest/config.py | 53 ++++++++++++++++++++---------------- probequest/packet_sniffer.py | 2 +- probequest/probe_request.py | 2 +- tests/unit/test_config.py | 49 ++++++++++++--------------------- 4 files changed, 49 insertions(+), 57 deletions(-) diff --git a/probequest/config.py b/probequest/config.py index df785bd..84bf9cc 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -42,6 +42,7 @@ class Config: _display_func = lambda *args: None # noqa: E731 _storage_func = lambda *args: None # noqa: E731 + _frame_filter = None def __init__(self): self.logger = logging.getLogger(__name__) @@ -84,43 +85,47 @@ def storage_func(self, func): self._storage_func = func self.logger.debug("Storage function set") - def generate_frame_filter(self): + @property + def frame_filter(self): """ Generates and returns the frame filter according to the different options set of the current 'Config' object. + + The value is cached once computed. """ - frame_filter = "type mgt subtype probe-req" + if self._frame_filter is None: + self._frame_filter = "type mgt subtype probe-req" - if self.mac_exclusions is not None: - frame_filter += " and not (" + if self.mac_exclusions is not None: + self._frame_filter += " and not (" - for i, station in enumerate(self.mac_exclusions): - if i == 0: - frame_filter += "ether src host {s_mac}".format( - s_mac=station) - else: - frame_filter += "|| ether src host {s_mac}".format( - s_mac=station) + for i, station in enumerate(self.mac_exclusions): + if i == 0: + self._frame_filter += \ + "ether src host {s_mac}".format(s_mac=station) + else: + self._frame_filter += \ + "|| ether src host {s_mac}".format(s_mac=station) - frame_filter += ")" + self._frame_filter += ")" - if self.mac_filters is not None: - frame_filter += " and (" + if self.mac_filters is not None: + self._frame_filter += " and (" - for i, station in enumerate(self.mac_filters): - if i == 0: - frame_filter += "ether src host {s_mac}".format( - s_mac=station) - else: - frame_filter += "|| ether src host {s_mac}".format( - s_mac=station) + for i, station in enumerate(self.mac_filters): + if i == 0: + self._frame_filter += \ + "ether src host {s_mac}".format(s_mac=station) + else: + self._frame_filter += \ + "|| ether src host {s_mac}".format(s_mac=station) - frame_filter += ")" + self._frame_filter += ")" - self.logger.debug("Frame filter: \"%s\"", frame_filter) + self.logger.debug("Frame filter: \"%s\"", self._frame_filter) - return frame_filter + return self._frame_filter def complile_essid_regex(self): """ diff --git a/probequest/packet_sniffer.py b/probequest/packet_sniffer.py index 49d13a8..388cba5 100644 --- a/probequest/packet_sniffer.py +++ b/probequest/packet_sniffer.py @@ -20,7 +20,7 @@ def __init__(self, config, new_packets): self.sniffer = AsyncSniffer( iface=self.config.interface, - filter=self.config.generate_frame_filter(), + filter=self.config.frame_filter, store=False, prn=self.new_packet ) diff --git a/probequest/probe_request.py b/probequest/probe_request.py index 535a5c3..68aa469 100644 --- a/probequest/probe_request.py +++ b/probequest/probe_request.py @@ -34,7 +34,7 @@ def s_mac_oui(self): """ OUI of the station's MAC address as a string. - The value is cached once already computed. + The value is cached once computed. """ # pylint: disable=no-member diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 0257821..a1ef4a3 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -47,6 +47,12 @@ def test_default_values(self): self.assertTrue(hasattr(config.display_func, "__call__")) self.assertTrue(hasattr(config.storage_func, "__call__")) + with self.assertLogs(self.logger, level=logging.DEBUG): + self.assertEqual( + config.frame_filter, + "type mgt subtype probe-req" + ) + def test_bad_display_function(self): """ Assigns a non-callable object to the display callback function. @@ -67,21 +73,6 @@ def test_bad_storage_function(self): config = Config() config.storage_func = "test" - def test_default_frame_filter(self): - """ - Tests the default frame filter. - """ - - config = Config() - - with self.assertLogs(self.logger, level=logging.DEBUG): - frame_filter = config.generate_frame_filter() - - self.assertEqual( - frame_filter, - "type mgt subtype probe-req" - ) - def test_frame_filter_with_mac_filtering(self): """ Tests the frame filter when some MAC addresses need to be filtered. @@ -91,14 +82,12 @@ def test_frame_filter_with_mac_filtering(self): config.mac_filters = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] with self.assertLogs(self.logger, level=logging.DEBUG): - frame_filter = config.generate_frame_filter() - - self.assertEqual( - frame_filter, - "type mgt subtype probe-req" + - " and (ether src host a4:77:33:9a:73:5c" + - "|| ether src host b0:05:94:5d:5a:4d)" - ) + self.assertEqual( + config.frame_filter, + "type mgt subtype probe-req" + + " and (ether src host a4:77:33:9a:73:5c" + + "|| ether src host b0:05:94:5d:5a:4d)" + ) def test_frame_filter_with_mac_exclusion(self): """ @@ -109,14 +98,12 @@ def test_frame_filter_with_mac_exclusion(self): config.mac_exclusions = ["a4:77:33:9a:73:5c", "b0:05:94:5d:5a:4d"] with self.assertLogs(self.logger, level=logging.DEBUG): - frame_filter = config.generate_frame_filter() - - self.assertEqual( - frame_filter, - "type mgt subtype probe-req" + - " and not (ether src host a4:77:33:9a:73:5c" + - "|| ether src host b0:05:94:5d:5a:4d)" - ) + self.assertEqual( + config.frame_filter, + "type mgt subtype probe-req" + + " and not (ether src host a4:77:33:9a:73:5c" + + "|| ether src host b0:05:94:5d:5a:4d)" + ) def test_compile_essid_regex_with_an_empty_regex(self): """ From 083d4976b9bd3a1af9bb6454beef9b3a3d2dd4ac Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 9 Oct 2020 16:03:25 +0100 Subject: [PATCH 071/126] Cache the compiled regex in 'Config' once computed --- probequest/config.py | 18 ++++++++++++------ probequest/probe_request_parser.py | 2 +- tests/unit/test_config.py | 18 +++++++++--------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/probequest/config.py b/probequest/config.py index 84bf9cc..422e17e 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -42,6 +42,7 @@ class Config: _display_func = lambda *args: None # noqa: E731 _storage_func = lambda *args: None # noqa: E731 + _compiled_essid_regex = None _frame_filter = None def __init__(self): @@ -127,22 +128,27 @@ def frame_filter(self): return self._frame_filter - def complile_essid_regex(self): + @property + def compiled_essid_regex(self): """ Returns the compiled version of the ESSID regex. + + The value is cached once computed. """ - if self.essid_regex is not None: + # If there is a regex in the configuration and it hasn't been compiled + # yet. + if self._compiled_essid_regex is None and self.essid_regex is not None: self.logger.debug("Compiling ESSID regex") if self.ignore_case: self.logger.debug("Ignoring case in ESSID regex") - return rcompile( + self._compiled_essid_regex = rcompile( self.essid_regex, IGNORECASE ) + else: + self._compiled_essid_regex = rcompile(self.essid_regex) - return rcompile(self.essid_regex) - - return None + return self._compiled_essid_regex diff --git a/probequest/probe_request_parser.py b/probequest/probe_request_parser.py index a0a0f01..8d58859 100644 --- a/probequest/probe_request_parser.py +++ b/probequest/probe_request_parser.py @@ -25,7 +25,7 @@ def __init__(self, config, new_packets): self.config = config self.new_packets = new_packets - self.cregex = self.config.complile_essid_regex() + self.cregex = self.config.compiled_essid_regex self.stop_parser = Event() diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index a1ef4a3..adc4d6e 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -105,32 +105,32 @@ def test_frame_filter_with_mac_exclusion(self): "|| ether src host b0:05:94:5d:5a:4d)" ) - def test_compile_essid_regex_with_an_empty_regex(self): + def test_compiled_essid_regex_with_an_empty_regex(self): """ - Tests 'complile_essid_regex' with an empty regex. + Tests 'compiled_essid_regex' with an empty regex. """ config = Config() - compiled_regex = config.complile_essid_regex() + compiled_regex = config.compiled_essid_regex self.assertEqual(compiled_regex, None) - def test_compile_essid_regex_with_a_case_sensitive_regex(self): + def test_compiled_essid_regex_with_a_case_sensitive_regex(self): """ - Tests 'complile_essid_regex' with a case-sensitive regex. + Tests 'compiled_essid_regex' with a case-sensitive regex. """ config = Config() config.essid_regex = "Free Wi-Fi" with self.assertLogs(self.logger, level=logging.DEBUG): - compiled_regex = config.complile_essid_regex() + compiled_regex = config.compiled_essid_regex self.assertEqual(compiled_regex, rcompile(config.essid_regex)) - def test_compile_essid_regex_with_a_case_insensitive_regex(self): + def test_compiled_essid_regex_with_a_case_insensitive_regex(self): """ - Tests 'complile_essid_regex' with a case-insensitive regex. + Tests 'compiled_essid_regex' with a case-insensitive regex. """ config = Config() @@ -138,7 +138,7 @@ def test_compile_essid_regex_with_a_case_insensitive_regex(self): config.ignore_case = True with self.assertLogs(self.logger, level=logging.DEBUG): - compiled_regex = config.complile_essid_regex() + compiled_regex = config.compiled_essid_regex self.assertEqual(compiled_regex, rcompile( config.essid_regex, IGNORECASE)) From 6fd23cef0cd70fbbfe37a541acd7eeaf6f98db60 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 9 Oct 2020 16:34:35 +0100 Subject: [PATCH 072/126] Use a fake config object in unit tests Unit tests shouldn't include components of the project other than the one currently tested. This is why fake 'Config' objects are now used in the relevant locations instead of real ones. --- tests/unit/test_fake_packet_sniffer.py | 9 +++--- tests/unit/test_packet_sniffer.py | 9 +++--- tests/unit/test_probe_request_sniffer.py | 7 +++-- tests/unit/utils.py | 37 ++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 tests/unit/utils.py diff --git a/tests/unit/test_fake_packet_sniffer.py b/tests/unit/test_fake_packet_sniffer.py index 9dbd69b..cf54cbb 100644 --- a/tests/unit/test_fake_packet_sniffer.py +++ b/tests/unit/test_fake_packet_sniffer.py @@ -6,10 +6,11 @@ import unittest from queue import Queue -from probequest.config import Config from probequest.fake_packet_sniffer import FakePacketSniffer from probequest.probe_request_parser import ProbeRequestParser +from .utils import create_fake_config + class TestFakePacketSniffer(unittest.TestCase): """ @@ -29,7 +30,7 @@ def test_new_packet(self): Tests the 'new_packet' method. """ - config = Config() + config = create_fake_config() new_packets = Queue() sniffer = FakePacketSniffer(config, new_packets) @@ -52,7 +53,7 @@ def test_stop_before_start(self): starting it. """ - config = Config() + config = create_fake_config() new_packets = Queue() sniffer = FakePacketSniffer(config, new_packets) @@ -66,7 +67,7 @@ def test_stop_before_start_using_join(self): starting it. """ - config = Config() + config = create_fake_config() new_packets = Queue() sniffer = FakePacketSniffer(config, new_packets) diff --git a/tests/unit/test_packet_sniffer.py b/tests/unit/test_packet_sniffer.py index 4246092..51fc9be 100644 --- a/tests/unit/test_packet_sniffer.py +++ b/tests/unit/test_packet_sniffer.py @@ -9,10 +9,11 @@ from scapy.error import Scapy_Exception from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt -from probequest.config import Config from probequest.packet_sniffer import PacketSniffer from probequest.probe_request_parser import ProbeRequestParser +from .utils import create_fake_config + class TestPacketSniffer(unittest.TestCase): """ @@ -33,7 +34,7 @@ def test_new_packet(self): """ new_packets = Queue() - config = Config() + config = create_fake_config() sniffer = PacketSniffer(config, new_packets) self.assertEqual(sniffer.new_packets.qsize(), 0) @@ -60,7 +61,7 @@ def test_stop_before_start(self): it. """ - config = Config() + config = create_fake_config() new_packets = Queue() sniffer = PacketSniffer(config, new_packets) @@ -74,7 +75,7 @@ def test_is_running_before_start(self): the sniffer. """ - config = Config() + config = create_fake_config() new_packets = Queue() sniffer = PacketSniffer(config, new_packets) diff --git a/tests/unit/test_probe_request_sniffer.py b/tests/unit/test_probe_request_sniffer.py index fa6a583..b699d28 100644 --- a/tests/unit/test_probe_request_sniffer.py +++ b/tests/unit/test_probe_request_sniffer.py @@ -5,9 +5,10 @@ import logging import unittest -from probequest.config import Config from probequest.probe_request_sniffer import ProbeRequestSniffer +from .utils import create_fake_config + class TestProbeRequestSniffer(unittest.TestCase): """ @@ -49,7 +50,7 @@ def test_create_sniffer(self): # pylint: disable=no-self-use - config = Config() + config = create_fake_config() _ = ProbeRequestSniffer(config) def test_stop_before_start(self): @@ -60,7 +61,7 @@ def test_stop_before_start(self): # pylint: disable=no-self-use - config = Config() + config = create_fake_config() sniffer = ProbeRequestSniffer(config) with self.assertLogs(self.logger, level=logging.DEBUG): diff --git a/tests/unit/utils.py b/tests/unit/utils.py new file mode 100644 index 0000000..b451b22 --- /dev/null +++ b/tests/unit/utils.py @@ -0,0 +1,37 @@ +""" +Common assets for the unit tests. +""" + +from argparse import Namespace + +from probequest.config import Mode + + +def create_fake_config(): + """ + Creates and returns a fake 'Config' object. + """ + + config = Namespace() + + config.interface = None + + config.essid_filters = None + config.essid_regex = None + config.ignore_case = False + + config.mac_exclusions = None + config.mac_filters = None + + config.output_file = None + + config.mode = Mode.RAW + config.fake = False + config.debug = False + + config.display_func = lambda *args: None + config.storage_func = lambda *args: None + config.compiled_essid_regex = None + config.frame_filter = None + + return config From 720d3a7646d70ae984c7f497292f31e3870922d9 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 9 Nov 2020 01:21:07 +0000 Subject: [PATCH 073/126] Turn 'interface' option into argument --- docs/usage.rst | 2 +- probequest/cli.py | 10 +++---- tests/unit/test_cli.py | 66 +++++++++++++++++------------------------- 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index ede7b0f..77deb85 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -58,7 +58,7 @@ Example of use :: - sudo probequest -i wlan0 + sudo probequest wlan0 Here is a sample output: diff --git a/probequest/cli.py b/probequest/cli.py index e7c476e..59118b1 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -28,6 +28,10 @@ def get_arg_parser(): arg_parser = ArgumentParser( description="Toolkit for Playing with Wi-Fi Probe Requests" ) + arg_parser.add_argument( + "interface", + help="wireless interface to use (must be in monitor mode)" + ) arg_parser.add_argument( "--debug", action="store_true", dest="debug", @@ -37,12 +41,6 @@ def get_arg_parser(): "--fake", action="store_true", dest="fake", help="display only fake ESSIDs") - arg_parser.add_argument( - "-i", "--interface", - required=True, - dest="interface", - help="wireless interface to use (must be in monitor mode)" - ) arg_parser.add_argument( "--ignore-case", action="store_true", dest="ignore_case", diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 143ad73..04ed79d 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -122,38 +122,24 @@ def test_default_values(self): self.assertFalse(config.fake) self.assertFalse(config.debug) - def test_short_interface_option(self): + def test_interface_argument(self): """ - Calls the argument parser with the '-i' option. + Calls the argument parser with the 'interface' argument. """ # pylint: disable=no-member config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0" + "wlan0", ], namespace=config) self.assertEqual(config.interface, "wlan0") - def test_long_interface_option(self): - """ - Calls the argument parser with the '--interface' option. - """ - - # pylint: disable=no-member - - config = Namespace() - self.arg_parser.parse_args([ - "--interface", "wlan0" - ], namespace=config) - - self.assertEqual(config.interface, "wlan0") - - def test_without_interface_option(self): + def test_without_interface_argument(self): """ Calls the argument parser with some options but not the required - interface one. + interface argument. """ # pylint: disable=no-member @@ -164,7 +150,7 @@ def test_without_interface_option(self): with redirect_stderr(error_output): config = Namespace() self.arg_parser.parse_args([ - "--debug", "--fake" + "--debug", "--fake", ], namespace=config) self.assertEqual(error_code.exception.code, 2) @@ -178,7 +164,7 @@ def test_debug_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--debug" + "--debug", "wlan0", ], namespace=config) self.assertTrue(config.debug) @@ -192,7 +178,7 @@ def test_fake_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--fake" + "--fake", "wlan0", ], namespace=config) self.assertTrue(config.fake) @@ -206,7 +192,7 @@ def test_ignore_case_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--ignore-case" + "--ignore-case", "wlan0", ], namespace=config) self.assertTrue(config.ignore_case) @@ -220,7 +206,7 @@ def test_short_output_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "-o", self.output_test_file + "-o", self.output_test_file, "wlan0", ], namespace=config) self.assertIsInstance(config.output_file, TextIOWrapper) @@ -235,7 +221,7 @@ def test_long_output_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--output", self.output_test_file + "--output", self.output_test_file, "wlan0", ], namespace=config) self.assertIsInstance(config.output_file, TextIOWrapper) @@ -250,7 +236,7 @@ def test_short_essid_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "-e", "essid_1", "essid_2", "essid_3" + "-e", "essid_1", "essid_2", "essid_3", "--", "wlan0", ], namespace=config) self.assertListEqual(config.essid_filters, [ @@ -266,7 +252,7 @@ def test_long_essid_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--essid", "essid_1", "essid_2", "essid_3" + "--essid", "essid_1", "essid_2", "essid_3", "--", "wlan0", ], namespace=config) self.assertListEqual(config.essid_filters, [ @@ -282,7 +268,7 @@ def test_short_regex_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "-r", "test_regex" + "-r", "test_regex", "wlan0", ], namespace=config) self.assertEqual(config.essid_regex, "test_regex") @@ -296,7 +282,7 @@ def test_long_regex_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--regex", "test_regex" + "--regex", "test_regex", "wlan0", ], namespace=config) self.assertEqual(config.essid_regex, "test_regex") @@ -315,8 +301,9 @@ def test_essid_regex_mutual_exclusivity(self): with redirect_stderr(error_output): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--essid", "essid_1", - "--regex", "test_regex" + "--essid", "essid_1", + "--regex", "test_regex", + "wlan0", ], namespace=config) self.assertEqual(error_code.exception.code, 2) @@ -330,8 +317,8 @@ def test_exclude_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--exclude", "aa:bb:cc:dd:ee:ff", - "ff:ee:dd:cc:bb:aa" + "--exclude", "aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa", + "--", "wlan0", ], namespace=config) self.assertListEqual(config.mac_exclusions, [ @@ -348,8 +335,8 @@ def test_short_station_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "-s", "aa:bb:cc:dd:ee:ff", - "ff:ee:dd:cc:bb:aa" + "-s", "aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa", + "--", "wlan0", ], namespace=config) self.assertListEqual(config.mac_filters, [ @@ -366,8 +353,8 @@ def test_long_station_option(self): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--station", "aa:bb:cc:dd:ee:ff", - "ff:ee:dd:cc:bb:aa" + "--station", "aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa", + "--", "wlan0", ], namespace=config) self.assertListEqual(config.mac_filters, [ @@ -390,8 +377,9 @@ def test_exclude_station_mutual_exclusivity(self): with redirect_stderr(error_output): config = Namespace() self.arg_parser.parse_args([ - "-i", "wlan0", "--exclude", "aa:bb:cc:dd:ee:ff", - "--station", "ff:ee:dd:cc:bb:aa" + "--exclude", "aa:bb:cc:dd:ee:ff", + "--station", "ff:ee:dd:cc:bb:aa", + "wlan0", ], namespace=config) self.assertEqual(error_code.exception.code, 2) From e4fcf8d32dab1315053f0b3e314241ab0115f158 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 9 Nov 2020 01:25:11 +0000 Subject: [PATCH 074/126] Check 'interface' value in each CLI test --- tests/unit/test_cli.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 04ed79d..4085e4b 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -168,6 +168,7 @@ def test_debug_option(self): ], namespace=config) self.assertTrue(config.debug) + self.assertEqual(config.interface, "wlan0") def test_fake_option(self): """ @@ -182,6 +183,7 @@ def test_fake_option(self): ], namespace=config) self.assertTrue(config.fake) + self.assertEqual(config.interface, "wlan0") def test_ignore_case_option(self): """ @@ -196,6 +198,7 @@ def test_ignore_case_option(self): ], namespace=config) self.assertTrue(config.ignore_case) + self.assertEqual(config.interface, "wlan0") def test_short_output_option(self): """ @@ -211,6 +214,7 @@ def test_short_output_option(self): self.assertIsInstance(config.output_file, TextIOWrapper) config.output_file.close() + self.assertEqual(config.interface, "wlan0") def test_long_output_option(self): """ @@ -226,6 +230,7 @@ def test_long_output_option(self): self.assertIsInstance(config.output_file, TextIOWrapper) config.output_file.close() + self.assertEqual(config.interface, "wlan0") def test_short_essid_option(self): """ @@ -242,6 +247,7 @@ def test_short_essid_option(self): self.assertListEqual(config.essid_filters, [ "essid_1", "essid_2", "essid_3" ]) + self.assertEqual(config.interface, "wlan0") def test_long_essid_option(self): """ @@ -258,6 +264,7 @@ def test_long_essid_option(self): self.assertListEqual(config.essid_filters, [ "essid_1", "essid_2", "essid_3" ]) + self.assertEqual(config.interface, "wlan0") def test_short_regex_option(self): """ @@ -272,6 +279,7 @@ def test_short_regex_option(self): ], namespace=config) self.assertEqual(config.essid_regex, "test_regex") + self.assertEqual(config.interface, "wlan0") def test_long_regex_option(self): """ @@ -286,6 +294,7 @@ def test_long_regex_option(self): ], namespace=config) self.assertEqual(config.essid_regex, "test_regex") + self.assertEqual(config.interface, "wlan0") def test_essid_regex_mutual_exclusivity(self): """ @@ -325,6 +334,7 @@ def test_exclude_option(self): "aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa", ]) + self.assertEqual(config.interface, "wlan0") def test_short_station_option(self): """ @@ -343,6 +353,7 @@ def test_short_station_option(self): "aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa", ]) + self.assertEqual(config.interface, "wlan0") def test_long_station_option(self): """ @@ -361,6 +372,7 @@ def test_long_station_option(self): "aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa", ]) + self.assertEqual(config.interface, "wlan0") def test_exclude_station_mutual_exclusivity(self): """ From 07a3319e92258d6ebd52dbed3102624c3bab763a Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 9 Nov 2020 01:49:34 +0000 Subject: [PATCH 075/126] Add metavars to argument parser --- probequest/cli.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/probequest/cli.py b/probequest/cli.py index 59118b1..5429e94 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -26,37 +26,38 @@ def get_arg_parser(): """ arg_parser = ArgumentParser( - description="Toolkit for Playing with Wi-Fi Probe Requests" + description="Toolkit for Playing with Wi-Fi Probe Requests", ) arg_parser.add_argument( "interface", - help="wireless interface to use (must be in monitor mode)" + help="wireless interface to use (must be in monitor mode)", ) arg_parser.add_argument( "--debug", action="store_true", dest="debug", - help="debug mode" + help="debug mode", ) arg_parser.add_argument( "--fake", action="store_true", dest="fake", - help="display only fake ESSIDs") + help="display only fake ESSIDs", + ) arg_parser.add_argument( "--ignore-case", action="store_true", dest="ignore_case", - help="ignore case distinctions in the regex pattern (default: false)" + help="ignore case distinctions in the regex pattern (default: false)", ) arg_parser.add_argument( "--mode", type=Mode, choices=Mode.__members__.values(), dest="mode", - help="set the mode to use" + help="set the mode to use", ) arg_parser.add_argument( "-o", "--output", type=FileType("a"), dest="output_file", - help="output file to save the captured data (CSV format)" + help="output file to save the captured data (CSV format)", ) arg_parser.add_argument("--version", action="version", version=VERSION) arg_parser.set_defaults(debug=False) @@ -68,27 +69,31 @@ def get_arg_parser(): essid_arguments.add_argument( "-e", "--essid", nargs="+", + metavar="ESSID", dest="essid_filters", - help="ESSID of the APs to filter (space-separated list)" + help="ESSID of the APs to filter (space-separated list)", ) essid_arguments.add_argument( "-r", "--regex", + metavar="REGEX", dest="essid_regex", - help="regex to filter the ESSIDs" + help="regex to filter the ESSIDs", ) station_arguments = arg_parser.add_mutually_exclusive_group() station_arguments.add_argument( "--exclude", nargs="+", + metavar="STATION", dest="mac_exclusions", - help="MAC addresses of the stations to exclude (space-separated list)" + help="MAC addresses of the stations to exclude (space-separated list)", ) station_arguments.add_argument( "-s", "--station", nargs="+", + metavar="STATION", dest="mac_filters", - help="MAC addresses of the stations to filter (space-separated list)" + help="MAC addresses of the stations to filter (space-separated list)", ) return arg_parser From 4418d693cb84863510177c46ed4a8f38d8ac236b Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 19 Feb 2021 23:58:39 +0000 Subject: [PATCH 076/126] Close open files before exiting --- probequest/cli.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/probequest/cli.py b/probequest/cli.py index 5429e94..432a92c 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -224,4 +224,9 @@ def main(): viewer.stop() finally: print("[*] Bye!") + + if config.output_file is not None: + logger.debug("Closing output file") + config.output_file.close() + logger.info("Program ended") From d348ad2fbc1de4117c6e7fe1ff1cf2591a0b0853 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 00:44:09 +0000 Subject: [PATCH 077/126] Add CSV exporter --- probequest/exporters/__init__.py | 0 probequest/exporters/csv.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 probequest/exporters/__init__.py create mode 100644 probequest/exporters/csv.py diff --git a/probequest/exporters/__init__.py b/probequest/exporters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/probequest/exporters/csv.py b/probequest/exporters/csv.py new file mode 100644 index 0000000..a8344ee --- /dev/null +++ b/probequest/exporters/csv.py @@ -0,0 +1,31 @@ +""" +Probe request CSV exporter module. +""" + +from csv import writer + +from scapy.pipetool import Sink + + +class ProbeRequestCSVExporter(Sink): + """ + A probe request CSV exporter. + """ + + def __init__(self, config, name=None): + Sink.__init__(self, name=name) + + self.csv_file = config.output_file + self.csv_writer = None + + if self.csv_file is not None: + self.csv_writer = writer(self.csv_file, delimiter=";") + + def push(self, msg): + if self.csv_writer is not None: + self.csv_writer.writerow([ + msg.timestamp, + msg.s_mac, + msg.s_mac_oui, + msg.essid + ]) From feb18cf302c9d1df6ad2bdec49b1e9ddf07cb7fc Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 00:45:12 +0000 Subject: [PATCH 078/126] Add console UI component --- probequest/ui/console.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 probequest/ui/console.py diff --git a/probequest/ui/console.py b/probequest/ui/console.py new file mode 100644 index 0000000..d4609a5 --- /dev/null +++ b/probequest/ui/console.py @@ -0,0 +1,17 @@ +""" +Probe request console module. +""" + +from scapy.pipetool import Sink + + +class ProbeRequestConsole(Sink): + """ + Probe request displaying sink. + """ + + def push(self, msg): + print(msg) + + def high_push(self, msg): + print(msg) From c691c2b5f6d99860d14025404d3bf18f362fab3f Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 01:00:14 +0000 Subject: [PATCH 079/126] Remove 'probequest/ui/raw.py' --- probequest/config.py | 40 -------------------------------- probequest/ui/raw.py | 54 -------------------------------------------- 2 files changed, 94 deletions(-) delete mode 100644 probequest/ui/raw.py diff --git a/probequest/config.py b/probequest/config.py index 422e17e..b806bbc 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -40,52 +40,12 @@ class Config: fake = False debug = False - _display_func = lambda *args: None # noqa: E731 - _storage_func = lambda *args: None # noqa: E731 _compiled_essid_regex = None _frame_filter = None def __init__(self): self.logger = logging.getLogger(__name__) - @property - def display_func(self): - """ - Callback function triggered when a packet needs to be displayed. - """ - - return self._display_func - - @property - def storage_func(self): - """ - Callback function triggered when a packet needs to be stored. - """ - - return self._storage_func - - @display_func.setter - def display_func(self, func): - if not hasattr(func, "__call__"): - self.logger.error("Not a callable object: %s", func) - raise TypeError( - "The display function property must be a callable object" - ) - - self._display_func = func - self.logger.debug("Display function set") - - @storage_func.setter - def storage_func(self, func): - if not hasattr(func, "__call__"): - self.logger.error("Not a callable object: %s", func) - raise TypeError( - "The storage function property must be a callable object" - ) - - self._storage_func = func - self.logger.debug("Storage function set") - @property def frame_filter(self): """ diff --git a/probequest/ui/raw.py b/probequest/ui/raw.py deleted file mode 100644 index 50cb8cf..0000000 --- a/probequest/ui/raw.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Raw probe request viewer. -""" - -from csv import writer - -from ..probe_request_sniffer import ProbeRequestSniffer - - -class RawProbeRequestViewer: - """ - Displays the raw probe requests passing nearby the Wi-Fi interface. - """ - - def __init__(self, config): - self.output = config.output_file - - if self.output is not None: - outfile = writer(self.output, delimiter=";") - - def write_csv(probe_req): - outfile.writerow([ - probe_req.timestamp, - probe_req.s_mac, - probe_req.s_mac_oui, - probe_req.essid - ]) - else: - write_csv = lambda *args: None # noqa: E731 - - def display_probe_req(probe_req): - print(probe_req) - - config.display_func = display_probe_req - config.storage_func = write_csv - - self.sniffer = ProbeRequestSniffer(config) - - def start(self): - """ - Starts the probe request sniffer. - """ - - self.sniffer.start() - - def stop(self): - """ - Stops the probe request sniffer. - """ - - self.sniffer.stop() - - if self.output is not None: - self.output.close() From a80089a51d9cf2ee4d621b372040b3afed7127f6 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 01:16:51 +0000 Subject: [PATCH 080/126] Refactor probe request sniffer --- probequest/packet_sniffer.py | 57 -------------------- probequest/sniffers/probe_request_sniffer.py | 24 +++++++++ 2 files changed, 24 insertions(+), 57 deletions(-) delete mode 100644 probequest/packet_sniffer.py create mode 100644 probequest/sniffers/probe_request_sniffer.py diff --git a/probequest/packet_sniffer.py b/probequest/packet_sniffer.py deleted file mode 100644 index 388cba5..0000000 --- a/probequest/packet_sniffer.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Packet sniffer module. -""" - -import logging - -from scapy.sendrecv import AsyncSniffer - - -class PacketSniffer: - """ - Wrapper around the 'AsyncSniffer' class from the Scapy project. - """ - - def __init__(self, config, new_packets): - self.logger = logging.getLogger(__name__) - - self.config = config - self.new_packets = new_packets - - self.sniffer = AsyncSniffer( - iface=self.config.interface, - filter=self.config.frame_filter, - store=False, - prn=self.new_packet - ) - - def start(self): - """ - Starts the packet sniffer. - """ - - self.logger.debug("Starting the packet sniffer") - self.sniffer.start() - - def stop(self): - """ - Stops the packet sniffer. - """ - - self.logger.debug("Stopping the packet sniffer") - self.sniffer.stop() - - def is_running(self): - """ - Returns true if the sniffer is running, false otherwise. - """ - - return self.sniffer.running - - def new_packet(self, packet): - """ - Adds the packet given as parameter to the queue to be processed by the - parser. - """ - - self.new_packets.put(packet) diff --git a/probequest/sniffers/probe_request_sniffer.py b/probequest/sniffers/probe_request_sniffer.py new file mode 100644 index 0000000..7cca8fb --- /dev/null +++ b/probequest/sniffers/probe_request_sniffer.py @@ -0,0 +1,24 @@ +""" +Probe request sniffer module. +""" + +from scapy.scapypipes import SniffSource + + +class ProbeRequestSniffer(SniffSource): + """ + Probe request sniffer. + + Wrapper around the 'SniffSource' Scapy pipe module. + """ + + def __init__(self, config): + self.config = config + + frame_filter = self.config.frame_filter + + SniffSource.__init__( + self, + iface=self.config.interface, + filter=frame_filter + ) From 3622fec302aa1b1c1a39ee026c44e19eda0cda00 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 01:22:03 +0000 Subject: [PATCH 081/126] Refactor fake probe request sniffer --- probequest/fake_packet_sniffer.py | 81 ---------------- .../sniffers/fake_probe_request_sniffer.py | 92 +++++++++++++++++++ 2 files changed, 92 insertions(+), 81 deletions(-) delete mode 100644 probequest/fake_packet_sniffer.py create mode 100644 probequest/sniffers/fake_probe_request_sniffer.py diff --git a/probequest/fake_packet_sniffer.py b/probequest/fake_packet_sniffer.py deleted file mode 100644 index 200a78d..0000000 --- a/probequest/fake_packet_sniffer.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Fake packet sniffer module. -""" - -import logging -from time import sleep -from threading import Thread, Event - -from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt - -from faker import Faker -from faker_wifi_essid import WifiESSID - - -class FakePacketSniffer(Thread): - """ - A fake packet sniffing thread. - - This thread returns fake Wi-Fi ESSIDs for development and test purposes. - """ - - def __init__(self, config, new_packets): - # pylint: disable=duplicate-code - - super().__init__() - - self.logger = logging.getLogger(__name__) - - self.config = config - self.new_packets = new_packets - - self.stop_sniffer = Event() - - self.fake = Faker() - self.fake.add_provider(WifiESSID) - - def run(self): - self.logger.debug("Starting the fake probe request sniffer") - - while not self.stop_sniffer.isSet(): - sleep(1) - self.new_packet() - - def join(self, timeout=None): - """ - Stops the fake packet sniffer. - """ - - self.logger.debug("Stopping the fake probe request sniffer") - - self.stop_sniffer.set() - super().join(timeout) - - def stop(self): - """ - Stops the fake packet sniffer. - - Alias for 'join()'. - """ - - self.join() - - def new_packet(self): - """ - Adds a new fake packet to the queue to be processed. - """ - - # pylint: disable=no-member - - fake_probe_req = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2=self.fake.mac_address(), - addr3=self.fake.mac_address() - ) \ - / Dot11ProbeReq() \ - / Dot11Elt( - info=self.fake.wifi_essid() - ) - - self.new_packets.put(fake_probe_req) diff --git a/probequest/sniffers/fake_probe_request_sniffer.py b/probequest/sniffers/fake_probe_request_sniffer.py new file mode 100644 index 0000000..2ba4201 --- /dev/null +++ b/probequest/sniffers/fake_probe_request_sniffer.py @@ -0,0 +1,92 @@ +""" +Fake probe request sniffer module. +""" + +from time import sleep + +from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt +from scapy.pipetool import ThreadGenSource + +from faker import Faker +from faker_wifi_essid import WifiESSID + + +class FakeProbeRequestSniffer(ThreadGenSource): + """ + A fake probe request sniffer. + + This pipe source sends periodically fake Wi-Fi ESSIDs for development and + test purposes. + + This class inherits from 'ThreadGenSource' and not from 'PeriodicSource' as + this last one only accepts lists, sets and tuples. + """ + + def __init__(self, period, period2=0, name=None): + ThreadGenSource.__init__(self, name=name) + + self.fake_probe_requests = FakeProbeRequest() + self.period = period + self.period2 = period2 + + def generate(self): + while self.RUN: + # Infinite loop until 'stop()' is called. + for fake_probe_req in self.fake_probe_requests: + self._gen_data(fake_probe_req) + sleep(self.period) + + self.is_exhausted = True + self._wake_up() + + sleep(self.period2) + + def stop(self): + ThreadGenSource.stop(self) + self.fake_probe_requests.stop() + + +class FakeProbeRequest: + """ + A fake probe request iterator. + """ + + def __init__(self): + self._fake = Faker() + self._fake.add_provider(WifiESSID) + + self._should_stop = False + + def __iter__(self): + return self + + def __next__(self): + """ + Generator of fake Wi-Fi probe requests. + """ + + # pylint: disable=no-member + + if self._should_stop: + raise StopIteration + + return RadioTap() \ + / Dot11( + addr1="ff:ff:ff:ff:ff:ff", + addr2=self._fake.mac_address(), + addr3=self._fake.mac_address() + ) \ + / Dot11ProbeReq() \ + / Dot11Elt( + info=self._fake.wifi_essid() + ) + + def stop(self): + """ + Interrupts the iteration. + + The next time the iterator will be called, a 'StopIteration' exception + will be raised. + """ + + self._should_stop = True From f83cfcee1f421dee3eaff8e023f91fc5f88e5b18 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 01:58:47 +0000 Subject: [PATCH 082/126] Remove 'probequest/probe_request_sniffer.py' --- probequest/probe_request_sniffer.py | 115 ---------------------------- 1 file changed, 115 deletions(-) delete mode 100644 probequest/probe_request_sniffer.py diff --git a/probequest/probe_request_sniffer.py b/probequest/probe_request_sniffer.py deleted file mode 100644 index 23193d3..0000000 --- a/probequest/probe_request_sniffer.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Wi-Fi probe request sniffer. -""" - -import logging -from queue import Queue - -from scapy.arch import get_if_hwaddr -from scapy.error import Scapy_Exception - -from .packet_sniffer import PacketSniffer -from .fake_packet_sniffer import FakePacketSniffer -from .probe_request_parser import ProbeRequestParser - - -class ProbeRequestSniffer: - """ - Wi-Fi probe request sniffer. - - It is composed of a packet sniffer and a packet parser, both running - in a thread and intercommunicating using a queue. - """ - - def __init__(self, config): - self.logger = logging.getLogger(__name__) - - self.config = config - - self.new_packets = Queue() - self.new_sniffer() - self.new_parser() - - def start(self): - """ - Starts the probe request sniffer. - - This method will start the sniffing and parsing threads. - """ - - self.logger.debug("Starting the probe request sniffer") - - try: - # Test if the interface exists. - get_if_hwaddr(self.config.interface) - except Scapy_Exception: - pass - - self.sniffer.start() - - try: - self.parser.start() - except RuntimeError: - self.new_parser() - self.parser.start() - - def stop(self): - """ - Stops the probe request sniffer. - - This method will stop the sniffing and parsing threads. - """ - - self.logger.debug("Stopping the probe request sniffer") - - try: - self.sniffer.stop() - except Scapy_Exception: - # The sniffer was not running. - pass - - try: - self.parser.join() - except RuntimeError: - # stop() has been called before start(). - pass - - def new_sniffer(self): - """ - Creates a new sniffing thread. - - If the '--fake' option is set, a fake packet sniffer will be used. - """ - - if self.config.fake: - self.logger.debug("Creating a new fake probe request sniffer") - self.sniffer = FakePacketSniffer( - self.config, - self.new_packets - ) - else: - self.logger.debug("Creating a new probe request sniffer") - self.sniffer = PacketSniffer( - self.config, - self.new_packets - ) - - def new_parser(self): - """ - Creates a new parsing thread. - """ - - self.logger.debug("Creating a new probe request parser") - - self.parser = ProbeRequestParser( - self.config, - self.new_packets - ) - - def is_running(self): - """ - Returns true if the probe request sniffer is running and false - otherwise. - """ - - return self.sniffer.is_running() From 72322fd79ab268b838bb2eb2ab695a377b04d2d1 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 02:02:28 +0000 Subject: [PATCH 083/126] Refactor 'ProbeRequestParser' --- probequest/probe_request_parser.py | 81 ++++++++---------------------- 1 file changed, 20 insertions(+), 61 deletions(-) diff --git a/probequest/probe_request_parser.py b/probequest/probe_request_parser.py index 8d58859..7679657 100644 --- a/probequest/probe_request_parser.py +++ b/probequest/probe_request_parser.py @@ -2,75 +2,33 @@ Probe request parser module. """ -import logging -from queue import Empty -from re import match -from threading import Thread, Event - +from scapy.pipetool import Drain from scapy.layers.dot11 import RadioTap, Dot11ProbeReq -from .probe_request import ProbeRequest +from probequest.probe_request import ProbeRequest -class ProbeRequestParser(Thread): +class ProbeRequestParser(Drain): """ - A Wi-Fi probe request parsing thread. + A Wi-Fi probe request parsing drain. """ - def __init__(self, config, new_packets): - super().__init__() - - self.logger = logging.getLogger(__name__) + def __init__(self, config, name=None): + Drain.__init__(self, name=name) self.config = config - self.new_packets = new_packets - - self.cregex = self.config.compiled_essid_regex - - self.stop_parser = Event() - - def run(self): - self.logger.debug("Starting the probe request parser") - - # The parser continues to do its job even after the call of the - # join method if the queue is not empty. - while not self.stop_parser.isSet() or not self.new_packets.empty(): - try: - packet = self.new_packets.get(timeout=1) - probe_request = self.parse(packet) - - if probe_request is None: - continue - - if not probe_request.essid: - continue - - if (self.config.essid_filters is not None - and probe_request.essid - not in self.config.essid_filters): - continue - if (self.cregex is not None - and not - match(self.cregex, probe_request.essid)): - continue - - self.config.display_func(probe_request) - self.config.storage_func(probe_request) - - self.new_packets.task_done() - except Empty: - pass - - def join(self, timeout=None): - """ - Stops the probe request parsing thread. - """ - - self.logger.debug("Stopping the probe request parser") + def push(self, msg): + try: + self._send(self.parse(msg)) + except TypeError: + return - self.stop_parser.set() - super().join(timeout) + def high_push(self, msg): + try: + self._high_send(self.parse(msg)) + except TypeError: + return @staticmethod def parse(packet): @@ -86,7 +44,8 @@ def parse(packet): return ProbeRequest(timestamp, s_mac, essid) - return None - except UnicodeDecodeError: + # The packet is not a probe request. + raise TypeError + except UnicodeDecodeError as unicode_decode_err: # The ESSID is not a valid UTF-8 string. - return None + raise TypeError from unicode_decode_err From 25ead9802456683a4ea1c6cb3387b6185b999c16 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 02:03:09 +0000 Subject: [PATCH 084/126] Add 'ProbeRequestFilter' --- probequest/probe_request_filter.py | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 probequest/probe_request_filter.py diff --git a/probequest/probe_request_filter.py b/probequest/probe_request_filter.py new file mode 100644 index 0000000..61a67cb --- /dev/null +++ b/probequest/probe_request_filter.py @@ -0,0 +1,50 @@ +""" +Probe request filter module. +""" + +from re import match + +from scapy.pipetool import Drain + + +class ProbeRequestFilter(Drain): + """ + A Wi-Fi probe request filtering drain. + """ + + def __init__(self, config, name=None): + Drain.__init__(self, name=name) + + self._config = config + self._cregex = self._config.compiled_essid_regex + + def push(self, msg): + if self.can_pass(msg): + self._send(msg) + + def high_push(self, msg): + if self.can_pass(msg): + self._send(msg) + + def can_pass(self, probe_req): + """ + Whether or not the probe request given as parameter can pass the drain + according to a set of filters. + """ + + # If the probe request doesn't have an ESSID. + if not probe_req.essid: + return False + + # If the probe request's ESSID is not one of those in the filtering + # list. + if (self._config.essid_filters is not None and + probe_req.essid not in self._config.essid_filters): + return False + + # If the probe request's ESSID doesn't match the regex. + if (self._cregex is not None and + not match(self._cregex, probe_req.essid)): + return False + + return True From 114ec03666abbf8881f246d84da7f621865c8ddf Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 02:58:57 +0000 Subject: [PATCH 085/126] Remove irrelevant tests --- tests/unit/test_config.py | 23 ------- tests/unit/test_fake_packet_sniffer.py | 76 ---------------------- tests/unit/test_packet_sniffer.py | 82 ------------------------ tests/unit/test_probe_request_sniffer.py | 68 -------------------- tests/unit/utils.py | 2 - 5 files changed, 251 deletions(-) delete mode 100644 tests/unit/test_fake_packet_sniffer.py delete mode 100644 tests/unit/test_packet_sniffer.py delete mode 100644 tests/unit/test_probe_request_sniffer.py diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index adc4d6e..34de7b7 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -44,35 +44,12 @@ def test_default_values(self): self.assertFalse(config.fake) self.assertFalse(config.debug) - self.assertTrue(hasattr(config.display_func, "__call__")) - self.assertTrue(hasattr(config.storage_func, "__call__")) - with self.assertLogs(self.logger, level=logging.DEBUG): self.assertEqual( config.frame_filter, "type mgt subtype probe-req" ) - def test_bad_display_function(self): - """ - Assigns a non-callable object to the display callback function. - """ - - with self.assertLogs(self.logger, level=logging.ERROR): - with self.assertRaises(TypeError): - config = Config() - config.display_func = "test" - - def test_bad_storage_function(self): - """ - Assigns a non-callable object to the storage callback function. - """ - - with self.assertLogs(self.logger, level=logging.ERROR): - with self.assertRaises(TypeError): - config = Config() - config.storage_func = "test" - def test_frame_filter_with_mac_filtering(self): """ Tests the frame filter when some MAC addresses need to be filtered. diff --git a/tests/unit/test_fake_packet_sniffer.py b/tests/unit/test_fake_packet_sniffer.py deleted file mode 100644 index cf54cbb..0000000 --- a/tests/unit/test_fake_packet_sniffer.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -Unit tests for the fake packet sniffer module. -""" - -import logging -import unittest -from queue import Queue - -from probequest.fake_packet_sniffer import FakePacketSniffer -from probequest.probe_request_parser import ProbeRequestParser - -from .utils import create_fake_config - - -class TestFakePacketSniffer(unittest.TestCase): - """ - Unit tests for the 'FakePacketSniffer' class. - """ - - def setUp(self): - """ - Creates a fake package logger. - """ - - self.logger = logging.getLogger("probequest") - self.logger.setLevel(logging.DEBUG) - - def test_new_packet(self): - """ - Tests the 'new_packet' method. - """ - - config = create_fake_config() - new_packets = Queue() - sniffer = FakePacketSniffer(config, new_packets) - - self.assertEqual(sniffer.new_packets.qsize(), 0) - - sniffer.new_packet() - self.assertEqual(sniffer.new_packets.qsize(), 1) - sniffer.new_packet() - self.assertEqual(sniffer.new_packets.qsize(), 2) - sniffer.new_packet() - self.assertEqual(sniffer.new_packets.qsize(), 3) - - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - - def test_stop_before_start(self): - """ - Creates a 'FakePacketSniffer' object and stops the sniffer before - starting it. - """ - - config = create_fake_config() - new_packets = Queue() - sniffer = FakePacketSniffer(config, new_packets) - - with self.assertLogs(self.logger, level=logging.DEBUG): - with self.assertRaises(RuntimeError): - sniffer.stop() - - def test_stop_before_start_using_join(self): - """ - Creates a 'FakePacketSniffer' object and stops the sniffer before - starting it. - """ - - config = create_fake_config() - new_packets = Queue() - sniffer = FakePacketSniffer(config, new_packets) - - with self.assertLogs(self.logger, level=logging.DEBUG): - with self.assertRaises(RuntimeError): - sniffer.join() diff --git a/tests/unit/test_packet_sniffer.py b/tests/unit/test_packet_sniffer.py deleted file mode 100644 index 51fc9be..0000000 --- a/tests/unit/test_packet_sniffer.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Unit tests for the packet sniffer module. -""" - -import logging -import unittest -from queue import Queue - -from scapy.error import Scapy_Exception -from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt - -from probequest.packet_sniffer import PacketSniffer -from probequest.probe_request_parser import ProbeRequestParser - -from .utils import create_fake_config - - -class TestPacketSniffer(unittest.TestCase): - """ - Unit tests for the 'PacketSniffer' class. - """ - - def setUp(self): - """ - Creates a fake package logger. - """ - - self.logger = logging.getLogger("probequest") - self.logger.setLevel(logging.DEBUG) - - def test_new_packet(self): - """ - Tests the 'new_packet' method. - """ - - new_packets = Queue() - config = create_fake_config() - sniffer = PacketSniffer(config, new_packets) - - self.assertEqual(sniffer.new_packets.qsize(), 0) - - packet = RadioTap() \ - / Dot11( - addr1="ff:ff:ff:ff:ff:ff", - addr2="aa:bb:cc:11:22:33", - addr3="dd:ee:ff:11:22:33" - ) \ - / Dot11ProbeReq() \ - / Dot11Elt( - info="Test" - ) - - sniffer.new_packet(packet) - self.assertEqual(sniffer.new_packets.qsize(), 1) - - ProbeRequestParser.parse(sniffer.new_packets.get(timeout=1)) - - def test_stop_before_start(self): - """ - Creates a 'PacketSniffer' object and stops the sniffer before starting - it. - """ - - config = create_fake_config() - new_packets = Queue() - sniffer = PacketSniffer(config, new_packets) - - with self.assertLogs(self.logger, level=logging.DEBUG): - with self.assertRaises(Scapy_Exception): - sniffer.stop() - - def test_is_running_before_start(self): - """ - Creates a 'PacketSniffer' object and runs 'is_running' before starting - the sniffer. - """ - - config = create_fake_config() - new_packets = Queue() - sniffer = PacketSniffer(config, new_packets) - - self.assertFalse(sniffer.is_running()) diff --git a/tests/unit/test_probe_request_sniffer.py b/tests/unit/test_probe_request_sniffer.py deleted file mode 100644 index b699d28..0000000 --- a/tests/unit/test_probe_request_sniffer.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Unit tests for the probe request sniffer module. -""" - -import logging -import unittest - -from probequest.probe_request_sniffer import ProbeRequestSniffer - -from .utils import create_fake_config - - -class TestProbeRequestSniffer(unittest.TestCase): - """ - Unit tests for the 'ProbeRequestSniffer' class. - """ - - def setUp(self): - """ - Creates a fake package logger. - """ - - self.logger = logging.getLogger("probequest") - self.logger.setLevel(logging.DEBUG) - - def test_without_parameters(self): - """ - Initialises a 'ProbeRequestSniffer' object without parameters. - """ - - # pylint: disable=no-value-for-parameter - - with self.assertRaises(TypeError): - _ = ProbeRequestSniffer() - - def test_bad_parameter(self): - """ - Initialises a 'ProbeRequestSniffer' object with a bad parameter. - """ - - # pylint: disable=no-value-for-parameter - - with self.assertRaises(AttributeError): - _ = ProbeRequestSniffer("test") - - def test_create_sniffer(self): - """ - Creates a 'ProbeRequestSniffer' object with the correct parameter. - """ - - # pylint: disable=no-self-use - - config = create_fake_config() - _ = ProbeRequestSniffer(config) - - def test_stop_before_start(self): - """ - Creates a 'ProbeRequestSniffer' object and stops the sniffer before - starting it. - """ - - # pylint: disable=no-self-use - - config = create_fake_config() - sniffer = ProbeRequestSniffer(config) - - with self.assertLogs(self.logger, level=logging.DEBUG): - sniffer.stop() diff --git a/tests/unit/utils.py b/tests/unit/utils.py index b451b22..14cc25d 100644 --- a/tests/unit/utils.py +++ b/tests/unit/utils.py @@ -29,8 +29,6 @@ def create_fake_config(): config.fake = False config.debug = False - config.display_func = lambda *args: None - config.storage_func = lambda *args: None config.compiled_essid_regex = None config.frame_filter = None From a06900c4c4556745f7fcc5d0d97d4cbe030a6a34 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 03:11:29 +0000 Subject: [PATCH 086/126] Remove PNL mode --- probequest/config.py | 15 --- probequest/ui/pnl.py | 203 -------------------------------------- tests/unit/test_cli.py | 2 - tests/unit/test_config.py | 3 +- tests/unit/utils.py | 3 - 5 files changed, 1 insertion(+), 225 deletions(-) delete mode 100644 probequest/ui/pnl.py diff --git a/probequest/config.py b/probequest/config.py index b806bbc..bb9925d 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -3,23 +3,9 @@ """ import logging -from enum import Enum from re import compile as rcompile, IGNORECASE -class Mode(Enum): - """ - Enumeration of the different operational modes - supported by this software. - """ - - RAW = "raw" - PNL = "pnl" - - def __str__(self): - return str(self.value) - - class Config: """ Configuration object. @@ -36,7 +22,6 @@ class Config: output_file = None - mode = Mode.RAW fake = False debug = False diff --git a/probequest/ui/pnl.py b/probequest/ui/pnl.py deleted file mode 100644 index c3e11c7..0000000 --- a/probequest/ui/pnl.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -Preferred network list viewer. -""" - -import urwid - -from ..probe_request_sniffer import ProbeRequestSniffer - - -class PNLViewer: - """ - TUI used to display the PNL of the nearby devices sending probe requests. - """ - - # pylint: disable=too-many-instance-attributes - - palette = [ - ("header_running", "white", "dark green", "bold"), - ("header_stopped", "white", "dark red", "bold"), - ("footer", "dark cyan", "dark blue", "bold"), - ("key", "light cyan", "dark blue", "underline"), - ("selected", "black", "light green"), - ] - - footer_text = ("footer", [ - " ", - ("key", "P"), " play/pause ", - ("key", "Q"), " quit", - ]) - - def __init__(self, config): - self.config = config - - self.stations = dict() - self.loop = None - - self.config.display_func = self.new_probe_req - self.sniffer = ProbeRequestSniffer(config) - - self.view = self.setup_view() - - def setup_view(self): - """ - Returns the root widget. - """ - - self.interface_text = urwid.Text(self.config.interface) - self.sniffer_state_text = urwid.Text("Stopped") - - self.header = urwid.AttrWrap(urwid.Columns([ - urwid.Text("Sniffer's state: "), - self.sniffer_state_text, - urwid.Text(" Interface: "), - self.interface_text, - ]), "header_stopped") - footer = urwid.AttrWrap(urwid.Text(self.footer_text), "footer") - vline = urwid.AttrWrap(urwid.SolidFill(u"\u2502"), "line") - - station_panel = urwid.Padding( - self.setup_menu("List of Stations", self.stations.keys()), - align="center", width=("relative", 90) - ) - pnl_panel = urwid.Padding( - urwid.ListBox(urwid.SimpleListWalker([])), - align="center", width=("relative", 90) - ) - - self.station_list = station_panel.base_widget - self.pnl_list = pnl_panel.base_widget - - body = urwid.Columns([ - station_panel, - ("fixed", 1, vline), - pnl_panel, - ], focus_column=0) - - top = urwid.Frame( - header=self.header, - body=body, - footer=footer, - focus_part="body" - ) - - return top - - def setup_menu(self, title, choices): - """ - Creates and returns a dynamic ListBox object containing a title and the - choices given as parameters. - """ - - body = [urwid.Text(title), urwid.Divider()] - - for choice in choices: - button = urwid.Button(choice) - urwid.connect_signal(button, "click", self.station_chosen, choice) - body.append(urwid.AttrMap(button, None, focus_map="selected")) - - return urwid.ListBox(urwid.SimpleFocusListWalker(body)) - - def new_probe_req(self, probe_req): - """ - Callback method called on each new probe request. - """ - - if probe_req.s_mac not in self.stations: - self.stations[probe_req.s_mac] = [] - self.add_station(probe_req.s_mac) - - if not any(essid.text == probe_req.essid - for essid in self.stations[probe_req.s_mac]): - self.stations[probe_req.s_mac].append(urwid.Text(probe_req.essid)) - - if len(self.stations.keys()) == 1: - self.station_list.set_focus(2) - self.station_chosen(None, probe_req.s_mac) - - self.loop.draw_screen() - - def add_station(self, name): - """ - Adds a new station to the stations list. - """ - - button = urwid.Button(name) - urwid.connect_signal(button, "click", self.station_chosen, name) - self.station_list.body.append( - urwid.AttrMap(button, None, focus_map="selected") - ) - - def station_chosen(self, button, choice): - """ - Callback method called when a station is selected in the station list. - """ - - # pylint: disable=unused-argument - - # 'button' is the widget object passed by Urwid. - - self.pnl_list.body = self.stations[choice] - - def start_sniffer(self): - """ - Starts the sniffer. - """ - - self.sniffer.start() - self.sniffer_state_text.set_text("Running") - self.header.set_attr("header_running") - - def stop_sniffer(self): - """ - Stops the sniffer. - """ - - self.sniffer.stop() - self.sniffer_state_text.set_text("Stopped") - self.header.set_attr("header_stopped") - - def toggle_sniffer_state(self): - """ - Toggles the sniffer's state. - """ - - if self.sniffer.is_running(): - self.stop_sniffer() - else: - self.start_sniffer() - - def main(self): - """ - Starts the TUI. - """ - - self.loop = urwid.MainLoop( - self.view, - self.palette, - unhandled_input=self.unhandled_keypress - ) - self.loop.run() - - def exit_program(self): - """ - Stops and exits the TUI. - """ - - self.sniffer.stop() - raise urwid.ExitMainLoop() - - def unhandled_keypress(self, key): - """ - Contains handlers for each keypress that is not handled by the widgets - being displayed. - """ - - if key in ("q", "Q"): - self.exit_program() - elif key in ("p", "P"): - self.toggle_sniffer_state() - else: - return False - - return True diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 4085e4b..f9c4b21 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -12,7 +12,6 @@ from probequest import __version__ as VERSION from probequest.cli import get_arg_parser -from probequest.config import Mode class TestArgParse(unittest.TestCase): @@ -118,7 +117,6 @@ def test_default_values(self): self.assertIsNone(config.mac_exclusions) self.assertIsNone(config.mac_filters) self.assertIsNone(config.output_file) - self.assertEqual(config.mode, Mode.RAW) self.assertFalse(config.fake) self.assertFalse(config.debug) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 34de7b7..e1fd39b 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -6,7 +6,7 @@ import unittest from re import compile as rcompile, IGNORECASE -from probequest.config import Config, Mode +from probequest.config import Config class TestConfig(unittest.TestCase): @@ -40,7 +40,6 @@ def test_default_values(self): self.assertIsNone(config.output_file) - self.assertEqual(config.mode, Mode.RAW) self.assertFalse(config.fake) self.assertFalse(config.debug) diff --git a/tests/unit/utils.py b/tests/unit/utils.py index 14cc25d..b09f217 100644 --- a/tests/unit/utils.py +++ b/tests/unit/utils.py @@ -4,8 +4,6 @@ from argparse import Namespace -from probequest.config import Mode - def create_fake_config(): """ @@ -25,7 +23,6 @@ def create_fake_config(): config.output_file = None - config.mode = Mode.RAW config.fake = False config.debug = False From 39b47a7333841bc7d9a11135fe8ff68e6cf9dc1f Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 03:16:59 +0000 Subject: [PATCH 087/126] Refactor the CLI module --- probequest/cli.py | 88 +++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/probequest/cli.py b/probequest/cli.py index 432a92c..57ce344 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -9,10 +9,16 @@ from sys import exit as sys_exit from time import sleep +from scapy.pipetool import PipeEngine + from . import __version__ as VERSION -from .config import Config, Mode -from .ui.pnl import PNLViewer -from .ui.raw import RawProbeRequestViewer +from .config import Config +from .probe_request_filter import ProbeRequestFilter +from .probe_request_parser import ProbeRequestParser +from .exporters.csv import ProbeRequestCSVExporter +from .sniffers.fake_probe_request_sniffer import FakeProbeRequestSniffer +from .sniffers.probe_request_sniffer import ProbeRequestSniffer +from .ui.console import ProbeRequestConsole # Used to specify the capacity of the memory handler which will store the logs # in memory until the argument parser is called to know whether they need to be @@ -47,12 +53,6 @@ def get_arg_parser(): dest="ignore_case", help="ignore case distinctions in the regex pattern (default: false)", ) - arg_parser.add_argument( - "--mode", - type=Mode, choices=Mode.__members__.values(), - dest="mode", - help="set the mode to use", - ) arg_parser.add_argument( "-o", "--output", type=FileType("a"), @@ -63,7 +63,6 @@ def get_arg_parser(): arg_parser.set_defaults(debug=False) arg_parser.set_defaults(fake=False) arg_parser.set_defaults(ignore_case=False) - arg_parser.set_defaults(mode=Mode.RAW) essid_arguments = arg_parser.add_mutually_exclusive_group() essid_arguments.add_argument( @@ -122,6 +121,33 @@ def set_up_root_logger(level=logging.DEBUG): return (root_logger, memory_handler, console) +def build_cluster(config): + """ + Build the ProbeQuest cluster. + """ + + # pylint: disable=pointless-statement + + if config.fake: + sniffer = FakeProbeRequestSniffer(1) + else: + sniffer = ProbeRequestSniffer(config) + + parser = ProbeRequestParser(config) + filters = ProbeRequestFilter(config) + console = ProbeRequestConsole() + + engine = PipeEngine(sniffer) + + sniffer > parser > filters > console + + if config.output_file: + csv_exporter = ProbeRequestCSVExporter(config) + filters > csv_exporter + + return engine + + def main(): """ Entry point of the command-line tool. @@ -179,37 +205,18 @@ def main(): sys_exit("[!] You must be root") # -------------------------------------------------- # - # Mode selection + # Sniffing loop # -------------------------------------------------- # - try: - # Default mode. - if config.mode == Mode.RAW: - logger.info("Raw mode selected") - - print("[*] Start sniffing probe requests...") + engine = build_cluster(config) - logger.debug("Configuring the raw viewer") - viewer = RawProbeRequestViewer(config) - - logger.info("Starting the raw viewer") - viewer.start() - - while True: - sleep(100) - elif config.mode == Mode.PNL: - logger.info("PNL mode selected") - - logger.debug("Configuring the PNL viewer") - viewer = PNLViewer(config) - - logger.info("Starting the PNL viewer") - viewer.main() - else: - logger.critical("Invalid mode: %s", config.mode) - sys_exit("[x] Invalid mode") + try: + print("[*] Start sniffing probe requests...") + engine.start() + while True: + sleep(100) except OSError as err: - logger.debug("Stopping the viewer") - viewer.stop() + logger.debug("Stopping the engine") + engine.stop() logger.critical(err, exc_info=True) sys_exit( @@ -219,9 +226,8 @@ def main(): ) except KeyboardInterrupt: logger.info("Keyboard interrupt received") - logger.info("Stopping the viewer") - print("[*] Stopping the threads...") - viewer.stop() + logger.info("Stopping the engine") + engine.stop() finally: print("[*] Bye!") From 2e3866a0745efc2634d55a1d46c971a6229ebe40 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 15:38:36 +0000 Subject: [PATCH 088/126] Fix some tests --- tests/unit/test_probe_request_parser.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/unit/test_probe_request_parser.py b/tests/unit/test_probe_request_parser.py index cbdf80f..6e47007 100644 --- a/tests/unit/test_probe_request_parser.py +++ b/tests/unit/test_probe_request_parser.py @@ -27,8 +27,9 @@ def test_no_probe_request_layer(self): 'ProbeRequestParser.parse()' function. """ - packet = RadioTap() / self.dot11_layer - ProbeRequestParser.parse(packet) + with self.assertRaises(TypeError): + packet = RadioTap() / self.dot11_layer + ProbeRequestParser.parse(packet) def test_empty_essid(self): """ @@ -53,6 +54,7 @@ def test_fuzz_packets(self): # pylint: disable=no-self-use - for _ in range(0, 1000): - packet = RadioTap()/fuzz(Dot11()/Dot11ProbeReq()/Dot11Elt()) - ProbeRequestParser.parse(packet) + with self.assertRaises(TypeError): + for _ in range(0, 1000): + packet = RadioTap()/fuzz(Dot11()/Dot11ProbeReq()/Dot11Elt()) + ProbeRequestParser.parse(packet) From c26a5e2ffe47829c722442037482500780a48464 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 15:38:52 +0000 Subject: [PATCH 089/126] Add missing '__init__.py' file --- probequest/sniffers/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 probequest/sniffers/__init__.py diff --git a/probequest/sniffers/__init__.py b/probequest/sniffers/__init__.py new file mode 100644 index 0000000..e69de29 From 41cb9763983c995f3c76cdb3069565b66d26caf5 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 15:40:10 +0000 Subject: [PATCH 090/126] Move call to 'engine.stop()' into 'finally' block --- probequest/cli.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/probequest/cli.py b/probequest/cli.py index 57ce344..e821b69 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -215,9 +215,6 @@ def main(): while True: sleep(100) except OSError as err: - logger.debug("Stopping the engine") - engine.stop() - logger.critical(err, exc_info=True) sys_exit( "[!] Interface {interface} doesn't exist".format( @@ -226,10 +223,10 @@ def main(): ) except KeyboardInterrupt: logger.info("Keyboard interrupt received") - logger.info("Stopping the engine") - engine.stop() - finally: print("[*] Bye!") + finally: + logger.debug("Stopping the engine") + engine.stop() if config.output_file is not None: logger.debug("Closing output file") From 40ac0dfe8e6d72211a1ff6d4f0d61b434ec6dfbc Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 15:55:44 +0000 Subject: [PATCH 091/126] Add the 'dependencies' issue label --- .github/settings.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/settings.yml b/.github/settings.yml index 71b84a5..d43c5ba 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -41,6 +41,9 @@ labels: - name: cli description: Related to the CLI tool color: 1d76db + - name: dependencies + description: Related to the dependencies + color: 1d76db - name: android description: Android platform support issues From 81a16ed71d085df5062e8a637a272a775acf0485 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 19:01:47 +0000 Subject: [PATCH 092/126] Fix interface checking --- probequest/cli.py | 17 ++++++++--------- probequest/config.py | 26 +++++++++++++++++++++++++- probequest/exceptions.py | 15 +++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 probequest/exceptions.py diff --git a/probequest/cli.py b/probequest/cli.py index e821b69..f2f43cc 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -13,9 +13,10 @@ from . import __version__ as VERSION from .config import Config +from .exceptions import InterfaceDoesNotExistException +from .exporters.csv import ProbeRequestCSVExporter from .probe_request_filter import ProbeRequestFilter from .probe_request_parser import ProbeRequestParser -from .exporters.csv import ProbeRequestCSVExporter from .sniffers.fake_probe_request_sniffer import FakeProbeRequestSniffer from .sniffers.probe_request_sniffer import ProbeRequestSniffer from .ui.console import ProbeRequestConsole @@ -171,7 +172,12 @@ def main(): # Parsing arguments # -------------------------------------------------- # logger.debug("Parsing arguments") - get_arg_parser().parse_args(namespace=config) + + try: + get_arg_parser().parse_args(namespace=config) + except InterfaceDoesNotExistException as err: + logger.critical(err, exc_info=True) + sys_exit("[!] {err_msg}".format(err_msg=err)) # -------------------------------------------------- # # Debug mode @@ -214,13 +220,6 @@ def main(): engine.start() while True: sleep(100) - except OSError as err: - logger.critical(err, exc_info=True) - sys_exit( - "[!] Interface {interface} doesn't exist".format( - interface=config.interface - ) - ) except KeyboardInterrupt: logger.info("Keyboard interrupt received") print("[*] Bye!") diff --git a/probequest/config.py b/probequest/config.py index bb9925d..ebaf7ff 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -5,13 +5,17 @@ import logging from re import compile as rcompile, IGNORECASE +from scapy.arch import get_if_list + +from .exceptions import InterfaceDoesNotExistException + class Config: """ Configuration object. """ - interface = None + _interface = None essid_filters = None essid_regex = None @@ -31,6 +35,26 @@ class Config: def __init__(self): self.logger = logging.getLogger(__name__) + @property + def interface(self): + """ + Interface from which the probe requests will be captured. + """ + + return self._interface + + @interface.setter + def interface(self, interface): + # If interface does not exist. + if interface not in get_if_list(): + raise InterfaceDoesNotExistException( + "Interface {interface} does not exist".format( + interface=interface, + ) + ) + + self._interface = interface + @property def frame_filter(self): """ diff --git a/probequest/exceptions.py b/probequest/exceptions.py new file mode 100644 index 0000000..12e1e5f --- /dev/null +++ b/probequest/exceptions.py @@ -0,0 +1,15 @@ +""" +ProbeQuest exceptions module. +""" + + +class ProbeQuestException(Exception): + """ + Base class for all exceptions thrown by the probequest module. + """ + + +class InterfaceDoesNotExistException(ProbeQuestException): + """ + Thrown when the network interface does not exist. + """ From c8052ff9a300cbae947d43741eb4ee2b842262a0 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 20 Feb 2021 19:45:11 +0000 Subject: [PATCH 093/126] Add some unit tests --- tests/unit/test_config.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index e1fd39b..5f495d9 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -4,9 +4,11 @@ import logging import unittest +from unittest.mock import patch from re import compile as rcompile, IGNORECASE from probequest.config import Config +from probequest.exceptions import InterfaceDoesNotExistException class TestConfig(unittest.TestCase): @@ -49,6 +51,31 @@ def test_default_values(self): "type mgt subtype probe-req" ) + def test_non_existing_interface(self): + """ + Tests if an exception is well raised when setting a non-existing + network interface. + """ + + with patch("probequest.config.get_if_list", return_value=("wlan0", + "wlan0mon")): + config = Config() + + with self.assertRaises(InterfaceDoesNotExistException): + config.interface = "wlan1" + + def test_existing_interface(self): + """ + Tests with an existing network interface. + """ + + # pylint: disable=no-self-use + + with patch("probequest.config.get_if_list", return_value=("wlan0", + "wlan0mon")): + config = Config() + config.interface = "wlan0" + def test_frame_filter_with_mac_filtering(self): """ Tests the frame filter when some MAC addresses need to be filtered. From 37de356ebdeb34bdef1a8e13884f26fb8323295e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 21 Feb 2021 13:53:35 +0000 Subject: [PATCH 094/126] Add log messages to PipeTools modules --- probequest/cli.py | 2 ++ probequest/exporters/csv.py | 5 +++++ probequest/probe_request_filter.py | 5 +++++ probequest/probe_request_parser.py | 6 ++++++ probequest/sniffers/fake_probe_request_sniffer.py | 5 +++++ probequest/sniffers/probe_request_sniffer.py | 6 ++++++ probequest/ui/console.py | 9 +++++++++ 7 files changed, 38 insertions(+) diff --git a/probequest/cli.py b/probequest/cli.py index f2f43cc..81d5011 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -213,9 +213,11 @@ def main(): # -------------------------------------------------- # # Sniffing loop # -------------------------------------------------- # + logger.info("Creating Pipe engine") engine = build_cluster(config) try: + logger.info("Starting Pipe engine") print("[*] Start sniffing probe requests...") engine.start() while True: diff --git a/probequest/exporters/csv.py b/probequest/exporters/csv.py index a8344ee..cfbcc8c 100644 --- a/probequest/exporters/csv.py +++ b/probequest/exporters/csv.py @@ -2,6 +2,7 @@ Probe request CSV exporter module. """ +import logging from csv import writer from scapy.pipetool import Sink @@ -13,6 +14,8 @@ class ProbeRequestCSVExporter(Sink): """ def __init__(self, config, name=None): + self.logger = logging.getLogger(__name__) + Sink.__init__(self, name=name) self.csv_file = config.output_file @@ -21,6 +24,8 @@ def __init__(self, config, name=None): if self.csv_file is not None: self.csv_writer = writer(self.csv_file, delimiter=";") + self.logger.info("CSV exporter initialised") + def push(self, msg): if self.csv_writer is not None: self.csv_writer.writerow([ diff --git a/probequest/probe_request_filter.py b/probequest/probe_request_filter.py index 61a67cb..c9a8450 100644 --- a/probequest/probe_request_filter.py +++ b/probequest/probe_request_filter.py @@ -2,6 +2,7 @@ Probe request filter module. """ +import logging from re import match from scapy.pipetool import Drain @@ -13,11 +14,15 @@ class ProbeRequestFilter(Drain): """ def __init__(self, config, name=None): + self.logger = logging.getLogger(__name__) + Drain.__init__(self, name=name) self._config = config self._cregex = self._config.compiled_essid_regex + self.logger.info("Probe request filter initialised") + def push(self, msg): if self.can_pass(msg): self._send(msg) diff --git a/probequest/probe_request_parser.py b/probequest/probe_request_parser.py index 7679657..d9c889c 100644 --- a/probequest/probe_request_parser.py +++ b/probequest/probe_request_parser.py @@ -2,6 +2,8 @@ Probe request parser module. """ +import logging + from scapy.pipetool import Drain from scapy.layers.dot11 import RadioTap, Dot11ProbeReq @@ -14,10 +16,14 @@ class ProbeRequestParser(Drain): """ def __init__(self, config, name=None): + self.logger = logging.getLogger(__name__) + Drain.__init__(self, name=name) self.config = config + self.logger.info("Probe request parser initialised") + def push(self, msg): try: self._send(self.parse(msg)) diff --git a/probequest/sniffers/fake_probe_request_sniffer.py b/probequest/sniffers/fake_probe_request_sniffer.py index 2ba4201..91c5e3d 100644 --- a/probequest/sniffers/fake_probe_request_sniffer.py +++ b/probequest/sniffers/fake_probe_request_sniffer.py @@ -2,6 +2,7 @@ Fake probe request sniffer module. """ +import logging from time import sleep from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt @@ -23,12 +24,16 @@ class FakeProbeRequestSniffer(ThreadGenSource): """ def __init__(self, period, period2=0, name=None): + self.logger = logging.getLogger(__name__) + ThreadGenSource.__init__(self, name=name) self.fake_probe_requests = FakeProbeRequest() self.period = period self.period2 = period2 + self.logger.info("Fake probe request sniffer initialised") + def generate(self): while self.RUN: # Infinite loop until 'stop()' is called. diff --git a/probequest/sniffers/probe_request_sniffer.py b/probequest/sniffers/probe_request_sniffer.py index 7cca8fb..5de1369 100644 --- a/probequest/sniffers/probe_request_sniffer.py +++ b/probequest/sniffers/probe_request_sniffer.py @@ -2,6 +2,8 @@ Probe request sniffer module. """ +import logging + from scapy.scapypipes import SniffSource @@ -13,6 +15,8 @@ class ProbeRequestSniffer(SniffSource): """ def __init__(self, config): + self.logger = logging.getLogger(__name__) + self.config = config frame_filter = self.config.frame_filter @@ -22,3 +26,5 @@ def __init__(self, config): iface=self.config.interface, filter=frame_filter ) + + self.logger.info("Probe request sniffer initialised") diff --git a/probequest/ui/console.py b/probequest/ui/console.py index d4609a5..716e903 100644 --- a/probequest/ui/console.py +++ b/probequest/ui/console.py @@ -2,6 +2,8 @@ Probe request console module. """ +import logging + from scapy.pipetool import Sink @@ -10,6 +12,13 @@ class ProbeRequestConsole(Sink): Probe request displaying sink. """ + def __init__(self): + self.logger = logging.getLogger(__name__) + + Sink.__init__(self) + + self.logger.info("Console initialised") + def push(self, msg): print(msg) From 1f762d03a07871afc4479820522bed0b4dcbdb68 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 21 Feb 2021 13:58:59 +0000 Subject: [PATCH 095/126] Remove Urwid from dependencies --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index c1db6ae..a452edd 100755 --- a/setup.py +++ b/setup.py @@ -68,7 +68,6 @@ "faker_wifi_essid", "netaddr >= 0.7.19", "scapy >= 2.4.3", - "urwid>= 2.0.1", ], extras_require={ "tests": [ From 48a43fc7590da0bff0240a7d2341143132eb2a6d Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 23 Mar 2021 19:43:38 +0000 Subject: [PATCH 096/126] Make some dependencies optional The '--fake' option is only used for testing and debugging purposes but requires additional dependencies. To avoid installing unnecessary dependencies, the ones related to the '--fake' option are now optional and can be installed with 'pip3 .[complete]'. --- probequest/cli.py | 31 +++++++++++++------ probequest/exceptions.py | 6 ++++ .../sniffers/fake_probe_request_sniffer.py | 4 +-- setup.py | 4 ++- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/probequest/cli.py b/probequest/cli.py index 81d5011..ad54027 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -14,10 +14,10 @@ from . import __version__ as VERSION from .config import Config from .exceptions import InterfaceDoesNotExistException +from .exceptions import DependencyNotPresentException from .exporters.csv import ProbeRequestCSVExporter from .probe_request_filter import ProbeRequestFilter from .probe_request_parser import ProbeRequestParser -from .sniffers.fake_probe_request_sniffer import FakeProbeRequestSniffer from .sniffers.probe_request_sniffer import ProbeRequestSniffer from .ui.console import ProbeRequestConsole @@ -127,12 +127,18 @@ def build_cluster(config): Build the ProbeQuest cluster. """ + # pylint: disable=import-outside-toplevel # pylint: disable=pointless-statement - if config.fake: - sniffer = FakeProbeRequestSniffer(1) - else: - sniffer = ProbeRequestSniffer(config) + try: + if config.fake: + from .sniffers.fake_probe_request_sniffer \ + import FakeProbeRequestSniffer + sniffer = FakeProbeRequestSniffer(1) + else: + sniffer = ProbeRequestSniffer(config) + except ModuleNotFoundError as err: + raise DependencyNotPresentException(err) from err parser = ProbeRequestParser(config) filters = ProbeRequestFilter(config) @@ -213,21 +219,26 @@ def main(): # -------------------------------------------------- # # Sniffing loop # -------------------------------------------------- # - logger.info("Creating Pipe engine") - engine = build_cluster(config) - try: + logger.info("Creating Pipe engine") + engine = build_cluster(config) + logger.info("Starting Pipe engine") print("[*] Start sniffing probe requests...") engine.start() while True: sleep(100) + except DependencyNotPresentException as err: + err_msg = "An optional dependency is missing: {err}".format(err=err) + logger.critical(err_msg, exc_info=True) + sys_exit("[x] " + err_msg) except KeyboardInterrupt: logger.info("Keyboard interrupt received") print("[*] Bye!") finally: - logger.debug("Stopping the engine") - engine.stop() + if "engine" in locals(): + logger.debug("Stopping the Pipe engine") + engine.stop() if config.output_file is not None: logger.debug("Closing output file") diff --git a/probequest/exceptions.py b/probequest/exceptions.py index 12e1e5f..3183881 100644 --- a/probequest/exceptions.py +++ b/probequest/exceptions.py @@ -13,3 +13,9 @@ class InterfaceDoesNotExistException(ProbeQuestException): """ Thrown when the network interface does not exist. """ + + +class DependencyNotPresentException(ProbeQuestException): + """ + Thrown when an optional dependency is not present on the system. + """ diff --git a/probequest/sniffers/fake_probe_request_sniffer.py b/probequest/sniffers/fake_probe_request_sniffer.py index 91c5e3d..6a2239c 100644 --- a/probequest/sniffers/fake_probe_request_sniffer.py +++ b/probequest/sniffers/fake_probe_request_sniffer.py @@ -8,8 +8,8 @@ from scapy.layers.dot11 import RadioTap, Dot11, Dot11ProbeReq, Dot11Elt from scapy.pipetool import ThreadGenSource -from faker import Faker -from faker_wifi_essid import WifiESSID +from faker import Faker # pylint: disable=import-error +from faker_wifi_essid import WifiESSID # pylint: disable=import-error class FakeProbeRequestSniffer(ThreadGenSource): diff --git a/setup.py b/setup.py index a452edd..e834cdc 100755 --- a/setup.py +++ b/setup.py @@ -65,11 +65,13 @@ python_requires=">=3.5, <4", install_requires=[ "argparse >= 1.4.0", - "faker_wifi_essid", "netaddr >= 0.7.19", "scapy >= 2.4.3", ], extras_require={ + "complete": [ + "faker_wifi_essid", + ], "tests": [ "flake8", "pylint", From a007b469727644f663b4af19f0bf37b822e1e8d6 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 23 Mar 2021 20:15:22 +0000 Subject: [PATCH 097/126] Add 'Test' GitHub Actions workflow --- .github/workflows/test.yml | 46 ++++++++++++++++++++++++++++++++++++++ tox.ini | 8 +++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1e58f49 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: Test + +on: + - push + - pull_request + +jobs: + test-code: + name: Test code + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - name: Check out code + uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[test] tox-gh-actions + - name: Test with Tox + run: tox + test-docs: + name: Test documentation + runs-on: ubuntu-20.04 + env: + PYTHON_VERSION: 3.8 + steps: + - name: Check out code + uses: actions/checkout@v1 + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[docs] + - name: Build documentation + working-directory: docs + run: make html diff --git a/tox.ini b/tox.ini index fa5d271..aab9fa5 100644 --- a/tox.ini +++ b/tox.ini @@ -35,3 +35,11 @@ python = 3.6: py36 3.7: py37, flake8, pylint 3.8: py38 + +[gh-actions] +description = "tox configuration when running on GitHub Actions" +python = + 3.5: py35 + 3.6: py36 + 3.7: py37, flake8, pylint + 3.8: py38 From dc0f3fda362a32551696405c17d6731beb69358c Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 30 Mar 2021 20:50:25 +0100 Subject: [PATCH 098/126] Add 'Publish' workflow --- .github/workflows/publish.yml | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..08cab41 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,60 @@ +name: Publish + +on: + workflow_run: + workflows: + - Test + branches: + - master + types: + - completed + +jobs: + publish-to-test-pypi: + name: Publish to TestPyPI + environment: staging + runs-on: ubuntu-20.04 + + steps: + - name: Check out code + uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: '__token__' + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload --repository testpypi dist/* + + publish-to-pypi: + name: Publish to PyPI + environment: production + runs-on: ubuntu-20.04 + needs: publish-to-test-pypi + + steps: + - name: Check out code + uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: '__token__' + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From ce770c3a318afad88dc2eda0f689d15e063326a9 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 5 Apr 2021 18:47:50 +0100 Subject: [PATCH 099/126] Remove Travis CI configuration file --- .travis.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5f43995..0000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: python - -install: pip install tox-travis - -script: tox - -jobs: - include: - - stage: "Unit Tests" - python: 3.5 - - python: 3.6 - - python: 3.7 - - python: 3.8 - - stage: "Documentation" - python: 3.8 - install: pip install .[docs] - script: cd docs; make html - - stage: "PyPI Release" - if: (branch = master) AND (tag IS present) - python: 3.8 - deploy: - provider: pypi - distributions: "sdist bdist_wheel" - user: skyplabs - password: - secure: "AAdbZ/WxjoCcGV9BAvphrxNp48D3n6yt1FEWZwA2V1BK2SnVV6YwQX/r7ryat2uoYrTONnGf/P5QgXc2wThl5mzPiRMaaONOIkyrmi2ZCD86yHYy2QQPR1gO1QOYyIsx545FRB6PaGXBC99Hw30vh3HPQaCKoQ8/PY+u3QbVn47BSBPuIdBi/FLl81uYk0ZE1457TxHkYrSDheRA3JVs72oD+izeoS+JJ+1YX+2zreukk9xuNfuwtcXPAn5B8LH8yYkFp1dd2pRsQzYMB4aH3rz7QEzSZ0mLBr3/J2bFCldmT8NToRijIjZNk04ik8XlGQ7xmpYG9rYIAkWwSBYSsZbfeLBxoxxIBcUy9xVxveZSaNl7TcRchCsyVO8leuL9aLmKz3wuKhWCRxJQSUDLlo83LYoBaTifqstUO85gC8IxR/Y/yqkW8wfSfVgVaDi9ET3/7UgSZEQJFEqfiYGdnD6/IkAy2tUCRO5xNsXsOyVJ5A0CsDTTtvlEfGxf1UtQyt0BmRSGYLMTnDBStW1Oua2QfVcTKIJOdfEOyL/VWnn/f0RCJQiUkRo9OFmSywoFjgSC9Arejwsff5smEd5i/jTKk6rOoHgIMnAGxn+75BjF3vQ7usAJeEOlLzHB5puc5dKeCpn5rwxOHha1lfmr6kDs1ec5XhcgQKgvulYfjVQ=" - on: - branch: master - tags: true - repo: SkypLabs/probequest - -notifications: - email: - on_success: never From cf34b919de5c49da82d18a3fd6b68218f2a479cf Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 5 Apr 2021 18:57:43 +0100 Subject: [PATCH 100/126] Add macOS to build matrix --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e58f49..74d0d27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,9 +7,10 @@ on: jobs: test-code: name: Test code - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.os }} strategy: matrix: + os: [ubuntu-20.04, macos-10.15] python-version: [3.5, 3.6, 3.7, 3.8] steps: From f60cf9756c9e6f4c9eb16497454727f0b9c9d1db Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sat, 8 May 2021 15:30:09 +0100 Subject: [PATCH 101/126] Ignore irrelevant linting errors --- probequest/sniffers/fake_probe_request_sniffer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/probequest/sniffers/fake_probe_request_sniffer.py b/probequest/sniffers/fake_probe_request_sniffer.py index 6a2239c..872a3f9 100644 --- a/probequest/sniffers/fake_probe_request_sniffer.py +++ b/probequest/sniffers/fake_probe_request_sniffer.py @@ -23,6 +23,8 @@ class FakeProbeRequestSniffer(ThreadGenSource): this last one only accepts lists, sets and tuples. """ + # pylint: disable=too-many-ancestors + def __init__(self, period, period2=0, name=None): self.logger = logging.getLogger(__name__) @@ -35,6 +37,9 @@ def __init__(self, period, period2=0, name=None): self.logger.info("Fake probe request sniffer initialised") def generate(self): + # Fix a false positive about not finding '_wake_up'. + # pylint: disable=no-member + while self.RUN: # Infinite loop until 'stop()' is called. for fake_probe_req in self.fake_probe_requests: From 30ea4c6cc37062921d8ecf666a4ce09c325bce51 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 19:21:57 +0000 Subject: [PATCH 102/126] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..cf4f919 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "09:00" + timezone: Europe/Dublin + open-pull-requests-limit: 10 + target-branch: develop From 920d5944e1439e8f304a8e58f4f13d0d943ca56c Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 28 Jul 2021 11:26:08 +0100 Subject: [PATCH 103/126] Upgrade 'actions/checkout' to v2 in GHA workflows --- .github/workflows/publish.yml | 10 ++++++++-- .github/workflows/test.yml | 11 +++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 08cab41..fe86556 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,15 +17,18 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v1 + uses: actions/checkout@v2 + - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' + - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine + - name: Build and publish env: TWINE_USERNAME: '__token__' @@ -42,15 +45,18 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v1 + uses: actions/checkout@v2 + - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' + - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine + - name: Build and publish env: TWINE_USERNAME: '__token__' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74d0d27..b6f7fe2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,17 +15,21 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v1 + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[test] tox-gh-actions + - name: Test with Tox run: tox + test-docs: name: Test documentation runs-on: ubuntu-20.04 @@ -33,15 +37,18 @@ jobs: PYTHON_VERSION: 3.8 steps: - name: Check out code - uses: actions/checkout@v1 + uses: actions/checkout@v2 + - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v2 with: python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[docs] + - name: Build documentation working-directory: docs run: make html From 234eabf6a33c15c91ad68e18386f219de905339f Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 28 Jul 2021 11:28:17 +0100 Subject: [PATCH 104/126] Remove Codacy badge in readme --- README.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1686b1a..cf12f96 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ ProbeQuest ========== -|PyPI Package| |PyPI Downloads| |PyPI Python Versions| |Build Status Master Branch| |Build Status Develop Branch| |Code Coverage| |LGTM Grade| |LGTM Alerts| |Documentation Status| +|PyPI Package| |PyPI Downloads| |PyPI Python Versions| |Build Status Master Branch| |Build Status Develop Branch| |LGTM Grade| |LGTM Alerts| |Documentation Status| Toolkit allowing to sniff and display the Wi-Fi probe requests passing nearby your wireless interface. @@ -49,9 +49,6 @@ License .. |Build Status Develop Branch| image:: https://img.shields.io/travis/SkypLabs/probequest/develop.svg?label=develop&logo=travis&style=flat :target: https://travis-ci.org/SkypLabs/probequest :alt: Build Status Develop Branch -.. |Code Coverage| image:: https://api.codacy.com/project/badge/Grade/16b9e70e51744256b37099ae8fe9132d - :target: https://www.codacy.com/app/skyper/probequest?utm_source=github.com&utm_medium=referral&utm_content=SkypLabs/probequest&utm_campaign=Badge_Grade - :alt: Code Coverage .. |Documentation Status| image:: https://readthedocs.org/projects/probequest/badge/?version=latest :target: http://probequest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status From 9775e693e95648d85f3f0bd0c38f07c5e5c44259 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 8 Sep 2021 12:04:08 +0100 Subject: [PATCH 105/126] Merge 'Test' and 'Publish' GH Actions workflows The 'workflow_run' event triggers a workflow run by given the last commit on the default branch as reference, which is incompatible with the environment protection rules in place. Per the environment protection rules, only an event occurring on the 'master' branch can trigger the 'Publish' workflow whereas the default branch is 'develop'. This commit fixes the issue by merging the two workflows. --- .github/workflows/publish.yml | 66 -------------- .github/workflows/test.yml | 54 ------------ .github/workflows/test_and_publish.yml | 114 +++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 120 deletions(-) delete mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/test.yml create mode 100644 .github/workflows/test_and_publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index fe86556..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Publish - -on: - workflow_run: - workflows: - - Test - branches: - - master - types: - - completed - -jobs: - publish-to-test-pypi: - name: Publish to TestPyPI - environment: staging - runs-on: ubuntu-20.04 - - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - - name: Build and publish - env: - TWINE_USERNAME: '__token__' - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - python setup.py sdist bdist_wheel - twine upload --repository testpypi dist/* - - publish-to-pypi: - name: Publish to PyPI - environment: production - runs-on: ubuntu-20.04 - needs: publish-to-test-pypi - - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - - name: Build and publish - env: - TWINE_USERNAME: '__token__' - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index b6f7fe2..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Test - -on: - - push - - pull_request - -jobs: - test-code: - name: Test code - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-20.04, macos-10.15] - python-version: [3.5, 3.6, 3.7, 3.8] - - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install .[test] tox-gh-actions - - - name: Test with Tox - run: tox - - test-docs: - name: Test documentation - runs-on: ubuntu-20.04 - env: - PYTHON_VERSION: 3.8 - steps: - - name: Check out code - uses: actions/checkout@v2 - - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v2 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install .[docs] - - - name: Build documentation - working-directory: docs - run: make html diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml new file mode 100644 index 0000000..9837cc1 --- /dev/null +++ b/.github/workflows/test_and_publish.yml @@ -0,0 +1,114 @@ +name: Test and Publish + +on: + - push + - pull_request + +jobs: + test-code: + name: Test code + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, macos-10.15] + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[tests] tox-gh-actions + + - name: Test with Tox + run: tox + + test-docs: + name: Test documentation + runs-on: ubuntu-20.04 + env: + PYTHON_VERSION: 3.8 + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[docs] + + - name: Build documentation + working-directory: docs + run: make html + + publish-to-test-pypi: + name: Publish to TestPyPI + environment: staging + runs-on: ubuntu-20.04 + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + needs: + - test-code + - test-docs + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + + - name: Build and publish + env: + TWINE_USERNAME: '__token__' + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload --repository testpypi dist/* + + publish-to-pypi: + name: Publish to PyPI + environment: production + runs-on: ubuntu-20.04 + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + needs: publish-to-test-pypi + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + + - name: Build and publish + env: + TWINE_USERNAME: '__token__' + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 638e33f360f49462b819ffd5fb99416f14f7b0f4 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 8 Sep 2021 13:24:13 +0100 Subject: [PATCH 106/126] Disable Pylint duplicate code check It is not possible to disable the duplicate code check locally at the moment. See https://github.com/PyCQA/pylint/issues/214. --- setup.cfg | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5144cc9 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[pylint.message_control] +disable = duplicate-code diff --git a/tox.ini b/tox.ini index aab9fa5..ddaf7c1 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ basepython = python3 deps = pylint changedir = test commands = - {envpython} -m pylint probequest tests + {envpython} -m pylint --rcfile={toxinidir}/setup.cfg probequest tests [travis] description = "tox configuration when running on Travis CI" From aa1d8eae8f724f1858e6034d47d1799aa3abedbd Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 8 Sep 2021 13:37:04 +0100 Subject: [PATCH 107/126] Add support for Python 3.9 --- .github/workflows/test_and_publish.yml | 4 ++-- setup.py | 1 + tox.ini | 12 +++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 9837cc1..3f7f75f 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04, macos-10.15] - python-version: [3.5, 3.6, 3.7, 3.8] + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] steps: - name: Check out code @@ -34,7 +34,7 @@ jobs: name: Test documentation runs-on: ubuntu-20.04 env: - PYTHON_VERSION: 3.8 + PYTHON_VERSION: '3.x' steps: - name: Check out code uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index e834cdc..2d286f9 100755 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", ], packages=find_packages(), diff --git a/tox.ini b/tox.ini index ddaf7c1..1dd2e0f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py35, py36, py37, py38, flake8, pylint +envlist = py35, py36, py37, py38, py39, flake8, pylint skip_missing_interpreters = true minversion = 3.0 @@ -33,13 +33,15 @@ description = "tox configuration when running on Travis CI" python = 3.5: py35 3.6: py36 - 3.7: py37, flake8, pylint - 3.8: py38 + 3.7: py37 + 3.8: py38, flake8, pylint + 3.9: py39 [gh-actions] description = "tox configuration when running on GitHub Actions" python = 3.5: py35 3.6: py36 - 3.7: py37, flake8, pylint - 3.8: py38 + 3.7: py37 + 3.8: py38, flake8, pylint + 3.9: py39 From 578e8ca7f55d5405176b24e2815fa8bd0d521290 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Wed, 8 Sep 2021 13:49:46 +0100 Subject: [PATCH 108/126] Drop support for Python 3.5 Python 3.5 has reached end-of-life in September 2020. --- .github/workflows/test_and_publish.yml | 2 +- setup.py | 1 - tox.ini | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 3f7f75f..870834c 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04, macos-10.15] - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - name: Check out code diff --git a/setup.py b/setup.py index 2d286f9..5231fd5 100755 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ "Topic :: System :: Networking", "Topic :: System :: Networking :: Monitoring", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", diff --git a/tox.ini b/tox.ini index 1dd2e0f..9b04dfa 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py35, py36, py37, py38, py39, flake8, pylint +envlist = py36, py37, py38, py39, flake8, pylint skip_missing_interpreters = true minversion = 3.0 @@ -31,7 +31,6 @@ commands = [travis] description = "tox configuration when running on Travis CI" python = - 3.5: py35 3.6: py36 3.7: py37 3.8: py38, flake8, pylint @@ -40,7 +39,6 @@ python = [gh-actions] description = "tox configuration when running on GitHub Actions" python = - 3.5: py35 3.6: py36 3.7: py37 3.8: py38, flake8, pylint From a05af9d7057390599b8ef6cd1b0102f5cfbc7844 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 30 Sep 2021 12:55:31 +0100 Subject: [PATCH 109/126] Update 'python_requires' in 'setup.py' --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5231fd5..d22728e 100755 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ "probequest = probequest.cli:main", ] }, - python_requires=">=3.5, <4", + python_requires=">=3.6, <4", install_requires=[ "argparse >= 1.4.0", "netaddr >= 0.7.19", From 092055db91064fc831522cc52ef426b484213da4 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 30 Sep 2021 13:54:51 +0100 Subject: [PATCH 110/126] Use f-strings instead of 'str.format()' --- probequest/cli.py | 4 ++-- probequest/config.py | 12 +++++------- probequest/probe_request.py | 14 +++++++------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/probequest/cli.py b/probequest/cli.py index ad54027..3d22a31 100644 --- a/probequest/cli.py +++ b/probequest/cli.py @@ -183,7 +183,7 @@ def main(): get_arg_parser().parse_args(namespace=config) except InterfaceDoesNotExistException as err: logger.critical(err, exc_info=True) - sys_exit("[!] {err_msg}".format(err_msg=err)) + sys_exit(f"[!] {err}") # -------------------------------------------------- # # Debug mode @@ -229,7 +229,7 @@ def main(): while True: sleep(100) except DependencyNotPresentException as err: - err_msg = "An optional dependency is missing: {err}".format(err=err) + err_msg = f"An optional dependency is missing: {err}" logger.critical(err_msg, exc_info=True) sys_exit("[x] " + err_msg) except KeyboardInterrupt: diff --git a/probequest/config.py b/probequest/config.py index ebaf7ff..3a06bbb 100644 --- a/probequest/config.py +++ b/probequest/config.py @@ -48,9 +48,7 @@ def interface(self, interface): # If interface does not exist. if interface not in get_if_list(): raise InterfaceDoesNotExistException( - "Interface {interface} does not exist".format( - interface=interface, - ) + f"Interface {interface} does not exist" ) self._interface = interface @@ -73,10 +71,10 @@ def frame_filter(self): for i, station in enumerate(self.mac_exclusions): if i == 0: self._frame_filter += \ - "ether src host {s_mac}".format(s_mac=station) + f"ether src host {station}" else: self._frame_filter += \ - "|| ether src host {s_mac}".format(s_mac=station) + f"|| ether src host {station}" self._frame_filter += ")" @@ -86,10 +84,10 @@ def frame_filter(self): for i, station in enumerate(self.mac_filters): if i == 0: self._frame_filter += \ - "ether src host {s_mac}".format(s_mac=station) + f"ether src host {station}" else: self._frame_filter += \ - "|| ether src host {s_mac}".format(s_mac=station) + f"|| ether src host {station}" self._frame_filter += ")" diff --git a/probequest/probe_request.py b/probequest/probe_request.py index 68aa469..0557c2b 100644 --- a/probequest/probe_request.py +++ b/probequest/probe_request.py @@ -19,15 +19,15 @@ def __init__(self, timestamp, s_mac, essid): self._s_mac_oui = None def __str__(self): - return "{timestamp} - {s_mac} ({s_mac_oui}) -> {essid}".format( - timestamp=strftime( + timestamp = strftime( "%a, %d %b %Y %H:%M:%S %Z", localtime(self.timestamp) - ), - s_mac=self.s_mac, - s_mac_oui=self.s_mac_oui, - essid=self.essid - ) + ) + s_mac = self.s_mac + s_mac_oui = self.s_mac_oui + essid = self.essid + + return f"{timestamp} - {s_mac} ({s_mac_oui}) -> {essid}" @property def s_mac_oui(self): From c23f500b6e4c36d7c43c8872a6c33bddd533a39f Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 30 Sep 2021 14:33:26 +0100 Subject: [PATCH 111/126] Update status cards in readme --- README.rst | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/README.rst b/README.rst index cf12f96..6ef5ddd 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,20 @@ ProbeQuest ========== -|PyPI Package| |PyPI Downloads| |PyPI Python Versions| |Build Status Master Branch| |Build Status Develop Branch| |LGTM Grade| |LGTM Alerts| |Documentation Status| +|PyPI Package| |PyPI Downloads| |PyPI Python Versions| |Build Status| |LGTM +Grade| |LGTM Alerts| |Documentation Status| -Toolkit allowing to sniff and display the Wi-Fi probe requests passing nearby your wireless interface. +Toolkit allowing to sniff and display the Wi-Fi probe requests passing nearby +your wireless interface. -Probe requests are sent by a station to elicit information about access points, in particular to determine if an access point is present or not in the nearby environment. Some devices (mostly smartphones and tablets) use these requests to determine if one of the networks they have previously been connected to is in range, leaking personal information. +Probe requests are sent by a station to elicit information about access points, +in particular to determine if an access point is present or not in the nearby +environment. Some devices (mostly smartphones and tablets) use these requests +to determine if one of the networks they have previously been connected to is +in range, leaking personal information. -Further details are discussed in `this -paper `__. +Further details are discussed in `this paper +`__. .. image:: docs/_static/img/probequest_demo.gif :target: https://asciinema.org/a/205172 @@ -25,48 +31,56 @@ Installation Documentation ============= -The project is documented `here `__. +The project is documented `here +`__. In the Media ============ ProbeQuest has appeared in the following media: -- `KitPloit `__ -- `Hakin9 Magazine, VOL.13, NO. 05, "Open Source Hacking Tools" `__ -- `WonderHowTo `__ (including a `YouTube video `__) -- `ShellVoide `__ -- `Cyber Pi Projects `__ (`Worksheet `__) +- `KitPloit + `__ +- `Hakin9 Magazine, VOL.13, NO. 05, "Open Source Hacking Tools" + `__ +- `WonderHowTo + `__ + (including a `YouTube video `__) +- `ShellVoide + `__ +- `Cyber Pi Projects + `__ (`Worksheet + `__) License ======= `GPL version 3 `__ -.. |Build Status Master Branch| image:: https://img.shields.io/travis/SkypLabs/probequest/master.svg?label=master&logo=travis&style=flat - :target: https://travis-ci.org/SkypLabs/probequest - :alt: Build Status Master Branch -.. |Build Status Develop Branch| image:: https://img.shields.io/travis/SkypLabs/probequest/develop.svg?label=develop&logo=travis&style=flat - :target: https://travis-ci.org/SkypLabs/probequest +.. |Build Status| image:: https://github.com/SkypLabs/probequest/actions/workflows/test_and_publish.yml/badge.svg?branch=develop + :target: https://github.com/SkypLabs/probequest/actions/workflows/test_and_publish.yml?query=branch%3Adevelop :alt: Build Status Develop Branch + .. |Documentation Status| image:: https://readthedocs.org/projects/probequest/badge/?version=latest - :target: http://probequest.readthedocs.io/en/latest/?badge=latest + :target: https://probequest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -.. |Known Vulnerabilities| image:: https://snyk.io/test/github/SkypLabs/probequest/badge.svg - :target: https://snyk.io/test/github/SkypLabs/probequest - :alt: Known Vulnerabilities + .. |LGTM Alerts| image:: https://img.shields.io/lgtm/alerts/g/SkypLabs/probequest.svg?logo=lgtm&logoWidth=18 :target: https://lgtm.com/projects/g/SkypLabs/probequest/alerts/ :alt: LGTM Alerts + .. |LGTM Grade| image:: https://img.shields.io/lgtm/grade/python/g/SkypLabs/probequest.svg?logo=lgtm&logoWidth=18 :target: https://lgtm.com/projects/g/SkypLabs/probequest/context:python :alt: LGTM Grade + .. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/probequest.svg?style=flat :target: https://pypi.org/project/probequest/ :alt: PyPI Package Downloads Per Month + .. |PyPI Package| image:: https://img.shields.io/pypi/v/probequest.svg?style=flat :target: https://pypi.org/project/probequest/ :alt: PyPI Package Latest Release + .. |PyPI Python Versions| image:: https://img.shields.io/pypi/pyversions/probequest.svg?logo=python&style=flat :target: https://pypi.org/project/probequest/ :alt: PyPI Package Python Versions From 9e7b21be93231757143b7dc35ec53b5b7c401199 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 17 Nov 2021 13:11:59 +0100 Subject: [PATCH 112/126] Remove argparse --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index d22728e..6340f60 100755 --- a/setup.py +++ b/setup.py @@ -64,7 +64,6 @@ }, python_requires=">=3.6, <4", install_requires=[ - "argparse >= 1.4.0", "netaddr >= 0.7.19", "scapy >= 2.4.3", ], From a0b60f9bfd109e55ee2342a6642dd65363f337ae Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 23 Dec 2021 09:35:31 +0000 Subject: [PATCH 113/126] Remove GitHub Sponsors configuration file The GitHub Sponsors configuration file is already shared globally across all my repositories via a community health file. See https://github.com/SkypLabs/.github. --- .github/FUNDING.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 0538874..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -github: SkypLabs -ko_fi: skyplabs -custom: ["https://blog.skyplabs.net/support/", "https://www.buymeacoffee.com/skyplabs", "https://paypal.me/skyplabs"] From 8a02dbdd0925939594d9f78fb76cbcb8a9b89f82 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 23 Dec 2021 17:13:59 +0000 Subject: [PATCH 114/126] Remove Snyk configuration file Snyk is no more used for this project. --- .snyk | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .snyk diff --git a/.snyk b/.snyk deleted file mode 100644 index 8a475e4..0000000 --- a/.snyk +++ /dev/null @@ -1,2 +0,0 @@ -language-settings: - python: '3' From 5a757fc1e4e9a2f58cf6a8e689db0395e51f4c36 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 14:19:21 +0000 Subject: [PATCH 115/126] Add 'pyproject.toml' and 'setup.cfg' This commit implements PEP 517 by replacing the 'setup.py' script with 'pyproject.toml' and 'setup.cfg'. See https://peps.python.org/pep-0517/. --- pyproject.toml | 7 ++ setup.cfg | 59 +++++++++++++ setup.py | 86 ------------------- {probequest => src/probequest}/__init__.py | 0 {probequest => src/probequest}/__main__.py | 0 {probequest => src/probequest}/cli.py | 0 {probequest => src/probequest}/config.py | 0 {probequest => src/probequest}/exceptions.py | 0 .../probequest}/exporters/__init__.py | 0 .../probequest}/exporters/csv.py | 0 .../probequest}/probe_request.py | 0 .../probequest}/probe_request_filter.py | 0 .../probequest}/probe_request_parser.py | 0 .../probequest}/sniffers/__init__.py | 0 .../sniffers/fake_probe_request_sniffer.py | 0 .../sniffers/probe_request_sniffer.py | 0 {probequest => src/probequest}/ui/__init__.py | 0 {probequest => src/probequest}/ui/console.py | 0 tox.ini | 28 ++---- 19 files changed, 75 insertions(+), 105 deletions(-) create mode 100644 pyproject.toml delete mode 100755 setup.py rename {probequest => src/probequest}/__init__.py (100%) rename {probequest => src/probequest}/__main__.py (100%) rename {probequest => src/probequest}/cli.py (100%) rename {probequest => src/probequest}/config.py (100%) rename {probequest => src/probequest}/exceptions.py (100%) rename {probequest => src/probequest}/exporters/__init__.py (100%) rename {probequest => src/probequest}/exporters/csv.py (100%) rename {probequest => src/probequest}/probe_request.py (100%) rename {probequest => src/probequest}/probe_request_filter.py (100%) rename {probequest => src/probequest}/probe_request_parser.py (100%) rename {probequest => src/probequest}/sniffers/__init__.py (100%) rename {probequest => src/probequest}/sniffers/fake_probe_request_sniffer.py (100%) rename {probequest => src/probequest}/sniffers/probe_request_sniffer.py (100%) rename {probequest => src/probequest}/ui/__init__.py (100%) rename {probequest => src/probequest}/ui/console.py (100%) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4602018 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires = [ + "setuptools >= 42", + "setuptools_scm >= 2.0.0, <3", + "wheel", +] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg index 5144cc9..8c9c584 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,61 @@ +[metadata] +name = probequest +version = 0.7.2 +description = Toolkit for Playing with Wi-Fi Probe Requests. +long_description = file: README.rst +keywords = wifi, wireless, security, sniffer +license = GPLv3 +license_files = LICENSE +author = Paul-Emmanuel Raoul +author_email = skyper@skyplabs.net +url = https://github.com/SkypLabs/probequest +project_urls = + Bug Tracker = https://github.com/SkypLabs/probequest/issues + Documentation = https://probequest.readthedocs.io + Source Code = https://github.com/SkypLabs/probequest +classifiers = + Development Status :: 4 - Beta + Environment :: Console + Intended Audience :: Information Technology + Natural Language :: English + Topic :: Security + Topic :: System :: Networking + Topic :: System :: Networking :: Monitoring + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + License :: OSI Approved :: GNU General Public License v3 (GPLv3) + +[options] +packages = find: +package_dir = + =src +python_requires = >=3.6, <4 +install_requires = + netaddr >= 0.7.19 + scapy >= 2.4.3 + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + probequest = probequest.cli:main + +[options.extras_require] +complete = + faker_wifi_essid +tests = + flake8 + pylint + tox +docs = + Sphinx >= 3.2 + sphinxcontrib-seqdiag >= 2.0.0 + sphinx-argparse >= 0.2.2 + sphinx_rtd_theme >= 0.5.0 + [pylint.message_control] disable = duplicate-code diff --git a/setup.py b/setup.py deleted file mode 100755 index 6340f60..0000000 --- a/setup.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -Setuptools build system configuration file -for ProbeQuest. - -See https://setuptools.readthedocs.io. -""" - -try: - from setuptools import setup, find_packages -except Exception as setuptools_not_present: - raise ImportError( - "Setuptools is required to install ProbeQuest!" - ) from setuptools_not_present - -from codecs import open as fopen -from os.path import dirname, abspath, join - -DIR = dirname(abspath(__file__)) - -VERSION = "0.7.2" - -with fopen(join(DIR, "README.rst"), encoding="utf-8") as f: - LONG_DESCRIPTION = f.read() - -setup( - name="probequest", - version=VERSION, - description="Toolkit for Playing with Wi-Fi Probe Requests", - long_description=LONG_DESCRIPTION, - license="GPLv3", - keywords="wifi wireless security sniffer", - author="Paul-Emmanuel Raoul", - author_email="skyper@skyplabs.net", - url="https://github.com/SkypLabs/probequest", - download_url="https://github.com/SkypLabs/probequest/archive/v{0}.zip" - .format(VERSION), - project_urls={ - "Documentation": "https://probequest.readthedocs.io", - "Source Code": "https://github.com/SkypLabs/probequest", - }, - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Information Technology", - "Natural Language :: English", - "Topic :: Security", - "Topic :: System :: Networking", - "Topic :: System :: Networking :: Monitoring", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - ], - packages=find_packages(), - entry_points={ - "console_scripts": [ - "probequest = probequest.cli:main", - ] - }, - python_requires=">=3.6, <4", - install_requires=[ - "netaddr >= 0.7.19", - "scapy >= 2.4.3", - ], - extras_require={ - "complete": [ - "faker_wifi_essid", - ], - "tests": [ - "flake8", - "pylint", - "tox" - ], - "docs": [ - "sphinx >= 3.2.0", - "sphinxcontrib-seqdiag >= 2.0.0", - "sphinx-argparse >= 0.2.2", - "sphinx_rtd_theme >= 0.5.0", - ], - }, -) diff --git a/probequest/__init__.py b/src/probequest/__init__.py similarity index 100% rename from probequest/__init__.py rename to src/probequest/__init__.py diff --git a/probequest/__main__.py b/src/probequest/__main__.py similarity index 100% rename from probequest/__main__.py rename to src/probequest/__main__.py diff --git a/probequest/cli.py b/src/probequest/cli.py similarity index 100% rename from probequest/cli.py rename to src/probequest/cli.py diff --git a/probequest/config.py b/src/probequest/config.py similarity index 100% rename from probequest/config.py rename to src/probequest/config.py diff --git a/probequest/exceptions.py b/src/probequest/exceptions.py similarity index 100% rename from probequest/exceptions.py rename to src/probequest/exceptions.py diff --git a/probequest/exporters/__init__.py b/src/probequest/exporters/__init__.py similarity index 100% rename from probequest/exporters/__init__.py rename to src/probequest/exporters/__init__.py diff --git a/probequest/exporters/csv.py b/src/probequest/exporters/csv.py similarity index 100% rename from probequest/exporters/csv.py rename to src/probequest/exporters/csv.py diff --git a/probequest/probe_request.py b/src/probequest/probe_request.py similarity index 100% rename from probequest/probe_request.py rename to src/probequest/probe_request.py diff --git a/probequest/probe_request_filter.py b/src/probequest/probe_request_filter.py similarity index 100% rename from probequest/probe_request_filter.py rename to src/probequest/probe_request_filter.py diff --git a/probequest/probe_request_parser.py b/src/probequest/probe_request_parser.py similarity index 100% rename from probequest/probe_request_parser.py rename to src/probequest/probe_request_parser.py diff --git a/probequest/sniffers/__init__.py b/src/probequest/sniffers/__init__.py similarity index 100% rename from probequest/sniffers/__init__.py rename to src/probequest/sniffers/__init__.py diff --git a/probequest/sniffers/fake_probe_request_sniffer.py b/src/probequest/sniffers/fake_probe_request_sniffer.py similarity index 100% rename from probequest/sniffers/fake_probe_request_sniffer.py rename to src/probequest/sniffers/fake_probe_request_sniffer.py diff --git a/probequest/sniffers/probe_request_sniffer.py b/src/probequest/sniffers/probe_request_sniffer.py similarity index 100% rename from probequest/sniffers/probe_request_sniffer.py rename to src/probequest/sniffers/probe_request_sniffer.py diff --git a/probequest/ui/__init__.py b/src/probequest/ui/__init__.py similarity index 100% rename from probequest/ui/__init__.py rename to src/probequest/ui/__init__.py diff --git a/probequest/ui/console.py b/src/probequest/ui/console.py similarity index 100% rename from probequest/ui/console.py rename to src/probequest/ui/console.py diff --git a/tox.ini b/tox.ini index 9b04dfa..6e86ca6 100644 --- a/tox.ini +++ b/tox.ini @@ -7,39 +7,29 @@ envlist = py36, py37, py38, py39, flake8, pylint skip_missing_interpreters = true minversion = 3.0 +isolated_build = true [testenv] -description = "ProbeQuest unit tests" +description = "ProbeQuest's unit tests" commands = - {envpython} -m unittest discover + {envpython} -m unittest discover -s tests [testenv:flake8] -description = "Check ProbeQuest code style & quality" -basepython = python3 +description = "Check ProbeQuest's code style & quality" deps = flake8 commands = - {envpython} -m flake8 probequest tests + {envpython} -m flake8 src tests [testenv:pylint] description = "Check ProbeQuest for programming errors" -basepython = python3 deps = pylint -changedir = test commands = - {envpython} -m pylint --rcfile={toxinidir}/setup.cfg probequest tests - -[travis] -description = "tox configuration when running on Travis CI" -python = - 3.6: py36 - 3.7: py37 - 3.8: py38, flake8, pylint - 3.9: py39 + {envpython} -m pylint --rcfile={toxinidir}/setup.cfg src tests [gh-actions] description = "tox configuration when running on GitHub Actions" python = - 3.6: py36 - 3.7: py37 + 3.6: py36, flake8, pylint + 3.7: py37, flake8, pylint 3.8: py38, flake8, pylint - 3.9: py39 + 3.9: py39, flake8, pylint From 875b955f3116caffb61327830ade64b001e6184b Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 16:02:33 +0000 Subject: [PATCH 116/126] Use 'main' as branch for production releases --- .github/workflows/test_and_publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 870834c..18d6ccc 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -57,7 +57,7 @@ jobs: name: Publish to TestPyPI environment: staging runs-on: ubuntu-20.04 - if: github.ref == 'refs/heads/master' && github.event_name == 'push' + if: github.ref == 'refs/heads/main' && github.event_name == 'push' needs: - test-code - test-docs @@ -88,7 +88,7 @@ jobs: name: Publish to PyPI environment: production runs-on: ubuntu-20.04 - if: github.ref == 'refs/heads/master' && github.event_name == 'push' + if: github.ref == 'refs/heads/main' && github.event_name == 'push' needs: publish-to-test-pypi steps: From cacffe17fcc933cd53d7f74d42459ccddc381326 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 16:14:42 +0000 Subject: [PATCH 117/126] Monitor GH Actions dependencies with Dependabot --- .github/dependabot.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cf4f919..4ec4496 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,20 @@ version: 2 updates: -- package-ecosystem: pip - directory: "/" - schedule: - interval: daily - time: "09:00" - timezone: Europe/Dublin - open-pull-requests-limit: 10 - target-branch: develop + + - package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + time: "09:00" + timezone: Europe/Dublin + open-pull-requests-limit: 10 + target-branch: develop + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + time: "09:00" + timezone: Europe/Dublin + open-pull-requests-limit: 10 + target-branch: develop From aaeaa54b87ef73b5413dfb142dc9bcaf5fb11075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:25:38 +0000 Subject: [PATCH 118/126] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test_and_publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 18d6ccc..e4726a7 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 @@ -37,7 +37,7 @@ jobs: PYTHON_VERSION: '3.x' steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v2 @@ -64,7 +64,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 @@ -93,7 +93,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 From b0887733cb00f73f259e00a648aca4611ad218ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:38:46 +0000 Subject: [PATCH 119/126] Bump actions/setup-python from 2 to 3 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 3. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test_and_publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index e4726a7..8eba5b0 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ env.PYTHON_VERSION }} @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.x' @@ -96,7 +96,7 @@ jobs: uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.x' From f8c1b918cb9f8c48d8399d768b65f4f6ba18e3bc Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 17:26:27 +0000 Subject: [PATCH 120/126] Drop support for Python 3.6 "The release you are looking at is Python 3.6.15, the final security bugfix release for the legacy 3.6 series which has now reached end-of-life and is no longer supported." https://www.python.org/downloads/release/python-3615/ --- .github/workflows/test_and_publish.yml | 5 ++++- setup.cfg | 3 +-- tox.ini | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 8eba5b0..46ff62f 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -11,7 +11,10 @@ jobs: strategy: matrix: os: [ubuntu-20.04, macos-10.15] - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: + - '3.7' + - '3.8' + - '3.9' steps: - name: Check out code diff --git a/setup.cfg b/setup.cfg index 8c9c584..54042b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,6 @@ classifiers = Topic :: System :: Networking Topic :: System :: Networking :: Monitoring Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -32,7 +31,7 @@ classifiers = packages = find: package_dir = =src -python_requires = >=3.6, <4 +python_requires = >=3.7, <4 install_requires = netaddr >= 0.7.19 scapy >= 2.4.3 diff --git a/tox.ini b/tox.ini index 6e86ca6..c2df371 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36, py37, py38, py39, flake8, pylint +envlist = py37, py38, py39, flake8, pylint skip_missing_interpreters = true minversion = 3.0 isolated_build = true @@ -29,7 +29,6 @@ commands = [gh-actions] description = "tox configuration when running on GitHub Actions" python = - 3.6: py36, flake8, pylint 3.7: py37, flake8, pylint 3.8: py38, flake8, pylint 3.9: py39, flake8, pylint From a325a91acac247470e9152161f1df63cc384fdec Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 17:58:57 +0000 Subject: [PATCH 121/126] Add support for Python 3.10 --- .github/workflows/test_and_publish.yml | 1 + setup.cfg | 1 + tox.ini | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 46ff62f..4d1058f 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -15,6 +15,7 @@ jobs: - '3.7' - '3.8' - '3.9' + - '3.10' steps: - name: Check out code diff --git a/setup.cfg b/setup.cfg index 54042b3..4d930b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,7 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 License :: OSI Approved :: GNU General Public License v3 (GPLv3) [options] diff --git a/tox.ini b/tox.ini index c2df371..12c38df 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py37, py38, py39, flake8, pylint +envlist = py37, py38, py39, py310, flake8, pylint skip_missing_interpreters = true minversion = 3.0 isolated_build = true @@ -32,3 +32,4 @@ python = 3.7: py37, flake8, pylint 3.8: py38, flake8, pylint 3.9: py39, flake8, pylint + 3.10: py310, flake8, pylint From eafaad61abb94a2b8529d4034cfb2c67e4aadb51 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 18:12:08 +0000 Subject: [PATCH 122/126] Fix the publication jobs The build instructions still required the 'setup.py' script in the publication jobs. This commit replaces those instructions with the 'build' package. --- .github/workflows/test_and_publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_and_publish.yml b/.github/workflows/test_and_publish.yml index 4d1058f..089b68a 100644 --- a/.github/workflows/test_and_publish.yml +++ b/.github/workflows/test_and_publish.yml @@ -78,14 +78,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install --upgrade setuptools wheel twine build - name: Build and publish env: TWINE_USERNAME: '__token__' TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload --repository testpypi dist/* publish-to-pypi: @@ -107,12 +107,12 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install --upgrade setuptools wheel twine build - name: Build and publish env: TWINE_USERNAME: '__token__' TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* From e39e71b820f8e93dedc00b1312a45ba916e847d9 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Sun, 20 Mar 2022 18:19:33 +0000 Subject: [PATCH 123/126] Bump version number to 0.8.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4d930b6..e527402 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = probequest -version = 0.7.2 +version = 0.8.0 description = Toolkit for Playing with Wi-Fi Probe Requests. long_description = file: README.rst keywords = wifi, wireless, security, sniffer From 59b1abb18edf80cdc9d254b605ba532aa4e31814 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 21 Mar 2022 23:50:18 +0000 Subject: [PATCH 124/126] Update the documentation --- docs/conf.py | 2 +- docs/development.rst | 24 +++++++------- docs/index.rst | 3 +- docs/installation.rst | 15 ++++++--- docs/mitigation.rst | 24 ++++++++++++-- docs/modules.rst | 2 +- docs/modules/cli.rst | 5 +++ docs/modules/exceptions.rst | 5 +++ docs/modules/exporters/csv.rst | 6 ++++ docs/modules/fake_packet_sniffer.rst | 5 --- docs/modules/packet_sniffer.rst | 5 --- docs/modules/pnl.rst | 5 --- docs/modules/probe_request_filter.rst | 5 +++ docs/modules/probe_request_parser.rst | 5 +++ docs/modules/raw.rst | 5 --- .../sniffers/fake_probe_request_sniffer.rst | 5 +++ .../{ => sniffers}/probe_request_sniffer.rst | 2 +- docs/modules/ui/console.rst | 5 +++ docs/probe_requests.rst | 19 ++++++++--- docs/security.rst | 7 ++-- docs/usage.rst | 22 ++++++++++++- docs/use_case.rst | 32 +++++++++++++++---- 22 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 docs/modules/cli.rst create mode 100644 docs/modules/exceptions.rst create mode 100644 docs/modules/exporters/csv.rst delete mode 100644 docs/modules/fake_packet_sniffer.rst delete mode 100644 docs/modules/packet_sniffer.rst delete mode 100644 docs/modules/pnl.rst create mode 100644 docs/modules/probe_request_filter.rst create mode 100644 docs/modules/probe_request_parser.rst delete mode 100644 docs/modules/raw.rst create mode 100644 docs/modules/sniffers/fake_probe_request_sniffer.rst rename docs/modules/{ => sniffers}/probe_request_sniffer.rst (50%) create mode 100644 docs/modules/ui/console.rst diff --git a/docs/conf.py b/docs/conf.py index 0d3d79e..c985dc4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ # -- Project information ----------------------------------------------------- project = 'ProbeQuest' -copyright = '2020, Paul-Emmanuel Raoul' +copyright = '2022, Paul-Emmanuel Raoul' author = 'Paul-Emmanuel Raoul' # The full version, including alpha/beta/rc tags diff --git a/docs/development.rst b/docs/development.rst index a072c80..efe8b71 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -15,19 +15,21 @@ Running the unit tests Releasing a new version ----------------------- -Below are the different steps to do before releasing a new version: - -- Run all tests and be sure they all pass -- Update the `VERSION` variable in `probequest/version.py` -- Update the requirements in `setup.py` if needed -- Update the package's metadata (description, classifiers, etc) in `setup.py` if needed -- Update `README.rst` if needed -- Update the documentation if needed and make sure it compiles well (`cd ./docs && make html`) -- Update the copyright year in `docs/conf.py` if needed -- Add the corresponding release note to `CHANGELOG.md` +Below are the different steps to follow before releasing a new version: + +- Run all tests and be sure they all pass. +- Update the `version` field in `setup.cfg`. +- Update the requirements in `setup.cfg` if needed. +- Update the package's metadata (description, classifiers, etc.) in `setup.cfg` + if needed. +- Update `README.rst` if needed. +- Update the documentation if needed and make sure it compiles well (`cd ./docs + && make html`). +- Update the copyright year in `docs/conf.py` if needed. +- Add the corresponding release note to `CHANGELOG.md`. After having pushed the new release: -- Edit the release note on GitHub +- Create the corresponding release note on GitHub. .. _tox: https://tox.readthedocs.io diff --git a/docs/index.rst b/docs/index.rst index 8f252ed..be78887 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,8 @@ Welcome to ProbeQuest's documentation! ====================================== -ProbeQuest is a toolkit allowing to sniff and display the Wi-Fi probe requests passing nearby your wireless interface. +ProbeQuest is a toolkit allowing to sniff and display the Wi-Fi probe requests +passing nearby your wireless interface. This project has been inspired by `this paper`_. diff --git a/docs/installation.rst b/docs/installation.rst index 2e1e727..1d1db53 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -2,18 +2,25 @@ Installation ============ -Using pip (recommended) +From PyPI (recommended) ----------------------- :: - sudo pip3 install --upgrade probequest + pip3 install --upgrade probequest From sources ------------ +ProbeQuest is packaged with `Setuptools`_. + +The default Git branch is `develop`. To install the latest stable version, you +need to clone the `main` branch. + :: - git clone https://github.com/SkypLabs/probequest.git + git clone -b main https://github.com/SkypLabs/probequest.git cd probequest - sudo pip3 install --upgrade . + pip3 install --upgrade . + +.. _Setuptools: https://setuptools.pypa.io/ diff --git a/docs/mitigation.rst b/docs/mitigation.rst index e2ef680..64440fd 100644 --- a/docs/mitigation.rst +++ b/docs/mitigation.rst @@ -4,17 +4,35 @@ Mitigation As far as I know, there are two mitigation techniques: -- Don’t use probe requests at all. It is by far the most efficient way not to leak any piece of information. As said earlier, it is not necessary to rely on probe requests to get the list of the nearby access points since they broadcast their name by themselves. -- Randomise the source MAC address of each probe request sent. This way, it’s no longer possible for a third party to link probe requests to a specific device based on the Wi-Fi data collected. However, using a Software-Defined Radio to capture RF metadata such as the frequency offset, it would be possible to fingerprint each Wi-Fi packet and so each Wi-Fi device, regardless of their source MAC address (this technique will be implemented in ProbeQuest). +- Don’t use probe requests at all. It is by far the most efficient way not to + leak any piece of information. As said earlier, it is not necessary to rely on + probe requests to get the list of the nearby access points since they + broadcast their name by themselves. +- Randomise the source MAC address of each probe request sent. This way, it’s no + longer possible for a third party to link probe requests to a specific device + based on the Wi-Fi data collected. However, using a Software-Defined Radio to + capture RF metadata such as the frequency offset, it would be possible to + fingerprint each Wi-Fi packet and so each Wi-Fi device, regardless of their + source MAC address (this technique will be implemented in ProbeQuest). -In practice, you can install `Wi-Fi Privacy Police`_ from `F-Droid`_ or the `Play Store`_ to prevent your Android devices from leaking their PNL. +Android +------- + +Some Android-based operating systems, like `GrapheneOS`_, randomise the source +MAC address natively. Otherwise, you can install `Wi-Fi Privacy Police`_ from +`F-Droid`_ or the `Play Store`_ to prevent your Android devices from leaking +their PNL. .. image:: _static/img/wifi_privacy_police_main_screen.png Once installed, the **Privacy protection** option should be switched on. +iOS +--- + On iOS, the source MAC address is randomised since iOS 8. .. _F-Droid: https://f-droid.org/packages/be.uhasselt.privacypolice/ +.. _GrapheneOS: https://grapheneos.org/ .. _Play Store: https://play.google.com/store/apps/details?id=be.uhasselt.privacypolice .. _Wi-Fi Privacy Police: https://github.com/BramBonne/privacypolice diff --git a/docs/modules.rst b/docs/modules.rst index f36d0f7..301316c 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -6,4 +6,4 @@ Modules :maxdepth: 1 :glob: - modules/* + modules/** diff --git a/docs/modules/cli.rst b/docs/modules/cli.rst new file mode 100644 index 0000000..6b5e71a --- /dev/null +++ b/docs/modules/cli.rst @@ -0,0 +1,5 @@ +CLI +--- + +.. automodule:: probequest.cli + :members: diff --git a/docs/modules/exceptions.rst b/docs/modules/exceptions.rst new file mode 100644 index 0000000..f21c369 --- /dev/null +++ b/docs/modules/exceptions.rst @@ -0,0 +1,5 @@ +Exceptions +---------- + +.. automodule:: probequest.exceptions + :members: diff --git a/docs/modules/exporters/csv.rst b/docs/modules/exporters/csv.rst new file mode 100644 index 0000000..dfad80a --- /dev/null +++ b/docs/modules/exporters/csv.rst @@ -0,0 +1,6 @@ +CSV Exporter +------------ + +.. automodule:: probequest.exporters.csv + :members: + diff --git a/docs/modules/fake_packet_sniffer.rst b/docs/modules/fake_packet_sniffer.rst deleted file mode 100644 index bbd8e07..0000000 --- a/docs/modules/fake_packet_sniffer.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fake Packet Sniffer -------------------- - -.. automodule:: probequest.fake_packet_sniffer - :members: diff --git a/docs/modules/packet_sniffer.rst b/docs/modules/packet_sniffer.rst deleted file mode 100644 index 9e885cc..0000000 --- a/docs/modules/packet_sniffer.rst +++ /dev/null @@ -1,5 +0,0 @@ -Packet Sniffer --------------- - -.. automodule:: probequest.packet_sniffer - :members: diff --git a/docs/modules/pnl.rst b/docs/modules/pnl.rst deleted file mode 100644 index a01f9dd..0000000 --- a/docs/modules/pnl.rst +++ /dev/null @@ -1,5 +0,0 @@ -PNL Viewer ----------- - -.. automodule:: probequest.ui.pnl - :members: diff --git a/docs/modules/probe_request_filter.rst b/docs/modules/probe_request_filter.rst new file mode 100644 index 0000000..e507514 --- /dev/null +++ b/docs/modules/probe_request_filter.rst @@ -0,0 +1,5 @@ +Probe Request Filter +-------------------- + +.. automodule:: probequest.probe_request_filter + :members: diff --git a/docs/modules/probe_request_parser.rst b/docs/modules/probe_request_parser.rst new file mode 100644 index 0000000..7c1945e --- /dev/null +++ b/docs/modules/probe_request_parser.rst @@ -0,0 +1,5 @@ +Probe Request Parser +-------------------- + +.. automodule:: probequest.probe_request_parser + :members: diff --git a/docs/modules/raw.rst b/docs/modules/raw.rst deleted file mode 100644 index 6354d42..0000000 --- a/docs/modules/raw.rst +++ /dev/null @@ -1,5 +0,0 @@ -Raw Probe Request Viewer ------------------------- - -.. automodule:: probequest.ui.raw - :members: diff --git a/docs/modules/sniffers/fake_probe_request_sniffer.rst b/docs/modules/sniffers/fake_probe_request_sniffer.rst new file mode 100644 index 0000000..2cf45ce --- /dev/null +++ b/docs/modules/sniffers/fake_probe_request_sniffer.rst @@ -0,0 +1,5 @@ +Fake Probe Request Sniffer +-------------------------- + +.. automodule:: probequest.sniffers.fake_probe_request_sniffer + :members: diff --git a/docs/modules/probe_request_sniffer.rst b/docs/modules/sniffers/probe_request_sniffer.rst similarity index 50% rename from docs/modules/probe_request_sniffer.rst rename to docs/modules/sniffers/probe_request_sniffer.rst index b095171..a3d379f 100644 --- a/docs/modules/probe_request_sniffer.rst +++ b/docs/modules/sniffers/probe_request_sniffer.rst @@ -1,5 +1,5 @@ Probe Request Sniffer --------------------- -.. automodule:: probequest.probe_request_sniffer +.. automodule:: probequest.sniffers.probe_request_sniffer :members: diff --git a/docs/modules/ui/console.rst b/docs/modules/ui/console.rst new file mode 100644 index 0000000..f5730fa --- /dev/null +++ b/docs/modules/ui/console.rst @@ -0,0 +1,5 @@ +Console +------- + +.. automodule:: probequest.ui.console + :members: diff --git a/docs/probe_requests.rst b/docs/probe_requests.rst index 1385d73..5113a2b 100644 --- a/docs/probe_requests.rst +++ b/docs/probe_requests.rst @@ -2,9 +2,15 @@ What are Wi-Fi probe requests? ============================== -Probe requests are sent by a station to elicit information about access points, in particular to determine if an access point is present or not in the nearby environment. Some devices (mostly smartphones and tablets) use these requests to determine if one of the networks they have previously been connected to is in range, leaking their preferred network list (PNL) and, therefore, your personal information. +Probe requests are sent by a station to elicit information about access points, +in particular to determine if an access point is present or not in the nearby +environment. Some devices (mostly smartphones and tablets) use these requests to +determine if one of the networks they have previously been connected to is in +range, leaking their preferred network list (PNL) and, therefore, your personal +information. -Below is a typical Wi-Fi authentication process between a mobile station (for example, your smartphone) and an access point (AP): +Below is a typical Wi-Fi authentication process between a mobile station (for +example, your smartphone) and an access point (AP): .. seqdiag:: @@ -22,8 +28,13 @@ Below is a typical Wi-Fi authentication process between a mobile station (for ex "Mobile Station" <-- "Access Point" [label = "Association Response"]; } -Step 1 is optional (and therefore, step 2) since the access points announce their presence by broadcasting their name (ESSID) using `beacon frames`_. Consequently, it is not necessary to rely on probe requests to get the list of the access points available. It is a design choice that, although it speeds up the discovery process, causes privacy and security issues. +Step 1 is optional (and therefore, step 2) since the access points announce +their presence by broadcasting their name (ESSID) using `beacon frames`_. +Consequently, it is not necessary to rely on probe requests to get the list of +the access points available. It is a design choice that, although it speeds up +the discovery process, causes privacy and security issues. -ProbeQuest can be used to leverage this leak of information to conduct diverse social engineering and network attacks. +ProbeQuest can be used to leverage this leak of information to conduct diverse +social engineering and network attacks. .. _beacon frames: https://en.wikipedia.org/wiki/Beacon_frame diff --git a/docs/security.rst b/docs/security.rst index 1e6e7ee..0e60a07 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -5,13 +5,16 @@ Security Policy Reporting a Vulnerability ------------------------- -If you have found a security issue in ProbeQuest, please disclose it responsibly by emailing me at `skyper(at)skyplabs[dot]net`. My PGP public key can be found on my `Keybase profile`_: +If you have found a security issue in ProbeQuest, please disclose it responsibly +by emailing me at `skyper(at)skyplabs[dot]net`. My PGP public key can be found +on my `Keybase profile`_: .. image:: https://img.shields.io/keybase/pgp/skyplabs.svg :target: https://keybase.io/skyplabs/pgp_keys.asc :alt: PGP key fingerprint -To facilitate the encryption process, you can use `this online tool`_. You can also use it to verify my signatures. +To facilitate the encryption process, you can use `this online tool`_. You can +also use it to verify my signatures. .. _Keybase profile: https://keybase.io/skyplabs .. _this online tool: https://keybase.io/encrypt#skyplabs diff --git a/docs/usage.rst b/docs/usage.rst index 77deb85..96a41ab 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -5,7 +5,25 @@ Usage Enabling the monitor mode ------------------------- -To be able to sniff the probe requests, your Wi-Fi network interface must be set to monitor mode. +To be able to sniff the probe requests, your Wi-Fi network interface must be set +to `monitor mode`_. + +With `ip` and `iw` +^^^^^^^^^^^^^^^^^^ + +:: + + sudo ip link set down + sudo iw set monitor control + sudo ip link set up + +For example: + +:: + + sudo ip link set wlan0 down + sudo iw wlan0 set monitor control + sudo ip link set wlan0 up With `ifconfig` and `iwconfig` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -63,3 +81,5 @@ Example of use Here is a sample output: .. image:: _static/img/probequest_output_example.png + +.. _monitor mode: https://en.wikipedia.org/wiki/Monitor_mode diff --git a/docs/use_case.rst b/docs/use_case.rst index 97656d4..d2b56b7 100644 --- a/docs/use_case.rst +++ b/docs/use_case.rst @@ -2,18 +2,38 @@ Use Case ======== -Let's consider the following simple scenario inspired from a real data collection (the data have been anonymised): a device tries to connect to `John's iPhone`, `CompanyX_staff`, `STARBUCKS-FREE-WIFI` and `VM21ECAB2`. Based on this information, several assumptions can be made: +Let's consider the following simple scenario inspired from a real data +collection (the data have been anonymised): a device tries to connect to `John's +iPhone`, `CompanyX_staff`, `STARBUCKS-FREE-WIFI` and `VM21ECAB2`. Based on this +information, several assumptions can be made: - The device owner's name is John. -- The device is set in English and its owner speaks this language (otherwise it would have been `iPhone de John` in French, `iPhone von John` in German, etc). -- The device should be a laptop trying to connect to an iPhone in hotspot mode. The owner has consequently at least two devices and is nomad. +- The device is set in English and its owner speaks this language (otherwise it + would have been `iPhone de John` in French, `iPhone von John` in German, etc). +- The device should be a laptop trying to connect to an iPhone in hotspot mode. + The owner has consequently at least two devices and is nomad. - The owner works for CompanyX. - The owner frequents coffee shops, in particular StarBucks. - The owner is used to connecting to open Wi-Fi access points. -- `VM21ECAB2` seems to be a home access point and is the only one in the device's PNL. It is likely the owner's place and, consequently, the device's owner is a customer of Virgin Media. +- `VM21ECAB2` seems to be a home access point and is the only one in the + device's PNL. It is likely the owner's place and, consequently, the device's + owner is a customer of Virgin Media. -As you can see, the amount of data inferred from these four probe requests is already impressive, but we can go further. Relying on a database of Wi-Fi access points’ location, such as `WIGLE.net`_, it becomes possible to determine the places the device’s owner has previously been to. VM21ECAB2 should be a unique name, easily localisable on a map. Same for CompanyX_staff. If this last one is not unique (because CompanyX has several offices), crossing the data we have can help us in our investigation. For example, if CompanyX is present in several countries, we can assume that the device’s owner lives in a country where both CompanyX and Virgin Media are present. Once we have determined which office it is, we can suppose that the device’s owner is used to stopping in StarBucks located on their way from home to their office. +As you can see, the amount of data inferred from these four probe requests is +already impressive, but we can go further. Relying on a database of Wi-Fi access +points’ location, such as `WIGLE.net`_, it becomes possible to determine the +places the device’s owner has previously been to. VM21ECAB2 should be a unique +name, easily localisable on a map. Same for CompanyX_staff. If this last one is +not unique (because CompanyX has several offices), crossing the data we have can +help us in our investigation. For example, if CompanyX is present in several +countries, we can assume that the device’s owner lives in a country where both +CompanyX and Virgin Media are present. Once we have determined which office it +is, we can suppose that the device’s owner is used to stopping in StarBucks +located on their way from home to their office. -Profiling a person is the first step to conduct a social engineering attack. The more we know about our target, the better chance the attack has to succeed. Also, because we know which Wi-Fi access points our target’s devices will try to connect to, an evil twin attack is conceivable. +Profiling a person is the first step to conduct a social engineering attack. The +more we know about our target, the better chance the attack has to succeed. +Also, because we know which Wi-Fi access points our target’s devices will try to +connect to, an evil twin attack is conceivable. .. _WIGLE.net: https://wigle.net/ From 4a72709e12f19dda93b19f56c15ee6d7144f35f8 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 22 Mar 2022 00:17:28 +0000 Subject: [PATCH 125/126] Update changelog --- CHANGELOG.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b24ed9..7063ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,54 @@ +# Changelog + +## v0.8.0 - Mar 22, 2022 + +### Breaking Changes + +* The PNL view has been removed. + +### Improvements + +* Add `pyproject.toml` and `setup.cfg` +* Remove argparse from dependencies (@fabaff) +* Use f-strings instead of `str.format()` +* Add support for Python 3.8, 3.9 and 3.10 +* Drop support for Python 3.4, 3.5 and 3.6 +* Make some dependencies optional +* Refactor code around Scapy's PipeTools +* Add metavars to argument parser +* Turn `interface` option into argument +* Cache the compiled regex in `Config` once computed +* Cache the frame filter in `Config` once computed +* Cache the MAC address' OUI in `ProbeRequest` +* Use the logging package +* Add extra dependency group `tests` +* Add unit tests for the argument parser +* Add `__version__` attribute to package +* Use an entry point to generate the CLI tool +* Use tox for unit testing + +### Fixes + +* Fix interface checking +* Close open files before exiting +* Use a fake `Config` object in unit tests +* Fix linting issues + +### Infrastructure + +* Upgrade RTD configuration file to version 2 +* Monitor GH Actions dependencies with Dependabot +* Use `main` as branch for production releases +* Upgrade to GitHub-native Dependabot +* Add macOS to build matrix +* Switch from Travis CI to GitHub Actions + ## v0.7.2 - Aug 26, 2019 ### Improvements -* Use the new [Scapy built-in asynchronous sniffer](https://scapy.readthedocs.io/en/latest/usage.html#asynchronous-sniffing) +* Use the new [Scapy built-in asynchronous + sniffer](https://scapy.readthedocs.io/en/latest/usage.html#asynchronous-sniffing) * Introduce the new `Config` object containing the configuration of ProbeQuest ### Fixes @@ -17,8 +63,10 @@ ### Fixes -* Error when trying to decode ESSIDs using invalid UTF-8 characters ([#4](https://github.com/SkypLabs/probequest/issues/4)) -* Arguments not working (-e, -r) ([#17](https://github.com/SkypLabs/probequest/issues/17)) +* Error when trying to decode ESSIDs using invalid UTF-8 characters + ([#4](https://github.com/SkypLabs/probequest/issues/4)) +* Arguments not working (-e, -r) + ([#17](https://github.com/SkypLabs/probequest/issues/17)) ## v0.7.0 - Oct 8, 2018 @@ -34,7 +82,9 @@ ### Fixes -* Test if a packet has a `Dot11ProbeReq` layer before parsing it ([#5](https://github.com/SkypLabs/probequest/issues/5), [#8](https://github.com/SkypLabs/probequest/issues/8)) +* Test if a packet has a `Dot11ProbeReq` layer before parsing it + ([#5](https://github.com/SkypLabs/probequest/issues/5), + [#8](https://github.com/SkypLabs/probequest/issues/8)) ## v0.6.1 - May 28, 2018 @@ -71,7 +121,8 @@ The project has been renamed to ProbeQuest. ### Fixes -* The sniffer stops after having received the first frame ([#3](https://github.com/SkypLabs/probequest/issues/3)) +* The sniffer stops after having received the first frame + ([#3](https://github.com/SkypLabs/probequest/issues/3)) ## v0.5.0 - Feb 7, 2018 From cb471754d09c13513ba8bdd4b2d1b79a031cbd16 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 22 Mar 2022 00:44:04 +0000 Subject: [PATCH 126/126] Upgrade RTD configuration file to version 2 --- .readthedocs.yaml | 17 +++++++++++++++++ .readthedocs.yml | 5 ----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 .readthedocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a479393 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3" + +python: + install: + - method: pip + path: . + extra_requirements: + - complete + - docs + +sphinx: + configuration: docs/conf.py diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index b752bb3..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,5 +0,0 @@ -python: - version: 3 - pip_install: true - extra_requirements: - - docs