lanscape 2.1.0b1__py3-none-any.whl → 2.1.2__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.
@@ -12,8 +12,9 @@ import psutil
12
12
  from scapy.sendrecv import srp
13
13
  from scapy.layers.l2 import ARP, Ether
14
14
  from icmplib import ping
15
+ from icmplib.exceptions import SocketPermissionError
15
16
 
16
- from lanscape.core.net_tools import Device
17
+ from lanscape.core.net_tools import Device, DeviceError
17
18
  from lanscape.core.scan_config import (
18
19
  ScanConfig, ScanType, PingConfig,
19
20
  ArpConfig, PokeConfig, ArpCacheConfig
@@ -72,18 +73,84 @@ class IcmpLookup():
72
73
  Returns:
73
74
  bool: True if the device is reachable via ICMP, False otherwise.
74
75
  """
75
- # Perform up to cfg.attempts rounds of ping(count=cfg.ping_count)
76
- for _ in range(cfg.attempts):
77
- result = ping(
78
- device.ip,
79
- count=cfg.ping_count,
80
- interval=cfg.retry_delay,
81
- timeout=cfg.timeout,
82
- privileged=psutil.WINDOWS # Use privileged mode on Windows
83
- )
84
- if result.is_alive:
85
- device.alive = True
86
- break
76
+ try:
77
+ # Try using icmplib first
78
+ for _ in range(cfg.attempts):
79
+ result = ping(
80
+ device.ip,
81
+ count=cfg.ping_count,
82
+ interval=cfg.retry_delay,
83
+ timeout=cfg.timeout,
84
+ privileged=psutil.WINDOWS # Use privileged mode on Windows
85
+ )
86
+ if result.is_alive:
87
+ device.alive = True
88
+ break
89
+ return device.alive is True
90
+ except SocketPermissionError:
91
+ # Fallback to system ping command when raw sockets aren't available
92
+ return cls._ping_fallback(device, cfg)
93
+
94
+ @classmethod
95
+ def _ping_fallback(cls, device: Device, cfg: PingConfig) -> bool:
96
+ """Fallback ping using system ping command via subprocess.
97
+
98
+ Args:
99
+ device (Device): The device to ping.
100
+ cfg (PingConfig): The ping configuration.
101
+
102
+ Returns:
103
+ bool: True if the device responds to ping, False otherwise.
104
+ """
105
+ cmd = []
106
+
107
+ if psutil.WINDOWS:
108
+ # -n count, -w timeout in ms
109
+ cmd = ['ping', '-n', str(cfg.ping_count), '-w', str(int(cfg.timeout * 1000)), device.ip]
110
+ else: # Linux, macOS, and other Unix-like systems
111
+ # -c count, -W timeout in s
112
+ cmd = ['ping', '-c', str(cfg.ping_count), '-W', str(int(cfg.timeout)), device.ip]
113
+
114
+ for r in range(cfg.attempts):
115
+ try:
116
+ # Remove check=True to handle return codes manually
117
+ # Add timeout to prevent hanging
118
+ timeout_val = cfg.timeout * cfg.ping_count + 5
119
+ proc = subprocess.run(
120
+ cmd,
121
+ text=True,
122
+ stdout=subprocess.PIPE,
123
+ stderr=subprocess.PIPE,
124
+ timeout=timeout_val,
125
+ check=False # Handle return codes manually
126
+ )
127
+
128
+ # Check if ping was successful
129
+ if proc.returncode == 0:
130
+ output = proc.stdout.lower()
131
+
132
+ # Windows/Linux both include "TTL" on a successful reply
133
+ if psutil.WINDOWS or psutil.LINUX:
134
+ if 'ttl' in output:
135
+ device.alive = True
136
+ return True # Early return on success
137
+
138
+ # some distributions of Linux and macOS
139
+ if psutil.MACOS or psutil.LINUX:
140
+ bad = '100.0% packet loss'
141
+ good = 'ping statistics'
142
+ # mac doesnt include TTL, so we check good is there, and bad is not
143
+ if good in output and bad not in output:
144
+ device.alive = True
145
+ return True # Early return on success
146
+
147
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired,
148
+ FileNotFoundError) as e:
149
+ device.caught_errors.append(DeviceError(e))
150
+
151
+ if r < cfg.attempts - 1:
152
+ time.sleep(cfg.retry_delay)
153
+
87
154
  return device.alive is True
88
155
 
89
156
 
@@ -31,10 +31,10 @@ else:
31
31
 
32
32
  from lanscape.core.service_scan import scan_service
33
33
  from lanscape.core.mac_lookup import MacLookup, get_macs
34
- from lanscape.core.ip_parser import get_address_count, MAX_IPS_ALLOWED
34
+ from lanscape.core.ip_parser import get_address_count, MAX_IPS_ALLOWED, parse_ip_input
35
35
  from lanscape.core.errors import DeviceError
36
36
  from lanscape.core.decorators import job_tracker, run_once, timeout_enforcer
37
- from lanscape.core.scan_config import ServiceScanConfig, PortScanConfig
37
+ from lanscape.core.scan_config import ServiceScanConfig, PortScanConfig, ScanType
38
38
 
39
39
  log = logging.getLogger('NetTools')
40
40
  mac_lookup = MacLookup()
@@ -552,6 +552,56 @@ def smart_select_primary_subnet(subnets: List[dict] = None) -> str:
552
552
  return selected.get("subnet", "")
553
553
 
554
554
 
555
+ def is_internal_block(subnet: str) -> bool:
556
+ """
557
+ Check if a subnet contains only internal/private IP addresses.
558
+
559
+ Supports CIDR notation, IP ranges, comma-separated lists, and single IPs.
560
+ For ranges and complex inputs, samples representative IPs for efficiency.
561
+
562
+ Args:
563
+ subnet: IP subnet string in various formats
564
+
565
+ Returns:
566
+ bool: True if all sampled IPs are private/internal, False otherwise
567
+ """
568
+ try:
569
+ # Handle comma-separated subnets recursively
570
+ if ',' in subnet:
571
+ return all(is_internal_block(part.strip()) for part in subnet.split(','))
572
+
573
+ # Handle CIDR notation directly
574
+ if '/' in subnet:
575
+ return ipaddress.IPv4Network(subnet, strict=False).is_private
576
+
577
+ # Handle ranges and single IPs by parsing and sampling
578
+ ip_list = parse_ip_input(subnet)
579
+ sample_ips = ([ip_list[0], ip_list[-1]] if len(ip_list) > 1 else ip_list)
580
+ return all(ipaddress.IPv4Address(ip).is_private for ip in sample_ips)
581
+
582
+ except (ValueError, ipaddress.AddressValueError):
583
+ return False # Assume external for unparseable input
584
+
585
+
586
+ def scan_config_uses_arp(config) -> bool:
587
+ """
588
+ Check if a scan configuration uses ARP-based scanning methods.
589
+
590
+ Args:
591
+ config: ScanConfig instance
592
+
593
+ Returns:
594
+ bool: True if the configuration uses ARP scanning, False otherwise
595
+ """
596
+ arp_scan_types = {
597
+ ScanType.ARP_LOOKUP,
598
+ ScanType.POKE_THEN_ARP,
599
+ ScanType.ICMP_THEN_ARP
600
+ }
601
+
602
+ return any(scan_type in arp_scan_types for scan_type in config.lookup_type)
603
+
604
+
555
605
  @run_once
556
606
  def is_arp_supported():
557
607
  """
@@ -22,7 +22,9 @@ from tabulate import tabulate
22
22
  # Local imports
23
23
  from lanscape.core.scan_config import ScanConfig
24
24
  from lanscape.core.decorators import job_tracker, terminator, JobStats
25
- from lanscape.core.net_tools import Device
25
+ from lanscape.core.net_tools import (
26
+ Device, is_internal_block, scan_config_uses_arp
27
+ )
26
28
  from lanscape.core.errors import SubnetScanTerminationFailure
27
29
  from lanscape.core.device_alive import is_device_alive
28
30
 
@@ -301,11 +303,11 @@ class ScannerResults:
301
303
  Calculate the runtime of the scan in seconds.
302
304
 
303
305
  Returns:
304
- int: Runtime in seconds
306
+ float: Runtime in seconds
305
307
  """
306
308
  if self.scan.running:
307
- return int(time() - self.start_time)
308
- return int(self.end_time - self.start_time)
309
+ return time() - self.start_time
310
+ return self.end_time - self.start_time
309
311
 
310
312
  def export(self, out_type=dict) -> Union[str, dict]:
311
313
  """
@@ -385,6 +387,13 @@ class ScanManager:
385
387
  Returns:
386
388
  SubnetScanner: The newly created scan instance
387
389
  """
390
+ if not is_internal_block(config.subnet) and scan_config_uses_arp(config):
391
+ self.log.warning(
392
+ f"ARP scanning detected for external subnet '{config.subnet}'. "
393
+ "ARP requests typically only work within the local network segment. "
394
+ "Consider using ICMP scanning for external IP ranges."
395
+ )
396
+
388
397
  scan = SubnetScanner(config)
389
398
  self._start(scan)
390
399
  self.log.info(f'Scan started - {config}')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 2.1.0b1
3
+ Version: 2.1.2
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  License-Expression: MIT
@@ -28,6 +28,8 @@ Requires-Dist: icmplib
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: pytest>=8.0; extra == "dev"
30
30
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
31
+ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
32
+ Requires-Dist: openai>=1.0.0; extra == "dev"
31
33
  Dynamic: license-file
32
34
 
33
35
  # LANscape
@@ -3,17 +3,17 @@ lanscape/__main__.py,sha256=PuY42yuCLAwHrOREJ6u2DgVyGX5hZKRQeoE9pajkNfM,170
3
3
  lanscape/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  lanscape/core/app_scope.py,sha256=qfzX8Ed4bFdxHMGjgnLlWuLZDTCBKObermz91KbGVn0,3298
5
5
  lanscape/core/decorators.py,sha256=CZbPEfnLS1OF-uejQweetadzqf0pVo736jKko4Xs-g4,7264
6
- lanscape/core/device_alive.py,sha256=TC6c5uR-2xwT8IXcRA3NAPJ9iPhdVx9a42bGSKq9N6w,6833
6
+ lanscape/core/device_alive.py,sha256=VY2dsoy6_MWUxcysZEFcsSoCtDkLMYzwqy0U_sbVWE0,9609
7
7
  lanscape/core/errors.py,sha256=QTf42UzR9Zxj1t1mdwfLvZIp0c9a5EItELOdCR7kTmE,1322
8
8
  lanscape/core/ip_parser.py,sha256=kn5H4ERitLnreRAqifWphwbxdjItGqwu50lsMCPDMcA,3474
9
9
  lanscape/core/logger.py,sha256=nzo6J8UdlMdhRkOJEDOIHKztoE3Du8PQZad7ixvNgeM,2534
10
10
  lanscape/core/mac_lookup.py,sha256=PxBSMe3wEVDtivCsh5NclSAguZz9rqdAS7QshBiuWvM,3519
11
- lanscape/core/net_tools.py,sha256=W-yyQ05k6BJc8VOnOi9_hpVD2G8i5HuQ_A39O8qk30Y,19676
11
+ lanscape/core/net_tools.py,sha256=Ht8TkLnKf-f6E1_AkTCde-yuiwUNI57TCXke0nIFi0o,21303
12
12
  lanscape/core/port_manager.py,sha256=3_ROOb6JEiB0NByZVtADuGcldFkgZwn1RKtvwgs9AIk,4479
13
13
  lanscape/core/runtime_args.py,sha256=2vIqRrcWr-NHRSBlZGrxh1PdkPY0ytkPguu8KZqy2L8,2543
14
14
  lanscape/core/scan_config.py,sha256=A2ZKXqXKW9nrP6yLb7b9b3XqSY_cQB3LZ5K0LVCSebE,11114
15
15
  lanscape/core/service_scan.py,sha256=wTDxOdazOsbI0hwCBR__4UCmB2RIbl2pw3F2YUW9aaE,6428
16
- lanscape/core/subnet_scan.py,sha256=IegcrpzevL2zMf1fuvCqegUGCBXtO3iAI49aCweXmvw,14444
16
+ lanscape/core/subnet_scan.py,sha256=PtSOk92dK05-reyr8LBkOXaI15qpYnar5nDqALCX1tQ,14850
17
17
  lanscape/core/version_manager.py,sha256=eGjyKgsv31QO0W26se9pPQ1TwmEN8qn37dHULtoocqc,2841
18
18
  lanscape/core/web_browser.py,sha256=23MuGIrBYdGhw6ejj6OWxwReeKIlWhtWukc1dKV_3_0,6736
19
19
  lanscape/resources/mac_addresses/convert_csv.py,sha256=hvlyLs0XmuuhBuvXBNRGP1cKJzYVRSf8VfUJ1VqROms,1189
@@ -69,9 +69,9 @@ lanscape/ui/templates/scan/ip-table-row.html,sha256=iSW3PYev3_k7pxTZUJUboqDUgdhs
69
69
  lanscape/ui/templates/scan/ip-table.html,sha256=AT2ZvCPYdKl-XJiAkEAawPOVuQw-w0MXumGQTr3zyKM,926
70
70
  lanscape/ui/templates/scan/overview.html,sha256=xWj9jWDPg2KcPLvS8fnSins23_UXjKCdb2NJwNG2U2Q,1176
71
71
  lanscape/ui/templates/scan/scan-error.html,sha256=wmAYQ13IJHUoO8fAGNDjMvNml7tu4rsIU3Vav71ETlA,999
72
- lanscape-2.1.0b1.dist-info/licenses/LICENSE,sha256=VLoE0IrNTIc09dFm7hMN0qzk4T3q8V0NaPcFQqMemDs,1070
73
- lanscape-2.1.0b1.dist-info/METADATA,sha256=4bfiG3YJu6XHs2bFT3OfxyCOgyafjkZk2waK0c0xjBg,3486
74
- lanscape-2.1.0b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
- lanscape-2.1.0b1.dist-info/entry_points.txt,sha256=evxSxUikFa1OEd4e0Boky9sLH87HdgM0YqB_AbB2HYc,51
76
- lanscape-2.1.0b1.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
77
- lanscape-2.1.0b1.dist-info/RECORD,,
72
+ lanscape-2.1.2.dist-info/licenses/LICENSE,sha256=VLoE0IrNTIc09dFm7hMN0qzk4T3q8V0NaPcFQqMemDs,1070
73
+ lanscape-2.1.2.dist-info/METADATA,sha256=oxHxoHbAYnhTnSIgsMyzzgqevSrWmF5Fzti1bqjLAao,3578
74
+ lanscape-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
+ lanscape-2.1.2.dist-info/entry_points.txt,sha256=evxSxUikFa1OEd4e0Boky9sLH87HdgM0YqB_AbB2HYc,51
76
+ lanscape-2.1.2.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
77
+ lanscape-2.1.2.dist-info/RECORD,,