github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/transport/internet/dialer.go (about)

     1  package internet
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/xmplusdev/xmcore/common"
     7  	"github.com/xmplusdev/xmcore/common/dice"
     8  	"github.com/xmplusdev/xmcore/common/net"
     9  	"github.com/xmplusdev/xmcore/common/net/cnc"
    10  	"github.com/xmplusdev/xmcore/common/session"
    11  	"github.com/xmplusdev/xmcore/features/dns"
    12  	"github.com/xmplusdev/xmcore/features/outbound"
    13  	"github.com/xmplusdev/xmcore/transport"
    14  	"github.com/xmplusdev/xmcore/transport/internet/stat"
    15  	"github.com/xmplusdev/xmcore/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  	ctx = session.ContextWithOutbound(ctx, &session.Outbound{Target: dst, Gateway: nil})
   116  	if h != nil {
   117  		ur, uw := pipe.New(pipe.OptionsFromContext(ctx)...)
   118  		dr, dw := pipe.New(pipe.OptionsFromContext(ctx)...)
   119  
   120  		go h.Dispatch(ctx, &transport.Link{Reader: ur, Writer: dw})
   121  		nc := cnc.NewConnection(
   122  			cnc.ConnectionInputMulti(uw),
   123  			cnc.ConnectionOutputMulti(dr),
   124  			cnc.ConnectionOnClose(common.ChainedClosable{uw, dw}),
   125  		)
   126  		return nc
   127  	}
   128  	return nil
   129  }
   130  
   131  // DialSystem calls system dialer to create a network connection.
   132  func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {
   133  	var src net.Address
   134  	if outbound := session.OutboundFromContext(ctx); outbound != nil {
   135  		src = outbound.Gateway
   136  	}
   137  	if sockopt == nil {
   138  		return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
   139  	}
   140  
   141  	if canLookupIP(ctx, dest, sockopt) {
   142  		ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
   143  		if err == nil && len(ips) > 0 {
   144  			dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
   145  			newError("replace destination with " + dest.String()).AtInfo().WriteToLog()
   146  		} else if err != nil {
   147  			newError("failed to resolve ip").Base(err).AtWarning().WriteToLog()
   148  		}
   149  	}
   150  
   151  	if obm != nil && len(sockopt.DialerProxy) > 0 {
   152  		nc := redirect(ctx, dest, sockopt.DialerProxy)
   153  		if nc != nil {
   154  			return nc, nil
   155  		}
   156  	}
   157  
   158  	return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
   159  }
   160  
   161  func InitSystemDialer(dc dns.Client, om outbound.Manager) {
   162  	dnsClient = dc
   163  	obm = om
   164  }