2021-03-14 15:02:07 +00:00
package grpc
import (
"context"
2026-02-13 19:49:47 +00:00
"reflect"
2021-03-14 15:02:07 +00:00
"sync"
"time"
"github.com/xtls/xray-core/common"
2024-06-29 14:32:57 -04:00
c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors"
2021-03-14 15:02:07 +00:00
"github.com/xtls/xray-core/common/net"
2026-02-06 01:33:30 +00:00
"github.com/xtls/xray-core/common/utils"
2026-03-21 12:24:08 +00:00
"github.com/xtls/xray-core/common/session"
2021-03-14 15:02:07 +00:00
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/grpc/encoding"
2023-02-27 19:52:01 +00:00
"github.com/xtls/xray-core/transport/internet/reality"
2021-09-20 20:11:21 +08:00
"github.com/xtls/xray-core/transport/internet/stat"
2021-03-14 15:02:07 +00:00
"github.com/xtls/xray-core/transport/internet/tls"
2022-05-18 15:29:01 +08:00
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/connectivity"
2024-03-03 23:52:22 +08:00
"google.golang.org/grpc/credentials/insecure"
2022-05-18 15:29:01 +08:00
"google.golang.org/grpc/keepalive"
2021-03-14 15:02:07 +00:00
)
2021-09-20 20:11:21 +08:00
func Dial ( ctx context . Context , dest net . Destination , streamSettings * internet . MemoryStreamConfig ) ( stat . Connection , error ) {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "creating connection to " , dest )
2021-03-14 15:02:07 +00:00
conn , err := dialgRPC ( ctx , dest , streamSettings )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "failed to dial gRPC" ) . Base ( err )
2021-03-14 15:02:07 +00:00
}
2021-09-20 20:11:21 +08:00
return stat . Connection ( conn ) , nil
2021-03-14 15:02:07 +00:00
}
func init ( ) {
common . Must ( internet . RegisterTransportDialer ( protocolName , Dial ) )
}
type dialerConf struct {
net . Destination
2021-07-03 16:01:59 +08:00
* internet . MemoryStreamConfig
2021-03-14 15:02:07 +00:00
}
var (
globalDialerMap map [ dialerConf ] * grpc . ClientConn
globalDialerAccess sync . Mutex
)
func dialgRPC ( ctx context . Context , dest net . Destination , streamSettings * internet . MemoryStreamConfig ) ( net . Conn , error ) {
grpcSettings := streamSettings . ProtocolSettings . ( * Config )
2021-10-16 09:07:45 -04:00
conn , err := getGrpcClient ( ctx , dest , streamSettings )
2021-03-14 15:02:07 +00:00
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Cannot dial gRPC" ) . Base ( err )
2021-03-14 15:02:07 +00:00
}
client := encoding . NewGRPCServiceClient ( conn )
if grpcSettings . MultiMode {
2024-11-09 14:16:11 +03:00
errors . LogDebug ( ctx , "using gRPC multi mode service name: `" + grpcSettings . getServiceName ( ) + "` stream name: `" + grpcSettings . getTunMultiStreamName ( ) + "`" )
2023-03-26 09:28:19 +03:30
grpcService , err := client . ( encoding . GRPCServiceClientX ) . TunMultiCustomName ( ctx , grpcSettings . getServiceName ( ) , grpcSettings . getTunMultiStreamName ( ) )
2021-03-14 15:02:07 +00:00
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Cannot dial gRPC" ) . Base ( err )
2021-03-14 15:02:07 +00:00
}
return encoding . NewMultiHunkConn ( grpcService , nil ) , nil
}
2024-11-09 14:16:11 +03:00
errors . LogDebug ( ctx , "using gRPC tun mode service name: `" + grpcSettings . getServiceName ( ) + "` stream name: `" + grpcSettings . getTunStreamName ( ) + "`" )
2023-03-26 09:28:19 +03:30
grpcService , err := client . ( encoding . GRPCServiceClientX ) . TunCustomName ( ctx , grpcSettings . getServiceName ( ) , grpcSettings . getTunStreamName ( ) )
2021-03-14 15:02:07 +00:00
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Cannot dial gRPC" ) . Base ( err )
2021-03-14 15:02:07 +00:00
}
return encoding . NewHunkConn ( grpcService , nil ) , nil
}
2021-10-16 09:07:45 -04:00
func getGrpcClient ( ctx context . Context , dest net . Destination , streamSettings * internet . MemoryStreamConfig ) ( * grpc . ClientConn , error ) {
2021-03-14 15:02:07 +00:00
globalDialerAccess . Lock ( )
defer globalDialerAccess . Unlock ( )
if globalDialerMap == nil {
globalDialerMap = make ( map [ dialerConf ] * grpc . ClientConn )
}
2021-07-03 16:01:59 +08:00
tlsConfig := tls . ConfigFromStreamSettings ( streamSettings )
2023-02-27 19:52:01 +00:00
realityConfig := reality . ConfigFromStreamSettings ( streamSettings )
2021-07-03 16:01:59 +08:00
sockopt := streamSettings . SocketSettings
grpcSettings := streamSettings . ProtocolSettings . ( * Config )
2021-03-14 15:02:07 +00:00
2021-07-03 16:01:59 +08:00
if client , found := globalDialerMap [ dialerConf { dest , streamSettings } ] ; found && client . GetState ( ) != connectivity . Shutdown {
2021-10-16 09:07:45 -04:00
return client , nil
2021-03-14 15:02:07 +00:00
}
2021-10-19 12:57:14 -04:00
dialOptions := [ ] grpc . DialOption {
2021-03-14 15:02:07 +00:00
grpc . WithConnectParams ( grpc . ConnectParams {
Backoff : backoff . Config {
BaseDelay : 500 * time . Millisecond ,
Multiplier : 1.5 ,
Jitter : 0.2 ,
MaxDelay : 19 * time . Second ,
} ,
MinConnectTimeout : 5 * time . Second ,
} ) ,
2025-12-10 15:17:29 +08:00
grpc . WithContextDialer ( func ( gctx context . Context , s string ) ( net . Conn , error ) {
2021-03-14 15:02:07 +00:00
select {
case <- gctx . Done ( ) :
return nil , gctx . Err ( )
default :
}
2023-08-27 05:55:58 +00:00
rawHost , rawPort , err := net . SplitHostPort ( s )
2021-03-14 15:02:07 +00:00
if err != nil {
return nil , err
}
if len ( rawPort ) == 0 {
rawPort = "443"
}
port , err := net . PortFromString ( rawPort )
if err != nil {
return nil , err
}
address := net . ParseAddress ( rawHost )
2023-08-27 05:55:58 +00:00
2024-06-29 14:32:57 -04:00
gctx = c . ContextWithID ( gctx , c . IDFromContext ( ctx ) )
2024-05-13 21:52:24 -04:00
gctx = session . ContextWithOutbounds ( gctx , session . OutboundsFromContext ( ctx ) )
2023-08-27 05:55:58 +00:00
gctx = session . ContextWithTimeoutOnly ( gctx , true )
2023-02-27 19:52:01 +00:00
c , err := internet . DialSystem ( gctx , net . TCPDestination ( address , port ) , sockopt )
2024-02-29 14:22:14 +00:00
if err == nil {
2026-03-07 23:42:18 +08:00
if streamSettings . TcpmaskManager != nil {
newConn , err := streamSettings . TcpmaskManager . WrapConnClient ( c )
if err != nil {
c . Close ( )
return nil , errors . New ( "mask err" ) . Base ( err )
}
c = newConn
}
2024-02-29 14:22:14 +00:00
if tlsConfig != nil {
config := tlsConfig . GetTLSConfig ( )
if config . ServerName == "" && address . Family ( ) . IsDomain ( ) {
config . ServerName = address . Domain ( )
}
if fingerprint := tls . GetFingerprint ( tlsConfig . Fingerprint ) ; fingerprint != nil {
return tls . UClient ( c , config , fingerprint ) , nil
} else { // Fallback to normal gRPC TLS
return tls . Client ( c , config ) , nil
}
}
if realityConfig != nil {
return reality . UClient ( c , realityConfig , gctx , dest )
}
2023-02-27 19:52:01 +00:00
}
return c , err
2021-03-14 15:02:07 +00:00
} ) ,
2021-07-03 16:01:59 +08:00
}
2024-03-03 23:52:22 +08:00
dialOptions = append ( dialOptions , grpc . WithTransportCredentials ( insecure . NewCredentials ( ) ) )
2024-02-29 14:22:14 +00:00
authority := ""
if grpcSettings . Authority != "" {
authority = grpcSettings . Authority
} else if tlsConfig != nil && tlsConfig . ServerName != "" {
authority = tlsConfig . ServerName
} else if realityConfig == nil && dest . Address . Family ( ) . IsDomain ( ) {
authority = dest . Address . Domain ( )
2021-07-03 16:01:59 +08:00
}
2024-02-29 14:22:14 +00:00
dialOptions = append ( dialOptions , grpc . WithAuthority ( authority ) )
2021-07-03 16:01:59 +08:00
2021-07-05 21:25:21 +08:00
if grpcSettings . IdleTimeout > 0 || grpcSettings . HealthCheckTimeout > 0 || grpcSettings . PermitWithoutStream {
2021-07-03 16:01:59 +08:00
dialOptions = append ( dialOptions , grpc . WithKeepaliveParams ( keepalive . ClientParameters {
2021-07-05 21:25:21 +08:00
Time : time . Second * time . Duration ( grpcSettings . IdleTimeout ) ,
Timeout : time . Second * time . Duration ( grpcSettings . HealthCheckTimeout ) ,
PermitWithoutStream : grpcSettings . PermitWithoutStream ,
2021-07-03 16:01:59 +08:00
} ) )
}
2021-12-20 00:47:21 +08:00
if grpcSettings . InitialWindowsSize > 0 {
dialOptions = append ( dialOptions , grpc . WithInitialWindowSize ( grpcSettings . InitialWindowsSize ) )
}
2021-07-03 16:01:59 +08:00
var grpcDestHost string
if dest . Address . Family ( ) . IsDomain ( ) {
grpcDestHost = dest . Address . Domain ( )
} else {
grpcDestHost = dest . Address . IP ( ) . String ( )
}
2026-02-13 19:49:47 +00:00
conn , err := grpc . NewClient (
"passthrough:///" + net . JoinHostPort ( grpcDestHost , dest . Port . String ( ) ) ,
2021-07-03 16:01:59 +08:00
dialOptions ... ,
2021-03-14 15:02:07 +00:00
)
2026-02-13 19:49:47 +00:00
if err == nil {
userAgent := grpcSettings . UserAgent
2026-03-21 12:24:08 +00:00
// It's NOT recommended to set the UA of gRPC connections to that of real browsers, as they are fundamentally incapable of initiating real gRPC connections.
switch userAgent {
case "chrome" , "" :
2026-02-13 19:49:47 +00:00
userAgent = utils . ChromeUA
2026-03-21 12:24:08 +00:00
case "firefox" :
userAgent = utils . FirefoxUA
case "edge" :
userAgent = utils . MSEdgeUA
case "golang" :
userAgent = ""
2026-02-13 19:49:47 +00:00
}
setUserAgent ( conn , userAgent )
conn . Connect ( )
}
2021-07-03 16:01:59 +08:00
globalDialerMap [ dialerConf { dest , streamSettings } ] = conn
2021-10-16 09:07:45 -04:00
return conn , err
2021-03-14 15:02:07 +00:00
}
2026-02-13 19:49:47 +00:00
// setUserAgent overrides the user-agent on a ClientConn to remove the
// "grpc-go/version" suffix that grpc.WithUserAgent unconditionally appends.
func setUserAgent ( conn * grpc . ClientConn , ua string ) {
if f := reflect . ValueOf ( conn ) . Elem ( ) . FieldByName ( "dopts" ) . FieldByName ( "copts" ) . FieldByName ( "UserAgent" ) ; f . IsValid ( ) {
* ( * string ) ( f . Addr ( ) . UnsafePointer ( ) ) = ua
}
}