github.com/sagernet/sing-box@v1.9.0-rc.20/transport/v2raygrpclite/client.go (about)

     1  package v2raygrpclite
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net"
     7  	"net/http"
     8  	"net/url"
     9  	"time"
    10  
    11  	"github.com/sagernet/sing-box/adapter"
    12  	"github.com/sagernet/sing-box/common/tls"
    13  	"github.com/sagernet/sing-box/option"
    14  	"github.com/sagernet/sing-box/transport/v2rayhttp"
    15  	E "github.com/sagernet/sing/common/exceptions"
    16  	M "github.com/sagernet/sing/common/metadata"
    17  	N "github.com/sagernet/sing/common/network"
    18  
    19  	"golang.org/x/net/http2"
    20  )
    21  
    22  var _ adapter.V2RayClientTransport = (*Client)(nil)
    23  
    24  var defaultClientHeader = http.Header{
    25  	"Content-Type": []string{"application/grpc"},
    26  	"User-Agent":   []string{"grpc-go/1.48.0"},
    27  	"TE":           []string{"trailers"},
    28  }
    29  
    30  type Client struct {
    31  	ctx        context.Context
    32  	dialer     N.Dialer
    33  	serverAddr M.Socksaddr
    34  	transport  *http2.Transport
    35  	options    option.V2RayGRPCOptions
    36  	url        *url.URL
    37  	host       string
    38  }
    39  
    40  func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
    41  	var host string
    42  	if tlsConfig != nil && tlsConfig.ServerName() != "" {
    43  		host = M.ParseSocksaddrHostPort(tlsConfig.ServerName(), serverAddr.Port).String()
    44  	} else {
    45  		host = serverAddr.String()
    46  	}
    47  	client := &Client{
    48  		ctx:        ctx,
    49  		dialer:     dialer,
    50  		serverAddr: serverAddr,
    51  		options:    options,
    52  		transport: &http2.Transport{
    53  			ReadIdleTimeout:    time.Duration(options.IdleTimeout),
    54  			PingTimeout:        time.Duration(options.PingTimeout),
    55  			DisableCompression: true,
    56  		},
    57  		url: &url.URL{
    58  			Scheme:  "https",
    59  			Host:    serverAddr.String(),
    60  			Path:    "/" + options.ServiceName + "/Tun",
    61  			RawPath: "/" + url.PathEscape(options.ServiceName) + "/Tun",
    62  		},
    63  		host: host,
    64  	}
    65  
    66  	if tlsConfig == nil {
    67  		client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
    68  			return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
    69  		}
    70  	} else {
    71  		if len(tlsConfig.NextProtos()) == 0 {
    72  			tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
    73  		}
    74  		client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
    75  			conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
    76  			if err != nil {
    77  				return nil, err
    78  			}
    79  			return tls.ClientHandshake(ctx, conn, tlsConfig)
    80  		}
    81  	}
    82  
    83  	return client
    84  }
    85  
    86  func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
    87  	pipeInReader, pipeInWriter := io.Pipe()
    88  	request := &http.Request{
    89  		Method: http.MethodPost,
    90  		Body:   pipeInReader,
    91  		URL:    c.url,
    92  		Header: defaultClientHeader,
    93  		Host:   c.host,
    94  	}
    95  	request = request.WithContext(ctx)
    96  	conn := newLateGunConn(pipeInWriter)
    97  	go func() {
    98  		response, err := c.transport.RoundTrip(request)
    99  		if err != nil {
   100  			conn.setup(nil, err)
   101  		} else if response.StatusCode != 200 {
   102  			response.Body.Close()
   103  			conn.setup(nil, E.New("unexpected status: ", response.Status))
   104  		} else {
   105  			conn.setup(response.Body, nil)
   106  		}
   107  	}()
   108  	return conn, nil
   109  }
   110  
   111  func (c *Client) Close() error {
   112  	if c.transport != nil {
   113  		v2rayhttp.CloseIdleConnections(c.transport)
   114  	}
   115  	return nil
   116  }