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 }