feat(lldp): implement rx

This commit is contained in:
Siyuan Miao 2025-06-13 22:06:27 +00:00
parent 1674a6666c
commit 748bfe5477
11 changed files with 608 additions and 26 deletions

View File

@ -1,6 +1,6 @@
{
"name": "JetKVM",
"image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm",
"image": "mcr.microsoft.com/devcontainers/go:1-1.24-bookworm",
"features": {
"ghcr.io/devcontainers/features/node:1": {
// Should match what is defined in ui/package.json

View File

@ -8,6 +8,9 @@ VERSION := 0.4.4
PROMETHEUS_TAG := github.com/prometheus/common/version
KVM_PKG_NAME := github.com/jetkvm/kvm
BUILDKIT_FLAVOR := arm-rockchip830-linux-uclibcgnueabihf
BUILDKIT_PATH ?= /opt/jetkvm-native-buildkit
GO_BUILD_ARGS := -tags netgo
GO_RELEASE_BUILD_ARGS := -trimpath $(GO_BUILD_ARGS)
GO_LDFLAGS := \
@ -17,7 +20,19 @@ GO_LDFLAGS := \
-X $(PROMETHEUS_TAG).Revision=$(REVISION) \
-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
TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u)
@ -32,6 +47,13 @@ build_dev: hash_resource
$(GO_RELEASE_BUILD_ARGS) \
-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:
$(GO_CMD) build -o $(BIN_DIR)/test2json cmd/test2json

28
go.mod
View File

@ -11,12 +11,15 @@ require (
github.com/coreos/go-oidc/v3 v3.11.0
github.com/creack/pty v1.1.23
github.com/fsnotify/fsnotify v1.9.0
github.com/gin-contrib/logger v1.2.6
github.com/gin-gonic/gin v1.10.1
github.com/gin-contrib/logger v1.2.5
github.com/gin-gonic/gin v1.10.0
github.com/google/gopacket v1.1.19
github.com/google/uuid v1.6.0
github.com/guregu/null/v6 v6.0.0
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
github.com/hanwen/go-fuse/v2 v2.8.0
github.com/hanwen/go-fuse/v2 v2.5.1
github.com/hashicorp/go-getter/v2 v2.2.3
github.com/jellydator/ttlcache/v3 v3.3.0
github.com/pion/logging v0.2.3
github.com/pion/mdns/v2 v2.0.7
github.com/pion/webrtc/v4 v4.0.16
@ -30,15 +33,16 @@ require (
github.com/stretchr/testify v1.10.0
github.com/vishvananda/netlink v1.3.0
go.bug.st/serial v1.6.2
golang.org/x/crypto v0.39.0
golang.org/x/net v0.41.0
golang.org/x/sys v0.33.0
golang.org/x/crypto v0.37.0
golang.org/x/net v0.39.0
golang.org/x/sys v0.32.0
)
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@ -52,11 +56,19 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.1.0 // 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/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // 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/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
@ -80,11 +92,13 @@ require (
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/arch v0.17.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/text v0.24.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

75
go.sum
View File

@ -4,6 +4,8 @@ github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@ -32,12 +34,12 @@ 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/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/gin-contrib/logger v1.2.6 h1:EPolruKUTzNXMVBD9LuAFQmRjTs7AH7yKGuXgYqrKWc=
github.com/gin-contrib/logger v1.2.6/go.mod h1:7niPrd7F0Nscw/zvgz8RiGJxSdbKM2yfQNy8xCHcm64=
github.com/gin-contrib/logger v1.2.5 h1:qVQI4omayQecuN4zX9ZZnsOq7w9J/ZLds3J/FMn8ypM=
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/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@ -54,14 +56,30 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
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/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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/guregu/null/v6 v6.0.0 h1:N14VRS+4di81i1PXRiprbQJ9EM9gqBa0+KVMeS/QSjQ=
github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ=
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto5zjQ3/VEnDM5rPbqIFuOhS0U0ByeA=
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
github.com/hanwen/go-fuse/v2 v2.8.0 h1:wV8rG7rmCz8XHSOwBZhG5YcVqcYjkzivjmbaMafPlAs=
github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI=
github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ=
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter/v2 v2.2.3 h1:6CVzhT0KJQHqd9b0pK3xSP0CM/Cv+bVhk+jcaRJ2pGk=
github.com/hashicorp/go-getter/v2 v2.2.3/go.mod h1:hp5Yy0GMQvwWVUmwLs3ygivz1JSLI323hdIE9J9m7TY=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@ -77,6 +95,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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
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/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@ -88,8 +107,12 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -167,6 +190,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
@ -175,23 +200,41 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU=
golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

85
internal/lldp/afpacket.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

68
internal/lldp/lldp.go Normal file
View File

@ -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
}

55
internal/lldp/neigh.go Normal file
View File

@ -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
}

254
internal/lldp/rx.go Normal file
View File

@ -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
}

View File

@ -3,6 +3,7 @@ package kvm
import (
"fmt"
"github.com/jetkvm/kvm/internal/lldp"
"github.com/jetkvm/kvm/internal/network"
"github.com/jetkvm/kvm/internal/udhcpc"
)
@ -31,6 +32,16 @@ func networkStateChanged() {
func initNetwork() error {
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{
DefaultHostname: GetDefaultHostname(),
InterfaceName: NetIfName,