2022-08-26 14:18:35 +03:00
|
|
|
// Package dnsmsg contains common constants, functions, and types for
|
|
|
|
// inspecting and constructing DNS messages.
|
|
|
|
//
|
|
|
|
// TODO(a.garipov): Consider moving all or some of this stuff to module golibs.
|
|
|
|
package dnsmsg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2023-08-08 18:31:48 +03:00
|
|
|
"math"
|
2022-08-26 14:18:35 +03:00
|
|
|
"net/netip"
|
|
|
|
|
2024-10-14 17:44:24 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
2022-11-07 10:21:24 +03:00
|
|
|
"github.com/AdguardTeam/golibs/netutil"
|
2022-08-26 14:18:35 +03:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
)
|
|
|
|
|
2024-10-14 17:44:24 +03:00
|
|
|
// RCode is a semantic alias for uint16 values when they are used as a DNS
|
2022-08-26 14:18:35 +03:00
|
|
|
// response code RCODE.
|
2024-10-14 17:44:24 +03:00
|
|
|
//
|
|
|
|
// See https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6.
|
|
|
|
type RCode = uint16
|
2022-08-26 14:18:35 +03:00
|
|
|
|
|
|
|
// RRType is a semantic alias for uint16 values when they are used as a DNS
|
|
|
|
// resource record (RR) type.
|
2024-10-14 17:44:24 +03:00
|
|
|
//
|
|
|
|
// See https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4.
|
2022-08-26 14:18:35 +03:00
|
|
|
type RRType = uint16
|
|
|
|
|
|
|
|
// Class is a semantic alias for uint16 values when they are used as a DNS class
|
|
|
|
// code.
|
|
|
|
type Class = uint16
|
|
|
|
|
|
|
|
// DefaultEDNSUDPSize is the default size used for EDNS content.
|
|
|
|
//
|
|
|
|
// See https://datatracker.ietf.org/doc/html/rfc6891#section-6.2.5.
|
|
|
|
const DefaultEDNSUDPSize = 4096
|
|
|
|
|
|
|
|
// MaxTXTStringLen is the maximum length of a single string within a TXT
|
|
|
|
// resource record.
|
|
|
|
//
|
|
|
|
// See also https://datatracker.ietf.org/doc/html/rfc6763#section-6.1.
|
|
|
|
const MaxTXTStringLen int = 255
|
|
|
|
|
|
|
|
// Clone returns a new *Msg which is a deep copy of msg. Use this instead of
|
|
|
|
// msg.Copy, because the latter does not actually produce a deep copy of msg.
|
|
|
|
//
|
|
|
|
// See https://github.com/miekg/dns/issues/1351.
|
|
|
|
//
|
|
|
|
// TODO(a.garipov): See if we can also decrease allocations for such cases by
|
|
|
|
// modifying more of the original code.
|
|
|
|
func Clone(msg *dns.Msg) (clone *dns.Msg) {
|
|
|
|
if msg == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-29 15:36:26 +03:00
|
|
|
// Don't just call clone.Copy to save call-stack space.
|
|
|
|
clone = &dns.Msg{}
|
|
|
|
msg.CopyTo(clone)
|
2022-08-26 14:18:35 +03:00
|
|
|
|
|
|
|
// Make sure that nilness of the RR slices is retained.
|
|
|
|
if msg.Answer == nil {
|
|
|
|
clone.Answer = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg.Ns == nil {
|
|
|
|
clone.Ns = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg.Extra == nil {
|
|
|
|
clone.Extra = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return clone
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsDO returns true if msg has an EDNS option pseudosection and that
|
|
|
|
// pseudosection has the DNSSEC OK (DO) bit set.
|
|
|
|
func IsDO(msg *dns.Msg) (ok bool) {
|
|
|
|
opt := msg.IsEdns0()
|
|
|
|
|
|
|
|
return opt != nil && opt.Do()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ECSFromMsg returns the EDNS Client Subnet option information from msg, if
|
|
|
|
// any. If there is none, it returns netip.Prefix{}. msg must not be nil. err
|
|
|
|
// is not nil only if msg contains a malformed EDNS Client Subnet option or the
|
|
|
|
// address family is unsupported (that is, neither IPv4 nor IPv6). Any error
|
|
|
|
// returned from ECSFromMsg will have the underlying type of BadECSError.
|
|
|
|
func ECSFromMsg(msg *dns.Msg) (subnet netip.Prefix, scope uint8, err error) {
|
|
|
|
opt := msg.IsEdns0()
|
|
|
|
if opt == nil {
|
|
|
|
return netip.Prefix{}, 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, opt := range opt.Option {
|
|
|
|
esn, ok := opt.(*dns.EDNS0_SUBNET)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
subnet, scope, err = ecsData(esn)
|
|
|
|
if err != nil {
|
|
|
|
return netip.Prefix{}, 0, BadECSError{Err: err}
|
|
|
|
} else if subnet != (netip.Prefix{}) {
|
|
|
|
return subnet, scope, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return netip.Prefix{}, 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ecsData returns the subnet and scope information from an EDNS Client Subnet
|
|
|
|
// option. It returns an error if esn does not contain valid, RFC-compliant
|
|
|
|
// EDNS Client Subnet information or the address family is unsupported.
|
|
|
|
func ecsData(esn *dns.EDNS0_SUBNET) (subnet netip.Prefix, scope uint8, err error) {
|
2022-11-07 10:21:24 +03:00
|
|
|
fam := netutil.AddrFamily(esn.Family)
|
|
|
|
if fam != netutil.AddrFamilyIPv4 && fam != netutil.AddrFamilyIPv6 {
|
2022-08-26 14:18:35 +03:00
|
|
|
return netip.Prefix{}, 0, fmt.Errorf("unsupported addr family number %d", fam)
|
|
|
|
}
|
|
|
|
|
2022-11-07 10:21:24 +03:00
|
|
|
ip, err := netutil.IPToAddr(esn.Address, fam)
|
2022-08-26 14:18:35 +03:00
|
|
|
if err != nil {
|
|
|
|
return netip.Prefix{}, 0, fmt.Errorf("bad ecs ip addr: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
prefixLen := int(esn.SourceNetmask)
|
|
|
|
subnet = netip.PrefixFrom(ip, prefixLen)
|
|
|
|
if !subnet.IsValid() {
|
|
|
|
return netip.Prefix{}, 0, fmt.Errorf(
|
|
|
|
"bad src netmask %d for addr family %s",
|
|
|
|
prefixLen,
|
|
|
|
fam,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that the subnet address does not have any bits beyond the given
|
|
|
|
// prefix set to one.
|
|
|
|
//
|
|
|
|
// See https://datatracker.ietf.org/doc/html/rfc7871#section-6.
|
|
|
|
if subnet.Masked() != subnet {
|
|
|
|
return netip.Prefix{}, 0, fmt.Errorf(
|
|
|
|
"ip %s has non-zero bits beyond prefix %d",
|
|
|
|
ip,
|
|
|
|
prefixLen,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return subnet, esn.SourceScope, nil
|
|
|
|
}
|
2023-08-08 18:31:48 +03:00
|
|
|
|
|
|
|
// SetMinTTL overrides TTL values of all answer records according to the min
|
|
|
|
// TTL.
|
|
|
|
func SetMinTTL(r *dns.Msg, minTTL uint32) {
|
|
|
|
for _, rr := range r.Answer {
|
|
|
|
h := rr.Header()
|
|
|
|
|
2024-01-04 19:22:32 +03:00
|
|
|
h.Ttl = max(h.Ttl, minTTL)
|
2023-08-08 18:31:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServFailMaxCacheTTL is the maximum time-to-live value for caching
|
|
|
|
// SERVFAIL responses in seconds. It's consistent with the upper constraint
|
|
|
|
// of 5 minutes given by RFC 2308.
|
|
|
|
//
|
|
|
|
// See https://datatracker.ietf.org/doc/html/rfc2308#section-7.1.
|
|
|
|
const ServFailMaxCacheTTL = 30
|
|
|
|
|
|
|
|
// FindLowestTTL gets the lowest TTL among all DNS message's RRs.
|
|
|
|
func FindLowestTTL(msg *dns.Msg) (ttl uint32) {
|
|
|
|
// Use the maximum value as a guard value. If the inner loop is entered,
|
|
|
|
// it's going to be rewritten with an actual TTL value that is lower than
|
|
|
|
// MaxUint32. If the inner loop isn't entered, catch that and return zero.
|
|
|
|
ttl = math.MaxUint32
|
|
|
|
for _, rrs := range [][]dns.RR{msg.Answer, msg.Ns, msg.Extra} {
|
|
|
|
for _, rr := range rrs {
|
|
|
|
ttl = getTTLIfLower(rr, ttl)
|
|
|
|
if ttl == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case msg.Rcode == dns.RcodeServerFailure && ttl > ServFailMaxCacheTTL:
|
|
|
|
return ServFailMaxCacheTTL
|
|
|
|
case ttl == math.MaxUint32:
|
|
|
|
return 0
|
|
|
|
default:
|
|
|
|
return ttl
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// getTTLIfLower is a helper function that checks the TTL of the specified RR
|
|
|
|
// and returns it if it's lower than the one passed in the arguments.
|
|
|
|
func getTTLIfLower(r dns.RR, ttl uint32) (res uint32) {
|
|
|
|
switch r := r.(type) {
|
|
|
|
case *dns.OPT:
|
|
|
|
// Don't even consider the OPT RRs TTL.
|
|
|
|
return ttl
|
|
|
|
case *dns.SOA:
|
|
|
|
if r.Minttl > 0 && r.Minttl < ttl {
|
|
|
|
// Per RFC 2308, the TTL of a SOA RR is the minimum of SOA.MINIMUM
|
|
|
|
// field and the header's value.
|
|
|
|
ttl = r.Minttl
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// Go on.
|
|
|
|
}
|
|
|
|
|
2024-01-04 19:22:32 +03:00
|
|
|
return min(r.Header().Ttl, ttl)
|
|
|
|
}
|
|
|
|
|
|
|
|
// appendIfNotNil returns nil if original is nil. Otherwise, it appends
|
|
|
|
// original to clones and returns it.
|
|
|
|
//
|
|
|
|
// TODO(a.garipov): Consider moving to module golibs.
|
|
|
|
func appendIfNotNil[T any](clones, original []T) (res []T) {
|
|
|
|
if len(original) == 0 {
|
|
|
|
if original == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return []T{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return append(clones, original...)
|
2023-08-08 18:31:48 +03:00
|
|
|
}
|
2024-10-14 17:44:24 +03:00
|
|
|
|
|
|
|
// ECS is the content of the EDNS Client Subnet option of a DNS message.
|
|
|
|
//
|
|
|
|
// See https://datatracker.ietf.org/doc/html/rfc7871#section-6.
|
|
|
|
type ECS struct {
|
|
|
|
// Location is the GeoIP location data about the IP address from the
|
|
|
|
// request's ECS data, if any.
|
|
|
|
Location *geoip.Location
|
|
|
|
|
|
|
|
// Subnet is the source subnet.
|
|
|
|
Subnet netip.Prefix
|
|
|
|
|
|
|
|
// Scope is the scope prefix.
|
|
|
|
Scope uint8
|
|
|
|
}
|