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  }