github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/transport/internet/dialer.go (about)

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