diff --git a/infra/conf/wireguard.go b/infra/conf/wireguard.go index 8131be28a969..0e41c9c61f8d 100644 --- a/infra/conf/wireguard.go +++ b/infra/conf/wireguard.go @@ -66,6 +66,7 @@ type WireGuardConfig struct { MTU int32 `json:"mtu"` Reserved []byte `json:"reserved"` DomainStrategy string `json:"domainStrategy"` + DNS []string `json:"remoteDNS"` } func (c *WireGuardConfig) Build() (proto.Message, error) { @@ -141,6 +142,7 @@ func (c *WireGuardConfig) Build() (proto.Message, error) { config.IsClient = c.IsClient config.NoKernelTun = c.NoKernelTun + config.DNS = c.DNS return config, nil } diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index 59709b4bf915..ef0cc1e2a8c4 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -5,9 +5,10 @@ import ( "fmt" gonet "net" "net/netip" - reflect "reflect" + "reflect" "strings" "sync" + "time" "golang.zx2c4.com/wireguard/tun" @@ -30,6 +31,11 @@ import ( "golang.zx2c4.com/wireguard/device" ) +type entry struct { + got []net.IP + time time.Time +} + type Handler struct { conf *DeviceConfig policyManager policy.Manager @@ -43,6 +49,11 @@ type Handler struct { tnet *Net dev *device.Device 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) { @@ -98,6 +109,20 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) { 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() if err != nil { 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 if !conf.NoKernelTun && kernelTunSupported { 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 { 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 { return nil, err @@ -126,6 +151,9 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) { tun: tun, tnet: tnet, + + local: local, + cache: make(map[string]entry), }, nil } @@ -343,31 +371,34 @@ func (h *Handler) init(ctx context.Context) error { } func (h *Handler) resolveLocal(host string) (net.IP, error) { - return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) { - ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true}) - return ips, err + return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) { + return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true}) }) } func (h *Handler) resolveRemote(host string) (net.IP, error) { - return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) { - addrs, err := h.tnet.LookupHost(host) - if err != nil { - return nil, err - } - ips := make([]net.IP, 0, len(addrs)) - for _, addr := range addrs { - ips = append(ips, net.ParseIP(addr)) + return h.resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, uint32, error) { + if h.local { + return h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true}) } - return ips, nil + return h.tnet.LookupHost(host) }) } -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 { 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 { return nil, err } @@ -407,6 +438,13 @@ func resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP f if len(got) == 0 { 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 } diff --git a/proxy/wireguard/config.pb.go b/proxy/wireguard/config.pb.go index 59db6564f7bd..4f434d1fc7aa 100644 --- a/proxy/wireguard/config.pb.go +++ b/proxy/wireguard/config.pb.go @@ -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"` 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"` + DNS []string `protobuf:"bytes,10,rep,name=DNS,proto3" json:"DNS,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -261,6 +262,13 @@ func (x *DeviceConfig) GetNoKernelTun() bool { return false } +func (x *DeviceConfig) GetDNS() []string { + if x != nil { + return x.DNS + } + return nil +} + var File_proxy_wireguard_config_proto protoreflect.FileDescriptor const file_proxy_wireguard_config_proto_rawDesc = "" + @@ -275,7 +283,7 @@ const file_proxy_wireguard_config_proto_rawDesc = "" + "\n" + "keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" + "\vallowed_ips\x18\x05 \x03(\tR\n" + - "allowedIps\"\xdc\x03\n" + + "allowedIps\"\xee\x03\n" + "\fDeviceConfig\x12\x1d\n" + "\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" + "\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\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" + "\bFORCE_IP\x10\x00\x12\r\n" + "\tFORCE_IP4\x10\x01\x12\r\n" + diff --git a/proxy/wireguard/config.proto b/proxy/wireguard/config.proto index 171d5b21aef5..628059827441 100644 --- a/proxy/wireguard/config.proto +++ b/proxy/wireguard/config.proto @@ -34,4 +34,5 @@ message DeviceConfig { DomainStrategy domain_strategy = 7; bool is_client = 8; bool no_kernel_tun = 9; + repeated string DNS = 10; } \ No newline at end of file diff --git a/proxy/wireguard/netstack.go b/proxy/wireguard/netstack.go index 22c42e3f9f68..813f46790571 100644 --- a/proxy/wireguard/netstack.go +++ b/proxy/wireguard/netstack.go @@ -248,7 +248,7 @@ var ( 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) } @@ -567,9 +567,9 @@ func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.T 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) { - 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) 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 { - return []string{ip.String()}, nil + return []net.IP{ip.AsSlice()}, 0, nil } 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 { p dnsmessage.Parser @@ -611,6 +611,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, lane <- result{p, server, err} }() } + ttl := uint32(300) for l := 0; l < lanes; l++ { result := <-lane if result.error != nil { @@ -644,6 +645,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, } break loop } + ttl = min(ttl, h.TTL) addrsV4 = append(addrsV4, netip.AddrFrom4(a.A)) case dnsmessage.TypeAAAA: @@ -656,6 +658,7 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, } break loop } + ttl = min(ttl, h.TTL) addrsV6 = append(addrsV6, netip.AddrFrom16(aaaa.AAAA)) default: @@ -680,11 +683,11 @@ func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, } 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 { - saddrs = append(saddrs, ip.String()) + ips = append(ips, ip.AsSlice()) } - return saddrs, nil + return ips, ttl, nil }