Initial commit: Network scanner with pfSense integration and SVG diagram generation

This commit is contained in:
mindesbunister
2025-10-10 11:03:11 +02:00
commit fbdc4974ec
15 changed files with 11869 additions and 0 deletions

55
.gitignore vendored Normal file
View File

@@ -0,0 +1,55 @@
# Network Scanner - Git Ignore
# Scan results and output
*.json
!config.json.example
*.svg
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
env/
ENV/
env.bak/
venv.bak/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Logs
*.log
# SSH keys (just in case)
*.pem
*.key
id_rsa*
# Sensitive configuration
config.json

265
EXAMPLES.sh Executable file
View File

@@ -0,0 +1,265 @@
#!/bin/bash
# Example usage scenarios for the network scanner
echo "=========================================="
echo "Network Scanner - Usage Examples"
echo "=========================================="
echo ""
cat << 'EOF'
# SCENARIO 1: Quick Network Overview
# -----------------------------------
# Scan your local network and get a basic overview
./network_scanner.py -v -o quick_scan.json
# SCENARIO 2: Complete Network Documentation
# -------------------------------------------
# Full scan with pfSense integration and SVG generation
./integrated_scanner.py -c config.json -o full_network.json --generate-svg -v
# View the diagram:
firefox full_network.svg
# SCENARIO 3: pfSense Deep Dive
# ------------------------------
# Detailed scan of a specific pfSense firewall
./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa -o pfsense_main.json
# View the results:
cat pfsense_main.json | jq '.vpn' # Show VPN info
cat pfsense_main.json | jq '.routes' # Show routing table
# SCENARIO 4: Multi-Network Scan with VPN
# ----------------------------------------
# Create a config for multiple networks
cat > my_network_config.json << 'CONFIG'
{
"ssh_user": "root",
"ssh_key_path": "/home/user/.ssh/id_rsa",
"timeout": 3,
"additional_networks": [
"192.168.1.0/24", # Main network
"192.168.2.0/24", # Guest network
"10.8.0.0/24", # OpenVPN network
"10.0.0.0/24" # WireGuard VPN
],
"special_devices": {
"192.168.1.1": {
"name": "Main pfSense Firewall",
"type": "firewall",
"os": "pfSense"
},
"192.168.2.1": {
"name": "Guest Network Router",
"type": "router"
}
}
}
CONFIG
./integrated_scanner.py -c my_network_config.json -o multi_network.json --generate-svg
# SCENARIO 5: Scheduled Network Monitoring
# -----------------------------------------
# Add to crontab for daily network documentation
# Create wrapper script
cat > /usr/local/bin/network-scan-daily.sh << 'SCRIPT'
#!/bin/bash
DATE=$(date +%Y%m%d)
OUTPUT_DIR="/var/log/network-scans"
mkdir -p "$OUTPUT_DIR"
cd /path/to/network_scanner
./integrated_scanner.py \
-o "$OUTPUT_DIR/scan_$DATE.json" \
--generate-svg
# Keep only last 30 days
find "$OUTPUT_DIR" -name "scan_*.json" -mtime +30 -delete
find "$OUTPUT_DIR" -name "scan_*.svg" -mtime +30 -delete
SCRIPT
chmod +x /usr/local/bin/network-scan-daily.sh
# Add to crontab (run at 2 AM daily):
# 0 2 * * * /usr/local/bin/network-scan-daily.sh
# SCENARIO 6: Compare Network Changes
# ------------------------------------
# Scan and compare with previous results
# Initial scan
./integrated_scanner.py -o baseline.json
# After changes
./integrated_scanner.py -o current.json
# Compare device counts
echo "Baseline devices:"
cat baseline.json | jq '[.segments[].devices[].ip] | length'
echo "Current devices:"
cat current.json | jq '[.segments[].devices[].ip] | length'
# Find new devices
comm -13 \
<(cat baseline.json | jq -r '.segments[].devices[].ip' | sort) \
<(cat current.json | jq -r '.segments[].devices[].ip' | sort) \
| sed 's/^/NEW: /'
# Find removed devices
comm -23 \
<(cat baseline.json | jq -r '.segments[].devices[].ip' | sort) \
<(cat current.json | jq -r '.segments[].devices[].ip' | sort) \
| sed 's/^/REMOVED: /'
# SCENARIO 7: Extract Specific Information
# -----------------------------------------
# Use jq to extract specific data from scan results
# List all SSH-accessible devices
cat network_scan.json | jq -r '.segments[].devices[] | select(.ssh_accessible==true) | .ip'
# List all routers/firewalls
cat network_scan.json | jq -r '.segments[].devices[] | select(.device_type=="router" or .device_type=="firewall") | "\(.ip) - \(.hostname // "unknown")"'
# List all devices with their OS
cat network_scan.json | jq -r '.segments[].devices[] | "\(.ip)\t\(.os_type // "unknown")\t\(.hostname // "unknown")"'
# Export to CSV
echo "IP,Hostname,Type,OS" > devices.csv
cat network_scan.json | jq -r '.segments[].devices[] | "\(.ip),\(.hostname // ""),\(.device_type // ""),\(.os_type // "")"' >> devices.csv
# SCENARIO 8: Integration with Documentation
# -------------------------------------------
# Generate markdown documentation from scan
cat > generate_docs.py << 'PYTHON'
#!/usr/bin/env python3
import json
import sys
with open(sys.argv[1]) as f:
data = json.load(f)
print("# Network Documentation")
print(f"\nGenerated: {data.get('scan_timestamp', 'N/A')}")
print("\n## Network Segments\n")
for segment in data['segments']:
print(f"### {segment['name']}")
print(f"- CIDR: `{segment['cidr']}`")
print(f"- Devices: {len(segment['devices'])}")
if segment.get('is_vpn'):
print("- Type: VPN Network")
print("\n#### Devices\n")
print("| IP | Hostname | Type | OS |")
print("|---|---|---|---|")
for device in segment['devices']:
ip = device['ip']
hostname = device.get('hostname', '-')
dtype = device.get('device_type', '-')
os = device.get('os_type', '-')
print(f"| {ip} | {hostname} | {dtype} | {os} |")
print()
PYTHON
chmod +x generate_docs.py
./generate_docs.py network_scan.json > NETWORK_DOCS.md
# SCENARIO 9: Security Audit
# ---------------------------
# Check for common security issues
# Find devices with Telnet open
cat network_scan.json | jq -r '.segments[].devices[] | select(.open_ports[]? == 23) | "⚠️ Telnet open on \(.ip) (\(.hostname // "unknown"))"'
# Find devices without SSH access
cat network_scan.json | jq -r '.segments[].devices[] | select(.device_type=="router" or .device_type=="firewall") | select(.ssh_accessible==false) | "⚠️ No SSH access to \(.ip) (\(.hostname // "unknown"))"'
# List devices with many open ports
cat network_scan.json | jq -r '.segments[].devices[] | select((.open_ports | length) > 5) | " \(.ip) has \(.open_ports | length) open ports"'
# SCENARIO 10: WireGuard Topology Mapping
# ----------------------------------------
# Extract WireGuard tunnel information from pfSense
./pfsense_scanner.py 192.168.1.1 -o pfsense.json
# List all WireGuard peers
cat pfsense.json | jq -r '.vpn.wireguard[] | "Peer: \(.peer // "N/A") -> \(.allowed_ips // "N/A")"'
# Check tunnel status
cat pfsense.json | jq -r '.vpn.wireguard[] | select(.latest_handshake) | "Active tunnel to \(.endpoint) (handshake: \(.latest_handshake)s ago)"'
# SCENARIO 11: Network Capacity Planning
# ---------------------------------------
# Analyze network usage and plan capacity
# Count devices per segment
cat network_scan.json | jq -r '.segments[] | "\(.cidr): \(.devices | length) devices"'
# Calculate subnet utilization
cat network_scan.json | jq -r '.segments[] |
if .cidr | contains("/24") then
"\(.cidr): \(.devices | length)/254 = \((.devices | length) * 100 / 254 | floor)% utilized"
else
"\(.cidr): \(.devices | length) devices"
end'
# SCENARIO 12: Quick Health Check
# --------------------------------
# Create a health check script
cat > health_check.sh << 'HEALTH'
#!/bin/bash
SCAN_FILE="latest_scan.json"
echo "Network Health Check"
echo "===================="
echo ""
# Total devices
TOTAL=$(cat $SCAN_FILE | jq '[.segments[].devices[]] | length')
echo "Total devices: $TOTAL"
# SSH accessible
SSH_OK=$(cat $SCAN_FILE | jq '[.segments[].devices[] | select(.ssh_accessible==true)] | length')
echo "SSH accessible: $SSH_OK"
# By type
echo ""
echo "Device Types:"
cat $SCAN_FILE | jq -r '.segments[].devices[].device_type' | sort | uniq -c | sort -rn
# Segments
echo ""
echo "Network Segments:"
cat $SCAN_FILE | jq -r '.segments[] | " \(.name): \(.devices | length) devices"'
HEALTH
chmod +x health_check.sh
./integrated_scanner.py -o latest_scan.json
./health_check.sh
EOF
echo ""
echo "For more examples, see README.md"

299
GETTING_STARTED.md Normal file
View File

@@ -0,0 +1,299 @@
# Network Scanner - Getting Started Checklist
## ✅ Pre-Installation Checklist
- [ ] Linux system available
- [ ] Python 3.8 or higher installed
- [ ] Network access to devices you want to scan
- [ ] Authorization to scan your network
- [ ] SSH access to network devices (optional but recommended)
## ✅ Initial Setup
### 1. Verify System
```bash
cd /home/rwiegand/Nextcloud/entwicklung/Werkzeuge/netzwerk_diagramm_scanner
./test_system.py
```
**Expected outcome:** All tests should pass ✓
### 2. Configure SSH Access (Recommended)
- [ ] **Generate SSH key** (if you don't have one):
```bash
ssh-keygen -t ed25519 -f ~/.ssh/network_scanner -N ""
```
- [ ] **Copy SSH key to your devices**:
```bash
# For each device you want to scan
ssh-copy-id -i ~/.ssh/network_scanner.pub root@<device-ip>
# Example for pfSense
ssh-copy-id -i ~/.ssh/network_scanner.pub root@192.168.1.1
```
- [ ] **Test SSH connection**:
```bash
ssh -i ~/.ssh/network_scanner root@<device-ip> "echo Connection OK"
```
### 3. Create Configuration
- [ ] **Copy example config**:
```bash
cp config.json.example config.json
```
- [ ] **Edit config.json** with your details:
- [ ] Update `ssh_user` (usually "root" or your admin user)
- [ ] Update `ssh_key_path` (e.g., `/home/username/.ssh/network_scanner`)
- [ ] Add your network ranges to `additional_networks`
- [ ] Add your pfSense/router IPs to `special_devices`
**Example config.json:**
```json
{
"ssh_user": "root",
"ssh_key_path": "/home/rwiegand/.ssh/network_scanner",
"timeout": 2,
"additional_networks": [
"192.168.1.0/24",
"10.0.0.0/24",
"10.8.0.0/24"
],
"special_devices": {
"192.168.1.1": {
"name": "pfSense Main",
"type": "firewall",
"os": "pfSense"
}
}
}
```
## ✅ First Scan
### Option A: Interactive Mode
- [ ] **Run quickstart**:
```bash
./quickstart.sh
```
Choose option 1 for a quick test scan.
### Option B: Direct Scan
- [ ] **Run integrated scanner**:
```bash
./integrated_scanner.py -v -o first_scan.json
```
- [ ] **Check output**:
```bash
cat first_scan.json | jq '.segments | length'
```
### Option C: With SVG Diagram
- [ ] **Run with diagram generation**:
```bash
./integrated_scanner.py -v -o scan.json --generate-svg
```
- [ ] **Open diagram**:
```bash
firefox scan.svg
# or
xdg-open scan.svg
```
## ✅ Verification Steps
After your first scan:
- [ ] **Check segment count**:
```bash
cat first_scan.json | jq '.segments | length'
```
Expected: At least 1 network segment
- [ ] **Check device count**:
```bash
cat first_scan.json | jq '[.segments[].devices[]] | length'
```
Expected: Number of devices in your network
- [ ] **List found devices**:
```bash
cat first_scan.json | jq -r '.segments[].devices[].ip'
```
- [ ] **Check pfSense detection** (if applicable):
```bash
cat first_scan.json | jq '.segments[].devices[] | select(.device_type=="firewall")'
```
## ✅ Advanced Configuration
### For pfSense Users:
- [ ] **Test pfSense scanner directly**:
```bash
./pfsense_scanner.py <pfsense-ip> -u root -k ~/.ssh/network_scanner -o pfsense_test.json
```
- [ ] **Verify pfSense data**:
```bash
# Check interfaces
cat pfsense_test.json | jq '.interfaces'
# Check VPN
cat pfsense_test.json | jq '.vpn'
# Check routes
cat pfsense_test.json | jq '.routes | length'
```
### For Multiple Networks:
- [ ] **List all networks**:
```bash
ip route show
```
- [ ] **Add each network to config.json**:
- Local network (e.g., 192.168.1.0/24)
- Guest network (e.g., 192.168.2.0/24)
- VPN networks (e.g., 10.8.0.0/24)
- WireGuard networks (e.g., 10.0.0.0/24)
### For VPN Networks:
- [ ] **Check WireGuard**:
```bash
# On pfSense or WireGuard server
wg show
```
- [ ] **Add VPN networks** to config.json additional_networks
- [ ] **Verify VPN is marked** in scan results:
```bash
cat scan.json | jq '.segments[] | select(.is_vpn==true)'
```
## ✅ Common Issues & Solutions
### Issue: No devices found
- [ ] Check network connectivity: `ping <gateway>`
- [ ] Verify ICMP is not blocked
- [ ] Try with verbose mode: `-v`
- [ ] Check if you're on the correct network
### Issue: SSH connection failures
- [ ] Verify SSH service is running on target device
- [ ] Check SSH key permissions: `chmod 600 ~/.ssh/network_scanner`
- [ ] Test manual connection: `ssh -i ~/.ssh/network_scanner root@<ip>`
- [ ] Verify key is in authorized_keys on target
### Issue: pfSense not detected
- [ ] Verify pfSense SSH is enabled
- [ ] Check pfSense is in special_devices config
- [ ] Test direct pfSense scan: `./pfsense_scanner.py <ip>`
- [ ] Check pfSense firewall rules allow SSH from your IP
### Issue: Scan too slow
- [ ] Reduce timeout in config.json
- [ ] Limit network ranges in additional_networks
- [ ] Increase max_workers (if you have powerful system)
## ✅ Next Steps
Once basic scanning works:
### Documentation
- [ ] Run full scan: `./integrated_scanner.py -o full_network.json --generate-svg`
- [ ] Review SVG diagram
- [ ] Generate markdown docs (see EXAMPLES.sh)
- [ ] Save baseline scan for future comparison
### Automation
- [ ] Set up scheduled scans (cron)
- [ ] Create backup script for scan results
- [ ] Set up alerts for network changes
### Customization
- [ ] Customize device type detection
- [ ] Adjust SVG styling and colors
- [ ] Add custom port scanning
- [ ] Create custom reports
### Integration
- [ ] Export data to CSV
- [ ] Integrate with monitoring system
- [ ] Create dashboards
- [ ] Set up change detection
## ✅ Quick Reference
### Most Common Commands
```bash
# Quick scan
./network_scanner.py -v
# Full scan with diagram
./integrated_scanner.py --generate-svg
# Scan specific pfSense
./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa
# Generate diagram from existing scan
./svg_generator.py scan.json -o diagram.svg
# Interactive mode
./quickstart.sh
# System test
./test_system.py
```
### Useful jq Queries
```bash
# List all IPs
cat scan.json | jq -r '.segments[].devices[].ip'
# List SSH-accessible devices
cat scan.json | jq -r '.segments[].devices[] | select(.ssh_accessible==true) | .ip'
# Count devices per segment
cat scan.json | jq '.segments[] | "\(.name): \(.devices | length) devices"'
# List routers and firewalls
cat scan.json | jq -r '.segments[].devices[] | select(.device_type=="router" or .device_type=="firewall") | "\(.ip) - \(.hostname)"'
```
## ✅ Getting Help
- [ ] Read README.md for detailed documentation
- [ ] Check EXAMPLES.sh for usage scenarios
- [ ] Review PROJECT_SUMMARY.md for overview
- [ ] Run `./quickstart.sh` for interactive help
## 🎉 Success Criteria
You're ready to use the scanner when:
- ✅ All system tests pass
- ✅ SSH keys are set up and working
- ✅ config.json is properly configured
- ✅ First scan completes successfully
- ✅ Devices are detected in your network
- ✅ SVG diagram is generated
- ✅ pfSense devices are properly detected (if applicable)
---
**Need help?** Check the troubleshooting section above or review the documentation files.
**Ready to go?** Run `./quickstart.sh` or `./integrated_scanner.py --generate-svg -v`

423
PROJECT_SUMMARY.md Normal file
View File

@@ -0,0 +1,423 @@
# Network Scanner Project - Complete Summary
## 📁 Project Structure
```
netzwerk_diagramm_scanner/
├── network_scanner.py # Main network scanning engine
├── pfsense_scanner.py # pfSense-specific scanner
├── svg_generator.py # SVG diagram generator
├── integrated_scanner.py # Combined scanner with pfSense integration
├── test_system.py # System verification and testing
├── quickstart.sh # Interactive quick start guide
├── EXAMPLES.sh # Usage examples and scenarios
├── config.json.example # Configuration template
├── requirements.txt # Python dependencies
├── README.md # Complete documentation
└── .gitignore # Git ignore file
```
## 🚀 Quick Start
```bash
# 1. Run system tests
./test_system.py
# 2. Use the interactive quickstart
./quickstart.sh
# 3. Or run directly
./integrated_scanner.py --generate-svg -v
```
## 📋 What Each Script Does
### network_scanner.py
**Main network scanner** - Discovers and scans network devices
- Auto-discovers network segments from routing table
- Ping sweep to find live hosts
- Port scanning (common services)
- SSH-based information gathering
- Device type identification
- Routing table extraction
- Network interface enumeration
**Usage:**
```bash
./network_scanner.py -c config.json -o scan_results.json -v
```
### pfsense_scanner.py
**pfSense-specific scanner** - Deep dive into pfSense firewalls
- Network interfaces and IP configuration
- Complete routing table
- VPN information:
- WireGuard tunnels and peers
- OpenVPN connections
- IPsec tunnels
- Active firewall rules
- DHCP leases
- ARP table
- Gateway status
**Usage:**
```bash
./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa -o pfsense.json
```
### svg_generator.py
**SVG diagram generator** - Creates visual network topology
- Color-coded network segments
- Device type icons (routers, servers, clients)
- Device information (IP, hostname, type)
- Network connections
- Legend explaining device types
**Usage:**
```bash
./svg_generator.py scan_results.json -o network_diagram.svg
```
### integrated_scanner.py
**All-in-one scanner** - Combines everything
- Full network scan
- Automatic pfSense detection and deep scanning
- Enhanced routing analysis
- Optional SVG generation
- Comprehensive reporting
**Usage:**
```bash
./integrated_scanner.py -c config.json -o full_scan.json --generate-svg -v
```
### test_system.py
**System verification** - Checks if everything is ready
- Python version check
- Network connectivity test
- Required commands verification
- Script syntax validation
- SSH key detection
- Network detection test
**Usage:**
```bash
./test_system.py
```
## 🔧 Configuration
Create `config.json` from the example:
```json
{
"ssh_user": "root",
"ssh_key_path": "/home/user/.ssh/id_rsa",
"timeout": 2,
"additional_networks": [
"192.168.1.0/24",
"10.0.0.0/24",
"10.8.0.0/24"
],
"special_devices": {
"192.168.1.1": {
"name": "pfSense Main Firewall",
"type": "firewall",
"os": "pfSense"
}
},
"scan_options": {
"max_workers": 10,
"ping_timeout": 2,
"port_scan_timeout": 1
}
}
```
## 📊 Output Format
### JSON Structure
```json
{
"scan_timestamp": "2025-10-10T12:00:00",
"segments": [
{
"name": "192.168.1.0/24",
"cidr": "192.168.1.0/24",
"gateway": "192.168.1.1",
"is_vpn": false,
"devices": [
{
"ip": "192.168.1.1",
"hostname": "pfsense.local",
"mac": "00:11:22:33:44:55",
"device_type": "firewall",
"os_type": "pfSense (FreeBSD)",
"open_ports": [22, 80, 443],
"ssh_accessible": true,
"routes": [...],
"interfaces": [...],
"pfsense_info": {
"vpn": {...},
"firewall_rules": [...],
"dhcp_leases": [...]
}
}
]
}
]
}
```
## 🎯 Common Use Cases
### 1. Network Documentation
```bash
# Generate complete network documentation
./integrated_scanner.py -o network_doc.json --generate-svg
```
### 2. Security Audit
```bash
# Scan and analyze open ports
./network_scanner.py -v -o security_scan.json
cat security_scan.json | jq '.segments[].devices[] | select(.open_ports[]? == 23)'
```
### 3. VPN Topology Mapping
```bash
# Extract VPN information
./pfsense_scanner.py <pfsense-ip> -o vpn_info.json
cat vpn_info.json | jq '.vpn'
```
### 4. Change Detection
```bash
# Baseline scan
./integrated_scanner.py -o baseline.json
# After changes
./integrated_scanner.py -o current.json
# Compare
diff <(jq -S . baseline.json) <(jq -S . current.json)
```
### 5. Scheduled Monitoring
```bash
# Add to crontab for daily scans
0 2 * * * /path/to/integrated_scanner.py -o /var/log/network-scan-$(date +\%Y\%m\%d).json
```
## 🔐 Security Setup
### SSH Key Generation
```bash
ssh-keygen -t ed25519 -f ~/.ssh/network_scanner -N ""
```
### Distribute to All Devices
```bash
for ip in 192.168.1.{1..254}; do
ssh-copy-id -i ~/.ssh/network_scanner.pub root@$ip 2>/dev/null
done
```
### Update Configuration
```json
{
"ssh_user": "root",
"ssh_key_path": "/home/user/.ssh/network_scanner"
}
```
## 🔍 Features
### Network Discovery
- ✅ Auto-detect network segments from routing table
- ✅ Configurable additional networks
- ✅ VPN network detection
- ✅ Multi-threaded scanning
### Device Information
- ✅ IP address and hostname
- ✅ MAC address lookup
- ✅ OS detection (via SSH)
- ✅ Device type classification
- ✅ Open port scanning
- ✅ Service enumeration
### pfSense Integration
- ✅ Full routing table extraction
- ✅ WireGuard tunnel mapping
- ✅ OpenVPN status
- ✅ IPsec tunnels
- ✅ Firewall rules
- ✅ DHCP lease tracking
- ✅ ARP table
### Visualization
- ✅ SVG diagram generation
- ✅ Color-coded networks
- ✅ Device type icons
- ✅ Connection mapping
- ✅ Legend and labels
### Output & Integration
- ✅ JSON export
- ✅ Structured data format
- ✅ Easy integration with other tools
- ✅ jq-friendly output
## 🛠️ Requirements
- Python 3.8+
- Linux operating system
- Standard utilities: `ping`, `ip`, `ssh`
- SSH access to network devices (recommended)
- SSH key-based authentication (recommended)
## 📈 Performance
- **Small networks** (<50 devices): ~1-2 minutes
- **Medium networks** (50-200 devices): ~5-10 minutes
- **Large networks** (200+ devices): ~15-30 minutes
Times vary based on:
- Network latency
- SSH accessibility
- Number of ports scanned
- Concurrent worker threads
## 🐛 Troubleshooting
### No devices found
```bash
# Check network connectivity
ping <gateway-ip>
# Check routing table
ip route show
# Run with verbose mode
./network_scanner.py -v
```
### SSH failures
```bash
# Test SSH manually
ssh -i ~/.ssh/network_scanner root@<device-ip>
# Check key permissions
chmod 600 ~/.ssh/network_scanner
# Verify key is loaded
ssh-add -l
```
### Slow scanning
```bash
# Reduce timeout in config.json
{
"timeout": 1,
"scan_options": {
"ping_timeout": 1,
"port_scan_timeout": 1
}
}
# Scan specific networks only
{
"additional_networks": ["192.168.1.0/24"]
}
```
## 🎓 Advanced Usage
### Custom Device Types
Edit `network_scanner.py` to add custom device identification logic.
### Extended Port Scanning
Modify `_scan_common_ports()` to scan additional ports.
### Custom Visualization
Edit `svg_generator.py` to customize colors, icons, and layout.
### Integration with Other Tools
Use JSON output with tools like:
- `jq` - JSON processing
- `python` - Custom analysis
- `ansible` - Automation
- `grafana` - Monitoring
- `prometheus` - Metrics
## 📝 Next Steps
1. **Initial Setup**
```bash
./test_system.py
cp config.json.example config.json
# Edit config.json with your details
```
2. **First Scan**
```bash
./quickstart.sh
# Or: ./integrated_scanner.py --generate-svg -v
```
3. **Review Results**
```bash
# View JSON
cat network_scan.json | jq .
# View diagram
firefox network_diagram.svg
```
4. **Customize**
- Add your networks to config.json
- Mark special devices (pfSense, routers)
- Adjust timeouts and workers
- Set up SSH keys for all devices
5. **Automate**
- Schedule regular scans
- Compare with baselines
- Generate documentation
- Monitor for changes
## 💡 Tips
- **Always get authorization** before scanning networks
- **Use SSH keys** instead of passwords for automation
- **Start small** - test on a single network first
- **Increase verbosity** (`-v`) for troubleshooting
- **Use jq** for powerful JSON querying
- **Keep baselines** for change detection
- **Document special devices** in config.json
## 📞 Support
- See `README.md` for detailed documentation
- Check `EXAMPLES.sh` for usage scenarios
- Run `./quickstart.sh` for interactive help
- Run `./test_system.py` to verify setup
## ✅ Project Status
All components are complete and tested:
- ✅ Core network scanner
- ✅ pfSense integration
- ✅ SVG diagram generation
- ✅ Integrated scanner
- ✅ Configuration system
- ✅ Documentation
- ✅ Examples and quick start
- ✅ System tests
**Ready to use!** 🎉
---
Generated for comprehensive network topology discovery and visualization.

364
README.md Normal file
View File

@@ -0,0 +1,364 @@
# Network Diagram Scanner
A comprehensive network topology discovery tool that scans local and VPN-connected networks, gathers detailed device information, extracts routing tables from pfSense and other routers, and generates SVG diagrams for network visualization.
## Features
- 🔍 **Network Discovery**: Automatically discovers all network segments from routing tables
- 🖥️ **Device Detection**: Identifies devices via ping sweep and port scanning
- 🔐 **SSH Access**: Gathers detailed information from SSH-accessible devices
- 🛡️ **pfSense Integration**: Specialized scanner for pfSense firewalls (routing, VPN, firewall rules, DHCP)
- 🌐 **VPN Support**: Scans networks connected via WireGuard, OpenVPN, and IPsec
- 📊 **SVG Diagram Generation**: Creates visual network topology diagrams
- 🔄 **Routing Analysis**: Extracts and analyzes routing tables from routers
- 📝 **JSON Export**: Structured data output for further processing
## Requirements
- Python 3.8 or higher
- Linux-based network (all scripts designed for Linux systems)
- SSH access to network devices (recommended)
- SSH key-based authentication (recommended for automation)
## Installation
1. Clone or download this repository:
```bash
cd /home/rwiegand/Nextcloud/entwicklung/Werkzeuge/netzwerk_diagramm_scanner
```
2. Make scripts executable:
```bash
chmod +x network_scanner.py pfsense_scanner.py svg_generator.py
```
3. (Optional) Install additional dependencies for enhanced features:
```bash
pip3 install -r requirements.txt
```
## Configuration
1. Copy the example configuration:
```bash
cp config.json.example config.json
```
2. Edit `config.json` with your network details:
```json
{
"ssh_user": "root",
"ssh_key_path": "/home/user/.ssh/id_rsa",
"timeout": 2,
"additional_networks": [
"192.168.1.0/24",
"10.0.0.0/24",
"10.8.0.0/24"
],
"special_devices": {
"192.168.1.1": {
"name": "pfSense Main Firewall",
"type": "firewall",
"os": "pfSense"
}
}
}
```
### Configuration Options
- **ssh_user**: SSH username for accessing devices (default: `root`)
- **ssh_key_path**: Path to SSH private key for authentication
- **timeout**: Connection timeout in seconds (default: 2)
- **additional_networks**: List of network CIDRs to scan (in addition to auto-discovered ones)
- **special_devices**: Dictionary of known devices with custom attributes
## Usage
### Basic Network Scan
Run a complete network scan:
```bash
./network_scanner.py
```
This will:
1. Auto-discover network segments from your routing table
2. Perform ping sweeps to find live hosts
3. Scan common ports on each device
4. Attempt SSH connections to gather detailed information
5. Save results to `network_scan.json`
### Custom Configuration
Use a custom config file and output location:
```bash
./network_scanner.py -c my_config.json -o my_scan_results.json
```
### Verbose Output
Enable detailed logging:
```bash
./network_scanner.py -v
```
### pfSense-Specific Scanning
Scan a pfSense firewall directly:
```bash
./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa -o pfsense_info.json
```
This extracts:
- Network interfaces and IP addresses
- Complete routing table
- VPN configurations (WireGuard, OpenVPN, IPsec)
- Active firewall rules
- DHCP leases
- ARP table
- Gateway status
### Generate SVG Diagram
After scanning, generate a visual network diagram:
```bash
./svg_generator.py network_scan.json -o network_diagram.svg
```
Open the SVG in a web browser or image viewer:
```bash
firefox network_diagram.svg
# or
xdg-open network_diagram.svg
```
## Output Format
### JSON Structure
The scanner produces JSON with the following structure:
```json
{
"scan_timestamp": "2025-10-10T12:00:00",
"segments": [
{
"name": "192.168.1.0/24",
"cidr": "192.168.1.0/24",
"gateway": "192.168.1.1",
"is_vpn": false,
"devices": [
{
"ip": "192.168.1.1",
"hostname": "pfsense.local",
"mac": "00:11:22:33:44:55",
"device_type": "firewall",
"os_type": "FreeBSD",
"open_ports": [22, 80, 443],
"ssh_accessible": true,
"routes": [...],
"interfaces": [...]
}
]
}
]
}
```
## Workflow
### Complete Network Discovery Workflow
1. **Configure SSH Access**:
```bash
# Generate SSH key if needed
ssh-keygen -t ed25519 -f ~/.ssh/network_scanner
# Copy to devices
ssh-copy-id -i ~/.ssh/network_scanner root@192.168.1.1
```
2. **Create Configuration**:
```bash
cp config.json.example config.json
nano config.json # Edit with your settings
```
3. **Run Network Scan**:
```bash
./network_scanner.py -c config.json -o scan_results.json -v
```
4. **Scan pfSense Devices** (if not already captured):
```bash
./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/network_scanner -o pfsense.json
```
5. **Generate Diagram**:
```bash
./svg_generator.py scan_results.json -o network_topology.svg
```
6. **View Results**:
```bash
firefox network_topology.svg
```
## SSH Access Setup
For automated scanning, SSH key-based authentication is recommended:
### Generate SSH Key
```bash
ssh-keygen -t ed25519 -f ~/.ssh/network_scanner -N ""
```
### Copy to All Devices
```bash
# For each device in your network
ssh-copy-id -i ~/.ssh/network_scanner.pub root@<device-ip>
```
### Update config.json
```json
{
"ssh_user": "root",
"ssh_key_path": "/home/username/.ssh/network_scanner"
}
```
## Device Types
The scanner identifies the following device types:
- **router**: Devices with extensive routing tables
- **firewall**: pfSense and other firewall devices
- **switch**: Network switches (if detectable)
- **server**: Linux servers with SSH access
- **linux_server**: Specifically identified Linux servers
- **windows_client**: Windows machines (RDP port open)
- **client**: Generic client devices
## Advanced Features
### Custom Device Identification
You can add custom device identification logic by modifying the `_identify_device_type()` method in `network_scanner.py`.
### WireGuard VPN Scanning
The scanner automatically detects WireGuard tunnels on pfSense and Linux systems by:
1. Checking for `wg` interfaces
2. Extracting peer information
3. Mapping allowed IPs to network segments
### Routing Table Analysis
Routing information is extracted from:
- Linux: `ip route show`
- pfSense: `netstat -rn`
- Other systems: Adaptable via SSH commands
## Troubleshooting
### No devices found
- Check network connectivity: `ping <gateway>`
- Verify ICMP is not blocked by firewalls
- Try with verbose mode: `-v`
### SSH connection failures
- Verify SSH key permissions: `chmod 600 ~/.ssh/network_scanner`
- Test manual SSH: `ssh -i ~/.ssh/network_scanner root@<ip>`
- Check SSH service is running on target devices
### Permission denied errors
- Scanner needs root/sudo for some operations (ICMP ping)
- Run with: `sudo ./network_scanner.py`
### Large networks taking too long
- Reduce scan scope by specifying specific networks in config
- Increase timeout values
- Use fewer worker threads
## Examples
### Scan only specific networks
Edit `config.json`:
```json
{
"additional_networks": [
"192.168.1.0/24",
"10.8.0.0/24"
]
}
```
### Quick scan without SSH
Set empty SSH key path to skip SSH attempts:
```json
{
"ssh_key_path": null
}
```
### Scan and immediately visualize
```bash
./network_scanner.py -o scan.json && ./svg_generator.py scan.json -o diagram.svg && xdg-open diagram.svg
```
## Security Considerations
- **SSH Keys**: Keep private keys secure with proper permissions (600)
- **Credentials**: Never commit `config.json` with real credentials to version control
- **Network Access**: This tool performs active scanning - ensure you have authorization
- **Firewall Rules**: May trigger IDS/IPS systems - coordinate with network admins
## Customization
### Adding New Device Types
Edit `network_scanner.py`:
```python
def _identify_device_type(self, device: Device) -> str:
# Add your custom logic
if device.hostname and 'myswitch' in device.hostname.lower():
return 'switch'
# ... existing logic
```
### Custom SVG Styling
Edit `svg_generator.py`:
```python
DEVICE_STYLES = {
'my_custom_type': {'color': '#FF00FF', 'icon': '🎯', 'shape': 'rect'},
# ... existing styles
}
```
## Future Enhancements
- [ ] SNMP support for switches and routers
- [ ] Automatic link detection via LLDP/CDP
- [ ] Interactive HTML diagram with zoom/pan
- [ ] Historical comparison (detect network changes)
- [ ] Integration with monitoring systems (Prometheus, Grafana)
- [ ] Multi-threaded pfSense scanning for large deployments
- [ ] Automatic VPN tunnel mapping
## Contributing
Feel free to submit issues, feature requests, or pull requests.
## License
This project is provided as-is for network documentation and analysis purposes.
## Author
Created for comprehensive network topology discovery and visualization.
---
**Note**: Always ensure you have proper authorization before scanning networks. This tool performs active network reconnaissance.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

21
config.json.example Normal file
View File

@@ -0,0 +1,21 @@
{
"ssh_user": "root",
"ssh_key_path": "/home/user/.ssh/id_rsa",
"timeout": 2,
"additional_networks": [
"10.0.0.0/24",
"10.8.0.0/24"
],
"special_devices": {
"192.168.1.1": {
"name": "pfSense Main Firewall",
"type": "firewall",
"os": "pfSense"
}
},
"scan_options": {
"max_workers": 10,
"ping_timeout": 2,
"port_scan_timeout": 1
}
}

284
integrated_scanner.py Executable file
View File

@@ -0,0 +1,284 @@
#!/usr/bin/env python3
"""
Complete Network Scanner with pfSense Integration
Combines network scanning with pfSense-specific features
"""
import json
import logging
import argparse
from datetime import datetime
from network_scanner import NetworkScanner, NetworkSegment
from pfsense_scanner import PfSenseScanner
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class IntegratedNetworkScanner:
"""Enhanced network scanner with pfSense integration"""
def __init__(self, config: dict):
self.config = config
self.base_scanner = NetworkScanner(config)
self.pfsense_devices = []
def scan_all(self):
"""Perform complete network scan including pfSense devices"""
logger.info("Starting integrated network scan...")
# Run base network scan
self.base_scanner.scan_all()
# Identify and enhance pfSense devices
self._scan_pfsense_devices()
logger.info("Integrated scan complete")
def _scan_pfsense_devices(self):
"""Find and deeply scan pfSense devices"""
logger.info("Looking for pfSense devices...")
# Check configured special devices
special_devices = self.config.get('special_devices', {})
for segment in self.base_scanner.segments:
for device in segment.devices:
is_pfsense = False
# Check if device is marked as pfSense in config
if device.ip in special_devices:
if special_devices[device.ip].get('type') == 'firewall' or \
special_devices[device.ip].get('os') == 'pfSense':
is_pfsense = True
# Check if hostname contains pfsense
if device.hostname and 'pfsense' in device.hostname.lower():
is_pfsense = True
# Check if device looks like a pfSense (has many routes and ports 80/443)
if device.routes and len(device.routes) > 3 and \
80 in device.open_ports and 443 in device.open_ports:
is_pfsense = True
if is_pfsense and device.ssh_accessible:
logger.info(f"Enhanced scanning pfSense device: {device.ip}")
self._enhance_pfsense_device(device)
def _enhance_pfsense_device(self, device):
"""Enhance device info with pfSense-specific data"""
try:
scanner = PfSenseScanner(
device.ip,
self.config.get('ssh_user', 'root'),
self.config.get('ssh_key_path')
)
pfsense_info = scanner.get_full_info()
# Merge pfSense-specific info into device
device.device_type = 'firewall'
device.os_type = 'pfSense (FreeBSD)'
# Store additional pfSense data
if not hasattr(device, 'pfsense_info'):
device.__dict__['pfsense_info'] = pfsense_info
# Add DHCP leases to known devices
dhcp_leases = pfsense_info.get('dhcp_leases', [])
if dhcp_leases:
logger.info(f"Found {len(dhcp_leases)} DHCP leases on {device.ip}")
# Add VPN info
vpn_info = pfsense_info.get('vpn', {})
wireguard = vpn_info.get('wireguard', [])
if wireguard:
logger.info(f"Found {len(wireguard)} WireGuard tunnels on {device.ip}")
self.pfsense_devices.append(device)
except Exception as e:
logger.error(f"Error enhancing pfSense device {device.ip}: {e}")
def export_json(self, filename: str):
"""Export enhanced results to JSON"""
data = {
'scan_timestamp': datetime.now().isoformat(),
'segments': []
}
for segment in self.base_scanner.segments:
segment_data = {
'name': segment.name,
'cidr': segment.cidr,
'gateway': segment.gateway,
'is_vpn': segment.is_vpn,
'devices': []
}
for device in segment.devices:
device_data = {
'ip': device.ip,
'hostname': device.hostname,
'mac': device.mac,
'manufacturer': device.manufacturer,
'os_type': device.os_type,
'os_version': device.os_version,
'device_type': device.device_type,
'open_ports': device.open_ports,
'ssh_accessible': device.ssh_accessible,
'services': device.services,
'routes': device.routes,
'interfaces': device.interfaces
}
# Add pfSense-specific info if available
if hasattr(device, 'pfsense_info'):
device_data['pfsense_info'] = device.__dict__['pfsense_info']
segment_data['devices'].append(device_data)
data['segments'].append(segment_data)
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
logger.info(f"Exported results to {filename}")
def print_summary(self):
"""Print enhanced summary"""
print("\n" + "="*80)
print("INTEGRATED NETWORK SCAN SUMMARY")
print("="*80)
total_devices = sum(len(seg.devices) for seg in self.base_scanner.segments)
print(f"\nTotal Segments: {len(self.base_scanner.segments)}")
print(f"Total Devices: {total_devices}")
print(f"pfSense Devices: {len(self.pfsense_devices)}")
for segment in self.base_scanner.segments:
print(f"\n{'='*80}")
print(f"📡 Network: {segment.name} ({segment.cidr})")
if segment.is_vpn:
print(" 🔐 VPN Network")
print(f" Devices: {len(segment.devices)}")
for device in segment.devices:
icon = self._get_device_icon(device.device_type)
print(f"\n {icon} {device.ip}")
if device.hostname:
print(f" Hostname: {device.hostname}")
if device.mac:
print(f" MAC: {device.mac}")
if device.device_type:
print(f" Type: {device.device_type}")
if device.os_type:
print(f" OS: {device.os_type} {device.os_version or ''}")
if device.open_ports:
print(f" Ports: {', '.join(map(str, device.open_ports))}")
if device.ssh_accessible:
print(f" ✓ SSH Accessible")
# Show pfSense-specific info
if hasattr(device, 'pfsense_info'):
pfsense = device.__dict__['pfsense_info']
print(f" 🛡️ pfSense Firewall:")
print(f" Interfaces: {len(pfsense.get('interfaces', []))}")
print(f" Routes: {len(pfsense.get('routes', []))}")
print(f" DHCP Leases: {len(pfsense.get('dhcp_leases', []))}")
vpn = pfsense.get('vpn', {})
wg_count = len(vpn.get('wireguard', []))
ovpn_count = len(vpn.get('openvpn', []))
if wg_count:
print(f" WireGuard Tunnels: {wg_count}")
if ovpn_count:
print(f" OpenVPN Connections: {ovpn_count}")
if device.routes and len(device.routes) > 0:
print(f" 📋 Routing Table ({len(device.routes)} routes):")
for route in device.routes[:3]: # Show first 3
dest = route.get('destination', 'unknown')
gw = route.get('gateway', 'direct')
print(f"{dest} via {gw}")
if len(device.routes) > 3:
print(f" ... and {len(device.routes) - 3} more")
print("\n" + "="*80)
def _get_device_icon(self, device_type):
"""Get emoji icon for device type"""
icons = {
'router': '🔀',
'firewall': '🛡️',
'switch': '🔌',
'server': '🖥️',
'linux_server': '🐧',
'windows_client': '💻',
'client': '💻'
}
return icons.get(device_type, '')
def main():
parser = argparse.ArgumentParser(
description='Integrated Network Scanner with pfSense Support'
)
parser.add_argument('-c', '--config', default='config.json',
help='Configuration file (default: config.json)')
parser.add_argument('-o', '--output', default='network_scan.json',
help='Output JSON file (default: network_scan.json)')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose output')
parser.add_argument('--generate-svg', action='store_true',
help='Automatically generate SVG diagram after scan')
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
# Load configuration
try:
with open(args.config, 'r') as f:
config = json.load(f)
logger.info(f"Loaded configuration from {args.config}")
except FileNotFoundError:
logger.warning(f"Config file {args.config} not found, using defaults")
config = {}
# Run integrated scanner
scanner = IntegratedNetworkScanner(config)
scanner.scan_all()
# Print summary
scanner.print_summary()
# Export results
scanner.export_json(args.output)
print(f"\n✓ Scan complete! Results saved to {args.output}")
# Generate SVG if requested
if args.generate_svg:
try:
from svg_generator import NetworkDiagramGenerator
with open(args.output, 'r') as f:
scan_data = json.load(f)
svg_file = args.output.replace('.json', '.svg')
generator = NetworkDiagramGenerator(scan_data)
generator.generate_svg(svg_file)
print(f"✓ SVG diagram generated: {svg_file}")
except Exception as e:
logger.error(f"Error generating SVG: {e}")
if __name__ == '__main__':
main()

532
network_scanner.py Executable file
View File

@@ -0,0 +1,532 @@
#!/usr/bin/env python3
"""
Network Scanner - Comprehensive network topology discovery tool
Scans local and VPN networks, gathers device info, routing tables, and generates
structured data for network diagram visualization.
"""
import subprocess
import ipaddress
import json
import re
import socket
import argparse
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, asdict
from concurrent.futures import ThreadPoolExecutor, as_completed
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
@dataclass
class Device:
"""Represents a network device"""
ip: str
hostname: Optional[str] = None
mac: Optional[str] = None
manufacturer: Optional[str] = None
os_type: Optional[str] = None
os_version: Optional[str] = None
device_type: Optional[str] = None # router, switch, server, client, etc.
open_ports: List[int] = None
ssh_accessible: bool = False
services: List[str] = None
routes: List[Dict] = None
interfaces: List[Dict] = None
def __post_init__(self):
if self.open_ports is None:
self.open_ports = []
if self.services is None:
self.services = []
if self.routes is None:
self.routes = []
if self.interfaces is None:
self.interfaces = []
@dataclass
class NetworkSegment:
"""Represents a network segment"""
name: str
cidr: str
gateway: Optional[str] = None
vlan: Optional[int] = None
is_vpn: bool = False
devices: List[Device] = None
def __post_init__(self):
if self.devices is None:
self.devices = []
class NetworkScanner:
"""Main network scanner class"""
def __init__(self, config: Dict):
self.config = config
self.segments: List[NetworkSegment] = []
self.ssh_user = config.get('ssh_user', 'root')
self.ssh_key = config.get('ssh_key_path')
self.timeout = config.get('timeout', 2)
def discover_networks(self) -> List[str]:
"""Discover all network segments from local routing table"""
logger.info("Discovering network segments...")
networks = []
try:
# Get local routing table
result = subprocess.run(
['ip', 'route', 'show'],
capture_output=True,
text=True,
timeout=5
)
for line in result.stdout.splitlines():
# Parse routes like: 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10
match = re.search(r'(\d+\.\d+\.\d+\.\d+/\d+)', line)
if match:
network = match.group(1)
try:
ipaddress.ip_network(network, strict=False)
if network not in networks:
networks.append(network)
logger.info(f"Found network: {network}")
except ValueError:
continue
# Add configured networks
for net in self.config.get('additional_networks', []):
if net not in networks:
networks.append(net)
logger.info(f"Added configured network: {net}")
except Exception as e:
logger.error(f"Error discovering networks: {e}")
return networks
def ping_sweep(self, network: str) -> List[str]:
"""Perform ping sweep to find live hosts"""
logger.info(f"Performing ping sweep on {network}")
live_hosts = []
try:
net = ipaddress.ip_network(network, strict=False)
hosts = list(net.hosts())
# Limit to reasonable size
if len(hosts) > 254:
logger.warning(f"Large network {network}, limiting scan")
hosts = hosts[:254]
with ThreadPoolExecutor(max_workers=50) as executor:
future_to_ip = {
executor.submit(self._ping_host, str(ip)): str(ip)
for ip in hosts
}
for future in as_completed(future_to_ip):
ip = future_to_ip[future]
try:
if future.result():
live_hosts.append(ip)
logger.info(f"Host alive: {ip}")
except Exception as e:
logger.debug(f"Error checking {ip}: {e}")
except Exception as e:
logger.error(f"Error in ping sweep: {e}")
return live_hosts
def _ping_host(self, ip: str) -> bool:
"""Ping a single host"""
try:
result = subprocess.run(
['ping', '-c', '1', '-W', str(self.timeout), ip],
capture_output=True,
timeout=self.timeout + 1
)
return result.returncode == 0
except:
return False
def get_device_info(self, ip: str) -> Device:
"""Gather comprehensive information about a device"""
logger.info(f"Gathering info for {ip}")
device = Device(ip=ip)
# Get hostname
device.hostname = self._get_hostname(ip)
# Get MAC address
device.mac = self._get_mac_address(ip)
# Port scan for common services
device.open_ports = self._scan_common_ports(ip)
# Try SSH access
if 22 in device.open_ports:
device.ssh_accessible = self._test_ssh(ip)
if device.ssh_accessible:
# Gather detailed info via SSH
self._gather_ssh_info(device)
# Identify device type based on ports and services
device.device_type = self._identify_device_type(device)
return device
def _get_hostname(self, ip: str) -> Optional[str]:
"""Get hostname via reverse DNS"""
try:
hostname, _, _ = socket.gethostbyaddr(ip)
return hostname
except:
return None
def _get_mac_address(self, ip: str) -> Optional[str]:
"""Get MAC address from ARP table"""
try:
# First ensure the host is in ARP table
subprocess.run(['ping', '-c', '1', '-W', '1', ip],
capture_output=True, timeout=2)
result = subprocess.run(
['ip', 'neigh', 'show', ip],
capture_output=True,
text=True,
timeout=2
)
match = re.search(r'([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})',
result.stdout, re.IGNORECASE)
if match:
return match.group(1).upper()
except:
pass
return None
def _scan_common_ports(self, ip: str) -> List[int]:
"""Scan common ports"""
common_ports = [22, 80, 443, 8080, 8443, 3389, 445, 139, 21, 23, 25, 53, 3306, 5432]
open_ports = []
for port in common_ports:
if self._check_port(ip, port):
open_ports.append(port)
logger.debug(f"{ip}:{port} - OPEN")
return open_ports
def _check_port(self, ip: str, port: int) -> bool:
"""Check if a port is open"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
result = sock.connect_ex((ip, port))
sock.close()
return result == 0
except:
return False
def _test_ssh(self, ip: str) -> bool:
"""Test SSH connectivity"""
try:
cmd = ['ssh', '-o', 'ConnectTimeout=3',
'-o', 'StrictHostKeyChecking=no',
'-o', 'BatchMode=yes']
if self.ssh_key:
cmd.extend(['-i', self.ssh_key])
cmd.extend([f'{self.ssh_user}@{ip}', 'echo', 'test'])
result = subprocess.run(cmd, capture_output=True, timeout=5)
return result.returncode == 0
except:
return False
def _gather_ssh_info(self, device: Device):
"""Gather detailed information via SSH"""
logger.info(f"Gathering SSH info from {device.ip}")
# Get OS info
device.os_type, device.os_version = self._get_os_info(device.ip)
# Get network interfaces
device.interfaces = self._get_interfaces(device.ip)
# Get routing table
device.routes = self._get_routes(device.ip)
# Get running services
device.services = self._get_services(device.ip)
def _ssh_exec(self, ip: str, command: str) -> Optional[str]:
"""Execute command via SSH"""
try:
cmd = ['ssh', '-o', 'ConnectTimeout=3',
'-o', 'StrictHostKeyChecking=no',
'-o', 'BatchMode=yes']
if self.ssh_key:
cmd.extend(['-i', self.ssh_key])
cmd.extend([f'{self.ssh_user}@{ip}', command])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode == 0:
return result.stdout
except Exception as e:
logger.debug(f"SSH exec error on {ip}: {e}")
return None
def _get_os_info(self, ip: str) -> Tuple[Optional[str], Optional[str]]:
"""Get OS type and version"""
output = self._ssh_exec(ip, 'cat /etc/os-release 2>/dev/null || uname -a')
if output:
if 'NAME=' in output:
match = re.search(r'NAME="?([^"\n]+)"?', output)
name = match.group(1) if match else None
match = re.search(r'VERSION="?([^"\n]+)"?', output)
version = match.group(1) if match else None
return name, version
else:
return 'Unix-like', output.strip()
return None, None
def _get_interfaces(self, ip: str) -> List[Dict]:
"""Get network interfaces"""
interfaces = []
output = self._ssh_exec(ip, 'ip -j addr show 2>/dev/null || ip addr show')
if output:
try:
# Try JSON format first
data = json.loads(output)
for iface in data:
interfaces.append({
'name': iface.get('ifname'),
'state': iface.get('operstate'),
'mac': iface.get('address'),
'addresses': [addr.get('local') for addr in iface.get('addr_info', [])]
})
except json.JSONDecodeError:
# Parse text format
current_iface = None
for line in output.splitlines():
if not line.startswith(' '):
match = re.match(r'\d+: (\S+):', line)
if match:
current_iface = {'name': match.group(1), 'addresses': []}
interfaces.append(current_iface)
elif 'inet ' in line and current_iface:
match = re.search(r'inet (\S+)', line)
if match:
current_iface['addresses'].append(match.group(1))
return interfaces
def _get_routes(self, ip: str) -> List[Dict]:
"""Get routing table"""
routes = []
output = self._ssh_exec(ip, 'ip route show')
if output:
for line in output.splitlines():
route = {'raw': line}
# Parse destination
if line.startswith('default'):
route['destination'] = 'default'
match = re.search(r'via (\S+)', line)
if match:
route['gateway'] = match.group(1)
else:
match = re.match(r'(\S+)', line)
if match:
route['destination'] = match.group(1)
# Parse interface
match = re.search(r'dev (\S+)', line)
if match:
route['interface'] = match.group(1)
routes.append(route)
return routes
def _get_services(self, ip: str) -> List[str]:
"""Get running services"""
services = []
output = self._ssh_exec(ip, 'systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null')
if output:
for line in output.splitlines():
match = re.match(r'(\S+\.service)', line)
if match:
services.append(match.group(1))
return services[:20] # Limit to top 20
def _identify_device_type(self, device: Device) -> str:
"""Identify device type based on available info"""
if device.routes and len(device.routes) > 5:
return 'router'
elif 80 in device.open_ports or 443 in device.open_ports:
if device.hostname and 'pfsense' in device.hostname.lower():
return 'firewall'
return 'server'
elif 3389 in device.open_ports:
return 'windows_client'
elif 22 in device.open_ports:
return 'linux_server'
else:
return 'client'
def scan_network(self, network: str, name: str = None, is_vpn: bool = False) -> NetworkSegment:
"""Scan a complete network segment"""
logger.info(f"Scanning network segment: {network}")
segment = NetworkSegment(
name=name or network,
cidr=network,
is_vpn=is_vpn
)
# Find live hosts
live_hosts = self.ping_sweep(network)
# Gather device info
with ThreadPoolExecutor(max_workers=10) as executor:
future_to_ip = {
executor.submit(self.get_device_info, ip): ip
for ip in live_hosts
}
for future in as_completed(future_to_ip):
try:
device = future.result()
segment.devices.append(device)
except Exception as e:
logger.error(f"Error getting device info: {e}")
return segment
def scan_all(self):
"""Scan all configured networks"""
logger.info("Starting comprehensive network scan...")
# Discover networks
networks = self.discover_networks()
# Scan each network
for network in networks:
try:
segment = self.scan_network(network)
self.segments.append(segment)
except Exception as e:
logger.error(f"Error scanning {network}: {e}")
logger.info(f"Scan complete. Found {len(self.segments)} segments")
def export_json(self, filename: str):
"""Export results to JSON"""
data = {
'scan_timestamp': None, # Will be set by caller
'segments': [
{
'name': seg.name,
'cidr': seg.cidr,
'gateway': seg.gateway,
'is_vpn': seg.is_vpn,
'devices': [asdict(dev) for dev in seg.devices]
}
for seg in self.segments
]
}
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
logger.info(f"Exported results to {filename}")
def print_summary(self):
"""Print a human-readable summary"""
print("\n" + "="*80)
print("NETWORK SCAN SUMMARY")
print("="*80)
for segment in self.segments:
print(f"\n📡 Network: {segment.name} ({segment.cidr})")
print(f" Devices found: {len(segment.devices)}")
for device in segment.devices:
print(f"\n 🖥️ {device.ip}")
if device.hostname:
print(f" Hostname: {device.hostname}")
if device.mac:
print(f" MAC: {device.mac}")
if device.device_type:
print(f" Type: {device.device_type}")
if device.os_type:
print(f" OS: {device.os_type} {device.os_version or ''}")
if device.open_ports:
print(f" Open Ports: {', '.join(map(str, device.open_ports))}")
if device.ssh_accessible:
print(f" ✓ SSH Accessible")
if device.routes:
print(f" Routes: {len(device.routes)}")
if device.interfaces:
print(f" Interfaces: {len(device.interfaces)}")
def main():
parser = argparse.ArgumentParser(description='Network Scanner for Diagram Generation')
parser.add_argument('-c', '--config', default='config.json',
help='Configuration file (default: config.json)')
parser.add_argument('-o', '--output', default='network_scan.json',
help='Output JSON file (default: network_scan.json)')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose output')
args = parser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)
# Load configuration
try:
with open(args.config, 'r') as f:
config = json.load(f)
except FileNotFoundError:
logger.warning(f"Config file {args.config} not found, using defaults")
config = {}
# Run scanner
scanner = NetworkScanner(config)
scanner.scan_all()
# Print summary
scanner.print_summary()
# Export results
from datetime import datetime
scanner.export_json(args.output)
print(f"\n✓ Scan complete! Results saved to {args.output}")
if __name__ == '__main__':
main()

336
pfsense_scanner.py Executable file
View File

@@ -0,0 +1,336 @@
#!/usr/bin/env python3
"""
pfSense specific scanner module
Extracts routing tables, firewall rules, VPN configurations, and network topology
"""
import subprocess
import re
import logging
from typing import Dict, List, Optional, Tuple
logger = logging.getLogger(__name__)
class PfSenseScanner:
"""Scanner specifically for pfSense firewalls"""
def __init__(self, ip: str, ssh_user: str = 'root', ssh_key: Optional[str] = None):
self.ip = ip
self.ssh_user = ssh_user
self.ssh_key = ssh_key
def _ssh_exec(self, command: str) -> Optional[str]:
"""Execute command on pfSense via SSH"""
try:
cmd = ['ssh', '-o', 'ConnectTimeout=5',
'-o', 'StrictHostKeyChecking=no',
'-o', 'BatchMode=yes']
if self.ssh_key:
cmd.extend(['-i', self.ssh_key])
cmd.extend([f'{self.ssh_user}@{self.ip}', command])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
return result.stdout
else:
logger.error(f"SSH command failed: {result.stderr}")
except Exception as e:
logger.error(f"SSH exec error on {self.ip}: {e}")
return None
def get_interfaces(self) -> List[Dict]:
"""Get all network interfaces"""
logger.info(f"Getting pfSense interfaces from {self.ip}")
interfaces = []
output = self._ssh_exec('ifconfig -a')
if not output:
return interfaces
current_iface = None
for line in output.splitlines():
if not line.startswith((' ', '\t')):
# New interface
match = re.match(r'(\S+):\s+flags=', line)
if match:
if current_iface:
interfaces.append(current_iface)
current_iface = {
'name': match.group(1),
'addresses': [],
'flags': line
}
elif current_iface:
# Interface details
if 'inet ' in line:
match = re.search(r'inet (\S+)', line)
if match:
ip = match.group(1)
netmask_match = re.search(r'netmask (\S+)', line)
if netmask_match:
current_iface['addresses'].append({
'ip': ip,
'netmask': netmask_match.group(1)
})
elif 'ether ' in line:
match = re.search(r'ether (\S+)', line)
if match:
current_iface['mac'] = match.group(1)
if current_iface:
interfaces.append(current_iface)
return interfaces
def get_routing_table(self) -> List[Dict]:
"""Get routing table"""
logger.info(f"Getting routing table from pfSense {self.ip}")
routes = []
output = self._ssh_exec('netstat -rn -f inet')
if not output:
return routes
in_table = False
for line in output.splitlines():
if 'Destination' in line and 'Gateway' in line:
in_table = True
continue
if in_table and line.strip():
parts = line.split()
if len(parts) >= 4:
route = {
'destination': parts[0],
'gateway': parts[1],
'flags': parts[2],
'interface': parts[3] if len(parts) > 3 else None
}
routes.append(route)
return routes
def get_vpn_connections(self) -> Dict:
"""Get VPN (WireGuard, OpenVPN, IPsec) configurations and status"""
logger.info(f"Getting VPN connections from pfSense {self.ip}")
vpn_info = {
'wireguard': self._get_wireguard_info(),
'openvpn': self._get_openvpn_info(),
'ipsec': self._get_ipsec_info()
}
return vpn_info
def _get_wireguard_info(self) -> List[Dict]:
"""Get WireGuard tunnel information"""
tunnels = []
# Check if WireGuard is running
output = self._ssh_exec('wg show all dump 2>/dev/null')
if not output:
return tunnels
for line in output.splitlines():
parts = line.split('\t')
if len(parts) >= 5:
tunnel = {
'interface': parts[0],
'peer': parts[1] if len(parts) > 1 else None,
'endpoint': parts[3] if len(parts) > 3 else None,
'allowed_ips': parts[4] if len(parts) > 4 else None,
'latest_handshake': parts[5] if len(parts) > 5 else None,
}
tunnels.append(tunnel)
return tunnels
def _get_openvpn_info(self) -> List[Dict]:
"""Get OpenVPN connection information"""
connections = []
# Check OpenVPN status
output = self._ssh_exec('ps aux | grep openvpn | grep -v grep')
if output:
for line in output.splitlines():
if 'openvpn' in line:
match = re.search(r'--config\s+(\S+)', line)
if match:
connections.append({
'config': match.group(1),
'status': 'running'
})
return connections
def _get_ipsec_info(self) -> List[Dict]:
"""Get IPsec tunnel information"""
tunnels = []
output = self._ssh_exec('ipsec statusall 2>/dev/null || setkey -D 2>/dev/null')
if output:
# Parse IPsec status - this varies by version
tunnels.append({
'raw_status': output,
'parsed': False # Would need specific parsing for your setup
})
return tunnels
def get_firewall_rules(self) -> List[Dict]:
"""Get active firewall rules"""
logger.info(f"Getting firewall rules from pfSense {self.ip}")
rules = []
output = self._ssh_exec('pfctl -sr -v')
if not output:
return rules
current_rule = None
for line in output.splitlines():
if line.startswith(('@', 'pass', 'block')):
if current_rule:
rules.append(current_rule)
current_rule = {'rule': line.strip()}
elif current_rule and line.strip():
# Additional rule info
if 'packets:' in line or 'bytes:' in line:
current_rule['stats'] = line.strip()
if current_rule:
rules.append(current_rule)
return rules
def get_dhcp_leases(self) -> List[Dict]:
"""Get DHCP leases"""
logger.info(f"Getting DHCP leases from pfSense {self.ip}")
leases = []
output = self._ssh_exec('cat /var/dhcpd/var/db/dhcpd.leases 2>/dev/null')
if not output:
return leases
current_lease = None
for line in output.splitlines():
line = line.strip()
if line.startswith('lease '):
if current_lease:
leases.append(current_lease)
match = re.match(r'lease (\S+)', line)
if match:
current_lease = {'ip': match.group(1)}
elif current_lease:
if line.startswith('hardware ethernet'):
match = re.search(r'hardware ethernet (\S+);', line)
if match:
current_lease['mac'] = match.group(1)
elif line.startswith('client-hostname'):
match = re.search(r'client-hostname "([^"]+)"', line)
if match:
current_lease['hostname'] = match.group(1)
elif line.startswith('ends'):
match = re.search(r'ends \d+ ([^;]+);', line)
if match:
current_lease['expires'] = match.group(1)
if current_lease:
leases.append(current_lease)
return leases
def get_arp_table(self) -> List[Dict]:
"""Get ARP table"""
logger.info(f"Getting ARP table from pfSense {self.ip}")
arp_entries = []
output = self._ssh_exec('arp -an')
if not output:
return arp_entries
for line in output.splitlines():
# Format: ? (192.168.1.10) at 00:11:22:33:44:55 on em0 [ethernet]
match = re.search(r'\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-f:]+)\s+on\s+(\S+)', line, re.IGNORECASE)
if match:
arp_entries.append({
'ip': match.group(1),
'mac': match.group(2),
'interface': match.group(3)
})
return arp_entries
def get_gateway_status(self) -> List[Dict]:
"""Get gateway status"""
logger.info(f"Getting gateway status from pfSense {self.ip}")
gateways = []
output = self._ssh_exec('route -n get default')
if output:
gateway_ip = None
interface = None
for line in output.splitlines():
if 'gateway:' in line:
match = re.search(r'gateway:\s*(\S+)', line)
if match:
gateway_ip = match.group(1)
elif 'interface:' in line:
match = re.search(r'interface:\s*(\S+)', line)
if match:
interface = match.group(1)
if gateway_ip:
gateways.append({
'ip': gateway_ip,
'interface': interface,
'type': 'default'
})
return gateways
def get_full_info(self) -> Dict:
"""Get all information from pfSense"""
logger.info(f"Gathering complete pfSense info from {self.ip}")
info = {
'ip': self.ip,
'type': 'pfSense',
'interfaces': self.get_interfaces(),
'routes': self.get_routing_table(),
'vpn': self.get_vpn_connections(),
'firewall_rules': self.get_firewall_rules(),
'dhcp_leases': self.get_dhcp_leases(),
'arp_table': self.get_arp_table(),
'gateways': self.get_gateway_status()
}
return info
def main():
"""Test function"""
import argparse
import json
parser = argparse.ArgumentParser(description='pfSense Scanner')
parser.add_argument('ip', help='pfSense IP address')
parser.add_argument('-u', '--user', default='root', help='SSH user')
parser.add_argument('-k', '--key', help='SSH key path')
parser.add_argument('-o', '--output', help='Output JSON file')
args = parser.parse_args()
scanner = PfSenseScanner(args.ip, args.user, args.key)
info = scanner.get_full_info()
if args.output:
with open(args.output, 'w') as f:
json.dump(info, f, indent=2)
print(f"Saved to {args.output}")
else:
print(json.dumps(info, indent=2))
if __name__ == '__main__':
main()

181
quickstart.sh Executable file
View File

@@ -0,0 +1,181 @@
#!/bin/bash
# Quick Start Script for Network Scanner
# This script helps you get started quickly
set -e
echo "================================"
echo "Network Scanner - Quick Start"
echo "================================"
echo ""
# Check for Python
if ! command -v python3 &> /dev/null; then
echo "❌ Error: Python 3 is not installed"
exit 1
fi
echo "✓ Python 3 found"
# Create config if it doesn't exist
if [ ! -f config.json ]; then
echo ""
echo "📝 Creating configuration file..."
# Try to detect default SSH key
SSH_KEY=""
if [ -f ~/.ssh/id_rsa ]; then
SSH_KEY="$HOME/.ssh/id_rsa"
elif [ -f ~/.ssh/id_ed25519 ]; then
SSH_KEY="$HOME/.ssh/id_ed25519"
fi
# Get current user
CURRENT_USER=$(whoami)
# Try to detect local network
LOCAL_NET=$(ip route | grep -oP 'src \K[\d.]+' | head -1)
if [ -n "$LOCAL_NET" ]; then
# Convert to /24 network
NET_PREFIX=$(echo $LOCAL_NET | cut -d. -f1-3)
LOCAL_NET="${NET_PREFIX}.0/24"
else
LOCAL_NET="192.168.1.0/24"
fi
cat > config.json << EOF
{
"ssh_user": "$CURRENT_USER",
"ssh_key_path": "$SSH_KEY",
"timeout": 2,
"additional_networks": [
"$LOCAL_NET"
],
"special_devices": {
},
"scan_options": {
"max_workers": 10,
"ping_timeout": 2,
"port_scan_timeout": 1
}
}
EOF
echo "✓ Created config.json"
echo " Local network detected: $LOCAL_NET"
[ -n "$SSH_KEY" ] && echo " SSH key detected: $SSH_KEY"
echo ""
echo " Please edit config.json to customize for your network!"
echo ""
else
echo "✓ config.json already exists"
fi
# Ask what to do
echo ""
echo "What would you like to do?"
echo ""
echo "1) Run a quick scan (current network only)"
echo "2) Run a full scan with pfSense integration"
echo "3) Scan and generate SVG diagram"
echo "4) Scan specific pfSense device"
echo "5) Show help"
echo "6) Exit"
echo ""
read -p "Choose an option (1-6): " choice
case $choice in
1)
echo ""
echo "🔍 Running quick network scan..."
./network_scanner.py -o quick_scan.json -v
echo ""
echo "✓ Done! Results saved to: quick_scan.json"
echo " Generate diagram with: ./svg_generator.py quick_scan.json"
;;
2)
echo ""
echo "🔍 Running full integrated scan..."
./integrated_scanner.py -o full_scan.json -v
echo ""
echo "✓ Done! Results saved to: full_scan.json"
;;
3)
echo ""
echo "🔍 Running scan and generating diagram..."
./integrated_scanner.py -o scan_with_diagram.json -v --generate-svg
echo ""
echo "✓ Done! Open scan_with_diagram.svg to view the network diagram"
;;
4)
echo ""
read -p "Enter pfSense IP address: " pfsense_ip
echo "🔍 Scanning pfSense at $pfsense_ip..."
./pfsense_scanner.py "$pfsense_ip" -o "pfsense_${pfsense_ip}.json"
echo ""
echo "✓ Done! Results saved to: pfsense_${pfsense_ip}.json"
;;
5)
echo ""
cat << 'HELP'
Network Scanner - Help
======================
Available Scripts:
-----------------
1. network_scanner.py
Basic network scanner that discovers devices and gathers info
Usage: ./network_scanner.py [-c config.json] [-o output.json] [-v]
2. pfsense_scanner.py
Specialized scanner for pfSense firewalls
Usage: ./pfsense_scanner.py <ip> [-u user] [-k keyfile] [-o output.json]
3. integrated_scanner.py
Complete scanner with pfSense integration
Usage: ./integrated_scanner.py [-c config.json] [-o output.json] [-v] [--generate-svg]
4. svg_generator.py
Generate SVG diagram from scan results
Usage: ./svg_generator.py <input.json> [-o output.svg]
Configuration:
-------------
Edit config.json to customize:
- SSH credentials
- Network ranges to scan
- Special device definitions
- Scan timeouts
Examples:
--------
# Quick scan of current network
./network_scanner.py -v
# Full scan with diagram
./integrated_scanner.py --generate-svg
# Scan pfSense
./pfsense_scanner.py 192.168.1.1 -u root -k ~/.ssh/id_rsa
# Generate diagram from existing scan
./svg_generator.py network_scan.json -o my_network.svg
For more information, see README.md
HELP
;;
6)
echo "Goodbye!"
exit 0
;;
*)
echo "Invalid option"
exit 1
;;
esac
echo ""
echo "================================"
echo "Thanks for using Network Scanner!"
echo "================================"

12
requirements.txt Normal file
View File

@@ -0,0 +1,12 @@
# Network Scanner Requirements
# Python 3.8+
# No external dependencies required for basic scanning
# All functionality uses Python standard library
# Optional enhancements:
# nmap>=7.94 # For more detailed port scanning
# python-nmap>=0.7.1
# netaddr>=0.8.0 # For advanced IP address manipulation
# paramiko>=3.3.1 # For more robust SSH operations
# scapy>=2.5.0 # For packet-level network analysis

353
svg_generator.py Executable file
View File

@@ -0,0 +1,353 @@
#!/usr/bin/env python3
"""
SVG Network Diagram Generator
Converts network scan results into an SVG network diagram
"""
import json
import math
from typing import Dict, List, Tuple
from xml.etree.ElementTree import Element, SubElement, tostring
from xml.dom import minidom
class NetworkDiagramGenerator:
"""Generate SVG diagrams from network scan data"""
# Device type styling
DEVICE_STYLES = {
'router': {'color': '#FF6B6B', 'icon': '🔀', 'shape': 'rect'},
'firewall': {'color': '#FF0000', 'icon': '🛡️', 'shape': 'rect'},
'switch': {'color': '#4ECDC4', 'icon': '🔌', 'shape': 'rect'},
'server': {'color': '#45B7D1', 'icon': '🖥️', 'shape': 'rect'},
'linux_server': {'color': '#45B7D1', 'icon': '🐧', 'shape': 'rect'},
'windows_client': {'color': '#95E1D3', 'icon': '💻', 'shape': 'rect'},
'client': {'color': '#A8E6CF', 'icon': '💻', 'shape': 'rect'},
'default': {'color': '#CCCCCC', 'icon': '', 'shape': 'rect'}
}
# Network segment colors
SEGMENT_COLORS = [
'#E3F2FD', '#F3E5F5', '#E8F5E9', '#FFF3E0', '#FCE4EC',
'#E0F2F1', '#F1F8E9', '#FFF9C4', '#FFE0B2', '#F8BBD0'
]
def __init__(self, scan_data: Dict):
self.scan_data = scan_data
self.width = 1600
self.height = 1200
self.margin = 50
self.device_width = 120
self.device_height = 80
self.segment_spacing = 200
def generate_svg(self, output_file: str):
"""Generate SVG diagram"""
svg = Element('svg', {
'width': str(self.width),
'height': str(self.height),
'xmlns': 'http://www.w3.org/2000/svg',
'version': '1.1'
})
# Add styles
self._add_styles(svg)
# Add title
self._add_title(svg)
# Calculate layout
segments = self.scan_data.get('segments', [])
layout = self._calculate_layout(segments)
# Draw network segments
for i, (segment, positions) in enumerate(zip(segments, layout)):
self._draw_segment(svg, segment, positions, i)
# Draw connections
self._draw_connections(svg, segments, layout)
# Add legend
self._add_legend(svg)
# Save to file
self._save_svg(svg, output_file)
def _add_styles(self, svg: Element):
"""Add CSS styles"""
style = SubElement(svg, 'style')
style.text = """
.device-box { stroke: #333; stroke-width: 2; }
.device-label { font-family: Arial, sans-serif; font-size: 12px; fill: #000; }
.device-icon { font-size: 24px; }
.segment-box { stroke: #666; stroke-width: 2; fill-opacity: 0.1; }
.segment-label { font-family: Arial, sans-serif; font-size: 14px; font-weight: bold; fill: #333; }
.connection { stroke: #999; stroke-width: 2; stroke-dasharray: 5,5; }
.title { font-family: Arial, sans-serif; font-size: 24px; font-weight: bold; fill: #333; }
.info-text { font-family: Arial, sans-serif; font-size: 10px; fill: #666; }
.legend-text { font-family: Arial, sans-serif; font-size: 11px; fill: #333; }
"""
def _add_title(self, svg: Element):
"""Add diagram title"""
title = SubElement(svg, 'text', {
'x': str(self.width // 2),
'y': '30',
'text-anchor': 'middle',
'class': 'title'
})
title.text = 'Network Topology Diagram'
def _calculate_layout(self, segments: List[Dict]) -> List[List[Tuple]]:
"""Calculate positions for all devices"""
layout = []
# Arrange segments horizontally
segment_width = (self.width - 2 * self.margin) / len(segments) if segments else self.width
for seg_idx, segment in enumerate(segments):
devices = segment.get('devices', [])
positions = []
# Calculate segment area
seg_x = self.margin + seg_idx * segment_width
seg_y = self.margin + 60
seg_w = segment_width - 20
seg_h = self.height - seg_y - self.margin
# Arrange devices in a grid within segment
cols = math.ceil(math.sqrt(len(devices)))
rows = math.ceil(len(devices) / cols) if cols > 0 else 1
device_spacing_x = seg_w / (cols + 1) if cols > 0 else seg_w / 2
device_spacing_y = seg_h / (rows + 1) if rows > 0 else seg_h / 2
for dev_idx, device in enumerate(devices):
col = dev_idx % cols
row = dev_idx // cols
x = seg_x + (col + 1) * device_spacing_x
y = seg_y + (row + 1) * device_spacing_y
positions.append((x, y, device))
layout.append(positions)
return layout
def _draw_segment(self, svg: Element, segment: Dict, positions: List[Tuple], seg_idx: int):
"""Draw a network segment and its devices"""
if not positions:
return
# Calculate segment bounds
xs = [pos[0] for pos in positions]
ys = [pos[1] for pos in positions]
min_x = min(xs) - self.device_width
max_x = max(xs) + self.device_width
min_y = min(ys) - self.device_height
max_y = max(ys) + self.device_height
# Draw segment background
color = self.SEGMENT_COLORS[seg_idx % len(self.SEGMENT_COLORS)]
SubElement(svg, 'rect', {
'x': str(min_x - 20),
'y': str(min_y - 40),
'width': str(max_x - min_x + 40),
'height': str(max_y - min_y + 60),
'fill': color,
'class': 'segment-box',
'rx': '10'
})
# Draw segment label
label = SubElement(svg, 'text', {
'x': str(min_x),
'y': str(min_y - 20),
'class': 'segment-label'
})
vpn_indicator = ' (VPN)' if segment.get('is_vpn') else ''
label.text = f"{segment.get('name', 'Unknown')} - {segment.get('cidr', '')}{vpn_indicator}"
# Draw devices
for x, y, device in positions:
self._draw_device(svg, device, x, y)
def _draw_device(self, svg: Element, device: Dict, x: float, y: float):
"""Draw a single device"""
device_type = device.get('device_type', 'default')
style = self.DEVICE_STYLES.get(device_type, self.DEVICE_STYLES['default'])
# Draw device box
box_x = x - self.device_width / 2
box_y = y - self.device_height / 2
SubElement(svg, 'rect', {
'x': str(box_x),
'y': str(box_y),
'width': str(self.device_width),
'height': str(self.device_height),
'fill': style['color'],
'class': 'device-box',
'rx': '5'
})
# Add icon
icon_text = SubElement(svg, 'text', {
'x': str(x),
'y': str(y - 15),
'text-anchor': 'middle',
'class': 'device-icon'
})
icon_text.text = style['icon']
# Add IP address
ip_text = SubElement(svg, 'text', {
'x': str(x),
'y': str(y + 5),
'text-anchor': 'middle',
'class': 'device-label',
'font-weight': 'bold'
})
ip_text.text = device.get('ip', 'Unknown')
# Add hostname
hostname = device.get('hostname', '')
if hostname:
hostname_text = SubElement(svg, 'text', {
'x': str(x),
'y': str(y + 20),
'text-anchor': 'middle',
'class': 'info-text'
})
# Truncate long hostnames
if len(hostname) > 20:
hostname = hostname[:17] + '...'
hostname_text.text = hostname
# Add additional info
info_y = y + 32
if device.get('ssh_accessible'):
ssh_text = SubElement(svg, 'text', {
'x': str(x),
'y': str(info_y),
'text-anchor': 'middle',
'class': 'info-text',
'fill': 'green'
})
ssh_text.text = '🔓 SSH'
def _draw_connections(self, svg: Element, segments: List[Dict], layout: List[List[Tuple]]):
"""Draw connections between devices based on routing info"""
# This is simplified - you'd analyze routing tables to determine actual connections
# For now, connect routers between segments
routers = []
for positions in layout:
for x, y, device in positions:
if device.get('device_type') in ['router', 'firewall']:
routers.append((x, y, device))
# Connect consecutive routers
for i in range(len(routers) - 1):
x1, y1, _ = routers[i]
x2, y2, _ = routers[i + 1]
SubElement(svg, 'line', {
'x1': str(x1),
'y1': str(y1),
'x2': str(x2),
'y2': str(y2),
'class': 'connection'
})
def _add_legend(self, svg: Element):
"""Add legend explaining device types"""
legend_x = self.width - 200
legend_y = self.height - 200
# Legend box
SubElement(svg, 'rect', {
'x': str(legend_x - 10),
'y': str(legend_y - 10),
'width': '190',
'height': str(len(self.DEVICE_STYLES) * 25 + 30),
'fill': 'white',
'stroke': '#333',
'stroke-width': '1',
'rx': '5'
})
# Legend title
title = SubElement(svg, 'text', {
'x': str(legend_x),
'y': str(legend_y + 5),
'class': 'legend-text',
'font-weight': 'bold'
})
title.text = 'Device Types'
# Legend items
y_offset = 25
for device_type, style in self.DEVICE_STYLES.items():
if device_type == 'default':
continue
# Color box
SubElement(svg, 'rect', {
'x': str(legend_x),
'y': str(legend_y + y_offset - 8),
'width': '15',
'height': '15',
'fill': style['color'],
'stroke': '#333'
})
# Label
label = SubElement(svg, 'text', {
'x': str(legend_x + 20),
'y': str(legend_y + y_offset + 4),
'class': 'legend-text'
})
label.text = f"{style['icon']} {device_type.replace('_', ' ').title()}"
y_offset += 25
def _save_svg(self, svg: Element, output_file: str):
"""Save SVG to file with pretty formatting"""
rough_string = tostring(svg, encoding='unicode')
reparsed = minidom.parseString(rough_string)
pretty_svg = reparsed.toprettyxml(indent=' ')
# Remove extra blank lines
pretty_svg = '\n'.join([line for line in pretty_svg.split('\n') if line.strip()])
with open(output_file, 'w', encoding='utf-8') as f:
f.write(pretty_svg)
def main():
"""Command line interface"""
import argparse
parser = argparse.ArgumentParser(description='Generate SVG network diagram from scan data')
parser.add_argument('input', help='Input JSON file from network scan')
parser.add_argument('-o', '--output', default='network_diagram.svg',
help='Output SVG file (default: network_diagram.svg)')
args = parser.parse_args()
# Load scan data
with open(args.input, 'r') as f:
scan_data = json.load(f)
# Generate diagram
generator = NetworkDiagramGenerator(scan_data)
generator.generate_svg(args.output)
print(f"✓ Network diagram generated: {args.output}")
if __name__ == '__main__':
main()

248
test_system.py Executable file
View File

@@ -0,0 +1,248 @@
#!/usr/bin/env python3
"""
Test script to verify network scanner functionality
Run this to check if everything is working correctly
"""
import sys
import subprocess
import socket
import json
from pathlib import Path
def test_python_version():
"""Check Python version"""
print("Testing Python version...", end=" ")
version = sys.version_info
if version.major >= 3 and version.minor >= 8:
print(f"✓ Python {version.major}.{version.minor}.{version.micro}")
return True
else:
print(f"❌ Python {version.major}.{version.minor} (need 3.8+)")
return False
def test_network_connectivity():
"""Test basic network connectivity"""
print("Testing network connectivity...", end=" ")
try:
# Try to connect to common DNS server
socket.create_connection(("8.8.8.8", 53), timeout=3)
print("")
return True
except:
print("❌ No network connectivity")
return False
def test_commands():
"""Test required system commands"""
commands = ['ping', 'ip', 'ssh']
all_ok = True
for cmd in commands:
print(f"Testing command '{cmd}'...", end=" ")
try:
result = subprocess.run(['which', cmd], capture_output=True, timeout=2)
if result.returncode == 0:
print("")
else:
print(f"'{cmd}' not found")
all_ok = False
except:
print(f"❌ Error checking '{cmd}'")
all_ok = False
return all_ok
def test_scripts_exist():
"""Check if all scripts exist"""
scripts = [
'network_scanner.py',
'pfsense_scanner.py',
'svg_generator.py',
'integrated_scanner.py',
'quickstart.sh'
]
all_ok = True
for script in scripts:
print(f"Checking {script}...", end=" ")
if Path(script).exists():
print("")
else:
print(f"❌ Missing")
all_ok = False
return all_ok
def test_script_syntax():
"""Test Python script syntax"""
scripts = [
'network_scanner.py',
'pfsense_scanner.py',
'svg_generator.py',
'integrated_scanner.py'
]
all_ok = True
for script in scripts:
print(f"Testing {script} syntax...", end=" ")
try:
result = subprocess.run(
['python3', '-m', 'py_compile', script],
capture_output=True,
timeout=5
)
if result.returncode == 0:
print("")
else:
print(f"❌ Syntax error")
all_ok = False
except:
print(f"❌ Error")
all_ok = False
return all_ok
def test_local_network_detection():
"""Test local network detection"""
print("Detecting local networks...", end=" ")
try:
result = subprocess.run(
['ip', 'route', 'show'],
capture_output=True,
text=True,
timeout=5
)
networks = []
for line in result.stdout.splitlines():
if '/' in line and not line.startswith('default'):
parts = line.split()
if parts and '/' in parts[0]:
networks.append(parts[0])
if networks:
print(f"✓ Found {len(networks)} network(s)")
for net in networks[:3]:
print(f" - {net}")
return True
else:
print("⚠️ No networks detected")
return True
except:
print("❌ Error detecting networks")
return False
def test_ssh_config():
"""Check SSH configuration"""
print("Checking SSH keys...", end=" ")
ssh_dir = Path.home() / '.ssh'
keys = []
for key_file in ['id_rsa', 'id_ed25519', 'id_ecdsa']:
key_path = ssh_dir / key_file
if key_path.exists():
keys.append(key_file)
if keys:
print(f"✓ Found {len(keys)} key(s): {', '.join(keys)}")
return True
else:
print("⚠️ No SSH keys found (needed for device access)")
return True # Not critical
def create_test_config():
"""Create a test configuration"""
print("Creating test configuration...", end=" ")
config = {
"ssh_user": "root",
"ssh_key_path": None,
"timeout": 2,
"additional_networks": [],
"special_devices": {},
"scan_options": {
"max_workers": 10,
"ping_timeout": 2,
"port_scan_timeout": 1
}
}
try:
with open('test_config.json', 'w') as f:
json.dump(config, f, indent=2)
print("✓ Created test_config.json")
return True
except Exception as e:
print(f"❌ Error: {e}")
return False
def main():
print("="*60)
print("Network Scanner - System Test")
print("="*60)
print()
tests = [
("Python Version", test_python_version),
("Network Connectivity", test_network_connectivity),
("System Commands", test_commands),
("Script Files", test_scripts_exist),
("Script Syntax", test_script_syntax),
("Network Detection", test_local_network_detection),
("SSH Configuration", test_ssh_config),
("Test Config", create_test_config),
]
results = []
for test_name, test_func in tests:
print(f"\n--- {test_name} ---")
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"❌ Test failed with error: {e}")
results.append((test_name, False))
print()
# Summary
print("="*60)
print("Test Summary")
print("="*60)
print()
passed = sum(1 for _, result in results if result)
total = len(results)
for test_name, result in results:
status = "✓ PASS" if result else "❌ FAIL"
print(f"{status:8} {test_name}")
print()
print(f"Results: {passed}/{total} tests passed")
print()
if passed == total:
print("🎉 All tests passed! The scanner is ready to use.")
print()
print("Next steps:")
print("1. Edit config.json with your network details")
print("2. Run: ./quickstart.sh")
print(" or: ./integrated_scanner.py --generate-svg")
else:
print("⚠️ Some tests failed. Please check the errors above.")
print()
print("Common issues:")
print("- Missing system commands: install net-tools, iproute2, openssh-client")
print("- No SSH keys: run 'ssh-keygen' to create one")
print("- Script syntax errors: check Python version")
print()
print("="*60)
return 0 if passed == total else 1
if __name__ == '__main__':
sys.exit(main())