lanscape 1.3.1a8__py3-none-any.whl → 1.3.2a6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lanscape might be problematic. Click here for more details.

Files changed (41) hide show
  1. lanscape/__init__.py +4 -1
  2. lanscape/__main__.py +4 -1
  3. lanscape/ui/static/css/style.css +1 -1
  4. lanscape/ui/templates/core/scripts.html +1 -1
  5. lanscape/ui/templates/info.html +1 -1
  6. lanscape/ui/templates/main.html +6 -4
  7. {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/METADATA +4 -3
  8. lanscape-1.3.2a6.dist-info/RECORD +43 -0
  9. lanscape/libraries/app_scope.py +0 -70
  10. lanscape/libraries/decorators.py +0 -75
  11. lanscape/libraries/errors.py +0 -29
  12. lanscape/libraries/ip_parser.py +0 -65
  13. lanscape/libraries/logger.py +0 -42
  14. lanscape/libraries/mac_lookup.py +0 -69
  15. lanscape/libraries/net_tools.py +0 -480
  16. lanscape/libraries/port_manager.py +0 -59
  17. lanscape/libraries/runtime_args.py +0 -44
  18. lanscape/libraries/service_scan.py +0 -51
  19. lanscape/libraries/subnet_scan.py +0 -373
  20. lanscape/libraries/version_manager.py +0 -54
  21. lanscape/libraries/web_browser.py +0 -141
  22. lanscape/resources/mac_addresses/convert_csv.py +0 -27
  23. lanscape/resources/ports/convert_csv.py +0 -27
  24. lanscape/tests/__init__.py +0 -3
  25. lanscape/tests/_helpers.py +0 -15
  26. lanscape/tests/test_api.py +0 -194
  27. lanscape/tests/test_env.py +0 -30
  28. lanscape/tests/test_library.py +0 -53
  29. lanscape/ui/app.py +0 -122
  30. lanscape/ui/blueprints/__init__.py +0 -7
  31. lanscape/ui/blueprints/api/__init__.py +0 -5
  32. lanscape/ui/blueprints/api/port.py +0 -27
  33. lanscape/ui/blueprints/api/scan.py +0 -69
  34. lanscape/ui/blueprints/api/tools.py +0 -30
  35. lanscape/ui/blueprints/web/__init__.py +0 -5
  36. lanscape/ui/blueprints/web/routes.py +0 -74
  37. lanscape/ui/main.py +0 -138
  38. lanscape-1.3.1a8.dist-info/RECORD +0 -72
  39. {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/WHEEL +0 -0
  40. {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/licenses/LICENSE +0 -0
  41. {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/top_level.txt +0 -0
lanscape/__init__.py CHANGED
@@ -1,3 +1,6 @@
1
+ """
2
+ Local network scanner
3
+ """
1
4
  from .libraries.subnet_scan import (
2
5
  SubnetScanner,
3
6
  ScanConfig,
@@ -6,4 +9,4 @@ from .libraries.subnet_scan import (
6
9
 
7
10
  from .libraries.port_manager import PortManager
8
11
 
9
- from .libraries import net_tools
12
+ from .libraries import net_tools
lanscape/__main__.py CHANGED
@@ -1,6 +1,9 @@
1
+ """
2
+ Lanscape module entry point.
3
+ """
1
4
  from .ui.main import main
2
5
 
3
6
  # leveraged when calling as module ie "python -m lanscape"
4
7
 
5
8
  if __name__ == "__main__":
6
- main()
9
+ main()
@@ -56,7 +56,7 @@ body:has(.submodule) footer {
56
56
  background-color: var(--primary-bg);
57
57
  border-radius: 8px;
58
58
  box-shadow: 0 0 10px var(--box-shadow);
59
- width: 90%;
59
+ width: 95%;
60
60
  margin-top: 10px;
61
61
  overflow: hidden;
62
62
  }
@@ -4,6 +4,6 @@
4
4
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"></script>
5
5
  <script src="{{ url_for('static', filename='js/core.js') }}"></script>
6
6
 
7
- {% if not section is defined %}
7
+ {% if section is not defined %}
8
8
  <script src="{{ url_for('static', filename='js/on-tab-close.js') }}"></script>
9
9
  {% endif %}
@@ -4,7 +4,7 @@
4
4
  <div id="header">
5
5
  <!-- Header and Scan Submission Inline -->
6
6
  <div class="d-flex justify-content-start align-items-center">
7
- <a href="/" class="text-decoration-none">
7
+ <a href="/" class="text-decoration-none" aria-label="Go to homepage">
8
8
  <h1 class="title">
9
9
  <span>LAN</span>scape
10
10
  </h1>
@@ -4,9 +4,11 @@
4
4
  <div id="header">
5
5
  <!-- Header and Scan Submission Inline -->
6
6
  <div class="d-flex justify-content-between align-items-center flex-wrap">
7
- <h1 class="title" onclick="location.reload()">
8
- <span>LAN</span>scape
9
- </h1>
7
+ <a href="/" class="text-decoration-none" aria-label="Go to homepage">
8
+ <h1 class="title">
9
+ <span>LAN</span>scape
10
+ </h1>
11
+ </a>
10
12
  <!-- Form -->
11
13
  <form id="scan-form" class="d-flex align-items-end">
12
14
  <div class="form-group me-2">
@@ -52,7 +54,7 @@
52
54
  <!-- ARP Error -->
53
55
  <div id="arp-error" class="{{ 'div-hide' if is_arp_supported else '' }}">
54
56
  <span>
55
- Unable to discover devices using ARP. Device discovery is degraded.
57
+ Unable to use ARP lookup. Device discovery is degraded.
56
58
  <a target="_blank" href="https://github.com/mdennis281/LANscape/blob/main/support/arp-issues.md">Steps to fix</a>
57
59
  </span>
58
60
  </div>
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 1.3.1a8
3
+ Version: 1.3.2a6
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
+ License-Expression: MIT
6
7
  Project-URL: Homepage, https://github.com/mdennis281/py-lanscape
7
8
  Project-URL: Issues, https://github.com/mdennis281/py-lanscape/issues
9
+ Keywords: network,scanner,lan,local,python
8
10
  Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
11
  Classifier: Operating System :: OS Independent
11
12
  Requires-Python: >=3.8
12
13
  Description-Content-Type: text/markdown
@@ -17,7 +18,7 @@ Requires-Dist: requests<3.0,>=2.32
17
18
  Requires-Dist: setuptools
18
19
  Requires-Dist: scapy<3.0,>=2.3.2
19
20
  Requires-Dist: tabulate==0.9.0
20
- Requires-Dist: pytest
21
+ Requires-Dist: pydantic
21
22
  Dynamic: license-file
22
23
 
23
24
  # LANscape
@@ -0,0 +1,43 @@
1
+ lanscape/__init__.py,sha256=dWWi_Epf3DohHEcKB49-FgB6RDNp1sioqM6RS-mvDDM,215
2
+ lanscape/__main__.py,sha256=AoDktsJfN-9YJrq7O2zQp1tZfCR9K4iAAyycKOHwDG8,171
3
+ lanscape/resources/mac_addresses/mac_db.json,sha256=ygtFSwNwJzDlg6hmAujdgCyzUjxt9Di75J8SO4xYIs8,2187804
4
+ lanscape/resources/ports/full.json,sha256=Abfbi-b5yZF4jR5NS6CT6QpIDfx4Vk04gIC1fKH2ws0,1506980
5
+ lanscape/resources/ports/large.json,sha256=jK4gkzlPT74uv4y0TvFjsaOaUcX7Cy8Zxe2bh5FYcc0,144496
6
+ lanscape/resources/ports/medium.json,sha256=3yHAca0_pEWXK4k1wmda1eNVM6ftzcpKn5VspVwmkRs,3667
7
+ lanscape/resources/ports/small.json,sha256=Mj3zGVG1F2eqZx2YkrLpTL8STeLcqB8_65IR67MS1Tg,397
8
+ lanscape/resources/services/definitions.jsonc,sha256=71w9Q7r4RoBYiIMkzzO2KdEJXaSIchNccYQueqAhD4E,8842
9
+ lanscape/ui/static/lanscape.webmanifest,sha256=0aauJk_Bybd0B2iwzJfvPcs7AX43kVHs0dtpV6_jSWk,459
10
+ lanscape/ui/static/css/style.css,sha256=lu9078sZKrMBmT_tM6084_DDnEEMXVMziO6E-gVYkhk,16372
11
+ lanscape/ui/static/img/ico/android-chrome-192x192.png,sha256=JmFT6KBCCuoyxMV-mLNtF9_QJbVBvfWPUizKN700fi8,18255
12
+ lanscape/ui/static/img/ico/android-chrome-512x512.png,sha256=88Jjx_1-4XAnZYz64KP6FdTl_kYkNG2_kQIKteQwSh4,138055
13
+ lanscape/ui/static/img/ico/apple-touch-icon.png,sha256=tEJlLwBZtF4v-NC90YCfRJQ2prTsF4i3VQLK_hnv2Mw,16523
14
+ lanscape/ui/static/img/ico/favicon-16x16.png,sha256=HpQOZk3rziZjT1xQxKuy5WourXsfrdwuzQY1hChzBJQ,573
15
+ lanscape/ui/static/img/ico/favicon-32x32.png,sha256=UpgiDPIHckK19udHtACiaI3ZPbmImUUcN1GcrjpEg9s,1302
16
+ lanscape/ui/static/img/ico/favicon.ico,sha256=rs5vq0MPJ1LzzioOzOz5aQLVfrtS2nLRc920dOeReTw,15406
17
+ lanscape/ui/static/img/ico/site.webmanifest,sha256=ep4Hzh9zhmiZF2At3Fp1dQrYQuYF_3ZPZxc1KcGBvwQ,263
18
+ lanscape/ui/static/js/core.js,sha256=y-f8iQPIetllUY0lSCwnGbPCk5fTJbbU6Pxm3rw1EBU,1111
19
+ lanscape/ui/static/js/layout-sizing.js,sha256=23UuKdEmRChg6fyqj3DRvcsNfMoa6MRt6dkaT0k7_UY,841
20
+ lanscape/ui/static/js/main.js,sha256=s0ipGqmuuFHnH9KKoUVaDRJ10_YqYoJ-9r_YnbsH8Mw,7676
21
+ lanscape/ui/static/js/on-tab-close.js,sha256=YYzNd1KMrLWW4-rFcC8EXckTRfG-pKRLA6xpdVTDt04,1417
22
+ lanscape/ui/static/js/quietReload.js,sha256=_mHzpUsGL4Lm1hNsr3VYSOGVcgGA2y1-eZHacssTXGs,724
23
+ lanscape/ui/static/js/shutdown-server.js,sha256=WkO7_SNSHM_6kReUoCoExIdCf7Sl7IPiSiNxpbI-r0s,469
24
+ lanscape/ui/static/js/subnet-info.js,sha256=aytt0LkBx4FVq36TxiMEw3aM7XQLHg_ng1U2WDwZVF4,577
25
+ lanscape/ui/static/js/subnet-selector.js,sha256=OG01pDaSOPLq3Ial0aO0CqPcob9tPZA1MZKGmQG0W7Q,366
26
+ lanscape/ui/templates/base.html,sha256=P5xnMlvDXYkYSXdSZUWaRfhsszNuZPP7A56hemBrAFs,1498
27
+ lanscape/ui/templates/error.html,sha256=zXFO0zPIfQORWq1ZMiSZ8G7FjfhVVr-aaYC0HeBl4Rs,1068
28
+ lanscape/ui/templates/info.html,sha256=1ISKdR0dWoGscS--cY_h65UCdL4ldYd8B4-4k6S3QqI,2614
29
+ lanscape/ui/templates/main.html,sha256=2PJEIRcNZSMFsuqn8rlaC-Q5qxaR6qLKnaQQce7C-Gs,4208
30
+ lanscape/ui/templates/scan.html,sha256=Fz1Q4CzRq5qpKgszTAQLhaLVV0A6gBraT33mNDmpYRE,390
31
+ lanscape/ui/templates/shutdown.html,sha256=v0cGT5CJWi-V8b5sUN3l-QIDNUmHTvKGi2gDlhmRlrs,724
32
+ lanscape/ui/templates/core/head.html,sha256=6XyoDIz-IMPIRG-ti2LFY9FFX39UTIf_K6-6USni_ek,788
33
+ lanscape/ui/templates/core/scripts.html,sha256=l9qoODOD9VDKMk2-4lsD_gwscHPXgwuDw-SkQXfXylo,649
34
+ lanscape/ui/templates/scan/export.html,sha256=Qi0m2xJPbC5I2rxzekXjvQ6q9gm2Lr4VJW6riLhIaU0,776
35
+ lanscape/ui/templates/scan/ip-table-row.html,sha256=ptY24rxJRaA4PEEQRDncaq6Q0ql5RJ87Kn0zKRCzOHw,4842
36
+ lanscape/ui/templates/scan/ip-table.html,sha256=ds__UP9JiTKf5IxCmTMzw--eN_yg1Pvn3Nj1KvQxeZg,940
37
+ lanscape/ui/templates/scan/overview.html,sha256=FsX-jSFhGKwCxZGKE8AMKk328UuawN6O9RNTzYvIOts,1205
38
+ lanscape/ui/templates/scan/scan-error.html,sha256=Q4eZM5ThrxnFaWOSTUpK8hA2ksHwhxOBTaVUCLALhyA,1032
39
+ lanscape-1.3.2a6.dist-info/licenses/LICENSE,sha256=cCO-NbS01Ilwc6djHjZ7LIgPFRkRmWdr0fH2ysXKioA,1090
40
+ lanscape-1.3.2a6.dist-info/METADATA,sha256=7cqoGasowbdC4PTiGP6KpygaCjlN4x8E_A8AVvh2Rnk,2586
41
+ lanscape-1.3.2a6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
+ lanscape-1.3.2a6.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
43
+ lanscape-1.3.2a6.dist-info/RECORD,,
@@ -1,70 +0,0 @@
1
- from pathlib import Path
2
- import json
3
- import sys
4
- import re
5
-
6
- class ResourceManager:
7
- """
8
- A class to manage assets in the resources folder.
9
- Works locally and if installed based on relative path from this file
10
- """
11
- def __init__(self, asset_folder: str):
12
- self.asset_dir = self._get_resource_path() / asset_folder
13
-
14
- def list(self):
15
- return [p.name for p in self.asset_dir.iterdir()]
16
-
17
- def get(self, asset_name: str):
18
- with open(self.asset_dir / asset_name, 'r') as f:
19
- return f.read()
20
-
21
- def get_json(self, asset_name: str):
22
- return json.loads(self.get(asset_name))
23
-
24
- def get_jsonc(self, asset_name: str):
25
- " Get JSON content with comments removed "
26
- content = self.get(asset_name)
27
- cleaned_content = re.sub(r'//.*', '', content)
28
- return json.loads(cleaned_content)
29
-
30
-
31
- def update(self, asset_name: str, content: str):
32
- with open(self.asset_dir / asset_name, 'w') as f:
33
- f.write(content)
34
-
35
- def create(self, asset_name: str, content: str):
36
- if (self.asset_dir / asset_name).exists():
37
- raise FileExistsError(f"File {asset_name} already exists")
38
- with open(self.asset_dir / asset_name, 'w') as f:
39
- f.write(content)
40
-
41
- def delete(self, asset_name: str):
42
- (self.asset_dir / asset_name).unlink()
43
-
44
- def _get_resource_path(self) -> Path:
45
- base_dir = Path(__file__).parent.parent
46
- resource_dir = base_dir / "resources"
47
- return resource_dir
48
-
49
-
50
-
51
-
52
- def is_local_run(module_name: str = 'lanscape') -> bool:
53
- """
54
- Determine if the code is running locally or as an installed PyPI package.
55
- """
56
- module_path = Path(__file__).parent
57
-
58
- # Check if the path is in site-packages/dist-packages
59
- if module_path and any(part in module_path.parts for part in ['site-packages', 'dist-packages']):
60
- return False # Installed package
61
-
62
- # Check for a .git directory in the path or its parents
63
- if module_path and any((parent / ".git").exists() for parent in module_path.parents):
64
- return True # Local development
65
-
66
- # Check sys.path for non-standard local paths
67
- if any(not str(Path(p)).startswith(('/usr', '/lib', '/site-packages')) for p in sys.path):
68
- return True # Local run
69
-
70
- return False # Default to installed package
@@ -1,75 +0,0 @@
1
- from time import time
2
- from dataclasses import dataclass, field
3
- from typing import DefaultDict
4
- from collections import defaultdict
5
- from tabulate import tabulate
6
- @dataclass
7
- class JobStats:
8
- running: DefaultDict[str, int] = field(default_factory=lambda: defaultdict(int))
9
- finished: DefaultDict[str, int] = field(default_factory=lambda: defaultdict(int))
10
- timing: DefaultDict[str, float] = field(default_factory=lambda: defaultdict(float))
11
-
12
- def __str__(self):
13
- data = [
14
- [name, self.running.get(name, 0), self.finished.get(name, 0), self.timing.get(name, 0.0)]
15
- for name in set(self.running) | set(self.finished)
16
- ]
17
-
18
- headers = ["Function", "Running", "Finished", "Avg Time (s)"]
19
- return tabulate(data, headers=headers, tablefmt="grid")
20
-
21
-
22
- def job_tracker(func):
23
- fxn = func.__name__
24
-
25
- def wrapper(*args, **kwargs):
26
- # Access the class instance and initialize job tracking stats
27
- class_instance = args[0]
28
- job_stats = init_job_tracker(class_instance)
29
-
30
- # Increment running counter and track execution time
31
- job_stats.running[fxn] += 1
32
- start = time()
33
-
34
- result = func(*args, **kwargs) # Execute the wrapped function
35
-
36
- # Update statistics after function execution
37
- elapsed = time() - start
38
- job_stats.running[fxn] -= 1
39
- job_stats.finished[fxn] += 1
40
-
41
- # Calculate the new average timing for the function
42
- job_stats.timing[fxn] = round(
43
- ((job_stats.finished[fxn] - 1) * job_stats.timing[fxn] + elapsed) / job_stats.finished[fxn], 4
44
- )
45
-
46
- # Clean up if no more running instances of this function
47
- if job_stats.running[fxn] == 0:
48
- job_stats.running.pop(fxn)
49
-
50
- return result
51
-
52
- def init_job_tracker(class_instance):
53
- # Initialize job_stats if it doesn't exist
54
- if not hasattr(class_instance, 'job_stats'):
55
- class_instance.job_stats = JobStats()
56
- return class_instance.job_stats
57
-
58
- return wrapper
59
-
60
-
61
- def terminator(func):
62
- """
63
- decorator designed specifically for the SubnetScanner class,
64
- helps facilitate termination of a job
65
- """
66
- def wrapper(*args, **kwargs):
67
- scan = args[0] # aka self
68
- if not scan.running:
69
- return
70
- return func(*args, **kwargs)
71
-
72
-
73
-
74
- return wrapper
75
-
@@ -1,29 +0,0 @@
1
-
2
-
3
- class SubnetTooLargeError(Exception):
4
- """Custom exception raised when the subnet size exceeds the allowed limit."""
5
- def __init__(self, subnet):
6
- self.subnet = subnet
7
- super().__init__(f"Subnet {subnet} exceeds the limit of IP addresses.")
8
-
9
-
10
- class SubnetScanTerminationFailure(Exception):
11
- def __init__(self,running_threads):
12
- super().__init__(f'Unable to terminate active threads: {running_threads}')
13
-
14
- class DeviceError(Exception):
15
- def __init__(self, e:Exception):
16
- self.base: Exception = e
17
- self.method = self._attempt_extract_method()
18
-
19
- def _attempt_extract_method(self):
20
- try:
21
- tb = self.base.__traceback__
22
- frame = tb.tb_frame
23
- return frame.f_code.co_name
24
- except Exception as e:
25
- print(e)
26
- return 'unknown'
27
-
28
- def __str__(self):
29
- return f'Error(source={self.method}, msg={self.base})'
@@ -1,65 +0,0 @@
1
- import ipaddress
2
- from .errors import SubnetTooLargeError
3
- import re
4
-
5
- MAX_IPS_ALLOWED = 100000
6
-
7
- def parse_ip_input(ip_input):
8
- # Split input on commas for multiple entries
9
- entries = [entry.strip() for entry in ip_input.split(',')]
10
- ip_ranges = []
11
-
12
- for entry in entries:
13
- # Handle CIDR notation or IP/32
14
- if '/' in entry:
15
- net = ipaddress.IPv4Network(entry,strict=False)
16
- if net.num_addresses > MAX_IPS_ALLOWED:
17
- raise SubnetTooLargeError(ip_input)
18
- for ip in net.hosts():
19
- ip_ranges.append(ip)
20
-
21
- # Handle IP range (e.g., 10.0.0.15-10.0.0.25)
22
- elif '-' in entry:
23
- ip_ranges += parse_ip_range(entry)
24
-
25
- # Handle shorthand IP range (e.g., 10.0.9.1-253)
26
- elif re.search(r'\d+\-\d+', entry):
27
- ip_ranges += parse_shorthand_ip_range(entry)
28
-
29
- # If no CIDR or range, assume a single IP
30
- else:
31
- ip_ranges.append(ipaddress.IPv4Address(entry))
32
- if len(ip_ranges) > MAX_IPS_ALLOWED:
33
- raise SubnetTooLargeError(ip_input)
34
- return ip_ranges
35
-
36
- def get_address_count(subnet: str):
37
- try:
38
- net = ipaddress.IPv4Network(subnet,strict=False)
39
- return net.num_addresses
40
- except:
41
- return 0
42
-
43
- def parse_ip_range(entry):
44
- start_ip, end_ip = entry.split('-')
45
- start_ip = ipaddress.IPv4Address(start_ip.strip())
46
-
47
- # Handle case where the second part is a partial IP (e.g., '253')
48
- if '.' not in end_ip:
49
- end_ip = start_ip.exploded.rsplit('.', 1)[0] + '.' + end_ip.strip()
50
-
51
- end_ip = ipaddress.IPv4Address(end_ip.strip())
52
- return list(ip_range_to_list(start_ip, end_ip))
53
-
54
- def parse_shorthand_ip_range(entry):
55
- start_ip, end_part = entry.split('-')
56
- start_ip = ipaddress.IPv4Address(start_ip.strip())
57
- end_ip = start_ip.exploded.rsplit('.', 1)[0] + '.' + end_part.strip()
58
-
59
- return list(ip_range_to_list(start_ip, ipaddress.IPv4Address(end_ip)))
60
-
61
- def ip_range_to_list(start_ip, end_ip):
62
- # Yield the range of IPs
63
- for ip_int in range(int(start_ip), int(end_ip) + 1):
64
- yield ipaddress.IPv4Address(ip_int)
65
-
@@ -1,42 +0,0 @@
1
- import logging
2
- from logging.handlers import RotatingFileHandler
3
- import click
4
-
5
-
6
- def configure_logging(loglevel:str, logfile:bool, flask_logging:bool=False) -> None:
7
- numeric_level = getattr(logging, loglevel.upper(), None)
8
- if not isinstance(numeric_level, int):
9
- raise ValueError(f'Invalid log level: {loglevel}')
10
-
11
-
12
- logging.basicConfig(level=numeric_level, format='[%(name)s] %(levelname)s - %(message)s')
13
-
14
- # flask spams too much on info
15
- if not flask_logging:
16
- disable_flask_logging()
17
-
18
- if logfile:
19
- handler = RotatingFileHandler('lanscape.log', maxBytes=100000, backupCount=3)
20
- handler.setLevel(numeric_level)
21
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
22
- handler.setFormatter(formatter)
23
- logging.getLogger().addHandler(handler)
24
- else:
25
- # For console, it defaults to basicConfig
26
- pass
27
-
28
- def disable_flask_logging() -> None:
29
-
30
- def override_click_logging():
31
- def secho(text, file=None, nl=None, err=None, color=None, **styles):
32
- pass
33
-
34
- def echo(text, file=None, nl=None, err=None, color=None, **styles):
35
- pass
36
-
37
- click.echo = echo
38
- click.secho = secho
39
- werkzeug_log = logging.getLogger('werkzeug')
40
- werkzeug_log.setLevel(logging.ERROR)
41
-
42
- override_click_logging()
@@ -1,69 +0,0 @@
1
- import re
2
- import logging
3
- import platform
4
- import subprocess
5
- from typing import List
6
-
7
- from .app_scope import ResourceManager
8
-
9
- DB = ResourceManager('mac_addresses').get_json('mac_db.json')
10
-
11
- log = logging.getLogger('MacLookup')
12
-
13
-
14
- def lookup_mac(mac: str) -> str:
15
- """
16
- Lookup a MAC address in the database and return the vendor name.
17
- """
18
- if mac:
19
- for m in DB:
20
- if mac.upper().startswith(str(m).upper()):
21
- return DB[m]
22
- return None
23
-
24
- def get_macs(ip: str) -> List[str]:
25
- """Try to get the MAC address using Scapy, fallback to ARP if it fails."""
26
- if mac := get_mac_by_scapy(ip):
27
- log.debug(f"Used Scapy to resolve ip {ip} to mac {mac}")
28
- return mac
29
- arp = get_mac_by_arp(ip)
30
- log.debug(f"Used ARP to resolve ip {ip} to mac {arp}")
31
- return arp
32
-
33
-
34
- def get_mac_by_arp(ip: str) -> List[str]:
35
- """Retrieve the last MAC address instance using the ARP command."""
36
- try:
37
- # Use the appropriate ARP command based on the platform
38
- cmd = f"arp -a {ip}" if platform.system() == "Windows" else f"arp {ip}"
39
-
40
- # Execute the ARP command and decode the output
41
- output = subprocess.check_output(
42
- cmd, shell=True
43
- ).decode().replace('-', ':')
44
-
45
- macs = re.findall(r'..:..:..:..:..:..', output)
46
- # found that typically last mac is the correct one
47
- return macs
48
- except:
49
- return []
50
-
51
- def get_mac_by_scapy(ip: str) -> List[str]:
52
- """Retrieve the MAC address using the Scapy library."""
53
- try:
54
- from scapy.all import ARP, Ether, srp
55
-
56
- # Construct and send an ARP request
57
- arp_request = ARP(pdst=ip)
58
- broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
59
- packet = broadcast / arp_request
60
-
61
- # Send the packet and wait for a response
62
- result = srp(packet, timeout=1, verbose=0)[0]
63
-
64
- # Extract the MAC addresses from the response
65
- return [res[1].hwsrc for res in result]
66
- # return result[0][1].hwsrc if result else None
67
- except:
68
- return None
69
-