mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-21 16:54:29 +00:00
The relay session context was derived from the inbound connection of the source address that created the session. After a client changes its source address (NAT rebinding), the UDP worker of the old address is cleaned up after ~2 minutes of inactivity and cancels that context, which tears down the dispatched outbound flow; the next packet then creates a brand-new session and outbound flow (new local port on the server, reset replay window). Sessions are routed by client session ID (SIP022 3.2.4), so detach the session context from the originating connection's cancellation and let the session's own inactivity timer govern its lifetime.
184 lines
4.8 KiB
Go
184 lines
4.8 KiB
Go
package shadowsocks_2022
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/log"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/session"
|
|
"github.com/xtls/xray-core/common/signal"
|
|
"github.com/xtls/xray-core/features/policy"
|
|
"github.com/xtls/xray-core/features/routing"
|
|
"github.com/xtls/xray-core/proxy"
|
|
"github.com/xtls/xray-core/transport"
|
|
"github.com/xtls/xray-core/transport/internet/stat"
|
|
)
|
|
|
|
func (s *ServerUDPSession) UpdateConn(conn stat.Connection) {
|
|
s.currentConn.Store(conn)
|
|
s.timer.Update()
|
|
}
|
|
|
|
func (s *ServerUDPSession) WriteToClient(b []byte) error {
|
|
connVal := s.currentConn.Load()
|
|
if connVal == nil {
|
|
return errors.New("client connection closed")
|
|
}
|
|
conn, ok := connVal.(stat.Connection)
|
|
if !ok || conn == nil {
|
|
return errors.New("client connection closed")
|
|
}
|
|
_, err := conn.Write(b)
|
|
return err
|
|
}
|
|
|
|
func (s *ServerUDPSession) Close() {
|
|
if s.timer != nil {
|
|
s.timer.SetTimeout(0)
|
|
}
|
|
if link := s.link.Load(); link != nil {
|
|
common.Interrupt(link.Reader)
|
|
common.Interrupt(link.Writer)
|
|
}
|
|
}
|
|
|
|
func (s *ServerUDPSession) EnsureLink(
|
|
ctx context.Context,
|
|
conn stat.Connection,
|
|
dest net.Destination,
|
|
dispatcher routing.Dispatcher,
|
|
policyManager policy.Manager,
|
|
responseEncoder func(dest net.Destination, payload []byte) ([]byte, error),
|
|
) (*transport.Link, error) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
if link := s.link.Load(); link != nil {
|
|
s.UpdateConn(conn)
|
|
return link, nil
|
|
}
|
|
|
|
s.currentConn.Store(conn)
|
|
|
|
// The session is routed by client session ID and may outlive the inbound
|
|
// connection of the source address it started from (SIP022 §3.2.4), so its
|
|
// lifetime is governed by its own timer rather than by that connection.
|
|
sessCtx, cancel := context.WithCancel(context.WithoutCancel(ctx))
|
|
inbound := session.InboundFromContext(sessCtx)
|
|
if inbound != nil && s.User != nil {
|
|
inbound.User = s.User
|
|
}
|
|
var email string
|
|
var level uint32
|
|
if s.User != nil {
|
|
email = s.User.Email
|
|
level = s.User.Level
|
|
}
|
|
sessCtx = log.ContextWithAccessMessage(sessCtx, &log.AccessMessage{
|
|
From: conn.RemoteAddr(),
|
|
To: dest,
|
|
Status: log.AccessAccepted,
|
|
Email: email,
|
|
})
|
|
|
|
link, err := dispatcher.Dispatch(sessCtx, dest)
|
|
if err != nil {
|
|
cancel()
|
|
return nil, err
|
|
}
|
|
|
|
sessionPolicy := policyManager.ForLevel(level)
|
|
s.timer = signal.CancelAfterInactivity(sessCtx, func() {
|
|
if s.manager != nil {
|
|
s.manager.Delete(s.SessionID)
|
|
}
|
|
s.Close()
|
|
cancel()
|
|
}, sessionPolicy.Timeouts.ConnectionIdle)
|
|
s.link.Store(link)
|
|
|
|
go handleUDPResponse(s, link, dest, responseEncoder)
|
|
return link, nil
|
|
}
|
|
|
|
// ResetTCPConn sets SO_LINGER to 0 per SIP022 §3.1.4 to consistently send RST on close
|
|
// when handshake or header validation fails.
|
|
func ResetTCPConn(conn net.Conn) {
|
|
rawConn, _, _ := proxy.UnwrapRawConn(conn)
|
|
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
|
|
_ = tcpConn.SetLinger(0)
|
|
}
|
|
}
|
|
|
|
func handleUDPResponse(s *ServerUDPSession, link *transport.Link, fallbackDest net.Destination, encode func(dest net.Destination, payload []byte) ([]byte, error)) {
|
|
defer s.timer.SetTimeout(0)
|
|
for {
|
|
resMb, err := link.Reader.ReadMultiBuffer()
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.timer.Update()
|
|
for i, rb := range resMb {
|
|
b := rb.Bytes()
|
|
if encode != nil {
|
|
replyDest := fallbackDest
|
|
if rb.UDP != nil {
|
|
replyDest = *rb.UDP
|
|
}
|
|
encPacket, err := encode(replyDest, b)
|
|
rb.Release()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if err := s.WriteToClient(encPacket); err != nil {
|
|
buf.ReleaseMulti(resMb[i+1:])
|
|
return
|
|
}
|
|
} else {
|
|
err := s.WriteToClient(b)
|
|
rb.Release()
|
|
if err != nil {
|
|
buf.ReleaseMulti(resMb[i+1:])
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const (
|
|
HeaderTypeClient = 0
|
|
HeaderTypeServer = 1
|
|
MaxPaddingLength = 900
|
|
PacketNonceSize = 24
|
|
MaxPacketSize = 65535
|
|
RequestHeaderFixedChunkLength = 1 + 8 + 2 // Type (1B) + Timestamp (8B) + VarHeaderLen (2B)
|
|
PacketMinimalHeaderSize = 30
|
|
StreamNonceSize = 12
|
|
AESBlockSize = 16
|
|
AEADTagSize = 16
|
|
)
|
|
|
|
var zeroPadding [MaxPaddingLength]byte
|
|
|
|
const (
|
|
MethodAES128GCM = "2022-blake3-aes-128-gcm"
|
|
MethodAES256GCM = "2022-blake3-aes-256-gcm"
|
|
MethodChaCha20Poly1305 = "2022-blake3-chacha20-poly1305"
|
|
)
|
|
|
|
var (
|
|
ErrBadKey = errors.New("bad key")
|
|
ErrBadHeaderType = errors.New("bad header type")
|
|
ErrBadTimestamp = errors.New("bad timestamp")
|
|
ErrSaltNotUnique = errors.New("salt not unique")
|
|
ErrPacketIdNotUnique = errors.New("packet id not unique")
|
|
ErrPacketTooShort = errors.New("packet too short")
|
|
ErrPacketTooLarge = errors.New("packet too large")
|
|
ErrNoPadding = errors.New("bad request: missing payload or padding")
|
|
ErrInvalidRequest = errors.New("invalid request")
|
|
)
|