Skip to content

Commit

Permalink
Merge github.com:/v2fly/v2ray-core tag v4.23.4
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholascw committed Jun 3, 2020
2 parents 85633ec + b610fc0 commit 6b5d2fe
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 62 deletions.
19 changes: 19 additions & 0 deletions common/dice/dice.go
Expand Up @@ -28,6 +28,25 @@ func RollUint16() uint16 {
return uint16(rand.Intn(65536))
}

func RollUint64() uint64 {
return rand.Uint64()
}

func NewDeterministicDice(seed int64) *deterministicDice {
return &deterministicDice{rand.New(rand.NewSource(seed))}
}

type deterministicDice struct {
*rand.Rand
}

func (dd *deterministicDice) Roll(n int) int {
if n == 1 {
return 0
}
return dd.Intn(n)
}

func init() {
rand.Seed(time.Now().Unix())
}
2 changes: 2 additions & 0 deletions common/protocol/headers.go
Expand Up @@ -38,6 +38,8 @@ const (
RequestOptionChunkMasking bitmask.Byte = 0x04

RequestOptionGlobalPadding bitmask.Byte = 0x08

RequestOptionEarlyChecksum bitmask.Byte = 0x16
)

type RequestHeader struct {
Expand Down
2 changes: 1 addition & 1 deletion core.go
Expand Up @@ -19,7 +19,7 @@ import (
)

var (
version = "4.23.3"
version = "4.23.4"
build = "Custom"
codename = "V2Fly, a community-driven edition of V2Ray."
intro = "A unified platform for anti-censorship."
Expand Down
4 changes: 3 additions & 1 deletion features/policy/policy.go
Expand Up @@ -113,7 +113,9 @@ func defaultBufferPolicy() Buffer {
func SessionDefault() Session {
return Session{
Timeouts: Timeout{
Handshake: time.Second * 4,
//Align Handshake timeout with nginx client_header_timeout
//So that this value will not indicate server identity
Handshake: time.Second * 60,
ConnectionIdle: time.Second * 300,
UplinkOnly: time.Second * 1,
DownlinkOnly: time.Second * 1,
Expand Down
36 changes: 27 additions & 9 deletions proxy/vmess/encoding/server.go
Expand Up @@ -125,15 +125,37 @@ func parseSecurityType(b byte) protocol.SecurityType {
// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.
func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) {
buffer := buf.New()
defer buffer.Release()
behaviorRand := dice.NewDeterministicDice(int64(s.userValidator.GetBehaviorSeed()))
BaseDrainSize := behaviorRand.Roll(3266)
RandDrainMax := behaviorRand.Roll(64) + 1
RandDrainRolled := dice.Roll(RandDrainMax)
DrainSize := BaseDrainSize + 16 + 38 + RandDrainRolled
readSizeRemain := DrainSize

drainConnection := func(e error) error {
//We read a deterministic generated length of data before closing the connection to offset padding read pattern
readSizeRemain -= int(buffer.Len())
if readSizeRemain > 0 {
err := s.DrainConnN(reader, readSizeRemain)
if err != nil {
return newError("failed to drain connection DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(err).Base(e)
}
return newError("connection drained DrainSize = ", BaseDrainSize, " ", RandDrainMax, " ", RandDrainRolled).Base(e)
}
return e
}

defer func() {
buffer.Release()
}()

if _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil {
return nil, newError("failed to read request header").Base(err)
}

user, timestamp, valid := s.userValidator.Get(buffer.Bytes())
if !valid {
return nil, newError("invalid user")
return nil, drainConnection(newError("invalid user"))
}

iv := hashTimestamp(md5.New(), timestamp)
Expand All @@ -142,6 +164,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv[:])
decryptor := crypto.NewCryptionReader(aesStream, reader)

readSizeRemain -= int(buffer.Len())
buffer.Clear()
if _, err := buffer.ReadFullFrom(decryptor, 38); err != nil {
return nil, newError("failed to read request header").Base(err)
Expand All @@ -159,7 +182,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
sid.key = s.requestBodyKey
sid.nonce = s.requestBodyIV
if !s.sessionHistory.addIfNotExits(sid) {
return nil, newError("duplicated session id, possibly under replay attack")
return nil, drainConnection(newError("duplicated session id, possibly under replay attack"))
}

s.responseHeader = buffer.Byte(33) // 1 byte
Expand Down Expand Up @@ -197,12 +220,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request

if actualHash != expectedHash {
//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")
return nil, drainConnection(newError("invalid auth"))
}

if request.Address == nil {
Expand Down
31 changes: 26 additions & 5 deletions proxy/vmess/validator.go
Expand Up @@ -3,9 +3,11 @@
package vmess

import (
"hash/crc64"
"strings"
"sync"
"time"
"v2ray.com/core/common/dice"

"v2ray.com/core/common"
"v2ray.com/core/common/protocol"
Expand All @@ -26,11 +28,13 @@ type user struct {
// TimedUserValidator is a user Validator based on time.
type TimedUserValidator struct {
sync.RWMutex
users []*user
userHash map[[16]byte]indexTimePair
hasher protocol.IDHash
baseTime protocol.Timestamp
task *task.Periodic
users []*user
userHash map[[16]byte]indexTimePair
hasher protocol.IDHash
baseTime protocol.Timestamp
task *task.Periodic
behaviorSeed uint64
behaviorFused bool
}

type indexTimePair struct {
Expand Down Expand Up @@ -124,13 +128,20 @@ func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error {
v.users = append(v.users, uu)
v.generateNewHashes(protocol.Timestamp(nowSec), uu)

if v.behaviorFused == false {
account := uu.user.Account.(*MemoryAccount)
v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), account.ID.Bytes())
}

return nil
}

func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protocol.Timestamp, bool) {
defer v.RUnlock()
v.RLock()

v.behaviorFused = true

var fixedSizeHash [16]byte
copy(fixedSizeHash[:], userHash)
pair, found := v.userHash[fixedSizeHash]
Expand Down Expand Up @@ -170,3 +181,13 @@ func (v *TimedUserValidator) Remove(email string) bool {
func (v *TimedUserValidator) Close() error {
return v.task.Close()
}

func (v *TimedUserValidator) GetBehaviorSeed() uint64 {
v.Lock()
defer v.Unlock()
v.behaviorFused = true
if v.behaviorSeed == 0 {
v.behaviorSeed = dice.RollUint64()
}
return v.behaviorSeed
}
5 changes: 4 additions & 1 deletion testing/scenarios/command_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -265,7 +266,9 @@ func TestCommanderAddRemoveUser(t *testing.T) {
common.Must(err)
defer CloseAllServers(servers)

if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != io.EOF {
if err := testTCPConn(clientPort, 1024, time.Second*5)(); err != io.EOF &&
/*We might wish to drain the connection*/
(err != nil && !strings.HasSuffix(err.Error(), "i/o timeout")) {
t.Fatal("expected error: ", err)
}

Expand Down

0 comments on commit 6b5d2fe

Please sign in to comment.