Initial commit: Network scanner with pfSense integration and SVG diagram generation
This commit is contained in:
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal 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
265
EXAMPLES.sh
Executable 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
299
GETTING_STARTED.md
Normal 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
423
PROJECT_SUMMARY.md
Normal 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
364
README.md
Normal 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.
|
||||
5259
config-gw-nue01.egonetix.lan-20251010104729.xml
Normal file
5259
config-gw-nue01.egonetix.lan-20251010104729.xml
Normal file
File diff suppressed because one or more lines are too long
3237
config-gw-st01.egonetix.lan-20251010104656.xml
Normal file
3237
config-gw-st01.egonetix.lan-20251010104656.xml
Normal file
File diff suppressed because it is too large
Load Diff
21
config.json.example
Normal file
21
config.json.example
Normal 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
284
integrated_scanner.py
Executable 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
532
network_scanner.py
Executable 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
336
pfsense_scanner.py
Executable 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
181
quickstart.sh
Executable 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
12
requirements.txt
Normal 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
353
svg_generator.py
Executable 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
248
test_system.py
Executable 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())
|
||||
Reference in New Issue
Block a user