lanscape 1.2.8a3__tar.gz → 1.2.9a2__tar.gz

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 (76) hide show
  1. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/MANIFEST.in +1 -0
  2. {lanscape-1.2.8a3/src/lanscape.egg-info → lanscape-1.2.9a2}/PKG-INFO +2 -10
  3. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/README.md +0 -8
  4. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/pyproject.toml +1 -1
  5. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/app_scope.py +12 -1
  6. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/ip_parser.py +1 -1
  7. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/mac_lookup.py +1 -2
  8. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/net_tools.py +9 -1
  9. lanscape-1.2.9a2/src/lanscape/libraries/service_scan.py +51 -0
  10. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/subnet_scan.py +62 -27
  11. lanscape-1.2.9a2/src/lanscape/resources/services/definitions.jsonc +456 -0
  12. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/tests/test_library.py +1 -1
  13. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/api/scan.py +3 -1
  14. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/web/routes.py +1 -1
  15. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/main.js +5 -1
  16. {lanscape-1.2.8a3 → lanscape-1.2.9a2/src/lanscape.egg-info}/PKG-INFO +2 -10
  17. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape.egg-info/SOURCES.txt +2 -0
  18. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/LICENSE +0 -0
  19. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/setup.cfg +0 -0
  20. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/__init__.py +0 -0
  21. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/__main__.py +0 -0
  22. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/decorators.py +0 -0
  23. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/errors.py +0 -0
  24. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/logger.py +0 -0
  25. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/port_manager.py +0 -0
  26. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/runtime_args.py +0 -0
  27. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/libraries/version_manager.py +0 -0
  28. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/mac_addresses/convert_csv.py +0 -0
  29. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/mac_addresses/mac_db.json +0 -0
  30. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/ports/convert_csv.py +0 -0
  31. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/ports/full.json +0 -0
  32. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/ports/large.json +0 -0
  33. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/ports/medium.json +0 -0
  34. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/resources/ports/small.json +0 -0
  35. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/tests/__init__.py +0 -0
  36. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/tests/_helpers.py +0 -0
  37. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/tests/test_api.py +0 -0
  38. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/tests/test_env.py +0 -0
  39. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/app.py +0 -0
  40. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/__init__.py +0 -0
  41. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/api/__init__.py +0 -0
  42. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/api/port.py +0 -0
  43. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/api/tools.py +0 -0
  44. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/blueprints/web/__init__.py +0 -0
  45. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/main.py +0 -0
  46. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/css/style.css +0 -0
  47. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/android-chrome-192x192.png +0 -0
  48. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/android-chrome-512x512.png +0 -0
  49. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/apple-touch-icon.png +0 -0
  50. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/favicon-16x16.png +0 -0
  51. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/favicon-32x32.png +0 -0
  52. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/favicon.ico +0 -0
  53. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/img/ico/site.webmanifest +0 -0
  54. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/core.js +0 -0
  55. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/layout-sizing.js +0 -0
  56. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/quietReload.js +0 -0
  57. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/shutdown-server.js +0 -0
  58. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/subnet-info.js +0 -0
  59. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/js/subnet-selector.js +0 -0
  60. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/static/lanscape.webmanifest +0 -0
  61. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/base.html +0 -0
  62. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/core/head.html +0 -0
  63. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/core/scripts.html +0 -0
  64. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/error.html +0 -0
  65. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/info.html +0 -0
  66. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/main.html +0 -0
  67. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/scan/export.html +0 -0
  68. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/scan/ip-table-row.html +0 -0
  69. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/scan/ip-table.html +0 -0
  70. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/scan/overview.html +0 -0
  71. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/scan/scan-error.html +0 -0
  72. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/scan.html +0 -0
  73. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape/ui/templates/shutdown.html +0 -0
  74. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape.egg-info/dependency_links.txt +0 -0
  75. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape.egg-info/requires.txt +0 -0
  76. {lanscape-1.2.8a3 → lanscape-1.2.9a2}/src/lanscape.egg-info/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
1
  recursive-include src/lanscape/resources *.json
2
+ recursive-include src/lanscape/resources *.jsonc
2
3
  recursive-include src/lanscape/ui/templates *.html
3
4
  recursive-include src/lanscape/ui/static *
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: lanscape
3
- Version: 1.2.8a3
3
+ Version: 1.2.9a2
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  Project-URL: Homepage, https://github.com/mdennis281/py-lanscape
@@ -55,14 +55,6 @@ can sometimes require admin-level permissions to retrieve accurate results.
55
55
  This is a missing dependency related to the ARP lookup. This is handled in the code, but you would get marginally faster/better results with this installed: [npcap download](https://npcap.com/#download)
56
56
 
57
57
 
58
- ### Unable to start webview client. Try --nogui (Linux)
59
- Linux and QT (GUI package) dont seem to play well with each other very well. If you really want the gui (`python -m lanscape --nogui` is almost as good) I had success on ubuntu desktop by running these:
60
- ```sh
61
- sudo apt install libcairo2-dev libxt-dev libgirepository1.0-dev
62
- pip install pycairo PyGObject qtpy PyQt5 PyQtWebEngine
63
- ```
64
-
65
-
66
58
  ### Something else
67
59
  Feel free to submit a github issue detailing your experience.
68
60
 
@@ -34,14 +34,6 @@ can sometimes require admin-level permissions to retrieve accurate results.
34
34
  This is a missing dependency related to the ARP lookup. This is handled in the code, but you would get marginally faster/better results with this installed: [npcap download](https://npcap.com/#download)
35
35
 
36
36
 
37
- ### Unable to start webview client. Try --nogui (Linux)
38
- Linux and QT (GUI package) dont seem to play well with each other very well. If you really want the gui (`python -m lanscape --nogui` is almost as good) I had success on ubuntu desktop by running these:
39
- ```sh
40
- sudo apt install libcairo2-dev libxt-dev libgirepository1.0-dev
41
- pip install pycairo PyGObject qtpy PyQt5 PyQtWebEngine
42
- ```
43
-
44
-
45
37
  ### Something else
46
38
  Feel free to submit a github issue detailing your experience.
47
39
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lanscape"
3
- version = "1.2.8a3"
3
+ version = "1.2.9a2"
4
4
  authors = [
5
5
  { name="Michael Dennis", email="michael@dipduo.com" },
6
6
  ]
@@ -1,6 +1,7 @@
1
1
  from pathlib import Path
2
- import importlib.util
2
+ import json
3
3
  import sys
4
+ import re
4
5
 
5
6
  class ResourceManager:
6
7
  """
@@ -17,6 +18,16 @@ class ResourceManager:
17
18
  with open(self.asset_dir / asset_name, 'r') as f:
18
19
  return f.read()
19
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
+
20
31
  def update(self, asset_name: str, content: str):
21
32
  with open(self.asset_dir / asset_name, 'w') as f:
22
33
  f.write(content)
@@ -28,7 +28,7 @@ def parse_ip_input(ip_input):
28
28
 
29
29
  # If no CIDR or range, assume a single IP
30
30
  else:
31
- ip_ranges.append(ipaddress.IPv4Address(entry,strict=False))
31
+ ip_ranges.append(ipaddress.IPv4Address(entry))
32
32
  if len(ip_ranges) > MAX_IPS_ALLOWED:
33
33
  raise SubnetTooLargeError(ip_input)
34
34
  return ip_ranges
@@ -1,5 +1,4 @@
1
1
  import re
2
- import json
3
2
  import logging
4
3
  import platform
5
4
  import subprocess
@@ -7,7 +6,7 @@ from typing import List
7
6
 
8
7
  from .app_scope import ResourceManager
9
8
 
10
- DB = json.loads(ResourceManager('mac_addresses').get('mac_db.json'))
9
+ DB = ResourceManager('mac_addresses').get_json('mac_db.json')
11
10
 
12
11
  log = logging.getLogger('MacLookup')
13
12
 
@@ -8,9 +8,10 @@ import ipaddress
8
8
  import traceback
9
9
  import subprocess
10
10
  from time import sleep
11
- from typing import List
11
+ from typing import List, Dict
12
12
  from scapy.all import ARP, Ether, srp
13
13
 
14
+ from .service_scan import scan_service
14
15
  from .mac_lookup import lookup_mac, get_macs
15
16
  from .ip_parser import get_address_count, MAX_IPS_ALLOWED
16
17
 
@@ -73,6 +74,7 @@ class Device(IPAlive):
73
74
  self.manufacturer: str = None
74
75
  self.ports: List[int] = []
75
76
  self.stage: str = 'found'
77
+ self.services: Dict[str,List[int]] = {}
76
78
  self.log = logging.getLogger('Device')
77
79
 
78
80
  def get_metadata(self):
@@ -100,6 +102,12 @@ class Device(IPAlive):
100
102
  return True
101
103
  return False
102
104
 
105
+ def scan_service(self,port:int):
106
+ service = scan_service(self.ip,port)
107
+ service_ports = self.services.get(service,[])
108
+ service_ports.append(port)
109
+ self.services[service] = service_ports
110
+
103
111
  def get_mac(self):
104
112
  if not self.macs:
105
113
  self.macs = self._get_mac_addresses()
@@ -0,0 +1,51 @@
1
+ import asyncio
2
+ import logging
3
+ import traceback
4
+ from .app_scope import ResourceManager
5
+
6
+ log = logging.getLogger('ServiceScan')
7
+ SERVICES = ResourceManager('services').get_jsonc('definitions.jsonc')
8
+
9
+ # skip printer ports because they cause blank pages to be printed
10
+ PRINTER_PORTS = [9100, 631]
11
+
12
+ def scan_service(ip: str, port: int, timeout=10) -> str:
13
+ """
14
+ Synchronous function that attempts to identify the service running on a given port.
15
+ """
16
+
17
+ async def _async_scan_service(ip: str, port: int, timeout) -> str:
18
+ if port in PRINTER_PORTS:
19
+ return "Printer"
20
+
21
+ try:
22
+ # Add a timeout to prevent hanging
23
+ reader, writer = await asyncio.wait_for(asyncio.open_connection(ip, port), timeout=5)
24
+
25
+ # Send a probe appropriate for common services
26
+ probe = "GET / HTTP/1.1\r\nHost: {}\r\n\r\n".format(ip).encode("utf-8")
27
+ writer.write(probe)
28
+ await writer.drain()
29
+
30
+ # Receive the response with a timeout
31
+ response = await asyncio.wait_for(reader.read(1024), timeout=timeout)
32
+ writer.close()
33
+ await writer.wait_closed()
34
+
35
+ # Analyze the response to identify the service
36
+ response_str = response.decode("utf-8", errors="ignore")
37
+ for service, hints in SERVICES.items():
38
+ if any(hint.lower() in response_str.lower() for hint in hints):
39
+ return service
40
+ except asyncio.TimeoutError:
41
+ log.warning(f"Timeout scanning {ip}:{port}")
42
+ except Exception as e:
43
+ log.error(f"Error scanning {ip}:{port}: {str(e)}")
44
+ log.debug(traceback.format_exc())
45
+ return "Unknown"
46
+
47
+ # Use asyncio.run to execute the asynchronous logic synchronously
48
+ return asyncio.run(_async_scan_service(ip, port,timeout=timeout))
49
+
50
+
51
+
@@ -27,7 +27,37 @@ TCNT_DEVICE_ISALIVE = 256
27
27
  class ScanConfig:
28
28
  subnet: str
29
29
  port_list: str
30
- parallelism: float = 1.0
30
+ t_multiplier: float = 1.0
31
+ t_cnt_port_scan: int = 10
32
+ t_cnt_port_test: int = 128
33
+ t_cnt_isalive: int = 256
34
+
35
+ task_scan_ports: bool = True
36
+ # below wont run if above false
37
+ task_scan_port_services: bool = True
38
+
39
+ def t_cnt(self, id: str) -> int:
40
+ return int(int(getattr(self, f't_cnt_{id}')) * float(self.t_multiplier))
41
+
42
+ @staticmethod
43
+ def from_dict(data: dict) -> 'ScanConfig':
44
+ return ScanConfig(
45
+ subnet = data['subnet'],
46
+ port_list = data['port_list'],
47
+ t_multiplier = data.get('parallelism',1.0),
48
+ t_cnt_port_scan = data.get('t_cnt_port_scan',10),
49
+ t_cnt_port_test = data.get('t_cnt_port_test',128),
50
+ t_cnt_isalive = data.get('t_cnt_isalive',256),
51
+ task_scan_ports = data.get('task_scan_ports',True),
52
+ task_scan_port_services = data.get('task_scan_port_services',True)
53
+ )
54
+
55
+ def get_ports(self) -> List[int]:
56
+ return PortManager().get_port_list(self.port_list).keys()
57
+
58
+ def parse_subnet(self) -> List[ipaddress.IPv4Network]:
59
+ return parse_ip_input(self.subnet)
60
+
31
61
 
32
62
 
33
63
 
@@ -36,12 +66,13 @@ class SubnetScanner:
36
66
  self,
37
67
  config: ScanConfig
38
68
  ):
39
- self.subnet = parse_ip_input(config.subnet)
40
- self.port_list = config.port_list
41
- self.ports: list = PortManager().get_port_list(config.port_list).keys()
69
+ self.cfg = config
70
+ self.subnet = config.parse_subnet()
71
+ self.ports: List[int] = config.get_ports()
42
72
  self.running = False
43
- self.parallelism: float = float(config.parallelism)
44
73
  self.subnet_str = config.subnet
74
+
75
+
45
76
  self.job_stats = JobStats()
46
77
  self.uid = str(uuid.uuid4())
47
78
  self.results = ScannerResults(self)
@@ -58,7 +89,7 @@ class SubnetScanner:
58
89
  """
59
90
  self._set_stage('scanning devices')
60
91
  self.running = True
61
- with ThreadPoolExecutor(max_workers=self._t_cnt(TCNT_DEVICE_ISALIVE)) as executor:
92
+ with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('isalive')) as executor:
62
93
  futures = {executor.submit(self._get_host_details, str(ip)): str(ip) for ip in self.subnet}
63
94
  for future in futures:
64
95
  ip = futures[future]
@@ -73,7 +104,8 @@ class SubnetScanner:
73
104
 
74
105
 
75
106
  self._set_stage('testing ports')
76
- self._scan_network_ports()
107
+ if self.cfg.task_scan_ports:
108
+ self._scan_network_ports()
77
109
  self.running = False
78
110
  self._set_stage('complete')
79
111
 
@@ -104,7 +136,7 @@ class SubnetScanner:
104
136
  remaining_isalive_sec = (self.results.devices_total - self.results.devices_scanned) * avg_host_detail_sec
105
137
  total_isalive_sec = self.results.devices_total * avg_host_detail_sec
106
138
 
107
- isalive_multiplier = self._t_cnt(TCNT_DEVICE_ISALIVE)
139
+ isalive_multiplier = self.cfg.t_cnt('isalive')
108
140
 
109
141
  # --- Port scanning calculations ---
110
142
  device_ports_scanned = self.job_stats.finished.get('_test_port', 0)
@@ -116,7 +148,7 @@ class SubnetScanner:
116
148
  remaining_port_test_sec = device_ports_unscanned * avg_port_test_sec
117
149
  total_port_test_sec = est_subnet_devices * len(self.ports) * avg_port_test_sec
118
150
 
119
- port_test_multiplier = self._t_cnt(TCNT_PORT_SCANS) * self._t_cnt(TCNT_PORT_TEST)
151
+ port_test_multiplier = self.cfg.t_cnt('port_scan') * self.cfg.t_cnt('port_test')
120
152
 
121
153
  # --- Overall progress ---
122
154
  est_total_time = (total_isalive_sec / isalive_multiplier) + (total_port_test_sec / port_test_multiplier)
@@ -126,17 +158,23 @@ class SubnetScanner:
126
158
 
127
159
 
128
160
 
129
- def debug_active_scan(self):
161
+ def debug_active_scan(self,sleep_sec=1):
130
162
  """
131
163
  Run this after running scan_subnet_threaded
132
164
  to see the progress of the scan
133
165
  """
134
166
  while self.running:
167
+ percent = self.calc_percent_complete()
168
+ t_elapsed = time() - self.results.start_time
169
+ t_remain = int((100-percent) * (t_elapsed / percent)) if percent else '∞'
170
+ buffer = f'{self.uid} - {self.subnet_str}\n'
171
+ buffer += f'Elapsed: {int(t_elapsed)} sec - Remain: {t_remain} sec\n'
172
+ buffer += f'Scanned: {self.results.devices_scanned}/{self.results.devices_total}'
173
+ buffer += f' - {percent}%\n'
174
+ buffer += str(self.job_stats)
135
175
  os.system('cls' if os.name == 'nt' else 'clear')
136
- print(f'{self.uid} - {self.subnet_str}')
137
- print(f"Scanned: {self.results.devices_scanned}/{self.results.devices_total} - {self.calc_percent_complete()}%")
138
- print(self.job_stats)
139
- sleep(1)
176
+ print(buffer)
177
+ sleep(sleep_sec)
140
178
 
141
179
  @terminator
142
180
  @job_tracker
@@ -157,7 +195,7 @@ class SubnetScanner:
157
195
 
158
196
  @terminator
159
197
  def _scan_network_ports(self):
160
- with ThreadPoolExecutor(max_workers=self._t_cnt(TCNT_PORT_SCANS)) as executor:
198
+ with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_scan')) as executor:
161
199
  futures = {executor.submit(self._scan_ports, device): device for device in self.results.devices}
162
200
  for future in futures:
163
201
  future.result()
@@ -167,7 +205,7 @@ class SubnetScanner:
167
205
  def _scan_ports(self, device: Device):
168
206
  self.log.debug(f'[{device.ip}] Initiating port scan')
169
207
  device.stage = 'scanning'
170
- with ThreadPoolExecutor(max_workers=self._t_cnt(TCNT_PORT_TEST)) as executor:
208
+ with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_test')) as executor:
171
209
  futures = {executor.submit(self._test_port, device, int(port)): port for port in self.ports}
172
210
  for future in futures:
173
211
  future.result()
@@ -179,9 +217,13 @@ class SubnetScanner:
179
217
  def _test_port(self,host: Device, port: int):
180
218
  """
181
219
  Test if a port is open on a given host.
220
+ If port open, determine service.
182
221
  Device class handles tracking open ports.
183
222
  """
184
- return host.test_port(port)
223
+ is_alive = host.test_port(port)
224
+ if is_alive and self.cfg.task_scan_port_services:
225
+ host.scan_service(port)
226
+ return is_alive
185
227
 
186
228
 
187
229
  @terminator
@@ -192,13 +234,6 @@ class SubnetScanner:
192
234
  """
193
235
  return host.is_alive(host.ip)
194
236
 
195
- def _t_cnt(self, base_threads: int) -> int:
196
- """
197
- Calculate the number of threads to use based on the base number
198
- of threads and the parallelism factor.
199
- """
200
- return int(base_threads * self.parallelism)
201
-
202
237
  def _set_stage(self,stage):
203
238
  self.log.debug(f'[{self.uid}] Moving to Stage: {stage}')
204
239
  self.results.stage = stage
@@ -208,9 +243,8 @@ class SubnetScanner:
208
243
  class ScannerResults:
209
244
  def __init__(self,scan: SubnetScanner):
210
245
  self.scan = scan
211
- self.port_list: str = scan.port_list
246
+ self.port_list: str = scan.cfg.port_list
212
247
  self.subnet: str = scan.subnet_str
213
- self.parallelism: float = scan.parallelism
214
248
  self.uid = scan.uid
215
249
 
216
250
  self.devices_total: int = len(list(scan.subnet))
@@ -249,6 +283,7 @@ class ScannerResults:
249
283
  out = vars(self).copy()
250
284
  out.pop('scan')
251
285
  out.pop('log')
286
+ out['cfg'] = vars(self.scan.cfg)
252
287
 
253
288
  devices: List[Device] = out.pop('devices')
254
289
  sortedDevices = sorted(devices, key=lambda obj: ipaddress.IPv4Address(obj.ip))
@@ -262,7 +297,7 @@ class ScannerResults:
262
297
  def __str__(self):
263
298
  # Prepare data for tabulate
264
299
  data = [
265
- [device.ip, device.hostname, device.mac_addr, ", ".join(map(str, device.ports))]
300
+ [device.ip, device.hostname, device.get_mac(), ", ".join(map(str, device.ports))]
266
301
  for device in self.devices
267
302
  ]
268
303
 
@@ -0,0 +1,456 @@
1
+ {
2
+ // ----------------------- Web Services -----------------------
3
+ "HTTP": [
4
+ "HTTP",
5
+ "Apache",
6
+ "Nginx",
7
+ "IIS",
8
+ "lighttpd",
9
+ "Caddy",
10
+ "OpenResty",
11
+ "GWS", // Google Web Server
12
+ "LiteSpeed",
13
+ "Gunicorn", // Often in X-Powered-By
14
+ "OpenLiteSpeed",
15
+ "Cloudflare"
16
+ ],
17
+ "HTTPS": [
18
+ "HTTPS",
19
+ "SSL",
20
+ "TLS"
21
+ // Possibly you'd see "443" in a banner or SNI reference,
22
+ // but typically "SSL" or "TLS" coverage is sufficient
23
+ ],
24
+
25
+ // ------------------ Remote Shell / Admin --------------------
26
+ "SSH": [
27
+ "SSH",
28
+ "OpenSSH",
29
+ "Dropbear",
30
+ "libssh"
31
+ ],
32
+ "Telnet": [
33
+ "Telnet",
34
+ "BusyBox on telnetd"
35
+ ],
36
+
37
+ // ------------------ File Transfer Protocols -----------------
38
+ "FTP": [
39
+ "FTP",
40
+ "FileZilla",
41
+ "ProFTPD",
42
+ "vsftpd",
43
+ "Pure-FTPd",
44
+ "WS_FTP",
45
+ "Microsoft ftpd"
46
+ ],
47
+ "SFTP": [
48
+ "SFTP"
49
+ // SFTP typically is an SSH subsystem, so you'd often see an SSH banner anyway
50
+ ],
51
+ "TFTP": [
52
+ "TFTP"
53
+ ],
54
+
55
+ // -------------------- Email / Messaging ----------------------
56
+ "SMTP": [
57
+ "SMTP",
58
+ "Postfix",
59
+ "Exim",
60
+ "Sendmail",
61
+ "Qmail",
62
+ "Exchange"
63
+ ],
64
+ "SMTPS": [
65
+ "SMTPS",
66
+ "465 secure",
67
+ "587 secure"
68
+ // Some servers might mention "TLS wrapper" or similar
69
+ ],
70
+ "POP3": [
71
+ "POP3",
72
+ "Dovecot",
73
+ "Courier",
74
+ "POP3 (Internet Mail)"
75
+ ],
76
+ "POP3S": [
77
+ "POP3S",
78
+ "SSL POP3",
79
+ "Dovecot",
80
+ "Courier"
81
+ ],
82
+ "IMAP": [
83
+ "IMAP",
84
+ "Dovecot",
85
+ "Courier",
86
+ "Cyrus",
87
+ "IMAP4"
88
+ ],
89
+ "IMAPS": [
90
+ "IMAPS",
91
+ "SSL IMAP",
92
+ "Dovecot",
93
+ "Courier",
94
+ "Cyrus"
95
+ ],
96
+
97
+ // ---------------- Domain / Directory Services ---------------
98
+ "DNS": [
99
+ "DNS",
100
+ "Bind",
101
+ "DNSMasq",
102
+ "PowerDNS",
103
+ "Unbound",
104
+ "Microsoft DNS",
105
+ "Knot DNS"
106
+ ],
107
+ "LDAP": [
108
+ "LDAP"
109
+ ],
110
+ "Active Directory": [
111
+ "Active Directory",
112
+ "Kerberos",
113
+ "MSAD",
114
+ "LDAP for AD",
115
+ "LDAP Service (AD)"
116
+ ],
117
+
118
+ // -------------------- Database Services ----------------------
119
+ "MySQL": [
120
+ "MySQL",
121
+ "MariaDB",
122
+ "Percona"
123
+ ],
124
+ "PostgreSQL": [
125
+ "PostgreSQL",
126
+ "Postgres"
127
+ ],
128
+ "Microsoft SQL Server": [
129
+ "MSSQL",
130
+ "Microsoft SQL Server",
131
+ "TDS",
132
+ "SQL Server",
133
+ "MS-SQL"
134
+ ],
135
+ "Oracle Database": [
136
+ "Oracle",
137
+ "TNS",
138
+ "Oracle Net"
139
+ ],
140
+ "MongoDB": [
141
+ "MongoDB"
142
+ ],
143
+ "Redis": [
144
+ "Redis"
145
+ ],
146
+ "Cassandra": [
147
+ "Cassandra"
148
+ ],
149
+ "Memcached": [
150
+ "Memcached"
151
+ ],
152
+
153
+ // ----------------- Caching / Indexing / Logging -------------
154
+ "Elasticsearch": [
155
+ "Elasticsearch"
156
+ ],
157
+ "Logstash": [
158
+ "Logstash"
159
+ ],
160
+
161
+ // --------------- Message Brokers & Streaming ----------------
162
+ "Kafka": [
163
+ "Kafka"
164
+ ],
165
+ "RabbitMQ": [
166
+ "RabbitMQ",
167
+ "AMQP"
168
+ ],
169
+ "ActiveMQ": [
170
+ "ActiveMQ"
171
+ ],
172
+ "Mosquitto": [
173
+ "Mosquitto",
174
+ "MQTT"
175
+ ],
176
+
177
+ // -------------------- Microsoft Services ---------------------
178
+ "RDP": [
179
+ "RDP",
180
+ "Terminal Services",
181
+ "Remote Desktop",
182
+ "MSTerminal"
183
+ ],
184
+ "SMB": [
185
+ "SMB",
186
+ "NetBIOS",
187
+ "Samba"
188
+ ],
189
+ "WMI": [
190
+ "WMI"
191
+ ],
192
+ "WinRM": [
193
+ "WinRM"
194
+ ],
195
+
196
+ // ---------------- Collaboration / Chat -----------------------
197
+ "IRC": [
198
+ "IRC"
199
+ ],
200
+ "XMPP": [
201
+ "XMPP",
202
+ "Jabber"
203
+ ],
204
+ "Matrix": [
205
+ "Matrix"
206
+ ],
207
+
208
+ // ------------- Virtualization / Container Services ----------
209
+ "Docker API": [
210
+ "Docker"
211
+ ],
212
+ "Kubernetes API": [
213
+ "Kubernetes",
214
+ "K8s",
215
+ "kube-apiserver"
216
+ ],
217
+ "VMware ESXi": [
218
+ "VMware ESXi",
219
+ "ESX"
220
+ ],
221
+ "Proxmox": [
222
+ "Proxmox"
223
+ ],
224
+
225
+ // ----------------- Remote Desktop / Control -----------------
226
+ "VNC": [
227
+ "VNC",
228
+ "RFB"
229
+ ],
230
+ "TeamViewer": [
231
+ "TeamViewer"
232
+ ],
233
+ "AnyDesk": [
234
+ "AnyDesk"
235
+ ],
236
+
237
+ // --------------------- Voice over IP ------------------------
238
+ "SIP": [
239
+ "SIP",
240
+ "Asterisk",
241
+ "FreeSWITCH"
242
+ ],
243
+ "H.323": [
244
+ "H.323"
245
+ ],
246
+
247
+ // ----------- Time, Network Config, and Infrastructure -------
248
+ "NTP": [
249
+ "NTP"
250
+ ],
251
+ "DHCP": [
252
+ "DHCP"
253
+ ],
254
+
255
+ // ------------------ VPN / Tunneling / Security --------------
256
+ "OpenVPN": [
257
+ "OpenVPN"
258
+ ],
259
+ "IPSec": [
260
+ "IPSec",
261
+ "IKE"
262
+ ],
263
+ "WireGuard": [
264
+ "WireGuard"
265
+ ],
266
+
267
+ // ---------------------- Other Protocols ----------------------
268
+ "SNMP": [
269
+ "SNMP"
270
+ ],
271
+ "SNMP Trap": [
272
+ "Trap",
273
+ "SNMPTRAP"
274
+ ],
275
+ "Syslog": [
276
+ "Syslog"
277
+ ],
278
+
279
+ // -------------------- Cloud Services / APIs -----------------
280
+ "AWS S3": [
281
+ "AmazonS3",
282
+ "S3"
283
+ ],
284
+ "AWS RDS": [
285
+ "AWS RDS",
286
+ "amazon-rds"
287
+ ],
288
+ "Azure DevOps": [
289
+ "Azure DevOps",
290
+ "VSTS"
291
+ ],
292
+ "GCP Services": [
293
+ "GCP",
294
+ "googleapis.com"
295
+ ],
296
+
297
+ // -------------------- Repo / Code Hosting --------------------
298
+ "Git": [
299
+ "git-daemon"
300
+ ],
301
+ "SVN": [
302
+ "SVN",
303
+ "Subversion"
304
+ ],
305
+
306
+ // -------------- HPC / Cluster Management / HPC --------------
307
+ "Slurm": [
308
+ "Slurm"
309
+ ],
310
+
311
+ // --------------------- Misc / Exotic ------------------------
312
+ "Tor": [
313
+ "Tor",
314
+ "obfs"
315
+ ],
316
+ "ZeroTier": [
317
+ "ZeroTier"
318
+ ],
319
+ "Mumble": [
320
+ "Mumble",
321
+ "Murmur"
322
+ ],
323
+ "Spice": [
324
+ "SPICE protocol"
325
+ ],
326
+ "FusionDirectory": [
327
+ "FusionDirectory"
328
+ ],
329
+ "Jenkins": [
330
+ "Jenkins"
331
+ ],
332
+ "GitLab": [
333
+ "GitLab"
334
+ ],
335
+ "GitHub": [
336
+ "GitHub",
337
+ "github.com"
338
+ ],
339
+ "Bitbucket": [
340
+ "Bitbucket"
341
+ ],
342
+
343
+ // ------------------ Legacy or Older Protocols ---------------
344
+ "Gopher": [
345
+ "Gopher"
346
+ ],
347
+ "Finger": [
348
+ "Finger"
349
+ ],
350
+ "Ident": [
351
+ "Ident"
352
+ ],
353
+ "Rlogin": [
354
+ "rlogin"
355
+ ],
356
+ "Rsync": [
357
+ "rsync"
358
+ ],
359
+ "UUCP": [
360
+ "UUCP"
361
+ ],
362
+ "AFS": [
363
+ "AFS",
364
+ "OpenAFS"
365
+ ],
366
+ "NIS": [
367
+ "NIS",
368
+ "YPServ"
369
+ ],
370
+ "Telnet SSL": [
371
+ "telnet-ssl"
372
+ ],
373
+
374
+ // ----------------- Big Data / Hadoop Ecosystem --------------
375
+ "Hadoop": [
376
+ "Hadoop",
377
+ "MapReduce",
378
+ "YARN"
379
+ ],
380
+ "HDFS": [
381
+ "HDFS"
382
+ ],
383
+ "Hive": [
384
+ "Hive",
385
+ "HiveServer2"
386
+ ],
387
+ "HBase": [
388
+ "HBase",
389
+ "HMaster"
390
+ ],
391
+ "Zookeeper": [
392
+ "Zookeeper",
393
+ "ZooKeeper"
394
+ ],
395
+ "Spark": [
396
+ "Spark"
397
+ ],
398
+
399
+ // ------------ Web Frameworks / Application Servers ----------
400
+ "Tomcat": [
401
+ "Apache Tomcat",
402
+ "Apache-Coyote"
403
+ ],
404
+ "JBoss": [
405
+ "JBoss",
406
+ "JBoss EAP"
407
+ ],
408
+ "GlassFish": [
409
+ "GlassFish"
410
+ ],
411
+ "WebLogic": [
412
+ "WebLogic"
413
+ ],
414
+ "WebSphere": [
415
+ "WebSphere"
416
+ ],
417
+ "Jetty": [
418
+ "Jetty"
419
+ ],
420
+ // Gunicorn and uWSGI also appear under HTTP if they surface in the banner.
421
+
422
+ // ------- Additional Container / Orchestration Tools ---------
423
+ "Docker Swarm": [
424
+ "Swarm"
425
+ ],
426
+
427
+ // ---------- Reverse Proxies / Load Balancers / Gateways -----
428
+ "HAProxy": [
429
+ "HAProxy"
430
+ ],
431
+ "Traefik": [
432
+ "Traefik"
433
+ ],
434
+ "Envoy": [
435
+ "Envoy"
436
+ ],
437
+
438
+ // ------------------ CI/CD and DevOps Tools ------------------
439
+ "Bamboo": [
440
+ "Bamboo"
441
+ ],
442
+ "TeamCity": [
443
+ "TeamCity"
444
+ ],
445
+ "Drone CI": [
446
+ "Drone"
447
+ ],
448
+
449
+ // ------------------- Just for completeness ------------------
450
+ "COBOL-based service": [
451
+ "Mainframe",
452
+ "AS/400",
453
+ // Banner might mention "Enterprise COBOL" or "z/OS"
454
+ "z/OS"
455
+ ]
456
+ }
@@ -11,7 +11,7 @@ class LibraryTestCase(unittest.TestCase):
11
11
  self.assertIsNotNone(subnet)
12
12
  cfg = ScanConfig(
13
13
  subnet = right_size_subnet(subnet),
14
- parallelism=1.0,
14
+ t_multiplier=1.0,
15
15
  port_list='small'
16
16
  )
17
17
  scan = sm.new_scan(cfg)
@@ -35,6 +35,8 @@ def get_scan(scan_id):
35
35
  @api_bp.route('/api/scan/<scan_id>/summary',methods=['GET'])
36
36
  def get_scan_summary(scan_id):
37
37
  scan = scan_manager.get_scan(scan_id)
38
+ if not scan:
39
+ return jsonify({'error':'scan not found'}), 404
38
40
  return jsonify({
39
41
  'running': scan.running,
40
42
  'percent_complete': scan.calc_percent_complete(),
@@ -61,5 +63,5 @@ def get_scan_config():
61
63
  return ScanConfig(
62
64
  subnet = data['subnet'],
63
65
  port_list= data['port_list'],
64
- parallelism=data.get('parallelism',1.0)
66
+ t_multiplier=data.get('parallelism',1.0)
65
67
  )
@@ -22,7 +22,7 @@ def index():
22
22
  scan = scanner.results.export()
23
23
  subnet = scan['subnet']
24
24
  port_list = scan['port_list']
25
- parallelism = scan['parallelism']
25
+ parallelism = scan['cfg']['t_multiplier']
26
26
  else:
27
27
  log.debug(f'Redirecting, scan {scan_id} doesnt exist in memory')
28
28
  return redirect('/')
@@ -164,7 +164,6 @@ function pollScanSummary(id) {
164
164
  if (summary.running || summary.stage == 'terminating') {
165
165
  progress.css('height','2px');
166
166
  progress.css('width',`${summary.percent_complete}vw`);
167
- // TODO: move overview here??
168
167
  setTimeout(() => {pollScanSummary(id)},500);
169
168
  } else {
170
169
  progress.css('width','100vw');
@@ -179,6 +178,11 @@ function pollScanSummary(id) {
179
178
  },1000);
180
179
  }
181
180
  updateOverviewUI(summary);
181
+ }).fail(function(req) {
182
+ if (req === 404) {
183
+ console.log('Scan not found, redirecting to home');
184
+ window.location.href = '/';
185
+ }
182
186
  });
183
187
  }
184
188
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: lanscape
3
- Version: 1.2.8a3
3
+ Version: 1.2.9a2
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  Project-URL: Homepage, https://github.com/mdennis281/py-lanscape
@@ -55,14 +55,6 @@ can sometimes require admin-level permissions to retrieve accurate results.
55
55
  This is a missing dependency related to the ARP lookup. This is handled in the code, but you would get marginally faster/better results with this installed: [npcap download](https://npcap.com/#download)
56
56
 
57
57
 
58
- ### Unable to start webview client. Try --nogui (Linux)
59
- Linux and QT (GUI package) dont seem to play well with each other very well. If you really want the gui (`python -m lanscape --nogui` is almost as good) I had success on ubuntu desktop by running these:
60
- ```sh
61
- sudo apt install libcairo2-dev libxt-dev libgirepository1.0-dev
62
- pip install pycairo PyGObject qtpy PyQt5 PyQtWebEngine
63
- ```
64
-
65
-
66
58
  ### Something else
67
59
  Feel free to submit a github issue detailing your experience.
68
60
 
@@ -18,6 +18,7 @@ src/lanscape/libraries/mac_lookup.py
18
18
  src/lanscape/libraries/net_tools.py
19
19
  src/lanscape/libraries/port_manager.py
20
20
  src/lanscape/libraries/runtime_args.py
21
+ src/lanscape/libraries/service_scan.py
21
22
  src/lanscape/libraries/subnet_scan.py
22
23
  src/lanscape/libraries/version_manager.py
23
24
  src/lanscape/resources/mac_addresses/convert_csv.py
@@ -27,6 +28,7 @@ src/lanscape/resources/ports/full.json
27
28
  src/lanscape/resources/ports/large.json
28
29
  src/lanscape/resources/ports/medium.json
29
30
  src/lanscape/resources/ports/small.json
31
+ src/lanscape/resources/services/definitions.jsonc
30
32
  src/lanscape/tests/__init__.py
31
33
  src/lanscape/tests/_helpers.py
32
34
  src/lanscape/tests/test_api.py
File without changes
File without changes