Skip to content

Commit 7aec6fa

Browse files
committed
feat(lldp): show neighbors in UI
1 parent 748bfe5 commit 7aec6fa

File tree

12 files changed

+271
-27
lines changed

12 files changed

+271
-27
lines changed

internal/confparser/confparser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type testNetworkConfig struct {
3939
IPv6Mode null.String `json:"ipv6_mode" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
4040
IPv6Static *testIPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
4141

42-
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,basic,all" default:"basic"`
42+
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,rx_only,tx_only,enabled" default:"enabled"`
4343
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
4444
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
4545
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`

internal/lldp/lldp.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package lldp
22

33
import (
4+
"context"
5+
"sync"
46
"time"
57

68
"github.com/google/gopacket"
@@ -16,6 +18,9 @@ type LLDP struct {
1618
l *zerolog.Logger
1719
tPacket *afpacket.TPacket
1820
pktSource *gopacket.PacketSource
21+
rxCtx context.Context
22+
rxCancel context.CancelFunc
23+
rxLock sync.Mutex
1924

2025
enableRx bool
2126
enableTx bool
@@ -53,6 +58,16 @@ func NewLLDP(opts *LLDPOptions) *LLDP {
5358
}
5459

5560
func (l *LLDP) Start() error {
61+
l.rxLock.Lock()
62+
defer l.rxLock.Unlock()
63+
64+
if l.rxCtx != nil {
65+
l.l.Info().Msg("LLDP already started")
66+
return nil
67+
}
68+
69+
l.rxCtx, l.rxCancel = context.WithCancel(context.Background())
70+
5671
if l.enableRx {
5772
l.l.Info().Msg("setting up AF_PACKET")
5873
if err := l.setUpCapture(); err != nil {
@@ -66,3 +81,23 @@ func (l *LLDP) Start() error {
6681

6782
return nil
6883
}
84+
85+
func (l *LLDP) Stop() error {
86+
l.rxLock.Lock()
87+
defer l.rxLock.Unlock()
88+
89+
if l.rxCancel != nil {
90+
l.rxCancel()
91+
l.rxCancel = nil
92+
l.rxCtx = nil
93+
}
94+
95+
if l.enableRx {
96+
l.shutdownCapture()
97+
}
98+
99+
l.neighbors.Stop()
100+
l.neighbors.DeleteAll()
101+
102+
return nil
103+
}

internal/lldp/neigh.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,7 @@ func (l *LLDP) GetNeighbors() []Neighbor {
5151
neighbors = append(neighbors, item.Value())
5252
}
5353

54+
l.l.Info().Interface("neighbors", neighbors).Msg("neighbors")
55+
5456
return neighbors
5557
}

internal/lldp/rx.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,18 @@ func (l *LLDP) startCapture() error {
9999
go func() {
100100
logger.Info().Msg("starting capture LLDP ethernet frames")
101101

102-
for packet := range l.pktSource.Packets() {
103-
if err := l.handlePacket(packet, &logger); err != nil {
104-
logger.Error().Msgf("error handling packet: %s", err)
102+
for {
103+
select {
104+
case <-l.rxCtx.Done():
105+
logger.Info().Msg("shutting down LLDP capture")
106+
return
107+
case packet := <-l.pktSource.Packets():
108+
if err := l.handlePacket(packet, &logger); err != nil {
109+
logger.Error().Msgf("error handling packet: %s", err)
110+
}
105111
}
106112
}
113+
107114
}()
108115

109116
return nil
@@ -242,11 +249,13 @@ func (l *LLDP) handlePacketCDP(mac string, raw *layers.CiscoDiscovery, info *lay
242249

243250
func (l *LLDP) shutdownCapture() error {
244251
if l.tPacket != nil {
252+
l.l.Info().Msg("closing TPacket")
245253
l.tPacket.Close()
246254
l.tPacket = nil
247255
}
248256

249257
if l.pktSource != nil {
258+
l.l.Info().Msg("closing packet source")
250259
l.pktSource = nil
251260
}
252261

internal/network/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type NetworkConfig struct {
4141
IPv6Mode null.String `json:"ipv6_mode,omitempty" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
4242
IPv6Static *IPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
4343

44-
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,basic,all" default:"basic"`
44+
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,rx_only,tx_only,basic,all,enabled" default:"enabled"`
4545
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
4646
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
4747
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`

internal/network/lldp.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package network
2+
3+
import (
4+
"errors"
5+
6+
"github.com/jetkvm/kvm/internal/lldp"
7+
)
8+
9+
func (s *NetworkInterfaceState) shouldStartLLDP() bool {
10+
if s.lldp == nil {
11+
s.l.Trace().Msg("LLDP not initialized")
12+
return false
13+
}
14+
15+
s.l.Trace().Msgf("LLDP mode: %s", s.config.LLDPMode.String)
16+
17+
if s.config.LLDPMode.String == "disabled" {
18+
return false
19+
}
20+
21+
return true
22+
}
23+
24+
func (s *NetworkInterfaceState) startLLDP() {
25+
if !s.shouldStartLLDP() || s.lldp == nil {
26+
return
27+
}
28+
29+
s.l.Trace().Msg("starting LLDP")
30+
s.lldp.Start()
31+
}
32+
33+
func (s *NetworkInterfaceState) stopLLDP() {
34+
if s.lldp == nil {
35+
return
36+
}
37+
s.l.Trace().Msg("stopping LLDP")
38+
s.lldp.Stop()
39+
}
40+
41+
func (s *NetworkInterfaceState) GetLLDPNeighbors() ([]lldp.Neighbor, error) {
42+
if s.lldp == nil {
43+
return nil, errors.New("lldp not initialized")
44+
}
45+
return s.lldp.GetNeighbors(), nil
46+
}

internal/network/netif.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sync"
77

88
"github.com/jetkvm/kvm/internal/confparser"
9+
"github.com/jetkvm/kvm/internal/lldp"
910
"github.com/jetkvm/kvm/internal/logging"
1011
"github.com/jetkvm/kvm/internal/udhcpc"
1112
"github.com/rs/zerolog"
@@ -29,6 +30,8 @@ type NetworkInterfaceState struct {
2930
config *NetworkConfig
3031
dhcpClient *udhcpc.DHCPClient
3132

33+
lldp *lldp.LLDP
34+
3235
defaultHostname string
3336
currentHostname string
3437
currentFqdn string
@@ -96,8 +99,16 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
9699
},
97100
})
98101

99-
s.dhcpClient = dhcpClient
102+
// create the lldp service
103+
lldpClient := lldp.NewLLDP(&lldp.LLDPOptions{
104+
InterfaceName: opts.InterfaceName,
105+
EnableRx: true,
106+
EnableTx: true,
107+
Logger: l,
108+
})
100109

110+
s.dhcpClient = dhcpClient
111+
s.lldp = lldpClient
101112
return s, nil
102113
}
103114

@@ -310,14 +321,30 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
310321
}
311322

312323
if initialCheck {
313-
s.onInitialCheck(s)
324+
s.handleInitialCheck()
314325
} else if changed {
315-
s.onStateChange(s)
326+
s.handleStateChange()
316327
}
317328

318329
return dhcpTargetState, nil
319330
}
320331

332+
func (s *NetworkInterfaceState) handleInitialCheck() {
333+
if s.IsUp() {
334+
s.startLLDP()
335+
}
336+
s.onInitialCheck(s)
337+
}
338+
339+
func (s *NetworkInterfaceState) handleStateChange() {
340+
if s.IsUp() {
341+
s.startLLDP()
342+
} else {
343+
s.stopLLDP()
344+
}
345+
s.onStateChange(s)
346+
}
347+
321348
func (s *NetworkInterfaceState) CheckAndUpdateDhcp() error {
322349
dhcpTargetState, err := s.update()
323350
if err != nil {

jsonrpc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,4 +1104,5 @@ var rpcHandlers = map[string]RPCHandler{
11041104
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
11051105
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
11061106
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
1107+
"getLLDPNeighbors": {Func: rpcGetLLDPNeighbors},
11071108
}

network.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,6 @@ func networkStateChanged() {
3232
func initNetwork() error {
3333
ensureConfigLoaded()
3434

35-
lldp := lldp.NewLLDP(&lldp.LLDPOptions{
36-
InterfaceName: NetIfName,
37-
EnableRx: true,
38-
EnableTx: true,
39-
Logger: networkLogger,
40-
})
41-
if err := lldp.Start(); err != nil {
42-
return err
43-
}
44-
4535
state, err := network.NewNetworkInterfaceState(&network.NetworkInterfaceOptions{
4636
DefaultHostname: GetDefaultHostname(),
4737
InterfaceName: NetIfName,
@@ -116,3 +106,7 @@ func rpcSetNetworkSettings(settings network.RpcNetworkSettings) (*network.RpcNet
116106
func rpcRenewDHCPLease() error {
117107
return networkState.RpcRenewDHCPLease()
118108
}
109+
110+
func rpcGetLLDPNeighbors() ([]lldp.Neighbor, error) {
111+
return networkState.GetLLDPNeighbors()
112+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { LLDPNeighbor } from "../hooks/stores";
2+
import { LifeTimeLabel } from "../routes/devices.$id.settings.network";
3+
4+
import { GridCard } from "./Card";
5+
6+
export default function LLDPNeighCard({
7+
neighbors,
8+
}: {
9+
neighbors: LLDPNeighbor[];
10+
}) {
11+
return (
12+
<GridCard>
13+
<div className="animate-fadeIn p-4 text-black opacity-0 animation-duration-500 dark:text-white">
14+
<div className="space-y-4">
15+
<h3 className="text-base font-bold text-slate-900 dark:text-white">
16+
LLDP Neighbors
17+
</h3>
18+
19+
<div className="space-y-3 pt-2">
20+
{neighbors.map(neighbor => (
21+
<div className="space-y-3" key={neighbor.mac}>
22+
<h4 className="text-sm font-semibold font-mono">{neighbor.mac}</h4>
23+
<div
24+
className="rounded-md rounded-l-none border border-slate-500/10 border-l-blue-700/50 bg-white p-4 pl-4 backdrop-blur-sm dark:bg-transparent"
25+
>
26+
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
27+
<div className="col-span-2 flex flex-col justify-between">
28+
<span className="text-sm text-slate-600 dark:text-slate-400">
29+
Interface
30+
</span>
31+
<span className="text-sm font-medium">{neighbor.port_description}</span>
32+
</div>
33+
34+
{neighbor.system_name && (
35+
<div className="flex flex-col justify-between">
36+
<span className="text-sm text-slate-600 dark:text-slate-400">
37+
System Name
38+
</span>
39+
<span className="text-sm font-medium">{neighbor.system_name}</span>
40+
</div>
41+
)}
42+
43+
{neighbor.system_description && (
44+
<div className="col-span-2 flex flex-col justify-between">
45+
<span className="text-sm text-slate-600 dark:text-slate-400">
46+
System Description
47+
</span>
48+
<span className="text-sm font-medium">{neighbor.system_description}</span>
49+
</div>
50+
)}
51+
52+
53+
{neighbor.port_id && (
54+
<div className="flex flex-col justify-between">
55+
<span className="text-sm text-slate-600 dark:text-slate-400">
56+
Port ID
57+
</span>
58+
<span className="text-sm font-medium">
59+
{neighbor.port_id}
60+
</span>
61+
</div>
62+
)}
63+
64+
65+
{neighbor.port_description && (
66+
<div className="flex flex-col justify-between">
67+
<span className="text-sm text-slate-600 dark:text-slate-400">
68+
Port Description
69+
</span>
70+
<span className="text-sm font-medium">
71+
{neighbor.port_description}
72+
</span>
73+
</div>
74+
)}
75+
</div>
76+
</div>
77+
</div>
78+
))}
79+
</div>
80+
</div>
81+
</div>
82+
</GridCard>
83+
);
84+
}

0 commit comments

Comments
 (0)