diff --git a/internal/network/types/type.go b/internal/network/types/type.go index cac4b2c4..3544fb35 100644 --- a/internal/network/types/type.go +++ b/internal/network/types/type.go @@ -105,20 +105,46 @@ func (c *NetworkConfig) GetTransportProxyFunc() func(*http.Request) (*url.URL, e } } -// DHCPLease represents a DHCP lease +// DHCPLease is a network configuration obtained by DHCP. type DHCPLease struct { - InterfaceName string `json:"interface_name"` - IPAddress net.IP `json:"ip_address"` - Netmask net.IP `json:"netmask"` - Gateway net.IP `json:"gateway"` - DNS []net.IP `json:"dns"` - SearchList []string `json:"search_list"` - Domain string `json:"domain"` - NTPServers []net.IP `json:"ntp_servers"` - LeaseTime time.Time `json:"lease_time"` - RenewalTime time.Time `json:"renewal_time"` - RebindingTime time.Time `json:"rebinding_time"` - ExpiryTime time.Time `json:"expiry_time"` + // 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 } // InterfaceState represents the current state of a network interface diff --git a/network.go b/network.go index 03618891..de2ff956 100644 --- a/network.go +++ b/network.go @@ -45,7 +45,7 @@ func restartMdns() { }, true) } -func networkStateChanged(isOnline bool) { +func networkStateChanged(iface string, state *types.InterfaceState) { // do not block the main thread go waitCtrlAndRequestDisplayUpdate(true, "network_state_changed") @@ -65,7 +65,7 @@ func networkStateChanged(isOnline bool) { } // if the network is now online, trigger an NTP sync if still needed - if isOnline && timeSync != nil && (isTimeSyncNeeded() || !timeSync.IsSyncSuccess()) { + if state.Up && timeSync != nil && (isTimeSyncNeeded() || !timeSync.IsSyncSuccess()) { if err := timeSync.Sync(); err != nil { logger.Warn().Str("error", err.Error()).Msg("unable to sync time on network state change") } @@ -76,6 +76,7 @@ func initNetwork() error { ensureConfigLoaded() networkManager = nmlite.NewNetworkManager(context.Background(), networkLogger) + networkManager.SetOnInterfaceStateChange(networkStateChanged) networkManager.AddInterface(NetIfName, config.NetworkConfig) return nil diff --git a/pkg/nmlite/dhclient/lease.go b/pkg/nmlite/dhclient/lease.go index d9f10218..b63600bf 100644 --- a/pkg/nmlite/dhclient/lease.go +++ b/pkg/nmlite/dhclient/lease.go @@ -13,6 +13,7 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/jetkvm/kvm/internal/network/types" ) var ( @@ -22,44 +23,7 @@ var ( // Lease is a network configuration obtained by DHCP. type Lease 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 + types.DHCPLease p4 *nclient4.Lease p6 *dhcpv6.Message @@ -67,6 +31,11 @@ type Lease struct { isEmpty map[string]bool } +// ToDHCPLease converts a lease to a DHCP lease. +func (l *Lease) ToDHCPLease() *types.DHCPLease { + return &l.DHCPLease +} + // fromNclient4Lease creates a lease from a nclient4.Lease. func fromNclient4Lease(l *nclient4.Lease, iface string) *Lease { lease := &Lease{} diff --git a/pkg/nmlite/dhcp.go b/pkg/nmlite/dhcp.go index b67857f1..fa1312e6 100644 --- a/pkg/nmlite/dhcp.go +++ b/pkg/nmlite/dhcp.go @@ -207,26 +207,5 @@ func (dc *DHCPClient) convertLease(lease *dhclient.Lease, isIPv6 bool) *types.DH return nil } - // Convert the lease - convertedLease := &types.DHCPLease{ - InterfaceName: dc.ifaceName, - Domain: lease.Domain, - SearchList: lease.SearchList, - NTPServers: lease.NTPServers, - } - - // Set IP address and related information - convertedLease.IPAddress = lease.IPAddress - convertedLease.Netmask = lease.Netmask - if len(lease.Routers) > 0 { - convertedLease.Gateway = lease.Routers[0] - } - - // Set DNS servers - convertedLease.DNS = lease.DNS - // convertedLease.LeaseTime = lease.LeaseTime - // convertedLease.RenewalTime = lease.RenewalTime - // convertedLease.RebindingTime = lease.RebindingTime - - return convertedLease + return lease.ToDHCPLease() } diff --git a/pkg/nmlite/interface.go b/pkg/nmlite/interface.go index f4968c98..90c2e5df 100644 --- a/pkg/nmlite/interface.go +++ b/pkg/nmlite/interface.go @@ -169,6 +169,8 @@ func (im *InterfaceManager) GetState() *types.InterfaceState { defer im.stateMu.RUnlock() // Return a copy to avoid race conditions + im.logger.Debug().Interface("state", im.state).Msg("getting interface state") + state := *im.state return &state } @@ -453,6 +455,8 @@ func (im *InterfaceManager) getDomain() string { func (im *InterfaceManager) monitorInterfaceState() { defer im.wg.Done() + im.logger.Debug().Msg("monitoring interface state") + // TODO: use netlink subscription instead of polling ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() @@ -464,6 +468,7 @@ func (im *InterfaceManager) monitorInterfaceState() { case <-im.stopCh: return case <-ticker.C: + im.logger.Debug().Msg("checking interface state") if err := im.updateInterfaceState(); err != nil { im.logger.Error().Err(err).Msg("failed to update interface state") } @@ -482,7 +487,7 @@ func (im *InterfaceManager) updateInterfaceState() error { isUp := attrs.OperState == netlink.OperUp hasAddrs := false - addrs, err := nl.AddrList(link.AfInet) + addrs, err := nl.AddrList(link.AfUnspec) if err != nil { return fmt.Errorf("failed to get addresses: %w", err) } @@ -504,10 +509,10 @@ func (im *InterfaceManager) updateInterfaceState() error { stateChanged = true } - // if im.state.MACAddr != attrs.HardwareAddr { - // im.state.MACAddr = attrs.HardwareAddr - // 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 { @@ -519,6 +524,7 @@ func (im *InterfaceManager) updateInterfaceState() error { // Notify callback if state changed if stateChanged && im.onStateChange != nil { state := *im.state + im.logger.Debug().Interface("state", state).Msg("notifying state change") im.onStateChange(&state) } @@ -538,6 +544,7 @@ func (im *InterfaceManager) updateIPAddresses(nl *link.Link) error { var ipv6LinkLocal string for _, addr := range addrs { + im.logger.Debug().Str("address", addr.IP.String()).Msg("checking address") if addr.IP.To4() != nil { // IPv4 address ipv4Addresses = append(ipv4Addresses, addr.IPNet.String()) diff --git a/pkg/nmlite/manager.go b/pkg/nmlite/manager.go index 8fe6819f..ba7d3a8f 100644 --- a/pkg/nmlite/manager.go +++ b/pkg/nmlite/manager.go @@ -31,7 +31,7 @@ type NetworkManager struct { // NewNetworkManager creates a new network manager func NewNetworkManager(ctx context.Context, logger *zerolog.Logger) *NetworkManager { if logger == nil { - logger = logging.GetSubsystemLogger("networkmgr") + logger = logging.GetSubsystemLogger("nm") } // Initialize the NetlinkManager singleton diff --git a/ui/src/components/Ipv6NetworkCard.tsx b/ui/src/components/Ipv6NetworkCard.tsx index ac9d20fb..ca6c3079 100644 --- a/ui/src/components/Ipv6NetworkCard.tsx +++ b/ui/src/components/Ipv6NetworkCard.tsx @@ -17,16 +17,14 @@ export default function Ipv6NetworkCard({