feat(dns): support fixed domain ttl (#100)

* feat(dns): support fixed domain ttl

* docs
This commit is contained in:
mzz
2023-05-30 22:10:32 +08:00
committed by GitHub
parent 2a8f8f9010
commit b936e7ada4
9 changed files with 149 additions and 21 deletions

View File

@ -142,6 +142,24 @@ func BpfMapBatchUpdate(m *ebpf.Map, keys interface{}, values interface{}, opts *
return vKeys.Len(), nil
}
// BpfMapBatchDelete deletes keys and ignores ErrKeyNotExist.
func BpfMapBatchDelete(m *ebpf.Map, keys interface{}) (n int, err error) {
// Simulate
vKeys := reflect.ValueOf(keys)
if vKeys.Kind() != reflect.Slice {
return 0, fmt.Errorf("keys must be slice")
}
length := vKeys.Len()
for i := 0; i < length; i++ {
vKey := vKeys.Index(i)
if err = m.Delete(vKey.Interface()); err != nil && !errors.Is(err, ebpf.ErrKeyNotExist) {
return i, err
}
}
return vKeys.Len(), nil
}
// detectCgroupPath returns the first-found mount point of type cgroup2
// and stores it in the cgroupPath global variable.
// Copied from https://github.com/cilium/ebpf/blob/v0.10.0/examples/cgroup_skb/main.go

View File

@ -371,6 +371,10 @@ func NewControlPlane(
return nil, err
}
/// Dns controller.
fixedDomainTtl, err := ParseFixedDomainTtl(dnsConfig.FixedDomainTtl)
if err != nil {
return nil, err
}
if plane.dnsController, err = NewDnsController(dnsUpstream, &DnsControllerOption{
Log: log,
CacheAccessCallback: func(cache *DnsCache) (err error) {
@ -381,6 +385,14 @@ func NewControlPlane(
}
return nil
},
CacheRemoveCallback: func(cache *DnsCache) (err error) {
// Write mappings into eBPF map:
// IP record (from dns lookup) -> domain routing
if err = core.BatchRemoveDomainRouting(cache); err != nil {
return fmt.Errorf("BatchUpdateDomainRouting: %w", err)
}
return nil
},
NewCache: func(fqdn string, answers []dnsmessage.Resource, deadline time.Time) (cache *DnsCache, err error) {
return &DnsCache{
DomainBitmap: plane.routingMatcher.domainMatcher.MatchDomainBitmap(fqdn),
@ -390,6 +402,7 @@ func NewControlPlane(
},
BestDialerChooser: plane.chooseBestDnsDialer,
IpVersionPrefer: dnsConfig.IpVersionPrefer,
FixedDomainTtl: fixedDomainTtl,
}); err != nil {
return nil, err
}
@ -405,7 +418,7 @@ func NewControlPlane(
}
host := cacheKey[:lastDot]
typ := cacheKey[lastDot+1:]
_ = plane.dnsController.UpdateDnsCache(host, typ, cache.Answers, cache.Deadline)
_ = plane.dnsController.UpdateDnsCacheDeadline(host, typ, cache.Answers, cache.Deadline)
}
} else if _bpf != nil {
// Is reloading, and dnsCache == nil.
@ -430,6 +443,19 @@ func NewControlPlane(
return plane, nil
}
func ParseFixedDomainTtl(ks []config.KeyableString) (map[string]int, error) {
m := make(map[string]int)
for _, k := range ks {
key, value, _ := strings.Cut(string(k), ":")
ttl, err := strconv.ParseInt(strings.TrimSpace(value), 0, strconv.IntSize)
if err != nil {
return nil, fmt.Errorf("failed to parse ttl: %v", err)
}
m[strings.TrimSpace(key)] = int(ttl)
}
return m, nil
}
// EjectBpf will resect bpf from destroying life-cycle of control plane.
func (c *ControlPlane) EjectBpf() *bpfObjects {
return c.core.EjectBpf()
@ -485,7 +511,7 @@ func (c *ControlPlane) dnsUpstreamReadyCallback(dnsUpstream *dns.Upstream) (err
A: dnsUpstream.Ip4.As4(),
},
}}
if err = c.dnsController.UpdateDnsCache(dnsUpstream.Hostname, typ.String(), answers, deadline); err != nil {
if err = c.dnsController.UpdateDnsCacheDeadline(dnsUpstream.Hostname, typ.String(), answers, deadline); err != nil {
return err
}
}
@ -503,7 +529,7 @@ func (c *ControlPlane) dnsUpstreamReadyCallback(dnsUpstream *dns.Upstream) (err
AAAA: dnsUpstream.Ip6.As16(),
},
}}
if err = c.dnsController.UpdateDnsCache(dnsUpstream.Hostname, typ.String(), answers, deadline); err != nil {
if err = c.dnsController.UpdateDnsCacheDeadline(dnsUpstream.Hostname, typ.String(), answers, deadline); err != nil {
return err
}
}

View File

@ -561,6 +561,40 @@ func (c *controlPlaneCore) BatchUpdateDomainRouting(cache *DnsCache) error {
return nil
}
// BatchRemoveDomainRouting remove bpf map domain_routing.
func (c *controlPlaneCore) BatchRemoveDomainRouting(cache *DnsCache) error {
// Parse ips from DNS resp answers.
var ips []netip.Addr
for _, ans := range cache.Answers {
var ip netip.Addr
switch ans.Header.Type {
case dnsmessage.TypeA:
ip = netip.AddrFrom4(ans.Body.(*dnsmessage.AResource).A)
case dnsmessage.TypeAAAA:
ip = netip.AddrFrom16(ans.Body.(*dnsmessage.AAAAResource).AAAA)
}
if ip.IsUnspecified() {
continue
}
ips = append(ips, ip)
}
if len(ips) == 0 {
return nil
}
// Update bpf map.
// Construct keys and vals, and BpfMapBatchUpdate.
var keys [][4]uint32
for _, ip := range ips {
ip6 := ip.As16()
keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:]))
}
if _, err := BpfMapBatchDelete(c.bpf.DomainRoutingMap, keys); err != nil {
return err
}
return nil
}
// EjectBpf will resect bpf from destroying life-cycle of control plane core.
func (c *controlPlaneCore) EjectBpf() *bpfObjects {
if !c.bpfEjected && !c.isReload {

View File

@ -32,9 +32,8 @@ import (
)
const (
MaxDnsLookupDepth = 3
minFirefoxCacheTtl = 120
minFirefoxCacheTimeout = minFirefoxCacheTtl * time.Second
MaxDnsLookupDepth = 3
minFirefoxCacheTtl = 120
)
type IpVersionPrefer int
@ -58,9 +57,11 @@ var (
type DnsControllerOption struct {
Log *logrus.Logger
CacheAccessCallback func(cache *DnsCache) (err error)
CacheRemoveCallback func(cache *DnsCache) (err error)
NewCache func(fqdn string, answers []dnsmessage.Resource, deadline time.Time) (cache *DnsCache, err error)
BestDialerChooser func(req *udpRequest, upstream *dns.Upstream) (*dialArgument, error)
IpVersionPrefer int
FixedDomainTtl map[string]int
}
type DnsController struct {
@ -71,9 +72,11 @@ type DnsController struct {
log *logrus.Logger
cacheAccessCallback func(cache *DnsCache) (err error)
cacheRemoveCallback func(cache *DnsCache) (err error)
newCache func(fqdn string, answers []dnsmessage.Resource, deadline time.Time) (cache *DnsCache, err error)
bestDialerChooser func(req *udpRequest, upstream *dns.Upstream) (*dialArgument, error)
fixedDomainTtl map[string]int
// mutex protects the dnsCache.
dnsCacheMu sync.Mutex
dnsCache map[string]*DnsCache
@ -105,11 +108,13 @@ func NewDnsController(routing *dns.Dns, option *DnsControllerOption) (c *DnsCont
log: option.Log,
cacheAccessCallback: option.CacheAccessCallback,
cacheRemoveCallback: option.CacheRemoveCallback,
newCache: option.NewCache,
bestDialerChooser: option.BestDialerChooser,
dnsCacheMu: sync.Mutex{},
dnsCache: make(map[string]*DnsCache),
fixedDomainTtl: option.FixedDomainTtl,
dnsCacheMu: sync.Mutex{},
dnsCache: make(map[string]*DnsCache),
}, nil
}
@ -276,19 +281,14 @@ func (c *DnsController) updateDnsCache(msg *dnsmessage.Message, ttl uint32, q *d
"addition": FormatDnsRsc(msg.Additionals),
}).Tracef("Update DNS record cache")
}
cacheTimeout := time.Duration(ttl) * time.Second // TTL.
if cacheTimeout < minFirefoxCacheTimeout {
cacheTimeout = minFirefoxCacheTimeout
}
cacheTimeout += 5 * time.Second // DNS lookup timeout.
if err := c.UpdateDnsCache(q.Name.String(), q.Type.String(), msg.Answers, time.Now().Add(cacheTimeout)); err != nil {
if err := c.UpdateDnsCacheTtl(q.Name.String(), q.Type.String(), msg.Answers, int(ttl)); err != nil {
return err
}
return nil
}
func (c *DnsController) UpdateDnsCache(host string, dnsTyp string, answers []dnsmessage.Resource, deadline time.Time) (err error) {
func (c *DnsController) __updateDnsCacheDeadline(host string, dnsTyp string, answers []dnsmessage.Resource, deadlineFunc func(now time.Time, host string) time.Time) (err error) {
var fqdn string
if strings.HasSuffix(host, ".") {
fqdn = host
@ -300,15 +300,16 @@ func (c *DnsController) UpdateDnsCache(host string, dnsTyp string, answers []dns
if _, err = netip.ParseAddr(host); err == nil {
return nil
}
now := time.Now()
deadline := deadlineFunc(now, host)
cacheKey := fqdn + dnsTyp
c.dnsCacheMu.Lock()
cache, ok := c.dnsCache[cacheKey]
if ok {
// To avoid overwriting DNS upstream resolution.
if deadline.After(cache.Deadline) {
cache.Deadline = deadline
}
cache.Answers = answers
cache.Deadline = deadline
c.dnsCacheMu.Unlock()
} else {
cache, err = c.newCache(fqdn, answers, deadline)
@ -322,9 +323,32 @@ func (c *DnsController) UpdateDnsCache(host string, dnsTyp string, answers []dns
if err = c.cacheAccessCallback(cache); err != nil {
return err
}
return nil
}
func (c *DnsController) UpdateDnsCacheDeadline(host string, dnsTyp string, answers []dnsmessage.Resource, deadline time.Time) (err error) {
return c.__updateDnsCacheDeadline(host, dnsTyp, answers, func(now time.Time, host string) time.Time {
if fixedTtl, ok := c.fixedDomainTtl[host]; ok {
/// NOTICE: Cannot set TTL accurately.
if now.Sub(deadline).Seconds() > float64(fixedTtl) {
return now.Add(time.Duration(fixedTtl) * time.Second)
}
}
return deadline
})
}
func (c *DnsController) UpdateDnsCacheTtl(host string, dnsTyp string, answers []dnsmessage.Resource, ttl int) (err error) {
return c.__updateDnsCacheDeadline(host, dnsTyp, answers, func(now time.Time, host string) time.Time {
if fixedTtl, ok := c.fixedDomainTtl[host]; ok {
return now.Add(time.Duration(fixedTtl) * time.Second)
} else {
return now.Add(time.Duration(ttl) * time.Second)
}
})
}
func (c *DnsController) DnsRespHandlerFactory(validateRushAnsFunc func(from netip.AddrPort) bool) func(data []byte, from netip.AddrPort) (msg *dnsmessage.Message, err error) {
return func(data []byte, from netip.AddrPort) (msg *dnsmessage.Message, err error) {
// Do not return conn-unrelated err in this func.