Files
Xray-core/proxy/shadowsocks_2022/replay.go
T
2026-09-24 22:00:36 +08:00

160 lines
3.5 KiB
Go

package shadowsocks_2022
import (
"crypto/cipher"
"sync"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/utils"
)
const (
swBlockBitLog = 6 // 1<<6 == 64 bits
swBlockBits = 1 << swBlockBitLog // 64
swRingBlocks = 1 << 7 // 128
swBlockMask = swRingBlocks - 1 // 127
swBitMask = swBlockBits - 1 // 63
swSize = (swRingBlocks - 1) * swBlockBits // 8128
)
type SlidingWindow struct {
last uint64
ring [swRingBlocks]uint64
}
func (f *SlidingWindow) Reset() {
f.last = 0
f.ring[0] = 0
}
func (f *SlidingWindow) Check(counter uint64) bool {
switch {
case counter > f.last:
return true
case f.last-counter > swSize:
return false
}
blockIndex := (counter >> swBlockBitLog) & swBlockMask
bitIndex := counter & swBitMask
return (f.ring[blockIndex]>>bitIndex)&1 == 0
}
func (f *SlidingWindow) Add(counter uint64) {
blockIndex := counter >> swBlockBitLog
if counter > f.last {
lastBlockIndex := f.last >> swBlockBitLog
diff := int(blockIndex - lastBlockIndex)
if diff > swRingBlocks {
diff = swRingBlocks
}
for i := 0; i < diff; i++ {
lastBlockIndex = (lastBlockIndex + 1) & swBlockMask
f.ring[lastBlockIndex] = 0
}
f.last = counter
}
blockIndex &= swBlockMask
bitIndex := counter & swBitMask
f.ring[blockIndex] |= 1 << bitIndex
}
func (f *SlidingWindow) CheckAndAdd(counter uint64) bool {
if !f.Check(counter) {
return false
}
f.Add(counter)
return true
}
type ServerUDPSession struct {
sync.Mutex
SessionID uint64
RemoteCipher atomic.Pointer[cipher.AEAD]
Window SlidingWindow
User *protocol.MemoryUser
UserPSK []byte
LastActive atomic.Int64 // Unix timestamp in seconds
ServerSessionID uint64
ServerPacketID atomic.Uint64
ServerCipher cipher.AEAD
ServerBlockCipher cipher.Block
ServerChaCha cipher.AEAD
}
func (s *ServerUDPSession) GetRemoteCipher() cipher.AEAD {
ptr := s.RemoteCipher.Load()
if ptr == nil {
return nil
}
return *ptr
}
func (s *ServerUDPSession) SetRemoteCipher(c cipher.AEAD) {
s.RemoteCipher.Store(&c)
}
type UDPSessionManager struct {
sessions *utils.TypedSyncMap[uint64, *ServerUDPSession]
timeout time.Duration
lastClean atomic.Int64 // Unix timestamp in seconds
}
func NewUDPSessionManager(timeout time.Duration) *UDPSessionManager {
return &UDPSessionManager{
sessions: utils.NewTypedSyncMap[uint64, *ServerUDPSession](),
timeout: timeout,
}
}
func (m *UDPSessionManager) GetOrCreate(sessionID uint64) (*ServerUDPSession, bool) {
now := time.Now().Unix()
if s, ok := m.sessions.Load(sessionID); ok {
s.LastActive.Store(now)
return s, true
}
s := &ServerUDPSession{
SessionID: sessionID,
}
s.LastActive.Store(now)
actual, loaded := m.sessions.LoadOrStore(sessionID, s)
if loaded {
actual.LastActive.Store(now)
return actual, true
}
// Trigger cleanup if at least 30 seconds have passed since last cleanup
last := m.lastClean.Load()
if now-last > 30 && m.lastClean.CompareAndSwap(last, now) {
go m.cleanup(now)
}
return s, false
}
func (m *UDPSessionManager) cleanup(now int64) {
timeoutSec := int64(m.timeout.Seconds())
if timeoutSec <= 0 {
timeoutSec = 60
}
m.sessions.Range(func(k uint64, v *ServerUDPSession) bool {
if now-v.LastActive.Load() > timeoutSec {
m.sessions.Delete(k)
}
return true
})
}
func (m *UDPSessionManager) Delete(sessionID uint64) {
m.sessions.Delete(sessionID)
}