chore: better uuid generation

This commit is contained in:
wwqgtxx
2026-04-22 11:10:43 +08:00
parent c59c99a051
commit 1f4cde8588
6 changed files with 147 additions and 78 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ func NewBase(opt BaseOption) *Base {
iface: opt.Interface,
rmark: opt.RoutingMark,
prefer: opt.Prefer,
id: utils.NewUUIDV6(),
id: utils.NewUUIDV7(),
}
}
+123 -49
View File
@@ -1,71 +1,145 @@
package utils
import (
"crypto/md5"
"crypto/sha1"
"encoding/binary"
"sync"
"time"
"unsafe"
"github.com/gofrs/uuid/v5"
"github.com/metacubex/randv2"
)
func UnsafeRandRead(p []byte) {
for len(p) > 0 {
v := randv2.Uint64()
if v == 0 {
continue
}
i := copy(p, (*[8]byte)(unsafe.Pointer(&v))[:])
p = p[i:]
}
}
type unsafeRandReader struct{}
func (r unsafeRandReader) Read(p []byte) (n int, err error) {
// modify from https://github.com/golang/go/blob/587c3847da81aa7cfc3b3db2677c8586c94df13a/src/runtime/rand.go#L70-L89
// Inspired by wyrand.
n = len(p)
v := randv2.Uint64()
for len(p) > 0 {
v ^= 0xa0761d6478bd642f
v *= 0xe7037ed1a0b428db
size := 8
if len(p) < 8 {
size = len(p)
}
for i := 0; i < size; i++ {
p[i] ^= byte(v >> (8 * i))
}
p = p[size:]
v = v>>32 | v<<32
func (r *unsafeRandReader) Read(p []byte) (n int, err error) {
UnsafeRandRead(p)
return len(p), nil
}
var UnsafeRandReader = (*unsafeRandReader)(nil)
// NewUUIDV3 returns a UUID based on the MD5 hash of the namespace UUID and name.
func NewUUIDV3(ns uuid.UUID, name string) (u uuid.UUID) {
h := md5.New()
h.Write(ns[:])
h.Write([]byte(name))
copy(u[:], h.Sum(make([]byte, 0, md5.Size)))
u.SetVersion(uuid.V3)
u.SetVariant(uuid.VariantRFC9562)
return u
}
// NewUUIDV4 returns a new version 4 UUID.
//
// Version 4 UUIDs contain 122 bits of random data.
func NewUUIDV4() (u uuid.UUID) {
UnsafeRandRead(u[:])
u.SetVersion(uuid.V4)
u.SetVariant(uuid.VariantRFC9562)
return u
}
// NewUUIDV5 returns a UUID based on SHA-1 hash of the namespace UUID and name.
func NewUUIDV5(ns uuid.UUID, name string) (u uuid.UUID) {
h := sha1.New()
h.Write(ns[:])
h.Write([]byte(name))
copy(u[:], h.Sum(make([]byte, 0, sha1.Size)))
u.SetVersion(uuid.V5)
u.SetVariant(uuid.VariantRFC9562)
return u
}
var (
v7mu sync.Mutex
v7lastSecs uint64
v7lastTimestamp uint64
)
// NewUUIDV7 returns a new version 7 UUID.
//
// Version 7 UUIDs contain a timestamp in the most significant 48 bits,
// and at least 62 bits of random data.
//
// NewUUIDV7 always returns UUIDs which sort in increasing order,
// except when the system clock moves backwards.
func NewUUIDV7() (u uuid.UUID) {
// UUIDv7 is defined in RFC 9562 section 5.7 as:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | unix_ts_ms |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | unix_ts_ms | ver | rand_a |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |var| rand_b |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | rand_b |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// We store a 12 bit sub-millisecond timestamp fraction in the rand_a section,
// as optionally permitted by the RFC.
v7mu.Lock()
// Generate our 60-bit timestamp: 48 bits of millisecond-resolution,
// followed by 12 bits of 1/4096-millisecond resolution.
now := time.Now()
secs := uint64(now.Unix())
nanos := uint64(now.Nanosecond())
msecs := nanos / 1000000
frac := nanos - (1000000 * msecs)
timestamp := (1000*secs + msecs) << 12 // ms shifted into position
timestamp += (frac * 4096) / 1000000 // ns converted to 1/4096-ms units
if v7lastSecs > secs {
// Time has gone backwards.
// This presumably indicates the system clock has changed.
// Ignore previously-generated UUIDs.
} else if timestamp <= v7lastTimestamp {
// This timestamp is the same as a previously-generated UUID.
// To preserve the property that we generate UUIDs in order,
// use a timestamp 1/4096 millisecond later than the most recently
// generated UUID.
timestamp = v7lastTimestamp + 1
}
return
}
v7lastSecs = secs
v7lastTimestamp = timestamp
v7mu.Unlock()
var UnsafeRandReader = unsafeRandReader{}
// Insert a gap for the 4 bits of the ver field into the timestamp.
hibits := ((timestamp << 4) & 0xffff_ffff_ffff_0000) | (timestamp & 0x0ffff)
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(UnsafeRandReader))
binary.BigEndian.PutUint64(u[0:8], hibits)
UnsafeRandRead(u[8:])
func NewUUIDV1() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV1() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
return UnsafeUUIDGenerator.NewV3(ns, name)
}
func NewUUIDV4() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV4() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
return UnsafeUUIDGenerator.NewV5(ns, name)
}
func NewUUIDV6() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV6() // unsafeRandReader wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV7() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV7() // unsafeRandReader wouldn't cause error, so ignore err is safe
u.SetVersion(uuid.V7)
u.SetVariant(uuid.VariantRFC9562)
return u
}
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
func UUIDMap(str string) (uuid.UUID, error) {
func UUIDMap(str string) uuid.UUID {
u, err := uuid.FromString(str)
if err != nil {
return NewUUIDV5(uuid.Nil, str), nil
return NewUUIDV5(uuid.Nil, str)
}
return u, nil
return u
}
+19 -16
View File
@@ -1,11 +1,23 @@
package utils
import (
"github.com/gofrs/uuid/v5"
"bytes"
"reflect"
"testing"
"github.com/gofrs/uuid/v5"
)
func TestUnsafeRandRead(t *testing.T) {
for i := 1; i < 100; i++ {
data := make([]byte, i)
UnsafeRandRead(data)
if bytes.Equal(data, make([]byte, i)) {
t.Fatal("UnsafeRandRead should not return all zero bytes")
}
}
}
func TestUUIDMap(t *testing.T) {
type args struct {
str string
@@ -22,24 +34,21 @@ func TestUUIDMap(t *testing.T) {
args: args{
str: "82410302-039e-41b6-98b0-d964084b4170",
},
want: uuid.FromStringOrNil("82410302-039e-41b6-98b0-d964084b4170"),
wantErr: false,
want: uuid.FromStringOrNil("82410302-039e-41b6-98b0-d964084b4170"),
},
{
name: "uuid-test-2",
args: args{
str: "88c502e6-d7eb-4c8e-8259-94cb13d83c77",
},
want: uuid.FromStringOrNil("88c502e6-d7eb-4c8e-8259-94cb13d83c77"),
wantErr: false,
want: uuid.FromStringOrNil("88c502e6-d7eb-4c8e-8259-94cb13d83c77"),
},
{
name: "uuid-map-1",
args: args{
str: "123456",
},
want: uuid.FromStringOrNil("f8598425-92f2-5508-a071-4fc67f9040ac"),
wantErr: false,
want: uuid.FromStringOrNil("f8598425-92f2-5508-a071-4fc67f9040ac"),
},
// GENERATED BY 'xray uuid -i'
{
@@ -47,25 +56,19 @@ func TestUUIDMap(t *testing.T) {
args: args{
str: "a9dk23bz0",
},
want: uuid.FromStringOrNil("c91481b6-fc0f-5d9e-b166-5ddf07b9c3c5"),
wantErr: false,
want: uuid.FromStringOrNil("c91481b6-fc0f-5d9e-b166-5ddf07b9c3c5"),
},
{
name: "uuid-map-2",
args: args{
str: "中文123",
},
want: uuid.FromStringOrNil("145c544c-2229-59e5-8dbb-3f33b7610d26"),
wantErr: false,
want: uuid.FromStringOrNil("145c544c-2229-59e5-8dbb-3f33b7610d26"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := UUIDMap(tt.args.str)
if (err != nil) != tt.wantErr {
t.Errorf("UUIDMap() error = %v, wantErr %v", err, tt.wantErr)
return
}
got := UUIDMap(tt.args.str)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("UUIDMap() got = %v, want %v", got, tt.want)
}
+2 -4
View File
@@ -8,6 +8,7 @@ import (
"io"
"net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/transport/vless"
"github.com/metacubex/mihomo/transport/vless/vision"
@@ -44,10 +45,7 @@ func (s *Service[T]) UpdateUsers(userList []T, userUUIDList []string, userFlowLi
userMap := make(map[[16]byte]T)
userFlowMap := make(map[T]string)
for i, userName := range userList {
userID, err := uuid.FromString(userUUIDList[i])
if err != nil {
userID = uuid.NewV5(uuid.Nil, userUUIDList[i])
}
userID := utils.UUIDMap(userUUIDList[i])
userMap[userID] = userName
userFlowMap[userName] = userFlowList[i]
}
+1 -4
View File
@@ -57,10 +57,7 @@ func (c *Client) PacketConn(conn net.Conn, rAddr net.Addr) net.PacketConn {
// NewClient return Client instance
func NewClient(uuidStr string, addons *Addons) (*Client, error) {
uid, err := utils.UUIDMap(uuidStr)
if err != nil {
return nil, err
}
uid := utils.UUIDMap(uuidStr)
return &Client{
uuid: uid,
+1 -4
View File
@@ -84,10 +84,7 @@ func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
// NewClient return Client instance
func NewClient(config Config) (*Client, error) {
uid, err := utils.UUIDMap(config.UUID)
if err != nil {
return nil, err
}
uid := utils.UUIDMap(config.UUID)
var security Security
switch config.Security {