package xdrive import ( "context" "io" "net/http" "sync" "time" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/tls" ) var errStalled = errors.New("the request made no progress") type serviceTransport struct { plain http.RoundTripper secure http.RoundTripper stall time.Duration } // RoundTrip ends a request that stands still for t.stall, with no answer that // long after its body went out or no byte of either body, but not one that just // runs long: a large object on a slow link takes minutes and is fine as long as // its bytes move func (t *serviceTransport) RoundTrip(r *http.Request) (*http.Response, error) { next := t.plain if r.URL.Scheme == "https" { next = t.secure } ctx, cancel := context.WithCancelCause(r.Context()) timer := time.AfterFunc(t.stall, func() { cancel(errStalled) }) alive := func() { timer.Reset(t.stall) } end := func() { timer.Stop() cancel(nil) } r = r.WithContext(ctx) if r.Body != nil && r.Body != http.NoBody { r.Body = &progressBody{ReadCloser: r.Body, alive: alive} } // a body sent again by the transport itself, after a GOAWAY or on a reused // connection that failed, moves just the same if getBody := r.GetBody; getBody != nil { r.GetBody = func() (io.ReadCloser, error) { body, err := getBody() if err != nil || body == http.NoBody { return body, err } return &progressBody{ReadCloser: body, alive: alive}, nil } } resp, err := next.RoundTrip(r) if err != nil { err = stalled(ctx, err) end() return nil, err } alive() resp.Body = &progressBody{ReadCloser: resp.Body, alive: alive, end: end, ctx: ctx} return resp, nil } // stalled returns errStalled for an error that the stall gave, which http2 reports // as a plain cancel func stalled(ctx context.Context, err error) error { if context.Cause(ctx) == errStalled { return errStalled } return err } type progressBody struct { io.ReadCloser alive func() end func() // ctx is that of the request, for the body of its response ctx context.Context } func (b *progressBody) Read(p []byte) (int, error) { // http2 reads a request body up to 512 KiB at a time and sends it all before // the next read, which on a slow link could look like no progress n, err := b.ReadCloser.Read(p[:min(len(p), 16<<10)]) if n > 0 { b.alive() } if err != nil && err != io.EOF && b.ctx != nil { err = stalled(b.ctx, err) } return n, err } func (b *progressBody) Close() error { err := b.ReadCloser.Close() if b.end != nil { b.end() } return err } // newServiceClient returns a client whose requests end after stall without // progress, see serviceTransport.RoundTrip func newServiceClient(streamSettings *internet.MemoryStreamConfig, stall time.Duration, maxConns int) *http.Client { var ( tlsConfig *tls.Config sockopt *internet.SocketConfig fronting *net.Destination ) if streamSettings != nil { tlsConfig = tls.ConfigFromStreamSettings(streamSettings) sockopt = streamSettings.SocketSettings fronting = streamSettings.Destination } if tlsConfig == nil { tlsConfig = &tls.Config{} } fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint) dial := func(ctx context.Context, addr string) (net.Conn, net.Destination, error) { host, err := net.ParseDestination("tcp:" + addr) if err != nil { return nil, host, errors.New("bad address: ", addr).Base(err) } target := host if fronting != nil { target.Address = fronting.Address if fronting.Port != 0 { target.Port = fronting.Port } } var conn net.Conn if streamSettings.FinalMask != nil { conn, err = streamSettings.FinalMask.DialTCP(ctx, target) } else { conn, err = internet.DialSystem(ctx, target, sockopt) } if err != nil { return nil, host, errors.New("failed to dial to dest").Base(err) } return conn, host, nil } dialPlain := func(ctx context.Context, network, addr string) (net.Conn, error) { conn, _, err := dial(ctx, addr) return conn, err } // http.Transport picks h2 or h1 by the ALPN in the ConnectionState of the conn dialTLS := func(ctx context.Context, network, addr string) (net.Conn, error) { conn, host, err := dial(ctx, addr) if err != nil { return nil, err } gotlsConfig := tlsConfig.GetTLSConfig(tls.WithDestination(host)) var tlsConn net.Conn if fingerprint == nil { tlsConn = tls.Client(conn, gotlsConfig) } else { tlsConn = tls.UClient(conn, gotlsConfig, fingerprint) } // the dial outlives the request that started it, and a handshake that hangs // would keep the one connection of the host to itself ctx, cancel := context.WithTimeout(ctx, stall) defer cancel() if err := tlsConn.(tls.Interface).HandshakeContext(ctx); err != nil { conn.Close() return nil, err } return tlsConn, nil } secure := func(connsPerHost int) *http.Transport { return &http.Transport{ DialTLSContext: dialTLS, ForceAttemptHTTP2: true, IdleConnTimeout: net.ConnIdleTimeout, MaxIdleConns: maxConns, MaxIdleConnsPerHost: maxConns, MaxConnsPerHost: connsPerHost, HTTP2: &http.HTTP2Config{SendPingTimeout: net.ChromeH2KeepAlivePeriod}, } } return &http.Client{ Transport: &serviceTransport{ plain: &http.Transport{ DialContext: dialPlain, IdleConnTimeout: net.ConnIdleTimeout, MaxIdleConns: maxConns, MaxIdleConnsPerHost: maxConns, MaxConnsPerHost: maxConns, }, secure: &secureTransport{h2: secure(1), h1: secure(maxConns)}, stall: stall, }, } } // secureTransport keeps a host to one connection, all that h2 needs, as without // a limit parallel requests would each dial their own. A host that answers over // h1 moves to a transport with more, since h1 carries one request at a time type secureTransport struct { h2, h1 http.RoundTripper h1Hosts sync.Map } func (t *secureTransport) RoundTrip(r *http.Request) (*http.Response, error) { if _, ok := t.h1Hosts.Load(r.URL.Host); ok { return t.h1.RoundTrip(r) } resp, err := t.h2.RoundTrip(r) if err == nil && resp.ProtoMajor == 1 { t.h1Hosts.Store(r.URL.Host, true) } return resp, err }