178 lines
No EOL
5.7 KiB
Python
178 lines
No EOL
5.7 KiB
Python
import socket
|
|
import threading
|
|
import ipaddress
|
|
import time
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from app.core.extensions import db
|
|
from app.core.models import Subnet, Server
|
|
import json
|
|
import subprocess
|
|
|
|
def scan(cidr, max_threads=10, save_results=False):
|
|
"""
|
|
Scan a subnet for active hosts
|
|
|
|
Args:
|
|
cidr: The subnet in CIDR notation (e.g. "192.168.1.0/24")
|
|
max_threads: Maximum number of threads to use
|
|
save_results: Whether to save results to the database
|
|
|
|
Returns:
|
|
A list of dictionaries with IP, hostname, and status
|
|
"""
|
|
print(f"Starting scan of {cidr}")
|
|
network = ipaddress.ip_network(cidr)
|
|
|
|
# Skip network and broadcast addresses for IPv4
|
|
if network.version == 4:
|
|
hosts = list(network.hosts())
|
|
else:
|
|
# For IPv6, just take the first 100 addresses to avoid scanning too many
|
|
hosts = list(network.hosts())[:100]
|
|
|
|
# Split the hosts into chunks for multithreading
|
|
chunks = [[] for _ in range(max_threads)]
|
|
for i, host in enumerate(hosts):
|
|
chunks[i % max_threads].append(host)
|
|
|
|
# Initialize results
|
|
results = [[] for _ in range(max_threads)]
|
|
|
|
# Create and start threads
|
|
threads = []
|
|
for i in range(max_threads):
|
|
if chunks[i]: # Only start a thread if there are IPs to scan
|
|
t = threading.Thread(target=scan_worker, args=(chunks[i], results, i))
|
|
threads.append(t)
|
|
t.start()
|
|
|
|
# Wait for all threads to complete
|
|
for t in threads:
|
|
t.join()
|
|
|
|
# Combine results
|
|
all_results = []
|
|
for r in results:
|
|
all_results.extend(r)
|
|
|
|
# Save results to database if requested
|
|
if save_results:
|
|
try:
|
|
save_scan_results(cidr, all_results)
|
|
except Exception as e:
|
|
print(f"Error saving scan results: {e}")
|
|
|
|
print(f"Scan completed. Found {len(all_results)} active hosts.")
|
|
return all_results
|
|
|
|
def scan_worker(ip_list, results, index):
|
|
"""Worker function for threading"""
|
|
for ip in ip_list:
|
|
if ping(ip):
|
|
hostname = get_hostname(ip)
|
|
results[index].append({
|
|
'ip': str(ip),
|
|
'hostname': hostname if hostname else str(ip),
|
|
'status': 'up'
|
|
})
|
|
|
|
def ping(ip):
|
|
"""Ping an IP address and return True if it responds"""
|
|
try:
|
|
# Faster timeout (1 second)
|
|
subprocess.check_output(['ping', '-c', '1', '-W', '1', str(ip)], stderr=subprocess.STDOUT)
|
|
return True
|
|
except subprocess.CalledProcessError:
|
|
return False
|
|
|
|
def get_hostname(ip):
|
|
"""Try to get the hostname for an IP address"""
|
|
try:
|
|
hostname = socket.gethostbyaddr(str(ip))[0]
|
|
return hostname
|
|
except (socket.herror, socket.gaierror):
|
|
return None
|
|
|
|
def is_host_active_ping(ip):
|
|
"""Simple ICMP ping test (platform dependent)"""
|
|
import platform
|
|
import subprocess
|
|
|
|
param = '-n' if platform.system().lower() == 'windows' else '-c'
|
|
command = ['ping', param, '1', '-w', '1', ip]
|
|
|
|
try:
|
|
return subprocess.call(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
|
|
except:
|
|
return False
|
|
|
|
def save_scan_results(cidr, results):
|
|
"""Save scan results to the database"""
|
|
from flask import current_app
|
|
|
|
# Need to be in application context
|
|
if not hasattr(current_app, 'app_context'):
|
|
print("Not in Flask application context, cannot save results")
|
|
return
|
|
|
|
try:
|
|
# Find subnet by CIDR
|
|
subnet = Subnet.query.filter_by(cidr=cidr).first()
|
|
if not subnet:
|
|
print(f"Subnet {cidr} not found in database")
|
|
return
|
|
|
|
# Get existing servers in this subnet
|
|
existing_servers = {server.ip_address: server for server in Server.query.filter_by(subnet_id=subnet.id).all()}
|
|
|
|
# Process scan results
|
|
for host in results:
|
|
ip = host['ip']
|
|
hostname = host['hostname']
|
|
|
|
# Check if server already exists
|
|
if ip in existing_servers:
|
|
# Update hostname if it was previously unknown
|
|
if existing_servers[ip].hostname == ip and hostname != ip:
|
|
existing_servers[ip].hostname = hostname
|
|
db.session.add(existing_servers[ip])
|
|
else:
|
|
# Create new server
|
|
server = Server(
|
|
hostname=hostname,
|
|
ip_address=ip,
|
|
subnet_id=subnet.id,
|
|
documentation=f"# {hostname}\n\nAutomatically discovered by network scan on {time.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
)
|
|
db.session.add(server)
|
|
|
|
db.session.commit()
|
|
print(f"Saved scan results for {cidr}")
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
print(f"Error saving scan results: {e}")
|
|
|
|
def schedule_subnet_scans():
|
|
"""Schedule automatic scans for subnets marked as auto_scan"""
|
|
from flask import current_app
|
|
|
|
with current_app.app_context():
|
|
try:
|
|
# Find all subnets with auto_scan enabled
|
|
subnets = Subnet.query.filter_by(auto_scan=True).all()
|
|
|
|
for subnet in subnets:
|
|
# Start a thread for each subnet
|
|
thread = threading.Thread(
|
|
target=scan,
|
|
args=(subnet.cidr,),
|
|
daemon=True
|
|
)
|
|
thread.start()
|
|
|
|
# Sleep briefly to avoid overloading
|
|
time.sleep(1)
|
|
|
|
except Exception as e:
|
|
print(f"Error scheduling subnet scans: {e}") |