Skip to content

Commit 1f4cde8

Browse files
committed
chore: better uuid generation
1 parent c59c99a commit 1f4cde8

6 files changed

Lines changed: 136 additions & 67 deletions

File tree

adapter/outbound/base.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func NewBase(opt BaseOption) *Base {
6969
iface: opt.Interface,
7070
rmark: opt.RoutingMark,
7171
prefer: opt.Prefer,
72-
id: utils.NewUUIDV6(),
72+
id: utils.NewUUIDV7(),
7373
}
7474
}
7575

common/utils/uuid.go

Lines changed: 112 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,145 @@
11
package utils
22

33
import (
4+
"crypto/md5"
5+
"crypto/sha1"
6+
"encoding/binary"
7+
"sync"
8+
"time"
9+
"unsafe"
10+
411
"github.com/gofrs/uuid/v5"
512
"github.com/metacubex/randv2"
613
)
714

8-
type unsafeRandReader struct{}
9-
10-
func (r unsafeRandReader) Read(p []byte) (n int, err error) {
11-
// modify from https://github.com/golang/go/blob/587c3847da81aa7cfc3b3db2677c8586c94df13a/src/runtime/rand.go#L70-L89
12-
// Inspired by wyrand.
13-
n = len(p)
14-
v := randv2.Uint64()
15+
func UnsafeRandRead(p []byte) {
1516
for len(p) > 0 {
16-
v ^= 0xa0761d6478bd642f
17-
v *= 0xe7037ed1a0b428db
18-
size := 8
19-
if len(p) < 8 {
20-
size = len(p)
17+
v := randv2.Uint64()
18+
if v == 0 {
19+
continue
2120
}
22-
for i := 0; i < size; i++ {
23-
p[i] ^= byte(v >> (8 * i))
24-
}
25-
p = p[size:]
26-
v = v>>32 | v<<32
21+
i := copy(p, (*[8]byte)(unsafe.Pointer(&v))[:])
22+
p = p[i:]
2723
}
24+
}
2825

29-
return
26+
type unsafeRandReader struct{}
27+
28+
func (r *unsafeRandReader) Read(p []byte) (n int, err error) {
29+
UnsafeRandRead(p)
30+
return len(p), nil
3031
}
3132

32-
var UnsafeRandReader = unsafeRandReader{}
33+
var UnsafeRandReader = (*unsafeRandReader)(nil)
3334

34-
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(UnsafeRandReader))
35+
// NewUUIDV3 returns a UUID based on the MD5 hash of the namespace UUID and name.
36+
func NewUUIDV3(ns uuid.UUID, name string) (u uuid.UUID) {
37+
h := md5.New()
38+
h.Write(ns[:])
39+
h.Write([]byte(name))
40+
copy(u[:], h.Sum(make([]byte, 0, md5.Size)))
3541

36-
func NewUUIDV1() uuid.UUID {
37-
u, _ := UnsafeUUIDGenerator.NewV1() // unsafeRandReader wouldn't cause error, so ignore err is safe
42+
u.SetVersion(uuid.V3)
43+
u.SetVariant(uuid.VariantRFC9562)
3844
return u
3945
}
4046

41-
func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
42-
return UnsafeUUIDGenerator.NewV3(ns, name)
43-
}
44-
45-
func NewUUIDV4() uuid.UUID {
46-
u, _ := UnsafeUUIDGenerator.NewV4() // unsafeRandReader wouldn't cause error, so ignore err is safe
47+
// NewUUIDV4 returns a new version 4 UUID.
48+
//
49+
// Version 4 UUIDs contain 122 bits of random data.
50+
func NewUUIDV4() (u uuid.UUID) {
51+
UnsafeRandRead(u[:])
52+
u.SetVersion(uuid.V4)
53+
u.SetVariant(uuid.VariantRFC9562)
4754
return u
4855
}
4956

50-
func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
51-
return UnsafeUUIDGenerator.NewV5(ns, name)
52-
}
57+
// NewUUIDV5 returns a UUID based on SHA-1 hash of the namespace UUID and name.
58+
func NewUUIDV5(ns uuid.UUID, name string) (u uuid.UUID) {
59+
h := sha1.New()
60+
h.Write(ns[:])
61+
h.Write([]byte(name))
62+
copy(u[:], h.Sum(make([]byte, 0, sha1.Size)))
5363

54-
func NewUUIDV6() uuid.UUID {
55-
u, _ := UnsafeUUIDGenerator.NewV6() // unsafeRandReader wouldn't cause error, so ignore err is safe
64+
u.SetVersion(uuid.V5)
65+
u.SetVariant(uuid.VariantRFC9562)
5666
return u
5767
}
5868

59-
func NewUUIDV7() uuid.UUID {
60-
u, _ := UnsafeUUIDGenerator.NewV7() // unsafeRandReader wouldn't cause error, so ignore err is safe
69+
var (
70+
v7mu sync.Mutex
71+
v7lastSecs uint64
72+
v7lastTimestamp uint64
73+
)
74+
75+
// NewUUIDV7 returns a new version 7 UUID.
76+
//
77+
// Version 7 UUIDs contain a timestamp in the most significant 48 bits,
78+
// and at least 62 bits of random data.
79+
//
80+
// NewUUIDV7 always returns UUIDs which sort in increasing order,
81+
// except when the system clock moves backwards.
82+
func NewUUIDV7() (u uuid.UUID) {
83+
// UUIDv7 is defined in RFC 9562 section 5.7 as:
84+
//
85+
// 0 1 2 3
86+
// 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
87+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
88+
// | unix_ts_ms |
89+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
90+
// | unix_ts_ms | ver | rand_a |
91+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
92+
// |var| rand_b |
93+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
94+
// | rand_b |
95+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
96+
//
97+
// We store a 12 bit sub-millisecond timestamp fraction in the rand_a section,
98+
// as optionally permitted by the RFC.
99+
v7mu.Lock()
100+
101+
// Generate our 60-bit timestamp: 48 bits of millisecond-resolution,
102+
// followed by 12 bits of 1/4096-millisecond resolution.
103+
now := time.Now()
104+
secs := uint64(now.Unix())
105+
nanos := uint64(now.Nanosecond())
106+
msecs := nanos / 1000000
107+
frac := nanos - (1000000 * msecs)
108+
timestamp := (1000*secs + msecs) << 12 // ms shifted into position
109+
timestamp += (frac * 4096) / 1000000 // ns converted to 1/4096-ms units
110+
111+
if v7lastSecs > secs {
112+
// Time has gone backwards.
113+
// This presumably indicates the system clock has changed.
114+
// Ignore previously-generated UUIDs.
115+
} else if timestamp <= v7lastTimestamp {
116+
// This timestamp is the same as a previously-generated UUID.
117+
// To preserve the property that we generate UUIDs in order,
118+
// use a timestamp 1/4096 millisecond later than the most recently
119+
// generated UUID.
120+
timestamp = v7lastTimestamp + 1
121+
}
122+
123+
v7lastSecs = secs
124+
v7lastTimestamp = timestamp
125+
v7mu.Unlock()
126+
127+
// Insert a gap for the 4 bits of the ver field into the timestamp.
128+
hibits := ((timestamp << 4) & 0xffff_ffff_ffff_0000) | (timestamp & 0x0ffff)
129+
130+
binary.BigEndian.PutUint64(u[0:8], hibits)
131+
UnsafeRandRead(u[8:])
132+
133+
u.SetVersion(uuid.V7)
134+
u.SetVariant(uuid.VariantRFC9562)
61135
return u
62136
}
63137

64138
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
65-
func UUIDMap(str string) (uuid.UUID, error) {
139+
func UUIDMap(str string) uuid.UUID {
66140
u, err := uuid.FromString(str)
67141
if err != nil {
68-
return NewUUIDV5(uuid.Nil, str), nil
142+
return NewUUIDV5(uuid.Nil, str)
69143
}
70-
return u, nil
144+
return u
71145
}

common/utils/uuid_test.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package utils
22

33
import (
4-
"github.com/gofrs/uuid/v5"
4+
"bytes"
55
"reflect"
66
"testing"
7+
8+
"github.com/gofrs/uuid/v5"
79
)
810

11+
func TestUnsafeRandRead(t *testing.T) {
12+
for i := 1; i < 100; i++ {
13+
data := make([]byte, i)
14+
UnsafeRandRead(data)
15+
if bytes.Equal(data, make([]byte, i)) {
16+
t.Fatal("UnsafeRandRead should not return all zero bytes")
17+
}
18+
}
19+
}
20+
921
func TestUUIDMap(t *testing.T) {
1022
type args struct {
1123
str string
@@ -22,50 +34,41 @@ func TestUUIDMap(t *testing.T) {
2234
args: args{
2335
str: "82410302-039e-41b6-98b0-d964084b4170",
2436
},
25-
want: uuid.FromStringOrNil("82410302-039e-41b6-98b0-d964084b4170"),
26-
wantErr: false,
37+
want: uuid.FromStringOrNil("82410302-039e-41b6-98b0-d964084b4170"),
2738
},
2839
{
2940
name: "uuid-test-2",
3041
args: args{
3142
str: "88c502e6-d7eb-4c8e-8259-94cb13d83c77",
3243
},
33-
want: uuid.FromStringOrNil("88c502e6-d7eb-4c8e-8259-94cb13d83c77"),
34-
wantErr: false,
44+
want: uuid.FromStringOrNil("88c502e6-d7eb-4c8e-8259-94cb13d83c77"),
3545
},
3646
{
3747
name: "uuid-map-1",
3848
args: args{
3949
str: "123456",
4050
},
41-
want: uuid.FromStringOrNil("f8598425-92f2-5508-a071-4fc67f9040ac"),
42-
wantErr: false,
51+
want: uuid.FromStringOrNil("f8598425-92f2-5508-a071-4fc67f9040ac"),
4352
},
4453
// GENERATED BY 'xray uuid -i'
4554
{
4655
name: "uuid-map-2",
4756
args: args{
4857
str: "a9dk23bz0",
4958
},
50-
want: uuid.FromStringOrNil("c91481b6-fc0f-5d9e-b166-5ddf07b9c3c5"),
51-
wantErr: false,
59+
want: uuid.FromStringOrNil("c91481b6-fc0f-5d9e-b166-5ddf07b9c3c5"),
5260
},
5361
{
5462
name: "uuid-map-2",
5563
args: args{
5664
str: "中文123",
5765
},
58-
want: uuid.FromStringOrNil("145c544c-2229-59e5-8dbb-3f33b7610d26"),
59-
wantErr: false,
66+
want: uuid.FromStringOrNil("145c544c-2229-59e5-8dbb-3f33b7610d26"),
6067
},
6168
}
6269
for _, tt := range tests {
6370
t.Run(tt.name, func(t *testing.T) {
64-
got, err := UUIDMap(tt.args.str)
65-
if (err != nil) != tt.wantErr {
66-
t.Errorf("UUIDMap() error = %v, wantErr %v", err, tt.wantErr)
67-
return
68-
}
71+
got := UUIDMap(tt.args.str)
6972
if !reflect.DeepEqual(got, tt.want) {
7073
t.Errorf("UUIDMap() got = %v, want %v", got, tt.want)
7174
}

listener/sing_vless/service.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io"
99
"net"
1010

11+
"github.com/metacubex/mihomo/common/utils"
1112
"github.com/metacubex/mihomo/transport/vless"
1213
"github.com/metacubex/mihomo/transport/vless/vision"
1314

@@ -44,10 +45,7 @@ func (s *Service[T]) UpdateUsers(userList []T, userUUIDList []string, userFlowLi
4445
userMap := make(map[[16]byte]T)
4546
userFlowMap := make(map[T]string)
4647
for i, userName := range userList {
47-
userID, err := uuid.FromString(userUUIDList[i])
48-
if err != nil {
49-
userID = uuid.NewV5(uuid.Nil, userUUIDList[i])
50-
}
48+
userID := utils.UUIDMap(userUUIDList[i])
5149
userMap[userID] = userName
5250
userFlowMap[userName] = userFlowList[i]
5351
}

transport/vless/vless.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,7 @@ func (c *Client) PacketConn(conn net.Conn, rAddr net.Addr) net.PacketConn {
5757

5858
// NewClient return Client instance
5959
func NewClient(uuidStr string, addons *Addons) (*Client, error) {
60-
uid, err := utils.UUIDMap(uuidStr)
61-
if err != nil {
62-
return nil, err
63-
}
60+
uid := utils.UUIDMap(uuidStr)
6461

6562
return &Client{
6663
uuid: uid,

transport/vmess/vmess.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,7 @@ func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
8484

8585
// NewClient return Client instance
8686
func NewClient(config Config) (*Client, error) {
87-
uid, err := utils.UUIDMap(config.UUID)
88-
if err != nil {
89-
return nil, err
90-
}
87+
uid := utils.UUIDMap(config.UUID)
9188

9289
var security Security
9390
switch config.Security {

0 commit comments

Comments
 (0)