From d60281d0a58643fb024cdb566b8b01c031a13a0a Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 17 Dec 2023 13:09:04 -0500 Subject: [PATCH] Add DestIpAddress() in Dialer interface Android client prepares an IP before proxy connection is established. It is useful when connecting to wireguard (or quic) outbound with domain address. E.g. engage.cloudflareclient.com:2408 --- app/proxyman/outbound/handler.go | 4 ++ proxy/wireguard/client.go | 73 +++++++++++++++++++++++++++-- transport/internet/dialer.go | 8 ++++ transport/internet/system_dialer.go | 9 ++++ 4 files changed, 91 insertions(+), 3 deletions(-) diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index d290b016..2df2b2c3 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -229,6 +229,10 @@ func (h *Handler) Address() net.Address { return h.senderSettings.Via.AsAddress() } +func (h *Handler) DestIpAddress() net.IP { + return internet.DestIpAddress() +} + // Dial implements internet.Dialer. func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) { if h.senderSettings != nil { diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index def07878..2560c538 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -22,7 +22,9 @@ package wireguard import ( "context" + "fmt" "net/netip" + "strings" "sync" "github.com/xtls/xray-core/common" @@ -49,7 +51,6 @@ type Handler struct { policyManager policy.Manager dns dns.Client // cached configuration - ipc string endpoints []netip.Addr hasIPv4, hasIPv6 bool wgLock sync.Mutex @@ -69,7 +70,6 @@ func New(ctx context.Context, conf *DeviceConfig) (*Handler, error) { conf: conf, policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), dns: d, - ipc: createIPCRequest(conf), endpoints: endpoints, hasIPv4: hasIPv4, hasIPv6: hasIPv6, @@ -247,9 +247,76 @@ func (h *Handler) makeVirtualTun(bind *netBindClient) (Tunnel, error) { bind.dnsOption.IPv4Enable = h.hasIPv4 bind.dnsOption.IPv6Enable = h.hasIPv6 - if err = t.BuildDevice(h.ipc, bind); err != nil { + if err = t.BuildDevice(h.createIPCRequest(bind, h.conf), bind); err != nil { _ = t.Close() return nil, err } return t, nil } + + +// serialize the config into an IPC request +func (h *Handler) createIPCRequest(bind *netBindClient, conf *DeviceConfig) string { + var request strings.Builder + + request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey)) + + if !conf.IsClient { + // placeholder, we'll handle actual port listening on Xray + request.WriteString("listen_port=1337\n") + } + + for _, peer := range conf.Peers { + if peer.PublicKey != "" { + request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey)) + } + + if peer.PreSharedKey != "" { + request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey)) + } + + split := strings.Split(peer.Endpoint, ":") + addr := net.ParseAddress(split[0]) + if addr.Family().IsDomain() { + dialerIp := bind.dialer.DestIpAddress() + if dialerIp != nil { + addr = net.ParseAddress(dialerIp.String()) + newError("createIPCRequest use dialer dest ip: ", addr).WriteToLog() + } else { + ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{ + IPv4Enable: h.hasIPv4 && h.conf.preferIP4(), + IPv6Enable: h.hasIPv6 && h.conf.preferIP6(), + }) + { // Resolve fallback + if (len(ips) == 0 || err != nil) && h.conf.hasFallback() { + ips, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{ + IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(), + IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(), + }) + } + } + if err != nil { + newError("createIPCRequest failed to lookup DNS").Base(err).WriteToLog() + } else if len(ips) == 0 { + newError("createIPCRequest empty lookup DNS").WriteToLog() + } else { + addr = net.IPAddress(ips[dice.Roll(len(ips))]) + } + } + } + + if peer.Endpoint != "" { + request.WriteString(fmt.Sprintf("endpoint=%s:%s\n", addr, split[1])) + } + + for _, ip := range peer.AllowedIps { + request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip)) + } + + if peer.KeepAlive != 0 { + request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive)) + } + } + + return request.String()[:request.Len()] +} diff --git a/transport/internet/dialer.go b/transport/internet/dialer.go index deae4df0..3d5d046f 100644 --- a/transport/internet/dialer.go +++ b/transport/internet/dialer.go @@ -22,6 +22,9 @@ type Dialer interface { // 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. @@ -68,6 +71,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStrea 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 diff --git a/transport/internet/system_dialer.go b/transport/internet/system_dialer.go index 5304595f..cdb6cb9c 100644 --- a/transport/internet/system_dialer.go +++ b/transport/internet/system_dialer.go @@ -16,6 +16,7 @@ var effectiveSystemDialer SystemDialer = &DefaultSystemDialer{} type SystemDialer interface { Dial(ctx context.Context, source net.Address, destination net.Destination, sockopt *SocketConfig) (net.Conn, error) + DestIpAddress() net.IP } type DefaultSystemDialer struct { @@ -108,6 +109,10 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne return dialer.DialContext(ctx, dest.Network.SystemString(), dest.NetAddr()) } +func (d *DefaultSystemDialer) DestIpAddress() net.IP { + return nil +} + type PacketConnWrapper struct { Conn net.PacketConn Dest net.Addr @@ -172,6 +177,10 @@ func (v *SimpleSystemDialer) Dial(ctx context.Context, src net.Address, dest net return v.adapter.Dial(dest.Network.SystemString(), dest.NetAddr()) } +func (d *SimpleSystemDialer) DestIpAddress() net.IP { + return nil +} + // UseAlternativeSystemDialer replaces the current system dialer with a given one. // Caller must ensure there is no race condition. //