filter multicast macs
This commit is contained in:
114
arp.go
Normal file
114
arp.go
Normal 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
5
go.mod
@@ -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
2
go.sum
@@ -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
14
lldp.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
87
nodes.go
87
nodes.go
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user