github.com/xraypb/Xray-core@v1.8.1/app/proxyman/outbound/handler.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/xraypb/Xray-core/app/proxyman"
    10  	"github.com/xraypb/Xray-core/common"
    11  	"github.com/xraypb/Xray-core/common/mux"
    12  	"github.com/xraypb/Xray-core/common/net"
    13  	"github.com/xraypb/Xray-core/common/net/cnc"
    14  	"github.com/xraypb/Xray-core/common/session"
    15  	"github.com/xraypb/Xray-core/core"
    16  	"github.com/xraypb/Xray-core/features/outbound"
    17  	"github.com/xraypb/Xray-core/features/policy"
    18  	"github.com/xraypb/Xray-core/features/stats"
    19  	"github.com/xraypb/Xray-core/proxy"
    20  	"github.com/xraypb/Xray-core/transport"
    21  	"github.com/xraypb/Xray-core/transport/internet"
    22  	"github.com/xraypb/Xray-core/transport/internet/stat"
    23  	"github.com/xraypb/Xray-core/transport/internet/tls"
    24  	"github.com/xraypb/Xray-core/transport/pipe"
    25  )
    26  
    27  func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
    28  	var uplinkCounter stats.Counter
    29  	var downlinkCounter stats.Counter
    30  
    31  	policy := v.GetFeature(policy.ManagerType()).(policy.Manager)
    32  	if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
    33  		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
    34  		name := "outbound>>>" + tag + ">>>traffic>>>uplink"
    35  		c, _ := stats.GetOrRegisterCounter(statsManager, name)
    36  		if c != nil {
    37  			uplinkCounter = c
    38  		}
    39  	}
    40  	if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
    41  		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
    42  		name := "outbound>>>" + tag + ">>>traffic>>>downlink"
    43  		c, _ := stats.GetOrRegisterCounter(statsManager, name)
    44  		if c != nil {
    45  			downlinkCounter = c
    46  		}
    47  	}
    48  
    49  	return uplinkCounter, downlinkCounter
    50  }
    51  
    52  // Handler is an implements of outbound.Handler.
    53  type Handler struct {
    54  	tag             string
    55  	senderSettings  *proxyman.SenderConfig
    56  	streamSettings  *internet.MemoryStreamConfig
    57  	proxy           proxy.Outbound
    58  	outboundManager outbound.Manager
    59  	mux             *mux.ClientManager
    60  	uplinkCounter   stats.Counter
    61  	downlinkCounter stats.Counter
    62  }
    63  
    64  // NewHandler creates a new Handler based on the given configuration.
    65  func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbound.Handler, error) {
    66  	v := core.MustFromContext(ctx)
    67  	uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
    68  	h := &Handler{
    69  		tag:             config.Tag,
    70  		outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
    71  		uplinkCounter:   uplinkCounter,
    72  		downlinkCounter: downlinkCounter,
    73  	}
    74  
    75  	if config.SenderSettings != nil {
    76  		senderSettings, err := config.SenderSettings.GetInstance()
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  		switch s := senderSettings.(type) {
    81  		case *proxyman.SenderConfig:
    82  			h.senderSettings = s
    83  			mss, err := internet.ToMemoryStreamConfig(s.StreamSettings)
    84  			if err != nil {
    85  				return nil, newError("failed to parse stream settings").Base(err).AtWarning()
    86  			}
    87  			h.streamSettings = mss
    88  		default:
    89  			return nil, newError("settings is not SenderConfig")
    90  		}
    91  	}
    92  
    93  	proxyConfig, err := config.ProxySettings.GetInstance()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	proxyHandler, ok := rawProxyHandler.(proxy.Outbound)
   104  	if !ok {
   105  		return nil, newError("not an outbound handler")
   106  	}
   107  
   108  	if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil {
   109  		config := h.senderSettings.MultiplexSettings
   110  		if config.Concurrency < 1 || config.Concurrency > 1024 {
   111  			return nil, newError("invalid mux concurrency: ", config.Concurrency).AtWarning()
   112  		}
   113  		h.mux = &mux.ClientManager{
   114  			Enabled: config.Enabled,
   115  			Picker: &mux.IncrementalWorkerPicker{
   116  				Factory: &mux.DialingWorkerFactory{
   117  					Proxy:  proxyHandler,
   118  					Dialer: h,
   119  					Strategy: mux.ClientStrategy{
   120  						MaxConcurrency: config.Concurrency,
   121  						MaxConnection:  128,
   122  					},
   123  				},
   124  			},
   125  			Only: config.Only,
   126  		}
   127  	}
   128  
   129  	h.proxy = proxyHandler
   130  	return h, nil
   131  }
   132  
   133  // Tag implements outbound.Handler.
   134  func (h *Handler) Tag() string {
   135  	return h.tag
   136  }
   137  
   138  // Dispatch implements proxy.Outbound.Dispatch.
   139  func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
   140  	outbound := session.OutboundFromContext(ctx)
   141  	if h.mux != nil && (h.mux.Enabled || session.MuxPreferedFromContext(ctx)) &&
   142  		(h.mux.Only == 0 || (outbound != nil && h.mux.Only == uint32(outbound.Target.Network))) {
   143  		if err := h.mux.Dispatch(ctx, link); err != nil {
   144  			err := newError("failed to process mux outbound traffic").Base(err)
   145  			session.SubmitOutboundErrorToOriginator(ctx, err)
   146  			err.WriteToLog(session.ExportIDToError(ctx))
   147  			common.Interrupt(link.Writer)
   148  		}
   149  	} else {
   150  		err := h.proxy.Process(ctx, link, h)
   151  		if err != nil {
   152  			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, context.Canceled) {
   153  				err = nil
   154  			}
   155  		}
   156  		if err != nil {
   157  			// Ensure outbound ray is properly closed.
   158  			err := newError("failed to process outbound traffic").Base(err)
   159  			session.SubmitOutboundErrorToOriginator(ctx, err)
   160  			err.WriteToLog(session.ExportIDToError(ctx))
   161  			common.Interrupt(link.Writer)
   162  		} else {
   163  			common.Must(common.Close(link.Writer))
   164  		}
   165  		common.Interrupt(link.Reader)
   166  	}
   167  }
   168  
   169  // Address implements internet.Dialer.
   170  func (h *Handler) Address() net.Address {
   171  	if h.senderSettings == nil || h.senderSettings.Via == nil {
   172  		return nil
   173  	}
   174  	return h.senderSettings.Via.AsAddress()
   175  }
   176  
   177  // Dial implements internet.Dialer.
   178  func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
   179  	if h.senderSettings != nil {
   180  		if h.senderSettings.ProxySettings.HasTag() {
   181  			tag := h.senderSettings.ProxySettings.Tag
   182  			handler := h.outboundManager.GetHandler(tag)
   183  			if handler != nil {
   184  				newError("proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog(session.ExportIDToError(ctx))
   185  				ctx = session.ContextWithOutbound(ctx, &session.Outbound{
   186  					Target: dest,
   187  				})
   188  
   189  				opts := pipe.OptionsFromContext(ctx)
   190  				uplinkReader, uplinkWriter := pipe.New(opts...)
   191  				downlinkReader, downlinkWriter := pipe.New(opts...)
   192  
   193  				go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
   194  				conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
   195  
   196  				if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
   197  					tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
   198  					conn = tls.Client(conn, tlsConfig)
   199  				}
   200  
   201  				return h.getStatCouterConnection(conn), nil
   202  			}
   203  
   204  			newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
   205  		}
   206  
   207  		if h.senderSettings.Via != nil {
   208  			outbound := session.OutboundFromContext(ctx)
   209  			if outbound == nil {
   210  				outbound = new(session.Outbound)
   211  				ctx = session.ContextWithOutbound(ctx, outbound)
   212  			}
   213  			outbound.Gateway = h.senderSettings.Via.AsAddress()
   214  		}
   215  	}
   216  
   217  	if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
   218  		return conn, err
   219  	}
   220  
   221  	conn, err := internet.Dial(ctx, dest, h.streamSettings)
   222  	return h.getStatCouterConnection(conn), err
   223  }
   224  
   225  func (h *Handler) getStatCouterConnection(conn stat.Connection) stat.Connection {
   226  	if h.uplinkCounter != nil || h.downlinkCounter != nil {
   227  		return &stat.CounterConnection{
   228  			Connection:   conn,
   229  			ReadCounter:  h.downlinkCounter,
   230  			WriteCounter: h.uplinkCounter,
   231  		}
   232  	}
   233  	return conn
   234  }
   235  
   236  // GetOutbound implements proxy.GetOutbound.
   237  func (h *Handler) GetOutbound() proxy.Outbound {
   238  	return h.proxy
   239  }
   240  
   241  // Start implements common.Runnable.
   242  func (h *Handler) Start() error {
   243  	return nil
   244  }
   245  
   246  // Close implements common.Closable.
   247  func (h *Handler) Close() error {
   248  	common.Close(h.mux)
   249  	return nil
   250  }