// Package proxy contains all proxies used by Xray. // // To implement an inbound or outbound proxy, one needs to do the following: // 1. Implement the interface(s) below. // 2. Register a config creator through common.RegisterConfig. package proxy import ( "context" gotls "crypto/tls" "io" "runtime" "github.com/pires/go-proxyproto" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" "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/signal" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" ) // An Inbound processes inbound connections. type Inbound interface { // Network returns a list of networks that this inbound supports. Connections with not-supported networks will not be passed into Process(). Network() []net.Network // Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound. Process(context.Context, net.Network, stat.Connection, routing.Dispatcher) error } // An Outbound process outbound connections. type Outbound interface { // Process processes the given connection. The given dialer may be used to dial a system outbound connection. Process(context.Context, *transport.Link, internet.Dialer) error } // UserManager is the interface for Inbounds and Outbounds that can manage their users. type UserManager interface { // AddUser adds a new user. AddUser(context.Context, *protocol.MemoryUser) error // RemoveUser removes a user by email. RemoveUser(context.Context, string) error } type GetInbound interface { GetInbound() Inbound } type GetOutbound interface { GetOutbound() Outbound } // UnwrapRawConn support unwrap stats, tls, utls, reality and proxyproto conn and get raw tcp conn from it func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { var readCounter, writerCounter stats.Counter if conn != nil { statConn, ok := conn.(*stat.CounterConnection) if ok { conn = statConn.Connection readCounter = statConn.ReadCounter writerCounter = statConn.WriteCounter } if xc, ok := conn.(*gotls.Conn); ok { conn = xc.NetConn() } else if utlsConn, ok := conn.(*tls.UConn); ok { conn = utlsConn.NetConn() } else if realityConn, ok := conn.(*reality.Conn); ok { conn = realityConn.NetConn() } else if realityUConn, ok := conn.(*reality.UConn); ok { conn = realityUConn.NetConn() } if pc, ok := conn.(*proxyproto.Conn); ok { conn = pc.Raw() // 8192 > 4096, there is no need to process pc's bufReader } } return conn, readCounter, writerCounter } // CopyRawConnIfExist use the most efficient copy method. // - If caller don't want to turn on splice, do not pass in both reader conn and writer conn // - writer are from *transport.Link func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net.Conn, writer buf.Writer, timer signal.ActivityUpdater) error { readerConn, readCounter, _ := UnwrapRawConn(readerConn) writerConn, _, writeCounter := UnwrapRawConn(writerConn) reader := buf.NewReader(readerConn) if inbound := session.InboundFromContext(ctx); inbound != nil { if tc, ok := writerConn.(*net.TCPConn); ok && readerConn != nil && writerConn != nil && (runtime.GOOS == "linux" || runtime.GOOS == "android") { for inbound.CanSpliceCopy != 3 { if inbound.CanSpliceCopy == 1 { newError("CopyRawConn splice").WriteToLog(session.ExportIDToError(ctx)) runtime.Gosched() // necessary w, err := tc.ReadFrom(readerConn) if readCounter != nil { readCounter.Add(w) } if writeCounter != nil { writeCounter.Add(w) } if err != nil && errors.Cause(err) != io.EOF { return err } return nil } buffer, err := reader.ReadMultiBuffer() if !buffer.IsEmpty() { if readCounter != nil { readCounter.Add(int64(buffer.Len())) } timer.Update() if werr := writer.WriteMultiBuffer(buffer); werr != nil { return werr } } if err != nil { return err } } } } newError("CopyRawConn readv").WriteToLog(session.ExportIDToError(ctx)) if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil { return newError("failed to process response").Base(err) } return nil }