mirror of https://github.com/jetkvm/kvm.git
264 lines
6.4 KiB
Go
264 lines
6.4 KiB
Go
package lldp
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/rs/zerolog"
|
|
"golang.org/x/net/bpf"
|
|
)
|
|
|
|
const IFNAMSIZ = 16
|
|
|
|
var (
|
|
lldpDefaultTTL = 120 * time.Second
|
|
cdpDefaultTTL = 180 * time.Second
|
|
)
|
|
|
|
// from lldpd
|
|
// https://github.com/lldpd/lldpd/blob/9034c9332cca0c8b1a20e1287f0e5fed81f7eb2a/src/daemon/lldpd.h#L246
|
|
var bpfFilter = []bpf.RawInstruction{
|
|
{0x30, 0, 0, 0x00000000}, {0x54, 0, 0, 0x00000001}, {0x15, 0, 16, 0x00000001},
|
|
{0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000088cc},
|
|
{0x20, 0, 0, 0x00000002}, {0x15, 2, 0, 0xc200000e},
|
|
{0x15, 1, 0, 0xc2000003}, {0x15, 0, 2, 0xc2000000},
|
|
{0x28, 0, 0, 0x00000000}, {0x15, 12, 13, 0x00000180},
|
|
{0x20, 0, 0, 0x00000002}, {0x15, 0, 2, 0x52cccccc},
|
|
{0x28, 0, 0, 0x00000000}, {0x15, 8, 9, 0x000001e0},
|
|
{0x15, 1, 0, 0x0ccccccc}, {0x15, 0, 2, 0x81000100},
|
|
{0x28, 0, 0, 0x00000000}, {0x15, 4, 5, 0x00000100},
|
|
{0x20, 0, 0, 0x00000002}, {0x15, 0, 3, 0x2b000000},
|
|
{0x28, 0, 0, 0x00000000}, {0x15, 0, 1, 0x000000e0},
|
|
{0x6, 0, 0, 0x00040000},
|
|
{0x6, 0, 0, 0x00000000},
|
|
}
|
|
|
|
var multicastAddrs = []string{
|
|
// LLDP
|
|
"01:80:C2:00:00:00",
|
|
"01:80:C2:00:00:03",
|
|
"01:80:C2:00:00:0E",
|
|
// CDP
|
|
"01:00:0C:CC:CC:CC",
|
|
}
|
|
|
|
func (l *LLDP) setUpCapture() error {
|
|
logger := l.l.With().Str("interface", l.interfaceName).Logger()
|
|
tPacket, err := afPacketNewTPacket(l.interfaceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logger.Info().Msg("created TPacket")
|
|
|
|
// set up multicast addresses
|
|
// otherwise the kernel might discard the packets
|
|
// another workaround would be to enable promiscuous mode but that's too tricky
|
|
for _, mac := range multicastAddrs {
|
|
hwAddr, err := net.ParseMAC(mac)
|
|
if err != nil {
|
|
logger.Error().Msgf("unable to parse MAC address %s: %s", mac, err)
|
|
continue
|
|
}
|
|
|
|
if err := addMulticastAddr(l.interfaceName, hwAddr); err != nil {
|
|
logger.Error().Msgf("unable to add multicast address %s: %s", mac, err)
|
|
continue
|
|
}
|
|
|
|
logger.Info().
|
|
Interface("hwaddr", hwAddr).
|
|
Msgf("added multicast address")
|
|
}
|
|
|
|
if err = tPacket.SetBPF(bpfFilter); err != nil {
|
|
logger.Error().Msgf("unable to set BPF filter: %s", err)
|
|
tPacket.Close()
|
|
return err
|
|
}
|
|
logger.Info().Msg("BPF filter set")
|
|
|
|
l.pktSource = gopacket.NewPacketSource(tPacket, layers.LayerTypeEthernet)
|
|
l.tPacket = tPacket
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LLDP) startCapture() error {
|
|
logger := l.l.With().Str("interface", l.interfaceName).Logger()
|
|
if l.tPacket == nil {
|
|
return fmt.Errorf("AFPacket not initialized")
|
|
}
|
|
|
|
if l.pktSource == nil {
|
|
return fmt.Errorf("packet source not initialized")
|
|
}
|
|
|
|
go func() {
|
|
logger.Info().Msg("starting capture LLDP ethernet frames")
|
|
|
|
for {
|
|
select {
|
|
case <-l.rxCtx.Done():
|
|
logger.Info().Msg("shutting down LLDP capture")
|
|
return
|
|
case packet := <-l.pktSource.Packets():
|
|
if err := l.handlePacket(packet, &logger); err != nil {
|
|
logger.Error().Msgf("error handling packet: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LLDP) handlePacket(packet gopacket.Packet, logger *zerolog.Logger) error {
|
|
linkLayer := packet.LinkLayer()
|
|
if linkLayer == nil {
|
|
return fmt.Errorf("no link layer")
|
|
}
|
|
|
|
srcMac := linkLayer.LinkFlow().Src().String()
|
|
dstMac := linkLayer.LinkFlow().Dst().String()
|
|
|
|
logger.Trace().
|
|
Str("src_mac", srcMac).
|
|
Str("dst_mac", dstMac).
|
|
Int("length", len(packet.Data())).
|
|
Hex("data", packet.Data()).
|
|
Msg("received packet")
|
|
|
|
lldpRaw := packet.Layer(layers.LayerTypeLinkLayerDiscovery)
|
|
if lldpRaw != nil {
|
|
logger.Trace().Msgf("Found LLDP Frame")
|
|
|
|
lldpInfo := packet.Layer(layers.LayerTypeLinkLayerDiscoveryInfo)
|
|
if lldpInfo == nil {
|
|
return fmt.Errorf("no LLDP info layer")
|
|
}
|
|
|
|
return l.handlePacketLLDP(
|
|
srcMac,
|
|
lldpRaw.(*layers.LinkLayerDiscovery),
|
|
lldpInfo.(*layers.LinkLayerDiscoveryInfo),
|
|
)
|
|
}
|
|
|
|
cdpRaw := packet.Layer(layers.LayerTypeCiscoDiscovery)
|
|
if cdpRaw != nil {
|
|
logger.Trace().Msgf("Found CDP Frame")
|
|
|
|
cdpInfo := packet.Layer(layers.LayerTypeCiscoDiscoveryInfo)
|
|
if cdpInfo == nil {
|
|
return fmt.Errorf("no CDP info layer")
|
|
}
|
|
|
|
return l.handlePacketCDP(
|
|
srcMac,
|
|
cdpRaw.(*layers.CiscoDiscovery),
|
|
cdpInfo.(*layers.CiscoDiscoveryInfo),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LLDP) handlePacketLLDP(mac string, raw *layers.LinkLayerDiscovery, info *layers.LinkLayerDiscoveryInfo) error {
|
|
n := &Neighbor{
|
|
Values: make(map[string]string),
|
|
Source: "lldp",
|
|
Mac: mac,
|
|
}
|
|
gotEnd := false
|
|
|
|
ttl := lldpDefaultTTL
|
|
|
|
for _, v := range raw.Values {
|
|
switch v.Type {
|
|
case layers.LLDPTLVEnd:
|
|
gotEnd = true
|
|
case layers.LLDPTLVChassisID:
|
|
n.ChassisID = string(raw.ChassisID.ID)
|
|
n.Values["chassis_id"] = n.ChassisID
|
|
case layers.LLDPTLVPortID:
|
|
n.PortID = string(raw.PortID.ID)
|
|
n.Values["port_id"] = n.PortID
|
|
case layers.LLDPTLVPortDescription:
|
|
n.PortDescription = info.PortDescription
|
|
n.Values["port_description"] = n.PortDescription
|
|
case layers.LLDPTLVSysName:
|
|
n.SystemName = info.SysName
|
|
n.Values["system_name"] = n.SystemName
|
|
case layers.LLDPTLVSysDescription:
|
|
n.SystemDescription = info.SysDescription
|
|
n.Values["system_description"] = n.SystemDescription
|
|
case layers.LLDPTLVMgmtAddress:
|
|
// n.ManagementAddress = info.MgmtAddress.Address
|
|
case layers.LLDPTLVTTL:
|
|
n.TTL = uint16(raw.TTL)
|
|
ttl = time.Duration(n.TTL) * time.Second
|
|
n.Values["ttl"] = fmt.Sprintf("%d", n.TTL)
|
|
case layers.LLDPTLVOrgSpecific:
|
|
for _, org := range info.OrgTLVs {
|
|
n.Values[fmt.Sprintf("org_specific_%d", org.OUI)] = string(org.Info)
|
|
}
|
|
}
|
|
}
|
|
|
|
if gotEnd || ttl < 1*time.Second {
|
|
l.deleteNeighbor(mac)
|
|
} else {
|
|
l.addNeighbor(mac, *n, ttl)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LLDP) handlePacketCDP(mac string, raw *layers.CiscoDiscovery, info *layers.CiscoDiscoveryInfo) error {
|
|
// TODO: implement full CDP parsing
|
|
n := &Neighbor{
|
|
Values: make(map[string]string),
|
|
Source: "cdp",
|
|
Mac: mac,
|
|
}
|
|
|
|
ttl := cdpDefaultTTL
|
|
|
|
n.ChassisID = info.DeviceID
|
|
n.PortID = info.PortID
|
|
n.SystemName = info.SysName
|
|
n.SystemDescription = info.Platform
|
|
n.TTL = uint16(raw.TTL)
|
|
|
|
if n.TTL > 1 {
|
|
ttl = time.Duration(n.TTL) * time.Second
|
|
}
|
|
|
|
if len(info.MgmtAddresses) > 0 {
|
|
n.ManagementAddress = fmt.Sprintf("%s", info.MgmtAddresses[0])
|
|
}
|
|
|
|
l.addNeighbor(mac, *n, ttl)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *LLDP) shutdownCapture() error {
|
|
if l.tPacket != nil {
|
|
l.l.Info().Msg("closing TPacket")
|
|
l.tPacket.Close()
|
|
l.tPacket = nil
|
|
}
|
|
|
|
if l.pktSource != nil {
|
|
l.l.Info().Msg("closing packet source")
|
|
l.pktSource = nil
|
|
}
|
|
|
|
return nil
|
|
}
|