network enhanecment / refactor (#361)

* chore(network): improve connectivity check

* refactor(network): rewrite network and timesync component

* feat(display): show cloud connection status

* chore: change logging verbosity

* chore(websecure): update log message

* fix(ota): validate root certificate when downloading update

* feat(ui): add network settings tab

* fix(display): cloud connecting animation

* fix: golintci issues

* feat: add network settings tab

* feat(timesync): query servers in parallel

* refactor(network): move to internal/network package

* feat(timesync): add metrics

* refactor(log): move log to internal/logging package

* refactor(mdms): move mdns to internal/mdns package

* feat(developer): add pprof endpoint

* feat(logging): add a simple logging streaming endpoint

* fix(mdns): do not start mdns until network is up

* feat(network): allow users to update network settings from ui

* fix(network): handle errors when net.IPAddr is nil

* fix(mdns): scopedLogger SIGSEGV

* fix(dhcp): watch directory instead of file to catch fsnotify.Create event

* refactor(nbd): move platform-specific code to different files

* refactor(native): move platform-specific code to different files

* chore: fix linter issues

* chore(dev_deploy): allow to override PION_LOG_TRACE
This commit is contained in:
Aveline
2025-04-16 01:39:23 +02:00
committed by GitHub
parent 2b2a14204d
commit 189b84380b
71 changed files with 4938 additions and 825 deletions

View File

@@ -1,237 +1,107 @@
package kvm
import (
"bytes"
"fmt"
"net"
"os"
"strings"
"time"
"os/exec"
"github.com/hashicorp/go-envparse"
"github.com/pion/mdns/v2"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
"github.com/jetkvm/kvm/internal/network"
"github.com/jetkvm/kvm/internal/udhcpc"
)
var mDNSConn *mdns.Conn
var networkState NetworkState
type NetworkState struct {
Up bool
IPv4 string
IPv6 string
MAC string
checked bool
}
type LocalIpInfo struct {
IPv4 string
IPv6 string
MAC string
}
const (
NetIfName = "eth0"
DHCPLeaseFile = "/run/udhcpc.%s.info"
NetIfName = "eth0"
)
// setDhcpClientState sends signals to udhcpc to change it's current mode
// of operation. Setting active to true will force udhcpc to renew the DHCP lease.
// Setting active to false will put udhcpc into idle mode.
func setDhcpClientState(active bool) {
var signal string
if active {
signal = "-SIGUSR1"
} else {
signal = "-SIGUSR2"
}
var (
networkState *network.NetworkInterfaceState
)
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc")
if err := cmd.Run(); err != nil {
logger.Warn().Err(err).Msg("network: setDhcpClientState: failed to change udhcpc state")
func networkStateChanged() {
// do not block the main thread
go waitCtrlAndRequestDisplayUpdate(true)
// always restart mDNS when the network state changes
if mDNS != nil {
_ = mDNS.SetLocalNames([]string{
networkState.GetHostname(),
networkState.GetFQDN(),
}, true)
}
}
func checkNetworkState() {
iface, err := netlink.LinkByName(NetIfName)
if err != nil {
logger.Warn().Err(err).Str("interface", NetIfName).Msg("failed to get interface")
return
}
func initNetwork() error {
ensureConfigLoaded()
newState := NetworkState{
Up: iface.Attrs().OperState == netlink.OperUp,
MAC: iface.Attrs().HardwareAddr.String(),
state, err := network.NewNetworkInterfaceState(&network.NetworkInterfaceOptions{
DefaultHostname: GetDefaultHostname(),
InterfaceName: NetIfName,
NetworkConfig: config.NetworkConfig,
Logger: networkLogger,
OnStateChange: func(state *network.NetworkInterfaceState) {
networkStateChanged()
},
OnInitialCheck: func(state *network.NetworkInterfaceState) {
networkStateChanged()
},
OnDhcpLeaseChange: func(lease *udhcpc.Lease) {
networkStateChanged()
checked: true,
}
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
if err != nil {
logger.Warn().Err(err).Str("interface", NetIfName).Msg("failed to get addresses")
}
// If the link is going down, put udhcpc into idle mode.
// If the link is coming back up, activate udhcpc and force it to renew the lease.
if newState.Up != networkState.Up {
setDhcpClientState(newState.Up)
}
for _, addr := range addrs {
if addr.IP.To4() != nil {
if !newState.Up && networkState.Up {
// If the network is going down, remove all IPv4 addresses from the interface.
logger.Info().Str("address", addr.IP.String()).Msg("network: state transitioned to down, removing IPv4 address")
err := netlink.AddrDel(iface, &addr)
if err != nil {
logger.Warn().Err(err).Str("address", addr.IP.String()).Msg("network: failed to delete address")
}
newState.IPv4 = "..."
} else {
newState.IPv4 = addr.IP.String()
if currentSession == nil {
return
}
} else if addr.IP.To16() != nil && newState.IPv6 == "" {
newState.IPv6 = addr.IP.String()
}
}
if newState != networkState {
logger.Info().
Interface("newState", newState).
Interface("oldState", networkState).
Msg("network state changed")
writeJSONRPCEvent("networkState", networkState.RpcGetNetworkState(), currentSession)
},
OnConfigChange: func(networkConfig *network.NetworkConfig) {
config.NetworkConfig = networkConfig
networkStateChanged()
// restart MDNS
_ = startMDNS()
networkState = newState
requestDisplayUpdate()
}
}
func startMDNS() error {
// If server was previously running, stop it
if mDNSConn != nil {
logger.Info().Msg("stopping mDNS server")
err := mDNSConn.Close()
if err != nil {
logger.Warn().Err(err).Msg("failed to stop mDNS server")
}
}
// Start a new server
hostname := "jetkvm.local"
scopedLogger := logger.With().Str("hostname", hostname).Logger()
scopedLogger.Info().Msg("starting mDNS server")
addr4, err := net.ResolveUDPAddr("udp4", mdns.DefaultAddressIPv4)
if err != nil {
return err
}
addr6, err := net.ResolveUDPAddr("udp6", mdns.DefaultAddressIPv6)
if err != nil {
return err
}
l4, err := net.ListenUDP("udp4", addr4)
if err != nil {
return err
}
l6, err := net.ListenUDP("udp6", addr6)
if err != nil {
return err
}
mDNSConn, err = mdns.Server(ipv4.NewPacketConn(l4), ipv6.NewPacketConn(l6), &mdns.Config{
LocalNames: []string{hostname}, //TODO: make it configurable
LoggerFactory: defaultLoggerFactory,
if mDNS != nil {
_ = mDNS.SetListenOptions(networkConfig.GetMDNSMode())
_ = mDNS.SetLocalNames([]string{
networkState.GetHostname(),
networkState.GetFQDN(),
}, true)
}
},
})
if err != nil {
scopedLogger.Warn().Err(err).Msg("failed to start mDNS server")
mDNSConn = nil
if state == nil {
if err == nil {
return fmt.Errorf("failed to create NetworkInterfaceState")
}
return err
}
//defer server.Close()
if err := state.Run(); err != nil {
return err
}
networkState = state
return nil
}
func getNTPServersFromDHCPInfo() ([]string, error) {
buf, err := os.ReadFile(fmt.Sprintf(DHCPLeaseFile, NetIfName))
if err != nil {
// do not return error if file does not exist
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to load udhcpc info: %w", err)
}
// parse udhcpc info
env, err := envparse.Parse(bytes.NewReader(buf))
if err != nil {
return nil, fmt.Errorf("failed to parse udhcpc info: %w", err)
}
val, ok := env["ntpsrv"]
if !ok {
return nil, nil
}
var servers []string
for _, server := range strings.Fields(val) {
if net.ParseIP(server) == nil {
logger.Info().Str("server", server).Msg("invalid NTP server IP, ignoring")
}
servers = append(servers, server)
}
return servers, nil
func rpcGetNetworkState() network.RpcNetworkState {
return networkState.RpcGetNetworkState()
}
func initNetwork() {
ensureConfigLoaded()
updates := make(chan netlink.LinkUpdate)
done := make(chan struct{})
if err := netlink.LinkSubscribe(updates, done); err != nil {
logger.Warn().Err(err).Msg("failed to subscribe to link updates")
return
}
go func() {
waitCtrlClientConnected()
checkNetworkState()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case update := <-updates:
if update.Link.Attrs().Name == NetIfName {
logger.Info().Interface("update", update).Msg("link update")
checkNetworkState()
}
case <-ticker.C:
checkNetworkState()
case <-done:
return
}
}
}()
err := startMDNS()
if err != nil {
logger.Warn().Err(err).Msg("failed to run mDNS")
}
func rpcGetNetworkSettings() network.RpcNetworkSettings {
return networkState.RpcGetNetworkSettings()
}
func rpcSetNetworkSettings(settings network.RpcNetworkSettings) (*network.RpcNetworkSettings, error) {
s := networkState.RpcSetNetworkSettings(settings)
if s != nil {
return nil, s
}
if err := SaveConfig(); err != nil {
return nil, err
}
return &network.RpcNetworkSettings{NetworkConfig: *config.NetworkConfig}, nil
}
func rpcRenewDHCPLease() error {
return networkState.RpcRenewDHCPLease()
}