diff --git a/go.mod b/go.mod index 5ddcfb6..a1c9f87 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf github.com/hanwen/go-fuse/v2 v2.5.1 + github.com/hashicorp/go-envparse v0.1.0 github.com/openstadia/go-usb-gadget v0.0.0-20231115171102-aebd56bbb965 github.com/pion/logging v0.2.2 github.com/pion/mdns/v2 v2.0.7 diff --git a/go.sum b/go.sum index be21917..0b3c219 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g= github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ= github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs= +github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= +github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= diff --git a/network.go b/network.go index c9ef919..120f9f6 100644 --- a/network.go +++ b/network.go @@ -1,13 +1,19 @@ 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" - "net" - "os/exec" - "time" "github.com/vishvananda/netlink" "github.com/vishvananda/netlink/nl" @@ -15,11 +21,15 @@ import ( var mDNSConn *mdns.Conn -var networkState struct { +var networkState NetworkState + +type NetworkState struct { Up bool IPv4 string IPv6 string MAC string + + checked bool } type LocalIpInfo struct { @@ -28,43 +38,45 @@ type LocalIpInfo struct { MAC string } +const ( + NetIfName = "eth0" + DHCPLeaseFile = "/run/udhcpc.%s.info" +) + // 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; + var signal string if active { signal = "-SIGUSR1" } else { signal = "-SIGUSR2" } - cmd := exec.Command("/usr/bin/killall", signal, "udhcpc"); + cmd := exec.Command("/usr/bin/killall", signal, "udhcpc") if err := cmd.Run(); err != nil { fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err) } } func checkNetworkState() { - iface, err := netlink.LinkByName("eth0") + iface, err := netlink.LinkByName(NetIfName) if err != nil { - fmt.Printf("failed to get eth0 interface: %v\n", err) + fmt.Printf("failed to get [%s] interface: %v\n", NetIfName, err) return } - newState := struct { - Up bool - IPv4 string - IPv6 string - MAC string - }{ + newState := NetworkState{ Up: iface.Attrs().OperState == netlink.OperUp, MAC: iface.Attrs().HardwareAddr.String(), + + checked: true, } addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL) if err != nil { - fmt.Printf("failed to get addresses for eth0: %v\n", err) + fmt.Printf("failed to get addresses for [%s]: %v\n", NetIfName, err) } // If the link is going down, put udhcpc into idle mode. @@ -94,7 +106,7 @@ func checkNetworkState() { if newState != networkState { fmt.Println("network state changed") - //restart MDNS + // restart MDNS startMDNS() networkState = newState requestDisplayUpdate() @@ -102,7 +114,7 @@ func checkNetworkState() { } func startMDNS() error { - //If server was previously running, stop it + // If server was previously running, stop it if mDNSConn != nil { fmt.Printf("Stopping mDNS server\n") err := mDNSConn.Close() @@ -111,7 +123,7 @@ func startMDNS() error { } } - //Start a new server + // Start a new server fmt.Printf("Starting mDNS server on jetkvm.local\n") addr4, err := net.ResolveUDPAddr("udp4", mdns.DefaultAddressIPv4) if err != nil { @@ -144,6 +156,39 @@ func startMDNS() error { 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 { + fmt.Printf("invalid NTP server IP: %s, ignoring ... \n", server) + } + servers = append(servers, server) + } + + return servers, nil +} + func init() { updates := make(chan netlink.LinkUpdate) done := make(chan struct{}) @@ -162,7 +207,7 @@ func init() { for { select { case update := <-updates: - if update.Link.Attrs().Name == "eth0" { + if update.Link.Attrs().Name == NetIfName { fmt.Printf("link update: %+v\n", update) checkNetworkState() } diff --git a/ntp.go b/ntp.go index f785d96..92d0471 100644 --- a/ntp.go +++ b/ntp.go @@ -11,20 +11,56 @@ import ( "github.com/beevik/ntp" ) -var timeSynced = false +const ( + timeSyncRetryStep = 5 * time.Second + timeSyncRetryMaxInt = 1 * time.Minute + timeSyncWaitNetChkInt = 100 * time.Millisecond + timeSyncWaitNetUpInt = 3 * time.Second + timeSyncInterval = 1 * time.Hour + timeSyncTimeout = 2 * time.Second +) + +var ( + timeSynced = false + timeSyncRetryInterval = 0 * time.Second + defaultNTPServers = []string{ + "time.cloudflare.com", + "time.apple.com", + } +) func TimeSyncLoop() { for { - fmt.Println("Syncing system time") + if !networkState.checked { + time.Sleep(timeSyncWaitNetChkInt) + continue + } + + if !networkState.Up { + log.Printf("Waiting for network to come up") + time.Sleep(timeSyncWaitNetUpInt) + continue + } + + log.Printf("Syncing system time") start := time.Now() err := SyncSystemTime() if err != nil { log.Printf("Failed to sync system time: %v", err) + + // retry after a delay + timeSyncRetryInterval += timeSyncRetryStep + time.Sleep(timeSyncRetryInterval) + // reset the retry interval if it exceeds the max interval + if timeSyncRetryInterval > timeSyncRetryMaxInt { + timeSyncRetryInterval = 0 + } + continue } log.Printf("Time sync successful, now is: %v, time taken: %v", time.Now(), time.Since(start)) timeSynced = true - time.Sleep(1 * time.Hour) //once the first sync is done, sync every hour + time.Sleep(timeSyncInterval) // after the first sync is done } } @@ -41,13 +77,22 @@ func SyncSystemTime() (err error) { } func queryNetworkTime() (*time.Time, error) { - ntpServers := []string{ - "time.cloudflare.com", - "time.apple.com", + ntpServers, err := getNTPServersFromDHCPInfo() + if err != nil { + log.Printf("failed to get NTP servers from DHCP info: %v\n", err) } + + if ntpServers == nil { + ntpServers = defaultNTPServers + log.Printf("Using default NTP servers: %v\n", ntpServers) + } else { + log.Printf("Using NTP servers from DHCP: %v\n", ntpServers) + } + for _, server := range ntpServers { - now, err := queryNtpServer(server, 2*time.Second) + now, err := queryNtpServer(server, timeSyncTimeout) if err == nil { + log.Printf("NTP server [%s] returned time: %v\n", server, now) return now, nil } } @@ -56,7 +101,7 @@ func queryNetworkTime() (*time.Time, error) { "http://cloudflare.com", } for _, url := range httpUrls { - now, err := queryHttpTime(url, 2*time.Second) + now, err := queryHttpTime(url, timeSyncTimeout) if err == nil { return now, nil }