风扇滑翔翼 4 месяцев назад
Родитель
Сommit
30e3fa690e

+ 1 - 1
proxy/vmess/aead/authid.go

@@ -10,10 +10,10 @@ import (
 	"hash/crc32"
 	"io"
 	"math"
-	"time"
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/antireplay"
+	"github.com/xtls/xray-core/proxy/vmess/time"
 )
 
 var (

+ 1 - 1
proxy/vmess/aead/authid_test.go

@@ -4,9 +4,9 @@ import (
 	"fmt"
 	"strconv"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/xtls/xray-core/proxy/vmess/time"
 )
 
 func TestCreateAuthID(t *testing.T) {

+ 1 - 1
proxy/vmess/aead/encrypt.go

@@ -5,10 +5,10 @@ import (
 	"crypto/rand"
 	"encoding/binary"
 	"io"
-	"time"
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/crypto"
+	"github.com/xtls/xray-core/proxy/vmess/time"
 )
 
 func SealVMessAEADHeader(key [16]byte, data []byte) []byte {

+ 2 - 1
proxy/vmess/outbound/command.go

@@ -7,6 +7,7 @@ import (
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/proxy/vmess"
+	xtime "github.com/xtls/xray-core/proxy/vmess/time"
 )
 
 func (h *Handler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) {
@@ -25,7 +26,7 @@ func (h *Handler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) {
 		Account: account,
 	}
 	dest := net.TCPDestination(cmd.Host, cmd.Port)
-	until := time.Now().Add(time.Duration(cmd.ValidMin) * time.Minute)
+	until := xtime.Now().Add(time.Duration(cmd.ValidMin) * time.Minute)
 	h.serverList.AddServer(protocol.NewServerSpec(dest, protocol.BeforeTime(until), user))
 }
 

+ 81 - 0
proxy/vmess/time/time.go

@@ -0,0 +1,81 @@
+package time
+
+import (
+	"context"
+	"net/http"
+	"sync/atomic"
+	"time"
+
+	"github.com/xtls/xray-core/common/errors"
+	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/common/platform"
+	"github.com/xtls/xray-core/transport/internet"
+)
+
+var timeOffset atomic.Pointer[time.Duration]
+
+func init() {
+	timeOffset.Store(new(time.Duration))
+	domain := platform.NewEnvFlag("xray.vmess.time.domain").GetValue(func() string { return "https://apple.com" })
+	if domain == "" {
+		errors.LogError(context.Background(), "vmess time domain is empty, skip time sync")
+		return
+	}
+	err := updateTime(domain)
+	if err != nil {
+		errors.LogError(context.Background(), err)
+	}
+	errors.LogWarning(context.Background(), "Initial time offset for vmess:", timeOffset.Load())
+	// only one sync should be enough, so disable periodic update for now
+	//go updateTimeMonitor(context.TODO(), domain)
+}
+
+func updateTimeMonitor(ctx context.Context, domain string) {
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-time.After(10 * time.Minute):
+			err := updateTime(domain)
+			if err != nil {
+				errors.LogError(ctx, err)
+			}
+		}
+	}
+}
+
+func updateTime(domain string) error {
+	httpClient := &http.Client{
+		Transport: &http.Transport{
+			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+				dest, err := net.ParseDestination(network + ":" + addr)
+				if err != nil {
+					return nil, err
+				}
+				return internet.DialSystem(ctx, dest, nil)
+			},
+		},
+	}
+	resp, err := httpClient.Get(domain)
+	if err != nil {
+		return errors.New("Failed to access monitor domain").Base(err)
+	}
+	timeHeader := resp.Header.Get("Date")
+	remoteTime, err := time.Parse(time.RFC1123, timeHeader)
+	if err != nil {
+		return errors.New("Failed to parse time from monitor domain").Base(err)
+	}
+	localTime := time.Now()
+	offset := remoteTime.Sub(localTime)
+	if offset < 2*time.Second && offset > -2*time.Second {
+		errors.LogWarning(context.Background(), "Time offset too small, ignoring:", offset)
+		return nil
+	}
+	timeOffset.Store(&offset)
+	return nil
+}
+
+func Now() time.Time {
+	offset := timeOffset.Load()
+	return time.Now().Add(*offset)
+}