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 }