lanscape 1.3.5a1__py3-none-any.whl → 1.3.6a1__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 +9 -1
- lanscape/libraries/app_scope.py +0 -1
- lanscape/libraries/decorators.py +26 -9
- lanscape/libraries/device_alive.py +227 -0
- lanscape/libraries/errors.py +10 -0
- lanscape/libraries/ip_parser.py +73 -1
- lanscape/libraries/logger.py +29 -1
- lanscape/libraries/mac_lookup.py +5 -0
- lanscape/libraries/net_tools.py +156 -188
- lanscape/libraries/port_manager.py +83 -0
- lanscape/libraries/scan_config.py +173 -19
- lanscape/libraries/service_scan.py +3 -3
- lanscape/libraries/subnet_scan.py +111 -26
- lanscape/libraries/version_manager.py +50 -7
- lanscape/libraries/web_browser.py +75 -58
- lanscape/resources/mac_addresses/convert_csv.py +13 -2
- lanscape/resources/ports/convert_csv.py +13 -3
- lanscape/ui/app.py +24 -6
- lanscape/ui/blueprints/__init__.py +4 -1
- lanscape/ui/blueprints/api/__init__.py +2 -0
- lanscape/ui/blueprints/api/port.py +46 -0
- lanscape/ui/blueprints/api/scan.py +57 -5
- lanscape/ui/blueprints/api/tools.py +1 -0
- lanscape/ui/blueprints/web/__init__.py +4 -0
- lanscape/ui/blueprints/web/routes.py +52 -2
- lanscape/ui/main.py +1 -10
- lanscape/ui/shutdown_handler.py +5 -1
- lanscape/ui/static/css/style.css +35 -24
- lanscape/ui/static/js/scan-config.js +76 -2
- lanscape/ui/templates/main.html +0 -7
- lanscape/ui/templates/scan/config.html +71 -10
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6a1.dist-info}/METADATA +1 -1
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6a1.dist-info}/RECORD +36 -35
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6a1.dist-info}/WHEEL +0 -0
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6a1.dist-info}/licenses/LICENSE +0 -0
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6a1.dist-info}/top_level.txt +0 -0
lanscape/__init__.py
CHANGED
|
@@ -3,10 +3,18 @@ Local network scanner
|
|
|
3
3
|
"""
|
|
4
4
|
from lanscape.libraries.subnet_scan import (
|
|
5
5
|
SubnetScanner,
|
|
6
|
-
ScanConfig,
|
|
7
6
|
ScanManager
|
|
8
7
|
)
|
|
9
8
|
|
|
9
|
+
from lanscape.libraries.scan_config import (
|
|
10
|
+
ScanConfig,
|
|
11
|
+
ArpConfig,
|
|
12
|
+
PingConfig,
|
|
13
|
+
PokeConfig,
|
|
14
|
+
ArpCacheConfig,
|
|
15
|
+
ScanType
|
|
16
|
+
)
|
|
17
|
+
|
|
10
18
|
from lanscape.libraries.port_manager import PortManager
|
|
11
19
|
|
|
12
20
|
from lanscape.libraries import net_tools
|
lanscape/libraries/app_scope.py
CHANGED
lanscape/libraries/decorators.py
CHANGED
|
@@ -18,9 +18,26 @@ class JobStats:
|
|
|
18
18
|
"""
|
|
19
19
|
Tracks statistics for job execution, including running, finished, and timing data.
|
|
20
20
|
"""
|
|
21
|
-
running: DefaultDict[str, int] = field(
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
running: DefaultDict[str, int] = field(
|
|
22
|
+
default_factory=lambda: defaultdict(int))
|
|
23
|
+
finished: DefaultDict[str, int] = field(
|
|
24
|
+
default_factory=lambda: defaultdict(int))
|
|
25
|
+
timing: DefaultDict[str, float] = field(
|
|
26
|
+
default_factory=lambda: defaultdict(float))
|
|
27
|
+
|
|
28
|
+
_instance = None
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
# Only initialize once
|
|
32
|
+
if not hasattr(self, "running"):
|
|
33
|
+
self.running = defaultdict(int)
|
|
34
|
+
self.finished = defaultdict(int)
|
|
35
|
+
self.timing = defaultdict(float)
|
|
36
|
+
|
|
37
|
+
def __new__(cls, *args, **kwargs):
|
|
38
|
+
if cls._instance is None:
|
|
39
|
+
cls._instance = super(JobStats, cls).__new__(cls)
|
|
40
|
+
return cls._instance
|
|
24
41
|
|
|
25
42
|
def __str__(self):
|
|
26
43
|
"""Return a formatted string representation of the job statistics."""
|
|
@@ -50,14 +67,13 @@ class JobStatsMixin: # pylint: disable=too-few-public-methods
|
|
|
50
67
|
@property
|
|
51
68
|
def job_stats(self):
|
|
52
69
|
"""Return the shared JobStats instance."""
|
|
53
|
-
|
|
54
|
-
JobStatsMixin._job_stats = JobStats()
|
|
55
|
-
return JobStatsMixin._job_stats
|
|
70
|
+
return JobStats()
|
|
56
71
|
|
|
57
72
|
|
|
58
73
|
def job_tracker(func):
|
|
59
74
|
"""
|
|
60
|
-
Decorator to track job statistics for a method,
|
|
75
|
+
Decorator to track job statistics for a method,
|
|
76
|
+
including running count, finished count, and average timing.
|
|
61
77
|
"""
|
|
62
78
|
def get_fxn_src_name(func, first_arg) -> str:
|
|
63
79
|
"""
|
|
@@ -77,7 +93,7 @@ def job_tracker(func):
|
|
|
77
93
|
def wrapper(*args, **kwargs):
|
|
78
94
|
"""Wrap the function to update job statistics before and after execution."""
|
|
79
95
|
class_instance = args[0]
|
|
80
|
-
job_stats =
|
|
96
|
+
job_stats = JobStats()
|
|
81
97
|
fxn = get_fxn_src_name(
|
|
82
98
|
func,
|
|
83
99
|
class_instance
|
|
@@ -112,7 +128,8 @@ def job_tracker(func):
|
|
|
112
128
|
|
|
113
129
|
def terminator(func):
|
|
114
130
|
"""
|
|
115
|
-
Decorator designed specifically for the SubnetScanner class,
|
|
131
|
+
Decorator designed specifically for the SubnetScanner class,
|
|
132
|
+
helps facilitate termination of a job.
|
|
116
133
|
"""
|
|
117
134
|
def wrapper(*args, **kwargs):
|
|
118
135
|
"""Wrap the function to check if the scan is running before execution."""
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handles device alive checks using various methods.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
import random
|
|
10
|
+
from typing import List
|
|
11
|
+
import psutil
|
|
12
|
+
|
|
13
|
+
from scapy.sendrecv import srp
|
|
14
|
+
from scapy.layers.l2 import ARP, Ether
|
|
15
|
+
from icmplib import ping
|
|
16
|
+
|
|
17
|
+
from lanscape.libraries.net_tools import Device
|
|
18
|
+
from lanscape.libraries.scan_config import (
|
|
19
|
+
ScanConfig, ScanType, PingConfig,
|
|
20
|
+
ArpConfig, PokeConfig, ArpCacheConfig
|
|
21
|
+
)
|
|
22
|
+
from lanscape.libraries.decorators import timeout_enforcer, job_tracker
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_device_alive(device: Device, scan_config: ScanConfig) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
Check if a device is alive based on the configured scan type.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
device (Device): The device to check.
|
|
31
|
+
scan_config (ScanConfig): The configuration for the scan.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
bool: True if the device is alive, False otherwise.
|
|
35
|
+
"""
|
|
36
|
+
methods = scan_config.lookup_type
|
|
37
|
+
|
|
38
|
+
if ScanType.ICMP in methods:
|
|
39
|
+
IcmpLookup.execute(device, scan_config.ping_config)
|
|
40
|
+
|
|
41
|
+
if ScanType.ARP_LOOKUP in methods and not device.alive:
|
|
42
|
+
ArpLookup.execute(device, scan_config.arp_config)
|
|
43
|
+
|
|
44
|
+
if ScanType.ICMP_THEN_ARP in methods and not device.alive:
|
|
45
|
+
IcmpLookup.execute(device, scan_config.ping_config)
|
|
46
|
+
ArpCacheLookup.execute(device, scan_config.arp_cache_config)
|
|
47
|
+
|
|
48
|
+
if ScanType.POKE_THEN_ARP in methods and not device.alive:
|
|
49
|
+
Poker.execute(device, scan_config.poke_config)
|
|
50
|
+
ArpCacheLookup.execute(device, scan_config.arp_cache_config)
|
|
51
|
+
|
|
52
|
+
return device.alive is True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class IcmpLookup():
|
|
56
|
+
"""Class to handle ICMP ping lookups for device presence.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
NotImplementedError: If the platform is not supported.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
bool: True if the device is reachable via ICMP, False otherwise.
|
|
63
|
+
"""
|
|
64
|
+
@classmethod
|
|
65
|
+
@job_tracker
|
|
66
|
+
def execute(cls, device: Device, cfg: PingConfig) -> bool:
|
|
67
|
+
"""Perform an ICMP ping lookup for the specified device.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
device (Device): The device to look up.
|
|
71
|
+
cfg (PingConfig): The configuration for the scan.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
bool: True if the device is reachable via ICMP, False otherwise.
|
|
75
|
+
"""
|
|
76
|
+
# Perform up to cfg.attempts rounds of ping(count=cfg.ping_count)
|
|
77
|
+
for _ in range(cfg.attempts):
|
|
78
|
+
result = ping(
|
|
79
|
+
device.ip,
|
|
80
|
+
count=cfg.ping_count,
|
|
81
|
+
interval=cfg.retry_delay,
|
|
82
|
+
timeout=cfg.timeout,
|
|
83
|
+
privileged=psutil.WINDOWS # Use privileged mode on Windows
|
|
84
|
+
)
|
|
85
|
+
if result.is_alive:
|
|
86
|
+
device.alive = True
|
|
87
|
+
break
|
|
88
|
+
return device.alive is True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ArpCacheLookup():
|
|
92
|
+
"""
|
|
93
|
+
Class to handle ARP cache lookups for device presence.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
@job_tracker
|
|
98
|
+
def execute(cls, device: Device, cfg: ArpCacheConfig) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
Perform an ARP cache lookup for the specified device.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
device (Device): The device to look up.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
bool: True if the device is found in the ARP cache, False otherwise.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
command = cls._get_platform_arp_command() + [device.ip]
|
|
110
|
+
|
|
111
|
+
for _ in range(cfg.attempts):
|
|
112
|
+
time.sleep(cfg.wait_before)
|
|
113
|
+
output = subprocess.check_output(command).decode()
|
|
114
|
+
macs = cls._extract_mac_address(output)
|
|
115
|
+
if macs:
|
|
116
|
+
device.macs = macs
|
|
117
|
+
device.alive = True
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
return device.alive is True
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def _get_platform_arp_command(cls) -> List[str]:
|
|
124
|
+
"""
|
|
125
|
+
Get the ARP command to execute based on the platform.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
list[str]: The ARP command to execute.
|
|
129
|
+
"""
|
|
130
|
+
if psutil.WINDOWS:
|
|
131
|
+
return ['arp', '-a']
|
|
132
|
+
if psutil.LINUX:
|
|
133
|
+
return ['arp', '-n']
|
|
134
|
+
if psutil.MACOS:
|
|
135
|
+
return ['arp', '-n']
|
|
136
|
+
|
|
137
|
+
raise NotImplementedError("Unsupported platform")
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def _extract_mac_address(cls, arp_resp: str) -> List[str]:
|
|
141
|
+
"""
|
|
142
|
+
Extract MAC addresses from ARP output.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
arp_resp (str): The ARP command output.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
List[str]: A list of extracted MAC addresses (may be empty).
|
|
149
|
+
"""
|
|
150
|
+
arp_resp = arp_resp.replace('-', ':')
|
|
151
|
+
return re.findall(r'..:..:..:..:..:..', arp_resp)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ArpLookup():
|
|
155
|
+
"""
|
|
156
|
+
Class to handle ARP lookups for device presence.
|
|
157
|
+
NOTE: This lookup method requires elevated privileges to access the ARP cache.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
[Arp Lookup Requirements](/support/arp-issues.md)
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
@job_tracker
|
|
165
|
+
def execute(cls, device: Device, cfg: ArpConfig) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Perform an ARP lookup for the specified device.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
device (Device): The device to look up.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
bool: True if the device is found via ARP, False otherwise.
|
|
174
|
+
"""
|
|
175
|
+
enforcer_timeout = cfg.timeout * 2
|
|
176
|
+
|
|
177
|
+
@timeout_enforcer(enforcer_timeout, raise_on_timeout=True)
|
|
178
|
+
def do_arp_lookup():
|
|
179
|
+
arp_request = ARP(pdst=device.ip)
|
|
180
|
+
broadcast = Ether(dst="ff:ff:ff:ff:ff:ff")
|
|
181
|
+
packet = broadcast / arp_request
|
|
182
|
+
|
|
183
|
+
answered, _ = srp(packet, timeout=cfg.timeout, verbose=False)
|
|
184
|
+
alive = any(resp.psrc == device.ip for _, resp in answered)
|
|
185
|
+
macs = []
|
|
186
|
+
if alive:
|
|
187
|
+
macs = [resp.hwsrc for _, resp in answered if resp.psrc == device.ip]
|
|
188
|
+
return alive, macs
|
|
189
|
+
|
|
190
|
+
alive, macs = do_arp_lookup()
|
|
191
|
+
if alive:
|
|
192
|
+
device.alive = True
|
|
193
|
+
device.macs = macs
|
|
194
|
+
|
|
195
|
+
return device.alive is True
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class Poker():
|
|
199
|
+
"""
|
|
200
|
+
Class to handle Poking the device to populate the ARP cache.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
@job_tracker
|
|
205
|
+
def execute(cls, device: Device, cfg: PokeConfig):
|
|
206
|
+
"""
|
|
207
|
+
Perform a Poke for the specified device.
|
|
208
|
+
Note: the purpose of this is to simply populate the arp cache.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
device (Device): The device to look up.
|
|
212
|
+
cfg (PokeConfig): The configuration for the Poke lookup.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
None: used to populate the arp cache
|
|
216
|
+
"""
|
|
217
|
+
enforcer_timeout = cfg.timeout * cfg.attempts * 2
|
|
218
|
+
|
|
219
|
+
@timeout_enforcer(enforcer_timeout, raise_on_timeout=True)
|
|
220
|
+
def do_poke():
|
|
221
|
+
for _ in range(cfg.attempts):
|
|
222
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
223
|
+
sock.settimeout(cfg.timeout)
|
|
224
|
+
sock.connect_ex((device.ip, random.randint(1024, 65535))) # port shouldn't matter
|
|
225
|
+
sock.close()
|
|
226
|
+
|
|
227
|
+
do_poke()
|
lanscape/libraries/errors.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions used by the lanscape application.
|
|
3
|
+
|
|
4
|
+
This module contains custom exception classes for handling various error cases
|
|
5
|
+
in the network scanning and device management operations.
|
|
6
|
+
"""
|
|
1
7
|
|
|
2
8
|
|
|
3
9
|
class SubnetTooLargeError(Exception):
|
|
@@ -9,12 +15,16 @@ class SubnetTooLargeError(Exception):
|
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
class SubnetScanTerminationFailure(Exception):
|
|
18
|
+
"""Exception raised when subnet scanning threads cannot be terminated properly."""
|
|
19
|
+
|
|
12
20
|
def __init__(self, running_threads):
|
|
13
21
|
super().__init__(
|
|
14
22
|
f'Unable to terminate active threads: {running_threads}')
|
|
15
23
|
|
|
16
24
|
|
|
17
25
|
class DeviceError(Exception):
|
|
26
|
+
"""Exception wrapper for device-related errors to provide context about failure source."""
|
|
27
|
+
|
|
18
28
|
def __init__(self, e: Exception):
|
|
19
29
|
self.base: Exception = e
|
|
20
30
|
self.method = self._attempt_extract_method()
|
lanscape/libraries/ip_parser.py
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IP address parsing module for network scanning operations.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for parsing various IP address formats including:
|
|
5
|
+
- Single IP addresses
|
|
6
|
+
- CIDR notation subnets
|
|
7
|
+
- IP ranges with hyphens (e.g., 192.168.1.1-192.168.1.10)
|
|
8
|
+
- Shorthand IP ranges (e.g., 192.168.1.1-10)
|
|
9
|
+
|
|
10
|
+
It also includes validation to prevent processing excessively large IP ranges.
|
|
11
|
+
"""
|
|
1
12
|
import ipaddress
|
|
2
|
-
from .errors import SubnetTooLargeError
|
|
3
13
|
import re
|
|
4
14
|
|
|
15
|
+
from lanscape.libraries.errors import SubnetTooLargeError
|
|
16
|
+
|
|
5
17
|
MAX_IPS_ALLOWED = 100000
|
|
6
18
|
|
|
7
19
|
|
|
8
20
|
def parse_ip_input(ip_input):
|
|
21
|
+
"""
|
|
22
|
+
Parse various IP address format inputs into a list of IPv4Address objects.
|
|
23
|
+
|
|
24
|
+
Supports:
|
|
25
|
+
- Comma-separated entries
|
|
26
|
+
- CIDR notation (e.g., 192.168.1.0/24)
|
|
27
|
+
- IP ranges with a hyphen (e.g., 192.168.1.1-192.168.1.10)
|
|
28
|
+
- Shorthand IP ranges (e.g., 192.168.1.1-10)
|
|
29
|
+
- Single IP addresses
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
ip_input (str): String containing IP addresses in various formats
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
list: List of IPv4Address objects
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
SubnetTooLargeError: If the number of IPs exceeds MAX_IPS_ALLOWED
|
|
39
|
+
"""
|
|
9
40
|
# Split input on commas for multiple entries
|
|
10
41
|
entries = [entry.strip() for entry in ip_input.split(',')]
|
|
11
42
|
ip_ranges = []
|
|
@@ -36,6 +67,15 @@ def parse_ip_input(ip_input):
|
|
|
36
67
|
|
|
37
68
|
|
|
38
69
|
def get_address_count(subnet: str):
|
|
70
|
+
"""
|
|
71
|
+
Get the number of addresses in an IP subnet.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
subnet (str): Subnet in CIDR notation
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
int: Number of addresses in the subnet, or 0 if invalid
|
|
78
|
+
"""
|
|
39
79
|
try:
|
|
40
80
|
net = ipaddress.IPv4Network(subnet, strict=False)
|
|
41
81
|
return net.num_addresses
|
|
@@ -44,6 +84,17 @@ def get_address_count(subnet: str):
|
|
|
44
84
|
|
|
45
85
|
|
|
46
86
|
def parse_ip_range(entry):
|
|
87
|
+
"""
|
|
88
|
+
Parse an IP range specified with a hyphen (e.g., 192.168.1.1-192.168.1.10).
|
|
89
|
+
|
|
90
|
+
Also handles partial end IPs by using the start IP's prefix.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
entry (str): String containing an IP range with a hyphen
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
list: List of IPv4Address objects in the range (inclusive)
|
|
97
|
+
"""
|
|
47
98
|
start_ip, end_ip = entry.split('-')
|
|
48
99
|
start_ip = ipaddress.IPv4Address(start_ip.strip())
|
|
49
100
|
|
|
@@ -56,6 +107,17 @@ def parse_ip_range(entry):
|
|
|
56
107
|
|
|
57
108
|
|
|
58
109
|
def parse_shorthand_ip_range(entry):
|
|
110
|
+
"""
|
|
111
|
+
Parse a shorthand IP range (e.g., 192.168.1.1-10).
|
|
112
|
+
|
|
113
|
+
In this format, only the last octet of the end IP is specified.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
entry (str): String containing a shorthand IP range
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
list: List of IPv4Address objects in the range (inclusive)
|
|
120
|
+
"""
|
|
59
121
|
start_ip, end_part = entry.split('-')
|
|
60
122
|
start_ip = ipaddress.IPv4Address(start_ip.strip())
|
|
61
123
|
end_ip = start_ip.exploded.rsplit('.', 1)[0] + '.' + end_part.strip()
|
|
@@ -64,6 +126,16 @@ def parse_shorthand_ip_range(entry):
|
|
|
64
126
|
|
|
65
127
|
|
|
66
128
|
def ip_range_to_list(start_ip, end_ip):
|
|
129
|
+
"""
|
|
130
|
+
Convert an IP range defined by start and end addresses to a list of addresses.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
start_ip (IPv4Address): The starting IP address
|
|
134
|
+
end_ip (IPv4Address): The ending IP address
|
|
135
|
+
|
|
136
|
+
Yields:
|
|
137
|
+
IPv4Address: Each IP address in the range (inclusive)
|
|
138
|
+
"""
|
|
67
139
|
# Yield the range of IPs
|
|
68
140
|
for ip_int in range(int(start_ip), int(end_ip) + 1):
|
|
69
141
|
yield ipaddress.IPv4Address(ip_int)
|
lanscape/libraries/logger.py
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging configuration module for the lanscape application.
|
|
3
|
+
|
|
4
|
+
This module provides utilities to configure logging for both console and file output,
|
|
5
|
+
with options to control log levels and disable Flask's verbose logging output.
|
|
6
|
+
"""
|
|
1
7
|
import logging
|
|
2
8
|
from logging.handlers import RotatingFileHandler
|
|
3
|
-
import click
|
|
4
9
|
from typing import Optional
|
|
5
10
|
|
|
11
|
+
import click
|
|
12
|
+
|
|
6
13
|
|
|
7
14
|
def configure_logging(loglevel: str, logfile: Optional[str], flask_logging: bool = False) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Configure the application's logging system.
|
|
17
|
+
|
|
18
|
+
Sets up logging with the specified log level and optionally directs output to a file.
|
|
19
|
+
When a logfile is specified, rotating file handlers are configured to manage log size.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
loglevel (str): Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
23
|
+
logfile (Optional[str]): Path to log file, or None for console-only logging
|
|
24
|
+
flask_logging (bool): Whether to allow Flask's default logging (defaults to False)
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ValueError: If an invalid log level is specified
|
|
28
|
+
"""
|
|
8
29
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
|
9
30
|
if not isinstance(numeric_level, int):
|
|
10
31
|
raise ValueError(f'Invalid log level: {loglevel}')
|
|
@@ -30,10 +51,17 @@ def configure_logging(loglevel: str, logfile: Optional[str], flask_logging: bool
|
|
|
30
51
|
|
|
31
52
|
|
|
32
53
|
def disable_flask_logging() -> None:
|
|
54
|
+
"""
|
|
55
|
+
Disable Flask and Werkzeug logging output.
|
|
33
56
|
|
|
57
|
+
Overrides click's echo and secho functions to suppress output and
|
|
58
|
+
sets Werkzeug's logger level to ERROR to reduce log verbosity.
|
|
59
|
+
"""
|
|
34
60
|
def override_click_logging():
|
|
61
|
+
# pylint: disable=unused-argument
|
|
35
62
|
def secho(text, file=None, nl=None, err=None, color=None, **styles):
|
|
36
63
|
pass
|
|
64
|
+
# pylint: disable=unused-argument
|
|
37
65
|
|
|
38
66
|
def echo(text, file=None, nl=None, err=None, color=None, **styles):
|
|
39
67
|
pass
|