mirror of https://github.com/jetkvm/kvm.git
feat(lldp): implement rx
This commit is contained in:
parent
83caa8f82d
commit
269222d471
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "JetKVM",
|
"name": "JetKVM",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm",
|
"image": "mcr.microsoft.com/devcontainers/go:1-1.24-bookworm",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
// Should match what is defined in ui/package.json
|
// Should match what is defined in ui/package.json
|
||||||
|
|
|
||||||
27
Makefile
27
Makefile
|
|
@ -8,6 +8,10 @@ VERSION := 0.4.7
|
||||||
PROMETHEUS_TAG := github.com/prometheus/common/version
|
PROMETHEUS_TAG := github.com/prometheus/common/version
|
||||||
KVM_PKG_NAME := github.com/jetkvm/kvm
|
KVM_PKG_NAME := github.com/jetkvm/kvm
|
||||||
|
|
||||||
|
BUILDKIT_FLAVOR := arm-rockchip830-linux-uclibcgnueabihf
|
||||||
|
BUILDKIT_PATH ?= /opt/jetkvm-native-buildkit
|
||||||
|
|
||||||
|
|
||||||
GO_BUILD_ARGS := -tags netgo -tags timetzdata
|
GO_BUILD_ARGS := -tags netgo -tags timetzdata
|
||||||
GO_RELEASE_BUILD_ARGS := -trimpath $(GO_BUILD_ARGS)
|
GO_RELEASE_BUILD_ARGS := -trimpath $(GO_BUILD_ARGS)
|
||||||
GO_LDFLAGS := \
|
GO_LDFLAGS := \
|
||||||
|
|
@ -17,7 +21,19 @@ GO_LDFLAGS := \
|
||||||
-X $(PROMETHEUS_TAG).Revision=$(REVISION) \
|
-X $(PROMETHEUS_TAG).Revision=$(REVISION) \
|
||||||
-X $(KVM_PKG_NAME).builtTimestamp=$(BUILDTS)
|
-X $(KVM_PKG_NAME).builtTimestamp=$(BUILDTS)
|
||||||
|
|
||||||
GO_CMD := GOOS=linux GOARCH=arm GOARM=7 go
|
GO_ARGS := GOOS=linux GOARCH=arm GOARM=7
|
||||||
|
# if BUILDKIT_PATH exists, use buildkit to build
|
||||||
|
ifneq ($(wildcard $(BUILDKIT_PATH)),)
|
||||||
|
GO_ARGS := $(GO_ARGS) \
|
||||||
|
CGO_CFLAGS="-I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/include -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/include" \
|
||||||
|
CGO_LDFLAGS="-L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/lib" \
|
||||||
|
CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \
|
||||||
|
LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \
|
||||||
|
CGO_ENABLED=1
|
||||||
|
endif
|
||||||
|
|
||||||
|
GO_CMD := $(GO_ARGS) go
|
||||||
|
|
||||||
BIN_DIR := $(shell pwd)/bin
|
BIN_DIR := $(shell pwd)/bin
|
||||||
|
|
||||||
TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u)
|
TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u)
|
||||||
|
|
@ -32,6 +48,13 @@ build_dev: hash_resource
|
||||||
$(GO_RELEASE_BUILD_ARGS) \
|
$(GO_RELEASE_BUILD_ARGS) \
|
||||||
-o $(BIN_DIR)/jetkvm_app cmd/main.go
|
-o $(BIN_DIR)/jetkvm_app cmd/main.go
|
||||||
|
|
||||||
|
build_afpacket:
|
||||||
|
@echo "Building..."
|
||||||
|
$(GO_CMD) build \
|
||||||
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||||
|
$(GO_RELEASE_BUILD_ARGS) \
|
||||||
|
-o $(BIN_DIR)/afpacket internal/lldp/cmd/afp.go
|
||||||
|
|
||||||
build_test2json:
|
build_test2json:
|
||||||
$(GO_CMD) build -o $(BIN_DIR)/test2json cmd/test2json
|
$(GO_CMD) build -o $(BIN_DIR)/test2json cmd/test2json
|
||||||
|
|
||||||
|
|
@ -101,4 +124,4 @@ release:
|
||||||
@echo "Uploading release..."
|
@echo "Uploading release..."
|
||||||
@shasum -a 256 bin/jetkvm_app | cut -d ' ' -f 1 > bin/jetkvm_app.sha256
|
@shasum -a 256 bin/jetkvm_app | cut -d ' ' -f 1 > bin/jetkvm_app.sha256
|
||||||
rclone copyto bin/jetkvm_app r2://jetkvm-update/app/$(VERSION)/jetkvm_app
|
rclone copyto bin/jetkvm_app r2://jetkvm-update/app/$(VERSION)/jetkvm_app
|
||||||
rclone copyto bin/jetkvm_app.sha256 r2://jetkvm-update/app/$(VERSION)/jetkvm_app.sha256
|
rclone copyto bin/jetkvm_app.sha256 r2://jetkvm-update/app/$(VERSION)/jetkvm_app.sha256
|
||||||
3
go.mod
3
go.mod
|
|
@ -55,10 +55,13 @@ require (
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0 // indirect
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
|
|
||||||
7
go.sum
7
go.sum
|
|
@ -32,8 +32,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/gin-contrib/logger v1.2.6 h1:EPolruKUTzNXMVBD9LuAFQmRjTs7AH7yKGuXgYqrKWc=
|
github.com/gin-contrib/logger v1.2.5 h1:qVQI4omayQecuN4zX9ZZnsOq7w9J/ZLds3J/FMn8ypM=
|
||||||
github.com/gin-contrib/logger v1.2.6/go.mod h1:7niPrd7F0Nscw/zvgz8RiGJxSdbKM2yfQNy8xCHcm64=
|
github.com/gin-contrib/logger v1.2.5/go.mod h1:/bj+vNMuA2xOEQ1aRHoJ1m9+uyaaXIAxQTvM2llsc6I=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
|
|
@ -58,6 +58,8 @@ github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZat
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||||
|
|
@ -83,6 +85,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package lldp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/google/gopacket/afpacket"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
afPacketBufferSize = 2 // in MiB
|
||||||
|
afPacketSnaplen = 9216
|
||||||
|
)
|
||||||
|
|
||||||
|
func afPacketComputeSize(targetSizeMb int, snaplen int, pageSize int) (
|
||||||
|
frameSize int, blockSize int, numBlocks int, err error) {
|
||||||
|
|
||||||
|
if snaplen < pageSize {
|
||||||
|
frameSize = pageSize / (pageSize / snaplen)
|
||||||
|
} else {
|
||||||
|
frameSize = (snaplen/pageSize + 1) * pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 128 is the default from the gopacket library so just use that
|
||||||
|
blockSize = frameSize * 128
|
||||||
|
numBlocks = (targetSizeMb * 1024 * 1024) / blockSize
|
||||||
|
|
||||||
|
if numBlocks == 0 {
|
||||||
|
return 0, 0, 0, fmt.Errorf("interface buffersize is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameSize, blockSize, numBlocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func afPacketNewTPacket(ifName string) (*afpacket.TPacket, error) {
|
||||||
|
szFrame, szBlock, numBlocks, err := afPacketComputeSize(
|
||||||
|
afPacketBufferSize,
|
||||||
|
afPacketSnaplen,
|
||||||
|
os.Getpagesize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return afpacket.NewTPacket(
|
||||||
|
afpacket.OptInterface(ifName),
|
||||||
|
afpacket.OptFrameSize(szFrame),
|
||||||
|
afpacket.OptBlockSize(szBlock),
|
||||||
|
afpacket.OptNumBlocks(numBlocks),
|
||||||
|
afpacket.OptAddVLANHeader(false),
|
||||||
|
afpacket.SocketRaw,
|
||||||
|
afpacket.TPacketVersion3)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifreq struct {
|
||||||
|
ifrName [IFNAMSIZ]byte
|
||||||
|
ifrHwaddr syscall.RawSockaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func addMulticastAddr(ifName string, addr net.HardwareAddr) error {
|
||||||
|
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
|
var name [IFNAMSIZ]byte
|
||||||
|
copy(name[:], []byte(ifName))
|
||||||
|
|
||||||
|
ifr := &ifreq{
|
||||||
|
ifrName: name,
|
||||||
|
ifrHwaddr: toRawSockaddr(addr),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ep := unix.Syscall(unix.SYS_IOCTL, uintptr(fd),
|
||||||
|
unix.SIOCADDMULTI, uintptr(unsafe.Pointer(ifr)))
|
||||||
|
|
||||||
|
if ep != 0 {
|
||||||
|
return syscall.Errno(ep)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build arm && linux
|
||||||
|
|
||||||
|
package lldp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toRawSockaddr(mac net.HardwareAddr) (sockaddr syscall.RawSockaddr) {
|
||||||
|
for i, n := range mac {
|
||||||
|
sockaddr.Data[i] = uint8(n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build !arm && linux
|
||||||
|
|
||||||
|
package lldp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toRawSockaddr(mac net.HardwareAddr) (sockaddr syscall.RawSockaddr) {
|
||||||
|
for i, n := range mac {
|
||||||
|
sockaddr.Data[i] = int8(n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package lldp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/afpacket"
|
||||||
|
"github.com/jellydator/ttlcache/v3"
|
||||||
|
"github.com/jetkvm/kvm/internal/logging"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultLogger = logging.GetSubsystemLogger("lldp")
|
||||||
|
|
||||||
|
type LLDP struct {
|
||||||
|
l *zerolog.Logger
|
||||||
|
tPacket *afpacket.TPacket
|
||||||
|
pktSource *gopacket.PacketSource
|
||||||
|
|
||||||
|
enableRx bool
|
||||||
|
enableTx bool
|
||||||
|
|
||||||
|
packets chan gopacket.Packet
|
||||||
|
interfaceName string
|
||||||
|
stop chan struct{}
|
||||||
|
|
||||||
|
neighbors *ttlcache.Cache[string, Neighbor]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LLDPOptions struct {
|
||||||
|
InterfaceName string
|
||||||
|
EnableRx bool
|
||||||
|
EnableTx bool
|
||||||
|
Logger *zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLLDP(opts *LLDPOptions) *LLDP {
|
||||||
|
if opts.Logger == nil {
|
||||||
|
opts.Logger = defaultLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.InterfaceName == "" {
|
||||||
|
opts.Logger.Fatal().Msg("InterfaceName is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LLDP{
|
||||||
|
interfaceName: opts.InterfaceName,
|
||||||
|
enableRx: opts.EnableRx,
|
||||||
|
enableTx: opts.EnableTx,
|
||||||
|
l: opts.Logger,
|
||||||
|
neighbors: ttlcache.New(ttlcache.WithTTL[string, Neighbor](1 * time.Hour)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LLDP) Start() error {
|
||||||
|
if l.enableRx {
|
||||||
|
l.l.Info().Msg("setting up AF_PACKET")
|
||||||
|
if err := l.setUpCapture(); err != nil {
|
||||||
|
l.l.Error().Err(err).Msg("unable to set up AF_PACKET")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.startCapture()
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.neighbors.Start()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package lldp
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Neighbor struct {
|
||||||
|
Mac string `json:"mac"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
ChassisID string `json:"chassis_id"`
|
||||||
|
PortID string `json:"port_id"`
|
||||||
|
PortDescription string `json:"port_description"`
|
||||||
|
SystemName string `json:"system_name"`
|
||||||
|
SystemDescription string `json:"system_description"`
|
||||||
|
TTL uint16 `json:"ttl"`
|
||||||
|
ManagementAddress string `json:"management_address"`
|
||||||
|
Values map[string]string `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LLDP) addNeighbor(mac string, neighbor Neighbor, ttl time.Duration) {
|
||||||
|
logger := l.l.With().
|
||||||
|
Str("mac", mac).
|
||||||
|
Interface("neighbor", neighbor).
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
current_neigh := l.neighbors.Get(mac)
|
||||||
|
if current_neigh != nil {
|
||||||
|
current_source := current_neigh.Value().Source
|
||||||
|
if current_source == "lldp" && neighbor.Source != "lldp" {
|
||||||
|
logger.Info().Msg("skip updating neighbor, as LLDP has higher priority")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("adding neighbor")
|
||||||
|
l.neighbors.Set(mac, neighbor, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LLDP) deleteNeighbor(mac string) {
|
||||||
|
logger := l.l.With().
|
||||||
|
Str("mac", mac).
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
logger.Info().Msg("deleting neighbor")
|
||||||
|
l.neighbors.Delete(mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LLDP) GetNeighbors() []Neighbor {
|
||||||
|
items := l.neighbors.Items()
|
||||||
|
neighbors := make([]Neighbor, 0, len(items))
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
neighbors = append(neighbors, item.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbors
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
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 packet := range 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.tPacket.Close()
|
||||||
|
l.tPacket = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.pktSource != nil {
|
||||||
|
l.pktSource = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
11
network.go
11
network.go
|
|
@ -3,6 +3,7 @@ package kvm
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jetkvm/kvm/internal/lldp"
|
||||||
"github.com/jetkvm/kvm/internal/network"
|
"github.com/jetkvm/kvm/internal/network"
|
||||||
"github.com/jetkvm/kvm/internal/udhcpc"
|
"github.com/jetkvm/kvm/internal/udhcpc"
|
||||||
)
|
)
|
||||||
|
|
@ -49,6 +50,16 @@ func networkStateChanged(isOnline bool) {
|
||||||
func initNetwork() error {
|
func initNetwork() error {
|
||||||
ensureConfigLoaded()
|
ensureConfigLoaded()
|
||||||
|
|
||||||
|
lldp := lldp.NewLLDP(&lldp.LLDPOptions{
|
||||||
|
InterfaceName: NetIfName,
|
||||||
|
EnableRx: true,
|
||||||
|
EnableTx: true,
|
||||||
|
Logger: networkLogger,
|
||||||
|
})
|
||||||
|
if err := lldp.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
state, err := network.NewNetworkInterfaceState(&network.NetworkInterfaceOptions{
|
state, err := network.NewNetworkInterfaceState(&network.NetworkInterfaceOptions{
|
||||||
DefaultHostname: GetDefaultHostname(),
|
DefaultHostname: GetDefaultHostname(),
|
||||||
InterfaceName: NetIfName,
|
InterfaceName: NetIfName,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue