github.com/v2fly/v2ray-core/v4@v4.45.2/proxy/http/client.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  	"net/url"
    13  	"sync"
    14  
    15  	"golang.org/x/net/http2"
    16  
    17  	core "github.com/v2fly/v2ray-core/v4"
    18  	"github.com/v2fly/v2ray-core/v4/common"
    19  	"github.com/v2fly/v2ray-core/v4/common/buf"
    20  	"github.com/v2fly/v2ray-core/v4/common/bytespool"
    21  	"github.com/v2fly/v2ray-core/v4/common/net"
    22  	"github.com/v2fly/v2ray-core/v4/common/protocol"
    23  	"github.com/v2fly/v2ray-core/v4/common/retry"
    24  	"github.com/v2fly/v2ray-core/v4/common/session"
    25  	"github.com/v2fly/v2ray-core/v4/common/signal"
    26  	"github.com/v2fly/v2ray-core/v4/common/task"
    27  	"github.com/v2fly/v2ray-core/v4/features/policy"
    28  	"github.com/v2fly/v2ray-core/v4/transport"
    29  	"github.com/v2fly/v2ray-core/v4/transport/internet"
    30  	"github.com/v2fly/v2ray-core/v4/transport/internet/tls"
    31  )
    32  
    33  type Client struct {
    34  	serverPicker  protocol.ServerPicker
    35  	policyManager policy.Manager
    36  }
    37  
    38  type h2Conn struct {
    39  	rawConn net.Conn
    40  	h2Conn  *http2.ClientConn
    41  }
    42  
    43  var (
    44  	cachedH2Mutex sync.Mutex
    45  	cachedH2Conns map[net.Destination]h2Conn
    46  )
    47  
    48  // NewClient create a new http client based on the given config.
    49  func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
    50  	serverList := protocol.NewServerList()
    51  	for _, rec := range config.Server {
    52  		s, err := protocol.NewServerSpecFromPB(rec)
    53  		if err != nil {
    54  			return nil, newError("failed to get server spec").Base(err)
    55  		}
    56  		serverList.AddServer(s)
    57  	}
    58  	if serverList.Size() == 0 {
    59  		return nil, newError("0 target server")
    60  	}
    61  
    62  	v := core.MustFromContext(ctx)
    63  	return &Client{
    64  		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
    65  		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
    66  	}, nil
    67  }
    68  
    69  // Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel.
    70  func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
    71  	outbound := session.OutboundFromContext(ctx)
    72  	if outbound == nil || !outbound.Target.IsValid() {
    73  		return newError("target not specified.")
    74  	}
    75  	target := outbound.Target
    76  	targetAddr := target.NetAddr()
    77  
    78  	if target.Network == net.Network_UDP {
    79  		return newError("UDP is not supported by HTTP outbound")
    80  	}
    81  
    82  	var user *protocol.MemoryUser
    83  	var conn internet.Connection
    84  
    85  	mbuf, _ := link.Reader.ReadMultiBuffer()
    86  	len := mbuf.Len()
    87  	firstPayload := bytespool.Alloc(len)
    88  	mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
    89  	firstPayload = firstPayload[:len]
    90  
    91  	buf.ReleaseMulti(mbuf)
    92  	defer bytespool.Free(firstPayload)
    93  
    94  	if err := retry.ExponentialBackoff(5, 100).On(func() error {
    95  		server := c.serverPicker.PickServer()
    96  		dest := server.Destination()
    97  		user = server.PickUser()
    98  
    99  		netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload)
   100  		if netConn != nil {
   101  			if _, ok := netConn.(*http2Conn); !ok {
   102  				if _, err := netConn.Write(firstPayload); err != nil {
   103  					netConn.Close()
   104  					return err
   105  				}
   106  			}
   107  			conn = internet.Connection(netConn)
   108  		}
   109  		return err
   110  	}); err != nil {
   111  		return newError("failed to find an available destination").Base(err)
   112  	}
   113  
   114  	defer func() {
   115  		if err := conn.Close(); err != nil {
   116  			newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
   117  		}
   118  	}()
   119  
   120  	p := c.policyManager.ForLevel(0)
   121  	if user != nil {
   122  		p = c.policyManager.ForLevel(user.Level)
   123  	}
   124  
   125  	ctx, cancel := context.WithCancel(ctx)
   126  	timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle)
   127  
   128  	requestFunc := func() error {
   129  		defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
   130  		return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
   131  	}
   132  	responseFunc := func() error {
   133  		defer timer.SetTimeout(p.Timeouts.UplinkOnly)
   134  		return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
   135  	}
   136  
   137  	responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))
   138  	if err := task.Run(ctx, requestFunc, responseDonePost); err != nil {
   139  		return newError("connection ends").Base(err)
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method
   146  func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) {
   147  	req := &http.Request{
   148  		Method: http.MethodConnect,
   149  		URL:    &url.URL{Host: target},
   150  		Header: make(http.Header),
   151  		Host:   target,
   152  	}
   153  
   154  	if user != nil && user.Account != nil {
   155  		account := user.Account.(*Account)
   156  		auth := account.GetUsername() + ":" + account.GetPassword()
   157  		req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
   158  	}
   159  
   160  	connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
   161  		req.Header.Set("Proxy-Connection", "Keep-Alive")
   162  
   163  		err := req.Write(rawConn)
   164  		if err != nil {
   165  			rawConn.Close()
   166  			return nil, err
   167  		}
   168  
   169  		resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
   170  		if err != nil {
   171  			rawConn.Close()
   172  			return nil, err
   173  		}
   174  		defer resp.Body.Close()
   175  
   176  		if resp.StatusCode != http.StatusOK {
   177  			rawConn.Close()
   178  			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
   179  		}
   180  		return rawConn, nil
   181  	}
   182  
   183  	connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) {
   184  		pr, pw := io.Pipe()
   185  		req.Body = pr
   186  
   187  		var pErr error
   188  		var wg sync.WaitGroup
   189  		wg.Add(1)
   190  
   191  		go func() {
   192  			_, pErr = pw.Write(firstPayload)
   193  			wg.Done()
   194  		}()
   195  
   196  		resp, err := h2clientConn.RoundTrip(req) // nolint: bodyclose
   197  		if err != nil {
   198  			rawConn.Close()
   199  			return nil, err
   200  		}
   201  
   202  		wg.Wait()
   203  		if pErr != nil {
   204  			rawConn.Close()
   205  			return nil, pErr
   206  		}
   207  
   208  		if resp.StatusCode != http.StatusOK {
   209  			rawConn.Close()
   210  			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
   211  		}
   212  		return newHTTP2Conn(rawConn, pw, resp.Body), nil
   213  	}
   214  
   215  	cachedH2Mutex.Lock()
   216  	cachedConn, cachedConnFound := cachedH2Conns[dest]
   217  	cachedH2Mutex.Unlock()
   218  
   219  	if cachedConnFound {
   220  		rc, cc := cachedConn.rawConn, cachedConn.h2Conn
   221  		if cc.CanTakeNewRequest() {
   222  			proxyConn, err := connectHTTP2(rc, cc)
   223  			if err != nil {
   224  				return nil, err
   225  			}
   226  
   227  			return proxyConn, nil
   228  		}
   229  	}
   230  
   231  	rawConn, err := dialer.Dial(ctx, dest)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	iConn := rawConn
   237  	if statConn, ok := iConn.(*internet.StatCouterConnection); ok {
   238  		iConn = statConn.Connection
   239  	}
   240  
   241  	nextProto := ""
   242  	if tlsConn, ok := iConn.(*tls.Conn); ok {
   243  		if err := tlsConn.Handshake(); err != nil {
   244  			rawConn.Close()
   245  			return nil, err
   246  		}
   247  		nextProto = tlsConn.ConnectionState().NegotiatedProtocol
   248  	}
   249  
   250  	switch nextProto {
   251  	case "", "http/1.1":
   252  		return connectHTTP1(rawConn)
   253  	case "h2":
   254  		t := http2.Transport{}
   255  		h2clientConn, err := t.NewClientConn(rawConn)
   256  		if err != nil {
   257  			rawConn.Close()
   258  			return nil, err
   259  		}
   260  
   261  		proxyConn, err := connectHTTP2(rawConn, h2clientConn)
   262  		if err != nil {
   263  			rawConn.Close()
   264  			return nil, err
   265  		}
   266  
   267  		cachedH2Mutex.Lock()
   268  		if cachedH2Conns == nil {
   269  			cachedH2Conns = make(map[net.Destination]h2Conn)
   270  		}
   271  
   272  		cachedH2Conns[dest] = h2Conn{
   273  			rawConn: rawConn,
   274  			h2Conn:  h2clientConn,
   275  		}
   276  		cachedH2Mutex.Unlock()
   277  
   278  		return proxyConn, err
   279  	default:
   280  		return nil, newError("negotiated unsupported application layer protocol: " + nextProto)
   281  	}
   282  }
   283  
   284  func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn {
   285  	return &http2Conn{Conn: c, in: pipedReqBody, out: respBody}
   286  }
   287  
   288  type http2Conn struct {
   289  	net.Conn
   290  	in  *io.PipeWriter
   291  	out io.ReadCloser
   292  }
   293  
   294  func (h *http2Conn) Read(p []byte) (n int, err error) {
   295  	return h.out.Read(p)
   296  }
   297  
   298  func (h *http2Conn) Write(p []byte) (n int, err error) {
   299  	return h.in.Write(p)
   300  }
   301  
   302  func (h *http2Conn) Close() error {
   303  	h.in.Close()
   304  	return h.out.Close()
   305  }
   306  
   307  func init() {
   308  	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
   309  		return NewClient(ctx, config.(*ClientConfig))
   310  	}))
   311  }