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