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 }