WireGuard outbound: Add remoteDNS & honor TTL (#6620)

Closes https://github.com/XTLS/Xray-core/pull/6569#issuecomment-5263789755

Fixes https://github.com/XTLS/Xray-core/issues/6567#issuecomment-5150957597

---------

Co-authored-by: LagPixelLOL <2282688304@qq.com>
This commit is contained in:
LjhAUMEM
2026-08-25 14:28:36 +00:00
committed by GitHub
co-authored by LagPixelLOL
parent f02a357861
commit c7e569b037
5 changed files with 82 additions and 28 deletions
+2
View File
@@ -66,6 +66,7 @@ type WireGuardConfig struct {
MTU int32 `json:"mtu"` MTU int32 `json:"mtu"`
Reserved []byte `json:"reserved"` Reserved []byte `json:"reserved"`
DomainStrategy string `json:"domainStrategy"` DomainStrategy string `json:"domainStrategy"`
DNS []string `json:"remoteDNS"`
} }
func (c *WireGuardConfig) Build() (proto.Message, error) { func (c *WireGuardConfig) Build() (proto.Message, error) {
@@ -141,6 +142,7 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
config.IsClient = c.IsClient config.IsClient = c.IsClient
config.NoKernelTun = c.NoKernelTun config.NoKernelTun = c.NoKernelTun
config.DNS = c.DNS
return config, nil return config, nil
} }
+55 -17
View File
@@ -5,9 +5,10 @@ import (
"fmt" "fmt"
gonet "net" gonet "net"
"net/netip" "net/netip"
reflect "reflect" "reflect"
"strings" "strings"
"sync" "sync"
"time"
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
@@ -30,6 +31,11 @@ import (
"golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/device"
) )
type entry struct {
got []net.IP
time time.Time
}
type Handler struct { type Handler struct {
conf *DeviceConfig conf *DeviceConfig
policyManager policy.Manager policyManager policy.Manager
@@ -43,6 +49,11 @@ type Handler struct {
tnet *Net tnet *Net
dev *device.Device dev *device.Device
mu sync.Mutex mu sync.Mutex
// TODO: cache cleanup loop
local bool
cache map[string]entry
cacheMu sync.Mutex
} }
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) { func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
@@ -98,6 +109,20 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
return nil, err return nil, err
} }
local := false
dns := conf.DNS
if len(dns) == 0 {
dns = []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"}
}
if len(dns) == 1 && dns[0] == "local" {
local = true
dns = nil
}
dnses := make([]netip.Addr, 0, len(dns))
for _, dns := range dns {
dnses = append(dnses, netip.MustParseAddr(dns))
}
kernelTunSupported, err := KernelTunSupported() kernelTunSupported, err := KernelTunSupported()
if err != nil { if err != nil {
errors.LogWarningInner(context.Background(), err, "Failed to check kernel TUN support") errors.LogWarningInner(context.Background(), err, "Failed to check kernel TUN support")
@@ -106,10 +131,10 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
var tnet *Net var tnet *Net
if !conf.NoKernelTun && kernelTunSupported { if !conf.NoKernelTun && kernelTunSupported {
errors.LogWarning(context.Background(), "Using kernel TUN") errors.LogWarning(context.Background(), "Using kernel TUN")
tun, tnet, err = createKernelTun(localAddresses, []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.0.0.1"), netip.MustParseAddr("2606:4700:4700::1111"), netip.MustParseAddr("2606:4700:4700::1001")}, int(conf.Mtu)) tun, tnet, err = createKernelTun(localAddresses, dnses, int(conf.Mtu))
} else { } else {
errors.LogWarning(context.Background(), "Using gVisor TUN") errors.LogWarning(context.Background(), "Using gVisor TUN")
tun, tnet, _, err = CreateNetTUN(localAddresses, []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.0.0.1"), netip.MustParseAddr("2606:4700:4700::1111"), netip.MustParseAddr("2606:4700:4700::1001")}, int(conf.Mtu), true) tun, tnet, _, err = CreateNetTUN(localAddresses, dnses, int(conf.Mtu), true)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -126,6 +151,9 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
tun: tun, tun: tun,
tnet: tnet, tnet: tnet,
local: local,
cache: make(map[string]entry),
}, nil }, nil
} }
@@ -343,31 +371,34 @@ func (h *Handler) init(ctx context.Context) error {
} }
func (h *Handler) resolveLocal(host string) (net.IP, error) { func (h *Handler) resolveLocal(host string) (net.IP, error) {
return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) { return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true}) return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
return ips, err
}) })
} }
func (h *Handler) resolveRemote(host string) (net.IP, error) { func (h *Handler) resolveRemote(host string) (net.IP, error) {
return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) { return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) {
addrs, err := h.tnet.LookupHost(host) if h.local {
if err != nil { return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
return nil, err
} }
ips := make([]net.IP, 0, len(addrs)) return h.tnet.LookupHost(host)
for _, addr := range addrs {
ips = append(ips, net.ParseIP(addr))
}
return ips, nil
}) })
} }
func resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, error)) (net.IP, error) { func (h *Handler) resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, uint32, error)) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil { if ip := net.ParseIP(host); ip != nil {
return ip, nil return ip, nil
} }
ips, err := lookupIP(host) h.cacheMu.Lock()
if entry, ok := h.cache[host]; ok {
if time.Now().Before(entry.time) {
h.cacheMu.Unlock()
return entry.got[dice.Roll(len(entry.got))], nil
}
delete(h.cache, host)
}
h.cacheMu.Unlock()
ips, ttl, err := lookupIP(host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -407,6 +438,13 @@ func resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP f
if len(got) == 0 { if len(got) == 0 {
return nil, dns.ErrEmptyResponse return nil, dns.ErrEmptyResponse
} }
entry := entry{
got: got,
time: time.Now().Add(time.Duration(ttl) * time.Second),
}
h.cacheMu.Lock()
h.cache[host] = entry
h.cacheMu.Unlock()
return got[dice.Roll(len(got))], nil return got[dice.Roll(len(got))], nil
} }
+12 -2
View File
@@ -164,6 +164,7 @@ type DeviceConfig struct {
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"` DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"` IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"` NoKernelTun bool `protobuf:"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3" json:"no_kernel_tun,omitempty"`
DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -261,6 +262,13 @@ func (x *DeviceConfig) GetNoKernelTun() bool {
return false return false
} }
func (x *DeviceConfig) GetDNS() []string {
if x != nil {
return x.DNS
}
return nil
}
var File_proxy_wireguard_config_proto protoreflect.FileDescriptor var File_proxy_wireguard_config_proto protoreflect.FileDescriptor
const file_proxy_wireguard_config_proto_rawDesc = "" + const file_proxy_wireguard_config_proto_rawDesc = "" +
@@ -275,7 +283,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\n" + "\n" +
"keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" + "keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" + "\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xdc\x03\n" + "allowedIps\"\xee\x03\n" +
"\fDeviceConfig\x12\x1d\n" + "\fDeviceConfig\x12\x1d\n" +
"\n" + "\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" + "secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
@@ -286,7 +294,9 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" + "\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" + "\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" + "\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
"\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\"\\\n" + "\rno_kernel_tun\x18\t \x01(\bR\vnoKernelTun\x12\x10\n" +
"\x03DNS\x18\n" +
" \x03(\tR\x03DNS\"\\\n" +
"\x0eDomainStrategy\x12\f\n" + "\x0eDomainStrategy\x12\f\n" +
"\bFORCE_IP\x10\x00\x12\r\n" + "\bFORCE_IP\x10\x00\x12\r\n" +
"\tFORCE_IP4\x10\x01\x12\r\n" + "\tFORCE_IP4\x10\x01\x12\r\n" +
+1
View File
@@ -34,4 +34,5 @@ message DeviceConfig {
DomainStrategy domain_strategy = 7; DomainStrategy domain_strategy = 7;
bool is_client = 8; bool is_client = 8;
bool no_kernel_tun = 9; bool no_kernel_tun = 9;
repeated string DNS = 10;
} }
+12 -9
View File
@@ -248,7 +248,7 @@ var (
errTimeout = errors.New("i/o timeout") errTimeout = errors.New("i/o timeout")
) )
func (net *Net) LookupHost(host string) (addrs []string, err error) { func (net *Net) LookupHost(host string) (addrs []net.IP, ttl uint32, err error) {
return net.LookupContextHost(context.Background(), host) return net.LookupContextHost(context.Background(), host)
} }
@@ -567,9 +567,9 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T
return dnsmessage.Parser{}, "", lastErr return dnsmessage.Parser{}, "", lastErr
} }
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) { func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]net.IP, uint32, error) {
if host == "" || (!tnet.hasV6 && !tnet.hasV4) { if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
} }
zlen := len(host) zlen := len(host)
if strings.IndexByte(host, ':') != -1 { if strings.IndexByte(host, ':') != -1 {
@@ -578,11 +578,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
} }
} }
if ip, err := netip.ParseAddr(host[:zlen]); err == nil { if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []string{ip.String()}, nil return []net.IP{ip.AsSlice()}, 0, nil
} }
if !isDomainName(host) { if !isDomainName(host) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} return nil, 0, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
} }
type result struct { type result struct {
p dnsmessage.Parser p dnsmessage.Parser
@@ -611,6 +611,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
lane <- result{p, server, err} lane <- result{p, server, err}
}() }()
} }
ttl := uint32(300)
for l := 0; l < lanes; l++ { for l := 0; l < lanes; l++ {
result := <-lane result := <-lane
if result.error != nil { if result.error != nil {
@@ -644,6 +645,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
} }
break loop break loop
} }
ttl = min(ttl, h.TTL)
addrsV4 = append(addrsV4, netip.AddrFrom4(a.A)) addrsV4 = append(addrsV4, netip.AddrFrom4(a.A))
case dnsmessage.TypeAAAA: case dnsmessage.TypeAAAA:
@@ -656,6 +658,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
} }
break loop break loop
} }
ttl = min(ttl, h.TTL)
addrsV6 = append(addrsV6, netip.AddrFrom16(aaaa.AAAA)) addrsV6 = append(addrsV6, netip.AddrFrom16(aaaa.AAAA))
default: default:
@@ -680,11 +683,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string,
} }
if len(addrs) == 0 && lastErr != nil { if len(addrs) == 0 && lastErr != nil {
return nil, lastErr return nil, 0, lastErr
} }
saddrs := make([]string, 0, len(addrs)) ips := make([]net.IP, 0, len(addrs))
for _, ip := range addrs { for _, ip := range addrs {
saddrs = append(saddrs, ip.String()) ips = append(ips, ip.AsSlice())
} }
return saddrs, nil return ips, ttl, nil
} }