From 6120525f81701424e97d453d515d38f14bbe99d9 Mon Sep 17 00:00:00 2001
From: holyfei <yangfeiyu20092010@163.com>
Date: Wed, 19 Aug 2020 20:25:00 +0800
Subject: [PATCH 16/16] clock: synchronizes clock info with proxy

reason: virtual machine's clock may be incorrect, proxy synchronizes
clock info to help virtual machine adjust clock time

Signed-off-by: yangfeiyu <yangfeiyu2@huawei.com>
---
 pkg/clock/clock_util.go   | 47 ++++++++++++++++++++++++
 sync_clock_server.go      | 94 +++++++++++++++++++++++++++++++++++++++++++++++
 sync_clock_server_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 229 insertions(+)
 create mode 100644 pkg/clock/clock_util.go
 create mode 100644 sync_clock_server.go
 create mode 100644 sync_clock_server_test.go

diff --git a/pkg/clock/clock_util.go b/pkg/clock/clock_util.go
new file mode 100644
index 0000000..4b78c63
--- /dev/null
+++ b/pkg/clock/clock_util.go
@@ -0,0 +1,47 @@
+// Copyright (c) Huawei Technologies Co., Ltd. 2018. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Description: sync clock related function
+// Author: xueshaojia
+// Create: 2018-11-10
+
+// Package clock provides clock sync functions
+package clock
+
+import (
+	"net"
+	"syscall"
+)
+
+type TimeValue struct {
+	ClientSendTime   int64 `json:"client_send_time"`
+	ClientArriveTime int64 `json:"client_arrive_time"`
+	ServerSendTime   int64 `json:"server_send_time"`
+	ServerArriveTime int64 `json:"server_arrive_time"`
+	Delta            int64 `json:"delta"`
+}
+
+const MaxSyncClockByteNum = 400                 // sync clock byte num, max=400
+const MaxTimeStampSupport = 3155731199000000000 // unit is ns, 2069/12/31 23:59:59
+const MinTimeStampSupport = 0                   // unit is ns, 1970/1/1 8:0:0
+
+// getCurrentTimeNs returns UTC time in Ns
+func GetCurrentTimeNs() int64 {
+	var tv syscall.Timeval
+	if err := syscall.Gettimeofday(&tv); err != nil {
+		return -1
+	}
+	return tv.Sec*1000000000 + tv.Usec*1000
+}
+
+// readConnData reads data from stream
+func ReadConnData(stream net.Conn) (buf []byte, byteNum int, err error) {
+	buf = make([]byte, MaxSyncClockByteNum)
+	byteNum, err = stream.Read(buf)
+	return buf, byteNum, err
+}
+
+// writeConnData writes data to stream
+func WriteConnData(stream net.Conn, buf []byte) error {
+	_, err := stream.Write(buf)
+	return err
+}
diff --git a/sync_clock_server.go b/sync_clock_server.go
new file mode 100644
index 0000000..a8e98c6
--- /dev/null
+++ b/sync_clock_server.go
@@ -0,0 +1,94 @@
+// Copyright (c) Huawei Technologies Co., Ltd. 2018. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Description: sync clock server related function
+// Author: xueshaojia
+// Create: 2018-11-10
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net"
+	"os/exec"
+	"syscall"
+
+	"github.com/kata-containers/agent/pkg/clock"
+)
+
+// getSyncClockInfo waits for client request
+func getSyncClockInfo(stream net.Conn, clockInfo *clock.TimeValue) error {
+	buf, byteNum, err := clock.ReadConnData(stream)
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(buf[:byteNum], clockInfo)
+}
+
+// ackSyncClockInfo sends clock info to client
+func ackSyncClockInfo(stream net.Conn, clockInfo *clock.TimeValue) error {
+	b, err := json.Marshal(clockInfo)
+	if err != nil {
+		return err
+	}
+	return clock.WriteConnData(stream, b)
+}
+
+// setGuestClock sets systemtime and hwclock
+func setGuestClock(delta int64) error {
+	dstClockNs := clock.GetCurrentTimeNs() + delta
+	if dstClockNs < clock.MinTimeStampSupport || dstClockNs > clock.MaxTimeStampSupport {
+		return fmt.Errorf("sync clock failed, the valid timestamp is from 1970/1/1 8:0:0 to 2069/12/31 23:59:59")
+	}
+	dstTimeVal := syscall.NsecToTimeval(dstClockNs)
+	if err := syscall.Settimeofday(&dstTimeVal); err != nil {
+		agentLog.WithError(err).Error("sync clock, set systemtime failed")
+		return err
+	}
+	cmd := exec.Command("hwclock", "-w")
+	if err := cmd.Run(); err != nil {
+		agentLog.WithError(err).Error("sync clock, set hwclock failed")
+		return err
+	}
+	agentLog.Infof("sync clock, set systemtime and hwclock succ, delta:%d", delta)
+	return nil
+}
+
+// SyncClock is a loop that deals with client's request
+// 1 read client request
+// 2 response with clock info or set guest clock
+// 3 if error happends, print error info
+func SyncClock(stream net.Conn) {
+	for {
+		var clockInfo clock.TimeValue
+		if err := getSyncClockInfo(stream, &clockInfo); err != nil {
+			if err == io.EOF {
+				agentLog.WithError(err).Error("sync clock, yamux session error happends")
+				stream.Close()
+				break
+			}
+			agentLog.WithError(err).Error("sync clock, read sync clock info failed")
+			continue
+		}
+		// set client's request arrive time
+		clockInfo.ClientArriveTime = clock.GetCurrentTimeNs()
+		if clockInfo.Delta == 0 {
+			// this is a request for clock diff info
+			// set server's response send time
+			// if fails, wait for next time
+			clockInfo.ServerSendTime = clock.GetCurrentTimeNs()
+			if err := ackSyncClockInfo(stream, &clockInfo); err != nil {
+				agentLog.WithError(err).Error("sync clock, send back clock info failed")
+			}
+		} else {
+			// this is a request for adjusting guest clock
+			// 1 adjust guest clock(system time and hwclock)
+			// 2 do not send response any more
+			// 3 if fails, wait for next request to adjust
+			if err := setGuestClock(clockInfo.Delta); err != nil {
+				agentLog.WithError(err).Error("sync clock, set local clock failed")
+			}
+		}
+	}
+}
diff --git a/sync_clock_server_test.go b/sync_clock_server_test.go
new file mode 100644
index 0000000..41d5227
--- /dev/null
+++ b/sync_clock_server_test.go
@@ -0,0 +1,88 @@
+// Copyright (c) Huawei Technologies Co., Ltd. 2018. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+// Description: sync clock server related test
+// Author: xueshaojia
+// Create: 2018-11-10
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"math/rand"
+	"net"
+	"testing"
+
+	"github.com/hashicorp/yamux"
+	"github.com/kata-containers/agent/pkg/clock"
+)
+
+var clientStream net.Conn
+
+func TestGetCurrentTimeNs(t *testing.T) {
+	timeRet := clock.GetCurrentTimeNs()
+	if timeRet <= 0 {
+		t.Fatalf("TestGetCurrentTimeNs Fail, time:%d", timeRet)
+	}
+}
+
+func TestSetGuestClock(t *testing.T) {
+	if err := setGuestClock(0); err != nil {
+		t.Fatalf("test set clock failed, err:%v", err)
+	}
+}
+
+func TestReadWriteData(t *testing.T) {
+	sock := GenSocket()
+	var err error
+	listener, err := net.Listen("unix", sock)
+	if err != nil {
+		t.Fatalf("listening err: %v", err)
+	}
+	go SetUpClient(sock)
+	conn, err := listener.Accept()
+	if err != nil {
+		t.Fatalf("accept err: %v", err)
+	}
+	session, err := yamux.Server(conn, nil)
+	if err != nil {
+		t.Fatalf("session err: %v", err)
+	}
+	stream, err := session.Accept()
+	if err != nil {
+		t.Fatalf("stream err: %v", err)
+	}
+	go func() {
+		var clockInfo clock.TimeValue = clock.TimeValue{1000, 0, 0, 0, 0}
+		b, _ := json.Marshal(clockInfo)
+		clock.WriteConnData(clientStream, b)
+	}()
+	_, _, err = clock.ReadConnData(stream)
+	if err != nil {
+		t.Fatalf("read conn data error, err:%v", err)
+	}
+}
+
+func SetUpClient(sock string) error {
+	conn, err := net.Dial("unix", sock)
+	if err != nil {
+		return err
+	}
+	session, err := yamux.Client(conn, nil)
+	if err != nil {
+		conn.Close()
+		return err
+	}
+	stream, err := session.Open()
+	if err != nil {
+		return err
+	}
+	clientStream = stream
+	return nil
+}
+
+func GenSocket() string {
+	randSeed := clock.GetCurrentTimeNs()
+	rand.Seed(randSeed)
+	return fmt.Sprintf("/tmp/%d.sock", rand.Uint32())
+}
-- 
2.14.3 (Apple Git-98)

