mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-25 10:01:28 +00:00
017f53b5fc
* Add session context outbounds as slice slice is needed for dialer proxy where two outbounds work on top of each other There are two sets of target addr for example It also enable Xtls to correctly do splice copy by checking both outbounds are ready to do direct copy * Fill outbound tag info * Splice now checks capalibility from all outbounds * Fix unit tests
172 lines
5.4 KiB
Go
172 lines
5.4 KiB
Go
package internet
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"github.com/xtls/xray-core/common/dice"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/net/cnc"
|
|
"github.com/xtls/xray-core/common/session"
|
|
"github.com/xtls/xray-core/features/dns"
|
|
"github.com/xtls/xray-core/features/outbound"
|
|
"github.com/xtls/xray-core/transport"
|
|
"github.com/xtls/xray-core/transport/internet/stat"
|
|
"github.com/xtls/xray-core/transport/pipe"
|
|
)
|
|
|
|
// Dialer is the interface for dialing outbound connections.
|
|
type Dialer interface {
|
|
// Dial dials a system connection to the given destination.
|
|
Dial(ctx context.Context, destination net.Destination) (stat.Connection, error)
|
|
|
|
// Address returns the address used by this Dialer. Maybe nil if not known.
|
|
Address() net.Address
|
|
|
|
// DestIpAddress returns the ip of proxy server. It is useful in case of Android client, which prepare an IP before proxy connection is established
|
|
DestIpAddress() net.IP
|
|
}
|
|
|
|
// dialFunc is an interface to dial network connection to a specific destination.
|
|
type dialFunc func(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error)
|
|
|
|
var transportDialerCache = make(map[string]dialFunc)
|
|
|
|
// RegisterTransportDialer registers a Dialer with given name.
|
|
func RegisterTransportDialer(protocol string, dialer dialFunc) error {
|
|
if _, found := transportDialerCache[protocol]; found {
|
|
return newError(protocol, " dialer already registered").AtError()
|
|
}
|
|
transportDialerCache[protocol] = dialer
|
|
return nil
|
|
}
|
|
|
|
// Dial dials a internet connection towards the given destination.
|
|
func Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error) {
|
|
if dest.Network == net.Network_TCP {
|
|
if streamSettings == nil {
|
|
s, err := ToMemoryStreamConfig(nil)
|
|
if err != nil {
|
|
return nil, newError("failed to create default stream settings").Base(err)
|
|
}
|
|
streamSettings = s
|
|
}
|
|
|
|
protocol := streamSettings.ProtocolName
|
|
dialer := transportDialerCache[protocol]
|
|
if dialer == nil {
|
|
return nil, newError(protocol, " dialer not registered").AtError()
|
|
}
|
|
return dialer(ctx, dest, streamSettings)
|
|
}
|
|
|
|
if dest.Network == net.Network_UDP {
|
|
udpDialer := transportDialerCache["udp"]
|
|
if udpDialer == nil {
|
|
return nil, newError("UDP dialer not registered").AtError()
|
|
}
|
|
return udpDialer(ctx, dest, streamSettings)
|
|
}
|
|
|
|
return nil, newError("unknown network ", dest.Network)
|
|
}
|
|
|
|
// DestIpAddress returns the ip of proxy server. It is useful in case of Android client, which prepare an IP before proxy connection is established
|
|
func DestIpAddress() net.IP {
|
|
return effectiveSystemDialer.DestIpAddress()
|
|
}
|
|
|
|
var (
|
|
dnsClient dns.Client
|
|
obm outbound.Manager
|
|
)
|
|
|
|
func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {
|
|
if dnsClient == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
ips, err := dnsClient.LookupIP(domain, dns.IPOption{
|
|
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && strategy.preferIP4(),
|
|
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && strategy.preferIP6(),
|
|
})
|
|
{ // Resolve fallback
|
|
if (len(ips) == 0 || err != nil) && strategy.hasFallback() && localAddr == nil {
|
|
ips, err = dnsClient.LookupIP(domain, dns.IPOption{
|
|
IPv4Enable: strategy.fallbackIP4(),
|
|
IPv6Enable: strategy.fallbackIP6(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return ips, err
|
|
}
|
|
|
|
func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
|
if dst.Address.Family().IsIP() || dnsClient == nil {
|
|
return false
|
|
}
|
|
return sockopt.DomainStrategy.hasStrategy()
|
|
}
|
|
|
|
func redirect(ctx context.Context, dst net.Destination, obt string) net.Conn {
|
|
newError("redirecting request " + dst.String() + " to " + obt).WriteToLog(session.ExportIDToError(ctx))
|
|
h := obm.GetHandler(obt)
|
|
outbounds := session.OutboundsFromContext(ctx)
|
|
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
|
Target: dst,
|
|
Gateway: nil,
|
|
Tag: obt,
|
|
})) // add another outbound in session ctx
|
|
if h != nil {
|
|
ur, uw := pipe.New(pipe.OptionsFromContext(ctx)...)
|
|
dr, dw := pipe.New(pipe.OptionsFromContext(ctx)...)
|
|
|
|
go h.Dispatch(ctx, &transport.Link{Reader: ur, Writer: dw})
|
|
nc := cnc.NewConnection(
|
|
cnc.ConnectionInputMulti(uw),
|
|
cnc.ConnectionOutputMulti(dr),
|
|
cnc.ConnectionOnClose(common.ChainedClosable{uw, dw}),
|
|
)
|
|
return nc
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DialSystem calls system dialer to create a network connection.
|
|
func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {
|
|
var src net.Address
|
|
outbounds := session.OutboundsFromContext(ctx)
|
|
if len(outbounds) > 0 {
|
|
ob := outbounds[len(outbounds) - 1]
|
|
src = ob.Gateway
|
|
}
|
|
if sockopt == nil {
|
|
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
|
}
|
|
|
|
if canLookupIP(ctx, dest, sockopt) {
|
|
ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
|
|
if err == nil && len(ips) > 0 {
|
|
dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
|
newError("replace destination with " + dest.String()).AtInfo().WriteToLog()
|
|
} else if err != nil {
|
|
newError("failed to resolve ip").Base(err).AtWarning().WriteToLog()
|
|
}
|
|
}
|
|
|
|
if obm != nil && len(sockopt.DialerProxy) > 0 {
|
|
nc := redirect(ctx, dest, sockopt.DialerProxy)
|
|
if nc != nil {
|
|
return nc, nil
|
|
}
|
|
}
|
|
|
|
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
|
}
|
|
|
|
func InitSystemDialer(dc dns.Client, om outbound.Manager) {
|
|
dnsClient = dc
|
|
obm = om
|
|
}
|