github.com/imannamdari/v2ray-core/v5@v5.0.5/proxy/vmess/validator.go (about)

     1  package vmess
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha256"
     6  	"hash/crc64"
     7  	"strings"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/imannamdari/v2ray-core/v5/common"
    13  	"github.com/imannamdari/v2ray-core/v5/common/dice"
    14  	"github.com/imannamdari/v2ray-core/v5/common/protocol"
    15  	"github.com/imannamdari/v2ray-core/v5/common/serial"
    16  	"github.com/imannamdari/v2ray-core/v5/common/task"
    17  	"github.com/imannamdari/v2ray-core/v5/proxy/vmess/aead"
    18  )
    19  
    20  const (
    21  	updateInterval   = 10 * time.Second
    22  	cacheDurationSec = 120
    23  )
    24  
    25  type user struct {
    26  	user    protocol.MemoryUser
    27  	lastSec protocol.Timestamp
    28  }
    29  
    30  // TimedUserValidator is a user Validator based on time.
    31  type TimedUserValidator struct {
    32  	sync.RWMutex
    33  	users    []*user
    34  	userHash map[[16]byte]indexTimePair
    35  	hasher   protocol.IDHash
    36  	baseTime protocol.Timestamp
    37  	task     *task.Periodic
    38  
    39  	behaviorSeed  uint64
    40  	behaviorFused bool
    41  
    42  	aeadDecoderHolder *aead.AuthIDDecoderHolder
    43  
    44  	legacyWarnShown bool
    45  }
    46  
    47  type indexTimePair struct {
    48  	user    *user
    49  	timeInc uint32
    50  
    51  	taintedFuse *uint32
    52  }
    53  
    54  // NewTimedUserValidator creates a new TimedUserValidator.
    55  func NewTimedUserValidator(hasher protocol.IDHash) *TimedUserValidator {
    56  	tuv := &TimedUserValidator{
    57  		users:             make([]*user, 0, 16),
    58  		userHash:          make(map[[16]byte]indexTimePair, 1024),
    59  		hasher:            hasher,
    60  		baseTime:          protocol.Timestamp(time.Now().Unix() - cacheDurationSec*2),
    61  		aeadDecoderHolder: aead.NewAuthIDDecoderHolder(),
    62  	}
    63  	tuv.task = &task.Periodic{
    64  		Interval: updateInterval,
    65  		Execute: func() error {
    66  			tuv.updateUserHash()
    67  			return nil
    68  		},
    69  	}
    70  	common.Must(tuv.task.Start())
    71  	return tuv
    72  }
    73  
    74  // visible for testing
    75  func (v *TimedUserValidator) GetBaseTime() protocol.Timestamp {
    76  	return v.baseTime
    77  }
    78  
    79  func (v *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, user *user) {
    80  	var hashValue [16]byte
    81  	genEndSec := nowSec + cacheDurationSec
    82  	genHashForID := func(id *protocol.ID) {
    83  		idHash := v.hasher(id.Bytes())
    84  		genBeginSec := user.lastSec
    85  		if genBeginSec < nowSec-cacheDurationSec {
    86  			genBeginSec = nowSec - cacheDurationSec
    87  		}
    88  		for ts := genBeginSec; ts <= genEndSec; ts++ {
    89  			common.Must2(serial.WriteUint64(idHash, uint64(ts)))
    90  			idHash.Sum(hashValue[:0])
    91  			idHash.Reset()
    92  
    93  			v.userHash[hashValue] = indexTimePair{
    94  				user:        user,
    95  				timeInc:     uint32(ts - v.baseTime),
    96  				taintedFuse: new(uint32),
    97  			}
    98  		}
    99  	}
   100  
   101  	account := user.user.Account.(*MemoryAccount)
   102  
   103  	genHashForID(account.ID)
   104  	for _, id := range account.AlterIDs {
   105  		genHashForID(id)
   106  	}
   107  	user.lastSec = genEndSec
   108  }
   109  
   110  func (v *TimedUserValidator) removeExpiredHashes(expire uint32) {
   111  	for key, pair := range v.userHash {
   112  		if pair.timeInc < expire {
   113  			delete(v.userHash, key)
   114  		}
   115  	}
   116  }
   117  
   118  func (v *TimedUserValidator) updateUserHash() {
   119  	now := time.Now()
   120  	nowSec := protocol.Timestamp(now.Unix())
   121  
   122  	v.Lock()
   123  	defer v.Unlock()
   124  
   125  	for _, user := range v.users {
   126  		v.generateNewHashes(nowSec, user)
   127  	}
   128  
   129  	expire := protocol.Timestamp(now.Unix() - cacheDurationSec)
   130  	if expire > v.baseTime {
   131  		v.removeExpiredHashes(uint32(expire - v.baseTime))
   132  	}
   133  }
   134  
   135  func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error {
   136  	v.Lock()
   137  	defer v.Unlock()
   138  
   139  	nowSec := time.Now().Unix()
   140  
   141  	uu := &user{
   142  		user:    *u,
   143  		lastSec: protocol.Timestamp(nowSec - cacheDurationSec),
   144  	}
   145  	v.users = append(v.users, uu)
   146  	v.generateNewHashes(protocol.Timestamp(nowSec), uu)
   147  
   148  	account := uu.user.Account.(*MemoryAccount)
   149  	if !v.behaviorFused {
   150  		hashkdf := hmac.New(sha256.New, []byte("VMESSBSKDF"))
   151  		hashkdf.Write(account.ID.Bytes())
   152  		v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))
   153  	}
   154  
   155  	var cmdkeyfl [16]byte
   156  	copy(cmdkeyfl[:], account.ID.CmdKey())
   157  	v.aeadDecoderHolder.AddUser(cmdkeyfl, u)
   158  
   159  	return nil
   160  }
   161  
   162  func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protocol.Timestamp, bool, error) {
   163  	v.RLock()
   164  	defer v.RUnlock()
   165  
   166  	v.behaviorFused = true
   167  
   168  	var fixedSizeHash [16]byte
   169  	copy(fixedSizeHash[:], userHash)
   170  	pair, found := v.userHash[fixedSizeHash]
   171  	if found {
   172  		user := pair.user.user
   173  		if atomic.LoadUint32(pair.taintedFuse) == 0 {
   174  			return &user, protocol.Timestamp(pair.timeInc) + v.baseTime, true, nil
   175  		}
   176  		return nil, 0, false, ErrTainted
   177  	}
   178  	return nil, 0, false, ErrNotFound
   179  }
   180  
   181  func (v *TimedUserValidator) GetAEAD(userHash []byte) (*protocol.MemoryUser, bool, error) {
   182  	v.RLock()
   183  	defer v.RUnlock()
   184  
   185  	var userHashFL [16]byte
   186  	copy(userHashFL[:], userHash)
   187  
   188  	userd, err := v.aeadDecoderHolder.Match(userHashFL)
   189  	if err != nil {
   190  		return nil, false, err
   191  	}
   192  	return userd.(*protocol.MemoryUser), true, err
   193  }
   194  
   195  func (v *TimedUserValidator) Remove(email string) bool {
   196  	v.Lock()
   197  	defer v.Unlock()
   198  
   199  	email = strings.ToLower(email)
   200  	idx := -1
   201  	for i, u := range v.users {
   202  		if strings.EqualFold(u.user.Email, email) {
   203  			idx = i
   204  			var cmdkeyfl [16]byte
   205  			copy(cmdkeyfl[:], u.user.Account.(*MemoryAccount).ID.CmdKey())
   206  			v.aeadDecoderHolder.RemoveUser(cmdkeyfl)
   207  			break
   208  		}
   209  	}
   210  	if idx == -1 {
   211  		return false
   212  	}
   213  	ulen := len(v.users)
   214  
   215  	v.users[idx] = v.users[ulen-1]
   216  	v.users[ulen-1] = nil
   217  	v.users = v.users[:ulen-1]
   218  
   219  	return true
   220  }
   221  
   222  // Close implements common.Closable.
   223  func (v *TimedUserValidator) Close() error {
   224  	return v.task.Close()
   225  }
   226  
   227  func (v *TimedUserValidator) GetBehaviorSeed() uint64 {
   228  	v.Lock()
   229  	defer v.Unlock()
   230  
   231  	v.behaviorFused = true
   232  	if v.behaviorSeed == 0 {
   233  		v.behaviorSeed = dice.RollUint64()
   234  	}
   235  	return v.behaviorSeed
   236  }
   237  
   238  func (v *TimedUserValidator) BurnTaintFuse(userHash []byte) error {
   239  	v.RLock()
   240  	defer v.RUnlock()
   241  
   242  	var userHashFL [16]byte
   243  	copy(userHashFL[:], userHash)
   244  
   245  	pair, found := v.userHash[userHashFL]
   246  	if found {
   247  		if atomic.CompareAndSwapUint32(pair.taintedFuse, 0, 1) {
   248  			return nil
   249  		}
   250  		return ErrTainted
   251  	}
   252  	return ErrNotFound
   253  }
   254  
   255  /*
   256  	ShouldShowLegacyWarn will return whether a Legacy Warning should be shown
   257  
   258  Not guaranteed to only return true once for every inbound, but it is okay.
   259  */
   260  func (v *TimedUserValidator) ShouldShowLegacyWarn() bool {
   261  	if v.legacyWarnShown {
   262  		return false
   263  	}
   264  	v.legacyWarnShown = true
   265  	return true
   266  }
   267  
   268  var ErrNotFound = newError("Not Found")
   269  
   270  var ErrTainted = newError("ErrTainted")