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