linuxkit/pkg/host-timesync-daemon/main.go
David Scott d24d0bd559 Add pkg/host-timesync-daemon
Some hypervisors (e.g. hyperkit / xhyve) don't provide a good way to keep
the VM's clock in sync with the Host's clock. NTP will usually keep the
clocks together, but after a the host or VM is suspended and resumed the
clocks can be suddenly too far apart for NTP to work properly.

This simple daemon listens on an AF_VSOCK port and resynchronises the VM
clock from the virtualised hardware clock.

This is a Go conversion of original C code written by
Magnus Skjegstad <magnus@skjegstad.com>

Signed-off-by: David Scott <dave.scott@docker.com>
2017-07-07 11:35:36 +01:00

121 lines
3.4 KiB
Go

package main
import (
"flag"
"fmt"
"log"
"os"
"syscall"
"time"
"unsafe"
"github.com/linuxkit/virtsock/pkg/vsock"
)
// Listen for connections on an AF_VSOCK address and update the system time
// from the hardware clock when a connection is received.
// From <linux/rtc.h> struct rtc_time
type rtcTime struct {
tmSec uint32
tmMin uint32
tmHour uint32
tmMday uint32
tmMon uint32
tmYear uint32
tmWday uint32
tmYday uint32
tmIsdst uint32
}
const (
// iocREAD and friends are from <linux/asm-generic/ioctl.h>
iocREAD = uintptr(2)
iocNRBITS = uintptr(8)
iocNRSHIFT = uintptr(0)
iocTYPEBITS = uintptr(8)
iocTYPESHIFT = iocNRSHIFT + iocNRBITS
iocSIZEBITS = uintptr(14)
iocSIZESHIFT = iocTYPESHIFT + iocTYPEBITS
iocDIRSHIFT = iocSIZESHIFT + iocSIZEBITS
// rtcRDTIMENR and friends are from <linux/rtc.h>
rtcRDTIMENR = uintptr(0x09)
rtcRDTIMETYPE = uintptr(112)
)
func rtcReadTime() rtcTime {
f, err := os.Open("/dev/rtc0")
if err != nil {
log.Fatalf("Failed to open /dev/rtc0: %v", err)
}
defer f.Close()
result := rtcTime{}
arg := uintptr(0)
arg |= (iocREAD << iocDIRSHIFT)
arg |= (rtcRDTIMETYPE << iocTYPESHIFT)
arg |= (rtcRDTIMENR << iocNRSHIFT)
arg |= (unsafe.Sizeof(result) << iocSIZESHIFT)
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), arg, uintptr(unsafe.Pointer(&result)))
if errno != 0 {
log.Fatalf("RTC_RD_TIME failed: %v", errno)
}
return result
}
func main() {
// host-timesync-daemon -cid <cid> -port <port>
cid := flag.Int("cid", 0, "AF_VSOCK CID to listen on")
port := flag.Int("port", 0, "AF_VSOCK port to listen on")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "%s: set the time after an AH_VSOCK connection is received.\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Example usage:\n")
fmt.Fprintf(os.Stderr, "%s -port 0xf3a4\n", os.Args[0])
fmt.Fprintf(os.Stderr, " -- when a connection is received on port 0xf3a4, query the hardware clock and\n")
fmt.Fprintf(os.Stderr, " set the system time. The connection will be closed after the clock has\n")
fmt.Fprintf(os.Stderr, " been changed.\n\n")
fmt.Fprintf(os.Stderr, "Arguments:\n")
flag.PrintDefaults()
}
flag.Parse()
if *port == 0 {
log.Fatalf("Please supply a -port argument")
}
if *cid == 0 {
// by default allow connections from anywhere on the local machine
*cid = vsock.CIDAny
}
l, err := vsock.Listen(uint32(*cid), uint32(*port))
if err != nil {
log.Fatalf("Failed to bind to vsock port %x:%x: %s", *cid, *port, err)
}
log.Printf("Listening on port %x:%x", *cid, *port)
for {
conn, err := l.Accept()
if err != nil {
log.Fatalf("Error accepting connection: %s", err)
}
log.Printf("Connection to: %x:%x from: %s\n", *cid, *port, conn.RemoteAddr())
t := rtcReadTime()
// Assume the RTC is set to UTC. This may not be true on a Windows host but in
// that case we assume the platform is capable of providing a PV clock and
// we don't use this code anyway.
d := time.Date(int(t.tmYear+1900), time.Month(t.tmMon+1), int(t.tmMday), int(t.tmHour), int(t.tmMin), int(t.tmSec), 0, time.UTC)
log.Printf("Setting system clock to %s", d)
tv := syscall.Timeval{
Sec: int64(d.Unix()),
Usec: 0, // the RTC only has second granularity
}
if err = syscall.Settimeofday(&tv); err != nil {
log.Printf("Unexpected failure from Settimeofday: %v", err)
}
// Close after the command terminates. The caller can use this as a notification.
conn.Close()
}
}