Files
AetherForge/agent/deploy/arp_unix.go

106 lines
2.2 KiB
Go

//go:build !windows
package deploy
import (
"bufio"
"net"
"os"
"os/exec"
"strings"
)
// arpHosts returns IPv4 hosts in the ARP cache on the local subnets.
// On Linux it reads /proc/net/arp; on macOS/BSD it falls back to running
// `arp -n`. Returns nil if nothing useful is found (caller does full scan).
func arpHosts() []string {
hosts := arpFromProc()
if len(hosts) == 0 {
hosts = arpFromCmd()
}
return hosts
}
// arpFromProc parses Linux's /proc/net/arp.
// Format: IP address HW type Flags HW address Mask Device
func arpFromProc() []string {
f, err := os.Open("/proc/net/arp")
if err != nil {
return nil
}
defer f.Close()
local := getLocalIPs()
subnets := make(map[string]bool)
for _, ip := range local {
subnets[getSubnet(ip)] = true
}
var hosts []string
seen := make(map[string]bool)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 4 {
continue // skip header
}
ip := net.ParseIP(fields[0])
if ip == nil || ip.To4() == nil {
continue
}
// Flags = 0x0 means incomplete — skip
if fields[2] == "0x0" {
continue
}
ipStr := ip.To4().String()
sub := getSubnet(ipStr)
if !subnets[sub] || seen[ipStr] {
continue
}
seen[ipStr] = true
hosts = append(hosts, ipStr)
}
_ = scanner.Err()
return hosts
}
// arpFromCmd runs `arp -n` for macOS/BSD where /proc/net/arp doesn't exist.
func arpFromCmd() []string {
raw, err := exec.Command("arp", "-n").Output()
if err != nil {
return nil
}
out := string(raw)
local := getLocalIPs()
subnets := make(map[string]bool)
for _, ip := range local {
subnets[getSubnet(ip)] = true
}
var hosts []string
seen := make(map[string]bool)
for _, line := range strings.Split(out, "\n") {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
ip := net.ParseIP(fields[0])
if ip == nil || ip.To4() == nil || ip.IsLoopback() {
continue
}
// arp -n marks incomplete entries as "(incomplete)"
if strings.Contains(line, "incomplete") {
continue
}
ipStr := ip.To4().String()
sub := getSubnet(ipStr)
if !subnets[sub] || seen[ipStr] {
continue
}
seen[ipStr] = true
hosts = append(hosts, ipStr)
}
return hosts
}