github.com/xraypb/Xray-core@v1.8.1/transport/internet/dialer.go (about)

     1  package internet
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/xraypb/Xray-core/common"
     7  	"github.com/xraypb/Xray-core/common/dice"
     8  	"github.com/xraypb/Xray-core/common/net"
     9  	"github.com/xraypb/Xray-core/common/net/cnc"
    10  	"github.com/xraypb/Xray-core/common/session"
    11  	"github.com/xraypb/Xray-core/features/dns"
    12  	"github.com/xraypb/Xray-core/features/outbound"
    13  	"github.com/xraypb/Xray-core/transport"
    14  	"github.com/xraypb/Xray-core/transport/internet/stat"
    15  	"github.com/xraypb/Xray-core/transport/pipe"
    16  )
    17  
    18  // Dialer is the interface for dialing outbound connections.
    19  type Dialer interface {
    20  	// Dial dials a system connection to the given destination.
    21  	Dial(ctx context.Context, destination net.Destination) (stat.Connection, error)
    22  
    23  	// Address returns the address used by this Dialer. Maybe nil if not known.
    24  	Address() net.Address
    25  }
    26  
    27  // dialFunc is an interface to dial network connection to a specific destination.
    28  type dialFunc func(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error)
    29  
    30  var transportDialerCache = make(map[string]dialFunc)
    31  
    32  // RegisterTransportDialer registers a Dialer with given name.
    33  func RegisterTransportDialer(protocol string, dialer dialFunc) error {
    34  	if _, found := transportDialerCache[protocol]; found {
    35  		return newError(protocol, " dialer already registered").AtError()
    36  	}
    37  	transportDialerCache[protocol] = dialer
    38  	return nil
    39  }
    40  
    41  // Dial dials a internet connection towards the given destination.
    42  func Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error) {
    43  	if dest.Network == net.Network_TCP {
    44  		if streamSettings == nil {
    45  			s, err := ToMemoryStreamConfig(nil)
    46  			if err != nil {
    47  				return nil, newError("failed to create default stream settings").Base(err)
    48  			}
    49  			streamSettings = s
    50  		}
    51  
    52  		protocol := streamSettings.ProtocolName
    53  		dialer := transportDialerCache[protocol]
    54  		if dialer == nil {
    55  			return nil, newError(protocol, " dialer not registered").AtError()
    56  		}
    57  		return dialer(ctx, dest, streamSettings)
    58  	}
    59  
    60  	if dest.Network == net.Network_UDP {
    61  		udpDialer := transportDialerCache["udp"]
    62  		if udpDialer == nil {
    63  			return nil, newError("UDP dialer not registered").AtError()
    64  		}
    65  		return udpDialer(ctx, dest, streamSettings)
    66  	}
    67  
    68  	return nil, newError("unknown network ", dest.Network)
    69  }
    70  
    71  var (
    72  	dnsClient dns.Client
    73  	obm       outbound.Manager
    74  )
    75  
    76  func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {
    77  	if dnsClient == nil {
    78  		return nil, nil
    79  	}
    80  
    81  	option := dns.IPOption{
    82  		IPv4Enable: true,
    83  		IPv6Enable: true,
    84  		FakeEnable: false,
    85  	}
    86  
    87  	switch {
    88  	case strategy == DomainStrategy_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()):
    89  		option = dns.IPOption{
    90  			IPv4Enable: true,
    91  			IPv6Enable: false,
    92  			FakeEnable: false,
    93  		}
    94  	case strategy == DomainStrategy_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()):
    95  		option = dns.IPOption{
    96  			IPv4Enable: false,
    97  			IPv6Enable: true,
    98  			FakeEnable: false,
    99  		}
   100  	case strategy == DomainStrategy_AS_IS:
   101  		return nil, nil
   102  	}
   103  
   104  	return dnsClient.LookupIP(domain, option)
   105  }
   106  
   107  func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
   108  	if dst.Address.Family().IsIP() || dnsClient == nil {
   109  		return false
   110  	}
   111  	return sockopt.DomainStrategy != DomainStrategy_AS_IS
   112  }
   113  
   114  func redirect(ctx context.Context, dst net.Destination, obt string) net.Conn {
   115  	newError("redirecting request " + dst.String() + " to " + obt).WriteToLog(session.ExportIDToError(ctx))
   116  	h := obm.GetHandler(obt)
   117  	ctx = session.ContextWithOutbound(ctx, &session.Outbound{Target: dst, Gateway: nil})
   118  	if h != nil {
   119  		ur, uw := pipe.New(pipe.OptionsFromContext(ctx)...)
   120  		dr, dw := pipe.New(pipe.OptionsFromContext(ctx)...)
   121  
   122  		go h.Dispatch(ctx, &transport.Link{Reader: ur, Writer: dw})
   123  		nc := cnc.NewConnection(
   124  			cnc.ConnectionInputMulti(uw),
   125  			cnc.ConnectionOutputMulti(dr),
   126  			cnc.ConnectionOnClose(common.ChainedClosable{uw, dw}),
   127  		)
   128  		return nc
   129  	}
   130  	return nil
   131  }
   132  
   133  // DialSystem calls system dialer to create a network connection.
   134  func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {
   135  	var src net.Address
   136  	if outbound := session.OutboundFromContext(ctx); outbound != nil {
   137  		src = outbound.Gateway
   138  	}
   139  	if sockopt == nil {
   140  		return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
   141  	}
   142  
   143  	if canLookupIP(ctx, dest, sockopt) {
   144  		ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
   145  		if err == nil && len(ips) > 0 {
   146  			dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
   147  			newError("replace destination with " + dest.String()).AtInfo().WriteToLog()
   148  		} else if err != nil {
   149  			newError("failed to resolve ip").Base(err).AtWarning().WriteToLog()
   150  		}
   151  	}
   152  
   153  	if obm != nil && len(sockopt.DialerProxy) > 0 {
   154  		nc := redirect(ctx, dest, sockopt.DialerProxy)
   155  		if nc != nil {
   156  			return nc, nil
   157  		}
   158  	}
   159  
   160  	return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
   161  }
   162  
   163  func InitSystemDialer(dc dns.Client, om outbound.Manager) {
   164  	dnsClient = dc
   165  	obm = om
   166  }