Skip to content

Commit 8067922

Browse files
authored
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
1 parent 5217377 commit 8067922

File tree

4 files changed

+120
-27
lines changed

4 files changed

+120
-27
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/google/uuid v1.6.0
1515
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
1616
github.com/hanwen/go-fuse/v2 v2.5.1
17+
github.com/hashicorp/go-envparse v0.1.0
1718
github.com/openstadia/go-usb-gadget v0.0.0-20231115171102-aebd56bbb965
1819
github.com/pion/logging v0.2.2
1920
github.com/pion/mdns/v2 v2.0.7

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto
4949
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
5050
github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ=
5151
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
52+
github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
53+
github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
5254
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
5355
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
5456
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

network.go

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
package kvm
22

33
import (
4+
"bytes"
45
"fmt"
6+
"net"
7+
"os"
8+
"strings"
9+
"time"
10+
11+
"os/exec"
12+
13+
"github.com/hashicorp/go-envparse"
514
"github.com/pion/mdns/v2"
615
"golang.org/x/net/ipv4"
716
"golang.org/x/net/ipv6"
8-
"net"
9-
"os/exec"
10-
"time"
1117

1218
"github.com/vishvananda/netlink"
1319
"github.com/vishvananda/netlink/nl"
1420
)
1521

1622
var mDNSConn *mdns.Conn
1723

18-
var networkState struct {
24+
var networkState NetworkState
25+
26+
type NetworkState struct {
1927
Up bool
2028
IPv4 string
2129
IPv6 string
2230
MAC string
31+
32+
checked bool
2333
}
2434

2535
type LocalIpInfo struct {
@@ -28,43 +38,45 @@ type LocalIpInfo struct {
2838
MAC string
2939
}
3040

41+
const (
42+
NetIfName = "eth0"
43+
DHCPLeaseFile = "/run/udhcpc.%s.info"
44+
)
45+
3146
// setDhcpClientState sends signals to udhcpc to change it's current mode
3247
// of operation. Setting active to true will force udhcpc to renew the DHCP lease.
3348
// Setting active to false will put udhcpc into idle mode.
3449
func setDhcpClientState(active bool) {
35-
var signal string;
50+
var signal string
3651
if active {
3752
signal = "-SIGUSR1"
3853
} else {
3954
signal = "-SIGUSR2"
4055
}
4156

42-
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc");
57+
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc")
4358
if err := cmd.Run(); err != nil {
4459
fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err)
4560
}
4661
}
4762

4863
func checkNetworkState() {
49-
iface, err := netlink.LinkByName("eth0")
64+
iface, err := netlink.LinkByName(NetIfName)
5065
if err != nil {
51-
fmt.Printf("failed to get eth0 interface: %v\n", err)
66+
fmt.Printf("failed to get [%s] interface: %v\n", NetIfName, err)
5267
return
5368
}
5469

55-
newState := struct {
56-
Up bool
57-
IPv4 string
58-
IPv6 string
59-
MAC string
60-
}{
70+
newState := NetworkState{
6171
Up: iface.Attrs().OperState == netlink.OperUp,
6272
MAC: iface.Attrs().HardwareAddr.String(),
73+
74+
checked: true,
6375
}
6476

6577
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
6678
if err != nil {
67-
fmt.Printf("failed to get addresses for eth0: %v\n", err)
79+
fmt.Printf("failed to get addresses for [%s]: %v\n", NetIfName, err)
6880
}
6981

7082
// If the link is going down, put udhcpc into idle mode.
@@ -94,15 +106,15 @@ func checkNetworkState() {
94106

95107
if newState != networkState {
96108
fmt.Println("network state changed")
97-
//restart MDNS
109+
// restart MDNS
98110
startMDNS()
99111
networkState = newState
100112
requestDisplayUpdate()
101113
}
102114
}
103115

104116
func startMDNS() error {
105-
//If server was previously running, stop it
117+
// If server was previously running, stop it
106118
if mDNSConn != nil {
107119
fmt.Printf("Stopping mDNS server\n")
108120
err := mDNSConn.Close()
@@ -111,7 +123,7 @@ func startMDNS() error {
111123
}
112124
}
113125

114-
//Start a new server
126+
// Start a new server
115127
fmt.Printf("Starting mDNS server on jetkvm.local\n")
116128
addr4, err := net.ResolveUDPAddr("udp4", mdns.DefaultAddressIPv4)
117129
if err != nil {
@@ -144,6 +156,39 @@ func startMDNS() error {
144156
return nil
145157
}
146158

159+
func getNTPServersFromDHCPInfo() ([]string, error) {
160+
buf, err := os.ReadFile(fmt.Sprintf(DHCPLeaseFile, NetIfName))
161+
if err != nil {
162+
// do not return error if file does not exist
163+
if os.IsNotExist(err) {
164+
return nil, nil
165+
}
166+
return nil, fmt.Errorf("failed to load udhcpc info: %w", err)
167+
}
168+
169+
// parse udhcpc info
170+
env, err := envparse.Parse(bytes.NewReader(buf))
171+
if err != nil {
172+
return nil, fmt.Errorf("failed to parse udhcpc info: %w", err)
173+
}
174+
175+
val, ok := env["ntpsrv"]
176+
if !ok {
177+
return nil, nil
178+
}
179+
180+
var servers []string
181+
182+
for _, server := range strings.Fields(val) {
183+
if net.ParseIP(server) == nil {
184+
fmt.Printf("invalid NTP server IP: %s, ignoring ... \n", server)
185+
}
186+
servers = append(servers, server)
187+
}
188+
189+
return servers, nil
190+
}
191+
147192
func init() {
148193
updates := make(chan netlink.LinkUpdate)
149194
done := make(chan struct{})
@@ -162,7 +207,7 @@ func init() {
162207
for {
163208
select {
164209
case update := <-updates:
165-
if update.Link.Attrs().Name == "eth0" {
210+
if update.Link.Attrs().Name == NetIfName {
166211
fmt.Printf("link update: %+v\n", update)
167212
checkNetworkState()
168213
}

ntp.go

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,56 @@ import (
1111
"github.com/beevik/ntp"
1212
)
1313

14-
var timeSynced = false
14+
const (
15+
timeSyncRetryStep = 5 * time.Second
16+
timeSyncRetryMaxInt = 1 * time.Minute
17+
timeSyncWaitNetChkInt = 100 * time.Millisecond
18+
timeSyncWaitNetUpInt = 3 * time.Second
19+
timeSyncInterval = 1 * time.Hour
20+
timeSyncTimeout = 2 * time.Second
21+
)
22+
23+
var (
24+
timeSynced = false
25+
timeSyncRetryInterval = 0 * time.Second
26+
defaultNTPServers = []string{
27+
"time.cloudflare.com",
28+
"time.apple.com",
29+
}
30+
)
1531

1632
func TimeSyncLoop() {
1733
for {
18-
fmt.Println("Syncing system time")
34+
if !networkState.checked {
35+
time.Sleep(timeSyncWaitNetChkInt)
36+
continue
37+
}
38+
39+
if !networkState.Up {
40+
log.Printf("Waiting for network to come up")
41+
time.Sleep(timeSyncWaitNetUpInt)
42+
continue
43+
}
44+
45+
log.Printf("Syncing system time")
1946
start := time.Now()
2047
err := SyncSystemTime()
2148
if err != nil {
2249
log.Printf("Failed to sync system time: %v", err)
50+
51+
// retry after a delay
52+
timeSyncRetryInterval += timeSyncRetryStep
53+
time.Sleep(timeSyncRetryInterval)
54+
// reset the retry interval if it exceeds the max interval
55+
if timeSyncRetryInterval > timeSyncRetryMaxInt {
56+
timeSyncRetryInterval = 0
57+
}
58+
2359
continue
2460
}
2561
log.Printf("Time sync successful, now is: %v, time taken: %v", time.Now(), time.Since(start))
2662
timeSynced = true
27-
time.Sleep(1 * time.Hour) //once the first sync is done, sync every hour
63+
time.Sleep(timeSyncInterval) // after the first sync is done
2864
}
2965
}
3066

@@ -41,13 +77,22 @@ func SyncSystemTime() (err error) {
4177
}
4278

4379
func queryNetworkTime() (*time.Time, error) {
44-
ntpServers := []string{
45-
"time.cloudflare.com",
46-
"time.apple.com",
80+
ntpServers, err := getNTPServersFromDHCPInfo()
81+
if err != nil {
82+
log.Printf("failed to get NTP servers from DHCP info: %v\n", err)
4783
}
84+
85+
if ntpServers == nil {
86+
ntpServers = defaultNTPServers
87+
log.Printf("Using default NTP servers: %v\n", ntpServers)
88+
} else {
89+
log.Printf("Using NTP servers from DHCP: %v\n", ntpServers)
90+
}
91+
4892
for _, server := range ntpServers {
49-
now, err := queryNtpServer(server, 2*time.Second)
93+
now, err := queryNtpServer(server, timeSyncTimeout)
5094
if err == nil {
95+
log.Printf("NTP server [%s] returned time: %v\n", server, now)
5196
return now, nil
5297
}
5398
}
@@ -56,7 +101,7 @@ func queryNetworkTime() (*time.Time, error) {
56101
"http://cloudflare.com",
57102
}
58103
for _, url := range httpUrls {
59-
now, err := queryHttpTime(url, 2*time.Second)
104+
now, err := queryHttpTime(url, timeSyncTimeout)
60105
if err == nil {
61106
return now, nil
62107
}

0 commit comments

Comments
 (0)