github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/transport/internet/grpc/dial.go (about)

     1  //go:build !confonly
     2  // +build !confonly
     3  
     4  package grpc
     5  
     6  import (
     7  	"context"
     8  	gonet "net"
     9  	"sync"
    10  	"time"
    11  
    12  	"google.golang.org/grpc"
    13  	"google.golang.org/grpc/backoff"
    14  	"google.golang.org/grpc/connectivity"
    15  	"google.golang.org/grpc/credentials"
    16  
    17  	core "github.com/v2fly/v2ray-core/v5"
    18  	"github.com/v2fly/v2ray-core/v5/common"
    19  	"github.com/v2fly/v2ray-core/v5/common/net"
    20  	"github.com/v2fly/v2ray-core/v5/common/session"
    21  	"github.com/v2fly/v2ray-core/v5/transport/internet"
    22  	"github.com/v2fly/v2ray-core/v5/transport/internet/grpc/encoding"
    23  	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
    24  )
    25  
    26  func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
    27  	newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx))
    28  
    29  	conn, err := dialgRPC(ctx, dest, streamSettings)
    30  	if err != nil {
    31  		return nil, newError("failed to dial Grpc").Base(err)
    32  	}
    33  	return internet.Connection(conn), nil
    34  }
    35  
    36  func init() {
    37  	common.Must(internet.RegisterTransportDialer(protocolName, Dial))
    38  }
    39  
    40  type dialerCanceller func()
    41  
    42  var (
    43  	globalDialerMap    map[net.Destination]*grpc.ClientConn
    44  	globalDialerAccess sync.Mutex
    45  )
    46  
    47  func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
    48  	grpcSettings := streamSettings.ProtocolSettings.(*Config)
    49  
    50  	config := tls.ConfigFromStreamSettings(streamSettings)
    51  	dialOption := grpc.WithInsecure()
    52  
    53  	if config != nil {
    54  		dialOption = grpc.WithTransportCredentials(credentials.NewTLS(config.GetTLSConfig()))
    55  	}
    56  
    57  	conn, canceller, err := getGrpcClient(ctx, dest, dialOption, streamSettings)
    58  	if err != nil {
    59  		return nil, newError("Cannot dial grpc").Base(err)
    60  	}
    61  	client := encoding.NewGunServiceClient(conn)
    62  	gunService, err := client.(encoding.GunServiceClientX).TunCustomName(ctx, grpcSettings.ServiceName)
    63  	if err != nil {
    64  		canceller()
    65  		return nil, newError("Cannot dial grpc").Base(err)
    66  	}
    67  	return encoding.NewGunConn(gunService, nil), nil
    68  }
    69  
    70  func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.DialOption, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, dialerCanceller, error) {
    71  	globalDialerAccess.Lock()
    72  	defer globalDialerAccess.Unlock()
    73  
    74  	if globalDialerMap == nil {
    75  		globalDialerMap = make(map[net.Destination]*grpc.ClientConn)
    76  	}
    77  
    78  	canceller := func() {
    79  		globalDialerAccess.Lock()
    80  		defer globalDialerAccess.Unlock()
    81  		delete(globalDialerMap, dest)
    82  	}
    83  
    84  	// TODO Should support chain proxy to the same destination
    85  	if client, found := globalDialerMap[dest]; found && client.GetState() != connectivity.Shutdown {
    86  		return client, canceller, nil
    87  	}
    88  
    89  	conn, err := grpc.Dial(
    90  		dest.Address.String()+":"+dest.Port.String(),
    91  		dialOption,
    92  		grpc.WithConnectParams(grpc.ConnectParams{
    93  			Backoff: backoff.Config{
    94  				BaseDelay:  500 * time.Millisecond,
    95  				Multiplier: 1.5,
    96  				Jitter:     0.2,
    97  				MaxDelay:   19 * time.Second,
    98  			},
    99  			MinConnectTimeout: 5 * time.Second,
   100  		}),
   101  		grpc.WithContextDialer(func(ctxGrpc context.Context, s string) (gonet.Conn, error) {
   102  			rawHost, rawPort, err := net.SplitHostPort(s)
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  			if len(rawPort) == 0 {
   107  				rawPort = "443"
   108  			}
   109  			port, err := net.PortFromString(rawPort)
   110  			if err != nil {
   111  				return nil, err
   112  			}
   113  			address := net.ParseAddress(rawHost)
   114  			detachedContext := core.ToBackgroundDetachedContext(ctx)
   115  			return internet.DialSystem(detachedContext, net.TCPDestination(address, port), streamSettings.SocketSettings)
   116  		}),
   117  	)
   118  	globalDialerMap[dest] = conn
   119  	return conn, canceller, err
   120  }