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
|
||||
|
||||
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
|
||||
|
||||
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/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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
|
||||
}
|
||||
|
||||
79
nodes.go
79
nodes.go
@@ -197,32 +197,97 @@ func (n *Nodes) logNode(id int, prefix string, isLast bool) {
|
||||
|
||||
if id == 0 {
|
||||
log.Printf("[root] %s", node)
|
||||
n.logChildrenByInterface(id, "")
|
||||
} else {
|
||||
connector := "├──"
|
||||
if isLast {
|
||||
connector = "└──"
|
||||
}
|
||||
|
||||
if node.ParentPort != "" && node.LocalPort != "" {
|
||||
log.Printf("%s%s %s -> %s on %s", prefix, connector, node.ParentPort, node.LocalPort, node)
|
||||
} else {
|
||||
log.Printf("%s%s %s", prefix, connector, node)
|
||||
}
|
||||
childPort := node.LocalPort
|
||||
if childPort == "" {
|
||||
childPort = "??"
|
||||
}
|
||||
|
||||
log.Printf("%s%s %s on %s", prefix, connector, childPort, node)
|
||||
|
||||
children := n.getChildren(id)
|
||||
for i, childID := range children {
|
||||
childIsLast := i == len(children)-1
|
||||
childPrefix := prefix
|
||||
if id != 0 {
|
||||
if isLast {
|
||||
childPrefix += " "
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nodes) getChildren(parentID int) []int {
|
||||
|
||||
@@ -20,6 +20,11 @@ func New() *Tendrils {
|
||||
}
|
||||
|
||||
func (t *Tendrils) Run() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go t.pollARP(ctx)
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user