mirror of https://github.com/jetkvm/kvm.git
fix state change detection
This commit is contained in:
parent
6743db6e3d
commit
ad0b86c8a6
|
|
@ -1,5 +1,10 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DHCPClient is the interface for a DHCP client.
|
||||
type DHCPClient interface {
|
||||
Domain() string
|
||||
|
|
@ -13,3 +18,75 @@ type DHCPClient interface {
|
|||
Start() error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// DHCPLease is a network configuration obtained by DHCP.
|
||||
type DHCPLease struct {
|
||||
// from https://udhcp.busybox.net/README.udhcpc
|
||||
IPAddress net.IP `env:"ip" json:"ip"` // The obtained IP
|
||||
Netmask net.IP `env:"subnet" json:"netmask"` // The assigned subnet mask
|
||||
Broadcast net.IP `env:"broadcast" json:"broadcast"` // The broadcast address for this network
|
||||
TTL int `env:"ipttl" json:"ttl,omitempty"` // The TTL to use for this network
|
||||
MTU int `env:"mtu" json:"mtu,omitempty"` // The MTU to use for this network
|
||||
HostName string `env:"hostname" json:"hostname,omitempty"` // The assigned hostname
|
||||
Domain string `env:"domain" json:"domain,omitempty"` // The domain name of the network
|
||||
SearchList []string `env:"search" json:"search_list,omitempty"` // The search list for the network
|
||||
BootPNextServer net.IP `env:"siaddr" json:"bootp_next_server,omitempty"` // The bootp next server option
|
||||
BootPServerName string `env:"sname" json:"bootp_server_name,omitempty"` // The bootp server name option
|
||||
BootPFile string `env:"boot_file" json:"bootp_file,omitempty"` // The bootp boot file option
|
||||
Timezone string `env:"timezone" json:"timezone,omitempty"` // Offset in seconds from UTC
|
||||
Routers []net.IP `env:"router" json:"routers,omitempty"` // A list of routers
|
||||
DNS []net.IP `env:"dns" json:"dns_servers,omitempty"` // A list of DNS servers
|
||||
NTPServers []net.IP `env:"ntpsrv" json:"ntp_servers,omitempty"` // A list of NTP servers
|
||||
LPRServers []net.IP `env:"lprsvr" json:"lpr_servers,omitempty"` // A list of LPR servers
|
||||
TimeServers []net.IP `env:"timesvr" json:"_time_servers,omitempty"` // A list of time servers (obsolete)
|
||||
IEN116NameServers []net.IP `env:"namesvr" json:"_name_servers,omitempty"` // A list of IEN 116 name servers (obsolete)
|
||||
LogServers []net.IP `env:"logsvr" json:"_log_servers,omitempty"` // A list of MIT-LCS UDP log servers (obsolete)
|
||||
CookieServers []net.IP `env:"cookiesvr" json:"_cookie_servers,omitempty"` // A list of RFC 865 cookie servers (obsolete)
|
||||
WINSServers []net.IP `env:"wins" json:"_wins_servers,omitempty"` // A list of WINS servers
|
||||
SwapServer net.IP `env:"swapsvr" json:"_swap_server,omitempty"` // The IP address of the client's swap server
|
||||
BootSize int `env:"bootsize" json:"bootsize,omitempty"` // The length in 512 octect blocks of the bootfile
|
||||
RootPath string `env:"rootpath" json:"root_path,omitempty"` // The path name of the client's root disk
|
||||
LeaseTime time.Duration `env:"lease" json:"lease,omitempty"` // The lease time, in seconds
|
||||
RenewalTime time.Duration `env:"renewal" json:"renewal,omitempty"` // The renewal time, in seconds
|
||||
RebindingTime time.Duration `env:"rebinding" json:"rebinding,omitempty"` // The rebinding time, in seconds
|
||||
DHCPType string `env:"dhcptype" json:"dhcp_type,omitempty"` // DHCP message type (safely ignored)
|
||||
ServerID string `env:"serverid" json:"server_id,omitempty"` // The IP of the server
|
||||
Message string `env:"message" json:"reason,omitempty"` // Reason for a DHCPNAK
|
||||
TFTPServerName string `env:"tftp" json:"tftp,omitempty"` // The TFTP server name
|
||||
BootFileName string `env:"bootfile" json:"bootfile,omitempty"` // The boot file name
|
||||
Uptime time.Duration `env:"uptime" json:"uptime,omitempty"` // The uptime of the device when the lease was obtained, in seconds
|
||||
ClassIdentifier string `env:"classid" json:"class_identifier,omitempty"` // The class identifier
|
||||
LeaseExpiry *time.Time `json:"lease_expiry,omitempty"` // The expiry time of the lease
|
||||
|
||||
InterfaceName string `json:"interface_name,omitempty"` // The name of the interface
|
||||
DHCPClient string `json:"dhcp_client,omitempty"` // The DHCP client that obtained the lease
|
||||
}
|
||||
|
||||
// IsIPv6 returns true if the DHCP lease is for an IPv6 address
|
||||
func (d *DHCPLease) IsIPv6() bool {
|
||||
return d.IPAddress.To4() == nil
|
||||
}
|
||||
|
||||
// IPMask returns the IP mask for the DHCP lease
|
||||
func (d *DHCPLease) IPMask() net.IPMask {
|
||||
if d.IsIPv6() {
|
||||
// TODO: not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
mask := net.ParseIP(d.Netmask.String())
|
||||
return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15])
|
||||
}
|
||||
|
||||
// IPNet returns the IP net for the DHCP lease
|
||||
func (d *DHCPLease) IPNet() *net.IPNet {
|
||||
if d.IsIPv6() {
|
||||
// TODO: not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: d.IPAddress,
|
||||
Mask: d.IPMask(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type RpcIPv6Address struct {
|
||||
Address string `json:"address"`
|
||||
Prefix string `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
Scope int `json:"scope"`
|
||||
}
|
||||
|
||||
type RpcInterfaceState struct {
|
||||
InterfaceState
|
||||
IPv6Addresses []RpcIPv6Address `json:"ipv6_addresses"`
|
||||
}
|
||||
|
||||
func (s *InterfaceState) ToRpcInterfaceState() *RpcInterfaceState {
|
||||
addrs := make([]RpcIPv6Address, len(s.IPv6Addresses))
|
||||
for i, addr := range s.IPv6Addresses {
|
||||
addrs[i] = RpcIPv6Address{
|
||||
Address: addr.Address.String(),
|
||||
Prefix: addr.Prefix.String(),
|
||||
ValidLifetime: addr.ValidLifetime,
|
||||
PreferredLifetime: addr.PreferredLifetime,
|
||||
Scope: addr.Scope,
|
||||
}
|
||||
}
|
||||
return &RpcInterfaceState{
|
||||
InterfaceState: *s,
|
||||
IPv6Addresses: addrs,
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ type IPv6Address struct {
|
|||
Prefix net.IPNet `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
valid_lft int `json:"valid_lft"`
|
||||
prefered_lft int `json:"prefered_lft"`
|
||||
Scope int `json:"scope"`
|
||||
}
|
||||
|
||||
|
|
@ -107,49 +109,6 @@ func (c *NetworkConfig) GetTransportProxyFunc() func(*http.Request) (*url.URL, e
|
|||
}
|
||||
}
|
||||
|
||||
// DHCPLease is a network configuration obtained by DHCP.
|
||||
type DHCPLease struct {
|
||||
// from https://udhcp.busybox.net/README.udhcpc
|
||||
IPAddress net.IP `env:"ip" json:"ip"` // The obtained IP
|
||||
Netmask net.IP `env:"subnet" json:"netmask"` // The assigned subnet mask
|
||||
Broadcast net.IP `env:"broadcast" json:"broadcast"` // The broadcast address for this network
|
||||
TTL int `env:"ipttl" json:"ttl,omitempty"` // The TTL to use for this network
|
||||
MTU int `env:"mtu" json:"mtu,omitempty"` // The MTU to use for this network
|
||||
HostName string `env:"hostname" json:"hostname,omitempty"` // The assigned hostname
|
||||
Domain string `env:"domain" json:"domain,omitempty"` // The domain name of the network
|
||||
SearchList []string `env:"search" json:"search_list,omitempty"` // The search list for the network
|
||||
BootPNextServer net.IP `env:"siaddr" json:"bootp_next_server,omitempty"` // The bootp next server option
|
||||
BootPServerName string `env:"sname" json:"bootp_server_name,omitempty"` // The bootp server name option
|
||||
BootPFile string `env:"boot_file" json:"bootp_file,omitempty"` // The bootp boot file option
|
||||
Timezone string `env:"timezone" json:"timezone,omitempty"` // Offset in seconds from UTC
|
||||
Routers []net.IP `env:"router" json:"routers,omitempty"` // A list of routers
|
||||
DNS []net.IP `env:"dns" json:"dns_servers,omitempty"` // A list of DNS servers
|
||||
NTPServers []net.IP `env:"ntpsrv" json:"ntp_servers,omitempty"` // A list of NTP servers
|
||||
LPRServers []net.IP `env:"lprsvr" json:"lpr_servers,omitempty"` // A list of LPR servers
|
||||
TimeServers []net.IP `env:"timesvr" json:"_time_servers,omitempty"` // A list of time servers (obsolete)
|
||||
IEN116NameServers []net.IP `env:"namesvr" json:"_name_servers,omitempty"` // A list of IEN 116 name servers (obsolete)
|
||||
LogServers []net.IP `env:"logsvr" json:"_log_servers,omitempty"` // A list of MIT-LCS UDP log servers (obsolete)
|
||||
CookieServers []net.IP `env:"cookiesvr" json:"_cookie_servers,omitempty"` // A list of RFC 865 cookie servers (obsolete)
|
||||
WINSServers []net.IP `env:"wins" json:"_wins_servers,omitempty"` // A list of WINS servers
|
||||
SwapServer net.IP `env:"swapsvr" json:"_swap_server,omitempty"` // The IP address of the client's swap server
|
||||
BootSize int `env:"bootsize" json:"bootsize,omitempty"` // The length in 512 octect blocks of the bootfile
|
||||
RootPath string `env:"rootpath" json:"root_path,omitempty"` // The path name of the client's root disk
|
||||
LeaseTime time.Duration `env:"lease" json:"lease,omitempty"` // The lease time, in seconds
|
||||
RenewalTime time.Duration `env:"renewal" json:"renewal,omitempty"` // The renewal time, in seconds
|
||||
RebindingTime time.Duration `env:"rebinding" json:"rebinding,omitempty"` // The rebinding time, in seconds
|
||||
DHCPType string `env:"dhcptype" json:"dhcp_type,omitempty"` // DHCP message type (safely ignored)
|
||||
ServerID string `env:"serverid" json:"server_id,omitempty"` // The IP of the server
|
||||
Message string `env:"message" json:"reason,omitempty"` // Reason for a DHCPNAK
|
||||
TFTPServerName string `env:"tftp" json:"tftp,omitempty"` // The TFTP server name
|
||||
BootFileName string `env:"bootfile" json:"bootfile,omitempty"` // The boot file name
|
||||
Uptime time.Duration `env:"uptime" json:"uptime,omitempty"` // The uptime of the device when the lease was obtained, in seconds
|
||||
ClassIdentifier string `env:"classid" json:"class_identifier,omitempty"` // The class identifier
|
||||
LeaseExpiry *time.Time `json:"lease_expiry,omitempty"` // The expiry time of the lease
|
||||
|
||||
InterfaceName string `json:"interface_name,omitempty"` // The name of the interface
|
||||
DHCPClient string `json:"dhcp_client,omitempty"` // The DHCP client that obtained the lease
|
||||
}
|
||||
|
||||
// InterfaceState represents the current state of a network interface
|
||||
type InterfaceState struct {
|
||||
InterfaceName string `json:"interface_name"`
|
||||
|
|
@ -175,32 +134,3 @@ type NetworkConfigInterface interface {
|
|||
IPv4Addresses() []IPAddress
|
||||
IPv6Addresses() []IPAddress
|
||||
}
|
||||
|
||||
// IsIPv6 returns true if the DHCP lease is for an IPv6 address
|
||||
func (d *DHCPLease) IsIPv6() bool {
|
||||
return d.IPAddress.To4() == nil
|
||||
}
|
||||
|
||||
// IPMask returns the IP mask for the DHCP lease
|
||||
func (d *DHCPLease) IPMask() net.IPMask {
|
||||
if d.IsIPv6() {
|
||||
// TODO: not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
mask := net.ParseIP(d.Netmask.String())
|
||||
return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15])
|
||||
}
|
||||
|
||||
// IPNet returns the IP net for the DHCP lease
|
||||
func (d *DHCPLease) IPNet() *net.IPNet {
|
||||
if d.IsIPv6() {
|
||||
// TODO: not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: d.IPAddress,
|
||||
Mask: d.IPMask(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ func networkStateChanged(_ string, state types.InterfaceState) {
|
|||
// do not block the main thread
|
||||
go waitCtrlAndRequestDisplayUpdate(true, "network_state_changed")
|
||||
|
||||
if currentSession != nil {
|
||||
writeJSONRPCEvent("networkState", state.ToRpcInterfaceState(), currentSession)
|
||||
}
|
||||
|
||||
if state.Online {
|
||||
networkLogger.Info().Msg("network state changed to online, triggering time sync")
|
||||
triggerTimeSyncOnNetworkStateChange()
|
||||
|
|
@ -108,9 +112,9 @@ func initNetwork() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func rpcGetNetworkState() *types.InterfaceState {
|
||||
func rpcGetNetworkState() *types.RpcInterfaceState {
|
||||
state, _ := networkManager.GetInterfaceState(NetIfName)
|
||||
return state
|
||||
return state.ToRpcInterfaceState()
|
||||
}
|
||||
|
||||
func rpcGetNetworkSettings() *RpcNetworkSettings {
|
||||
|
|
|
|||
|
|
@ -683,116 +683,6 @@ func (im *InterfaceManager) monitorInterfaceState() {
|
|||
|
||||
}
|
||||
|
||||
// updateInterfaceState updates the current interface state
|
||||
func (im *InterfaceManager) updateInterfaceState() error {
|
||||
nl, err := im.link()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface: %w", err)
|
||||
}
|
||||
|
||||
// Check if state changed
|
||||
stateChanged := false
|
||||
|
||||
attrs := nl.Attrs()
|
||||
isUp := attrs.OperState == netlink.OperUp
|
||||
|
||||
// check if the interface has unicast addresses
|
||||
isOnline := isUp && nl.HasGlobalUnicastAddress()
|
||||
|
||||
// We should release the lock before calling the callbacks
|
||||
// to avoid deadlocks
|
||||
im.stateMu.Lock()
|
||||
if im.state.Up != isUp {
|
||||
im.state.Up = isUp
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if im.state.Online != isOnline {
|
||||
im.state.Online = isOnline
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if im.state.MACAddress != attrs.HardwareAddr.String() {
|
||||
im.state.MACAddress = attrs.HardwareAddr.String()
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
// Update IP addresses
|
||||
if err := im.updateIPAddresses(nl); err != nil {
|
||||
im.logger.Error().Err(err).Msg("failed to update IP addresses")
|
||||
}
|
||||
|
||||
im.state.LastUpdated = time.Now()
|
||||
im.stateMu.Unlock()
|
||||
|
||||
// Notify callback if state changed
|
||||
if stateChanged && im.onStateChange != nil {
|
||||
im.logger.Debug().Interface("state", im.state).Msg("notifying state change")
|
||||
im.onStateChange(*im.state)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateIPAddresses updates the IP addresses in the state
|
||||
func (im *InterfaceManager) updateIPAddresses(nl *link.Link) error {
|
||||
if err := nl.Refresh(); err != nil {
|
||||
return fmt.Errorf("failed to refresh link: %w", err)
|
||||
}
|
||||
|
||||
addrs, err := nl.AddrList(link.AfUnspec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get addresses: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
ipv4Addresses []string
|
||||
ipv6Addresses []types.IPv6Address
|
||||
ipv4Addr, ipv6Addr string
|
||||
ipv6LinkLocal string
|
||||
ipv4Ready, ipv6Ready = false, false
|
||||
)
|
||||
|
||||
for _, addr := range addrs {
|
||||
im.logger.Debug().
|
||||
IPAddr("address", addr.IP).
|
||||
Msg("checking address")
|
||||
if addr.IP.To4() != nil {
|
||||
// IPv4 address
|
||||
ipv4Addresses = append(ipv4Addresses, addr.IPNet.String())
|
||||
if ipv4Addr == "" {
|
||||
ipv4Addr = addr.IP.String()
|
||||
ipv4Ready = true
|
||||
}
|
||||
} else if addr.IP.To16() != nil {
|
||||
// IPv6 address
|
||||
if addr.IP.IsLinkLocalUnicast() {
|
||||
ipv6LinkLocal = addr.IP.String()
|
||||
} else if addr.IP.IsGlobalUnicast() {
|
||||
ipv6Addresses = append(ipv6Addresses, types.IPv6Address{
|
||||
Address: addr.IP,
|
||||
Prefix: *addr.IPNet,
|
||||
Scope: addr.Scope,
|
||||
})
|
||||
if ipv6Addr == "" {
|
||||
ipv6Addr = addr.IP.String()
|
||||
ipv6Ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
im.state.IPv4Addresses = ipv4Addresses
|
||||
im.state.IPv6Addresses = ipv6Addresses
|
||||
im.state.IPv6LinkLocal = ipv6LinkLocal
|
||||
im.state.IPv4Address = ipv4Addr
|
||||
im.state.IPv6Address = ipv6Addr
|
||||
im.state.IPv4Ready = ipv4Ready
|
||||
im.state.IPv6Ready = ipv6Ready
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateStateFromDHCPLease updates the state from a DHCP lease
|
||||
func (im *InterfaceManager) updateStateFromDHCPLease(lease *types.DHCPLease) {
|
||||
im.stateMu.Lock()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
package nmlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/network/types"
|
||||
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// updateInterfaceState updates the current interface state
|
||||
func (im *InterfaceManager) updateInterfaceState() error {
|
||||
nl, err := im.link()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface: %w", err)
|
||||
}
|
||||
|
||||
var stateChanged bool
|
||||
|
||||
attrs := nl.Attrs()
|
||||
|
||||
// We should release the lock before calling the callbacks
|
||||
// to avoid deadlocks
|
||||
im.stateMu.Lock()
|
||||
|
||||
// Check if the interface is up
|
||||
isUp := attrs.OperState == netlink.OperUp
|
||||
if im.state.Up != isUp {
|
||||
im.state.Up = isUp
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
// Check if the interface is online
|
||||
isOnline := isUp && nl.HasGlobalUnicastAddress()
|
||||
if im.state.Online != isOnline {
|
||||
im.state.Online = isOnline
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
// Check if the MAC address has changed
|
||||
if im.state.MACAddress != attrs.HardwareAddr.String() {
|
||||
im.state.MACAddress = attrs.HardwareAddr.String()
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
// Update IP addresses
|
||||
if ipChanged, err := im.updateInterfaceStateAddresses(nl); err != nil {
|
||||
im.logger.Error().Err(err).Msg("failed to update IP addresses")
|
||||
} else if ipChanged {
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
im.state.LastUpdated = time.Now()
|
||||
im.stateMu.Unlock()
|
||||
|
||||
// Notify callback if state changed
|
||||
if stateChanged && im.onStateChange != nil {
|
||||
im.logger.Debug().Interface("state", im.state).Msg("notifying state change")
|
||||
im.onStateChange(*im.state)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateIPAddresses updates the IP addresses in the state
|
||||
func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool, error) {
|
||||
addrs, err := nl.AddrList(link.AfUnspec)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get addresses: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
ipv4Addresses []string
|
||||
ipv6Addresses []types.IPv6Address
|
||||
ipv4Addr, ipv6Addr string
|
||||
ipv6LinkLocal string
|
||||
ipv4Ready, ipv6Ready = false, false
|
||||
stateChanged = false
|
||||
)
|
||||
|
||||
for _, addr := range addrs {
|
||||
im.logger.Debug().
|
||||
IPAddr("address", addr.IP).
|
||||
Msg("checking address")
|
||||
if addr.IP.To4() != nil {
|
||||
// IPv4 address
|
||||
ipv4Addresses = append(ipv4Addresses, addr.IPNet.String())
|
||||
if ipv4Addr == "" {
|
||||
ipv4Addr = addr.IP.String()
|
||||
ipv4Ready = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// IPv6 address (if it's not an IPv4 address, it must be an IPv6 address)
|
||||
if addr.IP.IsLinkLocalUnicast() {
|
||||
ipv6LinkLocal = addr.IP.String()
|
||||
continue
|
||||
} else if !addr.IP.IsGlobalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
ipv6Addresses = append(ipv6Addresses, types.IPv6Address{
|
||||
Address: addr.IP,
|
||||
Prefix: *addr.IPNet,
|
||||
Scope: addr.Scope,
|
||||
ValidLifetime: lifetimeToTime(addr.ValidLft),
|
||||
PreferredLifetime: lifetimeToTime(addr.PreferedLft),
|
||||
})
|
||||
if ipv6Addr == "" {
|
||||
ipv6Addr = addr.IP.String()
|
||||
ipv6Ready = true
|
||||
}
|
||||
}
|
||||
|
||||
if !compareStringSlices(im.state.IPv4Addresses, ipv4Addresses) {
|
||||
im.state.IPv4Addresses = ipv4Addresses
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if !compareIPv6AddressSlices(im.state.IPv6Addresses, ipv6Addresses) {
|
||||
im.state.IPv6Addresses = ipv6Addresses
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if im.state.IPv4Address != ipv4Addr {
|
||||
im.state.IPv4Address = ipv4Addr
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if im.state.IPv6Address != ipv6Addr {
|
||||
im.state.IPv6Address = ipv6Addr
|
||||
stateChanged = true
|
||||
}
|
||||
if im.state.IPv6LinkLocal != ipv6LinkLocal {
|
||||
im.state.IPv6LinkLocal = ipv6LinkLocal
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if im.state.IPv4Ready != ipv4Ready {
|
||||
im.state.IPv4Ready = ipv4Ready
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
if im.state.IPv6Ready != ipv6Ready {
|
||||
im.state.IPv6Ready = ipv6Ready
|
||||
stateChanged = true
|
||||
}
|
||||
|
||||
return stateChanged, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package nmlite
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/network/types"
|
||||
)
|
||||
|
||||
func lifetimeToTime(lifetime int) *time.Time {
|
||||
if lifetime == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for infinite lifetime (0xFFFFFFFF = 4294967295)
|
||||
// This is used for static/permanent addresses
|
||||
// Use uint32 to avoid int overflow on 32-bit systems
|
||||
const infiniteLifetime uint32 = 0xFFFFFFFF
|
||||
if uint32(lifetime) == infiniteLifetime || lifetime < 0 {
|
||||
return nil // Infinite lifetime - no expiration
|
||||
}
|
||||
|
||||
// For finite lifetimes (SLAAC addresses)
|
||||
t := time.Now().Add(time.Duration(lifetime) * time.Second)
|
||||
return &t
|
||||
}
|
||||
|
||||
func compareStringSlices(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
sort.Strings(a)
|
||||
sort.Strings(b)
|
||||
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareIPv6AddressSlices(a, b []types.IPv6Address) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
sort.SliceStable(a, func(i, j int) bool {
|
||||
return a[i].Address.String() < b[j].Address.String()
|
||||
})
|
||||
sort.SliceStable(b, func(i, j int) bool {
|
||||
return b[i].Address.String() < a[j].Address.String()
|
||||
})
|
||||
|
||||
for i := range a {
|
||||
if a[i].Address.String() != b[i].Address.String() {
|
||||
return false
|
||||
}
|
||||
if a[i].Prefix.String() != b[i].Prefix.String() {
|
||||
return false
|
||||
}
|
||||
// we don't compare the lifetimes because they are not always same
|
||||
if a[i].Scope != b[i].Scope {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import validator from "validator";
|
|||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
||||
import { NetworkSettings, NetworkState, useRTCStore } from "@/hooks/stores";
|
||||
import { NetworkSettings, NetworkState, useNetworkStateStore, useRTCStore } from "@/hooks/stores";
|
||||
import notifications from "@/notifications";
|
||||
import { getNetworkSettings, getNetworkState } from "@/utils/jsonrpc";
|
||||
import { Button } from "@components/Button";
|
||||
|
|
@ -75,7 +75,8 @@ export function LifeTimeLabel({ lifetime }: { lifetime: string }) {
|
|||
export default function SettingsNetworkRoute() {
|
||||
const { send } = useJsonRpc();
|
||||
|
||||
const [networkState, setNetworkState] = useState<NetworkState | null>(null);
|
||||
const networkState = useNetworkStateStore(state => state);
|
||||
const setNetworkState = useNetworkStateStore(state => state.setNetworkState);
|
||||
|
||||
// Some input needs direct state management. Mostly options that open more details
|
||||
const [customDomain, setCustomDomain] = useState<string>("");
|
||||
|
|
|
|||
|
|
@ -123,9 +123,9 @@ export default function KvmIdRoute() {
|
|||
|
||||
const params = useParams() as { id: string };
|
||||
const { sidebarView, setSidebarView, disableVideoFocusTrap } = useUiStore();
|
||||
const [ queryParams, setQueryParams ] = useSearchParams();
|
||||
const [queryParams, setQueryParams] = useSearchParams();
|
||||
|
||||
const {
|
||||
const {
|
||||
peerConnection, setPeerConnection,
|
||||
peerConnectionState, setPeerConnectionState,
|
||||
setMediaStream,
|
||||
|
|
@ -597,10 +597,10 @@ export default function KvmIdRoute() {
|
|||
});
|
||||
}, 10000);
|
||||
|
||||
const { setNetworkState} = useNetworkStateStore();
|
||||
const { setNetworkState } = useNetworkStateStore();
|
||||
const { setHdmiState } = useVideoStore();
|
||||
const {
|
||||
keyboardLedState, setKeyboardLedState,
|
||||
const {
|
||||
keyboardLedState, setKeyboardLedState,
|
||||
keysDownState, setKeysDownState, setUsbState,
|
||||
} = useHidStore();
|
||||
const setHidRpcDisabled = useRTCStore(state => state.setHidRpcDisabled);
|
||||
|
|
@ -756,7 +756,7 @@ export default function KvmIdRoute() {
|
|||
if (location.pathname !== "/other-session") navigateTo("/");
|
||||
}, [navigateTo, location.pathname]);
|
||||
|
||||
const { appVersion, getLocalVersion} = useVersion();
|
||||
const { appVersion, getLocalVersion } = useVersion();
|
||||
|
||||
useEffect(() => {
|
||||
if (appVersion) return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue