diff --git a/hash/id.go b/hash/id.go new file mode 100644 index 000000000..d1538889b --- /dev/null +++ b/hash/id.go @@ -0,0 +1,51 @@ +package hash + +import ( + "crypto/hmac" + "crypto/md5" +) + +type CounterHash interface { + Hash(key []byte, counter int64) []byte +} + +type StringHash interface { + Hash(key []byte, data []byte) []byte +} + +type TimeHash struct { + baseHash StringHash +} + +func NewTimeHash(baseHash StringHash) CounterHash { + return TimeHash{ + baseHash: baseHash, + } +} + +func (h TimeHash) Hash(key []byte, counter int64) []byte { + counterBytes := int64ToBytes(counter) + return h.baseHash.Hash(key, counterBytes) +} + +type HMACHash struct { +} + +func (h HMACHash) Hash(key []byte, data []byte) []byte { + hash := hmac.New(md5.New, key) + hash.Write(data) + return hash.Sum(nil) +} + +func int64ToBytes(value int64) []byte { + return []byte{ + byte(value >> 56), + byte(value >> 48), + byte(value >> 40), + byte(value >> 32), + byte(value >> 24), + byte(value >> 16), + byte(value >> 8), + byte(value), + } +} diff --git a/hash/int.go b/hash/int.go new file mode 100644 index 000000000..431650350 --- /dev/null +++ b/hash/int.go @@ -0,0 +1,15 @@ +package hash + +import ( + "crypto/md5" +) + +func Int64Hash(value int64) []byte { + md5hash := md5.New() + buffer := int64ToBytes(value) + md5hash.Write(buffer) + md5hash.Write(buffer) + md5hash.Write(buffer) + md5hash.Write(buffer) + return md5hash.Sum(nil) +} diff --git a/id.go b/id.go index d00b3395a..ec178435b 100644 --- a/id.go +++ b/id.go @@ -1,11 +1,8 @@ package core import ( - "crypto/hmac" "crypto/md5" "encoding/hex" - mrand "math/rand" - "time" "github.com/v2ray/v2ray-core/log" ) @@ -32,60 +29,17 @@ func NewID(id string) (ID, error) { md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) cmdKey := md5.Sum(nil) - return ID{id, idBytes, cmdKey[:]}, nil -} - -func (v ID) TimeRangeHash(rangeSec int) ([]byte, int64) { - nowSec := time.Now().UTC().Unix() - delta := mrand.Intn(rangeSec*2) - rangeSec - - targetSec := nowSec + int64(delta) - return v.TimeHash(targetSec), targetSec -} - -func (v ID) TimeHash(timeSec int64) []byte { - buffer := []byte{ - byte(timeSec >> 56), - byte(timeSec >> 48), - byte(timeSec >> 40), - byte(timeSec >> 32), - byte(timeSec >> 24), - byte(timeSec >> 16), - byte(timeSec >> 8), - byte(timeSec), - } - return v.Hash(buffer) -} - -func (v ID) Hash(data []byte) []byte { - hasher := hmac.New(md5.New, v.Bytes) - hasher.Write(data) - return hasher.Sum(nil) + return ID{ + String: id, + Bytes: idBytes, + cmdKey: cmdKey[:], + }, nil } func (v ID) CmdKey() []byte { return v.cmdKey } -func TimestampHash(timeSec int64) []byte { - md5hash := md5.New() - buffer := []byte{ - byte(timeSec >> 56), - byte(timeSec >> 48), - byte(timeSec >> 40), - byte(timeSec >> 32), - byte(timeSec >> 24), - byte(timeSec >> 16), - byte(timeSec >> 8), - byte(timeSec), - } - md5hash.Write(buffer) - md5hash.Write(buffer) - md5hash.Write(buffer) - md5hash.Write(buffer) - return md5hash.Sum(nil) -} - var byteGroups = []int{8, 4, 4, 4, 12} // TODO: leverage a full functional UUID library diff --git a/io/socks/socks_test.go b/io/socks/socks_test.go index d9b5538e7..623671e23 100644 --- a/io/socks/socks_test.go +++ b/io/socks/socks_test.go @@ -43,7 +43,7 @@ func TestAuthenticationResponseWrite(t *testing.T) { response := NewAuthenticationResponse(byte(0x05)) buffer := bytes.NewBuffer(make([]byte, 0, 10)) - WriteAuthentication(buffer, &response) + WriteAuthentication(buffer, response) assert.Bytes(buffer.Bytes()).Equals([]byte{socksVersion, byte(0x05)}) } diff --git a/io/socks/udp.go b/io/socks/udp.go new file mode 100644 index 000000000..9180c5df2 --- /dev/null +++ b/io/socks/udp.go @@ -0,0 +1,19 @@ +package socks + +import ( + "io" + + v2net "github.com/v2ray/v2ray-core/net" +) + +type Socks5UDPRequest struct { + fragment byte + address v2net.Address + data []byte +} + +func ReadUDPRequest(reader io.Reader) (request Socks5UDPRequest, err error) { + //buf := make([]byte, 4 * 1024) // Regular UDP packet size is 1500 bytes. + + return +} diff --git a/io/vmess/vmess.go b/io/vmess/vmess.go index f673420f6..d319a4ef6 100644 --- a/io/vmess/vmess.go +++ b/io/vmess/vmess.go @@ -10,10 +10,13 @@ import ( "fmt" "io" mrand "math/rand" + "time" "github.com/v2ray/v2ray-core" + v2hash "github.com/v2ray/v2ray-core/hash" v2io "github.com/v2ray/v2ray-core/io" "github.com/v2ray/v2ray-core/log" + v2math "github.com/v2ray/v2ray-core/math" v2net "github.com/v2ray/v2ray-core/net" ) @@ -57,7 +60,6 @@ func NewVMessRequestReader(vUserSet core.UserSet) *VMessRequestReader { } func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) { - buffer := make([]byte, 256) nBytes, err := reader.Read(buffer[:core.IDBytesLen]) @@ -76,7 +78,7 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) { if err != nil { return nil, err } - aesStream := cipher.NewCFBDecrypter(aesCipher, core.TimestampHash(timeSec)) + aesStream := cipher.NewCFBDecrypter(aesCipher, v2hash.Int64Hash(timeSec)) decryptor := v2io.NewCryptionReader(aesStream, reader) if err != nil { @@ -180,18 +182,25 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) { } type VMessRequestWriter struct { + idHash v2hash.CounterHash + randomRangeInt64 v2math.RandomInt64InRange } -func NewVMessRequestWriter() *VMessRequestWriter { - return &VMessRequestWriter{} +func NewVMessRequestWriter(idHash v2hash.CounterHash, randomRangeInt64 v2math.RandomInt64InRange) *VMessRequestWriter { + return &VMessRequestWriter{ + idHash: idHash, + randomRangeInt64: randomRangeInt64, + } } func (w *VMessRequestWriter) Write(writer io.Writer, request *VMessRequest) error { buffer := make([]byte, 0, 300) - userHash, timeSec := request.UserId.TimeRangeHash(30) - log.Debug("Writing userhash: %v", userHash) - buffer = append(buffer, userHash...) + counter := w.randomRangeInt64(time.Now().UTC().Unix(), 30) + idHash := w.idHash.Hash(request.UserId.Bytes, counter) + + log.Debug("Writing userhash: %v", idHash) + buffer = append(buffer, idHash...) encryptionBegin := len(buffer) @@ -241,7 +250,7 @@ func (w *VMessRequestWriter) Write(writer io.Writer, request *VMessRequest) erro if err != nil { return err } - aesStream := cipher.NewCFBEncrypter(aesCipher, core.TimestampHash(timeSec)) + aesStream := cipher.NewCFBEncrypter(aesCipher, v2hash.Int64Hash(counter)) cWriter := v2io.NewCryptionWriter(aesStream, writer) _, err = writer.Write(buffer[0:encryptionBegin]) diff --git a/io/vmess/vmess_test.go b/io/vmess/vmess_test.go index 85a3424bd..29dab145f 100644 --- a/io/vmess/vmess_test.go +++ b/io/vmess/vmess_test.go @@ -7,13 +7,14 @@ import ( "testing" "github.com/v2ray/v2ray-core" + v2hash "github.com/v2ray/v2ray-core/hash" + v2math "github.com/v2ray/v2ray-core/math" v2net "github.com/v2ray/v2ray-core/net" "github.com/v2ray/v2ray-core/testing/mocks" "github.com/v2ray/v2ray-core/testing/unit" ) func TestVMessSerialization(t *testing.T) { - t.Skip() assert := unit.Assert(t) userId, err := core.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51") @@ -21,7 +22,7 @@ func TestVMessSerialization(t *testing.T) { t.Fatal(err) } - userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int)} + userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int), make(map[string]int64)} userSet.AddUser(core.User{userId}) request := new(VMessRequest) @@ -47,13 +48,15 @@ func TestVMessSerialization(t *testing.T) { request.Address = v2net.DomainAddress("v2ray.com", 80) buffer := bytes.NewBuffer(make([]byte, 0, 300)) - requestWriter := NewVMessRequestWriter() + mockTime := int64(1823730) + requestWriter := NewVMessRequestWriter(v2hash.NewTimeHash(v2hash.HMACHash{}), func(base int64, delta int) int64 { return mockTime }) err = requestWriter.Write(buffer, request) if err != nil { t.Fatal(err) } userSet.UserHashes[string(buffer.Bytes()[:16])] = 0 + userSet.Timestamps[string(buffer.Bytes()[:16])] = mockTime requestReader := NewVMessRequestReader(&userSet) actualRequest, err := requestReader.Read(buffer) @@ -72,7 +75,7 @@ func TestVMessSerialization(t *testing.T) { func BenchmarkVMessRequestWriting(b *testing.B) { userId, _ := core.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51") - userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int)} + userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int), make(map[string]int64)} userSet.AddUser(core.User{userId}) request := new(VMessRequest) @@ -86,7 +89,7 @@ func BenchmarkVMessRequestWriting(b *testing.B) { request.Command = byte(0x01) request.Address = v2net.DomainAddress("v2ray.com", 80) - requestWriter := NewVMessRequestWriter() + requestWriter := NewVMessRequestWriter(v2hash.NewTimeHash(v2hash.HMACHash{}), v2math.GenerateRandomInt64InRange) for i := 0; i < b.N; i++ { requestWriter.Write(ioutil.Discard, request) } diff --git a/math/rand.go b/math/rand.go new file mode 100644 index 000000000..09bc122d0 --- /dev/null +++ b/math/rand.go @@ -0,0 +1,12 @@ +package math + +import ( + "math/rand" +) + +type RandomInt64InRange func(base int64, delta int) int64 + +func GenerateRandomInt64InRange(base int64, delta int) int64 { + rangeInDelta := rand.Intn(delta*2) - delta + return base + int64(rangeInDelta) +} diff --git a/net/vmess/vmessout.go b/net/vmess/vmessout.go index bf736347a..482953f2a 100644 --- a/net/vmess/vmessout.go +++ b/net/vmess/vmessout.go @@ -7,9 +7,11 @@ import ( "net" "github.com/v2ray/v2ray-core" + v2hash "github.com/v2ray/v2ray-core/hash" v2io "github.com/v2ray/v2ray-core/io" vmessio "github.com/v2ray/v2ray-core/io/vmess" "github.com/v2ray/v2ray-core/log" + v2math "github.com/v2ray/v2ray-core/math" v2net "github.com/v2ray/v2ray-core/net" ) @@ -94,7 +96,7 @@ func startCommunicate(request *vmessio.VMessRequest, dest v2net.Address, ray cor func handleRequest(conn *net.TCPConn, request *vmessio.VMessRequest, input <-chan []byte, finish chan<- bool) error { defer close(finish) - requestWriter := vmessio.NewVMessRequestWriter() + requestWriter := vmessio.NewVMessRequestWriter(v2hash.NewTimeHash(v2hash.HMACHash{}), v2math.GenerateRandomInt64InRange) err := requestWriter.Write(conn, request) if err != nil { log.Error("Failed to write VMess request: %v", err) diff --git a/testing/mocks/mockuserset.go b/testing/mocks/mockuserset.go index 403ed0116..238c99c6a 100644 --- a/testing/mocks/mockuserset.go +++ b/testing/mocks/mockuserset.go @@ -7,6 +7,7 @@ import ( type MockUserSet struct { UserIds []core.ID UserHashes map[string]int + Timestamps map[string]int64 } func (us *MockUserSet) AddUser(user core.User) error { @@ -17,7 +18,7 @@ func (us *MockUserSet) AddUser(user core.User) error { func (us *MockUserSet) GetUser(userhash []byte) (*core.ID, int64, bool) { idx, found := us.UserHashes[string(userhash)] if found { - return &us.UserIds[idx], 1234, true + return &us.UserIds[idx], us.Timestamps[string(userhash)], true } return nil, 0, false } diff --git a/userset.go b/userset.go index f16e24b33..fc55e5c84 100644 --- a/userset.go +++ b/userset.go @@ -2,6 +2,8 @@ package core import ( "time" + + v2hash "github.com/v2ray/v2ray-core/hash" ) const ( @@ -44,6 +46,7 @@ func (us *TimedUserSet) updateUserHash(tick <-chan time.Time) { hash2Remove := make(chan hashEntry, cacheDurationSec*3*len(us.validUserIds)) lastSec2Remove := now.Unix() + idHash := v2hash.NewTimeHash(v2hash.HMACHash{}) for { now := <-tick nowSec := now.UTC().Unix() @@ -59,7 +62,7 @@ func (us *TimedUserSet) updateUserHash(tick <-chan time.Time) { for lastSec < nowSec+cacheDurationSec { for idx, id := range us.validUserIds { - idHash := id.TimeHash(lastSec) + idHash := idHash.Hash(id.Bytes, lastSec) hash2Remove <- hashEntry{string(idHash), lastSec} us.userHashes[string(idHash)] = indexTimePair{idx, lastSec} }