dns

package
v0.0.0-...-efbc44a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: GPL-2.0 Imports: 9 Imported by: 0

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

View Source
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).

View Source
const (
	RCodeNoError  = 0
	RCodeFormErr  = 1
	RCodeServFail = 2
	RCodeNXDomain = 3
	RCodeNotImp   = 4
	RCodeRefused  = 5
)

Response codes (RFC 1035 §4.1.1).

View Source
const ClassIN = 1

ClassIN is the internet class.

View Source
const DefaultTimeout = 5 * time.Second

DefaultTimeout bounds one exchange when none is configured.

Variables

View Source
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")
)
View Source
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

func CheckResponse(b []byte, queryID uint16, requireAnswer bool) error

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

func EncodeName(name string) ([]byte, error)

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 struct {
	ID      uint16
	Flags   uint16
	QDCount uint16
	ANCount uint16
	NSCount uint16
	ARCount uint16
}

Header is a parsed message header.

func ParseHeader

func ParseHeader(b []byte) (Header, error)

ParseHeader decodes a message header.

func (Header) RCode

func (h Header) RCode() uint8

RCode returns the response code, the low four bits of the flags.

func (Header) Response

func (h Header) Response() bool

Response reports whether the message is a response (QR set).

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.

func (Query) Encode

func (q Query) Encode() ([]byte, error)

Encode builds the query message.

type Type

type Type uint16

Type is a DNS record type.

const (
	TypeA     Type = 1
	TypeNS    Type = 2
	TypeCNAME Type = 5
	TypeSOA   Type = 6
	TypeMX    Type = 15
	TypeTXT   Type = 16
	TypeAAAA  Type = 28
	TypeRRSIG Type = 46
)

The record types keepalived's DNS_CHECK accepts.

func ParseType

func ParseType(name string) (Type, error)

ParseType maps a configured type name to its numeric value.

The set is the one keepalived's DNS_CHECK accepts (check_dns.c). An empty name defaults to A, matching C's default, because a check with no type is asking the commonest question.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL