add watchdog and max scans
This commit is contained in:
@@ -14,3 +14,12 @@ NETBOX_PREFIX_STATUS=active
|
|||||||
|
|
||||||
# NetBox tenant name to assign to discovered IPs
|
# NetBox tenant name to assign to discovered IPs
|
||||||
TENANT=Your Tenant Name
|
TENANT=Your Tenant Name
|
||||||
|
|
||||||
|
# Parallelism
|
||||||
|
MAX_SCAN_WORKERS=5
|
||||||
|
MAX_IMPORT_WORKERS=5
|
||||||
|
|
||||||
|
# Watchdog: comma-separated IPs that must stay reachable during the scan
|
||||||
|
# If any becomes unreachable the scan stops immediately
|
||||||
|
WATCHDOG_IPS=192.168.1.1,192.168.1.2
|
||||||
|
WATCHDOG_INTERVAL=10
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ All configuration is done via environment variables. Copy `.env.example` to `.en
|
|||||||
| `NETWORKS` | — | Comma-separated CIDR networks (used when `SCAN_SOURCE=env` or `mixed`) |
|
| `NETWORKS` | — | Comma-separated CIDR networks (used when `SCAN_SOURCE=env` or `mixed`) |
|
||||||
| `NETBOX_PREFIX_STATUS` | _(all)_ | Filter NetBox prefixes by status, e.g. `active`, `reserved` (used when `SCAN_SOURCE=netbox` or `mixed`) |
|
| `NETBOX_PREFIX_STATUS` | _(all)_ | Filter NetBox prefixes by status, e.g. `active`, `reserved` (used when `SCAN_SOURCE=netbox` or `mixed`) |
|
||||||
| `TENANT` | — | NetBox tenant name to assign to imported IPs |
|
| `TENANT` | — | NetBox tenant name to assign to imported IPs |
|
||||||
|
| `MAX_SCAN_WORKERS` | `5` | Number of networks scanned in parallel |
|
||||||
|
| `MAX_IMPORT_WORKERS` | `5` | Number of NetBox import calls made in parallel |
|
||||||
|
| `WATCHDOG_IPS` | _(none)_ | Comma-separated IPs to monitor during the scan — if any go down, the scan stops immediately |
|
||||||
|
| `WATCHDOG_INTERVAL` | `10` | Seconds between watchdog ping checks |
|
||||||
|
|
||||||
**`SCAN_SOURCE` values:**
|
**`SCAN_SOURCE` values:**
|
||||||
|
|
||||||
|
|||||||
80
ipscan-v2.py
80
ipscan-v2.py
@@ -3,6 +3,8 @@ import nmap
|
|||||||
import pynetbox
|
import pynetbox
|
||||||
import requests
|
import requests
|
||||||
import socket
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
# Disable SSL Warnings
|
# Disable SSL Warnings
|
||||||
@@ -22,6 +24,15 @@ networks = [network.strip() for network in networks_env.split(",") if network.st
|
|||||||
scan_source = os.getenv("SCAN_SOURCE", "env").strip().lower()
|
scan_source = os.getenv("SCAN_SOURCE", "env").strip().lower()
|
||||||
netbox_prefix_status = os.getenv("NETBOX_PREFIX_STATUS", "").strip().lower()
|
netbox_prefix_status = os.getenv("NETBOX_PREFIX_STATUS", "").strip().lower()
|
||||||
|
|
||||||
|
# Parallelism
|
||||||
|
max_scan_workers = int(os.getenv("MAX_SCAN_WORKERS", "5"))
|
||||||
|
max_import_workers = int(os.getenv("MAX_IMPORT_WORKERS", "5"))
|
||||||
|
|
||||||
|
# Watchdog: comma-separated IPs that must stay reachable during the scan
|
||||||
|
watchdog_ips_env = os.getenv("WATCHDOG_IPS", "").strip()
|
||||||
|
watchdog_ips = [ip.strip() for ip in watchdog_ips_env.split(",") if ip.strip()]
|
||||||
|
watchdog_interval = int(os.getenv("WATCHDOG_INTERVAL", "10"))
|
||||||
|
|
||||||
# NetBox configuration
|
# NetBox configuration
|
||||||
netbox_url = os.getenv("NETBOX_URL", "https://netbox.xxxxx.xx/")
|
netbox_url = os.getenv("NETBOX_URL", "https://netbox.xxxxx.xx/")
|
||||||
netbox_token = os.getenv("NETBOX_TOKEN", "xxxxx")
|
netbox_token = os.getenv("NETBOX_TOKEN", "xxxxx")
|
||||||
@@ -31,6 +42,31 @@ netbox.http_session.verify = ssl_verify
|
|||||||
|
|
||||||
tenant = os.getenv("TENANT", "Xxxxx Praha")
|
tenant = os.getenv("TENANT", "Xxxxx Praha")
|
||||||
|
|
||||||
|
# Shared abort flag set by the watchdog when a monitored IP goes down
|
||||||
|
abort_event = threading.Event()
|
||||||
|
|
||||||
|
|
||||||
|
def ping(ip):
|
||||||
|
result = subprocess.run(
|
||||||
|
["ping", "-c", "1", "-W", "2", ip],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def watchdog_loop():
|
||||||
|
if not watchdog_ips:
|
||||||
|
return
|
||||||
|
print(f"Watchdog monitoring: {', '.join(watchdog_ips)} (interval: {watchdog_interval}s)")
|
||||||
|
while not abort_event.is_set():
|
||||||
|
for ip in watchdog_ips:
|
||||||
|
if not ping(ip):
|
||||||
|
print(f"[WATCHDOG] {ip} is unreachable — aborting scan.")
|
||||||
|
abort_event.set()
|
||||||
|
return
|
||||||
|
abort_event.wait(watchdog_interval)
|
||||||
|
|
||||||
|
|
||||||
def load_networks_from_netbox():
|
def load_networks_from_netbox():
|
||||||
print("Loading networks from NetBox...")
|
print("Loading networks from NetBox...")
|
||||||
@@ -52,11 +88,17 @@ elif scan_source == 'mixed':
|
|||||||
if not networks:
|
if not networks:
|
||||||
raise ValueError('No networks configured to scan. Set NETWORKS or SCAN_SOURCE to include NetBox prefixes.')
|
raise ValueError('No networks configured to scan. Set NETWORKS or SCAN_SOURCE to include NetBox prefixes.')
|
||||||
|
|
||||||
|
|
||||||
def scan_network(network):
|
def scan_network(network):
|
||||||
|
if abort_event.is_set():
|
||||||
|
print(f"Skipping network {network} — abort signalled.")
|
||||||
|
return []
|
||||||
print(f"Scanning network: {network}")
|
print(f"Scanning network: {network}")
|
||||||
nm.scan(hosts=network, arguments='-p 1-32768 -T4 --host-timeout 2m') # Adding a host-timeout of 2 minutes
|
nm.scan(hosts=network, arguments='-p 1-32768 -T4 --host-timeout 2m')
|
||||||
host_results = []
|
host_results = []
|
||||||
for host in nm.all_hosts():
|
for host in nm.all_hosts():
|
||||||
|
if abort_event.is_set():
|
||||||
|
break
|
||||||
if 'tcp' in nm[host]:
|
if 'tcp' in nm[host]:
|
||||||
ports = [port for port in nm[host]['tcp'] if nm[host]['tcp'][port]['state'] == 'open']
|
ports = [port for port in nm[host]['tcp'] if nm[host]['tcp'][port]['state'] == 'open']
|
||||||
ports_str = ' '.join(str(port) for port in ports)
|
ports_str = ' '.join(str(port) for port in ports)
|
||||||
@@ -66,12 +108,13 @@ def scan_network(network):
|
|||||||
print(f"Host: {host}, Status: {nm[host]['status']['state']}, Open Ports: {ports_str}")
|
print(f"Host: {host}, Status: {nm[host]['status']['state']}, Open Ports: {ports_str}")
|
||||||
return host_results
|
return host_results
|
||||||
|
|
||||||
|
|
||||||
def add_ip_to_netbox(host, status, ports):
|
def add_ip_to_netbox(host, status, ports):
|
||||||
if status == 'up':
|
if status == 'up':
|
||||||
try:
|
try:
|
||||||
hostname = socket.gethostbyaddr(host)[0]
|
hostname = socket.gethostbyaddr(host)[0]
|
||||||
except socket.herror:
|
except socket.herror:
|
||||||
hostname = host # Use the IP if the hostname couldn't be resolved
|
hostname = host
|
||||||
|
|
||||||
ip_data = {
|
ip_data = {
|
||||||
"address": f"{host}/24",
|
"address": f"{host}/24",
|
||||||
@@ -79,16 +122,19 @@ def add_ip_to_netbox(host, status, ports):
|
|||||||
"status": "active",
|
"status": "active",
|
||||||
"tenant": tenant,
|
"tenant": tenant,
|
||||||
"comments": f"Open ports: {ports}",
|
"comments": f"Open ports: {ports}",
|
||||||
# Add other fields as needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create or update the IP address in NetBox
|
|
||||||
print(f"Adding IP address: {ip_data['address']}, Hostname: {hostname}, Open Ports: {ports}")
|
print(f"Adding IP address: {ip_data['address']}, Hostname: {hostname}, Open Ports: {ports}")
|
||||||
netbox.ipam.ip_addresses.create(ip_data)
|
netbox.ipam.ip_addresses.create(ip_data)
|
||||||
|
|
||||||
# Create a thread pool and scan networks in parallel
|
|
||||||
|
# Start watchdog in background thread
|
||||||
|
watchdog_thread = threading.Thread(target=watchdog_loop, daemon=True)
|
||||||
|
watchdog_thread.start()
|
||||||
|
|
||||||
|
# Scan networks in parallel
|
||||||
hosts_list = []
|
hosts_list = []
|
||||||
with ThreadPoolExecutor(max_workers=5) as executor: # Adjust number of workers as needed
|
with ThreadPoolExecutor(max_workers=max_scan_workers) as executor:
|
||||||
future_to_network = {executor.submit(scan_network, network): network for network in networks}
|
future_to_network = {executor.submit(scan_network, network): network for network in networks}
|
||||||
for future in as_completed(future_to_network):
|
for future in as_completed(future_to_network):
|
||||||
network = future_to_network[future]
|
network = future_to_network[future]
|
||||||
@@ -97,9 +143,20 @@ with ThreadPoolExecutor(max_workers=5) as executor: # Adjust number of workers
|
|||||||
hosts_list.extend(data)
|
hosts_list.extend(data)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"{network} generated an exception: {exc}")
|
print(f"{network} generated an exception: {exc}")
|
||||||
|
if abort_event.is_set():
|
||||||
|
print("Abort flag set — cancelling remaining scan futures.")
|
||||||
|
for f in future_to_network:
|
||||||
|
f.cancel()
|
||||||
|
break
|
||||||
|
|
||||||
# Add each IP address to NetBox
|
# Stop watchdog
|
||||||
with ThreadPoolExecutor(max_workers=5) as executor: # Adjust number of workers as needed
|
abort_event.set()
|
||||||
|
|
||||||
|
if abort_event.is_set() and not hosts_list:
|
||||||
|
raise SystemExit("Scan aborted by watchdog before any results were collected.")
|
||||||
|
|
||||||
|
# Import collected results into NetBox
|
||||||
|
with ThreadPoolExecutor(max_workers=max_import_workers) as executor:
|
||||||
futures = [executor.submit(add_ip_to_netbox, host, status, ports) for host, status, ports in hosts_list]
|
futures = [executor.submit(add_ip_to_netbox, host, status, ports) for host, status, ports in hosts_list]
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
future.result()
|
future.result()
|
||||||
@@ -107,19 +164,18 @@ with ThreadPoolExecutor(max_workers=5) as executor: # Adjust number of workers
|
|||||||
# Get all IP addresses from NetBox
|
# Get all IP addresses from NetBox
|
||||||
all_ip_addresses = netbox.ipam.ip_addresses.all()
|
all_ip_addresses = netbox.ipam.ip_addresses.all()
|
||||||
|
|
||||||
# Create a list of hostnames from the scanned hosts
|
# Build list of hostnames found during this scan
|
||||||
scanned_hosts = []
|
scanned_hosts = []
|
||||||
for host, status, ports in hosts_list:
|
for host, status, ports in hosts_list:
|
||||||
if status == 'up':
|
if status == 'up':
|
||||||
try:
|
try:
|
||||||
hostname = socket.gethostbyaddr(host)[0]
|
hostname = socket.gethostbyaddr(host)[0]
|
||||||
except socket.herror:
|
except socket.herror:
|
||||||
hostname = host # Use the IP if the hostname couldn't be resolved
|
hostname = host
|
||||||
scanned_hosts.append(hostname)
|
scanned_hosts.append(hostname)
|
||||||
|
|
||||||
# Check each IP address in NetBox
|
# Mark IPs not seen in this scan as offline
|
||||||
for ip_address in all_ip_addresses:
|
for ip_address in all_ip_addresses:
|
||||||
# If the IP address's hostname was not found in the scan results, mark it as offline
|
|
||||||
if ip_address.description not in scanned_hosts:
|
if ip_address.description not in scanned_hosts:
|
||||||
ip_address.status = 'offline'
|
ip_address.status = 'offline'
|
||||||
ip_address.save()
|
ip_address.save()
|
||||||
|
|||||||
Reference in New Issue
Block a user