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
|
||||
|
||||
_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)**
|
||||
- **LuCI 24.10 Compatibility Fix**:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@ -10,6 +10,24 @@ _Last updated: 2026-03-26 (Theme Deployment & Documentation)_
|
||||
|
||||
### 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)**
|
||||
- Fixed LuCI 24.10 compatibility with ucode templates (.ut files)
|
||||
- Fixed navbar layout: sidebar → horizontal top navigation
|
||||
|
||||
@ -47,6 +47,24 @@ var callNodeRotate = rpc.declare({
|
||||
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) {
|
||||
if (!seconds || seconds < 0) return '0s';
|
||||
var days = Math.floor(seconds / 86400);
|
||||
@ -105,7 +123,8 @@ return view.extend({
|
||||
callNodeInfo().catch(function() { return {}; }),
|
||||
callTelemetry().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 peers = data[3] || [];
|
||||
var config = data[4] || {};
|
||||
var devices = data[5] || [];
|
||||
|
||||
// Ensure peers is an array
|
||||
if (!Array.isArray(peers)) {
|
||||
peers = [];
|
||||
}
|
||||
// Ensure arrays
|
||||
if (!Array.isArray(peers)) peers = [];
|
||||
if (!Array.isArray(devices)) devices = [];
|
||||
|
||||
var self = this;
|
||||
|
||||
var view = E('div', { 'class': 'mesh-dashboard' }, [
|
||||
E('style', {}, [
|
||||
@ -279,6 +300,7 @@ return view.extend({
|
||||
E('th', {}, 'DID'),
|
||||
E('th', {}, 'Address'),
|
||||
E('th', {}, 'Role'),
|
||||
E('th', {}, 'Source'),
|
||||
E('th', {}, 'Last Seen')
|
||||
])
|
||||
]),
|
||||
@ -287,6 +309,7 @@ return view.extend({
|
||||
E('td', {}, peer.did || '-'),
|
||||
E('td', {}, peer.address || '-'),
|
||||
E('td', {}, (peer.role || 'edge').toUpperCase()),
|
||||
E('td', {}, peer.source || 'discovery'),
|
||||
E('td', {}, peer.last_seen || '-')
|
||||
]);
|
||||
}))
|
||||
@ -295,6 +318,64 @@ return view.extend({
|
||||
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([
|
||||
callMeshStatus().catch(function() { return {}; }),
|
||||
callTelemetry().catch(function() { return {}; }),
|
||||
callMeshPeers().catch(function() { return []; })
|
||||
callMeshPeers().catch(function() { return []; }),
|
||||
callDevices().catch(function() { return []; })
|
||||
]).then(L.bind(function(data) {
|
||||
var status = data[0] || {};
|
||||
var telemetry = data[1] || {};
|
||||
var peers = data[2] || [];
|
||||
var devices = data[3] || [];
|
||||
|
||||
if (!Array.isArray(peers)) peers = [];
|
||||
if (!Array.isArray(devices)) devices = [];
|
||||
|
||||
// Update 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');
|
||||
}
|
||||
|
||||
// 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), 10);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,31 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Mesh Peer Discovery
|
||||
# mDNS-based service discovery for mesh peers
|
||||
# Multi-method service discovery for mesh peers and network devices
|
||||
# CyberMind — SecuBox — 2026
|
||||
|
||||
PEERS_FILE="/var/lib/secubox-mesh/peers.json"
|
||||
DEVICES_FILE="/var/lib/secubox-mesh/devices.json"
|
||||
DISCOVERY_CACHE="/tmp/secubox_discovery_cache"
|
||||
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
|
||||
discovery_init() {
|
||||
mkdir -p "$(dirname "$PEERS_FILE")"
|
||||
mkdir -p "$(dirname "$DEVICES_FILE")"
|
||||
[ -f "$PEERS_FILE" ] || echo '[]' > "$PEERS_FILE"
|
||||
[ -f "$DEVICES_FILE" ] || echo '[]' > "$DEVICES_FILE"
|
||||
}
|
||||
|
||||
# Scan for peers using mDNS
|
||||
@ -111,17 +126,346 @@ _scan_config_peer() {
|
||||
[ -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() {
|
||||
local tmp_peers="/tmp/secubox_peers_$$.txt"
|
||||
local seen_dids=""
|
||||
|
||||
# Combine all discovery methods
|
||||
{
|
||||
# Standard discovery methods
|
||||
discovery_scan_mdns
|
||||
discovery_scan_wireguard
|
||||
discovery_scan_arp
|
||||
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"
|
||||
|
||||
# Build peers JSON
|
||||
@ -129,7 +473,7 @@ discovery_scan_peers() {
|
||||
local first=1
|
||||
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
|
||||
|
||||
# Skip self
|
||||
@ -144,7 +488,11 @@ discovery_scan_peers() {
|
||||
local last_seen
|
||||
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
|
||||
done < "$tmp_peers"
|
||||
|
||||
@ -154,6 +502,54 @@ discovery_scan_peers() {
|
||||
echo "$peers_json" > "$PEERS_FILE"
|
||||
|
||||
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
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
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_cmd() {
|
||||
@ -39,7 +43,10 @@ case_list() {
|
||||
"ping": {},
|
||||
"get_config": {},
|
||||
"set_config": { "role": "string", "beacon_interval": "number" },
|
||||
"restart": {}
|
||||
"restart": {},
|
||||
"devices": {},
|
||||
"scan_full": {},
|
||||
"scan_containers": {}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
@ -126,6 +133,72 @@ case_call() {
|
||||
json_add_boolean success 1
|
||||
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_add_string error "Unknown method: $method"
|
||||
|
||||
@ -11,7 +11,9 @@
|
||||
"node_info",
|
||||
"telemetry",
|
||||
"ping",
|
||||
"get_config"
|
||||
"get_config",
|
||||
"devices",
|
||||
"scan_containers"
|
||||
]
|
||||
},
|
||||
"uci": ["secubox"]
|
||||
@ -21,7 +23,8 @@
|
||||
"luci.secubox-mesh": [
|
||||
"node_rotate",
|
||||
"set_config",
|
||||
"restart"
|
||||
"restart",
|
||||
"scan_full"
|
||||
]
|
||||
},
|
||||
"uci": ["secubox"]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user