github.com/moqsien/xraycore@v1.8.5/transport/internet/grpc/dial.go (about) 1 package grpc 2 3 import ( 4 "context" 5 gonet "net" 6 "sync" 7 "time" 8 9 "github.com/moqsien/xraycore/common" 10 "github.com/moqsien/xraycore/common/net" 11 "github.com/moqsien/xraycore/common/session" 12 "github.com/moqsien/xraycore/transport/internet" 13 "github.com/moqsien/xraycore/transport/internet/grpc/encoding" 14 "github.com/moqsien/xraycore/transport/internet/reality" 15 "github.com/moqsien/xraycore/transport/internet/stat" 16 "github.com/moqsien/xraycore/transport/internet/tls" 17 "google.golang.org/grpc" 18 "google.golang.org/grpc/backoff" 19 "google.golang.org/grpc/connectivity" 20 "google.golang.org/grpc/credentials" 21 "google.golang.org/grpc/keepalive" 22 ) 23 24 func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) { 25 newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx)) 26 27 conn, err := dialgRPC(ctx, dest, streamSettings) 28 if err != nil { 29 return nil, newError("failed to dial gRPC").Base(err) 30 } 31 return stat.Connection(conn), nil 32 } 33 34 func init() { 35 common.Must(internet.RegisterTransportDialer(protocolName, Dial)) 36 } 37 38 type dialerConf struct { 39 net.Destination 40 *internet.MemoryStreamConfig 41 } 42 43 var ( 44 globalDialerMap map[dialerConf]*grpc.ClientConn 45 globalDialerAccess sync.Mutex 46 ) 47 48 func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { 49 grpcSettings := streamSettings.ProtocolSettings.(*Config) 50 51 conn, err := getGrpcClient(ctx, dest, streamSettings) 52 if err != nil { 53 return nil, newError("Cannot dial gRPC").Base(err) 54 } 55 client := encoding.NewGRPCServiceClient(conn) 56 if grpcSettings.MultiMode { 57 newError("using gRPC multi mode service name: `" + grpcSettings.getServiceName() + "` stream name: `" + grpcSettings.getTunMultiStreamName() + "`").AtDebug().WriteToLog() 58 grpcService, err := client.(encoding.GRPCServiceClientX).TunMultiCustomName(ctx, grpcSettings.getServiceName(), grpcSettings.getTunMultiStreamName()) 59 if err != nil { 60 return nil, newError("Cannot dial gRPC").Base(err) 61 } 62 return encoding.NewMultiHunkConn(grpcService, nil), nil 63 } 64 65 newError("using gRPC tun mode service name: `" + grpcSettings.getServiceName() + "` stream name: `" + grpcSettings.getTunStreamName() + "`").AtDebug().WriteToLog() 66 grpcService, err := client.(encoding.GRPCServiceClientX).TunCustomName(ctx, grpcSettings.getServiceName(), grpcSettings.getTunStreamName()) 67 if err != nil { 68 return nil, newError("Cannot dial gRPC").Base(err) 69 } 70 71 return encoding.NewHunkConn(grpcService, nil), nil 72 } 73 74 func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) { 75 globalDialerAccess.Lock() 76 defer globalDialerAccess.Unlock() 77 78 if globalDialerMap == nil { 79 globalDialerMap = make(map[dialerConf]*grpc.ClientConn) 80 } 81 tlsConfig := tls.ConfigFromStreamSettings(streamSettings) 82 realityConfig := reality.ConfigFromStreamSettings(streamSettings) 83 sockopt := streamSettings.SocketSettings 84 grpcSettings := streamSettings.ProtocolSettings.(*Config) 85 86 if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown { 87 return client, nil 88 } 89 90 dialOptions := []grpc.DialOption{ 91 grpc.WithConnectParams(grpc.ConnectParams{ 92 Backoff: backoff.Config{ 93 BaseDelay: 500 * time.Millisecond, 94 Multiplier: 1.5, 95 Jitter: 0.2, 96 MaxDelay: 19 * time.Second, 97 }, 98 MinConnectTimeout: 5 * time.Second, 99 }), 100 grpc.WithContextDialer(func(gctx context.Context, s string) (gonet.Conn, error) { 101 select { 102 case <-gctx.Done(): 103 return nil, gctx.Err() 104 default: 105 } 106 107 rawHost, rawPort, err := net.SplitHostPort(s) 108 if err != nil { 109 return nil, err 110 } 111 if len(rawPort) == 0 { 112 rawPort = "443" 113 } 114 port, err := net.PortFromString(rawPort) 115 if err != nil { 116 return nil, err 117 } 118 address := net.ParseAddress(rawHost) 119 120 gctx = session.ContextWithID(gctx, session.IDFromContext(ctx)) 121 gctx = session.ContextWithOutbound(gctx, session.OutboundFromContext(ctx)) 122 gctx = session.ContextWithTimeoutOnly(gctx, true) 123 124 c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) 125 if err == nil && realityConfig != nil { 126 return reality.UClient(c, realityConfig, gctx, dest) 127 } 128 return c, err 129 }), 130 } 131 132 if tlsConfig != nil { 133 var transportCredential credentials.TransportCredentials 134 if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { 135 transportCredential = tls.NewGrpcUtls(tlsConfig.GetTLSConfig(), fingerprint) 136 } else { // Fallback to normal gRPC TLS 137 transportCredential = credentials.NewTLS(tlsConfig.GetTLSConfig()) 138 } 139 dialOptions = append(dialOptions, grpc.WithTransportCredentials(transportCredential)) 140 } else { 141 dialOptions = append(dialOptions, grpc.WithInsecure()) 142 } 143 144 if grpcSettings.IdleTimeout > 0 || grpcSettings.HealthCheckTimeout > 0 || grpcSettings.PermitWithoutStream { 145 dialOptions = append(dialOptions, grpc.WithKeepaliveParams(keepalive.ClientParameters{ 146 Time: time.Second * time.Duration(grpcSettings.IdleTimeout), 147 Timeout: time.Second * time.Duration(grpcSettings.HealthCheckTimeout), 148 PermitWithoutStream: grpcSettings.PermitWithoutStream, 149 })) 150 } 151 152 if grpcSettings.InitialWindowsSize > 0 { 153 dialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize)) 154 } 155 156 if grpcSettings.UserAgent != "" { 157 dialOptions = append(dialOptions, grpc.WithUserAgent(grpcSettings.UserAgent)) 158 } 159 160 var grpcDestHost string 161 if dest.Address.Family().IsDomain() { 162 grpcDestHost = dest.Address.Domain() 163 } else { 164 grpcDestHost = dest.Address.IP().String() 165 } 166 167 conn, err := grpc.Dial( 168 gonet.JoinHostPort(grpcDestHost, dest.Port.String()), 169 dialOptions..., 170 ) 171 globalDialerMap[dialerConf{dest, streamSettings}] = conn 172 return conn, err 173 }