feat(secubox-mesh): Add network device and VM/container discovery
Enhanced mesh discovery with multi-method network device detection: - discovery_scan_subnet(): Active /24 subnet scanning for SecuBox peers - discovery_scan_docker(): Docker container detection via Unix socket - discovery_scan_lxc(): LXC and Proxmox container detection - discovery_scan_libvirt(): KVM/libvirt VM detection via virsh - discovery_scan_all_devices(): Full ARP neighbor discovery with fingerprinting - discovery_fingerprint_device(): Port scanning for service detection New RPCD API methods: - devices: List all discovered network devices - scan_full: Trigger full network scan (includes subnet scan) - scan_containers: Scan specifically for containers/VMs LuCI mesh dashboard updates: - "Discovered Devices" table with IP, MAC, type, hostname, services - "Scan Network" button to trigger full discovery - Device classification: secubox, server, container, vm, unknown - Peer table now shows source field (docker:name, lxc:name, etc.) Also includes CRT P31 theme CSS comprehensive fix for UI consistency. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b772c6da11
commit
e7a9062140
@ -1,6 +1,34 @@
|
|||||||
# SecuBox UI & Theme History
|
# SecuBox UI & Theme History
|
||||||
|
|
||||||
_Last updated: 2026-03-26 (Theme Deployment & Documentation)_
|
_Last updated: 2026-03-26 (Mesh Device Discovery)_
|
||||||
|
|
||||||
|
0. **Mesh Network Device/VM Discovery (2026-03-26)**
|
||||||
|
- **Enhanced discovery.sh** with multi-method network device detection:
|
||||||
|
- `discovery_scan_subnet()`: Active /24 subnet scanning for SecuBox peers
|
||||||
|
- `discovery_scan_docker()`: Docker container discovery via Unix socket API
|
||||||
|
- `discovery_scan_lxc()`: LXC and Proxmox (pct) container detection
|
||||||
|
- `discovery_scan_libvirt()`: KVM/libvirt VM detection via virsh
|
||||||
|
- `discovery_scan_all_devices()`: Full ARP neighbor discovery with fingerprinting
|
||||||
|
- **Device fingerprinting** by open ports (ssh, http, https, mitmproxy)
|
||||||
|
- **New RPCD methods**:
|
||||||
|
- `devices`: List all discovered network devices
|
||||||
|
- `scan_full`: Trigger full network scan (includes slow subnet scan)
|
||||||
|
- `scan_containers`: Scan specifically for Docker/LXC/libvirt containers
|
||||||
|
- **LuCI mesh dashboard** updated:
|
||||||
|
- "Discovered Devices" table with IP, MAC, type, hostname, services, state
|
||||||
|
- "Scan Network" button to trigger full discovery
|
||||||
|
- Device types: secubox, server, container, vm, unknown
|
||||||
|
- Peer table now shows source field (docker:name, lxc:name, etc.)
|
||||||
|
- **Files updated**:
|
||||||
|
- `secubox-mesh/files/usr/lib/secubox-mesh/discovery.sh`
|
||||||
|
- `secubox-mesh/files/usr/libexec/rpcd/luci.secubox-mesh`
|
||||||
|
- `secubox-mesh/root/usr/share/rpcd/acl.d/luci-app-secubox-mesh.json`
|
||||||
|
- `luci-app-secubox-mesh/htdocs/luci-static/resources/view/secubox/mesh.js`
|
||||||
|
|
||||||
|
0. **CRT P31 Theme UI Consistency Fix (2026-03-26)**
|
||||||
|
- Complete `cascade.css` rewrite (1100+ lines) for consistent styling
|
||||||
|
- All LuCI views now have uniform CRT P31 appearance
|
||||||
|
- Fixed: navigation, forms, tables, alerts, badges, modals, dropdowns
|
||||||
|
|
||||||
0. **Theme Deployment & Documentation (2026-03-26)**
|
0. **Theme Deployment & Documentation (2026-03-26)**
|
||||||
- **LuCI 24.10 Compatibility Fix**:
|
- **LuCI 24.10 Compatibility Fix**:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Work In Progress (Claude)
|
# Work In Progress (Claude)
|
||||||
|
|
||||||
_Last updated: 2026-03-26 (Theme Deployment & Documentation)_
|
_Last updated: 2026-03-26 (Mesh Device Discovery)_
|
||||||
|
|
||||||
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
|
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
|
||||||
|
|
||||||
@ -10,6 +10,24 @@ _Last updated: 2026-03-26 (Theme Deployment & Documentation)_
|
|||||||
|
|
||||||
### 2026-03-26
|
### 2026-03-26
|
||||||
|
|
||||||
|
- **Mesh Network Device/VM Discovery (Complete)**
|
||||||
|
- Enhanced `discovery.sh` with multi-method device detection
|
||||||
|
- New discovery methods:
|
||||||
|
- `discovery_scan_subnet()` - Active subnet scanning
|
||||||
|
- `discovery_scan_docker()` - Docker container detection via Unix socket
|
||||||
|
- `discovery_scan_lxc()` - LXC/Proxmox container detection
|
||||||
|
- `discovery_scan_libvirt()` - KVM/libvirt VM detection
|
||||||
|
- `discovery_scan_all_devices()` - Full network neighbor discovery with fingerprinting
|
||||||
|
- Device fingerprinting by services (ssh, http, https, mitmproxy)
|
||||||
|
- New RPCD methods: `devices`, `scan_full`, `scan_containers`
|
||||||
|
- Updated LuCI mesh.js with discovered devices table and scan button
|
||||||
|
- Devices classified by type: secubox, server, container, vm, unknown
|
||||||
|
|
||||||
|
- **CRT P31 Theme UI Fix (Complete)**
|
||||||
|
- Comprehensive cascade.css rewrite (1100+ lines)
|
||||||
|
- Fixed UI inconsistencies across all LuCI views
|
||||||
|
- Consistent CRT P31 styling for nav, forms, tables, alerts, modals
|
||||||
|
|
||||||
- **CRT P31 Theme Deployment & LuCI 24.10 Fix (Complete)**
|
- **CRT P31 Theme Deployment & LuCI 24.10 Fix (Complete)**
|
||||||
- Fixed LuCI 24.10 compatibility with ucode templates (.ut files)
|
- Fixed LuCI 24.10 compatibility with ucode templates (.ut files)
|
||||||
- Fixed navbar layout: sidebar → horizontal top navigation
|
- Fixed navbar layout: sidebar → horizontal top navigation
|
||||||
|
|||||||
@ -47,6 +47,24 @@ var callNodeRotate = rpc.declare({
|
|||||||
expect: {}
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callDevices = rpc.declare({
|
||||||
|
object: 'luci.secubox-mesh',
|
||||||
|
method: 'devices',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callScanFull = rpc.declare({
|
||||||
|
object: 'luci.secubox-mesh',
|
||||||
|
method: 'scan_full',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
var callScanContainers = rpc.declare({
|
||||||
|
object: 'luci.secubox-mesh',
|
||||||
|
method: 'scan_containers',
|
||||||
|
expect: {}
|
||||||
|
});
|
||||||
|
|
||||||
function formatUptime(seconds) {
|
function formatUptime(seconds) {
|
||||||
if (!seconds || seconds < 0) return '0s';
|
if (!seconds || seconds < 0) return '0s';
|
||||||
var days = Math.floor(seconds / 86400);
|
var days = Math.floor(seconds / 86400);
|
||||||
@ -105,7 +123,8 @@ return view.extend({
|
|||||||
callNodeInfo().catch(function() { return {}; }),
|
callNodeInfo().catch(function() { return {}; }),
|
||||||
callTelemetry().catch(function() { return {}; }),
|
callTelemetry().catch(function() { return {}; }),
|
||||||
callMeshPeers().catch(function() { return []; }),
|
callMeshPeers().catch(function() { return []; }),
|
||||||
callGetConfig().catch(function() { return {}; })
|
callGetConfig().catch(function() { return {}; }),
|
||||||
|
callDevices().catch(function() { return []; })
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -115,11 +134,13 @@ return view.extend({
|
|||||||
var telemetry = data[2] || {};
|
var telemetry = data[2] || {};
|
||||||
var peers = data[3] || [];
|
var peers = data[3] || [];
|
||||||
var config = data[4] || {};
|
var config = data[4] || {};
|
||||||
|
var devices = data[5] || [];
|
||||||
|
|
||||||
// Ensure peers is an array
|
// Ensure arrays
|
||||||
if (!Array.isArray(peers)) {
|
if (!Array.isArray(peers)) peers = [];
|
||||||
peers = [];
|
if (!Array.isArray(devices)) devices = [];
|
||||||
}
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
var view = E('div', { 'class': 'mesh-dashboard' }, [
|
var view = E('div', { 'class': 'mesh-dashboard' }, [
|
||||||
E('style', {}, [
|
E('style', {}, [
|
||||||
@ -279,6 +300,7 @@ return view.extend({
|
|||||||
E('th', {}, 'DID'),
|
E('th', {}, 'DID'),
|
||||||
E('th', {}, 'Address'),
|
E('th', {}, 'Address'),
|
||||||
E('th', {}, 'Role'),
|
E('th', {}, 'Role'),
|
||||||
|
E('th', {}, 'Source'),
|
||||||
E('th', {}, 'Last Seen')
|
E('th', {}, 'Last Seen')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@ -287,6 +309,7 @@ return view.extend({
|
|||||||
E('td', {}, peer.did || '-'),
|
E('td', {}, peer.did || '-'),
|
||||||
E('td', {}, peer.address || '-'),
|
E('td', {}, peer.address || '-'),
|
||||||
E('td', {}, (peer.role || 'edge').toUpperCase()),
|
E('td', {}, (peer.role || 'edge').toUpperCase()),
|
||||||
|
E('td', {}, peer.source || 'discovery'),
|
||||||
E('td', {}, peer.last_seen || '-')
|
E('td', {}, peer.last_seen || '-')
|
||||||
]);
|
]);
|
||||||
}))
|
}))
|
||||||
@ -295,6 +318,64 @@ return view.extend({
|
|||||||
E('div', { 'class': 'mesh-empty' }, 'No peers connected')
|
E('div', { 'class': 'mesh-empty' }, 'No peers connected')
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Network Devices Table
|
||||||
|
E('div', { 'class': 'mesh-card' }, [
|
||||||
|
E('div', { 'class': 'mesh-card-header', 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
|
||||||
|
E('span', {}, 'Discovered Devices (' + devices.length + ')'),
|
||||||
|
E('button', {
|
||||||
|
'class': 'mesh-btn',
|
||||||
|
'style': 'padding: 4px 12px; font-size: 12px;',
|
||||||
|
'click': ui.createHandlerFn(self, function() {
|
||||||
|
ui.addNotification(null, E('p', 'Full network scan initiated...'), 'info');
|
||||||
|
return callScanFull().then(function(res) {
|
||||||
|
if (res && res.started) {
|
||||||
|
ui.addNotification(null, E('p', 'Scan in progress. Results will appear shortly.'), 'success');
|
||||||
|
// Refresh after delay
|
||||||
|
setTimeout(function() { window.location.reload(); }, 5000);
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', 'Failed to start scan: ' + (res.error || 'unknown')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(e) {
|
||||||
|
ui.addNotification(null, E('p', 'Scan failed: ' + e.message), 'error');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}, 'Scan Network')
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'mesh-card-body', 'id': 'mesh-devices' },
|
||||||
|
devices.length > 0 ? [
|
||||||
|
E('table', { 'class': 'mesh-peers-table' }, [
|
||||||
|
E('thead', {}, [
|
||||||
|
E('tr', {}, [
|
||||||
|
E('th', {}, 'IP Address'),
|
||||||
|
E('th', {}, 'MAC'),
|
||||||
|
E('th', {}, 'Type'),
|
||||||
|
E('th', {}, 'Hostname'),
|
||||||
|
E('th', {}, 'Services'),
|
||||||
|
E('th', {}, 'State')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('tbody', {}, devices.map(function(dev) {
|
||||||
|
var typeColor = dev.type === 'secubox' ? '#33ff66' :
|
||||||
|
(dev.type === 'server' ? '#00aaff' : '#888888');
|
||||||
|
return E('tr', {}, [
|
||||||
|
E('td', {}, dev.ip || '-'),
|
||||||
|
E('td', { 'style': 'font-size: 11px;' }, dev.mac || '-'),
|
||||||
|
E('td', { 'style': 'color: ' + typeColor }, (dev.type || 'unknown').toUpperCase()),
|
||||||
|
E('td', {}, dev.hostname || '-'),
|
||||||
|
E('td', { 'style': 'font-size: 11px;' }, dev.services || '-'),
|
||||||
|
E('td', {}, dev.state || '-')
|
||||||
|
]);
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
] : [
|
||||||
|
E('div', { 'class': 'mesh-empty' }, [
|
||||||
|
E('p', {}, 'No devices discovered yet.'),
|
||||||
|
E('p', { 'style': 'font-size: 12px; margin-top: 10px;' }, 'Click "Scan Network" to discover devices on your local network.')
|
||||||
|
])
|
||||||
|
]
|
||||||
|
)
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -303,13 +384,16 @@ return view.extend({
|
|||||||
return Promise.all([
|
return Promise.all([
|
||||||
callMeshStatus().catch(function() { return {}; }),
|
callMeshStatus().catch(function() { return {}; }),
|
||||||
callTelemetry().catch(function() { return {}; }),
|
callTelemetry().catch(function() { return {}; }),
|
||||||
callMeshPeers().catch(function() { return []; })
|
callMeshPeers().catch(function() { return []; }),
|
||||||
|
callDevices().catch(function() { return []; })
|
||||||
]).then(L.bind(function(data) {
|
]).then(L.bind(function(data) {
|
||||||
var status = data[0] || {};
|
var status = data[0] || {};
|
||||||
var telemetry = data[1] || {};
|
var telemetry = data[1] || {};
|
||||||
var peers = data[2] || [];
|
var peers = data[2] || [];
|
||||||
|
var devices = data[3] || [];
|
||||||
|
|
||||||
if (!Array.isArray(peers)) peers = [];
|
if (!Array.isArray(peers)) peers = [];
|
||||||
|
if (!Array.isArray(devices)) devices = [];
|
||||||
|
|
||||||
// Update status badge
|
// Update status badge
|
||||||
var badge = document.querySelector('.mesh-status-badge');
|
var badge = document.querySelector('.mesh-status-badge');
|
||||||
@ -318,6 +402,18 @@ return view.extend({
|
|||||||
badge.className = 'mesh-status-badge ' + (status.state === 'running' ? 'mesh-status-running' : 'mesh-status-stopped');
|
badge.className = 'mesh-status-badge ' + (status.state === 'running' ? 'mesh-status-running' : 'mesh-status-stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update peer count in header
|
||||||
|
var peersHeader = document.querySelector('#mesh-peers')?.parentNode?.querySelector('.mesh-card-header');
|
||||||
|
if (peersHeader && !peersHeader.querySelector('button')) {
|
||||||
|
peersHeader.textContent = 'Connected Peers (' + peers.length + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update devices count in header
|
||||||
|
var devicesHeader = document.querySelector('#mesh-devices')?.parentNode?.querySelector('.mesh-card-header span');
|
||||||
|
if (devicesHeader) {
|
||||||
|
devicesHeader.textContent = 'Discovered Devices (' + devices.length + ')';
|
||||||
|
}
|
||||||
|
|
||||||
}, this));
|
}, this));
|
||||||
}, this), 10);
|
}, this), 10);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,31 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# SecuBox Mesh Peer Discovery
|
# SecuBox Mesh Peer Discovery
|
||||||
# mDNS-based service discovery for mesh peers
|
# Multi-method service discovery for mesh peers and network devices
|
||||||
# CyberMind — SecuBox — 2026
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
PEERS_FILE="/var/lib/secubox-mesh/peers.json"
|
PEERS_FILE="/var/lib/secubox-mesh/peers.json"
|
||||||
|
DEVICES_FILE="/var/lib/secubox-mesh/devices.json"
|
||||||
DISCOVERY_CACHE="/tmp/secubox_discovery_cache"
|
DISCOVERY_CACHE="/tmp/secubox_discovery_cache"
|
||||||
MDNS_SERVICE="_secubox._udp"
|
MDNS_SERVICE="_secubox._udp"
|
||||||
|
DISCOVERY_TIMEOUT=2
|
||||||
|
|
||||||
|
# Well-known SecuBox/service ports for fingerprinting
|
||||||
|
PORT_SECUBOX_API=7331
|
||||||
|
PORT_SECUBOX_P2P=7332
|
||||||
|
PORT_WIREGUARD=51820
|
||||||
|
PORT_HTTP=80
|
||||||
|
PORT_HTTPS=443
|
||||||
|
PORT_SSH=22
|
||||||
|
PORT_NETDATA=19999
|
||||||
|
PORT_MITMPROXY=8080
|
||||||
|
PORT_CROWDSEC=6060
|
||||||
|
|
||||||
# Initialize discovery
|
# Initialize discovery
|
||||||
discovery_init() {
|
discovery_init() {
|
||||||
mkdir -p "$(dirname "$PEERS_FILE")"
|
mkdir -p "$(dirname "$PEERS_FILE")"
|
||||||
|
mkdir -p "$(dirname "$DEVICES_FILE")"
|
||||||
[ -f "$PEERS_FILE" ] || echo '[]' > "$PEERS_FILE"
|
[ -f "$PEERS_FILE" ] || echo '[]' > "$PEERS_FILE"
|
||||||
|
[ -f "$DEVICES_FILE" ] || echo '[]' > "$DEVICES_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scan for peers using mDNS
|
# Scan for peers using mDNS
|
||||||
@ -111,17 +126,346 @@ _scan_config_peer() {
|
|||||||
[ -n "$did" ] && [ -n "$addr" ] && echo "$did|$addr|$role|$port"
|
[ -n "$did" ] && [ -n "$addr" ] && echo "$did|$addr|$role|$port"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Combined peer discovery
|
# Check if port is open (fast, non-blocking)
|
||||||
|
_check_port() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
|
||||||
|
# Use netcat if available (most reliable)
|
||||||
|
if command -v nc >/dev/null 2>&1; then
|
||||||
|
nc -z -w 1 "$host" "$port" 2>/dev/null
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: try wget
|
||||||
|
wget -q -T 1 -O /dev/null "http://$host:$port/" 2>/dev/null
|
||||||
|
[ $? -eq 0 ] || [ $? -eq 8 ] # 8 = server error (port open)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan subnet range for SecuBox peers (active discovery)
|
||||||
|
discovery_scan_subnet() {
|
||||||
|
local subnet="${1:-}"
|
||||||
|
|
||||||
|
# Auto-detect subnet from br-lan if not provided
|
||||||
|
if [ -z "$subnet" ]; then
|
||||||
|
local lan_ip
|
||||||
|
lan_ip=$(ip -4 addr show br-lan 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2)
|
||||||
|
[ -z "$lan_ip" ] && lan_ip=$(ip -4 addr show eth0 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2)
|
||||||
|
[ -z "$lan_ip" ] && return
|
||||||
|
|
||||||
|
# Extract network base (assume /24)
|
||||||
|
subnet=$(echo "$lan_ip" | cut -d. -f1-3)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract base if full CIDR given
|
||||||
|
local base
|
||||||
|
base=$(echo "$subnet" | cut -d/ -f1 | cut -d. -f1-3)
|
||||||
|
|
||||||
|
local my_ips
|
||||||
|
my_ips=$(ip -4 addr show 2>/dev/null | grep -o 'inet [0-9.]*' | cut -d' ' -f2 | tr '\n' ' ')
|
||||||
|
|
||||||
|
# Scan common SecuBox port across subnet
|
||||||
|
local i=1
|
||||||
|
while [ $i -lt 255 ]; do
|
||||||
|
local target="${base}.${i}"
|
||||||
|
|
||||||
|
# Skip self
|
||||||
|
echo "$my_ips" | grep -q "$target " && { i=$((i+1)); continue; }
|
||||||
|
|
||||||
|
# Quick port check
|
||||||
|
if _check_port "$target" "$PORT_SECUBOX_API" 2>/dev/null; then
|
||||||
|
local response
|
||||||
|
response=$(wget -q -T 1 -O- "http://$target:$PORT_SECUBOX_API/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
local did role
|
||||||
|
did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
|
||||||
|
[ -n "$did" ] && echo "$did|$target|${role:-edge}|$PORT_SECUBOX_API"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for Docker containers
|
||||||
|
discovery_scan_docker() {
|
||||||
|
local docker_sock="/var/run/docker.sock"
|
||||||
|
|
||||||
|
[ -S "$docker_sock" ] || return
|
||||||
|
|
||||||
|
# List running containers via Unix socket
|
||||||
|
local containers
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
containers=$(curl -s --unix-socket "$docker_sock" "http://localhost/containers/json" 2>/dev/null)
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -z "$containers" ] && return
|
||||||
|
|
||||||
|
# Parse each container
|
||||||
|
echo "$containers" | jsonfilter -e '@[*].Id' 2>/dev/null | while read -r full_id; do
|
||||||
|
[ -z "$full_id" ] && continue
|
||||||
|
|
||||||
|
local id name state ip
|
||||||
|
id=$(echo "$full_id" | cut -c1-12)
|
||||||
|
|
||||||
|
# Get container details
|
||||||
|
local info
|
||||||
|
info=$(curl -s --unix-socket "$docker_sock" "http://localhost/containers/$id/json" 2>/dev/null)
|
||||||
|
|
||||||
|
name=$(echo "$info" | jsonfilter -e '@.Name' 2>/dev/null | tr -d '/')
|
||||||
|
state=$(echo "$info" | jsonfilter -e '@.State.Running' 2>/dev/null)
|
||||||
|
ip=$(echo "$info" | jsonfilter -e '@.NetworkSettings.IPAddress' 2>/dev/null)
|
||||||
|
|
||||||
|
# Try bridge network if no IP
|
||||||
|
[ -z "$ip" ] && ip=$(echo "$info" | jsonfilter -e '@.NetworkSettings.Networks.bridge.IPAddress' 2>/dev/null)
|
||||||
|
|
||||||
|
[ -z "$ip" ] && continue
|
||||||
|
[ "$state" != "true" ] && continue
|
||||||
|
|
||||||
|
# Generate DID from container ID
|
||||||
|
local did="did:container:docker:$id"
|
||||||
|
|
||||||
|
# Check if it's a SecuBox container
|
||||||
|
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
|
||||||
|
local response
|
||||||
|
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
local real_did role
|
||||||
|
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
|
||||||
|
echo "${real_did:-$did}|$ip|${role:-container}|$PORT_SECUBOX_API|docker:$name"
|
||||||
|
else
|
||||||
|
echo "$did|$ip|container|0|docker:$name"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$did|$ip|container|0|docker:$name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for LXC containers
|
||||||
|
discovery_scan_lxc() {
|
||||||
|
# Check for lxc-ls command
|
||||||
|
if command -v lxc-ls >/dev/null 2>&1; then
|
||||||
|
lxc-ls --running 2>/dev/null | while read -r container; do
|
||||||
|
[ -z "$container" ] && continue
|
||||||
|
|
||||||
|
# Get container IP
|
||||||
|
local ip
|
||||||
|
ip=$(lxc-info -n "$container" -iH 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
[ -z "$ip" ] && continue
|
||||||
|
|
||||||
|
local did="did:container:lxc:$container"
|
||||||
|
|
||||||
|
# Check if it's a SecuBox container
|
||||||
|
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
|
||||||
|
local response
|
||||||
|
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
local real_did role
|
||||||
|
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
|
||||||
|
echo "${real_did:-$did}|$ip|${role:-container}|$PORT_SECUBOX_API|lxc:$container"
|
||||||
|
else
|
||||||
|
echo "$did|$ip|container|0|lxc:$container"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$did|$ip|container|0|lxc:$container"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for Proxmox pct command
|
||||||
|
if command -v pct >/dev/null 2>&1; then
|
||||||
|
pct list 2>/dev/null | tail -n +2 | while read -r vmid status _ name _; do
|
||||||
|
[ "$status" != "running" ] && continue
|
||||||
|
|
||||||
|
# Get container IP via config
|
||||||
|
local ip
|
||||||
|
ip=$(pct config "$vmid" 2>/dev/null | grep -o 'ip=[0-9.]*' | head -1 | cut -d= -f2)
|
||||||
|
|
||||||
|
[ -z "$ip" ] && continue
|
||||||
|
|
||||||
|
local did="did:container:pve:$vmid"
|
||||||
|
|
||||||
|
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
|
||||||
|
local response
|
||||||
|
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
local real_did role
|
||||||
|
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
|
||||||
|
echo "${real_did:-$did}|$ip|${role:-container}|$PORT_SECUBOX_API|pve:$name"
|
||||||
|
else
|
||||||
|
echo "$did|$ip|container|0|pve:$name"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$did|$ip|container|0|pve:$name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan for libvirt/KVM virtual machines
|
||||||
|
discovery_scan_libvirt() {
|
||||||
|
command -v virsh >/dev/null 2>&1 || return
|
||||||
|
|
||||||
|
virsh list --name 2>/dev/null | while read -r vm; do
|
||||||
|
[ -z "$vm" ] && continue
|
||||||
|
|
||||||
|
# Get VM IP from guest agent or ARP
|
||||||
|
local ip=""
|
||||||
|
|
||||||
|
# Try qemu-guest-agent
|
||||||
|
ip=$(virsh domifaddr "$vm" --source agent 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
|
||||||
|
|
||||||
|
# Fallback to ARP inspection via MAC
|
||||||
|
if [ -z "$ip" ]; then
|
||||||
|
local mac
|
||||||
|
mac=$(virsh domiflist "$vm" 2>/dev/null | awk 'NR>2 && $5 != "" {print $5}' | head -1)
|
||||||
|
[ -n "$mac" ] && ip=$(ip neigh show 2>/dev/null | grep -i "$mac" | awk '{print $1}' | head -1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -z "$ip" ] && continue
|
||||||
|
|
||||||
|
local did="did:vm:libvirt:$vm"
|
||||||
|
|
||||||
|
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
|
||||||
|
local response
|
||||||
|
response=$(wget -q -T 1 -O- "http://$ip:$PORT_SECUBOX_API/api/status" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$response" ]; then
|
||||||
|
local real_did role
|
||||||
|
real_did=$(echo "$response" | jsonfilter -e '@.did' 2>/dev/null)
|
||||||
|
role=$(echo "$response" | jsonfilter -e '@.role' 2>/dev/null)
|
||||||
|
echo "${real_did:-$did}|$ip|${role:-vm}|$PORT_SECUBOX_API|libvirt:$vm"
|
||||||
|
else
|
||||||
|
echo "$did|$ip|vm|0|libvirt:$vm"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$did|$ip|vm|0|libvirt:$vm"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fingerprint network device by open ports (quick check)
|
||||||
|
discovery_fingerprint_device() {
|
||||||
|
local ip="$1"
|
||||||
|
local services=""
|
||||||
|
|
||||||
|
# Quick port checks - prioritize common ports
|
||||||
|
_check_port "$ip" "$PORT_SSH" && services="${services}ssh,"
|
||||||
|
_check_port "$ip" "$PORT_HTTP" && services="${services}http,"
|
||||||
|
_check_port "$ip" "$PORT_HTTPS" && services="${services}https,"
|
||||||
|
_check_port "$ip" "$PORT_MITMPROXY" && services="${services}mitmproxy,"
|
||||||
|
|
||||||
|
echo "${services%,}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scan all network neighbors for any device (not just SecuBox)
|
||||||
|
discovery_scan_all_devices() {
|
||||||
|
local tmp_devices="/tmp/secubox_devices_$$.txt"
|
||||||
|
|
||||||
|
# Get all neighbors from ARP
|
||||||
|
# Format: IP dev INTERFACE lladdr MAC STATE
|
||||||
|
ip neigh show 2>/dev/null | grep -v FAILED | grep lladdr | while read -r line; do
|
||||||
|
local ip mac state
|
||||||
|
|
||||||
|
# Parse: "192.168.1.1 dev br-lan lladdr aa:bb:cc:dd:ee:ff REACHABLE"
|
||||||
|
ip=$(echo "$line" | awk '{print $1}')
|
||||||
|
mac=$(echo "$line" | awk '{print $5}')
|
||||||
|
state=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf $i" "; print ""}' | xargs)
|
||||||
|
|
||||||
|
[ -z "$ip" ] && continue
|
||||||
|
[ -z "$mac" ] && continue
|
||||||
|
|
||||||
|
# Skip multicast/broadcast MACs
|
||||||
|
echo "$mac" | grep -qi "^ff:" && continue
|
||||||
|
echo "$mac" | grep -qi "^01:" && continue
|
||||||
|
|
||||||
|
local device_type="unknown"
|
||||||
|
local hostname=""
|
||||||
|
local services=""
|
||||||
|
|
||||||
|
# Try reverse DNS (quick timeout)
|
||||||
|
hostname=$(nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $NF}' | sed 's/\.$//' | head -1)
|
||||||
|
|
||||||
|
# Check if it's a SecuBox peer
|
||||||
|
if _check_port "$ip" "$PORT_SECUBOX_API" 2>/dev/null; then
|
||||||
|
device_type="secubox"
|
||||||
|
services="secubox-api"
|
||||||
|
else
|
||||||
|
# Fingerprint by services (skip fingerprinting for IPv6 to be faster)
|
||||||
|
if ! echo "$ip" | grep -q ":"; then
|
||||||
|
services=$(discovery_fingerprint_device "$ip")
|
||||||
|
[ -n "$services" ] && device_type="server"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate device ID from MAC
|
||||||
|
local device_id
|
||||||
|
device_id=$(echo -n "$mac" | md5sum | cut -c1-12)
|
||||||
|
|
||||||
|
echo "$device_id|$ip|$mac|$device_type|$hostname|$services|$state"
|
||||||
|
done > "$tmp_devices"
|
||||||
|
|
||||||
|
# Build devices JSON
|
||||||
|
local devices_json='['
|
||||||
|
local first=1
|
||||||
|
|
||||||
|
while IFS='|' read -r id ip mac dtype hostname services state; do
|
||||||
|
[ -z "$id" ] && continue
|
||||||
|
|
||||||
|
[ "$first" = "1" ] || devices_json="$devices_json,"
|
||||||
|
|
||||||
|
local last_seen
|
||||||
|
last_seen=$(date -Iseconds)
|
||||||
|
|
||||||
|
devices_json="$devices_json{\"id\":\"$id\",\"ip\":\"$ip\",\"mac\":\"$mac\",\"type\":\"$dtype\",\"hostname\":\"${hostname:-}\",\"services\":\"${services:-}\",\"state\":\"$state\",\"last_seen\":\"$last_seen\"}"
|
||||||
|
first=0
|
||||||
|
done < "$tmp_devices"
|
||||||
|
|
||||||
|
devices_json="$devices_json]"
|
||||||
|
|
||||||
|
echo "$devices_json" > "$DEVICES_FILE"
|
||||||
|
rm -f "$tmp_devices"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all discovered devices
|
||||||
|
discovery_get_devices() {
|
||||||
|
cat "$DEVICES_FILE" 2>/dev/null || echo '[]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get device count
|
||||||
|
discovery_get_device_count() {
|
||||||
|
jsonfilter -i "$DEVICES_FILE" -e '@[*]' 2>/dev/null | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
# Combined peer discovery (SecuBox nodes + containers + VMs)
|
||||||
discovery_scan_peers() {
|
discovery_scan_peers() {
|
||||||
local tmp_peers="/tmp/secubox_peers_$$.txt"
|
local tmp_peers="/tmp/secubox_peers_$$.txt"
|
||||||
local seen_dids=""
|
local seen_dids=""
|
||||||
|
|
||||||
# Combine all discovery methods
|
# Combine all discovery methods
|
||||||
{
|
{
|
||||||
|
# Standard discovery methods
|
||||||
discovery_scan_mdns
|
discovery_scan_mdns
|
||||||
discovery_scan_wireguard
|
discovery_scan_wireguard
|
||||||
discovery_scan_arp
|
discovery_scan_arp
|
||||||
discovery_scan_config
|
discovery_scan_config
|
||||||
|
|
||||||
|
# Container/VM discovery (run if commands exist)
|
||||||
|
discovery_scan_docker 2>/dev/null
|
||||||
|
discovery_scan_lxc 2>/dev/null
|
||||||
|
discovery_scan_libvirt 2>/dev/null
|
||||||
} | sort -u > "$tmp_peers"
|
} | sort -u > "$tmp_peers"
|
||||||
|
|
||||||
# Build peers JSON
|
# Build peers JSON
|
||||||
@ -129,7 +473,7 @@ discovery_scan_peers() {
|
|||||||
local first=1
|
local first=1
|
||||||
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
||||||
|
|
||||||
while IFS='|' read -r did addr role port; do
|
while IFS='|' read -r did addr role port source; do
|
||||||
[ -z "$did" ] && continue
|
[ -z "$did" ] && continue
|
||||||
|
|
||||||
# Skip self
|
# Skip self
|
||||||
@ -144,7 +488,11 @@ discovery_scan_peers() {
|
|||||||
local last_seen
|
local last_seen
|
||||||
last_seen=$(date -Iseconds)
|
last_seen=$(date -Iseconds)
|
||||||
|
|
||||||
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":$port,\"last_seen\":\"$last_seen\"}"
|
# Include source if available (docker:name, lxc:name, etc.)
|
||||||
|
local source_field=""
|
||||||
|
[ -n "$source" ] && source_field=",\"source\":\"$source\""
|
||||||
|
|
||||||
|
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":${port:-0}$source_field,\"last_seen\":\"$last_seen\"}"
|
||||||
first=0
|
first=0
|
||||||
done < "$tmp_peers"
|
done < "$tmp_peers"
|
||||||
|
|
||||||
@ -154,6 +502,54 @@ discovery_scan_peers() {
|
|||||||
echo "$peers_json" > "$PEERS_FILE"
|
echo "$peers_json" > "$PEERS_FILE"
|
||||||
|
|
||||||
rm -f "$tmp_peers"
|
rm -f "$tmp_peers"
|
||||||
|
|
||||||
|
# Also run full device discovery in background
|
||||||
|
discovery_scan_all_devices &
|
||||||
|
}
|
||||||
|
|
||||||
|
# Full discovery scan (includes subnet scan - slower)
|
||||||
|
discovery_scan_full() {
|
||||||
|
local tmp_peers="/tmp/secubox_peers_full_$$.txt"
|
||||||
|
|
||||||
|
# Run all methods including slow subnet scan
|
||||||
|
{
|
||||||
|
discovery_scan_mdns
|
||||||
|
discovery_scan_wireguard
|
||||||
|
discovery_scan_arp
|
||||||
|
discovery_scan_config
|
||||||
|
discovery_scan_docker 2>/dev/null
|
||||||
|
discovery_scan_lxc 2>/dev/null
|
||||||
|
discovery_scan_libvirt 2>/dev/null
|
||||||
|
discovery_scan_subnet 2>/dev/null
|
||||||
|
} | sort -u > "$tmp_peers"
|
||||||
|
|
||||||
|
# Use same parsing logic as discovery_scan_peers
|
||||||
|
local peers_json='['
|
||||||
|
local first=1
|
||||||
|
local my_did="${NODE_DID:-$(cat /var/lib/mirrornet/identity/did.txt 2>/dev/null)}"
|
||||||
|
local seen_dids=""
|
||||||
|
|
||||||
|
while IFS='|' read -r did addr role port source; do
|
||||||
|
[ -z "$did" ] && continue
|
||||||
|
[ "$did" = "$my_did" ] && continue
|
||||||
|
echo "$seen_dids" | grep -q "$did" && continue
|
||||||
|
seen_dids="$seen_dids $did"
|
||||||
|
|
||||||
|
[ "$first" = "1" ] || peers_json="$peers_json,"
|
||||||
|
|
||||||
|
local last_seen source_field=""
|
||||||
|
last_seen=$(date -Iseconds)
|
||||||
|
[ -n "$source" ] && source_field=",\"source\":\"$source\""
|
||||||
|
|
||||||
|
peers_json="$peers_json{\"did\":\"$did\",\"address\":\"$addr\",\"role\":\"$role\",\"port\":${port:-0}$source_field,\"last_seen\":\"$last_seen\"}"
|
||||||
|
first=0
|
||||||
|
done < "$tmp_peers"
|
||||||
|
|
||||||
|
peers_json="$peers_json]"
|
||||||
|
echo "$peers_json" > "$PEERS_FILE"
|
||||||
|
rm -f "$tmp_peers"
|
||||||
|
|
||||||
|
discovery_scan_all_devices
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get peer count
|
# Get peer count
|
||||||
|
|||||||
@ -7,6 +7,10 @@
|
|||||||
. /usr/share/libubox/jshn.sh
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
SOCKET="/var/run/secuboxd/topo.sock"
|
SOCKET="/var/run/secuboxd/topo.sock"
|
||||||
|
STATEDIR="/var/lib/secubox-mesh"
|
||||||
|
|
||||||
|
# Source discovery library for direct calls
|
||||||
|
[ -f /usr/lib/secubox-mesh/discovery.sh ] && . /usr/lib/secubox-mesh/discovery.sh
|
||||||
|
|
||||||
# Send command to daemon
|
# Send command to daemon
|
||||||
_send_cmd() {
|
_send_cmd() {
|
||||||
@ -39,7 +43,10 @@ case_list() {
|
|||||||
"ping": {},
|
"ping": {},
|
||||||
"get_config": {},
|
"get_config": {},
|
||||||
"set_config": { "role": "string", "beacon_interval": "number" },
|
"set_config": { "role": "string", "beacon_interval": "number" },
|
||||||
"restart": {}
|
"restart": {},
|
||||||
|
"devices": {},
|
||||||
|
"scan_full": {},
|
||||||
|
"scan_containers": {}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@ -126,6 +133,72 @@ case_call() {
|
|||||||
json_add_boolean success 1
|
json_add_boolean success 1
|
||||||
json_dump
|
json_dump
|
||||||
;;
|
;;
|
||||||
|
devices)
|
||||||
|
# Return discovered network devices
|
||||||
|
if [ -f "$STATEDIR/devices.json" ]; then
|
||||||
|
cat "$STATEDIR/devices.json"
|
||||||
|
else
|
||||||
|
echo '[]'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
scan_full)
|
||||||
|
# Trigger full network scan (including subnet scan)
|
||||||
|
if type discovery_scan_full >/dev/null 2>&1; then
|
||||||
|
# Run in background to avoid timeout
|
||||||
|
discovery_scan_full &
|
||||||
|
local scan_pid=$!
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_boolean started 1
|
||||||
|
json_add_int pid "$scan_pid"
|
||||||
|
json_add_string message "Full scan initiated"
|
||||||
|
json_dump
|
||||||
|
else
|
||||||
|
json_init
|
||||||
|
json_add_boolean started 0
|
||||||
|
json_add_string error "Discovery library not available"
|
||||||
|
json_dump
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
scan_containers)
|
||||||
|
# Scan for containers and VMs specifically
|
||||||
|
local containers='[]'
|
||||||
|
|
||||||
|
if type discovery_scan_docker >/dev/null 2>&1; then
|
||||||
|
local docker_out lxc_out libvirt_out
|
||||||
|
docker_out=$(discovery_scan_docker 2>/dev/null)
|
||||||
|
lxc_out=$(discovery_scan_lxc 2>/dev/null)
|
||||||
|
libvirt_out=$(discovery_scan_libvirt 2>/dev/null)
|
||||||
|
|
||||||
|
# Build JSON from pipe-separated output
|
||||||
|
json_init
|
||||||
|
json_add_array containers
|
||||||
|
|
||||||
|
for line in $docker_out $lxc_out $libvirt_out; do
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
|
||||||
|
local did addr role port source
|
||||||
|
did=$(echo "$line" | cut -d'|' -f1)
|
||||||
|
addr=$(echo "$line" | cut -d'|' -f2)
|
||||||
|
role=$(echo "$line" | cut -d'|' -f3)
|
||||||
|
port=$(echo "$line" | cut -d'|' -f4)
|
||||||
|
source=$(echo "$line" | cut -d'|' -f5)
|
||||||
|
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string did "$did"
|
||||||
|
json_add_string address "$addr"
|
||||||
|
json_add_string role "$role"
|
||||||
|
json_add_int port "${port:-0}"
|
||||||
|
json_add_string source "$source"
|
||||||
|
json_close_object
|
||||||
|
done
|
||||||
|
|
||||||
|
json_close_array
|
||||||
|
json_dump
|
||||||
|
else
|
||||||
|
echo '{"containers":[],"error":"Discovery library not available"}'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
json_init
|
json_init
|
||||||
json_add_string error "Unknown method: $method"
|
json_add_string error "Unknown method: $method"
|
||||||
|
|||||||
@ -11,7 +11,9 @@
|
|||||||
"node_info",
|
"node_info",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"ping",
|
"ping",
|
||||||
"get_config"
|
"get_config",
|
||||||
|
"devices",
|
||||||
|
"scan_containers"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uci": ["secubox"]
|
"uci": ["secubox"]
|
||||||
@ -21,7 +23,8 @@
|
|||||||
"luci.secubox-mesh": [
|
"luci.secubox-mesh": [
|
||||||
"node_rotate",
|
"node_rotate",
|
||||||
"set_config",
|
"set_config",
|
||||||
"restart"
|
"restart",
|
||||||
|
"scan_full"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uci": ["secubox"]
|
"uci": ["secubox"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user