package arp import ( "os/exec" "regexp" "strings" "penguins.lan/utils" ) type ArpTable struct { AddrToMac map[utils.INet4]utils.Mac } // Lookup returns the canonical MAC for an IPv4 string, false if it isn't in the // table or isn't parseable IPv4 (e.g. an IPv6 peer). func (t *ArpTable) Lookup(ip string) (string, bool) { addr, err := utils.ParseINet4(ip) if err != nil { return "", false } mac, ok := t.AddrToMac[*addr] if !ok { return "", false } return mac.Format(), true } type TableProvider interface { Fetch() (*ArpTable, error) } var arpLine = regexp.MustCompile(`\((\d{1,3}(?:\.\d{1,3}){3})\) at ([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2}){5})`) type SystemProvider struct{} func (SystemProvider) Fetch() (*ArpTable, error) { out, err := exec.Command("arp", "-an").Output() if err != nil { return nil, err } return parseTable(string(out)), nil } func parseTable(out string) *ArpTable { table := &ArpTable{AddrToMac: make(map[utils.INet4]utils.Mac)} for _, line := range strings.Split(out, "\n") { m := arpLine.FindStringSubmatch(line) if m == nil { continue } addr, err := utils.ParseINet4(m[1]) if err != nil { continue } mac, err := utils.ParseMac(m[2]) if err != nil { continue } table.AddrToMac[*addr] = *mac } return table }