#!/usr/bin/env python3 """ Netzwerk IP-Scanner (Ultra-Zuverlässig) Scannt angegebene Netzwerke nach belegten und freien IP-Adressen Mehrschichtige Erkennung für 100% Zuverlässigkeit """ import argparse import ipaddress import subprocess import sys import socket from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass from typing import List, Set, Dict, Tuple, Optional import time import shutil import re # Globale Debug-Variable DEBUG = False # ANSI Farben class Color: RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' BLUE = '\033[94m' MAGENTA = '\033[95m' CYAN = '\033[96m' WHITE = '\033[97m' BOLD = '\033[1m' UNDERLINE = '\033[4m' RESET = '\033[0m' def debug_print(message: str): """Gibt Debug-Nachrichten aus wenn DEBUG aktiviert ist""" if DEBUG: print(f"{Color.YELLOW}[DEBUG] {message}{Color.RESET}") @dataclass class HostInfo: ip: str hostname: Optional[str] = None mac: Optional[str] = None responding: bool = True method: str = "unknown" @dataclass class ScanResult: network: str used_hosts: Dict[str, HostInfo] free_ips: Set[str] total: int scan_time: float method: str def print_banner(): """Zeigt einen Banner""" banner = f""" {Color.CYAN}{Color.BOLD}╔═══════════════════════════════════════════════════════╗ ║ Netzwerk IP-Scanner v3.0 (Ultra-Zuverlässig) ║ ╚═══════════════════════════════════════════════════════╝{Color.RESET} """ print(banner) def check_tool(tool: str) -> bool: """Prüft ob ein Tool verfügbar ist""" is_available = shutil.which(tool) is not None debug_print(f"Tool '{tool}' verfügbar: {is_available}") return is_available def get_hostname(ip: str, timeout: float = 0.3) -> Optional[str]: """Versucht den Hostnamen einer IP-Adresse aufzulösen""" try: socket.setdefaulttimeout(timeout) hostname = socket.gethostbyaddr(ip)[0] result = hostname if hostname != ip else None debug_print(f"Hostname für {ip}: {result}") return result except (socket.herror, socket.gaierror, socket.timeout, OSError) as e: debug_print(f"Hostname-Auflösung für {ip} fehlgeschlagen: {e}") return None def get_network_interface(network_str: str) -> Optional[str]: """Findet automatisch das passende Netzwerk-Interface für ein Netzwerk""" try: debug_print(f"Suche Interface für Netzwerk {network_str}") network = ipaddress.ip_network(network_str, strict=False) # ip route get um das Interface zu finden result = subprocess.run( ['ip', 'route', 'get', str(list(network.hosts())[0])], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5 ) if result.returncode == 0: # Parse die Ausgabe nach "dev " match = re.search(r'dev\s+(\S+)', result.stdout) if match: interface = match.group(1) debug_print(f"Gefundenes Interface: {interface}") return interface debug_print(f"Kein Interface gefunden via ip route") return None except Exception as e: debug_print(f"Fehler bei Interface-Erkennung: {e}") return None def arp_scan_network(network_str: str, interface: Optional[str] = None) -> Tuple[Dict[str, HostInfo], Set[str]]: """ Scannt Netzwerk mit arp-scan (Layer 2 - sehr zuverlässig) Benötigt root/sudo! """ try: network = ipaddress.ip_network(network_str, strict=False) # Interface bestimmen if interface: debug_print(f"Verwende manuell angegebenes Interface: {interface}") selected_interface = interface else: debug_print(f"Automatische Interface-Erkennung für {network_str}") selected_interface = get_network_interface(network_str) if not selected_interface: debug_print("Keine automatische Interface-Erkennung möglich, verwende --localnet") selected_interface = None # arp-scan Kommando aufbauen if selected_interface: cmd = ['arp-scan', '--interface', selected_interface, '--numeric', '--quiet', '--ignoredups', str(network)] debug_print(f"ARP-Scan Befehl: {' '.join(cmd)}") else: cmd = ['arp-scan', '--localnet', '--numeric', '--quiet', '--ignoredups', str(network)] debug_print(f"ARP-Scan Befehl (localnet): {' '.join(cmd)}") result = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=90, text=True ) if result.returncode != 0: debug_print(f"ARP-Scan stderr: {result.stderr}") debug_print(f"ARP-Scan returncode: {result.returncode}") debug_print(f"ARP-Scan stdout:\n{result.stdout}") used_hosts = {} all_ips = set(str(ip) for ip in network.hosts()) # Parse arp-scan output # Format: IPMACVendor lines_parsed = 0 for line in result.stdout.split('\n'): line = line.strip() if not line or line.startswith('#'): continue # Split by tabs oder whitespace parts = re.split(r'\s+', line) if len(parts) >= 2: ip = parts[0].strip() mac = parts[1].strip() # Prüfe ob IP gültig ist und im Netzwerk liegt try: ip_obj = ipaddress.ip_address(ip) if str(ip_obj) in all_ips: hostname = get_hostname(ip) used_hosts[ip] = HostInfo(ip=ip, hostname=hostname, mac=mac, method="arp-scan") lines_parsed += 1 debug_print(f"Gefunden: {ip} ({mac})") except ValueError as e: debug_print(f"Ungültige IP in arp-scan Ausgabe: {ip} - {e}") continue debug_print(f"ARP-Scan: {lines_parsed} Zeilen geparst, {len(used_hosts)} Hosts gefunden") free_ips = all_ips - set(used_hosts.keys()) return used_hosts, free_ips except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.CalledProcessError, Exception) as e: debug_print(f"ARP-Scan Exception: {type(e).__name__}: {e}") print(f" {Color.YELLOW}⚠ ARP-Scan Fehler: {e}{Color.RESET}") return None, None def nmap_aggressive_scan(network_str: str, scan_ports: bool = False) -> Tuple[Dict[str, HostInfo], Set[str]]: """ Scannt Netzwerk mit nmap - sehr aggressiv und zuverlässig """ try: network = ipaddress.ip_network(network_str, strict=False) if scan_ports: # Mit Port-Scan für maximale Zuverlässigkeit cmd = [ 'nmap', '-sS', # TCP SYN Scan '-p', '21,22,23,25,80,111,135,139,443,445,993,995,1723,3306,3389,5900,8080,8443', # Häufige Ports '--min-rate', '1000', # Schneller '-T4', # Aggressives Timing '-n', # Keine DNS-Auflösung durch nmap str(network) ] else: # Nur Host-Discovery cmd = [ 'nmap', '-sn', # Ping Scan '-PS21,22,23,25,80,443,3389,8080', # TCP SYN Ping '-PA80,443', # TCP ACK Ping '-PU53,67,68,161', # UDP Ping '-PE', # ICMP Echo '-PP', # ICMP Timestamp '--min-rate', '500', '-T4', '-n', str(network) ] debug_print(f"Nmap Befehl: {' '.join(cmd)}") result = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=300, text=True ) if result.returncode != 0: debug_print(f"Nmap stderr: {result.stderr}") debug_print(f"Nmap stdout (erste 500 Zeichen):\n{result.stdout[:500]}") used_hosts = {} all_ips = set(str(ip) for ip in network.hosts()) # Parse nmap output current_ip = None hosts_found = 0 for line in result.stdout.split('\n'): # Suche nach "Nmap scan report for" if 'Nmap scan report for' in line: match = re.search(r'(\d+\.\d+\.\d+\.\d+)', line) if match: current_ip = match.group(1) debug_print(f"Nmap: Gefunden IP {current_ip}") # Prüfe ob Host up ist if current_ip and ('Host is up' in line or 'open' in line): if current_ip in all_ips and current_ip not in used_hosts: hostname = get_hostname(current_ip) used_hosts[current_ip] = HostInfo(ip=current_ip, hostname=hostname, method="nmap") hosts_found += 1 debug_print(f"Nmap: Host {current_ip} ist up") debug_print(f"Nmap: {hosts_found} Hosts gefunden") free_ips = all_ips - set(used_hosts.keys()) return used_hosts, free_ips except (subprocess.TimeoutExpired, FileNotFoundError, Exception) as e: debug_print(f"Nmap Exception: {type(e).__name__}: {e}") print(f" {Color.YELLOW}⚠ Nmap Fehler: {e}{Color.RESET}") return None, None def check_tcp_ports(ip: str, ports: List[int]) -> bool: """Prüft ob TCP-Ports offen sind""" for port in ports: try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(0.3) result = sock.connect_ex((ip, port)) sock.close() if result == 0: debug_print(f"TCP Port {port} auf {ip} ist offen") return True except Exception as e: debug_print(f"TCP Port Check Fehler {ip}:{port}: {e}") continue return False def ping_check(ip: str) -> bool: """ICMP Ping""" try: result = subprocess.run( ['ping', '-c', '1', '-W', '1', str(ip)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=2 ) success = result.returncode == 0 if success: debug_print(f"Ping erfolgreich: {ip}") return success except Exception as e: debug_print(f"Ping Fehler {ip}: {e}") return False def comprehensive_check(ip: str, tcp_ports: List[int]) -> bool: """Kombinierte Prüfung: Ping + TCP-Ports""" if ping_check(ip): return True return check_tcp_ports(ip, tcp_ports) def scan_network(network_str: str, method: str = 'auto', max_workers: int = 100, tcp_ports: List[int] = None, aggressive: bool = False, interface: Optional[str] = None) -> ScanResult: """Scannt ein Netzwerk nach aktiven IPs""" if tcp_ports is None: tcp_ports = [22, 80, 443, 3389, 8080, 8443, 3306, 5432] try: network = ipaddress.ip_network(network_str, strict=False) except ValueError as e: print(f"{Color.RED}✗ Fehler beim Parsen von {network_str}: {e}{Color.RESET}") return None all_ips = list(network.hosts()) total_ips = len(all_ips) print(f"\n{Color.YELLOW}► Scanne Netzwerk: {Color.BOLD}{network_str}{Color.RESET}") print(f" {Color.WHITE}IPs zu scannen: {total_ips}{Color.RESET}") if interface: print(f" {Color.WHITE}Interface: {interface}{Color.RESET}") start_time = time.time() used_hosts = {} free_ips = set() scan_method = method # Automatische Methodenwahl if method == 'auto': if check_tool('arp-scan'): scan_method = 'arp-scan' debug_print("Auto-Methode: Verwende arp-scan") elif check_tool('nmap'): scan_method = 'nmap' debug_print("Auto-Methode: Verwende nmap") else: scan_method = 'comprehensive' debug_print("Auto-Methode: Verwende comprehensive") print(f" {Color.CYAN}Methode: {scan_method}{Color.RESET}") # ARP-Scan (beste Option) if scan_method == 'arp-scan': print(f" {Color.CYAN}Führe ARP-Scan durch...{Color.RESET}") used_hosts, free_ips = arp_scan_network(network_str, interface=interface) if used_hosts is None or len(used_hosts) == 0: print(f" {Color.YELLOW}⚠ ARP-Scan lieferte keine Ergebnisse, fallback zu nmap{Color.RESET}") debug_print("ARP-Scan Fallback zu nmap") scan_method = 'nmap' # Nmap-Scan if scan_method == 'nmap' and not used_hosts: print(f" {Color.CYAN}Führe Nmap-Scan durch...{Color.RESET}") used_hosts, free_ips = nmap_aggressive_scan(network_str, scan_ports=aggressive) if used_hosts is None: print(f" {Color.YELLOW}⚠ Nmap fehlgeschlagen, fallback zu comprehensive{Color.RESET}") debug_print("Nmap Fallback zu comprehensive") scan_method = 'comprehensive' # Comprehensive Scan (Fallback) if scan_method == 'comprehensive' or not used_hosts: print(f" {Color.CYAN}Führe umfassenden Scan durch (Ping + TCP)...{Color.RESET}") scanned = 0 def print_progress(): progress = (scanned / total_ips) * 100 bar_length = 40 filled = int(bar_length * scanned / total_ips) bar = '█' * filled + '░' * (bar_length - filled) print(f"\r {Color.CYAN}[{bar}] {progress:.1f}% ({scanned}/{total_ips}){Color.RESET}", end='', flush=True) with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(comprehensive_check, str(ip), tcp_ports): ip for ip in all_ips} for future in as_completed(futures): ip = str(futures[future]) try: is_alive = future.result() if is_alive: hostname = get_hostname(ip) used_hosts[ip] = HostInfo(ip=ip, hostname=hostname, method="comprehensive") else: free_ips.add(ip) except Exception: free_ips.add(ip) scanned += 1 print_progress() print() # Sicherstellen dass free_ips korrekt ist all_ips_set = set(str(ip) for ip in all_ips) free_ips = all_ips_set - set(used_hosts.keys()) scan_time = time.time() - start_time debug_print(f"Scan abgeschlossen: {len(used_hosts)} belegt, {len(free_ips)} frei, Dauer: {scan_time:.2f}s") return ScanResult( network=network_str, used_hosts=used_hosts, free_ips=free_ips, total=total_ips, scan_time=scan_time, method=scan_method ) def print_result(result: ScanResult, show_all: bool = False, show_free: bool = True): """Gibt das Scan-Ergebnis formatiert aus""" if not result: return used_count = len(result.used_hosts) free_count = len(result.free_ips) usage_percent = (used_count / result.total) * 100 if result.total > 0 else 0 print(f"\n{Color.BLUE}{Color.BOLD}╔═══ Ergebnis für {result.network} ═══╗{Color.RESET}") print(f"{Color.WHITE} Scan-Methode: {result.method}{Color.RESET}") print(f"{Color.WHITE} Scan-Dauer: {result.scan_time:.2f}s{Color.RESET}") print(f"{Color.GREEN} ✓ Belegte IPs: {used_count} ({usage_percent:.1f}%){Color.RESET}") print(f"{Color.RED} ✗ Freie IPs: {free_count} ({100-usage_percent:.1f}%){Color.RESET}") # Belegte IPs mit Hostnamen ausgeben if used_count > 0: print(f"\n{Color.GREEN}{Color.BOLD} Belegte IP-Adressen:{Color.RESET}") sorted_hosts = sorted(result.used_hosts.values(), key=lambda x: ipaddress.ip_address(x.ip)) display_hosts = sorted_hosts if (show_all or used_count <= 20) else sorted_hosts[:10] + [None] + sorted_hosts[-10:] for host in display_hosts: if host is None: print(f" {Color.YELLOW}... ({used_count - 20} weitere) ...{Color.RESET}") continue hostname_str = f" → {Color.CYAN}{host.hostname}{Color.RESET}" if host.hostname else "" mac_str = f" [{host.mac}]" if host.mac else "" print(f" {Color.GREEN}{host.ip:15}{Color.RESET}{hostname_str}{mac_str}") # Freie IPs ausgeben if show_free and free_count > 0: print(f"\n{Color.RED}{Color.BOLD} Freie IP-Adressen:{Color.RESET}") sorted_free = sorted(result.free_ips, key=lambda x: ipaddress.ip_address(x)) if free_count <= 20 or show_all: for i, ip in enumerate(sorted_free, 1): if i % 4 == 1: print(f" {Color.RED}", end='') print(f"{ip:15}", end=' ') if i % 4 == 0: print(Color.RESET) if free_count % 4 != 0: print(Color.RESET) else: for i, ip in enumerate(sorted_free[:10], 1): if i % 4 == 1: print(f" {Color.RED}", end='') print(f"{ip:15}", end=' ') if i % 4 == 0: print(Color.RESET) if len(sorted_free[:10]) % 4 != 0: print(Color.RESET) print(f"\n {Color.YELLOW} ... ({free_count - 20} weitere freie IPs) ...{Color.RESET}\n") remaining = sorted_free[-10:] for i, ip in enumerate(remaining, 1): if i % 4 == 1: print(f" {Color.RED}", end='') print(f"{ip:15}", end=' ') if i % 4 == 0: print(Color.RESET) if len(remaining) % 4 != 0: print(Color.RESET) print(f"{Color.BLUE}{Color.BOLD}╚{'═' * 50}╝{Color.RESET}\n") def print_common_free_hosts(results: List[ScanResult], show_all: bool = False): """ Zeigt Host-IDs an, die in ALLEN gescannten Netzwerken gleichzeitig frei sind """ if len(results) < 2: return free_host_ids_per_network = [] for result in results: host_ids = set() for ip_str in result.free_ips: try: ip_parts = ip_str.split('.') if len(ip_parts) == 4: host_id = int(ip_parts[3]) host_ids.add(host_id) except (ValueError, IndexError): continue free_host_ids_per_network.append(host_ids) common_free = set.intersection(*free_host_ids_per_network) if free_host_ids_per_network else set() if not common_free: print(f"{Color.YELLOW}⚠ Keine gemeinsam freien Host-IDs in allen Netzwerken gefunden{Color.RESET}\n") return common_count = len(common_free) sorted_common = sorted(common_free) networks_str = ", ".join([r.network for r in results]) print(f"{Color.MAGENTA}{Color.BOLD}╔═══ Gemeinsam freie Host-IDs ═══╗{Color.RESET}") print(f"{Color.WHITE} Netzwerke: {networks_str}{Color.RESET}") print(f"{Color.MAGENTA} Anzahl gemeinsam frei: {common_count}{Color.RESET}") print(f"\n{Color.MAGENTA}{Color.BOLD} Verfügbare Host-IDs (letztes Oktett):{Color.RESET}") print(f"\n{Color.CYAN} Host-ID ", end='') for result in results: network_prefix = '.'.join(result.network.split('.')[:3]) print(f"│ {network_prefix:15}", end=' ') print(Color.RESET) print(f"{Color.CYAN} ─────────{'─' * (len(results) * 19)}{Color.RESET}") display_limit = None if show_all else 30 if display_limit and common_count > display_limit: display_ids = sorted_common[:15] + [None] + sorted_common[-15:] else: display_ids = sorted_common for host_id in display_ids: if host_id is None: print(f"{Color.YELLOW} ... {'│' * len(results)} ({common_count - 30} weitere){Color.RESET}") continue print(f" {Color.MAGENTA}{host_id:>3} {Color.RESET}", end='') for result in results: network_prefix = '.'.join(result.network.split('.')[:3]) full_ip = f"{network_prefix}.{host_id}" print(f"│ {Color.GREEN}{full_ip:15}{Color.RESET}", end=' ') print() print(f"\n{Color.MAGENTA}{Color.BOLD}╚{'═' * 50}╝{Color.RESET}\n") if common_count > 0: example_id = sorted_common[0] example_ips = [] for result in results: network_prefix = '.'.join(result.network.split('.')[:3]) example_ips.append(f"{network_prefix}.{example_id}") print(f"{Color.CYAN}💡 Tipp: Für eine VM mit mehreren Interfaces kannst du z.B. verwenden:{Color.RESET}") for i, ip in enumerate(example_ips, 1): print(f" Interface {i}: {Color.GREEN}{ip}{Color.RESET}") print() def main(): global DEBUG parser = argparse.ArgumentParser( description='Scannt Netzwerke nach belegten und freien IP-Adressen (100%% zuverlässig)', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Scan-Methoden: auto - Automatisch beste verfügbare Methode (Standard) arp-scan - ARP-Scan (Layer 2, am zuverlässigsten, benötigt sudo!) nmap - Nmap Scan (sehr zuverlässig) comprehensive - Ping + TCP Port-Check (Fallback) Wichtig für maximale Zuverlässigkeit: sudo ./ip.py 10.150.8.0/24 --method arp-scan Beispiele: sudo %(prog)s 10.150.8.0/24 10.150.56.0/24 sudo %(prog)s 192.168.0.0/24 --method arp-scan -a sudo %(prog)s 10.0.0.0/24 --method arp-scan --interface vmbr0 %(prog)s 10.0.0.0/24 --aggressive %(prog)s 192.168.1.0/24 --no-free --debug """ ) parser.add_argument( 'networks', nargs='+', help='Netzwerke im CIDR-Format (z.B. 192.168.0.0/24)' ) parser.add_argument( '-a', '--show-all', action='store_true', help='Zeige alle IPs (auch bei großen Listen)' ) parser.add_argument( '-m', '--method', choices=['auto', 'arp-scan', 'nmap', 'comprehensive'], default='auto', help='Scan-Methode (Standard: auto)' ) parser.add_argument( '-t', '--threads', type=int, default=100, help='Anzahl paralleler Threads (Standard: 100)' ) parser.add_argument( '-p', '--ports', type=str, default='22,80,443,3389,8080,8443,3306,5432', help='TCP-Ports für Port-Check (Standard: 22,80,443,3389,8080,8443,3306,5432)' ) parser.add_argument( '--aggressive', action='store_true', help='Aggressiver Nmap-Scan mit Port-Scan (langsamer aber zuverlässiger)' ) parser.add_argument( '--no-free', action='store_true', help='Zeige keine freien IPs' ) parser.add_argument( '-i', '--interface', type=str, default=None, help='Netzwerk-Interface für ARP-Scan (z.B. eth0, vmbr0, enp67s0)' ) parser.add_argument( '--debug', action='store_true', help='Aktiviert Debug-Modus mit detaillierter Ausgabe' ) args = parser.parse_args() # Debug-Modus aktivieren if args.debug: DEBUG = True debug_print("Debug-Modus aktiviert") # Parse TCP-Ports try: tcp_ports = [int(p.strip()) for p in args.ports.split(',')] debug_print(f"TCP-Ports: {tcp_ports}") except ValueError: print(f"{Color.RED}✗ Ungültiges Port-Format{Color.RESET}") return print_banner() # Warnung wenn nicht als root if args.method == 'arp-scan': try: uid_result = subprocess.run(['id', '-u'], capture_output=True, text=True) if uid_result.stdout.strip() != '0': print(f"{Color.RED}⚠ WARNUNG: arp-scan benötigt root-Rechte! Führe mit 'sudo' aus.{Color.RESET}\n") debug_print("Nicht als root ausgeführt") except: pass # Verfügbare Tools anzeigen tools_available = [] if check_tool('arp-scan'): tools_available.append('arp-scan') if check_tool('nmap'): tools_available.append('nmap') tools_available.append('ping+tcp') print(f"{Color.CYAN}Verfügbare Scan-Methoden: {', '.join(tools_available)}{Color.RESET}\n") if args.interface: print(f"{Color.CYAN}Manuelles Interface: {args.interface}{Color.RESET}\n") debug_print(f"Verwende manuell angegebenes Interface: {args.interface}") results = [] total_start = time.time() for network in args.networks: result = scan_network( network, method=args.method, max_workers=args.threads, tcp_ports=tcp_ports, aggressive=args.aggressive, interface=args.interface ) if result: results.append(result) print_result(result, args.show_all, not args.no_free) total_time = time.time() - total_start # Zusammenfassung if len(results) > 1: total_used = sum(len(r.used_hosts) for r in results) total_free = sum(len(r.free_ips) for r in results) total_ips = sum(r.total for r in results) print(f"\n{Color.CYAN}{Color.BOLD}╔═══ Gesamtzusammenfassung ═══╗{Color.RESET}") print(f"{Color.WHITE} Gescannte Netzwerke: {len(results)}{Color.RESET}") print(f"{Color.WHITE} Gesamte IPs: {total_ips}{Color.RESET}") print(f"{Color.GREEN} Belegte IPs: {total_used}{Color.RESET}") print(f"{Color.RED} Freie IPs: {total_free}{Color.RESET}") print(f"{Color.WHITE} Gesamtdauer: {total_time:.2f}s{Color.RESET}") print(f"{Color.CYAN}{Color.BOLD}╚{'═' * 30}╝{Color.RESET}\n") # Gemeinsam freie Host-IDs print_common_free_hosts(results, args.show_all) if __name__ == '__main__': main()