package dns import ( "context" "fmt" "net" "strings" "sync" "time" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" "golang.org/x/exp/slices" ) const ( SystemDnsFlushTime = 5 * time.Minute SystemDnsDeleteTimes = 12 // 12*5 = 60min ) type systemDnsClient struct { disableTimes uint32 dnsClient } type systemClient struct { mu sync.Mutex dnsClients map[string]*systemDnsClient lastFlush time.Time } func (c *systemClient) getDnsClients() ([]dnsClient, error) { c.mu.Lock() defer c.mu.Unlock() var err error if time.Since(c.lastFlush) > SystemDnsFlushTime { var nameservers []string if nameservers, err = dnsReadConfig(); err == nil { log.Debugln("[DNS] system dns update to %s", nameservers) for _, addr := range nameservers { if _, ok := c.dnsClients[addr]; !ok { clients := transform( []NameServer{{ Addr: net.JoinHostPort(addr, "53"), Net: "udp", }}, nil, ) if len(clients) > 0 { c.dnsClients[addr] = &systemDnsClient{ disableTimes: 0, dnsClient: clients[0], } } } } available := 0 for nameserver, sdc := range c.dnsClients { if slices.Contains(nameservers, nameserver) { sdc.disableTimes = 0 // enable available++ } else { if sdc.disableTimes > SystemDnsDeleteTimes { delete(c.dnsClients, nameserver) // drop too old dnsClient } else { sdc.disableTimes++ } } } if available > 0 { c.lastFlush = time.Now() } } } dnsClients := make([]dnsClient, 0, len(c.dnsClients)) for _, sdc := range c.dnsClients { if sdc.disableTimes == 0 { dnsClients = append(dnsClients, sdc.dnsClient) } } if len(dnsClients) > 0 { return dnsClients, nil } return nil, err } func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { dnsClients, err := c.getDnsClients() if err != nil { return } msg, _, err = batchExchange(ctx, dnsClients, m) return } // Address implements dnsClient func (c *systemClient) Address() string { dnsClients, _ := c.getDnsClients() addrs := make([]string, 0, len(dnsClients)) for _, c := range dnsClients { addrs = append(addrs, c.Address()) } return fmt.Sprintf("system(%s)", strings.Join(addrs, ",")) } var _ dnsClient = (*systemClient)(nil) func newSystemClient() *systemClient { return &systemClient{ dnsClients: map[string]*systemDnsClient{}, } }