Repository: XTLS/Xray-core

Issue:
功能请求：DNS响应应包含正确的TTL
### 完整性要求

- [x] 我保证阅读了文档，了解所有我编写的配置文件项的含义，而不是大量堆砌看似有用的选项或默认值。
- [x] 我提供了完整的配置文件和日志，而不是出于自己的判断只给出截取的部分。
- [x] 我搜索了 issues, 没有发现已提出的类似问题。
- [x] 问题在 Release 最新的版本上可以成功复现

### 描述

如题，当前除FakeDNS外响应全是600
应正确响应TTL，体现出当前Xray缓存有效期

修改前
```bash
dig @192.168.1.1 www.google.com

; <<>> DiG 9.16.50 <<>> @192.168.1.1 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23712
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.google.com.                        IN      A

;; ANSWER SECTION:
www.google.com.         600     IN      A       142.250.206.196

;; Query time: 429 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Fri Mar 21 22:17:41 ;; MSG SIZE  rcvd: 48
```

预期
```bash
dig @192.168.1.2 www.google.com

; <<>> DiG 9.16.50 <<>> @192.168.1.2 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58572
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.google.com.                        IN      A

;; ANSWER SECTION:
www.google.com.         92      IN      A       142.251.222.36

;; Query time: 15 msec
;; SERVER: 192.168.1.2#53(192.168.1.2)
;; WHEN: Fri Mar 21 22:17:23 ;; MSG SIZE  rcvd: 48
```

解决方案
https://github.com/XTLS/Xray-core/pull/4526

### 重现方式

N/A

### 客户端配置

N/A

### 服务端配置

N/A

### 客户端日志

N/A

### 服务端日志

N/A

Hints from issue comments:

Solution patch:
diff --git a/app/dns/dns.go b/app/dns/dns.go
index bc544a5a..9b84106c 100644
--- a/app/dns/dns.go
+++ b/app/dns/dns.go
@@ -157,16 +157,16 @@ func (s *DNS) IsOwnLink(ctx context.Context) bool {
 }
 
 // LookupIP implements dns.Client.
-func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
+func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
 	if domain == "" {
-		return nil, errors.New("empty domain name")
+		return nil, 0, errors.New("empty domain name")
 	}
 
 	option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
 	option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
 
 	if !option.IPv4Enable && !option.IPv6Enable {
-		return nil, dns.ErrEmptyResponse
+		return nil, 0, dns.ErrEmptyResponse
 	}
 
 	// Normalize the FQDN form query
@@ -177,13 +177,14 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
 	case addrs == nil: // Domain not recorded in static host
 		break
 	case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
-		return nil, dns.ErrEmptyResponse
+		return nil, 0, dns.ErrEmptyResponse
 	case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
 		errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain())
 		domain = addrs[0].Domain()
 	default: // Successfully found ip records in static host
 		errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs)
-		return toNetIP(addrs)
+		ips, err := toNetIP(addrs)
+		return ips, 10, err // Hosts ttl is 10
 	}
 
 	// Name servers lookup
@@ -194,9 +195,9 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
 			errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
 			continue
 		}
-		ips, err := client.QueryIP(ctx, domain, option, s.disableCache)
+		ips, ttl, err := client.QueryIP(ctx, domain, option, s.disableCache)
 		if len(ips) > 0 {
-			return ips, nil
+			return ips, ttl, nil
 		}
 		if err != nil {
 			errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name())
@@ -204,11 +205,11 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
 		}
 		// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size
 		if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
-			return nil, err
+			return nil, 0, err
 		}
 	}
 
-	return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
+	return nil, 0, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
 }
 
 // LookupHosts implements dns.HostsLookup.
diff --git a/app/dns/dnscommon.go b/app/dns/dnscommon.go
index e11cc097..0dc07f72 100644
--- a/app/dns/dnscommon.go
+++ b/app/dns/dnscommon.go
@@ -38,14 +38,15 @@ type IPRecord struct {
 	RawHeader *dnsmessage.Header
 }
 
-func (r *IPRecord) getIPs() ([]net.Address, error) {
+func (r *IPRecord) getIPs() ([]net.Address, uint32, error) {
 	if r == nil || r.Expire.Before(time.Now()) {
-		return nil, errRecordNotFound
+		return nil, 0, errRecordNotFound
 	}
 	if r.RCode != dnsmessage.RCodeSuccess {
-		return nil, dns_feature.RCodeError(r.RCode)
+		return nil, 0, dns_feature.RCodeError(r.RCode)
 	}
-	return r.IP, nil
+	ttl := uint32(time.Until(r.Expire) / time.Second)
+	return r.IP, ttl, nil
 }
 
 func isNewer(baseRec *IPRecord, newRec *IPRecord) bool {
diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go
index 14513b3b..774d87c3 100644
--- a/app/dns/nameserver.go
+++ b/app/dns/nameserver.go
@@ -21,7 +21,7 @@ type Server interface {
 	// Name of the Client.
 	Name() string
 	// QueryIP sends IP queries to its configured server.
-	QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
+	QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, uint32, error)
 }
 
 // Client is the interface for DNS client.
@@ -191,7 +191,7 @@ func (c *Client) Name() string {
 }
 
 // QueryIP sends DNS query to the name server with the client's IP.
-func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
+func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, uint32, error) {
 	ctx, cancel := context.WithTimeout(ctx, c.timeoutMs)
 	if len(c.tag) != 0 {
 		content := session.InboundFromContext(ctx)
@@ -200,13 +200,14 @@ func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption
 		// do not direct set *content.Tag, it might be used by other clients
 		ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})
 	}
-	ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
+	ips, ttl, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
 	cancel()
 
 	if err != nil {
-		return ips, err
+		return ips, ttl, err
 	}
-	return c.MatchExpectedIPs(domain, ips)
+	netips, err := c.MatchExpectedIPs(domain, ips)
+	return netips, ttl, err
 }
 
 // MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go
index 1739062d..8a23e0c3 100644
--- a/app/dns/nameserver_doh.go
+++ b/app/dns/nameserver_doh.go
@@ -301,64 +301,66 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
 	return io.ReadAll(resp.Body)
 }
 
-func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
+func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
 	s.RLock()
 	record, found := s.ips[domain]
 	s.RUnlock()
 
 	if !found {
-		return nil, errRecordNotFound
+		return nil, 0, errRecordNotFound
 	}
 
 	var err4 error
 	var err6 error
 	var ips []net.Address
 	var ip6 []net.Address
+	var ttl uint32
 
 	if option.IPv4Enable {
-		ips, err4 = record.A.getIPs()
+		ips, ttl, err4 = record.A.getIPs()
 	}
 
 	if option.IPv6Enable {
-		ip6, err6 = record.AAAA.getIPs()
+		ip6, ttl, err6 = record.AAAA.getIPs()
 		ips = append(ips, ip6...)
 	}
 
 	if len(ips) > 0 {
-		return toNetIP(ips)
+		netips, err := toNetIP(ips)
+		return netips, ttl, err
 	}
 
 	if err4 != nil {
-		return nil, err4
+		return nil, 0, err4
 	}
 
 	if err6 != nil {
-		return nil, err6
+		return nil, 0, err6
 	}
 
 	if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
-		return nil, dns_feature.ErrEmptyResponse
+		return nil, 0, dns_feature.ErrEmptyResponse
 	}
 
-	return nil, errRecordNotFound
+	return nil, 0, errRecordNotFound
 }
 
 // QueryIP implements Server.
-func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
+func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) { // nolint: dupl
 	fqdn := Fqdn(domain)
 	option = ResolveIpOptionOverride(s.queryStrategy, option)
 	if !option.IPv4Enable && !option.IPv6Enable {
-		return nil, dns_feature.ErrEmptyResponse
+		return nil, 0, dns_feature.ErrEmptyResponse
 	}
 
 	if disableCache {
 		errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
 	} else {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err == nil || err == dns_feature.ErrEmptyResponse {
 			errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 	}
 
@@ -392,15 +394,15 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net
 	start := time.Now()
 
 	for {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 
 		select {
 		case <-ctx.Done():
-			return nil, ctx.Err()
+			return nil, 0, ctx.Err()
 		case <-done:
 		}
 	}
diff --git a/app/dns/nameserver_fakedns.go b/app/dns/nameserver_fakedns.go
index ae7a1a7d..37c2f723 100644
--- a/app/dns/nameserver_fakedns.go
+++ b/app/dns/nameserver_fakedns.go
@@ -20,9 +20,9 @@ func (FakeDNSServer) Name() string {
 	return "FakeDNS"
 }
 
-func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, error) {
+func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, uint32, error) {
 	if f.fakeDNSEngine == nil {
-		return nil, errors.New("Unable to locate a fake DNS Engine").AtError()
+		return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
 	}
 
 	var ips []net.Address
@@ -34,13 +34,13 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, op
 
 	netIP, err := toNetIP(ips)
 	if err != nil {
-		return nil, errors.New("Unable to convert IP to net ip").Base(err).AtError()
+		return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError()
 	}
 
 	errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
 
 	if len(netIP) > 0 {
-		return netIP, nil
+		return netIP, 1, nil // fakeIP ttl is 1
 	}
-	return nil, dns.ErrEmptyResponse
+	return nil, 0, dns.ErrEmptyResponse
 }
diff --git a/app/dns/nameserver_local.go b/app/dns/nameserver_local.go
index 14de8356..1b45e5f0 100644
--- a/app/dns/nameserver_local.go
+++ b/app/dns/nameserver_local.go
@@ -21,14 +21,14 @@ type LocalNameServer struct {
 const errEmptyResponse = "No address associated with hostname"
 
 // QueryIP implements Server.
-func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, err error) {
+func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, ttl uint32, err error) {
 	option = ResolveIpOptionOverride(s.queryStrategy, option)
 	if !option.IPv4Enable && !option.IPv6Enable {
-		return nil, dns.ErrEmptyResponse
+		return nil, 0, dns.ErrEmptyResponse
 	}
 
 	start := time.Now()
-	ips, err = s.client.LookupIP(domain, option)
+	ips, ttl, err = s.client.LookupIP(domain, option)
 
 	if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) {
 		err = dns.ErrEmptyResponse
diff --git a/app/dns/nameserver_quic.go b/app/dns/nameserver_quic.go
index f816cf1f..2b0b7b66 100644
--- a/app/dns/nameserver_quic.go
+++ b/app/dns/nameserver_quic.go
@@ -244,64 +244,66 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
 	}
 }
 
-func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
+func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
 	s.RLock()
 	record, found := s.ips[domain]
 	s.RUnlock()
 
 	if !found {
-		return nil, errRecordNotFound
+		return nil, 0, errRecordNotFound
 	}
 
 	var err4 error
 	var err6 error
 	var ips []net.Address
 	var ip6 []net.Address
+	var ttl uint32
 
 	if option.IPv4Enable {
-		ips, err4 = record.A.getIPs()
+		ips, ttl, err4 = record.A.getIPs()
 	}
 
 	if option.IPv6Enable {
-		ip6, err6 = record.AAAA.getIPs()
+		ip6, ttl, err6 = record.AAAA.getIPs()
 		ips = append(ips, ip6...)
 	}
 
 	if len(ips) > 0 {
-		return toNetIP(ips)
+		netips, err := toNetIP(ips)
+		return netips, ttl, err
 	}
 
 	if err4 != nil {
-		return nil, err4
+		return nil, 0, err4
 	}
 
 	if err6 != nil {
-		return nil, err6
+		return nil, 0, err6
 	}
 
 	if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
-		return nil, dns_feature.ErrEmptyResponse
+		return nil, 0, dns_feature.ErrEmptyResponse
 	}
 
-	return nil, errRecordNotFound
+	return nil, 0, errRecordNotFound
 }
 
 // QueryIP is called from dns.Server->queryIPTimeout
-func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
+func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) {
 	fqdn := Fqdn(domain)
 	option = ResolveIpOptionOverride(s.queryStrategy, option)
 	if !option.IPv4Enable && !option.IPv6Enable {
-		return nil, dns_feature.ErrEmptyResponse
+		return nil, 0, dns_feature.ErrEmptyResponse
 	}
 
 	if disableCache {
 		errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
 	} else {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err == nil || err == dns_feature.ErrEmptyResponse {
 			errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 	}
 
@@ -335,15 +337,15 @@ func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP ne
 	start := time.Now()
 
 	for {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 
 		select {
 		case <-ctx.Done():
-			return nil, ctx.Err()
+			return nil, 0, ctx.Err()
 		case <-done:
 		}
 	}
diff --git a/app/dns/nameserver_tcp.go b/app/dns/nameserver_tcp.go
index 35c9a07f..d84974b0 100644
--- a/app/dns/nameserver_tcp.go
+++ b/app/dns/nameserver_tcp.go
@@ -273,60 +273,62 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP n
 	}
 }
 
-func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
+func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
 	s.RLock()
 	record, found := s.ips[domain]
 	s.RUnlock()
 
 	if !found {
-		return nil, errRecordNotFound
+		return nil, 0, errRecordNotFound
 	}
 
 	var err4 error
 	var err6 error
 	var ips []net.Address
 	var ip6 []net.Address
+	var ttl uint32
 
 	if option.IPv4Enable {
-		ips, err4 = record.A.getIPs()
+		ips, ttl, err4 = record.A.getIPs()
 	}
 
 	if option.IPv6Enable {
-		ip6, err6 = record.AAAA.getIPs()
+		ip6, ttl, err6 = record.AAAA.getIPs()
 		ips = append(ips, ip6...)
 	}
 
 	if len(ips) > 0 {
-		return toNetIP(ips)
+		netips, err := toNetIP(ips)
+		return netips, ttl, err
 	}
 
 	if err4 != nil {
-		return nil, err4
+		return nil, 0, err4
 	}
 
 	if err6 != nil {
-		return nil, err6
+		return nil, 0, err6
 	}
 
-	return nil, dns_feature.ErrEmptyResponse
+	return nil, 0, dns_feature.ErrEmptyResponse
 }
 
 // QueryIP implements Server.
-func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
+func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) {
 	fqdn := Fqdn(domain)
 	option = ResolveIpOptionOverride(s.queryStrategy, option)
 	if !option.IPv4Enable && !option.IPv6Enable {
-		return nil, dns_feature.ErrEmptyResponse
+		return nil, 0, dns_feature.ErrEmptyResponse
 	}
 
 	if disableCache {
 		errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
 	} else {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err == nil || err == dns_feature.ErrEmptyResponse {
 			errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 	}
 
@@ -360,15 +362,15 @@ func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net
 	start := time.Now()
 
 	for {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 
 		select {
 		case <-ctx.Done():
-			return nil, ctx.Err()
+			return nil, 0, ctx.Err()
 		case <-done:
 		}
 	}
diff --git a/app/dns/nameserver_udp.go b/app/dns/nameserver_udp.go
index 79592e9b..79df14ed 100644
--- a/app/dns/nameserver_udp.go
+++ b/app/dns/nameserver_udp.go
@@ -230,60 +230,62 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, client
 	}
 }
 
-func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
+func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
 	s.RLock()
 	record, found := s.ips[domain]
 	s.RUnlock()
 
 	if !found {
-		return nil, errRecordNotFound
+		return nil, 0, errRecordNotFound
 	}
 
 	var err4 error
 	var err6 error
 	var ips []net.Address
 	var ip6 []net.Address
+	var ttl uint32
 
 	if option.IPv4Enable {
-		ips, err4 = record.A.getIPs()
+		ips, ttl, err4 = record.A.getIPs()
 	}
 
 	if option.IPv6Enable {
-		ip6, err6 = record.AAAA.getIPs()
+		ip6, ttl, err6 = record.AAAA.getIPs()
 		ips = append(ips, ip6...)
 	}
 
 	if len(ips) > 0 {
-		return toNetIP(ips)
+		netips, err := toNetIP(ips)
+		return netips, ttl, err
 	}
 
 	if err4 != nil {
-		return nil, err4
+		return nil, 0, err4
 	}
 
 	if err6 != nil {
-		return nil, err6
+		return nil, 0, err6
 	}
 
-	return nil, dns_feature.ErrEmptyResponse
+	return nil, 0, dns_feature.ErrEmptyResponse
 }
 
 // QueryIP implements Server.
-func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
+func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) {
 	fqdn := Fqdn(domain)
 	option = ResolveIpOptionOverride(s.queryStrategy, option)
 	if !option.IPv4Enable && !option.IPv6Enable {
-		return nil, dns_feature.ErrEmptyResponse
+		return nil, 0, dns_feature.ErrEmptyResponse
 	}
 
 	if disableCache {
 		errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
 	} else {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err == nil || err == dns_feature.ErrEmptyResponse {
 			errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 	}
 
@@ -317,15 +319,15 @@ func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP
 	start := time.Now()
 
 	for {
-		ips, err := s.findIPsForDomain(fqdn, option)
+		ips, ttl, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 			log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
-			return ips, err
+			return ips, ttl, err
 		}
 
 		select {
 		case <-ctx.Done():
-			return nil, ctx.Err()
+			return nil, 0, ctx.Err()
 		case <-done:
 		}
 	}
diff --git a/features/dns/client.go b/features/dns/client.go
index d3a911da..9e7be91d 100644
--- a/features/dns/client.go
+++ b/features/dns/client.go
@@ -21,7 +21,7 @@ type Client interface {
 	features.Feature
 
 	// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
-	LookupIP(domain string, option IPOption) ([]net.IP, error)
+	LookupIP(domain string, option IPOption) ([]net.IP, uint32, error)
 }
 
 type HostsLookup interface {
diff --git a/features/dns/localdns/client.go b/features/dns/localdns/client.go
index 92419dfa..a94c9474 100644
--- a/features/dns/localdns/client.go
+++ b/features/dns/localdns/client.go
@@ -20,10 +20,10 @@ func (*Client) Start() error { return nil }
 func (*Client) Close() error { return nil }
 
 // LookupIP implements Client.
-func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) {
+func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) {
 	ips, err := net.LookupIP(host)
 	if err != nil {
-		return nil, err
+		return nil, 0, err
 	}
 	parsedIPs := make([]net.IP, 0, len(ips))
 	ipv4 := make([]net.IP, 0, len(ips))
@@ -40,21 +40,22 @@ func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) {
 			ipv6 = append(ipv6, ip)
 		}
 	}
+	// Local DNS ttl is 600
 	switch {
 	case option.IPv4Enable && option.IPv6Enable:
 		if len(parsedIPs) > 0 {
-			return parsedIPs, nil
+			return parsedIPs, 600, nil
 		}
 	case option.IPv4Enable:
 		if len(ipv4) > 0 {
-			return ipv4, nil
+			return ipv4, 600, nil
 		}
 	case option.IPv6Enable:
 		if len(ipv6) > 0 {
-			return ipv6, nil
+			return ipv6, 600, nil
 		}
 	}
-	return nil, dns.ErrEmptyResponse
+	return nil, 0, dns.ErrEmptyResponse
 }
 
 // New create a new dns.Client that queries localhost for DNS.
diff --git a/features/routing/dns/context.go b/features/routing/dns/context.go
index d5b138d7..a65895f6 100644
--- a/features/routing/dns/context.go
+++ b/features/routing/dns/context.go
@@ -23,7 +23,7 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
 	}
 
 	if domain := ctx.GetTargetDomain(); len(domain) != 0 {
-		ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
+		ips, _, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go
index b7a3264a..2344d412 100644
--- a/proxy/dns/dns.go
+++ b/proxy/dns/dns.go
@@ -236,17 +236,18 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
 	var ips []net.IP
 	var err error
 
-	var ttl uint32 = 600
+	var ttl4 uint32
+	var ttl6 uint32
 
 	switch qType {
 	case dnsmessage.TypeA:
-		ips, err = h.client.LookupIP(domain, dns.IPOption{
+		ips, ttl4, err = h.client.LookupIP(domain, dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: false,
 			FakeEnable: true,
 		})
 	case dnsmessage.TypeAAAA:
-		ips, err = h.client.LookupIP(domain, dns.IPOption{
+		ips, ttl6, err = h.client.LookupIP(domain, dns.IPOption{
 			IPv4Enable: false,
 			IPv6Enable: true,
 			FakeEnable: true,
@@ -259,10 +260,6 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
 		return
 	}
 
-	if fkr0, ok := h.fdns.(dns.FakeDNSEngineRev0); ok && len(ips) > 0 && fkr0.IsIPInIPPool(net.IPAddress(ips[0])) {
-		ttl = 1
-	}
-
 	switch qType {
 	case dnsmessage.TypeA:
 		for i, ip := range ips {
@@ -293,16 +290,17 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
 	}))
 	common.Must(builder.StartAnswers())
 
-	rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl}
+	rHeader4 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl4}
+	rHeader6 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl6}
 	for _, ip := range ips {
 		if len(ip) == net.IPv4len {
 			var r dnsmessage.AResource
 			copy(r.A[:], ip)
-			common.Must(builder.AResource(rHeader, r))
+			common.Must(builder.AResource(rHeader4, r))
 		} else {
 			var r dnsmessage.AAAAResource
 			copy(r.AAAA[:], ip)
-			common.Must(builder.AAAAResource(rHeader, r))
+			common.Must(builder.AAAAResource(rHeader6, r))
 		}
 	}
 	msgBytes, err := builder.Finish()
diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go
index 324941ee..779992f7 100644
--- a/proxy/freedom/freedom.go
+++ b/proxy/freedom/freedom.go
@@ -71,13 +71,13 @@ func (h *Handler) policy() policy.Session {
 }
 
 func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
-	ips, err := h.dns.LookupIP(domain, dns.IPOption{
+	ips, _, err := h.dns.LookupIP(domain, dns.IPOption{
 		IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && h.config.preferIP4(),
 		IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && h.config.preferIP6(),
 	})
 	{ // Resolve fallback
 		if (len(ips) == 0 || err != nil) && h.config.hasFallback() && localAddr == nil {
-			ips, err = h.dns.LookupIP(domain, dns.IPOption{
+			ips, _, err = h.dns.LookupIP(domain, dns.IPOption{
 				IPv4Enable: h.config.fallbackIP4(),
 				IPv6Enable: h.config.fallbackIP6(),
 			})
diff --git a/proxy/wireguard/bind.go b/proxy/wireguard/bind.go
index 80ab6a8d..79de7eba 100644
--- a/proxy/wireguard/bind.go
+++ b/proxy/wireguard/bind.go
@@ -54,7 +54,7 @@ func (n *netBind) ParseEndpoint(s string) (conn.Endpoint, error) {
 
 	addr := xnet.ParseAddress(ipStr)
 	if addr.Family() == xnet.AddressFamilyDomain {
-		ips, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)
+		ips, _, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)
 		if err != nil {
 			return nil, err
 		} else if len(ips) == 0 {
diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go
index c26cbf4d..ff1592ef 100644
--- a/proxy/wireguard/client.go
+++ b/proxy/wireguard/client.go
@@ -150,13 +150,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 	// resolve dns
 	addr := destination.Address
 	if addr.Family().IsDomain() {
-		ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
+		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{
+				ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
 					IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
 					IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
 				})
@@ -284,13 +284,13 @@ func (h *Handler) createIPCRequest() string {
 				addr = net.ParseAddress(dialerIp.String())
 				errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
 			} else {
-				ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
+				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{
+						ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
 							IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
 							IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
 						})
diff --git a/transport/internet/dialer.go b/transport/internet/dialer.go
index 3b6fbfd5..554ee5cf 100644
--- a/transport/internet/dialer.go
+++ b/transport/internet/dialer.go
@@ -90,13 +90,13 @@ func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]
 		return nil, nil
 	}
 
-	ips, err := dnsClient.LookupIP(domain, dns.IPOption{
+	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{
+			ips, _, err = dnsClient.LookupIP(domain, dns.IPOption{
 				IPv4Enable: strategy.fallbackIP4(),
 				IPv6Enable: strategy.fallbackIP6(),
 			})



Test patch:
diff --git a/app/dns/dns_test.go b/app/dns/dns_test.go
index 500c057e..ab985384 100644
--- a/app/dns/dns_test.go
+++ b/app/dns/dns_test.go
@@ -155,7 +155,7 @@ func TestUDPServerSubnet(t *testing.T) {
 
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
-	ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+	ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 		FakeEnable: false,
@@ -216,7 +216,7 @@ func TestUDPServer(t *testing.T) {
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 	{
-		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -231,7 +231,7 @@ func TestUDPServer(t *testing.T) {
 	}
 
 	{
-		ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("facebook.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -246,7 +246,7 @@ func TestUDPServer(t *testing.T) {
 	}
 
 	{
-		_, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
+		_, _, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -260,7 +260,7 @@ func TestUDPServer(t *testing.T) {
 	}
 
 	{
-		ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
 			IPv4Enable: false,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -276,7 +276,7 @@ func TestUDPServer(t *testing.T) {
 	dnsServer.Shutdown()
 
 	{
-		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -357,7 +357,7 @@ func TestPrioritizedDomain(t *testing.T) {
 	startTime := time.Now()
 
 	{
-		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -423,7 +423,7 @@ func TestUDPServerIPv6(t *testing.T) {
 
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 	{
-		ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
 			IPv4Enable: false,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -492,7 +492,7 @@ func TestStaticHostDomain(t *testing.T) {
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 	{
-		ips, err := client.LookupIP("example.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("example.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -603,7 +603,7 @@ func TestIPMatch(t *testing.T) {
 	startTime := time.Now()
 
 	{
-		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -726,7 +726,7 @@ func TestLocalDomain(t *testing.T) {
 	startTime := time.Now()
 
 	{ // Will match dotless:
-		ips, err := client.LookupIP("hostname", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("hostname", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -741,7 +741,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match domain:local
-		ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("hostname.local", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -756,7 +756,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match static ip
-		ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -771,7 +771,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match domain replacing
-		ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -786,7 +786,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
-		ips, err := client.LookupIP("localhost", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("localhost", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -801,7 +801,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
-		ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("localhost-a", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -816,7 +816,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
-		ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("localhost-b", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -831,7 +831,7 @@ func TestLocalDomain(t *testing.T) {
 	}
 
 	{ // Will match dotless:
-		ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -997,7 +997,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	startTime := time.Now()
 
 	{ // Will match server 1,2 and server 1 returns expected ip
-		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -1012,7 +1012,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	}
 
 	{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
-		ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: false,
 			FakeEnable: false,
@@ -1027,7 +1027,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	}
 
 	{ // Will match server 3,1,2 and server 3 returns expected one
-		ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("api.google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
@@ -1042,7 +1042,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	}
 
 	{ // Will match server 4,3,1,2 and server 4 returns expected one
-		ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
+		ips, _, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
 			IPv4Enable: true,
 			IPv6Enable: true,
 			FakeEnable: false,
diff --git a/app/dns/nameserver_doh_test.go b/app/dns/nameserver_doh_test.go
index 3dfcb15c..a27a5e9f 100644
--- a/app/dns/nameserver_doh_test.go
+++ b/app/dns/nameserver_doh_test.go
@@ -19,7 +19,7 @@ func TestDOHNameServer(t *testing.T) {
 
 	s := NewDoHNameServer(url, QueryStrategy_USE_IP, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -36,7 +36,7 @@ func TestDOHNameServerWithCache(t *testing.T) {
 
 	s := NewDoHNameServer(url, QueryStrategy_USE_IP, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -47,7 +47,7 @@ func TestDOHNameServerWithCache(t *testing.T) {
 	}
 
 	ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, true)
@@ -64,7 +64,7 @@ func TestDOHNameServerWithIPv4Override(t *testing.T) {
 
 	s := NewDoHNameServer(url, QueryStrategy_USE_IP4, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -87,7 +87,7 @@ func TestDOHNameServerWithIPv6Override(t *testing.T) {
 
 	s := NewDoHNameServer(url, QueryStrategy_USE_IP6, nil, false)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
diff --git a/app/dns/nameserver_local_test.go b/app/dns/nameserver_local_test.go
index 500962f7..a32c69e6 100644
--- a/app/dns/nameserver_local_test.go
+++ b/app/dns/nameserver_local_test.go
@@ -14,7 +14,7 @@ import (
 func TestLocalNameServer(t *testing.T) {
 	s := NewLocalNameServer(QueryStrategy_USE_IP)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 		FakeEnable: false,
diff --git a/app/dns/nameserver_quic_test.go b/app/dns/nameserver_quic_test.go
index 0088aa33..56f9c3ee 100644
--- a/app/dns/nameserver_quic_test.go
+++ b/app/dns/nameserver_quic_test.go
@@ -19,7 +19,7 @@ func TestQUICNameServer(t *testing.T) {
 	s, err := NewQUICNameServer(url, QueryStrategy_USE_IP)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -30,7 +30,7 @@ func TestQUICNameServer(t *testing.T) {
 	}
 
 	ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{
+	ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, true)
@@ -47,7 +47,7 @@ func TestQUICNameServerWithIPv4Override(t *testing.T) {
 	s, err := NewQUICNameServer(url, QueryStrategy_USE_IP4)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -70,7 +70,7 @@ func TestQUICNameServerWithIPv6Override(t *testing.T) {
 	s, err := NewQUICNameServer(url, QueryStrategy_USE_IP6)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
diff --git a/app/dns/nameserver_tcp_test.go b/app/dns/nameserver_tcp_test.go
index 6b2b2465..de4ecb89 100644
--- a/app/dns/nameserver_tcp_test.go
+++ b/app/dns/nameserver_tcp_test.go
@@ -19,7 +19,7 @@ func TestTCPLocalNameServer(t *testing.T) {
 	s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -36,7 +36,7 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
 	s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -47,7 +47,7 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
 	}
 
 	ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, true)
@@ -64,7 +64,7 @@ func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
 	s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP4)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
@@ -88,7 +88,7 @@ func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
 	s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP6)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
+	ips, _, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv6Enable: true,
 	}, false)
diff --git a/app/router/router_test.go b/app/router/router_test.go
index 09f831b2..a0516e05 100644
--- a/app/router/router_test.go
+++ b/app/router/router_test.go
@@ -177,7 +177,7 @@ func TestIPOnDemand(t *testing.T) {
 		IPv4Enable: true,
 		IPv6Enable: true,
 		FakeEnable: false,
-	}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
+	}).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()
 
 	r := new(Router)
 	common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
@@ -222,7 +222,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
 		IPv4Enable: true,
 		IPv6Enable: true,
 		FakeEnable: false,
-	}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
+	}).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()
 
 	r := new(Router)
 	common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
diff --git a/testing/mocks/dns.go b/testing/mocks/dns.go
index 15ddfbf4..fb398366 100644
--- a/testing/mocks/dns.go
+++ b/testing/mocks/dns.go
@@ -50,12 +50,13 @@ func (mr *DNSClientMockRecorder) Close() *gomock.Call {
 }
 
 // LookupIP mocks base method
-func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) {
+func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, uint32, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "LookupIP", arg0, arg1)
 	ret0, _ := ret[0].([]net.IP)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret1, _ := ret[1].(uint32)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
 }
 
 // LookupIP indicates an expected call of LookupIP