Files
netzwerk_diagramm_scanner/comprehensive_mapper.py
mindesbunister afe8903454 Add comprehensive network mapper and workflow script
- comprehensive_mapper.py: Combines network scanning with pfSense XML parsing
- run_network_mapping.sh: Complete workflow script for network discovery
- Successfully tested with both pfSense XML files and live network scan
- Generates comprehensive JSON data and SVG network diagrams
- Includes WireGuard VPN topology, static routes, and DHCP mappings
2025-10-10 11:14:37 +02:00

356 lines
15 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Comprehensive Network Diagram Generator
Combines network scanning with pfSense XML parsing for complete network topology
"""
import json
import argparse
import logging
from pathlib import Path
from typing import Dict, List, Any
from network_scanner import NetworkScanner
from pfsense_xml_parser import PfSenseXMLParser
from svg_generator import NetworkDiagramGenerator
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class ComprehensiveNetworkMapper:
"""Complete network mapping solution combining multiple data sources"""
def __init__(self, config: Dict):
self.config = config
self.network_data = {}
self.pfsense_data = {}
self.combined_data = {
'scan_timestamp': None,
'data_sources': [],
'segments': [],
'pfsense_firewalls': [],
'wireguard_networks': [],
'openvpn_networks': [],
'routing_table': [],
'dhcp_leases': [],
'static_mappings': []
}
def load_network_scan(self, scan_file: str):
"""Load network scan data"""
logger.info(f"Loading network scan data from {scan_file}")
try:
with open(scan_file, 'r') as f:
self.network_data = json.load(f)
self.combined_data['data_sources'].append('network_scan')
logger.info("Network scan data loaded successfully")
except Exception as e:
logger.error(f"Error loading network scan: {e}")
def load_pfsense_configs(self, xml_files: List[str]):
"""Load and parse pfSense XML configuration files"""
for xml_file in xml_files:
if Path(xml_file).exists():
logger.info(f"Parsing pfSense config: {xml_file}")
parser = PfSenseXMLParser(xml_file)
data = parser.parse_all()
if data:
hostname = data.get('hostname', f'pfsense_{len(self.pfsense_data)}')
self.pfsense_data[hostname] = data
self.combined_data['data_sources'].append(f'pfsense_{hostname}')
self.combined_data['pfsense_firewalls'].append(data)
logger.info(f"pfSense config {hostname} loaded successfully")
else:
logger.error(f"Failed to parse pfSense config: {xml_file}")
else:
logger.warning(f"pfSense config file not found: {xml_file}")
def merge_network_data(self):
"""Merge all data sources into comprehensive network map"""
logger.info("Merging network data sources...")
# Start with network scan data if available
if self.network_data:
self.combined_data.update({
'scan_timestamp': self.network_data.get('scan_timestamp'),
'segments': self.network_data.get('segments', [])
})
# Process pfSense data
self._process_pfsense_data()
# Extract additional network information
self._extract_network_topology()
logger.info("Network data merging complete")
def _process_pfsense_data(self):
"""Process and integrate pfSense configuration data"""
for hostname, pfsense in self.pfsense_data.items():
logger.info(f"Processing pfSense data for {hostname}")
# Add interfaces as network segments
interfaces = pfsense.get('interfaces', {})
for iface_name, iface_data in interfaces.items():
if iface_data.get('ipaddr') and iface_data.get('ipaddr') != 'dhcp':
try:
# Create network segment from interface
ip = iface_data['ipaddr']
subnet = iface_data.get('subnet', '24')
network_cidr = f"{ip.rsplit('.', 1)[0]}.0/{subnet}"
segment = {
'name': f"{hostname}_{iface_name}",
'cidr': network_cidr,
'gateway': iface_data.get('gateway', ip),
'interface': iface_data.get('interface'),
'pfsense_host': hostname,
'is_vpn': iface_data.get('type') in ['wireguard', 'openvpn'],
'devices': []
}
# Add gateway device
gateway_device = {
'ip': ip,
'hostname': hostname,
'device_type': 'firewall',
'os_type': 'pfSense',
'interface': iface_name,
'pfsense_config': True
}
segment['devices'].append(gateway_device)
self.combined_data['segments'].append(segment)
# Track VPN networks
if iface_data.get('type') == 'wireguard':
self.combined_data['wireguard_networks'].append({
'network': network_cidr,
'interface': iface_data.get('interface'),
'pfsense': hostname,
'peers': pfsense.get('wireguard', {}).get('peers', [])
})
elif iface_data.get('type') == 'openvpn':
self.combined_data['openvpn_networks'].append({
'network': network_cidr,
'interface': iface_data.get('interface'),
'pfsense': hostname
})
except Exception as e:
logger.debug(f"Error processing interface {iface_name}: {e}")
# Add static routes
static_routes = pfsense.get('static_routes', [])
for route in static_routes:
route_info = {
'network': route.get('network'),
'gateway': route.get('gateway'),
'description': route.get('description'),
'pfsense': hostname
}
self.combined_data['routing_table'].append(route_info)
# Add DHCP information
dhcp_config = pfsense.get('dhcp', {})
for iface_name, dhcp_data in dhcp_config.items():
if dhcp_data.get('static_mappings'):
for mapping in dhcp_data['static_mappings']:
mapping_info = {
'ip': mapping.get('ipaddr'),
'mac': mapping.get('mac'),
'hostname': mapping.get('hostname'),
'description': mapping.get('description'),
'interface': iface_name,
'pfsense': hostname
}
self.combined_data['static_mappings'].append(mapping_info)
def _extract_network_topology(self):
"""Extract network topology information from all sources"""
logger.info("Extracting network topology...")
# Process WireGuard peer information
for wg_net in self.combined_data['wireguard_networks']:
for peer in wg_net.get('peers', []):
for allowed_ip in peer.get('allowed_ips', []):
# Create segment for remote networks
if allowed_ip.get('mask') and allowed_ip['mask'] != '32':
remote_segment = {
'name': f"WG_Remote_{allowed_ip['address']}_{allowed_ip['mask']}",
'cidr': f"{allowed_ip['address']}/{allowed_ip['mask']}",
'gateway': wg_net.get('network', '').split('/')[0].rsplit('.', 1)[0] + '.1',
'is_vpn': True,
'vpn_type': 'wireguard',
'pfsense_host': wg_net.get('pfsense'),
'devices': []
}
self.combined_data['segments'].append(remote_segment)
def generate_svg_diagram(self, output_file: str):
"""Generate SVG network diagram"""
logger.info(f"Generating SVG diagram: {output_file}")
# Use the existing SVG generator
generator = NetworkDiagramGenerator(self.combined_data)
generator.generate_svg(output_file)
logger.info(f"SVG diagram generated: {output_file}")
def export_comprehensive_json(self, output_file: str):
"""Export comprehensive network data"""
logger.info(f"Exporting comprehensive data to {output_file}")
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(self.combined_data, f, indent=2, ensure_ascii=False)
logger.info(f"Comprehensive network data exported to {output_file}")
def print_summary(self):
"""Print comprehensive network summary"""
print("\n" + "="*80)
print("COMPREHENSIVE NETWORK MAPPING SUMMARY")
print("="*80)
print(f"\nData Sources: {', '.join(self.combined_data['data_sources'])}")
print(f"Network Segments: {len(self.combined_data['segments'])}")
print(f"pfSense Firewalls: {len(self.combined_data['pfsense_firewalls'])}")
print(f"WireGuard Networks: {len(self.combined_data['wireguard_networks'])}")
print(f"OpenVPN Networks: {len(self.combined_data['openvpn_networks'])}")
print(f"Static Routes: {len(self.combined_data['routing_table'])}")
print(f"DHCP Static Mappings: {len(self.combined_data['static_mappings'])}")
# Network segments summary
print(f"\n{'='*80}")
print("NETWORK SEGMENTS")
print(f"{'='*80}")
for segment in self.combined_data['segments']:
vpn_indicator = " (VPN)" if segment.get('is_vpn') else ""
pfsense_indicator = f" [{segment.get('pfsense_host')}]" if segment.get('pfsense_host') else ""
print(f"\n📡 {segment['name']}{vpn_indicator}{pfsense_indicator}")
print(f" CIDR: {segment['cidr']}")
print(f" Gateway: {segment.get('gateway', 'N/A')}")
print(f" Devices: {len(segment.get('devices', []))}")
# Show devices
for device in segment.get('devices', [])[:5]: # Show first 5
print(f"{device.get('ip', 'N/A')} - {device.get('hostname', 'N/A')} ({device.get('device_type', 'N/A')})")
if len(segment.get('devices', [])) > 5:
print(f" ... and {len(segment.get('devices', [])) - 5} more devices")
# pfSense summary
if self.combined_data['pfsense_firewalls']:
print(f"\n{'='*80}")
print("PFSENSE FIREWALLS")
print(f"{'='*80}")
for pfsense in self.combined_data['pfsense_firewalls']:
hostname = pfsense.get('hostname', 'Unknown')
print(f"\n🛡️ {hostname}")
print(f" Interfaces: {len(pfsense.get('interfaces', {}))}")
print(f" Static Routes: {len(pfsense.get('static_routes', []))}")
wg_config = pfsense.get('wireguard', {})
if wg_config.get('enabled'):
print(f" WireGuard: {len(wg_config.get('tunnels', []))} tunnels, {len(wg_config.get('peers', []))} peers")
dhcp_config = pfsense.get('dhcp', {})
total_mappings = sum(len(dhcp.get('static_mappings', [])) for dhcp in dhcp_config.values())
print(f" DHCP Static Mappings: {total_mappings}")
# VPN Networks
if self.combined_data['wireguard_networks'] or self.combined_data['openvpn_networks']:
print(f"\n{'='*80}")
print("VPN NETWORKS")
print(f"{'='*80}")
for wg_net in self.combined_data['wireguard_networks']:
print(f"\n🔐 WireGuard: {wg_net['network']} via {wg_net['pfsense']}")
print(f" Interface: {wg_net['interface']}")
print(f" Peers: {len(wg_net.get('peers', []))}")
for ovpn_net in self.combined_data['openvpn_networks']:
print(f"\n🔐 OpenVPN: {ovpn_net['network']} via {ovpn_net['pfsense']}")
print(f" Interface: {ovpn_net['interface']}")
print(f"\n{'='*80}")
print("NETWORK TOPOLOGY ANALYSIS COMPLETE")
print(f"{'='*80}")
def main():
"""Command line interface"""
parser = argparse.ArgumentParser(
description='Comprehensive Network Diagram Generator with pfSense Integration'
)
parser.add_argument('-c', '--config', default='config.json',
help='Network scanner configuration file')
parser.add_argument('-s', '--scan-data', help='Existing network scan JSON file')
parser.add_argument('-p', '--pfsense-xml', nargs='+',
help='pfSense XML configuration files')
parser.add_argument('-o', '--output', default='comprehensive_network.json',
help='Output comprehensive JSON file')
parser.add_argument('--svg', help='Generate SVG diagram')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose output')
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
# Load configuration
config = {}
if Path(args.config).exists():
try:
with open(args.config, 'r') as f:
config = json.load(f)
except Exception as e:
logger.warning(f"Could not load config {args.config}: {e}")
# Initialize comprehensive mapper
mapper = ComprehensiveNetworkMapper(config)
# Load network scan data if provided
if args.scan_data and Path(args.scan_data).exists():
mapper.load_network_scan(args.scan_data)
else:
logger.info("No network scan data provided - will use pfSense data only")
# Load pfSense configurations
pfsense_files = args.pfsense_xml or []
# Auto-detect pfSense XML files if none specified
if not pfsense_files:
xml_files = list(Path('.').glob('config-*.xml'))
if xml_files:
pfsense_files = [str(f) for f in xml_files]
logger.info(f"Auto-detected pfSense XML files: {pfsense_files}")
if pfsense_files:
mapper.load_pfsense_configs(pfsense_files)
else:
logger.warning("No pfSense XML files found")
# Merge all data
mapper.merge_network_data()
# Print summary
mapper.print_summary()
# Export comprehensive data
mapper.export_comprehensive_json(args.output)
print(f"\n✅ Comprehensive network data saved to: {args.output}")
# Generate SVG if requested
if args.svg:
svg_file = args.svg
mapper.generate_svg_diagram(svg_file)
print(f"✅ SVG network diagram generated: {svg_file}")
if __name__ == '__main__':
main()