github.com/Uhtred009/v2ray-core-1@v4.31.2+incompatible/transport/internet/http/dialer.go (about)

     1  // +build !confonly
     2  
     3  package http
     4  
     5  import (
     6  	"context"
     7  	gotls "crypto/tls"
     8  	"net/http"
     9  	"net/url"
    10  	"sync"
    11  
    12  	"golang.org/x/net/http2"
    13  	"v2ray.com/core/common"
    14  	"v2ray.com/core/common/buf"
    15  	"v2ray.com/core/common/net"
    16  	"v2ray.com/core/transport/internet"
    17  	"v2ray.com/core/transport/internet/tls"
    18  	"v2ray.com/core/transport/pipe"
    19  )
    20  
    21  var (
    22  	globalDialerMap    map[net.Destination]*http.Client
    23  	globalDialerAccess sync.Mutex
    24  )
    25  
    26  func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.Config) (*http.Client, error) {
    27  	globalDialerAccess.Lock()
    28  	defer globalDialerAccess.Unlock()
    29  
    30  	if globalDialerMap == nil {
    31  		globalDialerMap = make(map[net.Destination]*http.Client)
    32  	}
    33  
    34  	if client, found := globalDialerMap[dest]; found {
    35  		return client, nil
    36  	}
    37  
    38  	transport := &http2.Transport{
    39  		DialTLS: func(network string, addr string, tlsConfig *gotls.Config) (net.Conn, error) {
    40  			rawHost, rawPort, err := net.SplitHostPort(addr)
    41  			if err != nil {
    42  				return nil, err
    43  			}
    44  			if len(rawPort) == 0 {
    45  				rawPort = "443"
    46  			}
    47  			port, err := net.PortFromString(rawPort)
    48  			if err != nil {
    49  				return nil, err
    50  			}
    51  			address := net.ParseAddress(rawHost)
    52  
    53  			pconn, err := internet.DialSystem(context.Background(), net.TCPDestination(address, port), nil)
    54  			if err != nil {
    55  				return nil, err
    56  			}
    57  
    58  			cn := gotls.Client(pconn, tlsConfig)
    59  			if err := cn.Handshake(); err != nil {
    60  				return nil, err
    61  			}
    62  			if !tlsConfig.InsecureSkipVerify {
    63  				if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil {
    64  					return nil, err
    65  				}
    66  			}
    67  			state := cn.ConnectionState()
    68  			if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
    69  				return nil, newError("http2: unexpected ALPN protocol " + p + "; want q" + http2.NextProtoTLS).AtError()
    70  			}
    71  			if !state.NegotiatedProtocolIsMutual {
    72  				return nil, newError("http2: could not negotiate protocol mutually").AtError()
    73  			}
    74  			return cn, nil
    75  		},
    76  		TLSClientConfig: tlsSettings.GetTLSConfig(tls.WithDestination(dest)),
    77  	}
    78  
    79  	client := &http.Client{
    80  		Transport: transport,
    81  	}
    82  
    83  	globalDialerMap[dest] = client
    84  	return client, nil
    85  }
    86  
    87  // Dial dials a new TCP connection to the given destination.
    88  func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
    89  	httpSettings := streamSettings.ProtocolSettings.(*Config)
    90  	tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
    91  	if tlsConfig == nil {
    92  		return nil, newError("TLS must be enabled for http transport.").AtWarning()
    93  	}
    94  	client, err := getHTTPClient(ctx, dest, tlsConfig)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	opts := pipe.OptionsFromContext(ctx)
   100  	preader, pwriter := pipe.New(opts...)
   101  	breader := &buf.BufferedReader{Reader: preader}
   102  	request := &http.Request{
   103  		Method: "PUT",
   104  		Host:   httpSettings.getRandomHost(),
   105  		Body:   breader,
   106  		URL: &url.URL{
   107  			Scheme: "https",
   108  			Host:   dest.NetAddr(),
   109  			Path:   httpSettings.getNormalizedPath(),
   110  		},
   111  		Proto:      "HTTP/2",
   112  		ProtoMajor: 2,
   113  		ProtoMinor: 0,
   114  		Header:     make(http.Header),
   115  	}
   116  	// Disable any compression method from server.
   117  	request.Header.Set("Accept-Encoding", "identity")
   118  
   119  	response, err := client.Do(request)
   120  	if err != nil {
   121  		return nil, newError("failed to dial to ", dest).Base(err).AtWarning()
   122  	}
   123  	if response.StatusCode != 200 {
   124  		return nil, newError("unexpected status", response.StatusCode).AtWarning()
   125  	}
   126  
   127  	bwriter := buf.NewBufferedWriter(pwriter)
   128  	common.Must(bwriter.SetBuffered(false))
   129  	return net.NewConnection(
   130  		net.ConnectionOutput(response.Body),
   131  		net.ConnectionInput(bwriter),
   132  		net.ConnectionOnClose(common.ChainedClosable{breader, bwriter, response.Body}),
   133  	), nil
   134  }
   135  
   136  func init() {
   137  	common.Must(internet.RegisterTransportDialer(protocolName, Dial))
   138  }