lanscape 1.4.4__py3-none-any.whl → 2.0.0a1__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.
- lanscape/__init__.py +9 -4
- lanscape/__main__.py +1 -0
- lanscape/{libraries → core}/app_scope.py +22 -3
- lanscape/{libraries → core}/decorators.py +88 -52
- lanscape/{libraries → core}/device_alive.py +4 -3
- lanscape/{libraries → core}/errors.py +1 -0
- lanscape/{libraries → core}/ip_parser.py +2 -1
- lanscape/{libraries → core}/logger.py +1 -0
- lanscape/{libraries → core}/mac_lookup.py +1 -0
- lanscape/{libraries → core}/net_tools.py +140 -46
- lanscape/{libraries → core}/port_manager.py +1 -0
- lanscape/{libraries → core}/runtime_args.py +1 -0
- lanscape/{libraries → core}/scan_config.py +104 -5
- lanscape/core/service_scan.py +205 -0
- lanscape/{libraries → core}/subnet_scan.py +19 -11
- lanscape/{libraries → core}/version_manager.py +3 -2
- lanscape/{libraries → core}/web_browser.py +1 -0
- lanscape/resources/mac_addresses/convert_csv.py +1 -0
- lanscape/resources/ports/convert_csv.py +1 -0
- lanscape/resources/services/definitions.jsonc +576 -400
- lanscape/ui/app.py +5 -4
- lanscape/ui/blueprints/__init__.py +2 -1
- lanscape/ui/blueprints/api/__init__.py +1 -0
- lanscape/ui/blueprints/api/port.py +2 -1
- lanscape/ui/blueprints/api/scan.py +2 -1
- lanscape/ui/blueprints/api/tools.py +5 -4
- lanscape/ui/blueprints/web/__init__.py +1 -0
- lanscape/ui/blueprints/web/routes.py +30 -2
- lanscape/ui/main.py +5 -4
- lanscape/ui/shutdown_handler.py +2 -1
- lanscape/ui/static/css/style.css +145 -2
- lanscape/ui/static/js/main.js +30 -2
- lanscape/ui/static/js/scan-config.js +39 -0
- lanscape/ui/templates/scan/config.html +43 -0
- lanscape/ui/templates/scan/device-detail.html +111 -0
- lanscape/ui/templates/scan/ip-table-row.html +12 -78
- lanscape/ui/templates/scan/ip-table.html +1 -1
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/METADATA +7 -2
- lanscape-2.0.0a1.dist-info/RECORD +76 -0
- lanscape-2.0.0a1.dist-info/entry_points.txt +2 -0
- lanscape/libraries/service_scan.py +0 -50
- lanscape-1.4.4.dist-info/RECORD +0 -74
- /lanscape/{libraries → core}/__init__.py +0 -0
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/WHEEL +0 -0
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -11,8 +11,8 @@ from enum import Enum
|
|
|
11
11
|
|
|
12
12
|
from pydantic import BaseModel, Field
|
|
13
13
|
|
|
14
|
-
from lanscape.
|
|
15
|
-
from lanscape.
|
|
14
|
+
from lanscape.core.port_manager import PortManager
|
|
15
|
+
from lanscape.core.ip_parser import parse_ip_input
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class PingConfig(BaseModel):
|
|
@@ -154,6 +154,87 @@ class PokeConfig(BaseModel):
|
|
|
154
154
|
return self.model_dump()
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
class ServiceScanStrategy(Enum):
|
|
158
|
+
"""
|
|
159
|
+
Enumeration of strategies for service scanning on open ports.
|
|
160
|
+
|
|
161
|
+
LAZY: Several common probes to see if we can identify the service.
|
|
162
|
+
BASIC: Common probes plus probes correlated to the port number.
|
|
163
|
+
AGGRESSIVE: All known probes in parallel to try to elicit a response.
|
|
164
|
+
"""
|
|
165
|
+
LAZY = 'LAZY'
|
|
166
|
+
BASIC = 'BASIC'
|
|
167
|
+
AGGRESSIVE = 'AGGRESSIVE'
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ServiceScanConfig(BaseModel):
|
|
171
|
+
"""
|
|
172
|
+
Configuration for service scanning on open ports.
|
|
173
|
+
"""
|
|
174
|
+
timeout: float = 5.0
|
|
175
|
+
lookup_type: ServiceScanStrategy = ServiceScanStrategy.BASIC
|
|
176
|
+
max_concurrent_probes: int = 10
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def from_dict(cls, data: dict) -> 'ServiceScanConfig':
|
|
180
|
+
"""
|
|
181
|
+
Create a ServiceScanConfig instance from a dictionary.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
data: Dictionary containing ServiceScanConfig parameters
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
A new ServiceScanConfig instance with the provided settings
|
|
188
|
+
"""
|
|
189
|
+
return cls.model_validate(data)
|
|
190
|
+
|
|
191
|
+
def to_dict(self) -> dict:
|
|
192
|
+
"""
|
|
193
|
+
Convert the ServiceScanConfig instance to a dictionary.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Dictionary representation of the ServiceScanConfig
|
|
197
|
+
"""
|
|
198
|
+
return self.model_dump()
|
|
199
|
+
|
|
200
|
+
def __str__(self):
|
|
201
|
+
return f'ServiceScanCfg(timeout={self.timeout})'
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PortScanConfig(BaseModel):
|
|
205
|
+
"""
|
|
206
|
+
Configuration for port scanning.
|
|
207
|
+
"""
|
|
208
|
+
timeout: float = 1.0
|
|
209
|
+
retries: int = 0
|
|
210
|
+
retry_delay: float = 0.1
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def from_dict(cls, data: dict) -> 'PortScanConfig':
|
|
214
|
+
"""
|
|
215
|
+
Create a PortScanConfig instance from a dictionary.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
data: Dictionary containing PortScanConfig parameters
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
A new PortScanConfig instance with the provided settings
|
|
222
|
+
"""
|
|
223
|
+
return cls.model_validate(data)
|
|
224
|
+
|
|
225
|
+
def to_dict(self) -> dict:
|
|
226
|
+
"""
|
|
227
|
+
Convert the PortScanConfig instance to a dictionary.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Dictionary representation of the PortScanConfig
|
|
231
|
+
"""
|
|
232
|
+
return self.model_dump()
|
|
233
|
+
|
|
234
|
+
def __str__(self):
|
|
235
|
+
return f'PortScanCfg(timeout={self.timeout}, retry_delay={self.retry_delay})'
|
|
236
|
+
|
|
237
|
+
|
|
157
238
|
class ScanType(Enum):
|
|
158
239
|
"""
|
|
159
240
|
Enumeration of supported network scan types.
|
|
@@ -184,7 +265,7 @@ class ScanConfig(BaseModel):
|
|
|
184
265
|
|
|
185
266
|
task_scan_ports: bool = True
|
|
186
267
|
# below wont run if above false
|
|
187
|
-
task_scan_port_services: bool =
|
|
268
|
+
task_scan_port_services: bool = True
|
|
188
269
|
|
|
189
270
|
lookup_type: List[ScanType] = [ScanType.ICMP_THEN_ARP]
|
|
190
271
|
|
|
@@ -192,6 +273,8 @@ class ScanConfig(BaseModel):
|
|
|
192
273
|
arp_config: ArpConfig = Field(default_factory=ArpConfig)
|
|
193
274
|
poke_config: PokeConfig = Field(default_factory=PokeConfig)
|
|
194
275
|
arp_cache_config: ArpCacheConfig = Field(default_factory=ArpCacheConfig)
|
|
276
|
+
port_scan_config: PortScanConfig = Field(default_factory=PortScanConfig)
|
|
277
|
+
service_scan_config: ServiceScanConfig = Field(default_factory=ServiceScanConfig)
|
|
195
278
|
|
|
196
279
|
def t_cnt(self, thread_id: str) -> int:
|
|
197
280
|
"""
|
|
@@ -259,7 +342,7 @@ DEFAULT_CONFIGS: Dict[str, ScanConfig] = {
|
|
|
259
342
|
t_cnt_port_test=64,
|
|
260
343
|
t_cnt_isalive=64,
|
|
261
344
|
task_scan_ports=True,
|
|
262
|
-
task_scan_port_services=
|
|
345
|
+
task_scan_port_services=True,
|
|
263
346
|
lookup_type=[ScanType.ICMP_THEN_ARP, ScanType.ARP_LOOKUP],
|
|
264
347
|
arp_config=ArpConfig(
|
|
265
348
|
attempts=3,
|
|
@@ -274,6 +357,16 @@ DEFAULT_CONFIGS: Dict[str, ScanConfig] = {
|
|
|
274
357
|
arp_cache_config=ArpCacheConfig(
|
|
275
358
|
attempts=2,
|
|
276
359
|
wait_before=0.3
|
|
360
|
+
),
|
|
361
|
+
port_scan_config=PortScanConfig(
|
|
362
|
+
timeout=2.5,
|
|
363
|
+
retries=1,
|
|
364
|
+
retry_delay=0.2
|
|
365
|
+
),
|
|
366
|
+
service_scan_config=ServiceScanConfig(
|
|
367
|
+
timeout=8.0,
|
|
368
|
+
lookup_type=ServiceScanStrategy.AGGRESSIVE,
|
|
369
|
+
max_concurrent_probes=5
|
|
277
370
|
)
|
|
278
371
|
),
|
|
279
372
|
'fast': ScanConfig(
|
|
@@ -283,7 +376,7 @@ DEFAULT_CONFIGS: Dict[str, ScanConfig] = {
|
|
|
283
376
|
t_cnt_port_test=256,
|
|
284
377
|
t_cnt_isalive=512,
|
|
285
378
|
task_scan_ports=True,
|
|
286
|
-
task_scan_port_services=
|
|
379
|
+
task_scan_port_services=True,
|
|
287
380
|
lookup_type=[ScanType.POKE_THEN_ARP],
|
|
288
381
|
arp_config=ArpConfig(
|
|
289
382
|
attempts=1,
|
|
@@ -294,6 +387,12 @@ DEFAULT_CONFIGS: Dict[str, ScanConfig] = {
|
|
|
294
387
|
ping_count=1,
|
|
295
388
|
timeout=0.5,
|
|
296
389
|
retry_delay=0.25
|
|
390
|
+
),
|
|
391
|
+
service_scan_config=ServiceScanConfig(
|
|
392
|
+
timeout=2.0,
|
|
393
|
+
lookup_type=ServiceScanStrategy.LAZY,
|
|
394
|
+
max_concurrent_probes=15
|
|
297
395
|
)
|
|
298
396
|
)
|
|
299
397
|
}
|
|
398
|
+
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Service scanning module for identifying services running on network ports.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
import sys
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import traceback
|
|
9
|
+
|
|
10
|
+
from lanscape.core.app_scope import ResourceManager
|
|
11
|
+
from lanscape.core.scan_config import ServiceScanConfig, ServiceScanStrategy
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger('ServiceScan')
|
|
14
|
+
SERVICES = ResourceManager('services').get_jsonc('definitions.jsonc')
|
|
15
|
+
|
|
16
|
+
# skip printer ports because they cause blank pages to be printed
|
|
17
|
+
PRINTER_PORTS = [9100, 631]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def _try_probe(
|
|
21
|
+
ip: str,
|
|
22
|
+
port: int,
|
|
23
|
+
payload: Optional[Union[str, bytes]] = None,
|
|
24
|
+
*,
|
|
25
|
+
timeout: float = 5.0,
|
|
26
|
+
read_len: int = 1024,
|
|
27
|
+
) -> Optional[str]:
|
|
28
|
+
"""
|
|
29
|
+
Open a connection, optionally send a payload, and read a single response chunk.
|
|
30
|
+
Returns the decoded response string or None.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
reader, writer = await asyncio.wait_for(
|
|
34
|
+
asyncio.open_connection(ip, port), timeout=timeout
|
|
35
|
+
)
|
|
36
|
+
try:
|
|
37
|
+
if payload is not None:
|
|
38
|
+
data = payload if isinstance(
|
|
39
|
+
payload, (bytes, bytearray)) else str(payload).encode(
|
|
40
|
+
"utf-8", errors="ignore")
|
|
41
|
+
writer.write(data)
|
|
42
|
+
await writer.drain()
|
|
43
|
+
try:
|
|
44
|
+
response = await asyncio.wait_for(reader.read(read_len), timeout=timeout / 2)
|
|
45
|
+
except asyncio.TimeoutError:
|
|
46
|
+
response = b""
|
|
47
|
+
resp_str = response.decode("utf-8", errors="ignore") if response else ""
|
|
48
|
+
return resp_str if resp_str else None
|
|
49
|
+
finally:
|
|
50
|
+
# Guarded close to avoid surfacing connection-lost noise
|
|
51
|
+
try:
|
|
52
|
+
writer.close()
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
try:
|
|
56
|
+
await asyncio.wait_for(writer.wait_closed(), timeout=0.5)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
except Exception as e:
|
|
60
|
+
# Suppress common/expected network errors that simply indicate no useful banner
|
|
61
|
+
expected_types = (ConnectionResetError, ConnectionRefusedError, TimeoutError, OSError)
|
|
62
|
+
expected_errnos = {10054, 10061, 10060} # reset, refused, timeout (Win specific)
|
|
63
|
+
eno = getattr(e, 'errno', None)
|
|
64
|
+
if isinstance(e, expected_types) and (eno in expected_errnos or eno is None):
|
|
65
|
+
return None
|
|
66
|
+
log.debug(f"Probe error on {ip}:{port} - {repr(e)}")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def _multi_probe_generic(
|
|
71
|
+
ip: str, port: int, cfg: ServiceScanConfig
|
|
72
|
+
) -> Optional[str]:
|
|
73
|
+
"""
|
|
74
|
+
Run a small set of generic probes in parallel and return the first non-empty response.
|
|
75
|
+
"""
|
|
76
|
+
probes = get_port_probes(port, cfg.lookup_type)
|
|
77
|
+
|
|
78
|
+
semaphore = asyncio.Semaphore(cfg.max_concurrent_probes)
|
|
79
|
+
|
|
80
|
+
async def limited_probe(ip, port, payload, timeout_val):
|
|
81
|
+
async with semaphore:
|
|
82
|
+
return await _try_probe(
|
|
83
|
+
ip, port, payload,
|
|
84
|
+
timeout=timeout_val
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
tasks = [
|
|
88
|
+
asyncio.create_task(
|
|
89
|
+
limited_probe(ip, port, p, cfg.timeout)
|
|
90
|
+
)
|
|
91
|
+
for p in probes
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
for fut in asyncio.as_completed(tasks, timeout=cfg.timeout):
|
|
96
|
+
try:
|
|
97
|
+
resp = await fut
|
|
98
|
+
except Exception:
|
|
99
|
+
resp = None
|
|
100
|
+
if resp and resp.strip():
|
|
101
|
+
# Cancel remaining tasks
|
|
102
|
+
for t in tasks:
|
|
103
|
+
if not t.done():
|
|
104
|
+
t.cancel()
|
|
105
|
+
return resp
|
|
106
|
+
except asyncio.TimeoutError:
|
|
107
|
+
pass
|
|
108
|
+
finally:
|
|
109
|
+
# Ensure remaining tasks are cancelled and awaited to suppress warnings
|
|
110
|
+
for t in tasks:
|
|
111
|
+
if not t.done():
|
|
112
|
+
t.cancel()
|
|
113
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
114
|
+
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_port_probes(port: int, strategy: ServiceScanStrategy):
|
|
119
|
+
"""
|
|
120
|
+
Return a list of probe payloads based on the port and strategy.
|
|
121
|
+
"""
|
|
122
|
+
# For now, we use generic probes for all ports.
|
|
123
|
+
# This can be extended to use specific probes per port/service.
|
|
124
|
+
|
|
125
|
+
probes = [
|
|
126
|
+
None, # banner-first protocols (SSH/FTP/SMTP/etc.)
|
|
127
|
+
b"\r\n", # nudge for many line-oriented services
|
|
128
|
+
b"HELP\r\n", # sometimes yields usage/help (SMTP/POP/IMAP-ish)
|
|
129
|
+
b"OPTIONS * HTTP/1.0\r\n\r\n", # elicit Server header without path
|
|
130
|
+
b"HEAD / HTTP/1.0\r\n\r\n", # basic HTTP
|
|
131
|
+
b"QUIT\r\n", # graceful close if understood
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if strategy == ServiceScanStrategy.LAZY:
|
|
135
|
+
return probes
|
|
136
|
+
|
|
137
|
+
if strategy == ServiceScanStrategy.BASIC:
|
|
138
|
+
for _, detail in SERVICES.items():
|
|
139
|
+
if port in detail.get("ports", []):
|
|
140
|
+
if probe := detail.get("probe", ''):
|
|
141
|
+
probes.append(probe)
|
|
142
|
+
return probes
|
|
143
|
+
|
|
144
|
+
if strategy == ServiceScanStrategy.AGGRESSIVE:
|
|
145
|
+
for _, detail in SERVICES.items():
|
|
146
|
+
if probe := detail.get("probe", ''):
|
|
147
|
+
probes.append(probe)
|
|
148
|
+
return probes
|
|
149
|
+
|
|
150
|
+
return [None] # Default to banner grab only
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def scan_service(ip: str, port: int, cfg: ServiceScanConfig) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Synchronous function that attempts to identify the service running on a given port.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
async def _async_scan_service(
|
|
159
|
+
ip: str, port: int,
|
|
160
|
+
cfg: ServiceScanConfig
|
|
161
|
+
) -> str:
|
|
162
|
+
if port in PRINTER_PORTS:
|
|
163
|
+
return "Printer"
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Run multiple generic probes concurrently and take first useful response
|
|
167
|
+
response_str = await _multi_probe_generic(ip, port, cfg)
|
|
168
|
+
if not response_str:
|
|
169
|
+
return "Unknown"
|
|
170
|
+
|
|
171
|
+
log.debug(f"Service scan response from {ip}:{port} - {response_str}")
|
|
172
|
+
|
|
173
|
+
# Analyze the response to identify the service
|
|
174
|
+
for service, config in SERVICES.items():
|
|
175
|
+
if any(hint.lower() in response_str.lower() for hint in config.get("hints", [])):
|
|
176
|
+
return service
|
|
177
|
+
except asyncio.TimeoutError:
|
|
178
|
+
log.warning(f"Timeout scanning {ip}:{port}")
|
|
179
|
+
except Exception as e:
|
|
180
|
+
log.error(f"Error scanning {ip}:{port}: {str(e)}")
|
|
181
|
+
log.debug(traceback.format_exc())
|
|
182
|
+
return "Unknown"
|
|
183
|
+
|
|
184
|
+
# Use asyncio.run to execute the asynchronous logic synchronously
|
|
185
|
+
return asyncio.run(_async_scan_service(ip, port, cfg=cfg))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def asyncio_logger_suppression():
|
|
189
|
+
"""Suppress the noisy asyncio transport errors since they are expected in service scanning."""
|
|
190
|
+
|
|
191
|
+
# Reduce noisy asyncio transport errors on Windows by switching to Selector policy
|
|
192
|
+
if sys.platform.startswith("win"):
|
|
193
|
+
try:
|
|
194
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
195
|
+
except Exception:
|
|
196
|
+
pass
|
|
197
|
+
# Also tone down asyncio logger noise from transport callbacks
|
|
198
|
+
try:
|
|
199
|
+
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
asyncio_logger_suppression()
|
|
205
|
+
|
|
@@ -20,11 +20,11 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
20
20
|
from tabulate import tabulate
|
|
21
21
|
|
|
22
22
|
# Local imports
|
|
23
|
-
from lanscape.
|
|
24
|
-
from lanscape.
|
|
25
|
-
from lanscape.
|
|
26
|
-
from lanscape.
|
|
27
|
-
from lanscape.
|
|
23
|
+
from lanscape.core.scan_config import ScanConfig
|
|
24
|
+
from lanscape.core.decorators import job_tracker, terminator, JobStats
|
|
25
|
+
from lanscape.core.net_tools import Device
|
|
26
|
+
from lanscape.core.errors import SubnetScanTerminationFailure
|
|
27
|
+
from lanscape.core.device_alive import is_device_alive
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class SubnetScanner():
|
|
@@ -62,7 +62,10 @@ class SubnetScanner():
|
|
|
62
62
|
"""
|
|
63
63
|
self._set_stage('scanning devices')
|
|
64
64
|
self.running = True
|
|
65
|
-
with ThreadPoolExecutor(
|
|
65
|
+
with ThreadPoolExecutor(
|
|
66
|
+
max_workers=self.cfg.t_cnt('isalive'),
|
|
67
|
+
thread_name_prefix="DeviceAlive") as executor:
|
|
68
|
+
|
|
66
69
|
futures = {executor.submit(self._get_host_details, str(
|
|
67
70
|
ip)): str(ip) for ip in self.subnet}
|
|
68
71
|
for future in as_completed(futures):
|
|
@@ -187,7 +190,7 @@ class SubnetScanner():
|
|
|
187
190
|
"""
|
|
188
191
|
Get the MAC address and open ports of the given host.
|
|
189
192
|
"""
|
|
190
|
-
device = Device(host)
|
|
193
|
+
device = Device(ip=host)
|
|
191
194
|
device.alive = self._ping(device)
|
|
192
195
|
self.results.scanned()
|
|
193
196
|
if not device.alive:
|
|
@@ -199,7 +202,8 @@ class SubnetScanner():
|
|
|
199
202
|
|
|
200
203
|
@terminator
|
|
201
204
|
def _scan_network_ports(self):
|
|
202
|
-
with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_scan')
|
|
205
|
+
with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_scan'),
|
|
206
|
+
thread_name_prefix="DevicePortScanParent") as executor:
|
|
203
207
|
futures = {executor.submit(
|
|
204
208
|
self._scan_ports, device): device for device in self.results.devices}
|
|
205
209
|
for future in futures:
|
|
@@ -210,7 +214,9 @@ class SubnetScanner():
|
|
|
210
214
|
def _scan_ports(self, device: Device):
|
|
211
215
|
self.log.debug(f'[{device.ip}] Initiating port scan')
|
|
212
216
|
device.stage = 'scanning'
|
|
213
|
-
with ThreadPoolExecutor(
|
|
217
|
+
with ThreadPoolExecutor(
|
|
218
|
+
max_workers=self.cfg.t_cnt('port_test'),
|
|
219
|
+
thread_name_prefix=f"{device.ip}-PortScan") as executor:
|
|
214
220
|
futures = {executor.submit(self._test_port, device, int(
|
|
215
221
|
port)): port for port in self.ports}
|
|
216
222
|
for future in futures:
|
|
@@ -226,9 +232,9 @@ class SubnetScanner():
|
|
|
226
232
|
If port open, determine service.
|
|
227
233
|
Device class handles tracking open ports.
|
|
228
234
|
"""
|
|
229
|
-
is_alive = host.test_port(port)
|
|
235
|
+
is_alive = host.test_port(port, self.cfg.port_scan_config)
|
|
230
236
|
if is_alive and self.cfg.task_scan_port_services:
|
|
231
|
-
host.scan_service(port)
|
|
237
|
+
host.scan_service(port, self.cfg.service_scan_config)
|
|
232
238
|
return is_alive
|
|
233
239
|
|
|
234
240
|
@terminator
|
|
@@ -264,6 +270,7 @@ class ScannerResults:
|
|
|
264
270
|
# Scan statistics
|
|
265
271
|
self.devices_total: int = len(list(scan.subnet))
|
|
266
272
|
self.devices_scanned: int = 0
|
|
273
|
+
self.port_list_length: int = len(scan.ports)
|
|
267
274
|
self.devices: List[Device] = []
|
|
268
275
|
|
|
269
276
|
# Status tracking
|
|
@@ -421,3 +428,4 @@ class ScanManager:
|
|
|
421
428
|
t = threading.Thread(target=scan.start)
|
|
422
429
|
t.start()
|
|
423
430
|
return t
|
|
431
|
+
|
|
@@ -11,8 +11,8 @@ from random import randint
|
|
|
11
11
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
14
|
-
from lanscape.
|
|
15
|
-
from lanscape.
|
|
14
|
+
from lanscape.core.app_scope import is_local_run
|
|
15
|
+
from lanscape.core.decorators import run_once
|
|
16
16
|
|
|
17
17
|
log = logging.getLogger('VersionManager')
|
|
18
18
|
|
|
@@ -95,3 +95,4 @@ def get_installed_version(package=PACKAGE):
|
|
|
95
95
|
log.debug(traceback.format_exc())
|
|
96
96
|
log.warning(f'Cannot find {package} installation')
|
|
97
97
|
return LOCAL_VERSION
|
|
98
|
+
|