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>
This commit is contained in:
David Scott 2017-06-28 12:28:26 +01:00
parent 5d0a8fd4a8
commit d24d0bd559
4 changed files with 168 additions and 0 deletions

View File

@ -0,0 +1,22 @@
FROM linuxkit/alpine:2e362f4459ba4491655061cccdd2fcc7a2de5eb3 AS mirror
RUN apk add --no-cache go musl-dev git
ENV GOPATH=/go PATH=$PATH:/go/bin
ENV VIRTSOCK_COMMIT=a381dcc5bcddf1d7f449495c373dbf70f8e501c0
RUN mkdir -p $GOPATH/src/github.com/linuxkit && \
cd $GOPATH/src/github.com/linuxkit && \
git clone https://github.com/linuxkit/virtsock.git && \
cd virtsock && \
git checkout $VIRTSOCK_COMMIT
COPY . /go/src/host-timesync-daemon
RUN go-compile.sh /go/src/host-timesync-daemon
FROM scratch
ENTRYPOINT []
CMD []
WORKDIR /
COPY --from=mirror /go/bin/host-timesync-daemon /usr/bin/host-timesync-daemon
CMD ["/usr/bin/host-timesync-daemon", "-port", "0xf3a4"]
LABEL org.mobyproject.config='{"binds": [ "/dev/rtc0:/dev/rtc0" ], "capabilities": ["CAP_SYS_TIME"]}'

View File

@ -0,0 +1,5 @@
IMAGE=host-timesync-daemon
DEPS=$(wildcard *.go)
NETWORK=1
include ../package.mk

View File

@ -0,0 +1,21 @@
### 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 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. When a connection is
received, the daemon
- reads the (hypervisor's virtual) hardware clock via `RTC_RD_TIME` on
`/dev/rtc0`
- calls `settimeofday` to set the time in the VM
- closes the connection
Note the hardware clock has only second granularity so you will still need
NTP (or some other process) to keep the clocks closely synchronised.
To use this, simply connect to the AF_VSOCK port when you want the clock
to be resynchronised. If you want to wait for completion then `read` from
the socket -- `EOF` means the resychronisation is complete.

View File

@ -0,0 +1,120 @@
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()
}
}