mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 17:49:10 +00:00
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:
parent
5d0a8fd4a8
commit
d24d0bd559
22
pkg/host-timesync-daemon/Dockerfile
Normal file
22
pkg/host-timesync-daemon/Dockerfile
Normal 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"]}'
|
5
pkg/host-timesync-daemon/Makefile
Normal file
5
pkg/host-timesync-daemon/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
IMAGE=host-timesync-daemon
|
||||||
|
DEPS=$(wildcard *.go)
|
||||||
|
NETWORK=1
|
||||||
|
|
||||||
|
include ../package.mk
|
21
pkg/host-timesync-daemon/README.md
Normal file
21
pkg/host-timesync-daemon/README.md
Normal 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.
|
120
pkg/host-timesync-daemon/main.go
Normal file
120
pkg/host-timesync-daemon/main.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user