github.com/v2fly/v2ray-core/v4@v4.45.2/proxy/http/server.go (about) 1 //go:build !confonly 2 // +build !confonly 3 4 package http 5 6 import ( 7 "bufio" 8 "context" 9 "encoding/base64" 10 "io" 11 "net/http" 12 "strings" 13 "time" 14 15 core "github.com/v2fly/v2ray-core/v4" 16 "github.com/v2fly/v2ray-core/v4/common" 17 "github.com/v2fly/v2ray-core/v4/common/buf" 18 "github.com/v2fly/v2ray-core/v4/common/errors" 19 "github.com/v2fly/v2ray-core/v4/common/log" 20 "github.com/v2fly/v2ray-core/v4/common/net" 21 "github.com/v2fly/v2ray-core/v4/common/protocol" 22 http_proto "github.com/v2fly/v2ray-core/v4/common/protocol/http" 23 "github.com/v2fly/v2ray-core/v4/common/session" 24 "github.com/v2fly/v2ray-core/v4/common/signal" 25 "github.com/v2fly/v2ray-core/v4/common/task" 26 "github.com/v2fly/v2ray-core/v4/features/policy" 27 "github.com/v2fly/v2ray-core/v4/features/routing" 28 "github.com/v2fly/v2ray-core/v4/transport/internet" 29 ) 30 31 // Server is an HTTP proxy server. 32 type Server struct { 33 config *ServerConfig 34 policyManager policy.Manager 35 } 36 37 // NewServer creates a new HTTP inbound handler. 38 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { 39 v := core.MustFromContext(ctx) 40 s := &Server{ 41 config: config, 42 policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), 43 } 44 45 return s, nil 46 } 47 48 func (s *Server) policy() policy.Session { 49 config := s.config 50 p := s.policyManager.ForLevel(config.UserLevel) 51 if config.Timeout > 0 && config.UserLevel == 0 { 52 p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second 53 } 54 return p 55 } 56 57 // Network implements proxy.Inbound. 58 func (*Server) Network() []net.Network { 59 return []net.Network{net.Network_TCP, net.Network_UNIX} 60 } 61 62 func isTimeout(err error) bool { 63 nerr, ok := errors.Cause(err).(net.Error) 64 return ok && nerr.Timeout() 65 } 66 67 func parseBasicAuth(auth string) (username, password string, ok bool) { 68 const prefix = "Basic " 69 if !strings.HasPrefix(auth, prefix) { 70 return 71 } 72 c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) 73 if err != nil { 74 return 75 } 76 cs := string(c) 77 s := strings.IndexByte(cs, ':') 78 if s < 0 { 79 return 80 } 81 return cs[:s], cs[s+1:], true 82 } 83 84 type readerOnly struct { 85 io.Reader 86 } 87 88 func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error { 89 inbound := session.InboundFromContext(ctx) 90 if inbound != nil { 91 inbound.User = &protocol.MemoryUser{ 92 Level: s.config.UserLevel, 93 } 94 } 95 96 reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size) 97 98 Start: 99 if err := conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)); err != nil { 100 newError("failed to set read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) 101 } 102 103 request, err := http.ReadRequest(reader) 104 if err != nil { 105 trace := newError("failed to read http request").Base(err) 106 if errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) { 107 trace.AtWarning() 108 } 109 return trace 110 } 111 112 if len(s.config.Accounts) > 0 { 113 user, pass, ok := parseBasicAuth(request.Header.Get("Proxy-Authorization")) 114 if !ok || !s.config.HasAccount(user, pass) { 115 return common.Error2(conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"proxy\"\r\n\r\n"))) 116 } 117 if inbound != nil { 118 inbound.User.Email = user 119 } 120 } 121 122 newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]").WriteToLog(session.ExportIDToError(ctx)) 123 if err := conn.SetReadDeadline(time.Time{}); err != nil { 124 newError("failed to clear read deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) 125 } 126 127 defaultPort := net.Port(80) 128 if strings.EqualFold(request.URL.Scheme, "https") { 129 defaultPort = net.Port(443) 130 } 131 host := request.Host 132 if host == "" { 133 host = request.URL.Host 134 } 135 dest, err := http_proto.ParseHost(host, defaultPort) 136 if err != nil { 137 return newError("malformed proxy host: ", host).AtWarning().Base(err) 138 } 139 ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ 140 From: conn.RemoteAddr(), 141 To: request.URL, 142 Status: log.AccessAccepted, 143 Reason: "", 144 }) 145 146 if strings.EqualFold(request.Method, "CONNECT") { 147 return s.handleConnect(ctx, request, reader, conn, dest, dispatcher) 148 } 149 150 keepAlive := (strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive") 151 152 err = s.handlePlainHTTP(ctx, request, conn, dest, dispatcher) 153 if err == errWaitAnother { 154 if keepAlive { 155 goto Start 156 } 157 err = nil 158 } 159 160 return err 161 } 162 163 func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher routing.Dispatcher) error { 164 _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) 165 if err != nil { 166 return newError("failed to write back OK response").Base(err) 167 } 168 169 plcy := s.policy() 170 ctx, cancel := context.WithCancel(ctx) 171 timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) 172 173 ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer) 174 link, err := dispatcher.Dispatch(ctx, dest) 175 if err != nil { 176 return err 177 } 178 179 if reader.Buffered() > 0 { 180 payload, err := buf.ReadFrom(io.LimitReader(reader, int64(reader.Buffered()))) 181 if err != nil { 182 return err 183 } 184 if err := link.Writer.WriteMultiBuffer(payload); err != nil { 185 return err 186 } 187 reader = nil 188 } 189 190 requestDone := func() error { 191 defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) 192 193 return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) 194 } 195 196 responseDone := func() error { 197 defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) 198 199 v2writer := buf.NewWriter(conn) 200 if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil { 201 return err 202 } 203 204 return nil 205 } 206 207 closeWriter := task.OnSuccess(requestDone, task.Close(link.Writer)) 208 if err := task.Run(ctx, closeWriter, responseDone); err != nil { 209 common.Interrupt(link.Reader) 210 common.Interrupt(link.Writer) 211 return newError("connection ends").Base(err) 212 } 213 214 return nil 215 } 216 217 var errWaitAnother = newError("keep alive") 218 219 func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher) error { 220 if !s.config.AllowTransparent && request.URL.Host == "" { 221 // RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy. 222 response := &http.Response{ 223 Status: "Bad Request", 224 StatusCode: 400, 225 Proto: "HTTP/1.1", 226 ProtoMajor: 1, 227 ProtoMinor: 1, 228 Header: http.Header(make(map[string][]string)), 229 Body: nil, 230 ContentLength: 0, 231 Close: true, 232 } 233 response.Header.Set("Proxy-Connection", "close") 234 response.Header.Set("Connection", "close") 235 return response.Write(writer) 236 } 237 238 if len(request.URL.Host) > 0 { 239 request.Host = request.URL.Host 240 } 241 http_proto.RemoveHopByHopHeaders(request.Header) 242 243 // Prevent UA from being set to golang's default ones 244 if request.Header.Get("User-Agent") == "" { 245 request.Header.Set("User-Agent", "") 246 } 247 248 content := &session.Content{ 249 Protocol: "http/1.1", 250 } 251 252 content.SetAttribute(":method", strings.ToUpper(request.Method)) 253 content.SetAttribute(":path", request.URL.Path) 254 for key := range request.Header { 255 value := request.Header.Get(key) 256 content.SetAttribute(strings.ToLower(key), value) 257 } 258 259 ctx = session.ContextWithContent(ctx, content) 260 261 link, err := dispatcher.Dispatch(ctx, dest) 262 if err != nil { 263 return err 264 } 265 266 // Plain HTTP request is not a stream. The request always finishes before response. Hense request has to be closed later. 267 defer common.Close(link.Writer) 268 var result error = errWaitAnother 269 270 requestDone := func() error { 271 request.Header.Set("Connection", "close") 272 273 requestWriter := buf.NewBufferedWriter(link.Writer) 274 common.Must(requestWriter.SetBuffered(false)) 275 if err := request.Write(requestWriter); err != nil { 276 return newError("failed to write whole request").Base(err).AtWarning() 277 } 278 return nil 279 } 280 281 responseDone := func() error { 282 responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size) 283 response, err := http.ReadResponse(responseReader, request) 284 if err == nil { 285 http_proto.RemoveHopByHopHeaders(response.Header) 286 if response.ContentLength >= 0 { 287 response.Header.Set("Proxy-Connection", "keep-alive") 288 response.Header.Set("Connection", "keep-alive") 289 response.Header.Set("Keep-Alive", "timeout=4") 290 response.Close = false 291 } else { 292 response.Close = true 293 result = nil 294 } 295 defer response.Body.Close() 296 } else { 297 newError("failed to read response from ", request.Host).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) 298 response = &http.Response{ 299 Status: "Service Unavailable", 300 StatusCode: 503, 301 Proto: "HTTP/1.1", 302 ProtoMajor: 1, 303 ProtoMinor: 1, 304 Header: http.Header(make(map[string][]string)), 305 Body: nil, 306 ContentLength: 0, 307 Close: true, 308 } 309 response.Header.Set("Connection", "close") 310 response.Header.Set("Proxy-Connection", "close") 311 } 312 if err := response.Write(writer); err != nil { 313 return newError("failed to write response").Base(err).AtWarning() 314 } 315 return nil 316 } 317 318 if err := task.Run(ctx, requestDone, responseDone); err != nil { 319 common.Interrupt(link.Reader) 320 common.Interrupt(link.Writer) 321 return newError("connection ends").Base(err) 322 } 323 324 return result 325 } 326 327 func init() { 328 common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 329 return NewServer(ctx, config.(*ServerConfig)) 330 })) 331 }