1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-10-19 10:13:37 -04:00
v2fly/transport/internet/grpc/dial.go
Shelikhoo 861d6be19a
use detached context to fix #1059 #1059
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.
2021-06-22 12:20:27 +01:00

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
}