github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/proxy/vmess/outbound/outbound.go (about) 1 package outbound 2 3 //go:generate go run github.com/xtls/xray-core/common/errors/errorgen 4 5 import ( 6 "context" 7 "crypto/hmac" 8 "crypto/sha256" 9 "hash/crc64" 10 "time" 11 12 "github.com/xtls/xray-core/common" 13 "github.com/xtls/xray-core/common/buf" 14 "github.com/xtls/xray-core/common/net" 15 "github.com/xtls/xray-core/common/platform" 16 "github.com/xtls/xray-core/common/protocol" 17 "github.com/xtls/xray-core/common/retry" 18 "github.com/xtls/xray-core/common/session" 19 "github.com/xtls/xray-core/common/signal" 20 "github.com/xtls/xray-core/common/task" 21 "github.com/xtls/xray-core/common/xudp" 22 core "github.com/xtls/xray-core/core" 23 "github.com/xtls/xray-core/features/policy" 24 "github.com/xtls/xray-core/proxy/vmess" 25 "github.com/xtls/xray-core/proxy/vmess/encoding" 26 "github.com/xtls/xray-core/transport" 27 "github.com/xtls/xray-core/transport/internet" 28 "github.com/xtls/xray-core/transport/internet/stat" 29 ) 30 31 // Handler is an outbound connection handler for VMess protocol. 32 type Handler struct { 33 serverList *protocol.ServerList 34 serverPicker protocol.ServerPicker 35 policyManager policy.Manager 36 cone bool 37 } 38 39 // New creates a new VMess outbound handler. 40 func New(ctx context.Context, config *Config) (*Handler, error) { 41 serverList := protocol.NewServerList() 42 for _, rec := range config.Receiver { 43 s, err := protocol.NewServerSpecFromPB(rec) 44 if err != nil { 45 return nil, newError("failed to parse server spec").Base(err) 46 } 47 serverList.AddServer(s) 48 } 49 50 v := core.MustFromContext(ctx) 51 handler := &Handler{ 52 serverList: serverList, 53 serverPicker: protocol.NewRoundRobinServerPicker(serverList), 54 policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), 55 cone: ctx.Value("cone").(bool), 56 } 57 58 return handler, nil 59 } 60 61 // Process implements proxy.Outbound.Process(). 62 func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { 63 outbounds := session.OutboundsFromContext(ctx) 64 ob := outbounds[len(outbounds) - 1] 65 if !ob.Target.IsValid() { 66 return newError("target not specified").AtError() 67 } 68 ob.Name = "vmess" 69 ob.CanSpliceCopy = 3 70 71 var rec *protocol.ServerSpec 72 var conn stat.Connection 73 err := retry.ExponentialBackoff(5, 200).On(func() error { 74 rec = h.serverPicker.PickServer() 75 rawConn, err := dialer.Dial(ctx, rec.Destination()) 76 if err != nil { 77 return err 78 } 79 conn = rawConn 80 81 return nil 82 }) 83 if err != nil { 84 return newError("failed to find an available destination").Base(err).AtWarning() 85 } 86 defer conn.Close() 87 88 target := ob.Target 89 newError("tunneling request to ", target, " via ", rec.Destination().NetAddr()).WriteToLog(session.ExportIDToError(ctx)) 90 91 command := protocol.RequestCommandTCP 92 if target.Network == net.Network_UDP { 93 command = protocol.RequestCommandUDP 94 } 95 if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" { 96 command = protocol.RequestCommandMux 97 } 98 99 user := rec.PickUser() 100 request := &protocol.RequestHeader{ 101 Version: encoding.Version, 102 User: user, 103 Command: command, 104 Address: target.Address, 105 Port: target.Port, 106 Option: protocol.RequestOptionChunkStream, 107 } 108 109 account := request.User.Account.(*vmess.MemoryAccount) 110 request.Security = account.Security 111 112 if request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_NONE || request.Security == protocol.SecurityType_CHACHA20_POLY1305 { 113 request.Option.Set(protocol.RequestOptionChunkMasking) 114 } 115 116 if shouldEnablePadding(request.Security) && request.Option.Has(protocol.RequestOptionChunkMasking) { 117 request.Option.Set(protocol.RequestOptionGlobalPadding) 118 } 119 120 if request.Security == protocol.SecurityType_ZERO { 121 request.Security = protocol.SecurityType_NONE 122 request.Option.Clear(protocol.RequestOptionChunkStream) 123 request.Option.Clear(protocol.RequestOptionChunkMasking) 124 } 125 126 if account.AuthenticatedLengthExperiment { 127 request.Option.Set(protocol.RequestOptionAuthenticatedLength) 128 } 129 130 input := link.Reader 131 output := link.Writer 132 133 hashkdf := hmac.New(sha256.New, []byte("VMessBF")) 134 hashkdf.Write(account.ID.Bytes()) 135 136 behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO)) 137 138 var newCtx context.Context 139 var newCancel context.CancelFunc 140 if session.TimeoutOnlyFromContext(ctx) { 141 newCtx, newCancel = context.WithCancel(context.Background()) 142 } 143 144 session := encoding.NewClientSession(ctx, int64(behaviorSeed)) 145 sessionPolicy := h.policyManager.ForLevel(request.User.Level) 146 147 ctx, cancel := context.WithCancel(ctx) 148 timer := signal.CancelAfterInactivity(ctx, func() { 149 cancel() 150 if newCancel != nil { 151 newCancel() 152 } 153 }, sessionPolicy.Timeouts.ConnectionIdle) 154 155 if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 { 156 request.Command = protocol.RequestCommandMux 157 request.Address = net.DomainAddress("v1.mux.cool") 158 request.Port = net.Port(666) 159 } 160 161 requestDone := func() error { 162 defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) 163 164 writer := buf.NewBufferedWriter(buf.NewWriter(conn)) 165 if err := session.EncodeRequestHeader(request, writer); err != nil { 166 return newError("failed to encode request").Base(err).AtWarning() 167 } 168 169 bodyWriter, err := session.EncodeRequestBody(request, writer) 170 if err != nil { 171 return newError("failed to start encoding").Base(err) 172 } 173 bodyWriter2 := bodyWriter 174 if request.Command == protocol.RequestCommandMux && request.Port == 666 { 175 bodyWriter = xudp.NewPacketWriter(bodyWriter, target, xudp.GetGlobalID(ctx)) 176 } 177 if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { 178 return newError("failed to write first payload").Base(err) 179 } 180 181 if err := writer.SetBuffered(false); err != nil { 182 return err 183 } 184 185 if err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil { 186 return err 187 } 188 189 if request.Option.Has(protocol.RequestOptionChunkStream) && !account.NoTerminationSignal { 190 if err := bodyWriter2.WriteMultiBuffer(buf.MultiBuffer{}); err != nil { 191 return err 192 } 193 } 194 195 return nil 196 } 197 198 responseDone := func() error { 199 defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) 200 201 reader := &buf.BufferedReader{Reader: buf.NewReader(conn)} 202 header, err := session.DecodeResponseHeader(reader) 203 if err != nil { 204 return newError("failed to read header").Base(err) 205 } 206 h.handleCommand(rec.Destination(), header.Command) 207 208 bodyReader, err := session.DecodeResponseBody(request, reader) 209 if err != nil { 210 return newError("failed to start encoding response").Base(err) 211 } 212 if request.Command == protocol.RequestCommandMux && request.Port == 666 { 213 bodyReader = xudp.NewPacketReader(&buf.BufferedReader{Reader: bodyReader}) 214 } 215 216 return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) 217 } 218 219 if newCtx != nil { 220 ctx = newCtx 221 } 222 223 responseDonePost := task.OnSuccess(responseDone, task.Close(output)) 224 if err := task.Run(ctx, requestDone, responseDonePost); err != nil { 225 return newError("connection ends").Base(err) 226 } 227 228 return nil 229 } 230 231 var ( 232 enablePadding = false 233 ) 234 235 func shouldEnablePadding(s protocol.SecurityType) bool { 236 return enablePadding || s == protocol.SecurityType_AES128_GCM || s == protocol.SecurityType_CHACHA20_POLY1305 || s == protocol.SecurityType_AUTO 237 } 238 239 func init() { 240 common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 241 return New(ctx, config.(*Config)) 242 })) 243 244 const defaultFlagValue = "NOT_DEFINED_AT_ALL" 245 246 paddingValue := platform.NewEnvFlag(platform.UseVmessPadding).GetValue(func() string { return defaultFlagValue }) 247 if paddingValue != defaultFlagValue { 248 enablePadding = true 249 } 250 }