diff --git a/proxy/vmess/aead/authid.go b/proxy/vmess/aead/authid.go index 2ec48f11..5897f3b5 100644 --- a/proxy/vmess/aead/authid.go +++ b/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 ( diff --git a/proxy/vmess/aead/authid_test.go b/proxy/vmess/aead/authid_test.go index 837d3372..0037d7a0 100644 --- a/proxy/vmess/aead/authid_test.go +++ b/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) { diff --git a/proxy/vmess/aead/encrypt.go b/proxy/vmess/aead/encrypt.go index a3faec7e..5a3d6f59 100644 --- a/proxy/vmess/aead/encrypt.go +++ b/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 { diff --git a/proxy/vmess/outbound/command.go b/proxy/vmess/outbound/command.go index 2d4747dc..6524666d 100644 --- a/proxy/vmess/outbound/command.go +++ b/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)) } diff --git a/proxy/vmess/time/time.go b/proxy/vmess/time/time.go new file mode 100644 index 00000000..64beeefe --- /dev/null +++ b/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) +}