From d24d0bd559ad756314ecafb1d0ea2d6d4aef31ae Mon Sep 17 00:00:00 2001 From: David Scott Date: Wed, 28 Jun 2017 12:28:26 +0100 Subject: [PATCH 1/2] 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 Signed-off-by: David Scott --- pkg/host-timesync-daemon/Dockerfile | 22 +++++ pkg/host-timesync-daemon/Makefile | 5 ++ pkg/host-timesync-daemon/README.md | 21 +++++ pkg/host-timesync-daemon/main.go | 120 ++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 pkg/host-timesync-daemon/Dockerfile create mode 100644 pkg/host-timesync-daemon/Makefile create mode 100644 pkg/host-timesync-daemon/README.md create mode 100644 pkg/host-timesync-daemon/main.go diff --git a/pkg/host-timesync-daemon/Dockerfile b/pkg/host-timesync-daemon/Dockerfile new file mode 100644 index 000000000..8c943b4d8 --- /dev/null +++ b/pkg/host-timesync-daemon/Dockerfile @@ -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"]}' diff --git a/pkg/host-timesync-daemon/Makefile b/pkg/host-timesync-daemon/Makefile new file mode 100644 index 000000000..28d66a17d --- /dev/null +++ b/pkg/host-timesync-daemon/Makefile @@ -0,0 +1,5 @@ +IMAGE=host-timesync-daemon +DEPS=$(wildcard *.go) +NETWORK=1 + +include ../package.mk diff --git a/pkg/host-timesync-daemon/README.md b/pkg/host-timesync-daemon/README.md new file mode 100644 index 000000000..6cb3fde7e --- /dev/null +++ b/pkg/host-timesync-daemon/README.md @@ -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. diff --git a/pkg/host-timesync-daemon/main.go b/pkg/host-timesync-daemon/main.go new file mode 100644 index 000000000..56c368e20 --- /dev/null +++ b/pkg/host-timesync-daemon/main.go @@ -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 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 + 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 + 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 -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() + } +} From c9de2fb2a5b4b288117fa0602faab5e2696ce143 Mon Sep 17 00:00:00 2001 From: David Scott Date: Wed, 28 Jun 2017 14:59:25 +0100 Subject: [PATCH 2/2] blueprints/docker-for-mac.yml: add host-timesync-daemon This adds the helper program which resynchronises the VM's clock after a host resume. Signed-off-by: David Scott --- blueprints/docker-for-mac/base.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blueprints/docker-for-mac/base.yml b/blueprints/docker-for-mac/base.yml index ce8811a6e..ae959f90d 100644 --- a/blueprints/docker-for-mac/base.yml +++ b/blueprints/docker-for-mac/base.yml @@ -61,6 +61,9 @@ services: # Monitor for image deletes and invoke a TRIM on the container filesystem - name: trim-after-delete image: "linuxkit/trim-after-delete:2a5fcbe080cd4a45bd75c2ea3856c069475d706d" + # When the host resumes from sleep, force a clock resync + - name: host-timesync-daemon + image: "linuxkit/host-timesync-daemon:e6c15e6ef75d302c1c22827bac249598fb365a83" trust: org: