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.
- lanscape/__init__.py +4 -1
- lanscape/__main__.py +4 -1
- lanscape/ui/static/css/style.css +1 -1
- lanscape/ui/templates/core/scripts.html +1 -1
- lanscape/ui/templates/info.html +1 -1
- lanscape/ui/templates/main.html +6 -4
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/METADATA +4 -3
- lanscape-1.3.2a6.dist-info/RECORD +43 -0
- lanscape/libraries/app_scope.py +0 -70
- lanscape/libraries/decorators.py +0 -75
- lanscape/libraries/errors.py +0 -29
- lanscape/libraries/ip_parser.py +0 -65
- lanscape/libraries/logger.py +0 -42
- lanscape/libraries/mac_lookup.py +0 -69
- lanscape/libraries/net_tools.py +0 -480
- lanscape/libraries/port_manager.py +0 -59
- lanscape/libraries/runtime_args.py +0 -44
- lanscape/libraries/service_scan.py +0 -51
- lanscape/libraries/subnet_scan.py +0 -373
- lanscape/libraries/version_manager.py +0 -54
- lanscape/libraries/web_browser.py +0 -141
- lanscape/resources/mac_addresses/convert_csv.py +0 -27
- lanscape/resources/ports/convert_csv.py +0 -27
- lanscape/tests/__init__.py +0 -3
- lanscape/tests/_helpers.py +0 -15
- lanscape/tests/test_api.py +0 -194
- lanscape/tests/test_env.py +0 -30
- lanscape/tests/test_library.py +0 -53
- lanscape/ui/app.py +0 -122
- lanscape/ui/blueprints/__init__.py +0 -7
- lanscape/ui/blueprints/api/__init__.py +0 -5
- lanscape/ui/blueprints/api/port.py +0 -27
- lanscape/ui/blueprints/api/scan.py +0 -69
- lanscape/ui/blueprints/api/tools.py +0 -30
- lanscape/ui/blueprints/web/__init__.py +0 -5
- lanscape/ui/blueprints/web/routes.py +0 -74
- lanscape/ui/main.py +0 -138
- lanscape-1.3.1a8.dist-info/RECORD +0 -72
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/WHEEL +0 -0
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/licenses/LICENSE +0 -0
- {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
lanscape/ui/static/css/style.css
CHANGED
|
@@ -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
|
|
7
|
+
{% if section is not defined %}
|
|
8
8
|
<script src="{{ url_for('static', filename='js/on-tab-close.js') }}"></script>
|
|
9
9
|
{% endif %}
|
lanscape/ui/templates/info.html
CHANGED
|
@@ -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>
|
lanscape/ui/templates/main.html
CHANGED
|
@@ -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
|
-
<
|
|
8
|
-
<
|
|
9
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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,,
|
lanscape/libraries/app_scope.py
DELETED
|
@@ -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
|
lanscape/libraries/decorators.py
DELETED
|
@@ -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
|
-
|
lanscape/libraries/errors.py
DELETED
|
@@ -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})'
|
lanscape/libraries/ip_parser.py
DELETED
|
@@ -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
|
-
|
lanscape/libraries/logger.py
DELETED
|
@@ -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()
|
lanscape/libraries/mac_lookup.py
DELETED
|
@@ -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
|
-
|