github.com/sagernet/sing-box@v1.9.0-rc.20/transport/v2raygrpc/client.go (about) 1 package v2raygrpc 2 3 import ( 4 "context" 5 "net" 6 "sync" 7 "time" 8 9 "github.com/sagernet/sing-box/adapter" 10 "github.com/sagernet/sing-box/common/tls" 11 "github.com/sagernet/sing-box/option" 12 "github.com/sagernet/sing/common" 13 M "github.com/sagernet/sing/common/metadata" 14 N "github.com/sagernet/sing/common/network" 15 16 "golang.org/x/net/http2" 17 "google.golang.org/grpc" 18 "google.golang.org/grpc/backoff" 19 "google.golang.org/grpc/connectivity" 20 "google.golang.org/grpc/credentials/insecure" 21 "google.golang.org/grpc/keepalive" 22 ) 23 24 var _ adapter.V2RayClientTransport = (*Client)(nil) 25 26 type Client struct { 27 ctx context.Context 28 dialer N.Dialer 29 serverAddr string 30 serviceName string 31 dialOptions []grpc.DialOption 32 conn *grpc.ClientConn 33 connAccess sync.Mutex 34 } 35 36 func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { 37 var dialOptions []grpc.DialOption 38 if tlsConfig != nil { 39 if len(tlsConfig.NextProtos()) == 0 { 40 tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) 41 } 42 dialOptions = append(dialOptions, grpc.WithTransportCredentials(NewTLSTransportCredentials(tlsConfig))) 43 } else { 44 dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) 45 } 46 if options.IdleTimeout > 0 { 47 dialOptions = append(dialOptions, grpc.WithKeepaliveParams(keepalive.ClientParameters{ 48 Time: time.Duration(options.IdleTimeout), 49 Timeout: time.Duration(options.PingTimeout), 50 PermitWithoutStream: options.PermitWithoutStream, 51 })) 52 } 53 dialOptions = append(dialOptions, grpc.WithConnectParams(grpc.ConnectParams{ 54 Backoff: backoff.Config{ 55 BaseDelay: 500 * time.Millisecond, 56 Multiplier: 1.5, 57 Jitter: 0.2, 58 MaxDelay: 19 * time.Second, 59 }, 60 MinConnectTimeout: 5 * time.Second, 61 })) 62 dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, server string) (net.Conn, error) { 63 return dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(server)) 64 })) 65 dialOptions = append(dialOptions, grpc.WithReturnConnectionError()) 66 return &Client{ 67 ctx: ctx, 68 dialer: dialer, 69 serverAddr: serverAddr.String(), 70 serviceName: options.ServiceName, 71 dialOptions: dialOptions, 72 }, nil 73 } 74 75 func (c *Client) Close() error { 76 return common.Close( 77 common.PtrOrNil(c.conn), 78 ) 79 } 80 81 func (c *Client) connect() (*grpc.ClientConn, error) { 82 conn := c.conn 83 if conn != nil && conn.GetState() != connectivity.Shutdown { 84 return conn, nil 85 } 86 c.connAccess.Lock() 87 defer c.connAccess.Unlock() 88 conn = c.conn 89 if conn != nil && conn.GetState() != connectivity.Shutdown { 90 return conn, nil 91 } 92 //nolint:staticcheck 93 //goland:noinspection GoDeprecation 94 conn, err := grpc.DialContext(c.ctx, c.serverAddr, c.dialOptions...) 95 if err != nil { 96 return nil, err 97 } 98 c.conn = conn 99 return conn, nil 100 } 101 102 func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { 103 clientConn, err := c.connect() 104 if err != nil { 105 return nil, err 106 } 107 client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient) 108 ctx, cancel := common.ContextWithCancelCause(ctx) 109 stream, err := client.TunCustomName(ctx, c.serviceName) 110 if err != nil { 111 cancel(err) 112 return nil, err 113 } 114 return NewGRPCConn(stream, cancel), nil 115 }