filter multicast macs

This commit is contained in:
Ian Gulliver
2025-11-29 21:08:32 -08:00
parent c215800b17
commit e29e88b598
6 changed files with 214 additions and 13 deletions

114
arp.go Normal file
View File

@@ -0,0 +1,114 @@
package tendrils
import (
"bufio"
"context"
"log"
"net"
"os/exec"
"runtime"
"strings"
"time"
)
func (t *Tendrils) pollARP(ctx context.Context) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
t.readARPTable()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
t.readARPTable()
}
}
}
type arpEntry struct {
ip net.IP
mac net.HardwareAddr
iface string
}
func (t *Tendrils) readARPTable() {
entries := t.parseARPTable()
for _, entry := range entries {
if isBroadcastOrZero(entry.mac) {
continue
}
t.nodes.Update([]net.IP{entry.ip}, []net.HardwareAddr{entry.mac}, entry.iface, "", "arp")
}
}
func (t *Tendrils) parseARPTable() []arpEntry {
if runtime.GOOS == "darwin" {
return t.parseARPDarwin()
}
return t.parseARPLinux()
}
func (t *Tendrils) parseARPDarwin() []arpEntry {
cmd := exec.Command("arp", "-a")
output, err := cmd.Output()
if err != nil {
return nil
}
var entries []arpEntry
scanner := bufio.NewScanner(strings.NewReader(string(output)))
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 6 {
continue
}
ipStr := strings.Trim(fields[1], "()")
ip := net.ParseIP(ipStr)
if ip == nil {
continue
}
macStr := fields[3]
if macStr == "(incomplete)" {
continue
}
macStr = normalizeMACAddress(macStr)
mac, err := net.ParseMAC(macStr)
if err != nil {
log.Printf("[arp] failed to parse MAC %q for IP %s: %v", macStr, ipStr, err)
continue
}
ifaceName := fields[5]
entries = append(entries, arpEntry{
ip: ip,
mac: mac,
iface: ifaceName,
})
}
return entries
}
func (t *Tendrils) parseARPLinux() []arpEntry {
var entries []arpEntry
return entries
}
func normalizeMACAddress(mac string) string {
parts := strings.Split(mac, ":")
for i, part := range parts {
if len(part) == 1 {
parts[i] = "0" + part
}
}
return strings.Join(parts, ":")
}

5
go.mod
View File

@@ -2,6 +2,9 @@ module github.com/gopatchy/tendrils
go 1.24.4 go 1.24.4
require github.com/google/gopacket v1.1.19 require (
github.com/google/gopacket v1.1.19
github.com/mostlygeek/arp v0.0.0-20170424181311-541a2129847a
)
require golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect require golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect

2
go.sum
View File

@@ -1,5 +1,7 @@
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/mostlygeek/arp v0.0.0-20170424181311-541a2129847a h1:AfneHvfmYgUIcgdUrrDFklLdEzQAvG9AKRTe1x1mx/0=
github.com/mostlygeek/arp v0.0.0-20170424181311-541a2129847a/go.mod h1:jZxafo9CAqaKFQE4zitrg5QNlA6CXUsjwXPlIppF3tk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=

14
lldp.go
View File

@@ -77,5 +77,17 @@ func isBroadcastOrZero(mac net.HardwareAddr) bool {
} }
} }
return allZero || allFF if allZero || allFF {
return true
}
if mac[0] == 0x01 && mac[1] == 0x00 && mac[2] == 0x5e {
return true
}
if mac[0] == 0x33 && mac[1] == 0x33 {
return true
}
return false
} }

View File

@@ -197,31 +197,96 @@ func (n *Nodes) logNode(id int, prefix string, isLast bool) {
if id == 0 { if id == 0 {
log.Printf("[root] %s", node) log.Printf("[root] %s", node)
n.logChildrenByInterface(id, "")
} else { } else {
connector := "├──" connector := "├──"
if isLast { if isLast {
connector = "└──" connector = "└──"
} }
if node.ParentPort != "" && node.LocalPort != "" { childPort := node.LocalPort
log.Printf("%s%s %s -> %s on %s", prefix, connector, node.ParentPort, node.LocalPort, node) if childPort == "" {
} else { childPort = "??"
log.Printf("%s%s %s", prefix, connector, node)
} }
}
children := n.getChildren(id) log.Printf("%s%s %s on %s", prefix, connector, childPort, node)
for i, childID := range children {
childIsLast := i == len(children)-1 children := n.getChildren(id)
childPrefix := prefix for i, childID := range children {
if id != 0 { childIsLast := i == len(children)-1
childPrefix := prefix
if isLast { if isLast {
childPrefix += " " childPrefix += " "
} else { } else {
childPrefix += "│ " childPrefix += "│ "
} }
n.logNode(childID, childPrefix, childIsLast)
}
}
}
func (n *Nodes) logChildrenByInterface(parentID int, prefix string) {
children := n.getChildren(parentID)
byInterface := map[string][]int{}
for _, childID := range children {
child := n.nodes[childID]
iface := child.ParentPort
if iface == "" {
iface = "??"
}
byInterface[iface] = append(byInterface[iface], childID)
}
var interfaces []string
for iface := range byInterface {
interfaces = append(interfaces, iface)
}
sort.Strings(interfaces)
for i, iface := range interfaces {
isLastInterface := i == len(interfaces)-1
connector := "├──"
if isLastInterface {
connector = "└──"
}
log.Printf("%s%s %s", prefix, connector, iface)
nodes := byInterface[iface]
for j, nodeID := range nodes {
isLastNode := j == len(nodes)-1
nodeConnector := "├──"
if isLastNode {
nodeConnector = "└──"
}
nodePrefix := prefix
if isLastInterface {
nodePrefix += " "
} else {
nodePrefix += "│ "
}
node := n.nodes[nodeID]
childPort := node.LocalPort
if childPort == "" {
childPort = "??"
}
log.Printf("%s%s %s on %s", nodePrefix, nodeConnector, childPort, node)
grandchildren := n.getChildren(nodeID)
if len(grandchildren) > 0 {
grandchildPrefix := nodePrefix
if isLastNode {
grandchildPrefix += " "
} else {
grandchildPrefix += "│ "
}
n.logChildrenByInterface(nodeID, grandchildPrefix)
}
} }
n.logNode(childID, childPrefix, childIsLast)
} }
} }

View File

@@ -20,6 +20,11 @@ func New() *Tendrils {
} }
func (t *Tendrils) Run() { func (t *Tendrils) Run() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go t.pollARP(ctx)
ticker := time.NewTicker(1 * time.Second) ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() defer ticker.Stop()