Files
dns-server/dns/cache.go
2026-01-14 23:08:46 +08:00

281 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dns
import (
"sync"
"time"
"github.com/miekg/dns"
)
// DNSCacheItem 表示缓存中的DNS响应项
type DNSCacheItem struct {
Response *dns.Msg // DNS响应消息
Expiry time.Time // 过期时间
HasDNSSEC bool // 是否包含DNSSEC记录
}
// LRUNode 双向链表节点用于LRU缓存
type LRUNode struct {
key string
value *DNSCacheItem
prev *LRUNode
next *LRUNode
}
// DNSCache DNS缓存结构
type DNSCache struct {
cache map[string]*LRUNode // 缓存映射表,直接存储链表节点
mutex sync.RWMutex // 读写锁,保护缓存
defaultTTL time.Duration // 默认缓存TTL
maxSize int // 最大缓存条目数
// 双向链表头和尾指针用于LRU淘汰
head *LRUNode // 头指针,指向最久未使用的节点
tail *LRUNode // 尾指针,指向最近使用的节点
}
// NewDNSCache 创建新的DNS缓存实例
func NewDNSCache(defaultTTL time.Duration) *DNSCache {
cache := &DNSCache{
cache: make(map[string]*LRUNode),
defaultTTL: defaultTTL,
maxSize: 10000, // 默认最大缓存10000条记录
head: nil,
tail: nil,
}
// 启动缓存清理协程
go cache.startCleanupLoop()
return cache
}
// addNodeToTail 将节点添加到链表尾部(表示最近使用)
func (c *DNSCache) addNodeToTail(node *LRUNode) {
if c.tail == nil {
// 链表为空
c.head = node
c.tail = node
} else {
// 添加到尾部
node.prev = c.tail
c.tail.next = node
c.tail = node
}
}
// removeNode 从链表中移除指定节点
func (c *DNSCache) removeNode(node *LRUNode) {
if node.prev != nil {
node.prev.next = node.next
} else {
// 移除的是头节点
c.head = node.next
}
if node.next != nil {
node.next.prev = node.prev
} else {
// 移除的是尾节点
c.tail = node.prev
}
// 清空节点的前后指针
node.prev = nil
node.next = nil
}
// moveNodeToTail 将节点移动到链表尾部(表示最近使用)
func (c *DNSCache) moveNodeToTail(node *LRUNode) {
// 如果已经是尾节点,不需要移动
if node == c.tail {
return
}
// 从链表中移除节点
c.removeNode(node)
// 重新添加到尾部
c.addNodeToTail(node)
}
// cacheKey 生成缓存键
func cacheKey(qName string, qType uint16) string {
return qName + "|" + dns.TypeToString[qType]
}
// hasDNSSECRecords 检查响应是否包含DNSSEC记录
func hasDNSSECRecords(response *dns.Msg) bool {
// 定义检查单个RR是否为DNSSEC记录的辅助函数
isDNSSECRecord := func(rr dns.RR) bool {
switch rr.(type) {
case *dns.DNSKEY, *dns.RRSIG, *dns.DS, *dns.NSEC, *dns.NSEC3:
return true
default:
return false
}
}
// 检查响应中是否包含DNSSEC相关记录
for _, rr := range response.Answer {
if isDNSSECRecord(rr) {
return true
}
}
for _, rr := range response.Ns {
if isDNSSECRecord(rr) {
return true
}
}
for _, rr := range response.Extra {
if isDNSSECRecord(rr) {
return true
}
}
return false
}
// Set 设置缓存项
func (c *DNSCache) Set(qName string, qType uint16, response *dns.Msg, ttl time.Duration) {
if ttl <= 0 {
ttl = c.defaultTTL
}
key := cacheKey(qName, qType)
item := &DNSCacheItem{
Response: response.Copy(), // 复制响应以避免外部修改
Expiry: time.Now().Add(ttl),
HasDNSSEC: hasDNSSECRecords(response), // 检查并设置DNSSEC标志
}
c.mutex.Lock()
defer c.mutex.Unlock()
// 如果条目已存在,先从链表和缓存中移除
if existingNode, found := c.cache[key]; found {
c.removeNode(existingNode)
delete(c.cache, key)
}
// 创建新的链表节点并添加到尾部
newNode := &LRUNode{
key: key,
value: item,
}
c.addNodeToTail(newNode)
c.cache[key] = newNode
// 检查是否超过最大大小限制,如果超过则移除最久未使用的条目
if len(c.cache) > c.maxSize {
// 最久未使用的条目是链表的头节点
if c.head != nil {
oldestKey := c.head.key
// 从缓存和链表中移除头节点
delete(c.cache, oldestKey)
c.removeNode(c.head)
}
}
}
// Get 获取缓存项
func (c *DNSCache) Get(qName string, qType uint16) (*dns.Msg, bool) {
key := cacheKey(qName, qType)
// 首先使用读锁检查缓存项是否存在和是否过期
c.mutex.RLock()
node, found := c.cache[key]
if !found {
c.mutex.RUnlock()
return nil, false
}
// 检查是否过期
if time.Now().After(node.value.Expiry) {
c.mutex.RUnlock()
// 需要删除过期条目,使用写锁
c.mutex.Lock()
// 再次检查,防止在读写锁切换期间被其他协程处理
if node, stillExists := c.cache[key]; stillExists && time.Now().After(node.value.Expiry) {
delete(c.cache, key)
c.removeNode(node)
}
c.mutex.Unlock()
return nil, false
}
// 返回前释放读锁,避免长时间持有锁
response := node.value.Response.Copy()
c.mutex.RUnlock()
// 标记为最近使用需要修改链表,使用写锁
c.mutex.Lock()
// 再次检查节点是否存在,防止在读写锁切换期间被删除
if node, stillExists := c.cache[key]; stillExists {
c.moveNodeToTail(node)
}
c.mutex.Unlock()
return response, true
}
// delete 删除缓存项
func (c *DNSCache) delete(key string) {
c.mutex.Lock()
defer c.mutex.Unlock()
// 从缓存和链表中删除
if node, found := c.cache[key]; found {
delete(c.cache, key)
c.removeNode(node)
}
}
// Clear 清空缓存
func (c *DNSCache) Clear() {
c.mutex.Lock()
c.cache = make(map[string]*LRUNode)
// 重置链表指针
c.head = nil
c.tail = nil
c.mutex.Unlock()
}
// Size 获取缓存大小
func (c *DNSCache) Size() int {
c.mutex.RLock()
defer c.mutex.RUnlock()
return len(c.cache)
}
// startCleanupLoop 启动定期清理过期缓存的协程
func (c *DNSCache) startCleanupLoop() {
ticker := time.NewTicker(time.Minute * 1) // 每1分钟清理一次减少内存占用
defer ticker.Stop()
for range ticker.C {
c.cleanupExpired()
}
}
// cleanupExpired 清理过期的缓存项
func (c *DNSCache) cleanupExpired() {
now := time.Now()
c.mutex.Lock()
defer c.mutex.Unlock()
// 收集所有过期的键
var expiredKeys []string
for key, node := range c.cache {
if now.After(node.value.Expiry) {
expiredKeys = append(expiredKeys, key)
}
}
// 删除过期的缓存项
for _, key := range expiredKeys {
if node, found := c.cache[key]; found {
delete(c.cache, key)
c.removeNode(node)
}
}
}