client.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package v2rayhttp
  2. import (
  3. "context"
  4. "io"
  5. "math/rand"
  6. "net"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "time"
  11. "github.com/sagernet/sing-box/adapter"
  12. "github.com/sagernet/sing-box/common/tls"
  13. "github.com/sagernet/sing-box/option"
  14. E "github.com/sagernet/sing/common/exceptions"
  15. M "github.com/sagernet/sing/common/metadata"
  16. N "github.com/sagernet/sing/common/network"
  17. sHTTP "github.com/sagernet/sing/protocol/http"
  18. "golang.org/x/net/http2"
  19. )
  20. var _ adapter.V2RayClientTransport = (*Client)(nil)
  21. type Client struct {
  22. ctx context.Context
  23. dialer N.Dialer
  24. serverAddr M.Socksaddr
  25. transport http.RoundTripper
  26. http2 bool
  27. requestURL url.URL
  28. host []string
  29. method string
  30. headers http.Header
  31. }
  32. func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
  33. var transport http.RoundTripper
  34. if tlsConfig == nil {
  35. transport = &http.Transport{
  36. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  37. return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
  38. },
  39. }
  40. } else {
  41. if len(tlsConfig.NextProtos()) == 0 {
  42. tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
  43. }
  44. transport = &http2.Transport{
  45. ReadIdleTimeout: time.Duration(options.IdleTimeout),
  46. PingTimeout: time.Duration(options.PingTimeout),
  47. DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
  48. conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
  49. if err != nil {
  50. return nil, err
  51. }
  52. return tls.ClientHandshake(ctx, conn, tlsConfig)
  53. },
  54. }
  55. }
  56. if options.Method == "" {
  57. options.Method = http.MethodPut
  58. }
  59. var requestURL url.URL
  60. if tlsConfig == nil {
  61. requestURL.Scheme = "http"
  62. } else {
  63. requestURL.Scheme = "https"
  64. }
  65. requestURL.Host = serverAddr.String()
  66. requestURL.Path = options.Path
  67. err := sHTTP.URLSetPath(&requestURL, options.Path)
  68. if err != nil {
  69. return nil, E.Cause(err, "parse path")
  70. }
  71. if !strings.HasPrefix(requestURL.Path, "/") {
  72. requestURL.Path = "/" + requestURL.Path
  73. }
  74. return &Client{
  75. ctx: ctx,
  76. dialer: dialer,
  77. serverAddr: serverAddr,
  78. requestURL: requestURL,
  79. host: options.Host,
  80. method: options.Method,
  81. headers: options.Headers.Build(),
  82. transport: transport,
  83. http2: tlsConfig != nil,
  84. }, nil
  85. }
  86. func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
  87. if !c.http2 {
  88. return c.dialHTTP(ctx)
  89. } else {
  90. return c.dialHTTP2(ctx)
  91. }
  92. }
  93. func (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {
  94. conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
  95. if err != nil {
  96. return nil, err
  97. }
  98. request := &http.Request{
  99. Method: c.method,
  100. URL: &c.requestURL,
  101. Header: c.headers.Clone(),
  102. }
  103. switch hostLen := len(c.host); hostLen {
  104. case 0:
  105. request.Host = c.serverAddr.AddrString()
  106. case 1:
  107. request.Host = c.host[0]
  108. default:
  109. request.Host = c.host[rand.Intn(hostLen)]
  110. }
  111. return NewHTTP1Conn(conn, request), nil
  112. }
  113. func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
  114. pipeInReader, pipeInWriter := io.Pipe()
  115. request := &http.Request{
  116. Method: c.method,
  117. Body: pipeInReader,
  118. URL: &c.requestURL,
  119. Header: c.headers.Clone(),
  120. }
  121. request = request.WithContext(ctx)
  122. switch hostLen := len(c.host); hostLen {
  123. case 0:
  124. // https://github.com/v2fly/v2ray-core/blob/master/transport/internet/http/config.go#L13
  125. request.Host = "www.example.com"
  126. case 1:
  127. request.Host = c.host[0]
  128. default:
  129. request.Host = c.host[rand.Intn(hostLen)]
  130. }
  131. conn := NewLateHTTPConn(pipeInWriter)
  132. go func() {
  133. response, err := c.transport.RoundTrip(request)
  134. if err != nil {
  135. conn.Setup(nil, err)
  136. } else if response.StatusCode != 200 {
  137. response.Body.Close()
  138. conn.Setup(nil, E.New("v2ray-http: unexpected status: ", response.Status))
  139. } else {
  140. conn.Setup(response.Body, nil)
  141. }
  142. }()
  143. return conn, nil
  144. }
  145. func (c *Client) Close() error {
  146. c.transport = ResetTransport(c.transport)
  147. return nil
  148. }