AdGuardDNS/internal/dnsmsg/httpscloner.go
Andrey Meshkov 41f7e6cb22 Sync v2.8.0
2024-07-10 19:49:07 +03:00

247 lines
5.7 KiB
Go

package dnsmsg
import (
"net"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
// httpsCloner is a pool that can clone common parts of DNS messages of type
// HTTPS with fewer allocations.
type httpsCloner struct {
// Top-level structures.
rr *syncutil.Pool[dns.HTTPS]
// Values.
alpn *syncutil.Pool[dns.SVCBAlpn]
dohpath *syncutil.Pool[dns.SVCBDoHPath]
echconfig *syncutil.Pool[dns.SVCBECHConfig]
ipv4hint *syncutil.Pool[dns.SVCBIPv4Hint]
ipv6hint *syncutil.Pool[dns.SVCBIPv6Hint]
local *syncutil.Pool[dns.SVCBLocal]
mandatory *syncutil.Pool[dns.SVCBMandatory]
port *syncutil.Pool[dns.SVCBPort]
// Miscellaneous.
ip *syncutil.Pool[[16]byte]
}
// newHTTPSCloner returns a new properly initialized *httpsCloner.
func newHTTPSCloner() (c *httpsCloner) {
return &httpsCloner{
rr: syncutil.NewPool(func() (v *dns.HTTPS) {
return &dns.HTTPS{}
}),
alpn: syncutil.NewPool(func() (v *dns.SVCBAlpn) {
return &dns.SVCBAlpn{}
}),
dohpath: syncutil.NewPool(func() (v *dns.SVCBDoHPath) {
return &dns.SVCBDoHPath{}
}),
echconfig: syncutil.NewPool(func() (v *dns.SVCBECHConfig) {
return &dns.SVCBECHConfig{}
}),
ipv4hint: syncutil.NewPool(func() (v *dns.SVCBIPv4Hint) {
return &dns.SVCBIPv4Hint{}
}),
ipv6hint: syncutil.NewPool(func() (v *dns.SVCBIPv6Hint) {
return &dns.SVCBIPv6Hint{}
}),
local: syncutil.NewPool(func() (v *dns.SVCBLocal) {
return &dns.SVCBLocal{}
}),
mandatory: syncutil.NewPool(func() (v *dns.SVCBMandatory) {
return &dns.SVCBMandatory{}
}),
port: syncutil.NewPool(func() (v *dns.SVCBPort) {
return &dns.SVCBPort{}
}),
ip: syncutil.NewPool(func() (v *[16]byte) {
// Use the IPv6 length to increase the effectiveness of the pool.
return &[16]byte{}
}),
}
}
// clone returns a deep clone of rr. full is true if rr was cloned entirely
// without the use of [dns.Copy].
func (c *httpsCloner) clone(rr *dns.HTTPS) (clone *dns.HTTPS, full bool) {
if rr == nil {
return nil, true
}
clone = c.rr.Get()
clone.Hdr = rr.Hdr
clone.Priority = rr.Priority
clone.Target = rr.Target
if rr.Value == nil {
clone.Value = nil
return clone, true
}
clone.Value = clone.Value[:0]
for _, orig := range rr.Value {
valClone := c.cloneKV(orig)
if valClone == nil {
// This branch is only reached if there is a new SVCB key-value type
// in miekg/dns. Give up and just use their copy function.
return dns.Copy(rr).(*dns.HTTPS), false
}
clone.Value = append(clone.Value, valClone)
}
return clone, true
}
// cloneKV returns a deep clone of orig. clone is nil if orig wasn't
// recognized.
func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue) {
switch orig := orig.(type) {
case *dns.SVCBAlpn:
v := c.alpn.Get()
v.Alpn = appendIfNotNil(v.Alpn[:0], orig.Alpn)
clone = v
case *dns.SVCBDoHPath:
v := c.dohpath.Get()
*v = *orig
clone = v
case *dns.SVCBECHConfig:
v := c.echconfig.Get()
v.ECH = appendIfNotNil(v.ECH[:0], orig.ECH)
clone = v
case *dns.SVCBLocal:
v := c.local.Get()
v.KeyCode = orig.KeyCode
v.Data = appendIfNotNil(v.Data[:0], orig.Data)
clone = v
case *dns.SVCBMandatory:
v := c.mandatory.Get()
v.Code = appendIfNotNil(v.Code[:0], orig.Code)
clone = v
case *dns.SVCBPort:
v := c.port.Get()
*v = *orig
clone = v
case
*dns.SVCBNoDefaultAlpn,
*dns.SVCBOhttp:
// Just use the original value since these [dns.SVCBKeyValue] types are
// pointers to empty structures, so we're only interested in the actual
// type.
clone = orig
default:
clone = c.cloneIfHint(orig)
}
// This is only nil if there is a new SVCB key-value type in miekg/dns.
return clone
}
// cloneIfHint returns a deep clone of orig if it's either an [dns.SVCBIPv4Hint]
// or [dns.SVCBIPv6Hint]. Otherwise, it returns nil.
func (c *httpsCloner) cloneIfHint(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue) {
switch orig := orig.(type) {
case *dns.SVCBIPv4Hint:
v := c.ipv4hint.Get()
v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
return v
case *dns.SVCBIPv6Hint:
v := c.ipv6hint.Get()
v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
return v
default:
return nil
}
}
// appendIPs appends the clones of IP addresses from orig to hints and returns
// the resulting slice. clone is allocated as a single continuous slice.
func (c *httpsCloner) appendIPs(hints, orig []net.IP) (clone []net.IP) {
if len(orig) == 0 {
if orig == nil {
return nil
}
return []net.IP{}
}
for _, origIP := range orig {
ipArr := c.ip.Get()
ip := append(ipArr[:0], origIP...)
hints = append(hints, ip)
}
return hints
}
// put returns structures from rr into c's pools.
func (c *httpsCloner) put(rr *dns.HTTPS) {
if rr == nil {
return
}
for _, kv := range rr.Value {
c.putKV(kv)
}
c.rr.Put(rr)
}
// putKV returns structures from kv into c's pools.
func (c *httpsCloner) putKV(kv dns.SVCBKeyValue) {
switch kv := kv.(type) {
case *dns.SVCBAlpn:
c.alpn.Put(kv)
case *dns.SVCBDoHPath:
c.dohpath.Put(kv)
case *dns.SVCBECHConfig:
c.echconfig.Put(kv)
case *dns.SVCBIPv4Hint:
c.putIPs(kv.Hint)
c.ipv4hint.Put(kv)
case *dns.SVCBIPv6Hint:
c.putIPs(kv.Hint)
c.ipv6hint.Put(kv)
case *dns.SVCBLocal:
c.local.Put(kv)
case *dns.SVCBMandatory:
c.mandatory.Put(kv)
case *dns.SVCBPort:
c.port.Put(kv)
case
*dns.SVCBNoDefaultAlpn,
*dns.SVCBOhttp:
// Don't use pool for empty structures, see comment in [cloneKV].
default:
// This branch is only reached if there is a new SVCB key-value type
// in miekg/dns. Noting to do.
}
}
// putIPs returns the underlying arrays of ips into c if possible.
func (c *httpsCloner) putIPs(ips []net.IP) {
for _, ip := range ips {
if cap(ip) >= 16 {
c.ip.Put((*[16]byte)(ip[:16]))
}
}
}