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}) } } }