mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-10-19 10:13:37 -04:00
861d6be19a
It is important to use a detached context when creating shared transport connections that will outlive the initializing connection. Otherwise, once the original initializing connection is cancelled, the transport connection will be cancelled as well. This issue will be addressed in v5 by providing a transport session storage that also reduce the usage of globalDialerMap and other global variables.
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
// +build !confonly
|
|
|
|
package grpc
|
|
|
|
import (
|
|
"context"
|
|
core "github.com/v2fly/v2ray-core/v4"
|
|
gonet "net"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/backoff"
|
|
"google.golang.org/grpc/connectivity"
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"github.com/v2fly/v2ray-core/v4/common"
|
|
"github.com/v2fly/v2ray-core/v4/common/net"
|
|
"github.com/v2fly/v2ray-core/v4/common/session"
|
|
"github.com/v2fly/v2ray-core/v4/transport/internet"
|
|
"github.com/v2fly/v2ray-core/v4/transport/internet/grpc/encoding"
|
|
"github.com/v2fly/v2ray-core/v4/transport/internet/tls"
|
|
)
|
|
|
|
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
|
|
newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx))
|
|
|
|
conn, err := dialgRPC(ctx, dest, streamSettings)
|
|
if err != nil {
|
|
return nil, newError("failed to dial Grpc").Base(err)
|
|
}
|
|
return internet.Connection(conn), nil
|
|
}
|
|
|
|
func init() {
|
|
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
|
|
}
|
|
|
|
var (
|
|
globalDialerMap map[net.Destination]*grpc.ClientConn
|
|
globalDialerAccess sync.Mutex
|
|
)
|
|
|
|
func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
|
|
grpcSettings := streamSettings.ProtocolSettings.(*Config)
|
|
|
|
config := tls.ConfigFromStreamSettings(streamSettings)
|
|
dialOption := grpc.WithInsecure()
|
|
|
|
if config != nil {
|
|
dialOption = grpc.WithTransportCredentials(credentials.NewTLS(config.GetTLSConfig()))
|
|
}
|
|
|
|
conn, err := getGrpcClient(ctx, dest, dialOption)
|
|
if err != nil {
|
|
return nil, newError("Cannot dial grpc").Base(err)
|
|
}
|
|
client := encoding.NewGunServiceClient(conn)
|
|
gunService, err := client.(encoding.GunServiceClientX).TunCustomName(ctx, grpcSettings.ServiceName)
|
|
if err != nil {
|
|
return nil, newError("Cannot dial grpc").Base(err)
|
|
}
|
|
return encoding.NewGunConn(gunService, nil), nil
|
|
}
|
|
|
|
func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.DialOption) (*grpc.ClientConn, error) {
|
|
globalDialerAccess.Lock()
|
|
defer globalDialerAccess.Unlock()
|
|
|
|
if globalDialerMap == nil {
|
|
globalDialerMap = make(map[net.Destination]*grpc.ClientConn)
|
|
}
|
|
|
|
// TODO Should support chain proxy to the same destination
|
|
if client, found := globalDialerMap[dest]; found && client.GetState() != connectivity.Shutdown {
|
|
return client, nil
|
|
}
|
|
|
|
conn, err := grpc.Dial(
|
|
dest.Address.String()+":"+dest.Port.String(),
|
|
dialOption,
|
|
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,
|
|
}),
|
|
grpc.WithContextDialer(func(ctxGrpc context.Context, s string) (gonet.Conn, error) {
|
|
rawHost, rawPort, err := net.SplitHostPort(s)
|
|
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)
|
|
detachedContext := core.ToBackgroundDetachedContext(ctx)
|
|
return internet.DialSystem(detachedContext, net.TCPDestination(address, port), nil)
|
|
}),
|
|
)
|
|
globalDialerMap[dest] = conn
|
|
return conn, err
|
|
}
|