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 }