Documentation
¶
Overview ¶
Package dns builds and parses the queries the DNS_CHECK health check uses.
This replaces the encoder at keepalived/check/check_dns.c:275-292, which the port's RFC lists as a fuzz target for good reason. That loop writes labels into a fixed 768-byte buffer (DNS_BUFFER_SIZE, check_dns.h:34) with no bounds check of any kind, and it writes each label length as
*(p++) = (uint8_t)n;
which is wrong twice over:
- A configured name longer than the buffer overflows it outright.
- A label of 64 bytes or more does not fit the length field. DNS reserves the top two bits of that byte for compression pointers (RFC 1035 §4.1.4), so a 64-byte label sets bit 6 and turns the length into a malformed pointer; a 256-byte label wraps to zero and silently terminates the name.
Neither is reported. The check simply sends a query that means something other than what was configured.
Index ¶
Constants ¶
const ( // MaxLabel is the longest single label. The length byte's top two bits // are reserved for compression pointers, leaving six bits. MaxLabel = 63 // MaxName is the longest encoded name, including length bytes and the // root terminator. MaxName = 255 // HeaderLen is the fixed message header. HeaderLen = 12 // BufferSize matches keepalived's DNS_BUFFER_SIZE, for parity on the // size of what is sent and received. BufferSize = 768 )
Protocol limits (RFC 1035 §2.3.4).
const ( RCodeNoError = 0 RCodeFormErr = 1 RCodeServFail = 2 RCodeNXDomain = 3 RCodeNotImp = 4 RCodeRefused = 5 )
Response codes (RFC 1035 §4.1.1).
const ClassIN = 1
ClassIN is the internet class.
const DefaultTimeout = 5 * time.Second
DefaultTimeout bounds one exchange when none is configured.
Variables ¶
var ( // ErrLabelTooLong is returned for a label over MaxLabel bytes — the // case C silently corrupts. ErrLabelTooLong = errors.New("dns: label exceeds 63 bytes") // ErrNameTooLong is returned for an encoded name over MaxName bytes. ErrNameTooLong = errors.New("dns: name exceeds 255 bytes") // ErrEmptyLabel is returned for a name containing an empty label, which // would encode as a premature root terminator. ErrEmptyLabel = errors.New("dns: name contains an empty label") // ErrShortMessage is returned when a buffer is too small to hold what // it claims. ErrShortMessage = errors.New("dns: message truncated") )
var ErrNotOurs = errors.New("dns: not this query's response")
ErrNotOurs marks a datagram that is not this exchange's answer: too short to hold a header, carrying another query's id, or not a response at all.
It is distinct from a failed check because C treats it that way. dns_recv_thread re-arms the read for each of the three (check_dns.c:202-236) and only a non-zero response code reaches dns_final as an error (:238-239). A datagram that is not the answer means the answer has not arrived, and the thing that decides the check is the timeout.
Failing on it instead would let one stray or duplicate datagram from the server take a healthy backend out of rotation.
Functions ¶
func Check ¶
func Check(ctx context.Context, server netip.AddrPort, name, qtype string, timeout time.Duration) (bool, error)
Check sends one query to a resolver and validates the answer.
func CheckResponse ¶
CheckResponse decides whether a response satisfies the health check.
keepalived requires the transaction id to match, the message to be a response, the rcode to be zero, and — when a type was configured — at least one answer. A NOERROR with no answers means the name exists but has no record of that type, which is not a working backend for this check.
func EncodeName ¶
EncodeName writes a domain name in wire format: each label prefixed by its length, terminated by a zero byte.
Every limit is enforced and reported rather than truncated. A name that does not fit is a configuration error the operator can fix; a name silently mangled into a different one produces a health check that passes or fails for reasons nobody can see.
Types ¶
type Header ¶
type Header struct {
ID uint16
Flags uint16
QDCount uint16
ANCount uint16
NSCount uint16
ARCount uint16
}
Header is a parsed message header.
type Query ¶
type Query struct {
ID uint16
Name string
Type Type
// RecursionDesired sets the RD flag, which keepalived sets by default
// because a health check normally asks a resolver rather than an
// authority.
RecursionDesired bool
}
Query is a DNS question.