mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Add optimistic DNS cache
This commit is contained in:
+21
-11
@@ -3,6 +3,7 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -31,12 +32,13 @@ type DNSClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
LookupStrategy C.DomainStrategy
|
LookupStrategy C.DomainStrategy
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
RewriteTTL *uint32
|
DisableOptimisticCache bool
|
||||||
ClientSubnet netip.Prefix
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||||
@@ -49,11 +51,12 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio
|
|||||||
return nil, E.New("domain resolver not found: " + options.Server)
|
return nil, E.New("domain resolver not found: " + options.Server)
|
||||||
}
|
}
|
||||||
return &DNSQueryOptions{
|
return &DNSQueryOptions{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: C.DomainStrategy(options.Strategy),
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
DisableCache: options.DisableCache,
|
DisableCache: options.DisableCache,
|
||||||
RewriteTTL: options.RewriteTTL,
|
DisableOptimisticCache: options.DisableOptimisticCache,
|
||||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
RewriteTTL: options.RewriteTTL,
|
||||||
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +66,13 @@ type RDRCStore interface {
|
|||||||
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DNSCacheStore interface {
|
||||||
|
LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool)
|
||||||
|
SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error
|
||||||
|
SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger)
|
||||||
|
ClearDNSCache() error
|
||||||
|
}
|
||||||
|
|
||||||
type DNSTransport interface {
|
type DNSTransport interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
|
StoreDNS() bool
|
||||||
|
DNSCacheStore
|
||||||
|
|
||||||
|
SetDisableExpire(disableExpire bool)
|
||||||
|
SetOptimisticTimeout(timeout time.Duration)
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
|
|||||||
@@ -196,7 +196,10 @@ func New(options Options) (*Box, error) {
|
|||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter, err := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize DNS router")
|
||||||
|
}
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
@@ -372,7 +375,7 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, logFactory.NewLogger("cache-file"), common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
internalServices = append(internalServices, cacheFile)
|
internalServices = append(internalServices, cacheFile)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,11 +87,12 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
server = dialOptions.DomainResolver.Server
|
server = dialOptions.DomainResolver.Server
|
||||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
DisableCache: dialOptions.DomainResolver.DisableCache,
|
DisableCache: dialOptions.DomainResolver.DisableCache,
|
||||||
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
DisableOptimisticCache: dialOptions.DomainResolver.DisableOptimisticCache,
|
||||||
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
||||||
|
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||||
}
|
}
|
||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
} else if options.DirectResolver {
|
} else if options.DirectResolver {
|
||||||
|
|||||||
+298
-270
@@ -30,56 +30,63 @@ var (
|
|||||||
var _ adapter.DNSClient = (*Client)(nil)
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
timeout time.Duration
|
ctx context.Context
|
||||||
disableCache bool
|
timeout time.Duration
|
||||||
disableExpire bool
|
disableCache bool
|
||||||
independentCache bool
|
disableExpire bool
|
||||||
clientSubnet netip.Prefix
|
optimisticTimeout time.Duration
|
||||||
rdrc adapter.RDRCStore
|
cacheCapacity uint32
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
clientSubnet netip.Prefix
|
||||||
logger logger.ContextLogger
|
rdrc adapter.RDRCStore
|
||||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
cacheLock compatible.Map[transportCacheKey, chan struct{}]
|
dnsCache adapter.DNSCacheStore
|
||||||
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
initDNSCacheFunc func() adapter.DNSCacheStore
|
||||||
transportCacheLock compatible.Map[transportCacheKey, chan struct{}]
|
logger logger.ContextLogger
|
||||||
|
cache freelru.Cache[dnsCacheKey, *dns.Msg]
|
||||||
|
cacheLock compatible.Map[dnsCacheKey, chan struct{}]
|
||||||
|
backgroundRefresh compatible.Map[dnsCacheKey, struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
Timeout time.Duration
|
Context context.Context
|
||||||
DisableCache bool
|
Timeout time.Duration
|
||||||
DisableExpire bool
|
DisableCache bool
|
||||||
IndependentCache bool
|
DisableExpire bool
|
||||||
CacheCapacity uint32
|
OptimisticTimeout time.Duration
|
||||||
ClientSubnet netip.Prefix
|
CacheCapacity uint32
|
||||||
RDRC func() adapter.RDRCStore
|
ClientSubnet netip.Prefix
|
||||||
Logger logger.ContextLogger
|
RDRC func() adapter.RDRCStore
|
||||||
|
DNSCache func() adapter.DNSCacheStore
|
||||||
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(options ClientOptions) *Client {
|
func NewClient(options ClientOptions) *Client {
|
||||||
|
cacheCapacity := options.CacheCapacity
|
||||||
|
if cacheCapacity < 1024 {
|
||||||
|
cacheCapacity = 1024
|
||||||
|
}
|
||||||
client := &Client{
|
client := &Client{
|
||||||
timeout: options.Timeout,
|
ctx: options.Context,
|
||||||
disableCache: options.DisableCache,
|
timeout: options.Timeout,
|
||||||
disableExpire: options.DisableExpire,
|
disableCache: options.DisableCache,
|
||||||
independentCache: options.IndependentCache,
|
disableExpire: options.DisableExpire,
|
||||||
clientSubnet: options.ClientSubnet,
|
optimisticTimeout: options.OptimisticTimeout,
|
||||||
initRDRCFunc: options.RDRC,
|
cacheCapacity: cacheCapacity,
|
||||||
logger: options.Logger,
|
clientSubnet: options.ClientSubnet,
|
||||||
|
initRDRCFunc: options.RDRC,
|
||||||
|
initDNSCacheFunc: options.DNSCache,
|
||||||
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
if client.timeout == 0 {
|
if client.timeout == 0 {
|
||||||
client.timeout = C.DNSTimeout
|
client.timeout = C.DNSTimeout
|
||||||
}
|
}
|
||||||
cacheCapacity := max(options.CacheCapacity, 1024)
|
if !client.disableCache && client.initDNSCacheFunc == nil {
|
||||||
if !client.disableCache {
|
client.initializeMemoryCache()
|
||||||
if !client.independentCache {
|
|
||||||
client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32))
|
|
||||||
} else {
|
|
||||||
client.transportCache = common.Must1(freelru.NewSharded[transportCacheKey, *dns.Msg](cacheCapacity, maphash.NewHasher[transportCacheKey]().Hash32))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
type transportCacheKey struct {
|
type dnsCacheKey struct {
|
||||||
dns.Question
|
dns.Question
|
||||||
transportTag string
|
transportTag string
|
||||||
}
|
}
|
||||||
@@ -88,6 +95,19 @@ func (c *Client) Start() {
|
|||||||
if c.initRDRCFunc != nil {
|
if c.initRDRCFunc != nil {
|
||||||
c.rdrc = c.initRDRCFunc()
|
c.rdrc = c.initRDRCFunc()
|
||||||
}
|
}
|
||||||
|
if c.initDNSCacheFunc != nil {
|
||||||
|
c.dnsCache = c.initDNSCacheFunc()
|
||||||
|
}
|
||||||
|
if c.dnsCache == nil {
|
||||||
|
c.initializeMemoryCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) initializeMemoryCache() {
|
||||||
|
if c.disableCache || c.cache != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.cache = common.Must1(freelru.NewSharded[dnsCacheKey, *dns.Msg](c.cacheCapacity, maphash.NewHasher[dnsCacheKey]().Hash32))
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
||||||
@@ -104,6 +124,37 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func computeTimeToLive(response *dns.Msg) uint32 {
|
||||||
|
var timeToLive uint32
|
||||||
|
if len(response.Answer) == 0 {
|
||||||
|
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
||||||
|
return soaTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if record.Header().Rrtype == dns.TypeOPT {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||||
|
timeToLive = record.Header().Ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeToLive
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeTTL(response *dns.Msg, timeToLive uint32) {
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if record.Header().Rrtype == dns.TypeOPT {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
record.Header().Ttl = timeToLive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) {
|
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) {
|
||||||
if len(message.Question) == 0 {
|
if len(message.Question) == 0 {
|
||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
@@ -118,13 +169,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
||||||
}
|
}
|
||||||
clientSubnet := options.ClientSubnet
|
message = c.prepareExchangeMessage(message, options)
|
||||||
if !clientSubnet.IsValid() {
|
|
||||||
clientSubnet = c.clientSubnet
|
|
||||||
}
|
|
||||||
if clientSubnet.IsValid() {
|
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
|
||||||
}
|
|
||||||
|
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -136,41 +181,32 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
!options.ClientSubnet.IsValid()
|
!options.ClientSubnet.IsValid()
|
||||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
cacheKey := transportCacheKey{Question: question, transportTag: transport.Tag()}
|
cacheKey := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
||||||
if c.cache != nil {
|
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
||||||
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
if loaded {
|
||||||
if loaded {
|
select {
|
||||||
select {
|
case <-cond:
|
||||||
case <-cond:
|
case <-ctx.Done():
|
||||||
case <-ctx.Done():
|
return nil, ctx.Err()
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defer func() {
|
|
||||||
c.cacheLock.Delete(cacheKey)
|
|
||||||
close(cond)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
} else if c.transportCache != nil {
|
|
||||||
cond, loaded := c.transportCacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
|
||||||
if loaded {
|
|
||||||
select {
|
|
||||||
case <-cond:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defer func() {
|
|
||||||
c.transportCacheLock.Delete(cacheKey)
|
|
||||||
close(cond)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
c.cacheLock.Delete(cacheKey)
|
||||||
|
close(cond)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
response, ttl := c.loadResponse(question, transport)
|
response, ttl, isStale := c.loadResponse(question, transport)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
logCachedResponse(c.logger, ctx, response, ttl)
|
if isStale && !options.DisableOptimisticCache {
|
||||||
response.Id = message.Id
|
c.backgroundRefreshDNS(transport, question, message.Copy(), options, responseChecker)
|
||||||
return response, nil
|
logOptimisticResponse(c.logger, ctx, response)
|
||||||
|
response.Id = message.Id
|
||||||
|
return response, nil
|
||||||
|
} else if !isStale {
|
||||||
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
|
response.Id = message.Id
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,52 +222,10 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return nil, ErrResponseRejectedCached
|
return nil, ErrResponseRejectedCached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
response, err := c.exchangeToTransport(ctx, transport, message)
|
||||||
response, err := transport.Exchange(ctx, message)
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var rcodeError RcodeError
|
return nil, err
|
||||||
if errors.As(err, &rcodeError) {
|
|
||||||
response = FixedResponseStatus(message, int(rcodeError))
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
|
||||||
validResponse := response
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
var (
|
|
||||||
addresses int
|
|
||||||
queryCNAME string
|
|
||||||
)
|
|
||||||
for _, rawRR := range validResponse.Answer {
|
|
||||||
switch rr := rawRR.(type) {
|
|
||||||
case *dns.A:
|
|
||||||
break loop
|
|
||||||
case *dns.AAAA:
|
|
||||||
break loop
|
|
||||||
case *dns.CNAME:
|
|
||||||
queryCNAME = rr.Target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if queryCNAME == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
exMessage := *message
|
|
||||||
exMessage.Question = []dns.Question{{
|
|
||||||
Name: queryCNAME,
|
|
||||||
Qtype: question.Qtype,
|
|
||||||
}}
|
|
||||||
validResponse, err = c.Exchange(ctx, transport, &exMessage, options, responseChecker)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if validResponse != response {
|
|
||||||
response.Answer = append(response.Answer, validResponse.Answer...)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
@@ -248,54 +242,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, ErrResponseRejected
|
return response, ErrResponseRejected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if question.Qtype == dns.TypeHTTPS {
|
timeToLive := applyResponseOptions(question, response, options)
|
||||||
if options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only {
|
|
||||||
for _, rr := range response.Answer {
|
|
||||||
https, isHTTPS := rr.(*dns.HTTPS)
|
|
||||||
if !isHTTPS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content := https.SVCB
|
|
||||||
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
|
||||||
if options.Strategy == C.DomainStrategyIPv4Only {
|
|
||||||
return it.Key() != dns.SVCB_IPV6HINT
|
|
||||||
} else {
|
|
||||||
return it.Key() != dns.SVCB_IPV4HINT
|
|
||||||
}
|
|
||||||
})
|
|
||||||
https.SVCB = content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var timeToLive uint32
|
|
||||||
if len(response.Answer) == 0 {
|
|
||||||
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
|
||||||
timeToLive = soaTTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if timeToLive == 0 {
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
|
||||||
timeToLive = record.Header().Ttl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.RewriteTTL != nil {
|
|
||||||
timeToLive = *options.RewriteTTL
|
|
||||||
}
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = timeToLive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
}
|
}
|
||||||
@@ -362,8 +309,12 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
if c.cache != nil {
|
if c.cache != nil {
|
||||||
c.cache.Purge()
|
c.cache.Purge()
|
||||||
} else if c.transportCache != nil {
|
}
|
||||||
c.transportCache.Purge()
|
if c.dnsCache != nil {
|
||||||
|
err := c.dnsCache.ClearDNSCache()
|
||||||
|
if err != nil && c.logger != nil {
|
||||||
|
c.logger.Warn("clear DNS cache: ", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,24 +330,22 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
if timeToLive == 0 {
|
if timeToLive == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if c.dnsCache != nil {
|
||||||
|
packed, err := message.Pack()
|
||||||
|
if err == nil {
|
||||||
|
expireAt := time.Now().Add(time.Second * time.Duration(timeToLive))
|
||||||
|
c.dnsCache.SaveDNSCacheAsync(transport.Tag(), question.Name, question.Qtype, packed, expireAt, c.logger)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.cache == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
||||||
if c.disableExpire {
|
if c.disableExpire {
|
||||||
if !c.independentCache {
|
c.cache.Add(key, message.Copy())
|
||||||
c.cache.Add(question, message.Copy())
|
|
||||||
} else {
|
|
||||||
c.transportCache.Add(transportCacheKey{
|
|
||||||
Question: question,
|
|
||||||
transportTag: transport.Tag(),
|
|
||||||
}, message.Copy())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if !c.independentCache {
|
c.cache.AddWithLifetime(key, message.Copy(), time.Second*time.Duration(timeToLive))
|
||||||
c.cache.AddWithLifetime(question, message.Copy(), time.Second*time.Duration(timeToLive))
|
|
||||||
} else {
|
|
||||||
c.transportCache.AddWithLifetime(transportCacheKey{
|
|
||||||
Question: question,
|
|
||||||
transportTag: transport.Tag(),
|
|
||||||
}, message.Copy(), time.Second*time.Duration(timeToLive))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,19 +355,19 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
|
|||||||
Qtype: qType,
|
Qtype: qType,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}
|
}
|
||||||
disableCache := c.disableCache || options.DisableCache
|
|
||||||
if !disableCache {
|
|
||||||
cachedAddresses, err := c.questionCache(question, transport)
|
|
||||||
if err != ErrNotCached {
|
|
||||||
return cachedAddresses, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message := dns.Msg{
|
message := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
|
disableCache := c.disableCache || options.DisableCache
|
||||||
|
if !disableCache {
|
||||||
|
cachedAddresses, err := c.questionCache(ctx, transport, &message, options, responseChecker)
|
||||||
|
if err != ErrNotCached {
|
||||||
|
return cachedAddresses, err
|
||||||
|
}
|
||||||
|
}
|
||||||
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -429,95 +378,174 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
|
|||||||
return MessageToAddresses(response), nil
|
return MessageToAddresses(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
func (c *Client) questionCache(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
||||||
response, _ := c.loadResponse(question, transport)
|
question := message.Question[0]
|
||||||
|
response, _, isStale := c.loadResponse(question, transport)
|
||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, ErrNotCached
|
return nil, ErrNotCached
|
||||||
}
|
}
|
||||||
|
if isStale {
|
||||||
|
if options.DisableOptimisticCache {
|
||||||
|
return nil, ErrNotCached
|
||||||
|
}
|
||||||
|
c.backgroundRefreshDNS(transport, question, c.prepareExchangeMessage(message.Copy(), options), options, responseChecker)
|
||||||
|
logOptimisticResponse(c.logger, ctx, response)
|
||||||
|
}
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
if response.Rcode != dns.RcodeSuccess {
|
||||||
return nil, RcodeError(response.Rcode)
|
return nil, RcodeError(response.Rcode)
|
||||||
}
|
}
|
||||||
return MessageToAddresses(response), nil
|
return MessageToAddresses(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) {
|
||||||
var (
|
if c.dnsCache != nil {
|
||||||
response *dns.Msg
|
return c.loadPersistentResponse(question, transport)
|
||||||
loaded bool
|
|
||||||
)
|
|
||||||
if c.disableExpire {
|
|
||||||
if !c.independentCache {
|
|
||||||
response, loaded = c.cache.Get(question)
|
|
||||||
} else {
|
|
||||||
response, loaded = c.transportCache.Get(transportCacheKey{
|
|
||||||
Question: question,
|
|
||||||
transportTag: transport.Tag(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !loaded {
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
return response.Copy(), 0
|
|
||||||
} else {
|
|
||||||
var expireAt time.Time
|
|
||||||
if !c.independentCache {
|
|
||||||
response, expireAt, loaded = c.cache.GetWithLifetime(question)
|
|
||||||
} else {
|
|
||||||
response, expireAt, loaded = c.transportCache.GetWithLifetime(transportCacheKey{
|
|
||||||
Question: question,
|
|
||||||
transportTag: transport.Tag(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !loaded {
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
timeNow := time.Now()
|
|
||||||
if timeNow.After(expireAt) {
|
|
||||||
if !c.independentCache {
|
|
||||||
c.cache.Remove(question)
|
|
||||||
} else {
|
|
||||||
c.transportCache.Remove(transportCacheKey{
|
|
||||||
Question: question,
|
|
||||||
transportTag: transport.Tag(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
var originTTL int
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
|
|
||||||
originTTL = int(record.Header().Ttl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nowTTL := max(int(expireAt.Sub(timeNow).Seconds()), 0)
|
|
||||||
response = response.Copy()
|
|
||||||
if originTTL > 0 {
|
|
||||||
duration := uint32(originTTL - nowTTL)
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = record.Header().Ttl - duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = uint32(nowTTL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response, nowTTL
|
|
||||||
}
|
}
|
||||||
|
if c.cache == nil {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
||||||
|
if c.disableExpire {
|
||||||
|
response, loaded := c.cache.Get(key)
|
||||||
|
if !loaded {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
return response.Copy(), 0, false
|
||||||
|
}
|
||||||
|
response, expireAt, loaded := c.cache.GetWithLifetimeNoExpire(key)
|
||||||
|
if !loaded {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
timeNow := time.Now()
|
||||||
|
if timeNow.After(expireAt) {
|
||||||
|
if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) {
|
||||||
|
response = response.Copy()
|
||||||
|
normalizeTTL(response, 1)
|
||||||
|
return response, 0, true
|
||||||
|
}
|
||||||
|
c.cache.Remove(key)
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
||||||
|
if nowTTL < 0 {
|
||||||
|
nowTTL = 0
|
||||||
|
}
|
||||||
|
response = response.Copy()
|
||||||
|
normalizeTTL(response, uint32(nowTTL))
|
||||||
|
return response, nowTTL, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) loadPersistentResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) {
|
||||||
|
rawMessage, expireAt, loaded := c.dnsCache.LoadDNSCache(transport.Tag(), question.Name, question.Qtype)
|
||||||
|
if !loaded {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
response := new(dns.Msg)
|
||||||
|
err := response.Unpack(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
if c.disableExpire {
|
||||||
|
return response, 0, false
|
||||||
|
}
|
||||||
|
timeNow := time.Now()
|
||||||
|
if timeNow.After(expireAt) {
|
||||||
|
if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) {
|
||||||
|
normalizeTTL(response, 1)
|
||||||
|
return response, 0, true
|
||||||
|
}
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
nowTTL := max(int(expireAt.Sub(timeNow).Seconds()), 0)
|
||||||
|
normalizeTTL(response, uint32(nowTTL))
|
||||||
|
return response, nowTTL, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyResponseOptions(question dns.Question, response *dns.Msg, options adapter.DNSQueryOptions) uint32 {
|
||||||
|
if question.Qtype == dns.TypeHTTPS && (options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only) {
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
https, isHTTPS := rr.(*dns.HTTPS)
|
||||||
|
if !isHTTPS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content := https.SVCB
|
||||||
|
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
|
return it.Key() != dns.SVCB_IPV6HINT
|
||||||
|
}
|
||||||
|
return it.Key() != dns.SVCB_IPV4HINT
|
||||||
|
})
|
||||||
|
https.SVCB = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeToLive := computeTimeToLive(response)
|
||||||
|
if options.RewriteTTL != nil {
|
||||||
|
timeToLive = *options.RewriteTTL
|
||||||
|
}
|
||||||
|
normalizeTTL(response, timeToLive)
|
||||||
|
return timeToLive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) {
|
||||||
|
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
||||||
|
_, loaded := c.backgroundRefresh.LoadOrStore(key, struct{}{})
|
||||||
|
if loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer c.backgroundRefresh.Delete(key)
|
||||||
|
ctx := contextWithTransportTag(c.ctx, transport.Tag())
|
||||||
|
response, err := c.exchangeToTransport(ctx, transport, message)
|
||||||
|
if err != nil {
|
||||||
|
if c.logger != nil {
|
||||||
|
c.logger.Debug("optimistic refresh failed for ", FqdnToDomain(question.Name), ": ", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if responseChecker != nil {
|
||||||
|
var rejected bool
|
||||||
|
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||||
|
rejected = true
|
||||||
|
} else {
|
||||||
|
rejected = !responseChecker(response)
|
||||||
|
}
|
||||||
|
if rejected {
|
||||||
|
if c.rdrc != nil {
|
||||||
|
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeToLive := applyResponseOptions(question, response, options)
|
||||||
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) prepareExchangeMessage(message *dns.Msg, options adapter.DNSQueryOptions) *dns.Msg {
|
||||||
|
clientSubnet := options.ClientSubnet
|
||||||
|
if !clientSubnet.IsValid() {
|
||||||
|
clientSubnet = c.clientSubnet
|
||||||
|
}
|
||||||
|
if clientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) exchangeToTransport(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg) (*dns.Msg, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
|
defer cancel()
|
||||||
|
response, err := transport.Exchange(ctx, message)
|
||||||
|
if err == nil {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
var rcodeError RcodeError
|
||||||
|
if errors.As(err, &rcodeError) {
|
||||||
|
return FixedResponseStatus(message, int(rcodeError)), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ func logCachedResponse(logger logger.ContextLogger, ctx context.Context, respons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logOptimisticResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) {
|
||||||
|
if logger == nil || len(response.Question) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domain := FqdnToDomain(response.Question[0].Name)
|
||||||
|
logger.DebugContext(ctx, "optimistic ", domain, " ", dns.RcodeToString[response.Rcode])
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
logger.InfoContext(ctx, "optimistic ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
||||||
if logger == nil || len(response.Question) == 0 {
|
if logger == nil || len(response.Question) == 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
+55
-9
@@ -51,7 +51,7 @@ type Router struct {
|
|||||||
closing bool
|
closing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) (*Router, error) {
|
||||||
router := &Router{
|
router := &Router{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logFactory.NewLogger("dns"),
|
logger: logFactory.NewLogger("dns"),
|
||||||
@@ -61,12 +61,30 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
||||||
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
||||||
}
|
}
|
||||||
|
if options.DNSClientOptions.IndependentCache {
|
||||||
|
deprecated.Report(ctx, deprecated.OptionIndependentDNSCache)
|
||||||
|
}
|
||||||
|
var optimisticTimeout time.Duration
|
||||||
|
optimisticOptions := common.PtrValueOrDefault(options.DNSClientOptions.Optimistic)
|
||||||
|
if optimisticOptions.Enabled {
|
||||||
|
if options.DNSClientOptions.DisableCache {
|
||||||
|
return nil, E.New("`optimistic` is conflict with `disable_cache`")
|
||||||
|
}
|
||||||
|
if options.DNSClientOptions.DisableExpire {
|
||||||
|
return nil, E.New("`optimistic` is conflict with `disable_expire`")
|
||||||
|
}
|
||||||
|
optimisticTimeout = time.Duration(optimisticOptions.Timeout)
|
||||||
|
if optimisticTimeout == 0 {
|
||||||
|
optimisticTimeout = 3 * 24 * time.Hour
|
||||||
|
}
|
||||||
|
}
|
||||||
router.client = NewClient(ClientOptions{
|
router.client = NewClient(ClientOptions{
|
||||||
DisableCache: options.DNSClientOptions.DisableCache,
|
Context: ctx,
|
||||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
DisableCache: options.DNSClientOptions.DisableCache,
|
||||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
OptimisticTimeout: optimisticTimeout,
|
||||||
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
|
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||||
RDRC: func() adapter.RDRCStore {
|
RDRC: func() adapter.RDRCStore {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
if cacheFile == nil {
|
if cacheFile == nil {
|
||||||
@@ -77,12 +95,24 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
}
|
}
|
||||||
return cacheFile
|
return cacheFile
|
||||||
},
|
},
|
||||||
|
DNSCache: func() adapter.DNSCacheStore {
|
||||||
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
|
if cacheFile == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !cacheFile.StoreDNS() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cacheFile.SetDisableExpire(options.DNSClientOptions.DisableExpire)
|
||||||
|
cacheFile.SetOptimisticTimeout(optimisticTimeout)
|
||||||
|
return cacheFile
|
||||||
|
},
|
||||||
Logger: router.logger,
|
Logger: router.logger,
|
||||||
})
|
})
|
||||||
if options.ReverseMapping {
|
if options.ReverseMapping {
|
||||||
router.dnsReverseMapping = common.Must1(freelru.NewSharded[netip.Addr, string](1024, maphash.NewHasher[netip.Addr]().Hash32))
|
router.dnsReverseMapping = common.Must1(freelru.NewSharded[netip.Addr, string](1024, maphash.NewHasher[netip.Addr]().Hash32))
|
||||||
}
|
}
|
||||||
return router
|
return router, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Initialize(rules []option.DNSRule) error {
|
func (r *Router) Initialize(rules []option.DNSRule) error {
|
||||||
@@ -319,6 +349,9 @@ func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOpt
|
|||||||
if routeOptions.DisableCache {
|
if routeOptions.DisableCache {
|
||||||
options.DisableCache = true
|
options.DisableCache = true
|
||||||
}
|
}
|
||||||
|
if routeOptions.DisableOptimisticCache {
|
||||||
|
options.DisableOptimisticCache = true
|
||||||
|
}
|
||||||
if routeOptions.RewriteTTL != nil {
|
if routeOptions.RewriteTTL != nil {
|
||||||
options.RewriteTTL = routeOptions.RewriteTTL
|
options.RewriteTTL = routeOptions.RewriteTTL
|
||||||
}
|
}
|
||||||
@@ -907,7 +940,9 @@ func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule, m
|
|||||||
return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions, metadataOverrides)
|
return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions, metadataOverrides)
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
flags := dnsRuleModeFlags{
|
flags := dnsRuleModeFlags{
|
||||||
disabled: dnsRuleActionType(rule) == C.RuleActionTypeEvaluate || dnsRuleActionType(rule) == C.RuleActionTypeRespond,
|
disabled: dnsRuleActionType(rule) == C.RuleActionTypeEvaluate ||
|
||||||
|
dnsRuleActionType(rule) == C.RuleActionTypeRespond ||
|
||||||
|
dnsRuleActionDisablesLegacyDNSMode(rule.LogicalOptions.DNSRuleAction),
|
||||||
neededFromStrategy: dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction),
|
neededFromStrategy: dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction),
|
||||||
}
|
}
|
||||||
flags.needed = flags.neededFromStrategy
|
flags.needed = flags.neededFromStrategy
|
||||||
@@ -926,7 +961,7 @@ func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule, m
|
|||||||
|
|
||||||
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
||||||
flags := dnsRuleModeFlags{
|
flags := dnsRuleModeFlags{
|
||||||
disabled: defaultRuleDisablesLegacyDNSMode(rule),
|
disabled: defaultRuleDisablesLegacyDNSMode(rule) || dnsRuleActionDisablesLegacyDNSMode(rule.DNSRuleAction),
|
||||||
neededFromStrategy: dnsRuleActionHasStrategy(rule.DNSRuleAction),
|
neededFromStrategy: dnsRuleActionHasStrategy(rule.DNSRuleAction),
|
||||||
}
|
}
|
||||||
flags.needed = defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || flags.neededFromStrategy
|
flags.needed = defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || flags.neededFromStrategy
|
||||||
@@ -1063,6 +1098,17 @@ func validateLegacyDNSModeDisabledDefaultRule(rule option.DefaultDNSRule) (bool,
|
|||||||
return rule.MatchResponse || rule.Action == C.RuleActionTypeRespond, nil
|
return rule.MatchResponse || rule.Action == C.RuleActionTypeRespond, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dnsRuleActionDisablesLegacyDNSMode(action option.DNSRuleAction) bool {
|
||||||
|
switch action.Action {
|
||||||
|
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||||
|
return action.RouteOptions.DisableOptimisticCache
|
||||||
|
case C.RuleActionTypeRouteOptions:
|
||||||
|
return action.RouteOptionsOptions.DisableOptimisticCache
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
||||||
switch action.Action {
|
switch action.Action {
|
||||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
|
:material-delete-clock: [independent_cache](#independent_cache)
|
||||||
|
:material-plus: [optimistic](#optimistic)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
:material-decagram: [servers](#servers)
|
||||||
@@ -25,6 +30,7 @@ icon: material/alert-decagram
|
|||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"independent_cache": false,
|
"independent_cache": false,
|
||||||
"cache_capacity": 0,
|
"cache_capacity": 0,
|
||||||
|
"optimistic": false, // or {}
|
||||||
"reverse_mapping": false,
|
"reverse_mapping": false,
|
||||||
"client_subnet": "",
|
"client_subnet": "",
|
||||||
"fakeip": {}
|
"fakeip": {}
|
||||||
@@ -57,12 +63,20 @@ One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|||||||
|
|
||||||
Disable dns cache.
|
Disable dns cache.
|
||||||
|
|
||||||
|
Conflict with `optimistic`.
|
||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
Disable dns cache expire.
|
Disable dns cache expire.
|
||||||
|
|
||||||
|
Conflict with `optimistic`.
|
||||||
|
|
||||||
#### independent_cache
|
#### independent_cache
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.14.0"
|
||||||
|
|
||||||
|
`independent_cache` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-independent-dns-cache).
|
||||||
|
|
||||||
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
||||||
|
|
||||||
#### cache_capacity
|
#### cache_capacity
|
||||||
@@ -73,6 +87,34 @@ LRU cache capacity.
|
|||||||
|
|
||||||
Value less than 1024 will be ignored.
|
Value less than 1024 will be ignored.
|
||||||
|
|
||||||
|
#### optimistic
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
Enable optimistic DNS caching. When a cached DNS entry has expired but is still within the timeout window,
|
||||||
|
the stale response is returned immediately while a background refresh is triggered.
|
||||||
|
|
||||||
|
Conflict with `disable_cache` and `disable_expire`.
|
||||||
|
|
||||||
|
Accepts a boolean or an object. When set to `true`, the default timeout of `3d` is used.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"timeout": "3d"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### enabled
|
||||||
|
|
||||||
|
Enable optimistic DNS caching.
|
||||||
|
|
||||||
|
##### timeout
|
||||||
|
|
||||||
|
The maximum time an expired cache entry can be served optimistically.
|
||||||
|
|
||||||
|
`3d` is used by default.
|
||||||
|
|
||||||
#### reverse_mapping
|
#### reverse_mapping
|
||||||
|
|
||||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.14.0 中的更改"
|
||||||
|
|
||||||
|
:material-delete-clock: [independent_cache](#independent_cache)
|
||||||
|
:material-plus: [optimistic](#optimistic)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
:material-decagram: [servers](#servers)
|
||||||
@@ -25,6 +30,7 @@ icon: material/alert-decagram
|
|||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"independent_cache": false,
|
"independent_cache": false,
|
||||||
"cache_capacity": 0,
|
"cache_capacity": 0,
|
||||||
|
"optimistic": false, // or {}
|
||||||
"reverse_mapping": false,
|
"reverse_mapping": false,
|
||||||
"client_subnet": "",
|
"client_subnet": "",
|
||||||
"fakeip": {}
|
"fakeip": {}
|
||||||
@@ -56,12 +62,20 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
禁用 DNS 缓存。
|
禁用 DNS 缓存。
|
||||||
|
|
||||||
|
与 `optimistic` 冲突。
|
||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
禁用 DNS 缓存过期。
|
禁用 DNS 缓存过期。
|
||||||
|
|
||||||
|
与 `optimistic` 冲突。
|
||||||
|
|
||||||
#### independent_cache
|
#### independent_cache
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||||
|
|
||||||
|
`independent_cache` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除,参阅[迁移指南](/zh/migration/#迁移-independent-dns-cache)。
|
||||||
|
|
||||||
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
||||||
|
|
||||||
#### cache_capacity
|
#### cache_capacity
|
||||||
@@ -72,6 +86,34 @@ LRU 缓存容量。
|
|||||||
|
|
||||||
小于 1024 的值将被忽略。
|
小于 1024 的值将被忽略。
|
||||||
|
|
||||||
|
#### optimistic
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
启用乐观 DNS 缓存。当缓存的 DNS 条目已过期但仍在超时窗口内时,
|
||||||
|
立即返回过期的响应,同时在后台触发刷新。
|
||||||
|
|
||||||
|
与 `disable_cache` 和 `disable_expire` 冲突。
|
||||||
|
|
||||||
|
接受布尔值或对象。当设置为 `true` 时,使用默认超时 `3d`。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"timeout": "3d"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### enabled
|
||||||
|
|
||||||
|
启用乐观 DNS 缓存。
|
||||||
|
|
||||||
|
##### timeout
|
||||||
|
|
||||||
|
过期缓存条目可被乐观提供的最长时间。
|
||||||
|
|
||||||
|
默认使用 `3d`。
|
||||||
|
|
||||||
#### reverse_mapping
|
#### reverse_mapping
|
||||||
|
|
||||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-delete-clock: [strategy](#strategy)
|
:material-delete-clock: [strategy](#strategy)
|
||||||
:material-plus: [evaluate](#evaluate)
|
:material-plus: [evaluate](#evaluate)
|
||||||
:material-plus: [respond](#respond)
|
:material-plus: [respond](#respond)
|
||||||
|
:material-plus: [disable_optimistic_cache](#disable_optimistic_cache)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ icon: material/new-box
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,12 @@ One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
|
#### disable_optimistic_cache
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
Disable optimistic DNS caching in this query.
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
Rewrite TTL in DNS responses.
|
||||||
@@ -73,6 +81,7 @@ Will override `dns.client_subnet`.
|
|||||||
"action": "evaluate",
|
"action": "evaluate",
|
||||||
"server": "",
|
"server": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -97,6 +106,12 @@ Tag of target server.
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
|
#### disable_optimistic_cache
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
Disable optimistic DNS caching in this query.
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
Rewrite TTL in DNS responses.
|
||||||
@@ -131,6 +146,7 @@ Only allowed after a preceding top-level `evaluate` rule. If the action is reach
|
|||||||
{
|
{
|
||||||
"action": "route-options",
|
"action": "route-options",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-delete-clock: [strategy](#strategy)
|
:material-delete-clock: [strategy](#strategy)
|
||||||
:material-plus: [evaluate](#evaluate)
|
:material-plus: [evaluate](#evaluate)
|
||||||
:material-plus: [respond](#respond)
|
:material-plus: [respond](#respond)
|
||||||
|
:material-plus: [disable_optimistic_cache](#disable_optimistic_cache)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ icon: material/new-box
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,12 @@ icon: material/new-box
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
|
#### disable_optimistic_cache
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
在此查询中禁用乐观 DNS 缓存。
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
重写 DNS 回应中的 TTL。
|
||||||
@@ -73,6 +81,7 @@ icon: material/new-box
|
|||||||
"action": "evaluate",
|
"action": "evaluate",
|
||||||
"server": "",
|
"server": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -95,6 +104,12 @@ icon: material/new-box
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
|
#### disable_optimistic_cache
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
在此查询中禁用乐观 DNS 缓存。
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
重写 DNS 回应中的 TTL。
|
||||||
@@ -129,6 +144,7 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"action": "route-options",
|
"action": "route-options",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
|
:material-delete-clock: [store_rdrc](#store_rdrc)
|
||||||
|
:material-plus: [store_dns](#store_dns)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [store_rdrc](#store_rdrc)
|
:material-plus: [store_rdrc](#store_rdrc)
|
||||||
@@ -14,7 +19,8 @@
|
|||||||
"cache_id": "",
|
"cache_id": "",
|
||||||
"store_fakeip": false,
|
"store_fakeip": false,
|
||||||
"store_rdrc": false,
|
"store_rdrc": false,
|
||||||
"rdrc_timeout": ""
|
"rdrc_timeout": "",
|
||||||
|
"store_dns": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,6 +48,10 @@ Store fakeip in the cache file
|
|||||||
|
|
||||||
#### store_rdrc
|
#### store_rdrc
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.14.0"
|
||||||
|
|
||||||
|
`store_rdrc` is deprecated and will be removed in sing-box 1.16.0, check [Migration](/migration/#migrate-store-rdrc).
|
||||||
|
|
||||||
Store rejected DNS response cache in the cache file
|
Store rejected DNS response cache in the cache file
|
||||||
|
|
||||||
The check results of [Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields)
|
The check results of [Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields)
|
||||||
@@ -52,3 +62,9 @@ will be cached until expiration.
|
|||||||
Timeout of rejected DNS response cache.
|
Timeout of rejected DNS response cache.
|
||||||
|
|
||||||
`7d` is used by default.
|
`7d` is used by default.
|
||||||
|
|
||||||
|
#### store_dns
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
Store DNS cache in the cache file.
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.14.0 中的更改"
|
||||||
|
|
||||||
|
:material-delete-clock: [store_rdrc](#store_rdrc)
|
||||||
|
:material-plus: [store_dns](#store_dns)
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [store_rdrc](#store_rdrc)
|
:material-plus: [store_rdrc](#store_rdrc)
|
||||||
@@ -14,7 +19,8 @@
|
|||||||
"cache_id": "",
|
"cache_id": "",
|
||||||
"store_fakeip": false,
|
"store_fakeip": false,
|
||||||
"store_rdrc": false,
|
"store_rdrc": false,
|
||||||
"rdrc_timeout": ""
|
"rdrc_timeout": "",
|
||||||
|
"store_dns": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -40,6 +46,10 @@
|
|||||||
|
|
||||||
#### store_rdrc
|
#### store_rdrc
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||||
|
|
||||||
|
`store_rdrc` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除,参阅[迁移指南](/zh/migration/#迁移-store_rdrc)。
|
||||||
|
|
||||||
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
||||||
|
|
||||||
[旧版地址筛选字段](/zh/configuration/dns/rule/#旧版地址筛选字段) 的检查结果将被缓存至过期。
|
[旧版地址筛选字段](/zh/configuration/dns/rule/#旧版地址筛选字段) 的检查结果将被缓存至过期。
|
||||||
@@ -49,3 +59,9 @@
|
|||||||
拒绝的 DNS 响应缓存超时。
|
拒绝的 DNS 响应缓存超时。
|
||||||
|
|
||||||
默认使用 `7d`。
|
默认使用 `7d`。
|
||||||
|
|
||||||
|
#### store_dns
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
将 DNS 缓存存储在缓存文件中。
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ icon: material/new-box
|
|||||||
:material-plus: [bypass](#bypass)
|
:material-plus: [bypass](#bypass)
|
||||||
:material-alert: [reject](#reject)
|
:material-alert: [reject](#reject)
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
|
:material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
@@ -279,6 +283,7 @@ Timeout for sniffing.
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -302,6 +307,12 @@ DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ip
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
|
#### disable_optimistic_cache
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
Disable optimistic DNS caching in this query.
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ icon: material/new-box
|
|||||||
:material-plus: [bypass](#bypass)
|
:material-plus: [bypass](#bypass)
|
||||||
:material-alert: [reject](#reject)
|
:material-alert: [reject](#reject)
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.14.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
@@ -268,6 +272,7 @@ UDP 连接超时时间。
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
|
"disable_optimistic_cache": false,
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -291,6 +296,12 @@ DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
|
#### disable_optimistic_cache
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
在此查询中禁用乐观 DNS 缓存。
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|||||||
@@ -27,6 +27,21 @@ check [Migration](../migration/#migrate-address-filter-fields-to-response-matchi
|
|||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
Old fields will be removed in sing-box 1.16.0.
|
||||||
|
|
||||||
|
#### `independent_cache` DNS option
|
||||||
|
|
||||||
|
`independent_cache` DNS option is deprecated.
|
||||||
|
The DNS cache now always keys by transport, making this option unnecessary,
|
||||||
|
check [Migration](../migration/#migrate-independent-dns-cache).
|
||||||
|
|
||||||
|
Old fields will be removed in sing-box 1.16.0.
|
||||||
|
|
||||||
|
#### `store_rdrc` cache file option
|
||||||
|
|
||||||
|
`store_rdrc` cache file option is deprecated,
|
||||||
|
check [Migration](../migration/#migrate-store-rdrc).
|
||||||
|
|
||||||
|
Old fields will be removed in sing-box 1.16.0.
|
||||||
|
|
||||||
#### Legacy Address Filter Fields in DNS rules
|
#### Legacy Address Filter Fields in DNS rules
|
||||||
|
|
||||||
Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`)
|
Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`)
|
||||||
|
|||||||
@@ -27,6 +27,21 @@ TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
|||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
旧字段将在 sing-box 1.16.0 中被移除。
|
||||||
|
|
||||||
|
#### `independent_cache` DNS 选项
|
||||||
|
|
||||||
|
`independent_cache` DNS 选项已废弃。
|
||||||
|
DNS 缓存现在始终按传输分离,使此选项不再需要,
|
||||||
|
参阅[迁移指南](/zh/migration/#迁移-independent-dns-cache)。
|
||||||
|
|
||||||
|
旧字段将在 sing-box 1.16.0 中被移除。
|
||||||
|
|
||||||
|
#### `store_rdrc` 缓存文件选项
|
||||||
|
|
||||||
|
`store_rdrc` 缓存文件选项已废弃,
|
||||||
|
参阅[迁移指南](/zh/migration/#迁移-store_rdrc)。
|
||||||
|
|
||||||
|
旧字段将在 sing-box 1.16.0 中被移除。
|
||||||
|
|
||||||
#### 旧版地址筛选字段 (DNS 规则)
|
#### 旧版地址筛选字段 (DNS 规则)
|
||||||
|
|
||||||
旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
||||||
|
|||||||
@@ -137,6 +137,68 @@ to fetch a DNS response, then match against it explicitly with `match_response`.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Migrate independent DNS cache
|
||||||
|
|
||||||
|
The DNS cache now always keys by transport name, making `independent_cache` unnecessary.
|
||||||
|
Simply remove the field.
|
||||||
|
|
||||||
|
!!! info "References"
|
||||||
|
|
||||||
|
[DNS](/configuration/dns/)
|
||||||
|
|
||||||
|
=== ":material-card-remove: Deprecated"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"independent_cache": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: New"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migrate store_rdrc
|
||||||
|
|
||||||
|
`store_rdrc` is deprecated and can be replaced by `store_dns`,
|
||||||
|
which persists the full DNS cache to the cache file.
|
||||||
|
|
||||||
|
!!! info "References"
|
||||||
|
|
||||||
|
[Cache File](/configuration/experimental/cache-file/)
|
||||||
|
|
||||||
|
=== ":material-card-remove: Deprecated"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"experimental": {
|
||||||
|
"cache_file": {
|
||||||
|
"enabled": true,
|
||||||
|
"store_rdrc": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: New"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"experimental": {
|
||||||
|
"cache_file": {
|
||||||
|
"enabled": true,
|
||||||
|
"store_dns": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### ip_version and query_type behavior changes in DNS rules
|
### ip_version and query_type behavior changes in DNS rules
|
||||||
|
|
||||||
In sing-box 1.14.0, the behavior of
|
In sing-box 1.14.0, the behavior of
|
||||||
|
|||||||
@@ -137,6 +137,68 @@ sing-box 1.14.0 新增字段参阅 [ACME](/zh/configuration/shared/certificate-p
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 迁移 independent DNS cache
|
||||||
|
|
||||||
|
DNS 缓存现在始终按传输名称分离,使 `independent_cache` 不再需要。
|
||||||
|
直接移除该字段即可。
|
||||||
|
|
||||||
|
!!! info "参考"
|
||||||
|
|
||||||
|
[DNS](/zh/configuration/dns/)
|
||||||
|
|
||||||
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"independent_cache": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 迁移 store_rdrc
|
||||||
|
|
||||||
|
`store_rdrc` 已废弃,且可以被 `store_dns` 替代,
|
||||||
|
后者将完整的 DNS 缓存持久化到缓存文件中。
|
||||||
|
|
||||||
|
!!! info "参考"
|
||||||
|
|
||||||
|
[缓存文件](/zh/configuration/experimental/cache-file/)
|
||||||
|
|
||||||
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"experimental": {
|
||||||
|
"cache_file": {
|
||||||
|
"enabled": true,
|
||||||
|
"store_rdrc": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"experimental": {
|
||||||
|
"cache_file": {
|
||||||
|
"enabled": true,
|
||||||
|
"store_dns": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### DNS 规则中的 ip_version 和 query_type 行为更改
|
### DNS 规则中的 ip_version 和 query_type 行为更改
|
||||||
|
|
||||||
在 sing-box 1.14.0 中,DNS 规则中的
|
在 sing-box 1.14.0 中,DNS 规则中的
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import (
|
|||||||
"github.com/sagernet/bbolt"
|
"github.com/sagernet/bbolt"
|
||||||
bboltErrors "github.com/sagernet/bbolt/errors"
|
bboltErrors "github.com/sagernet/bbolt/errors"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ var (
|
|||||||
string(bucketMode),
|
string(bucketMode),
|
||||||
string(bucketRuleSet),
|
string(bucketRuleSet),
|
||||||
string(bucketRDRC),
|
string(bucketRDRC),
|
||||||
|
string(bucketDNSCache),
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheIDDefault = []byte("default")
|
cacheIDDefault = []byte("default")
|
||||||
@@ -39,11 +42,15 @@ var _ adapter.CacheFile = (*CacheFile)(nil)
|
|||||||
|
|
||||||
type CacheFile struct {
|
type CacheFile struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
logger logger.Logger
|
||||||
path string
|
path string
|
||||||
cacheID []byte
|
cacheID []byte
|
||||||
storeFakeIP bool
|
storeFakeIP bool
|
||||||
storeRDRC bool
|
storeRDRC bool
|
||||||
|
storeDNS bool
|
||||||
|
disableExpire bool
|
||||||
rdrcTimeout time.Duration
|
rdrcTimeout time.Duration
|
||||||
|
optimisticTimeout time.Duration
|
||||||
DB *bbolt.DB
|
DB *bbolt.DB
|
||||||
resetAccess sync.Mutex
|
resetAccess sync.Mutex
|
||||||
saveMetadataAccess sync.Mutex
|
saveMetadataAccess sync.Mutex
|
||||||
@@ -54,16 +61,25 @@ type CacheFile struct {
|
|||||||
saveAddress4 map[string]netip.Addr
|
saveAddress4 map[string]netip.Addr
|
||||||
saveAddress6 map[string]netip.Addr
|
saveAddress6 map[string]netip.Addr
|
||||||
saveRDRCAccess sync.RWMutex
|
saveRDRCAccess sync.RWMutex
|
||||||
saveRDRC map[saveRDRCCacheKey]bool
|
saveRDRC map[saveCacheKey]bool
|
||||||
|
saveDNSCacheAccess sync.RWMutex
|
||||||
|
saveDNSCache map[saveCacheKey]saveDNSCacheEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
type saveRDRCCacheKey struct {
|
type saveCacheKey struct {
|
||||||
TransportName string
|
TransportName string
|
||||||
QuestionName string
|
QuestionName string
|
||||||
QType uint16
|
QType uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
type saveDNSCacheEntry struct {
|
||||||
|
rawMessage []byte
|
||||||
|
expireAt time.Time
|
||||||
|
sequence uint64
|
||||||
|
saving bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, logger logger.Logger, options option.CacheFileOptions) *CacheFile {
|
||||||
var path string
|
var path string
|
||||||
if options.Path != "" {
|
if options.Path != "" {
|
||||||
path = options.Path
|
path = options.Path
|
||||||
@@ -74,6 +90,9 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
|||||||
if options.CacheID != "" {
|
if options.CacheID != "" {
|
||||||
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
||||||
}
|
}
|
||||||
|
if options.StoreRDRC {
|
||||||
|
deprecated.Report(ctx, deprecated.OptionStoreRDRC)
|
||||||
|
}
|
||||||
var rdrcTimeout time.Duration
|
var rdrcTimeout time.Duration
|
||||||
if options.StoreRDRC {
|
if options.StoreRDRC {
|
||||||
if options.RDRCTimeout > 0 {
|
if options.RDRCTimeout > 0 {
|
||||||
@@ -84,15 +103,18 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
|||||||
}
|
}
|
||||||
return &CacheFile{
|
return &CacheFile{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
path: filemanager.BasePath(ctx, path),
|
path: filemanager.BasePath(ctx, path),
|
||||||
cacheID: cacheIDBytes,
|
cacheID: cacheIDBytes,
|
||||||
storeFakeIP: options.StoreFakeIP,
|
storeFakeIP: options.StoreFakeIP,
|
||||||
storeRDRC: options.StoreRDRC,
|
storeRDRC: options.StoreRDRC,
|
||||||
|
storeDNS: options.StoreDNS,
|
||||||
rdrcTimeout: rdrcTimeout,
|
rdrcTimeout: rdrcTimeout,
|
||||||
saveDomain: make(map[netip.Addr]string),
|
saveDomain: make(map[netip.Addr]string),
|
||||||
saveAddress4: make(map[string]netip.Addr),
|
saveAddress4: make(map[string]netip.Addr),
|
||||||
saveAddress6: make(map[string]netip.Addr),
|
saveAddress6: make(map[string]netip.Addr),
|
||||||
saveRDRC: make(map[saveRDRCCacheKey]bool),
|
saveRDRC: make(map[saveCacheKey]bool),
|
||||||
|
saveDNSCache: make(map[saveCacheKey]saveDNSCacheEntry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,10 +126,44 @@ func (c *CacheFile) Dependencies() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) SetOptimisticTimeout(timeout time.Duration) {
|
||||||
|
c.optimisticTimeout = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) SetDisableExpire(disableExpire bool) {
|
||||||
|
c.disableExpire = disableExpire
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CacheFile) Start(stage adapter.StartStage) error {
|
func (c *CacheFile) Start(stage adapter.StartStage) error {
|
||||||
if stage != adapter.StartStateInitialize {
|
switch stage {
|
||||||
return nil
|
case adapter.StartStateInitialize:
|
||||||
|
return c.start()
|
||||||
|
case adapter.StartStateStart:
|
||||||
|
c.startCacheCleanup()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) startCacheCleanup() {
|
||||||
|
if c.storeDNS {
|
||||||
|
c.clearRDRC()
|
||||||
|
c.cleanupDNSCache()
|
||||||
|
interval := c.optimisticTimeout / 2
|
||||||
|
if interval <= 0 {
|
||||||
|
interval = time.Hour
|
||||||
|
}
|
||||||
|
go c.loopCacheCleanup(interval, c.cleanupDNSCache)
|
||||||
|
} else if c.storeRDRC {
|
||||||
|
c.cleanupRDRC()
|
||||||
|
interval := c.rdrcTimeout / 2
|
||||||
|
if interval <= 0 {
|
||||||
|
interval = time.Hour
|
||||||
|
}
|
||||||
|
go c.loopCacheCleanup(interval, c.cleanupRDRC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) start() error {
|
||||||
const fileMode = 0o666
|
const fileMode = 0o666
|
||||||
options := bbolt.Options{Timeout: time.Second}
|
options := bbolt.Options{Timeout: time.Second}
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
package cachefile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/bbolt"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bucketDNSCache = []byte("dns_cache")
|
||||||
|
|
||||||
|
func (c *CacheFile) StoreDNS() bool {
|
||||||
|
return c.storeDNS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool) {
|
||||||
|
c.saveDNSCacheAccess.RLock()
|
||||||
|
entry, cached := c.saveDNSCache[saveCacheKey{transportName, qName, qType}]
|
||||||
|
c.saveDNSCacheAccess.RUnlock()
|
||||||
|
if cached {
|
||||||
|
return entry.rawMessage, entry.expireAt, true
|
||||||
|
}
|
||||||
|
key := buf.Get(2 + len(qName))
|
||||||
|
binary.BigEndian.PutUint16(key, qType)
|
||||||
|
copy(key[2:], qName)
|
||||||
|
defer buf.Put(key)
|
||||||
|
err := c.view(func(tx *bbolt.Tx) error {
|
||||||
|
bucket := c.bucket(tx, bucketDNSCache)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bucket = bucket.Bucket([]byte(transportName))
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
content := bucket.Get(key)
|
||||||
|
if len(content) < 8 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
expireAt = time.Unix(int64(binary.BigEndian.Uint64(content[:8])), 0)
|
||||||
|
rawMessage = make([]byte, len(content)-8)
|
||||||
|
copy(rawMessage, content[8:])
|
||||||
|
loaded = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error {
|
||||||
|
return c.batch(func(tx *bbolt.Tx) error {
|
||||||
|
bucket, err := c.createBucket(tx, bucketDNSCache)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bucket, err = bucket.CreateBucketIfNotExists([]byte(transportName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := buf.Get(2 + len(qName))
|
||||||
|
binary.BigEndian.PutUint16(key, qType)
|
||||||
|
copy(key[2:], qName)
|
||||||
|
defer buf.Put(key)
|
||||||
|
value := buf.Get(8 + len(rawMessage))
|
||||||
|
defer buf.Put(value)
|
||||||
|
binary.BigEndian.PutUint64(value[:8], uint64(expireAt.Unix()))
|
||||||
|
copy(value[8:], rawMessage)
|
||||||
|
return bucket.Put(key, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger) {
|
||||||
|
saveKey := saveCacheKey{transportName, qName, qType}
|
||||||
|
if !c.queueDNSCacheSave(saveKey, rawMessage, expireAt) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go c.flushPendingDNSCache(saveKey, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) queueDNSCacheSave(saveKey saveCacheKey, rawMessage []byte, expireAt time.Time) bool {
|
||||||
|
c.saveDNSCacheAccess.Lock()
|
||||||
|
defer c.saveDNSCacheAccess.Unlock()
|
||||||
|
entry := c.saveDNSCache[saveKey]
|
||||||
|
entry.rawMessage = append([]byte(nil), rawMessage...)
|
||||||
|
entry.expireAt = expireAt
|
||||||
|
entry.sequence++
|
||||||
|
startFlush := !entry.saving
|
||||||
|
entry.saving = true
|
||||||
|
c.saveDNSCache[saveKey] = entry
|
||||||
|
return startFlush
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) flushPendingDNSCache(saveKey saveCacheKey, logger logger.Logger) {
|
||||||
|
c.flushPendingDNSCacheWith(saveKey, logger, func(entry saveDNSCacheEntry) error {
|
||||||
|
return c.SaveDNSCache(saveKey.TransportName, saveKey.QuestionName, saveKey.QType, entry.rawMessage, entry.expireAt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) flushPendingDNSCacheWith(saveKey saveCacheKey, logger logger.Logger, save func(saveDNSCacheEntry) error) {
|
||||||
|
for {
|
||||||
|
c.saveDNSCacheAccess.RLock()
|
||||||
|
entry, loaded := c.saveDNSCache[saveKey]
|
||||||
|
c.saveDNSCacheAccess.RUnlock()
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := save(entry)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("save DNS cache: ", err)
|
||||||
|
}
|
||||||
|
c.saveDNSCacheAccess.Lock()
|
||||||
|
currentEntry, loaded := c.saveDNSCache[saveKey]
|
||||||
|
if !loaded {
|
||||||
|
c.saveDNSCacheAccess.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if currentEntry.sequence != entry.sequence {
|
||||||
|
c.saveDNSCacheAccess.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(c.saveDNSCache, saveKey)
|
||||||
|
c.saveDNSCacheAccess.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) ClearDNSCache() error {
|
||||||
|
c.saveDNSCacheAccess.Lock()
|
||||||
|
clear(c.saveDNSCache)
|
||||||
|
c.saveDNSCacheAccess.Unlock()
|
||||||
|
return c.batch(func(tx *bbolt.Tx) error {
|
||||||
|
if c.cacheID == nil {
|
||||||
|
bucket := tx.Bucket(bucketDNSCache)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tx.DeleteBucket(bucketDNSCache)
|
||||||
|
}
|
||||||
|
bucket := tx.Bucket(c.cacheID)
|
||||||
|
if bucket == nil || bucket.Bucket(bucketDNSCache) == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bucket.DeleteBucket(bucketDNSCache)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) loopCacheCleanup(interval time.Duration, cleanupFunc func()) {
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
cleanupFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) cleanupDNSCache() {
|
||||||
|
now := time.Now()
|
||||||
|
err := c.batch(func(tx *bbolt.Tx) error {
|
||||||
|
bucket := c.bucket(tx, bucketDNSCache)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var emptyTransports [][]byte
|
||||||
|
err := bucket.ForEachBucket(func(transportName []byte) error {
|
||||||
|
transportBucket := bucket.Bucket(transportName)
|
||||||
|
if transportBucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var expiredKeys [][]byte
|
||||||
|
err := transportBucket.ForEach(func(key, value []byte) error {
|
||||||
|
if len(value) < 8 {
|
||||||
|
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.disableExpire {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
expireAt := time.Unix(int64(binary.BigEndian.Uint64(value[:8])), 0)
|
||||||
|
if now.After(expireAt.Add(c.optimisticTimeout)) {
|
||||||
|
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, key := range expiredKeys {
|
||||||
|
err = transportBucket.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first, _ := transportBucket.Cursor().First()
|
||||||
|
if first == nil {
|
||||||
|
emptyTransports = append(emptyTransports, append([]byte(nil), transportName...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, name := range emptyTransports {
|
||||||
|
err = bucket.DeleteBucket(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("cleanup DNS cache: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) clearRDRC() {
|
||||||
|
c.saveRDRCAccess.Lock()
|
||||||
|
clear(c.saveRDRC)
|
||||||
|
c.saveRDRCAccess.Unlock()
|
||||||
|
err := c.batch(func(tx *bbolt.Tx) error {
|
||||||
|
if c.cacheID == nil {
|
||||||
|
if tx.Bucket(bucketRDRC) == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tx.DeleteBucket(bucketRDRC)
|
||||||
|
}
|
||||||
|
bucket := tx.Bucket(c.cacheID)
|
||||||
|
if bucket == nil || bucket.Bucket(bucketRDRC) == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bucket.DeleteBucket(bucketRDRC)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("clear RDRC: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) cleanupRDRC() {
|
||||||
|
now := time.Now()
|
||||||
|
err := c.batch(func(tx *bbolt.Tx) error {
|
||||||
|
bucket := c.bucket(tx, bucketRDRC)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var emptyTransports [][]byte
|
||||||
|
err := bucket.ForEachBucket(func(transportName []byte) error {
|
||||||
|
transportBucket := bucket.Bucket(transportName)
|
||||||
|
if transportBucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var expiredKeys [][]byte
|
||||||
|
err := transportBucket.ForEach(func(key, value []byte) error {
|
||||||
|
if len(value) < 8 {
|
||||||
|
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
expiresAt := time.Unix(int64(binary.BigEndian.Uint64(value)), 0)
|
||||||
|
if now.After(expiresAt) {
|
||||||
|
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, key := range expiredKeys {
|
||||||
|
err = transportBucket.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first, _ := transportBucket.Cursor().First()
|
||||||
|
if first == nil {
|
||||||
|
emptyTransports = append(emptyTransports, append([]byte(nil), transportName...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, name := range emptyTransports {
|
||||||
|
err = bucket.DeleteBucket(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("cleanup RDRC: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ func (c *CacheFile) RDRCTimeout() time.Duration {
|
|||||||
|
|
||||||
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
|
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
|
||||||
c.saveRDRCAccess.RLock()
|
c.saveRDRCAccess.RLock()
|
||||||
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]
|
rejected, cached := c.saveRDRC[saveCacheKey{transportName, qName, qType}]
|
||||||
c.saveRDRCAccess.RUnlock()
|
c.saveRDRCAccess.RUnlock()
|
||||||
if cached {
|
if cached {
|
||||||
return
|
return
|
||||||
@@ -93,7 +93,7 @@ func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
|
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
|
||||||
saveKey := saveRDRCCacheKey{transportName, qName, qType}
|
saveKey := saveCacheKey{transportName, qName, qType}
|
||||||
c.saveRDRCAccess.Lock()
|
c.saveRDRCAccess.Lock()
|
||||||
c.saveRDRC[saveKey] = true
|
c.saveRDRC[saveKey] = true
|
||||||
c.saveRDRCAccess.Unlock()
|
c.saveRDRCAccess.Unlock()
|
||||||
|
|||||||
@@ -120,6 +120,24 @@ var OptionLegacyDNSRuleStrategy = Note{
|
|||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-dns-rule-action-strategy-to-rule-items",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-dns-rule-action-strategy-to-rule-items",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var OptionIndependentDNSCache = Note{
|
||||||
|
Name: "independent-dns-cache",
|
||||||
|
Description: "`independent_cache` DNS option",
|
||||||
|
DeprecatedVersion: "1.14.0",
|
||||||
|
ScheduledVersion: "1.16.0",
|
||||||
|
EnvName: "INDEPENDENT_DNS_CACHE",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-independent-dns-cache",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionStoreRDRC = Note{
|
||||||
|
Name: "store-rdrc",
|
||||||
|
Description: "`store_rdrc` cache file option",
|
||||||
|
DeprecatedVersion: "1.14.0",
|
||||||
|
ScheduledVersion: "1.16.0",
|
||||||
|
EnvName: "STORE_RDRC",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-store-rdrc",
|
||||||
|
}
|
||||||
|
|
||||||
var Options = []Note{
|
var Options = []Note{
|
||||||
OptionOutboundDNSRuleItem,
|
OptionOutboundDNSRuleItem,
|
||||||
OptionMissingDomainResolver,
|
OptionMissingDomainResolver,
|
||||||
@@ -128,4 +146,6 @@ var Options = []Note{
|
|||||||
OptionRuleSetIPCIDRAcceptEmpty,
|
OptionRuleSetIPCIDRAcceptEmpty,
|
||||||
OptionLegacyDNSAddressFilter,
|
OptionLegacyDNSAddressFilter,
|
||||||
OptionLegacyDNSRuleStrategy,
|
OptionLegacyDNSRuleStrategy,
|
||||||
|
OptionIndependentDNSCache,
|
||||||
|
OptionStoreRDRC,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,9 +52,32 @@ type DNSClientOptions struct {
|
|||||||
DisableExpire bool `json:"disable_expire,omitempty"`
|
DisableExpire bool `json:"disable_expire,omitempty"`
|
||||||
IndependentCache bool `json:"independent_cache,omitempty"`
|
IndependentCache bool `json:"independent_cache,omitempty"`
|
||||||
CacheCapacity uint32 `json:"cache_capacity,omitempty"`
|
CacheCapacity uint32 `json:"cache_capacity,omitempty"`
|
||||||
|
Optimistic *OptimisticDNSOptions `json:"optimistic,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type _OptimisticDNSOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptimisticDNSOptions _OptimisticDNSOptions
|
||||||
|
|
||||||
|
func (o OptimisticDNSOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
if o.Timeout == 0 {
|
||||||
|
return json.Marshal(o.Enabled)
|
||||||
|
}
|
||||||
|
return json.Marshal((_OptimisticDNSOptions)(o))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OptimisticDNSOptions) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, &o.Enabled)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.UnmarshalDisallowUnknownFields(bytes, (*_OptimisticDNSOptions)(o))
|
||||||
|
}
|
||||||
|
|
||||||
type DNSTransportOptionsRegistry interface {
|
type DNSTransportOptionsRegistry interface {
|
||||||
CreateOptions(transportType string) (any, bool)
|
CreateOptions(transportType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type CacheFileOptions struct {
|
|||||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||||
StoreRDRC bool `json:"store_rdrc,omitempty"`
|
StoreRDRC bool `json:"store_rdrc,omitempty"`
|
||||||
RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
|
RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
|
||||||
|
StoreDNS bool `json:"store_dns,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClashAPIOptions struct {
|
type ClashAPIOptions struct {
|
||||||
|
|||||||
+7
-5
@@ -93,11 +93,12 @@ type DialerOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _DomainResolveOptions struct {
|
type _DomainResolveOptions struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainResolveOptions _DomainResolveOptions
|
type DomainResolveOptions _DomainResolveOptions
|
||||||
@@ -107,6 +108,7 @@ func (o DomainResolveOptions) MarshalJSON() ([]byte, error) {
|
|||||||
return []byte("{}"), nil
|
return []byte("{}"), nil
|
||||||
} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
|
} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
|
||||||
!o.DisableCache &&
|
!o.DisableCache &&
|
||||||
|
!o.DisableOptimisticCache &&
|
||||||
o.RewriteTTL == nil &&
|
o.RewriteTTL == nil &&
|
||||||
o.ClientSubnet == nil {
|
o.ClientSubnet == nil {
|
||||||
return json.Marshal(o.Server)
|
return json.Marshal(o.Server)
|
||||||
|
|||||||
+17
-14
@@ -201,18 +201,20 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSRouteActionOptions struct {
|
type DNSRouteActionOptions struct {
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRouteOptionsActionOptions struct {
|
type _DNSRouteOptionsActionOptions struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions
|
type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions
|
||||||
@@ -321,11 +323,12 @@ type RouteActionSniff struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RouteActionResolve struct {
|
type RouteActionResolve struct {
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
DisableOptimisticCache bool `json:"disable_optimistic_cache,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRouteActionPredefined struct {
|
type DNSRouteActionPredefined struct {
|
||||||
|
|||||||
+5
-4
@@ -80,10 +80,11 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options
|
|||||||
RoutingMark: uint32(options.DefaultMark),
|
RoutingMark: uint32(options.DefaultMark),
|
||||||
DomainResolver: defaultDomainResolver.Server,
|
DomainResolver: defaultDomainResolver.Server,
|
||||||
DomainResolveOptions: adapter.DNSQueryOptions{
|
DomainResolveOptions: adapter.DNSQueryOptions{
|
||||||
Strategy: C.DomainStrategy(defaultDomainResolver.Strategy),
|
Strategy: C.DomainStrategy(defaultDomainResolver.Strategy),
|
||||||
DisableCache: defaultDomainResolver.DisableCache,
|
DisableCache: defaultDomainResolver.DisableCache,
|
||||||
RewriteTTL: defaultDomainResolver.RewriteTTL,
|
DisableOptimisticCache: defaultDomainResolver.DisableOptimisticCache,
|
||||||
ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
RewriteTTL: defaultDomainResolver.RewriteTTL,
|
||||||
|
ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||||
},
|
},
|
||||||
NetworkStrategy: (*C.NetworkStrategy)(options.DefaultNetworkStrategy),
|
NetworkStrategy: (*C.NetworkStrategy)(options.DefaultNetworkStrategy),
|
||||||
NetworkType: common.Map(options.DefaultNetworkType, option.InterfaceType.Build),
|
NetworkType: common.Map(options.DefaultNetworkType, option.InterfaceType.Build),
|
||||||
|
|||||||
+6
-5
@@ -786,11 +786,12 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{
|
addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: action.Strategy,
|
Strategy: action.Strategy,
|
||||||
DisableCache: action.DisableCache,
|
DisableCache: action.DisableCache,
|
||||||
RewriteTTL: action.RewriteTTL,
|
DisableOptimisticCache: action.DisableOptimisticCache,
|
||||||
ClientSubnet: action.ClientSubnet,
|
RewriteTTL: action.RewriteTTL,
|
||||||
|
ClientSubnet: action.ClientSubnet,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+41
-26
@@ -107,11 +107,12 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
|||||||
return sniffAction, sniffAction.build()
|
return sniffAction, sniffAction.build()
|
||||||
case C.RuleActionTypeResolve:
|
case C.RuleActionTypeResolve:
|
||||||
return &RuleActionResolve{
|
return &RuleActionResolve{
|
||||||
Server: action.ResolveOptions.Server,
|
Server: action.ResolveOptions.Server,
|
||||||
Strategy: C.DomainStrategy(action.ResolveOptions.Strategy),
|
Strategy: C.DomainStrategy(action.ResolveOptions.Strategy),
|
||||||
DisableCache: action.ResolveOptions.DisableCache,
|
DisableCache: action.ResolveOptions.DisableCache,
|
||||||
RewriteTTL: action.ResolveOptions.RewriteTTL,
|
DisableOptimisticCache: action.ResolveOptions.DisableOptimisticCache,
|
||||||
ClientSubnet: action.ResolveOptions.ClientSubnet.Build(netip.Prefix{}),
|
RewriteTTL: action.ResolveOptions.RewriteTTL,
|
||||||
|
ClientSubnet: action.ResolveOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
panic(F.ToString("unknown rule action: ", action.Action))
|
panic(F.ToString("unknown rule action: ", action.Action))
|
||||||
@@ -126,30 +127,33 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
|
|||||||
return &RuleActionDNSRoute{
|
return &RuleActionDNSRoute{
|
||||||
Server: action.RouteOptions.Server,
|
Server: action.RouteOptions.Server,
|
||||||
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
||||||
Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
|
Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
|
||||||
DisableCache: action.RouteOptions.DisableCache,
|
DisableCache: action.RouteOptions.DisableCache,
|
||||||
RewriteTTL: action.RouteOptions.RewriteTTL,
|
DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache,
|
||||||
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
RewriteTTL: action.RouteOptions.RewriteTTL,
|
||||||
|
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeEvaluate:
|
||||||
return &RuleActionEvaluate{
|
return &RuleActionEvaluate{
|
||||||
Server: action.RouteOptions.Server,
|
Server: action.RouteOptions.Server,
|
||||||
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
||||||
Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
|
Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
|
||||||
DisableCache: action.RouteOptions.DisableCache,
|
DisableCache: action.RouteOptions.DisableCache,
|
||||||
RewriteTTL: action.RouteOptions.RewriteTTL,
|
DisableOptimisticCache: action.RouteOptions.DisableOptimisticCache,
|
||||||
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
RewriteTTL: action.RouteOptions.RewriteTTL,
|
||||||
|
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case C.RuleActionTypeRespond:
|
case C.RuleActionTypeRespond:
|
||||||
return &RuleActionRespond{}
|
return &RuleActionRespond{}
|
||||||
case C.RuleActionTypeRouteOptions:
|
case C.RuleActionTypeRouteOptions:
|
||||||
return &RuleActionDNSRouteOptions{
|
return &RuleActionDNSRouteOptions{
|
||||||
Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy),
|
Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy),
|
||||||
DisableCache: action.RouteOptionsOptions.DisableCache,
|
DisableCache: action.RouteOptionsOptions.DisableCache,
|
||||||
RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
|
DisableOptimisticCache: action.RouteOptionsOptions.DisableOptimisticCache,
|
||||||
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
|
RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
|
||||||
|
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
|
||||||
}
|
}
|
||||||
case C.RuleActionTypeReject:
|
case C.RuleActionTypeReject:
|
||||||
return &RuleActionReject{
|
return &RuleActionReject{
|
||||||
@@ -310,6 +314,9 @@ func formatDNSRouteAction(action string, server string, options RuleActionDNSRou
|
|||||||
if options.DisableCache {
|
if options.DisableCache {
|
||||||
descriptions = append(descriptions, "disable-cache")
|
descriptions = append(descriptions, "disable-cache")
|
||||||
}
|
}
|
||||||
|
if options.DisableOptimisticCache {
|
||||||
|
descriptions = append(descriptions, "disable-optimistic-cache")
|
||||||
|
}
|
||||||
if options.RewriteTTL != nil {
|
if options.RewriteTTL != nil {
|
||||||
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *options.RewriteTTL))
|
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *options.RewriteTTL))
|
||||||
}
|
}
|
||||||
@@ -320,10 +327,11 @@ func formatDNSRouteAction(action string, server string, options RuleActionDNSRou
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuleActionDNSRouteOptions struct {
|
type RuleActionDNSRouteOptions struct {
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
RewriteTTL *uint32
|
DisableOptimisticCache bool
|
||||||
ClientSubnet netip.Prefix
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleActionDNSRouteOptions) Type() string {
|
func (r *RuleActionDNSRouteOptions) Type() string {
|
||||||
@@ -335,6 +343,9 @@ func (r *RuleActionDNSRouteOptions) String() string {
|
|||||||
if r.DisableCache {
|
if r.DisableCache {
|
||||||
descriptions = append(descriptions, "disable-cache")
|
descriptions = append(descriptions, "disable-cache")
|
||||||
}
|
}
|
||||||
|
if r.DisableOptimisticCache {
|
||||||
|
descriptions = append(descriptions, "disable-optimistic-cache")
|
||||||
|
}
|
||||||
if r.RewriteTTL != nil {
|
if r.RewriteTTL != nil {
|
||||||
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
|
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
|
||||||
}
|
}
|
||||||
@@ -510,11 +521,12 @@ func (r *RuleActionSniff) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuleActionResolve struct {
|
type RuleActionResolve struct {
|
||||||
Server string
|
Server string
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
RewriteTTL *uint32
|
DisableOptimisticCache bool
|
||||||
ClientSubnet netip.Prefix
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleActionResolve) Type() string {
|
func (r *RuleActionResolve) Type() string {
|
||||||
@@ -532,6 +544,9 @@ func (r *RuleActionResolve) String() string {
|
|||||||
if r.DisableCache {
|
if r.DisableCache {
|
||||||
options = append(options, "disable_cache")
|
options = append(options, "disable_cache")
|
||||||
}
|
}
|
||||||
|
if r.DisableOptimisticCache {
|
||||||
|
options = append(options, "disable_optimistic_cache")
|
||||||
|
}
|
||||||
if r.RewriteTTL != nil {
|
if r.RewriteTTL != nil {
|
||||||
options = append(options, F.ToString("rewrite_ttl=", *r.RewriteTTL))
|
options = append(options, F.ToString("rewrite_ttl=", *r.RewriteTTL))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user