diff --git a/common/dice/dice.go b/common/dice/dice.go index ac03fe9e..6dc94b71 100644 --- a/common/dice/dice.go +++ b/common/dice/dice.go @@ -15,6 +15,14 @@ func Roll(n int) int { return rand.Intn(n) } +// Roll returns a non-negative number between 0 (inclusive) and n (exclusive). +func RollDeterministic(n int, seed int64) int { + if n == 1 { + return 0 + } + return rand.New(rand.NewSource(seed)).Intn(n) +} + // RollUint16 returns a random uint16 value. func RollUint16() uint16 { return uint16(rand.Intn(65536)) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 0840ce41..6c04fa33 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -5,8 +5,10 @@ import ( "encoding/binary" "hash/fnv" "io" + "io/ioutil" "sync" "time" + "v2ray.com/core/common/dice" "golang.org/x/crypto/chacha20poly1305" @@ -194,7 +196,13 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request expectedHash := binary.BigEndian.Uint32(buffer.BytesFrom(-4)) if actualHash != expectedHash { - return nil, newError("invalid auth") + //It is possible that we are under attack described in https://github.com/v2ray/v2ray-core/issues/2523 + //We read a deterministic generated length of data before closing the connection to offset padding read pattern + drainSum := dice.RollDeterministic(48, int64(actualHash)) + if err := s.DrainConnN(reader, drainSum); err != nil { + return nil, newError("invalid auth, failed to drain connection").Base(err) + } + return nil, newError("invalid auth, connection drained") } if request.Address == nil { @@ -347,3 +355,8 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ panic("Unknown security type.") } } + +func (s *ServerSession) DrainConnN(reader io.Reader, n int) error { + _, err := io.CopyN(ioutil.Discard, reader, int64(n)) + return err +}