mirror of https://github.com/jetkvm/kvm.git
feat(ntp): add delay between sync attempts and support NTP servers from DHCP (#162)
* feat(ntp): use ntp server from dhcp info * feat(ntp): use ntp server from dhcp info * feat(ntp): add delay between time sync attempts * chore(ntp): more logging
This commit is contained in:
parent
5217377175
commit
806792203f
1
go.mod
1
go.mod
|
@ -14,6 +14,7 @@ require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
|
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
|
||||||
github.com/hanwen/go-fuse/v2 v2.5.1
|
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/openstadia/go-usb-gadget v0.0.0-20231115171102-aebd56bbb965
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
github.com/pion/mdns/v2 v2.0.7
|
github.com/pion/mdns/v2 v2.0.7
|
||||||
|
|
2
go.sum
2
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/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 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ=
|
||||||
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
|
77
network.go
77
network.go
|
@ -1,13 +1,19 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-envparse"
|
||||||
"github.com/pion/mdns/v2"
|
"github.com/pion/mdns/v2"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"github.com/vishvananda/netlink/nl"
|
"github.com/vishvananda/netlink/nl"
|
||||||
|
@ -15,11 +21,15 @@ import (
|
||||||
|
|
||||||
var mDNSConn *mdns.Conn
|
var mDNSConn *mdns.Conn
|
||||||
|
|
||||||
var networkState struct {
|
var networkState NetworkState
|
||||||
|
|
||||||
|
type NetworkState struct {
|
||||||
Up bool
|
Up bool
|
||||||
IPv4 string
|
IPv4 string
|
||||||
IPv6 string
|
IPv6 string
|
||||||
MAC string
|
MAC string
|
||||||
|
|
||||||
|
checked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalIpInfo struct {
|
type LocalIpInfo struct {
|
||||||
|
@ -28,43 +38,45 @@ type LocalIpInfo struct {
|
||||||
MAC string
|
MAC string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NetIfName = "eth0"
|
||||||
|
DHCPLeaseFile = "/run/udhcpc.%s.info"
|
||||||
|
)
|
||||||
|
|
||||||
// setDhcpClientState sends signals to udhcpc to change it's current mode
|
// 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.
|
// of operation. Setting active to true will force udhcpc to renew the DHCP lease.
|
||||||
// Setting active to false will put udhcpc into idle mode.
|
// Setting active to false will put udhcpc into idle mode.
|
||||||
func setDhcpClientState(active bool) {
|
func setDhcpClientState(active bool) {
|
||||||
var signal string;
|
var signal string
|
||||||
if active {
|
if active {
|
||||||
signal = "-SIGUSR1"
|
signal = "-SIGUSR1"
|
||||||
} else {
|
} else {
|
||||||
signal = "-SIGUSR2"
|
signal = "-SIGUSR2"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc");
|
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err)
|
fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkNetworkState() {
|
func checkNetworkState() {
|
||||||
iface, err := netlink.LinkByName("eth0")
|
iface, err := netlink.LinkByName(NetIfName)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := struct {
|
newState := NetworkState{
|
||||||
Up bool
|
|
||||||
IPv4 string
|
|
||||||
IPv6 string
|
|
||||||
MAC string
|
|
||||||
}{
|
|
||||||
Up: iface.Attrs().OperState == netlink.OperUp,
|
Up: iface.Attrs().OperState == netlink.OperUp,
|
||||||
MAC: iface.Attrs().HardwareAddr.String(),
|
MAC: iface.Attrs().HardwareAddr.String(),
|
||||||
|
|
||||||
|
checked: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
|
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
|
||||||
if err != nil {
|
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.
|
// If the link is going down, put udhcpc into idle mode.
|
||||||
|
@ -144,6 +156,39 @@ func startMDNS() error {
|
||||||
return nil
|
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() {
|
func init() {
|
||||||
updates := make(chan netlink.LinkUpdate)
|
updates := make(chan netlink.LinkUpdate)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
@ -162,7 +207,7 @@ func init() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case update := <-updates:
|
case update := <-updates:
|
||||||
if update.Link.Attrs().Name == "eth0" {
|
if update.Link.Attrs().Name == NetIfName {
|
||||||
fmt.Printf("link update: %+v\n", update)
|
fmt.Printf("link update: %+v\n", update)
|
||||||
checkNetworkState()
|
checkNetworkState()
|
||||||
}
|
}
|
||||||
|
|
61
ntp.go
61
ntp.go
|
@ -11,20 +11,56 @@ import (
|
||||||
"github.com/beevik/ntp"
|
"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() {
|
func TimeSyncLoop() {
|
||||||
for {
|
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()
|
start := time.Now()
|
||||||
err := SyncSystemTime()
|
err := SyncSystemTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to sync system time: %v", err)
|
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
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("Time sync successful, now is: %v, time taken: %v", time.Now(), time.Since(start))
|
log.Printf("Time sync successful, now is: %v, time taken: %v", time.Now(), time.Since(start))
|
||||||
timeSynced = true
|
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) {
|
func queryNetworkTime() (*time.Time, error) {
|
||||||
ntpServers := []string{
|
ntpServers, err := getNTPServersFromDHCPInfo()
|
||||||
"time.cloudflare.com",
|
if err != nil {
|
||||||
"time.apple.com",
|
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 {
|
for _, server := range ntpServers {
|
||||||
now, err := queryNtpServer(server, 2*time.Second)
|
now, err := queryNtpServer(server, timeSyncTimeout)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
log.Printf("NTP server [%s] returned time: %v\n", server, now)
|
||||||
return now, nil
|
return now, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +101,7 @@ func queryNetworkTime() (*time.Time, error) {
|
||||||
"http://cloudflare.com",
|
"http://cloudflare.com",
|
||||||
}
|
}
|
||||||
for _, url := range httpUrls {
|
for _, url := range httpUrls {
|
||||||
now, err := queryHttpTime(url, 2*time.Second)
|
now, err := queryHttpTime(url, timeSyncTimeout)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return now, nil
|
return now, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue