mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-30 19:07:58 +03:00
https://github.com/XTLS/Xray-core/pull/6866#issuecomment-5904374529 Fixes https://github.com/XTLS/Xray-core/pull/6831#issuecomment-5884408501 and https://github.com/XTLS/Xray-core/pull/6866#issuecomment-5889568266
266 lines
7.1 KiB
Go
266 lines
7.1 KiB
Go
package shadowsocks_2022
|
|
|
|
import (
|
|
"context"
|
|
"crypto/cipher"
|
|
"encoding/binary"
|
|
"strconv"
|
|
"time"
|
|
|
|
"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/protocol"
|
|
"github.com/xtls/xray-core/common/session"
|
|
"github.com/xtls/xray-core/common/uuid"
|
|
"github.com/xtls/xray-core/core"
|
|
"github.com/xtls/xray-core/features/policy"
|
|
"github.com/xtls/xray-core/features/routing"
|
|
"github.com/xtls/xray-core/transport/internet/stat"
|
|
)
|
|
|
|
func init() {
|
|
common.Must(common.RegisterConfig((*RelayServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
|
return NewRelayServer(ctx, config.(*RelayServerConfig))
|
|
}))
|
|
}
|
|
|
|
type relayDest struct {
|
|
destination net.Destination
|
|
email string
|
|
level uint32
|
|
blockCipher cipher.Block
|
|
}
|
|
|
|
type RelayInbound struct {
|
|
networks []net.Network
|
|
method *CipherMethod
|
|
relayPSK []byte
|
|
relayBlock cipher.Block
|
|
destinations map[[AESBlockSize]byte]*relayDest
|
|
udpSessions *UDPSessionManager
|
|
policyManager policy.Manager
|
|
}
|
|
|
|
func NewRelayServer(ctx context.Context, config *RelayServerConfig) (*RelayInbound, error) {
|
|
networks := config.Network
|
|
if len(networks) == 0 {
|
|
networks = []net.Network{
|
|
net.Network_TCP,
|
|
net.Network_UDP,
|
|
}
|
|
}
|
|
|
|
method, err := GetCipherMethod(config.Method)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if method.IsChaCha {
|
|
return nil, errors.New("shadowsocks 2022 relay: only aes methods are supported")
|
|
}
|
|
|
|
relayPSK, err := ParseKey(config.Key, method.KeySaltLength)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
relayBlock, err := method.NewBlock(relayPSK)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := core.MustFromContext(ctx)
|
|
i := &RelayInbound{
|
|
networks: networks,
|
|
method: method,
|
|
relayPSK: relayPSK,
|
|
relayBlock: relayBlock,
|
|
destinations: make(map[[AESBlockSize]byte]*relayDest),
|
|
udpSessions: NewUDPSessionManager(500 * time.Second),
|
|
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
|
}
|
|
|
|
for idx, d := range config.Destinations {
|
|
if d.Email == "" {
|
|
u := uuid.New()
|
|
d.Email = "unnamed-destination-" + strconv.Itoa(idx) + "-" + u.String()
|
|
}
|
|
destKey, err := ParseKey(d.Key, method.KeySaltLength)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
destBlock, err := method.NewBlock(destKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hash := DeriveUserPSKHash(destKey)
|
|
|
|
i.destinations[hash] = &relayDest{
|
|
destination: net.TCPDestination(d.Address.AsAddress(), net.Port(d.Port)),
|
|
email: d.Email,
|
|
level: uint32(d.Level),
|
|
blockCipher: destBlock,
|
|
}
|
|
}
|
|
|
|
return i, nil
|
|
}
|
|
|
|
func (i *RelayInbound) Network() []net.Network {
|
|
return i.networks
|
|
}
|
|
|
|
func (i *RelayInbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {
|
|
inbound := session.InboundFromContext(ctx)
|
|
inbound.Name = "shadowsocks-2022-relay"
|
|
inbound.CanSpliceCopy = 3
|
|
|
|
if network == net.Network_TCP {
|
|
return i.processTCP(ctx, connection, dispatcher)
|
|
}
|
|
return i.processUDP(ctx, connection, dispatcher)
|
|
}
|
|
|
|
func (i *RelayInbound) processTCP(ctx context.Context, conn net.Conn, dispatcher routing.Dispatcher) error {
|
|
defer conn.Close()
|
|
|
|
sessionPolicy := i.policyManager.ForLevel(0)
|
|
if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
|
|
return errors.New("unable to set read deadline").Base(err)
|
|
}
|
|
|
|
// Read initial handshake in a single read call per SIP022 §3.1.3 & §3.1.4
|
|
needed := i.method.KeySaltLength + AESBlockSize
|
|
requestHeader := buf.New()
|
|
n, err := requestHeader.ReadFrom(conn)
|
|
if err != nil {
|
|
requestHeader.Release()
|
|
ResetTCPConn(conn)
|
|
return err
|
|
}
|
|
if int(n) < needed {
|
|
requestHeader.Release()
|
|
ResetTCPConn(conn)
|
|
return ErrInvalidRequest
|
|
}
|
|
|
|
headerSlice := requestHeader.Bytes()
|
|
salt := headerSlice[:i.method.KeySaltLength]
|
|
eih := headerSlice[i.method.KeySaltLength:needed]
|
|
|
|
decryptedHash, err := DecryptEIH(i.method, i.relayPSK, salt, eih)
|
|
if err != nil {
|
|
requestHeader.Release()
|
|
ResetTCPConn(conn)
|
|
return err
|
|
}
|
|
|
|
targetDest, ok := i.destinations[decryptedHash]
|
|
if !ok {
|
|
requestHeader.Release()
|
|
ResetTCPConn(conn)
|
|
return ErrInvalidRequest
|
|
}
|
|
conn.SetReadDeadline(time.Time{})
|
|
|
|
inbound := session.InboundFromContext(ctx)
|
|
inbound.User = &protocol.MemoryUser{
|
|
Email: targetDest.email,
|
|
Level: targetDest.level,
|
|
}
|
|
|
|
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
|
From: conn.RemoteAddr(),
|
|
To: targetDest.destination,
|
|
Status: log.AccessAccepted,
|
|
Email: targetDest.email,
|
|
})
|
|
|
|
errors.LogInfo(ctx, "relaying connection to ", targetDest.destination)
|
|
|
|
link, err := dispatcher.Dispatch(ctx, targetDest.destination)
|
|
if err != nil {
|
|
requestHeader.Release()
|
|
return err
|
|
}
|
|
|
|
// Unwrap outer EIH: send client salt and remaining handshake bytes to next hop
|
|
// in a single write call, satisfying downstream server's single-read handshake expectation (SIP022 §3.1.3).
|
|
var saltCopy [32]byte
|
|
copy(saltCopy[:i.method.KeySaltLength], salt)
|
|
copy(requestHeader.Bytes()[AESBlockSize:AESBlockSize+i.method.KeySaltLength], saltCopy[:i.method.KeySaltLength])
|
|
requestHeader.Advance(AESBlockSize)
|
|
|
|
if err := link.Writer.WriteMultiBuffer(buf.MultiBuffer{requestHeader}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return TransportTCP(ctx, i.policyManager.ForLevel(targetDest.level), buf.NewReader(conn), buf.NewWriter(conn), link)
|
|
}
|
|
|
|
func (i *RelayInbound) processUDP(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
|
reader := buf.NewPacketReader(conn)
|
|
for {
|
|
mb, err := reader.ReadMultiBuffer()
|
|
if err != nil {
|
|
buf.ReleaseMulti(mb)
|
|
return err
|
|
}
|
|
|
|
for _, b := range mb {
|
|
data := b.Bytes()
|
|
if len(data) < 2*AESBlockSize {
|
|
b.Release()
|
|
continue
|
|
}
|
|
|
|
var packetHeader [AESBlockSize]byte
|
|
i.relayBlock.Decrypt(packetHeader[:], data[:AESBlockSize])
|
|
|
|
eiHeader := DecryptUDPEIH(i.relayBlock, packetHeader[:], data[AESBlockSize:2*AESBlockSize])
|
|
|
|
targetDest, ok := i.destinations[eiHeader]
|
|
if !ok {
|
|
b.Release()
|
|
continue
|
|
}
|
|
|
|
// Extract sessionID from raw packetHeader for session-level link caching before re-encrypting
|
|
sessionID := binary.BigEndian.Uint64(packetHeader[:8])
|
|
|
|
// Re-encrypt packetHeader with next hop block cipher
|
|
targetDest.blockCipher.Encrypt(packetHeader[:], packetHeader[:])
|
|
|
|
// Strip outer EIH: replace second block with re-encrypted packetHeader and advance
|
|
copy(data[AESBlockSize:2*AESBlockSize], packetHeader[:])
|
|
b.Advance(int32(AESBlockSize))
|
|
|
|
dest := targetDest.destination
|
|
dest.Network = net.Network_UDP
|
|
|
|
sessionItem := i.udpSessions.GetOrCreate(sessionID)
|
|
if sessionItem.User == nil {
|
|
sessionItem.Lock()
|
|
if sessionItem.User == nil {
|
|
sessionItem.User = &protocol.MemoryUser{
|
|
Email: targetDest.email,
|
|
Level: targetDest.level,
|
|
}
|
|
}
|
|
sessionItem.Unlock()
|
|
}
|
|
link, err := sessionItem.EnsureLink(ctx, conn, dest, dispatcher, i.policyManager, nil)
|
|
if err != nil {
|
|
b.Release()
|
|
continue
|
|
}
|
|
|
|
_ = link.Writer.WriteMultiBuffer(buf.MultiBuffer{b})
|
|
}
|
|
}
|
|
}
|