Recon network batch 2: fleet relay scan and UDP hints.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
POST /api/v1/recon/relay-scan probes from the dashboard when reachable, otherwise dispatches recon_relay_scan to a same-/24 online agent. Agents reuse probePortsFn for TCP and optionally UDP 53/51820 with dns/wireguard Path Tracer tags.
This commit is contained in:
@@ -827,7 +827,7 @@ func (c *AgentClient) handleCommand(action string, tailLines int, command, path,
|
||||
if c.handleFileCommand(action, path, data) {
|
||||
return
|
||||
}
|
||||
if c.handleReconCommand(action, command) {
|
||||
if c.handleReconCommand(action, command, path) {
|
||||
return
|
||||
}
|
||||
c.sendCommandResult(action, false, "unknown action")
|
||||
|
||||
@@ -24,7 +24,22 @@ func (c *AgentClient) runExecCommand(command string) ([]byte, error) {
|
||||
return exec.Command("/bin/sh", "-c", command).CombinedOutput()
|
||||
}
|
||||
|
||||
func (c *AgentClient) handleReconCommand(action, command string) bool {
|
||||
func (c *AgentClient) handleReconCommand(action, command, path string) bool {
|
||||
if action == "recon_relay_scan" {
|
||||
host := strings.TrimSpace(command)
|
||||
if host == "" {
|
||||
c.sendCommandResult(action, false, "host is required in command field")
|
||||
return true
|
||||
}
|
||||
udpGuess := strings.EqualFold(strings.TrimSpace(path), "true")
|
||||
out, err := deploy.RunRelayScanJSON(host, udpGuess)
|
||||
if err != nil {
|
||||
c.sendCommandResult(action, false, err.Error())
|
||||
return true
|
||||
}
|
||||
c.sendCommandResult(action, true, out)
|
||||
return true
|
||||
}
|
||||
if action == "connectivity_probe" {
|
||||
probe := runConnectivityProbe(c.cfg.ServerURL, c.cfg.PoolHost, c.cfg.PoolPort)
|
||||
b, _ := json.Marshal(probe)
|
||||
|
||||
57
agent/client/recon_relay_scan_test.go
Normal file
57
agent/client/recon_relay_scan_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/deploy"
|
||||
)
|
||||
|
||||
func TestHandleReconRelayScanCommand(t *testing.T) {
|
||||
deploy.SetProbePortsHook(func(host string, ports []int) []int {
|
||||
if host == "10.1.2.3" {
|
||||
return []int{22, 445}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
t.Cleanup(func() { deploy.SetProbePortsHook(nil) })
|
||||
deploy.SetRelayUDPGuessHook(func(host string, ports []int) []deploy.RelayUDPHint {
|
||||
return []deploy.RelayUDPHint{{Port: 51820, Open: true, Service: "wireguard"}}
|
||||
})
|
||||
t.Cleanup(func() { deploy.SetRelayUDPGuessHook(nil) })
|
||||
|
||||
var gotAction string
|
||||
var gotSuccess bool
|
||||
var gotMsg string
|
||||
c := &AgentClient{}
|
||||
c.commandResultHook = func(action string, success bool, message string) {
|
||||
gotAction, gotSuccess, gotMsg = action, success, message
|
||||
}
|
||||
|
||||
if !c.handleReconCommand("recon_relay_scan", "10.1.2.3", "true") {
|
||||
t.Fatal("expected handled")
|
||||
}
|
||||
if gotAction != "recon_relay_scan" || !gotSuccess {
|
||||
t.Fatalf("action=%s success=%v", gotAction, gotSuccess)
|
||||
}
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(gotMsg), &parsed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ports, _ := parsed["open_ports"].([]interface{})
|
||||
if len(ports) != 2 {
|
||||
t.Fatalf("ports=%v", ports)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleReconRelayScanMissingHost(t *testing.T) {
|
||||
c := &AgentClient{}
|
||||
c.commandResultHook = func(action string, success bool, _ string) {
|
||||
if action != "recon_relay_scan" || success {
|
||||
t.Fatalf("action=%s success=%v", action, success)
|
||||
}
|
||||
}
|
||||
if !c.handleReconCommand("recon_relay_scan", "", "") {
|
||||
t.Fatal("expected handled")
|
||||
}
|
||||
}
|
||||
@@ -339,6 +339,8 @@ func ScanLocalSubnet(maxHosts int) string {
|
||||
// probePortsFn overrides TCP port probes in tests (nil = live dial).
|
||||
var probePortsFn func(host string, ports []int) []int
|
||||
|
||||
func SetProbePortsHook(fn func(host string, ports []int) []int) { probePortsFn = fn }
|
||||
|
||||
func probePorts(host string, ports []int) []int {
|
||||
if probePortsFn != nil {
|
||||
return probePortsFn(host, ports)
|
||||
|
||||
94
agent/deploy/relay_scan.go
Normal file
94
agent/deploy/relay_scan.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var RelayScanUDPPorts = []int{53, 51820}
|
||||
|
||||
type RelayScanResult struct {
|
||||
Host string `json:"host"`
|
||||
OpenPorts []int `json:"open_ports"`
|
||||
UDPHints []RelayUDPHint `json:"udp_hints,omitempty"`
|
||||
}
|
||||
|
||||
type RelayUDPHint struct {
|
||||
Port int `json:"port"`
|
||||
Open bool `json:"open"`
|
||||
Service string `json:"service"`
|
||||
}
|
||||
|
||||
var udpGuessPortsFn func(host string, ports []int) []RelayUDPHint
|
||||
|
||||
func SetRelayUDPGuessHook(fn func(host string, ports []int) []RelayUDPHint) { udpGuessPortsFn = fn }
|
||||
|
||||
func RunRelayScan(host string, udpGuess bool) RelayScanResult {
|
||||
host = strings.TrimSpace(host)
|
||||
open := probePorts(host, SubnetReconPorts)
|
||||
result := RelayScanResult{Host: host, OpenPorts: append([]int(nil), open...)}
|
||||
if udpGuess {
|
||||
result.UDPHints = guessRelayUDPPorts(host, RelayScanUDPPorts)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func RunRelayScanJSON(host string, udpGuess bool) (string, error) {
|
||||
b, err := json.Marshal(RunRelayScan(host, udpGuess))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func guessRelayUDPPorts(host string, ports []int) []RelayUDPHint {
|
||||
if udpGuessPortsFn != nil {
|
||||
return udpGuessPortsFn(host, ports)
|
||||
}
|
||||
var out []RelayUDPHint
|
||||
for _, port := range ports {
|
||||
out = append(out, RelayUDPHint{Port: port, Open: probeRelayUDPQuick(host, port), Service: relayUDPServiceLabel(port)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func relayUDPServiceLabel(port int) string {
|
||||
switch port {
|
||||
case 53:
|
||||
return "dns"
|
||||
case 51820:
|
||||
return "wireguard"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func probeRelayUDPQuick(host string, port int) bool {
|
||||
conn, err := net.DialTimeout("udp", net.JoinHostPort(host, strconv.Itoa(port)), 800*time.Millisecond)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
_ = conn.SetDeadline(time.Now().Add(800 * time.Millisecond))
|
||||
if b := relayUDPProbePayload(port); len(b) > 0 {
|
||||
_, _ = conn.Write(b)
|
||||
}
|
||||
buf := make([]byte, 512)
|
||||
n, err := conn.Read(buf)
|
||||
return err == nil && n > 0
|
||||
}
|
||||
|
||||
func relayUDPProbePayload(port int) []byte {
|
||||
switch port {
|
||||
case 53:
|
||||
return []byte{0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x04, 'b', 'i', 'n', 'd', 0x00, 0x00, 0x10, 0x00, 0x03}
|
||||
case 51820:
|
||||
return []byte{0x01, 0x00, 0x00, 0x00}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user