mirror of https://github.com/jetkvm/kvm.git
feat: add LLDP neighbors store and update network settings
This commit is contained in:
parent
fd44ff49fd
commit
3e39361aa7
|
|
@ -116,16 +116,6 @@ func (l *LLDP) startRx() error {
|
|||
return l.startCapture()
|
||||
}
|
||||
|
||||
// StopRx stops the LLDP receiver if running
|
||||
func (l *LLDP) StopRx() error {
|
||||
return l.stopCapture()
|
||||
}
|
||||
|
||||
// StopTx stops the LLDP transmitter if running
|
||||
func (l *LLDP) StopTx() error {
|
||||
return l.stopTx()
|
||||
}
|
||||
|
||||
// SetAdvertiseOptions updates the advertise options and resends LLDP packets if TX is running
|
||||
func (l *LLDP) SetAdvertiseOptions(opts *AdvertiseOptions) error {
|
||||
l.mu.Lock()
|
||||
|
|
@ -143,3 +133,34 @@ func (l *LLDP) SetAdvertiseOptions(opts *AdvertiseOptions) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LLDP) SetRxAndTx(rx, tx bool) error {
|
||||
l.mu.Lock()
|
||||
l.enableRx = rx
|
||||
l.enableTx = tx
|
||||
l.mu.Unlock()
|
||||
|
||||
// if rx is enabled, start the RX
|
||||
if rx {
|
||||
if err := l.startRx(); err != nil {
|
||||
return fmt.Errorf("failed to start RX: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := l.stopRx(); err != nil {
|
||||
return fmt.Errorf("failed to stop RX: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if tx is enabled, start the TX
|
||||
if tx {
|
||||
if err := l.startTx(); err != nil {
|
||||
return fmt.Errorf("failed to start TX: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := l.stopTx(); err != nil {
|
||||
return fmt.Errorf("failed to stop TX: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package lldp
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -50,8 +48,10 @@ func (l *LLDP) addNeighbor(neighbor *Neighbor, ttl time.Duration) {
|
|||
}
|
||||
}
|
||||
|
||||
logger.Info().Msg("adding neighbor")
|
||||
logger.Trace().Msg("adding neighbor")
|
||||
l.neighbors.Set(key, *neighbor, ttl)
|
||||
|
||||
l.onChange(l.GetNeighbors())
|
||||
}
|
||||
|
||||
func (l *LLDP) deleteNeighbor(neighbor *Neighbor) {
|
||||
|
|
@ -61,6 +61,8 @@ func (l *LLDP) deleteNeighbor(neighbor *Neighbor) {
|
|||
|
||||
logger.Info().Msg("deleting neighbor")
|
||||
l.neighbors.Delete(neighbor.cacheKey())
|
||||
|
||||
l.onChange(l.GetNeighbors())
|
||||
}
|
||||
|
||||
func (l *LLDP) GetNeighbors() []Neighbor {
|
||||
|
|
@ -71,10 +73,5 @@ func (l *LLDP) GetNeighbors() []Neighbor {
|
|||
neighbors = append(neighbors, item.Value())
|
||||
}
|
||||
|
||||
// sort based on MAC address
|
||||
sort.Slice(neighbors, func(i, j int) bool {
|
||||
return strings.Compare(neighbors[i].Mac, neighbors[j].Mac) > 0
|
||||
})
|
||||
|
||||
return neighbors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,8 +166,7 @@ func (l *LLDP) handlePacket(packet gopacket.Packet, logger *zerolog.Logger) erro
|
|||
|
||||
lldpRaw := packet.Layer(layers.LayerTypeLinkLayerDiscovery)
|
||||
if lldpRaw != nil {
|
||||
logger.Trace().Msg("Found LLDP Frame")
|
||||
l.l.Info().Hex("packet", packet.Data()).Msg("received packet")
|
||||
l.l.Trace().Hex("packet", packet.Data()).Msg("received LLDP frame")
|
||||
|
||||
lldpInfo := packet.Layer(layers.LayerTypeLinkLayerDiscoveryInfo)
|
||||
if lldpInfo == nil {
|
||||
|
|
@ -183,7 +182,7 @@ func (l *LLDP) handlePacket(packet gopacket.Packet, logger *zerolog.Logger) erro
|
|||
|
||||
cdpRaw := packet.Layer(layers.LayerTypeCiscoDiscovery)
|
||||
if cdpRaw != nil {
|
||||
logger.Trace().Msg("Found CDP Frame")
|
||||
l.l.Trace().Hex("packet", packet.Data()).Msg("received CDP frame")
|
||||
|
||||
cdpInfo := packet.Layer(layers.LayerTypeCiscoDiscoveryInfo)
|
||||
if cdpInfo == nil {
|
||||
|
|
@ -351,6 +350,9 @@ func (l *LLDP) stopCapture() error {
|
|||
l.rxCancel = nil
|
||||
}
|
||||
|
||||
// Wait a bit for goroutine to finish
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
if l.tPacketRx != nil {
|
||||
l.tPacketRx.Close()
|
||||
l.tPacketRx = nil
|
||||
|
|
@ -360,7 +362,17 @@ func (l *LLDP) stopCapture() error {
|
|||
l.pktSourceRx = nil
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LLDP) stopRx() error {
|
||||
if err := l.stopCapture(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// clean up the neighbors table
|
||||
l.neighbors.DeleteAll()
|
||||
l.onChange([]Neighbor{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ type NetworkConfig struct {
|
|||
IPv6Mode null.String `json:"ipv6_mode,omitempty" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
|
||||
IPv6Static *IPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
|
||||
|
||||
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,basic,all" default:"basic"`
|
||||
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,rx_only,tx_only,rx_and_tx,basic,all" default:"rx_and_tx"`
|
||||
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
|
||||
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
|
||||
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
|
||||
|
|
@ -53,6 +53,14 @@ type NetworkConfig struct {
|
|||
TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_user_provided"`
|
||||
}
|
||||
|
||||
func (c *NetworkConfig) ShouldEnableLLDPTransmit() bool {
|
||||
return c.LLDPMode.String != "rx_only" && c.LLDPMode.String != "disabled"
|
||||
}
|
||||
|
||||
func (c *NetworkConfig) ShouldEnableLLDPReceive() bool {
|
||||
return c.LLDPMode.String != "tx_only" && c.LLDPMode.String != "disabled"
|
||||
}
|
||||
|
||||
// GetMDNSMode returns the MDNS mode configuration
|
||||
func (c *NetworkConfig) GetMDNSMode() *MDNSListenOptions {
|
||||
mode := c.MDNSMode.String
|
||||
|
|
|
|||
67
network.go
67
network.go
|
|
@ -144,6 +144,15 @@ func validateNetworkConfig() {
|
|||
}
|
||||
}
|
||||
|
||||
func getLLDPAdvertiseOptions(nm *nmlite.NetworkManager) *lldp.AdvertiseOptions {
|
||||
return &lldp.AdvertiseOptions{
|
||||
SysName: nm.Hostname(),
|
||||
SysDescription: toLLDPSysDescription(),
|
||||
SysCapabilities: []string{"other", "router", "wlanap"},
|
||||
EnabledCapabilities: []string{"other"},
|
||||
}
|
||||
}
|
||||
|
||||
func initNetwork() error {
|
||||
ensureConfigLoaded()
|
||||
|
||||
|
|
@ -163,17 +172,11 @@ func initNetwork() error {
|
|||
|
||||
networkManager = nm
|
||||
|
||||
advertiseOptions := &lldp.AdvertiseOptions{
|
||||
SysName: networkManager.Hostname(),
|
||||
SysDescription: toLLDPSysDescription(nc),
|
||||
SysCapabilities: []string{"other", "router", "wlanap"},
|
||||
EnabledCapabilities: []string{"other"},
|
||||
}
|
||||
|
||||
advertiseOptions := getLLDPAdvertiseOptions(nm)
|
||||
lldpService = lldp.NewLLDP(&lldp.Options{
|
||||
InterfaceName: NetIfName,
|
||||
EnableRx: nc.LLDPMode.String != "disabled",
|
||||
EnableTx: nc.LLDPMode.String != "disabled",
|
||||
EnableRx: nc.ShouldEnableLLDPReceive(),
|
||||
EnableTx: nc.ShouldEnableLLDPTransmit(),
|
||||
AdvertiseOptions: advertiseOptions,
|
||||
OnChange: func(neighbors []lldp.Neighbor) {
|
||||
writeJSONRPCEvent("lldpNeighbors", neighbors, currentSession)
|
||||
|
|
@ -187,7 +190,7 @@ func initNetwork() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func toLLDPSysDescription(nc *types.NetworkConfig) string {
|
||||
func toLLDPSysDescription() string {
|
||||
systemVersion, appVersion, err := GetLocalVersion()
|
||||
if err == nil {
|
||||
return fmt.Sprintf("JetKVM (app: %s)", GetBuiltAppVersion())
|
||||
|
|
@ -196,6 +199,21 @@ func toLLDPSysDescription(nc *types.NetworkConfig) string {
|
|||
return fmt.Sprintf("JetKVM (app: %s, system: %s)", appVersion.String(), systemVersion.String())
|
||||
}
|
||||
|
||||
func updateLLDPOptions(nc *types.NetworkConfig) {
|
||||
if lldpService == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := lldpService.SetRxAndTx(nc.ShouldEnableLLDPReceive(), nc.ShouldEnableLLDPTransmit()); err != nil {
|
||||
networkLogger.Error().Err(err).Msg("failed to set LLDP RX and TX")
|
||||
}
|
||||
|
||||
advertiseOptions := getLLDPAdvertiseOptions(networkManager)
|
||||
if err := lldpService.SetAdvertiseOptions(advertiseOptions); err != nil {
|
||||
networkLogger.Error().Err(err).Msg("failed to set LLDP advertise options")
|
||||
}
|
||||
}
|
||||
|
||||
func setHostname(nm *nmlite.NetworkManager, hostname, domain string) error {
|
||||
if nm == nil {
|
||||
return nil
|
||||
|
|
@ -209,6 +227,12 @@ func setHostname(nm *nmlite.NetworkManager, hostname, domain string) error {
|
|||
}
|
||||
|
||||
func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (rebootRequired bool, postRebootAction *PostRebootAction) {
|
||||
rebootReasons := []string{}
|
||||
defer func() {
|
||||
if len(rebootReasons) > 0 {
|
||||
networkLogger.Info().Strs("reasons", rebootReasons).Msg("reboot required")
|
||||
}
|
||||
}()
|
||||
oldDhcpClient := oldConfig.DHCPClient.String
|
||||
|
||||
l := networkLogger.With().
|
||||
|
|
@ -217,9 +241,10 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
|
|||
Logger()
|
||||
|
||||
// DHCP client change always requires reboot
|
||||
if newConfig.DHCPClient.String != oldDhcpClient {
|
||||
newDhcpClient := newConfig.DHCPClient.String
|
||||
if newDhcpClient != oldDhcpClient {
|
||||
rebootRequired = true
|
||||
l.Info().Msg("DHCP client changed, reboot required")
|
||||
rebootReasons = append(rebootReasons, fmt.Sprintf("DHCP client changed from %s to %s", oldDhcpClient, newDhcpClient))
|
||||
return rebootRequired, postRebootAction
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +254,7 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
|
|||
// IPv4 mode change requires reboot
|
||||
if newIPv4Mode != oldIPv4Mode {
|
||||
rebootRequired = true
|
||||
l.Info().Msg("IPv4 mode changed with udhcpc, reboot required")
|
||||
rebootReasons = append(rebootReasons, fmt.Sprintf("IPv4 mode changed from %s to %s", oldIPv4Mode, newIPv4Mode))
|
||||
|
||||
if newIPv4Mode == "static" && oldIPv4Mode != "static" {
|
||||
postRebootAction = &PostRebootAction{
|
||||
|
|
@ -243,8 +268,11 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
|
|||
}
|
||||
|
||||
// IPv4 static config changes require reboot
|
||||
if !reflect.DeepEqual(oldConfig.IPv4Static, newConfig.IPv4Static) {
|
||||
// but if it's not activated, don't care about the changes
|
||||
if !reflect.DeepEqual(oldConfig.IPv4Static, newConfig.IPv4Static) && newIPv4Mode == "static" {
|
||||
rebootRequired = true
|
||||
// TODO: do not restart if it's just the DNS servers that changed
|
||||
rebootReasons = append(rebootReasons, "IPv4 static config changed")
|
||||
|
||||
// Handle IP change for redirect (only if both are not nil and IP changed)
|
||||
if newConfig.IPv4Static != nil && oldConfig.IPv4Static != nil &&
|
||||
|
|
@ -253,17 +281,17 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
|
|||
HealthCheck: fmt.Sprintf("//%s/device/status", newConfig.IPv4Static.Address.String),
|
||||
RedirectTo: fmt.Sprintf("//%s", newConfig.IPv4Static.Address.String),
|
||||
}
|
||||
|
||||
l.Info().Interface("postRebootAction", postRebootAction).Msg("IPv4 static config changed, reboot required")
|
||||
}
|
||||
|
||||
return rebootRequired, postRebootAction
|
||||
}
|
||||
|
||||
// IPv6 mode change requires reboot when using udhcpc
|
||||
oldIPv6Mode := oldConfig.IPv6Mode.String
|
||||
newIPv6Mode := newConfig.IPv6Mode.String
|
||||
if newConfig.IPv6Mode.String != oldConfig.IPv6Mode.String && oldDhcpClient == "udhcpc" {
|
||||
rebootRequired = true
|
||||
l.Info().Msg("IPv6 mode changed with udhcpc, reboot required")
|
||||
rebootReasons = append(rebootReasons, fmt.Sprintf("IPv6 mode changed from %s to %s when using udhcpc", oldIPv6Mode, newIPv6Mode))
|
||||
}
|
||||
|
||||
return rebootRequired, postRebootAction
|
||||
|
|
@ -288,6 +316,8 @@ func rpcSetNetworkSettings(settings RpcNetworkSettings) (*RpcNetworkSettings, er
|
|||
|
||||
l.Debug().Msg("setting new config")
|
||||
|
||||
// TODO: do not restart everything if it's just the LLDP mode that changed
|
||||
|
||||
// Check if reboot is needed
|
||||
rebootRequired, postRebootAction := shouldRebootForNetworkChange(config.NetworkConfig, netConfig)
|
||||
|
||||
|
|
@ -311,6 +341,9 @@ func rpcSetNetworkSettings(settings RpcNetworkSettings) (*RpcNetworkSettings, er
|
|||
}
|
||||
config.NetworkConfig = newConfig
|
||||
|
||||
// update the LLDP advertise options
|
||||
updateLLDPOptions(newConfig)
|
||||
|
||||
l.Debug().Msg("saving new config")
|
||||
if err := SaveConfig(); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -634,10 +634,11 @@
|
|||
"network_ipv6_mode_title": "IPv6 Mode",
|
||||
"network_ipv6_prefix": "IP Prefix",
|
||||
"network_ipv6_prefix_invalid": "Prefix must be between 0 and 128",
|
||||
"network_ll_dp_all": "All",
|
||||
"network_ll_dp_basic": "Basic",
|
||||
"network_ll_dp_description": "Control which TLVs will be sent over Link Layer Discovery Protocol",
|
||||
"network_ll_dp_disabled": "Disabled",
|
||||
"network_ll_dp_rx_only": "Receive only",
|
||||
"network_ll_dp_tx_only": "Transmit only",
|
||||
"network_ll_dp_rx_and_tx": "Receive and transmit",
|
||||
"network_ll_dp_title": "LLDP",
|
||||
"network_mac_address_copy_error": "Failed to copy MAC address",
|
||||
"network_mac_address_copy_success": "MAC address { mac } copied to clipboard",
|
||||
|
|
|
|||
|
|
@ -761,7 +761,7 @@ export type IPv6Mode =
|
|||
| "link_local"
|
||||
| "unknown";
|
||||
export type IPv4Mode = "disabled" | "static" | "dhcp" | "unknown";
|
||||
export type LLDPMode = "disabled" | "basic" | "all" | "tx_only" | "rx_only" | "unknown";
|
||||
export type LLDPMode = "disabled" | "rx_only" | "tx_only" | "rx_and_tx" | "unknown";
|
||||
export type mDNSMode = "disabled" | "auto" | "ipv4_only" | "ipv6_only" | "unknown";
|
||||
export type TimeSyncMode =
|
||||
| "ntp_only"
|
||||
|
|
@ -835,6 +835,18 @@ export const useNetworkStateStore = create<NetworkState>((set, get) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export interface LLDPNeighborsState {
|
||||
neighbors: LLDPNeighbor[];
|
||||
setNeighbors: (neighbors: LLDPNeighbor[]) => void;
|
||||
}
|
||||
|
||||
export const useLLDPNeighborsStore = create<LLDPNeighborsState>((set) => ({
|
||||
neighbors: [],
|
||||
setNeighbors: (neighbors: LLDPNeighbor[]) => set({ neighbors }),
|
||||
}));
|
||||
|
||||
export interface KeySequenceStep {
|
||||
keys: string[];
|
||||
modifiers: string[];
|
||||
|
|
|
|||
|
|
@ -564,8 +564,9 @@ export default function SettingsNetworkRoute() {
|
|||
size="SM"
|
||||
options={[
|
||||
{ value: "disabled", label: m.network_ll_dp_disabled() },
|
||||
{ value: "basic", label: m.network_ll_dp_basic() },
|
||||
{ value: "all", label: m.network_ll_dp_all() },
|
||||
{ value: "rx_only", label: m.network_ll_dp_rx_only() },
|
||||
{ value: "tx_only", label: m.network_ll_dp_tx_only() },
|
||||
{ value: "rx_and_tx", label: m.network_ll_dp_rx_and_tx() },
|
||||
]}
|
||||
{...register("lldp_mode")}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -21,11 +21,13 @@ import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
|||
import {
|
||||
KeyboardLedState,
|
||||
KeysDownState,
|
||||
LLDPNeighbor,
|
||||
NetworkState,
|
||||
OtaState,
|
||||
PostRebootAction,
|
||||
USBStates,
|
||||
useHidStore,
|
||||
useLLDPNeighborsStore,
|
||||
useNetworkStateStore,
|
||||
User,
|
||||
useRTCStore,
|
||||
|
|
@ -612,6 +614,7 @@ export default function KvmIdRoute() {
|
|||
}, 10000);
|
||||
|
||||
const { setNetworkState } = useNetworkStateStore();
|
||||
const { setNeighbors } = useLLDPNeighborsStore();
|
||||
const { setHdmiState } = useVideoStore();
|
||||
const {
|
||||
keyboardLedState, setKeyboardLedState,
|
||||
|
|
@ -634,6 +637,12 @@ export default function KvmIdRoute() {
|
|||
setUsbState(usbState);
|
||||
}
|
||||
|
||||
if (resp.method === "lldpNeighbors") {
|
||||
const neighbors = resp.params as LLDPNeighbor[];
|
||||
console.debug("Setting LLDP neighbors", neighbors);
|
||||
setNeighbors(neighbors);
|
||||
}
|
||||
|
||||
if (resp.method === "videoInputState") {
|
||||
const hdmiState = resp.params as Parameters<VideoState["setHdmiState"]>[0];
|
||||
console.debug("Setting HDMI state", hdmiState);
|
||||
|
|
|
|||
Loading…
Reference in New Issue