github.com/xtls/xray-core@v1.8.3/app/proxyman/outbound/handler.go (about)

     1  package outbound
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/xtls/xray-core/app/proxyman"
    10  	"github.com/xtls/xray-core/common"
    11  	"github.com/xtls/xray-core/common/mux"
    12  	"github.com/xtls/xray-core/common/net"
    13  	"github.com/xtls/xray-core/common/net/cnc"
    14  	"github.com/xtls/xray-core/common/session"
    15  	"github.com/xtls/xray-core/core"
    16  	"github.com/xtls/xray-core/features/outbound"
    17  	"github.com/xtls/xray-core/features/policy"
    18  	"github.com/xtls/xray-core/features/stats"
    19  	"github.com/xtls/xray-core/proxy"
    20  	"github.com/xtls/xray-core/transport"
    21  	"github.com/xtls/xray-core/transport/internet"
    22  	"github.com/xtls/xray-core/transport/internet/stat"
    23  	"github.com/xtls/xray-core/transport/internet/tls"
    24  	"github.com/xtls/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  	xudp            *mux.ClientManager
    61  	udp443          string
    62  	uplinkCounter   stats.Counter
    63  	downlinkCounter stats.Counter
    64  }
    65  
    66  // NewHandler creates a new Handler based on the given configuration.
    67  func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbound.Handler, error) {
    68  	v := core.MustFromContext(ctx)
    69  	uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
    70  	h := &Handler{
    71  		tag:             config.Tag,
    72  		outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
    73  		uplinkCounter:   uplinkCounter,
    74  		downlinkCounter: downlinkCounter,
    75  	}
    76  
    77  	if config.SenderSettings != nil {
    78  		senderSettings, err := config.SenderSettings.GetInstance()
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  		switch s := senderSettings.(type) {
    83  		case *proxyman.SenderConfig:
    84  			h.senderSettings = s
    85  			mss, err := internet.ToMemoryStreamConfig(s.StreamSettings)
    86  			if err != nil {
    87  				return nil, newError("failed to parse stream settings").Base(err).AtWarning()
    88  			}
    89  			h.streamSettings = mss
    90  		default:
    91  			return nil, newError("settings is not SenderConfig")
    92  		}
    93  	}
    94  
    95  	proxyConfig, err := config.ProxySettings.GetInstance()
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	proxyHandler, ok := rawProxyHandler.(proxy.Outbound)
   106  	if !ok {
   107  		return nil, newError("not an outbound handler")
   108  	}
   109  
   110  	if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil {
   111  		if config := h.senderSettings.MultiplexSettings; config.Enabled {
   112  			if config.Concurrency < 0 {
   113  				h.mux = &mux.ClientManager{Enabled: false}
   114  			}
   115  			if config.Concurrency == 0 {
   116  				config.Concurrency = 8 // same as before
   117  			}
   118  			if config.Concurrency > 0 {
   119  				h.mux = &mux.ClientManager{
   120  					Enabled: true,
   121  					Picker: &mux.IncrementalWorkerPicker{
   122  						Factory: &mux.DialingWorkerFactory{
   123  							Proxy:  proxyHandler,
   124  							Dialer: h,
   125  							Strategy: mux.ClientStrategy{
   126  								MaxConcurrency: uint32(config.Concurrency),
   127  								MaxConnection:  128,
   128  							},
   129  						},
   130  					},
   131  				}
   132  			}
   133  			if config.XudpConcurrency < 0 {
   134  				h.xudp = &mux.ClientManager{Enabled: false}
   135  			}
   136  			if config.XudpConcurrency == 0 {
   137  				h.xudp = nil // same as before
   138  			}
   139  			if config.XudpConcurrency > 0 {
   140  				h.xudp = &mux.ClientManager{
   141  					Enabled: true,
   142  					Picker: &mux.IncrementalWorkerPicker{
   143  						Factory: &mux.DialingWorkerFactory{
   144  							Proxy:  proxyHandler,
   145  							Dialer: h,
   146  							Strategy: mux.ClientStrategy{
   147  								MaxConcurrency: uint32(config.XudpConcurrency),
   148  								MaxConnection:  128,
   149  							},
   150  						},
   151  					},
   152  				}
   153  			}
   154  			h.udp443 = config.XudpProxyUDP443
   155  		}
   156  	}
   157  
   158  	h.proxy = proxyHandler
   159  	return h, nil
   160  }
   161  
   162  // Tag implements outbound.Handler.
   163  func (h *Handler) Tag() string {
   164  	return h.tag
   165  }
   166  
   167  // Dispatch implements proxy.Outbound.Dispatch.
   168  func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
   169  	if h.mux != nil {
   170  		test := func(err error) {
   171  			if err != nil {
   172  				err := newError("failed to process mux outbound traffic").Base(err)
   173  				session.SubmitOutboundErrorToOriginator(ctx, err)
   174  				err.WriteToLog(session.ExportIDToError(ctx))
   175  				common.Interrupt(link.Writer)
   176  			}
   177  		}
   178  		outbound := session.OutboundFromContext(ctx)
   179  		if outbound.Target.Network == net.Network_UDP && outbound.Target.Port == 443 {
   180  			switch h.udp443 {
   181  			case "reject":
   182  				test(newError("XUDP rejected UDP/443 traffic").AtInfo())
   183  				return
   184  			case "skip":
   185  				goto out
   186  			}
   187  		}
   188  		if h.xudp != nil && outbound.Target.Network == net.Network_UDP {
   189  			if !h.xudp.Enabled {
   190  				goto out
   191  			}
   192  			test(h.xudp.Dispatch(ctx, link))
   193  			return
   194  		}
   195  		if h.mux.Enabled {
   196  			test(h.mux.Dispatch(ctx, link))
   197  			return
   198  		}
   199  	}
   200  out:
   201  	err := h.proxy.Process(ctx, link, h)
   202  	if err != nil {
   203  		if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, context.Canceled) {
   204  			err = nil
   205  		}
   206  	}
   207  	if err != nil {
   208  		// Ensure outbound ray is properly closed.
   209  		err := newError("failed to process outbound traffic").Base(err)
   210  		session.SubmitOutboundErrorToOriginator(ctx, err)
   211  		err.WriteToLog(session.ExportIDToError(ctx))
   212  		common.Interrupt(link.Writer)
   213  	} else {
   214  		common.Close(link.Writer)
   215  	}
   216  	common.Interrupt(link.Reader)
   217  }
   218  
   219  // Address implements internet.Dialer.
   220  func (h *Handler) Address() net.Address {
   221  	if h.senderSettings == nil || h.senderSettings.Via == nil {
   222  		return nil
   223  	}
   224  	return h.senderSettings.Via.AsAddress()
   225  }
   226  
   227  // Dial implements internet.Dialer.
   228  func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
   229  	if h.senderSettings != nil {
   230  		if h.senderSettings.ProxySettings.HasTag() {
   231  			tag := h.senderSettings.ProxySettings.Tag
   232  			handler := h.outboundManager.GetHandler(tag)
   233  			if handler != nil {
   234  				newError("proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog(session.ExportIDToError(ctx))
   235  				ctx = session.ContextWithOutbound(ctx, &session.Outbound{
   236  					Target: dest,
   237  				})
   238  
   239  				opts := pipe.OptionsFromContext(ctx)
   240  				uplinkReader, uplinkWriter := pipe.New(opts...)
   241  				downlinkReader, downlinkWriter := pipe.New(opts...)
   242  
   243  				go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
   244  				conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
   245  
   246  				if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
   247  					tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
   248  					conn = tls.Client(conn, tlsConfig)
   249  				}
   250  
   251  				return h.getStatCouterConnection(conn), nil
   252  			}
   253  
   254  			newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
   255  		}
   256  
   257  		if h.senderSettings.Via != nil {
   258  			outbound := session.OutboundFromContext(ctx)
   259  			if outbound == nil {
   260  				outbound = new(session.Outbound)
   261  				ctx = session.ContextWithOutbound(ctx, outbound)
   262  			}
   263  			outbound.Gateway = h.senderSettings.Via.AsAddress()
   264  		}
   265  	}
   266  
   267  	if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
   268  		return conn, err
   269  	}
   270  
   271  	conn, err := internet.Dial(ctx, dest, h.streamSettings)
   272  	return h.getStatCouterConnection(conn), err
   273  }
   274  
   275  func (h *Handler) getStatCouterConnection(conn stat.Connection) stat.Connection {
   276  	if h.uplinkCounter != nil || h.downlinkCounter != nil {
   277  		return &stat.CounterConnection{
   278  			Connection:   conn,
   279  			ReadCounter:  h.downlinkCounter,
   280  			WriteCounter: h.uplinkCounter,
   281  		}
   282  	}
   283  	return conn
   284  }
   285  
   286  // GetOutbound implements proxy.GetOutbound.
   287  func (h *Handler) GetOutbound() proxy.Outbound {
   288  	return h.proxy
   289  }
   290  
   291  // Start implements common.Runnable.
   292  func (h *Handler) Start() error {
   293  	return nil
   294  }
   295  
   296  // Close implements common.Closable.
   297  func (h *Handler) Close() error {
   298  	common.Close(h.mux)
   299  	return nil
   300  }