diff --git a/id.go b/id.go index 0acb38930..dd4bc5c97 100644 --- a/id.go +++ b/id.go @@ -1,27 +1,68 @@ package core import ( + "crypto/hmac" "crypto/md5" "encoding/hex" + "hash" + mrand "math/rand" + "time" "github.com/v2ray/v2ray-core/log" ) -// The ID of en entity, in the form of an UUID. -type ID [16]byte +const ( + IDBytesLen = 16 +) -// Hash generates a MD5 hash based on current ID and a suffix string. -func (v ID) Hash(suffix []byte) []byte { - md5 := md5.New() - md5.Write(v[:]) - md5.Write(suffix) - return md5.Sum(nil) +// The ID of en entity, in the form of an UUID. +type ID struct { + String string + Bytes []byte + hasher hash.Hash +} + +func NewID(id string) (ID, error) { + idBytes, err := UUIDToID(id) + if err != nil { + return ID{}, log.Error("Failed to parse id %s", id) + } + hasher := hmac.New(md5.New, idBytes) + return ID{id, idBytes, hasher}, nil +} + +func (v ID) TimeRangeHash(rangeSec int) []byte { + nowSec := time.Now().UTC().Unix() + delta := mrand.Intn(rangeSec*2) - rangeSec + + targetSec := nowSec + int64(delta) + return v.TimeHash(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 { + return v.hasher.Sum(data) } var byteGroups = []int{8, 4, 4, 4, 12} // TODO: leverage a full functional UUID library -func UUIDToID(uuid string) (v ID, err error) { +func UUIDToID(uuid string) (v []byte, err error) { + v = make([]byte, 16) + text := []byte(uuid) if len(text) < 32 { err = log.Error("uuid: invalid UUID string: %s", text) diff --git a/id_test.go b/id_test.go index 3346e4f76..1d9d74f04 100644 --- a/id_test.go +++ b/id_test.go @@ -13,5 +13,5 @@ func TestUUIDToID(t *testing.T) { expectedBytes := []byte{0x24, 0x18, 0xd0, 0x87, 0x64, 0x8d, 0x49, 0x90, 0x86, 0xe8, 0x19, 0xdc, 0xa1, 0xd0, 0x06, 0xd3} actualBytes, _ := UUIDToID(uuid) - assert.Bytes(actualBytes[:]).Named("UUID").Equals(expectedBytes) + assert.Bytes(actualBytes.Bytes()).Named("UUID").Equals(expectedBytes) } diff --git a/io/vmess/vmess.go b/io/vmess/vmess.go index 4873c5f07..dbaab0389 100644 --- a/io/vmess/vmess.go +++ b/io/vmess/vmess.go @@ -22,8 +22,10 @@ const ( addrTypeDomain = byte(0x02) Version = byte(0x01) - - blockSize = 16 + + blockSize = 16 + + CryptoMessage = "c48619fe-8f02-49e0-b9e9-edf763e17e21" ) var ( @@ -67,7 +69,7 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) { // TODO: verify version number request.Version = buffer[0] - nBytes, err = reader.Read(buffer[:len(request.UserId)]) + nBytes, err = reader.Read(buffer[:core.IDBytesLen]) if err != nil { return nil, err } @@ -77,8 +79,8 @@ func (r *VMessRequestReader) Read(reader io.Reader) (*VMessRequest, error) { return nil, ErrorInvalidUser } request.UserId = *userId - - aesCipher, err := aes.NewCipher(userId.Hash([]byte("PWD"))) + + aesCipher, err := aes.NewCipher(userId.Hash([]byte(CryptoMessage))) if err != nil { return nil, err } @@ -181,7 +183,7 @@ func NewVMessRequestWriter() *VMessRequestWriter { func (w *VMessRequestWriter) Write(writer io.Writer, request *VMessRequest) error { buffer := make([]byte, 0, 300) buffer = append(buffer, request.Version) - buffer = append(buffer, request.UserId.Hash([]byte("ASK"))...) + buffer = append(buffer, request.UserId.TimeRangeHash(30)...) encryptionBegin := len(buffer) @@ -229,7 +231,7 @@ func (w *VMessRequestWriter) Write(writer io.Writer, request *VMessRequest) erro buffer = append(buffer, paddingBuffer...) encryptionEnd := len(buffer) - aesCipher, err := aes.NewCipher(request.UserId.Hash([]byte("PWD"))) + aesCipher, err := aes.NewCipher(request.UserId.Hash([]byte(CryptoMessage))) if err != nil { return err } diff --git a/net/freedom/freedom.go b/net/freedom/freedom.go index 83c6e7fb3..4824cb888 100644 --- a/net/freedom/freedom.go +++ b/net/freedom/freedom.go @@ -28,8 +28,8 @@ func (vconn *FreedomConnection) Start(ray core.OutboundRay) error { log.Debug("Sending outbound tcp: %s", vconn.dest.String()) readFinish := make(chan bool) - writeFinish := make(chan bool) - + writeFinish := make(chan bool) + go vconn.DumpInput(conn, input, writeFinish) go vconn.DumpOutput(conn, output, readFinish) go vconn.CloseConn(conn, readFinish, writeFinish) @@ -38,23 +38,23 @@ func (vconn *FreedomConnection) Start(ray core.OutboundRay) error { func (vconn *FreedomConnection) DumpInput(conn net.Conn, input <-chan []byte, finish chan<- bool) { v2net.ChanToWriter(conn, input) - log.Debug("Freedom closing input") + log.Debug("Freedom closing input") finish <- true } func (vconn *FreedomConnection) DumpOutput(conn net.Conn, output chan<- []byte, finish chan<- bool) { v2net.ReaderToChan(output, conn) close(output) - log.Debug("Freedom closing output") + log.Debug("Freedom closing output") finish <- true } func (vconn *FreedomConnection) CloseConn(conn net.Conn, readFinish <-chan bool, writeFinish <-chan bool) { <-writeFinish - if tcpConn, ok := conn.(*net.TCPConn); ok { - log.Debug("Closing freedom write.") - tcpConn.CloseWrite(); - } + if tcpConn, ok := conn.(*net.TCPConn); ok { + log.Debug("Closing freedom write.") + tcpConn.CloseWrite() + } <-readFinish conn.Close() } diff --git a/net/socks/socks.go b/net/socks/socks.go index 65b5ab493..92c9720ce 100644 --- a/net/socks/socks.go +++ b/net/socks/socks.go @@ -116,7 +116,7 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error { input := ray.InboundInput() output := ray.InboundOutput() readFinish := make(chan bool) - writeFinish := make(chan bool) + writeFinish := make(chan bool) go server.dumpInput(connection, input, readFinish) go server.dumpOutput(connection, output, writeFinish) @@ -128,12 +128,12 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error { func (server *SocksServer) dumpInput(conn net.Conn, input chan<- []byte, finish chan<- bool) { v2net.ReaderToChan(input, conn) close(input) - log.Debug("Socks input closed") + log.Debug("Socks input closed") finish <- true } func (server *SocksServer) dumpOutput(conn net.Conn, output <-chan []byte, finish chan<- bool) { v2net.ChanToWriter(conn, output) - log.Debug("Socks output closed") + log.Debug("Socks output closed") finish <- true } diff --git a/net/transport.go b/net/transport.go index caf93678b..63d5919d6 100644 --- a/net/transport.go +++ b/net/transport.go @@ -14,9 +14,9 @@ func ReaderToChan(stream chan<- []byte, reader io.Reader) error { for { buffer := make([]byte, bufferSize) nBytes, err := reader.Read(buffer) - if nBytes > 0 { - stream <- buffer[:nBytes] - } + if nBytes > 0 { + stream <- buffer[:nBytes] + } if err != nil { return err } diff --git a/net/vmess/config.go b/net/vmess/config.go index b823a3034..479784a40 100644 --- a/net/vmess/config.go +++ b/net/vmess/config.go @@ -14,7 +14,7 @@ type VMessUser struct { } func (u *VMessUser) ToUser() (core.User, error) { - id, err := core.UUIDToID(u.Id) + id, err := core.NewID(u.Id) return core.User{id}, err } diff --git a/net/vmess/vmessin.go b/net/vmess/vmessin.go index b24355eca..8cc036953 100644 --- a/net/vmess/vmessin.go +++ b/net/vmess/vmessin.go @@ -50,7 +50,7 @@ func (handler *VMessInboundHandler) AcceptConnections(listener net.Listener) err func (handler *VMessInboundHandler) HandleConnection(connection net.Conn) error { defer connection.Close() - + reader := vmessio.NewVMessRequestReader(handler.clients) request, err := reader.Read(connection) @@ -83,19 +83,19 @@ func (handler *VMessInboundHandler) HandleConnection(connection net.Conn) error ray := handler.vPoint.NewInboundConnectionAccepted(request.Address) input := ray.InboundInput() output := ray.InboundOutput() - + readFinish := make(chan bool) - writeFinish := make(chan bool) + writeFinish := make(chan bool) go handler.dumpInput(requestReader, input, readFinish) go handler.dumpOutput(responseWriter, output, writeFinish) - - <-writeFinish - if tcpConn, ok := connection.(*net.TCPConn); ok { - log.Debug("VMessIn closing write") - tcpConn.CloseWrite(); - } - <-readFinish + + <-writeFinish + if tcpConn, ok := connection.(*net.TCPConn); ok { + log.Debug("VMessIn closing write") + tcpConn.CloseWrite() + } + <-readFinish return nil } @@ -103,17 +103,16 @@ func (handler *VMessInboundHandler) HandleConnection(connection net.Conn) error func (handler *VMessInboundHandler) dumpInput(reader io.Reader, input chan<- []byte, finish chan<- bool) { v2net.ReaderToChan(input, reader) close(input) - log.Debug("VMessIn closing input") + log.Debug("VMessIn closing input") finish <- true } func (handler *VMessInboundHandler) dumpOutput(writer io.Writer, output <-chan []byte, finish chan<- bool) { v2net.ChanToWriter(writer, output) - log.Debug("VMessOut closing output") + log.Debug("VMessOut closing output") finish <- true } - type VMessInboundHandlerFactory struct { } diff --git a/net/vmess/vmessout.go b/net/vmess/vmessout.go index 4a1b40e43..e4ef7ebb6 100644 --- a/net/vmess/vmessout.go +++ b/net/vmess/vmessout.go @@ -110,28 +110,28 @@ func (handler *VMessOutboundHandler) startCommunicate(request *vmessio.VMessRequ input := ray.OutboundInput() output := ray.OutboundOutput() readFinish := make(chan bool) - writeFinish := make(chan bool) + writeFinish := make(chan bool) go handler.dumpInput(encryptRequestWriter, input, readFinish) go handler.dumpOutput(decryptResponseReader, output, writeFinish) - - <-readFinish - conn.CloseWrite() - log.Debug("VMessOut closing write") - <-writeFinish - return nil + + <-readFinish + conn.CloseWrite() + log.Debug("VMessOut closing write") + <-writeFinish + return nil } func (handler *VMessOutboundHandler) dumpOutput(reader io.Reader, output chan<- []byte, finish chan<- bool) { v2net.ReaderToChan(output, reader) close(output) - log.Debug("VMessOut closing output") + log.Debug("VMessOut closing output") finish <- true } func (handler *VMessOutboundHandler) dumpInput(writer io.Writer, input <-chan []byte, finish chan<- bool) { v2net.ChanToWriter(writer, input) - log.Debug("VMessOut closing input") + log.Debug("VMessOut closing input") finish <- true } diff --git a/ray.go b/ray.go index 96ac01416..313bdbc91 100644 --- a/ray.go +++ b/ray.go @@ -1,7 +1,7 @@ package core const ( - bufferSize = 16 + bufferSize = 16 ) type Ray struct { diff --git a/userset.go b/userset.go index f861f2159..f3e98d9eb 100644 --- a/userset.go +++ b/userset.go @@ -1,38 +1,70 @@ package core import ( - "encoding/base64" + "time" +) + +const ( + updateIntervalSec = 10 + cacheDurationSec = 120 ) type UserSet struct { - validUserIds []ID - userIdsAskHash map[string]int + validUserIds []ID + userHashes map[string]int +} + +type hashEntry struct { + hash string + timeSec int64 } func NewUserSet() *UserSet { vuSet := new(UserSet) vuSet.validUserIds = make([]ID, 0, 16) - vuSet.userIdsAskHash = make(map[string]int) + vuSet.userHashes = make(map[string]int) + + go vuSet.updateUserHash(time.Tick(updateIntervalSec * time.Second)) return vuSet } -func hashBytesToString(hash []byte) string { - return base64.StdEncoding.EncodeToString(hash) +func (us *UserSet) updateUserHash(tick <-chan time.Time) { + now := time.Now().UTC() + lastSec := now.Unix() - cacheDurationSec + + hash2Remove := make(chan hashEntry, updateIntervalSec*2) + lastSec2Remove := now.Unix() + cacheDurationSec + for { + now := <-tick + nowSec := now.UTC().Unix() + + remove2Sec := nowSec - cacheDurationSec + if remove2Sec > lastSec2Remove { + for lastSec2Remove+1 < remove2Sec { + entry := <-hash2Remove + lastSec2Remove = entry.timeSec + delete(us.userHashes, entry.hash) + } + } + + for i := lastSec + 1; i <= nowSec; i++ { + for idx, id := range us.validUserIds { + idHash := id.TimeHash(i) + hash2Remove <- hashEntry{string(idHash), i} + us.userHashes[string(idHash)] = idx + } + } + } } func (us *UserSet) AddUser(user User) error { id := user.Id us.validUserIds = append(us.validUserIds, id) - - idBase64 := hashBytesToString(id.Hash([]byte("ASK"))) - us.userIdsAskHash[idBase64] = len(us.validUserIds) - 1 - return nil } -func (us UserSet) IsValidUserId(askHash []byte) (*ID, bool) { - askBase64 := hashBytesToString(askHash) - idIndex, found := us.userIdsAskHash[askBase64] +func (us UserSet) IsValidUserId(userHash []byte) (*ID, bool) { + idIndex, found := us.userHashes[string(userHash)] if found { return &us.validUserIds[idIndex], true }