diff --git a/internal/dns/message.go b/internal/dns/message.go index 1cc6511..690ce7e 100644 --- a/internal/dns/message.go +++ b/internal/dns/message.go @@ -35,10 +35,11 @@ const ( FlagRD = 0x0100 // Bit 8: Recursion Desired FlagRA = 0x0080 // Bit 7: Recursion Available - RcodeSuccess = 0 // No error - RcodeFormat = 1 // Format error - request malformed - RcodeServer = 2 // Server failure - RcodeNXDomain = 3 // Non-existent domain + RcodeSuccess = 0 // No error + RcodeFormat = 1 // Format error - request malformed + RcodeServer = 2 // Server failure + RcodeNXDomain = 3 // Non-existent domain + RcodeMask = 0x000F // Bottom 4 bits: Response code TypeA uint16 = 1 TypeNS uint16 = 2 @@ -48,6 +49,7 @@ const ( TypeTXT uint16 = 16 TypeAAAA uint16 = 28 TypeOPT uint16 = 41 + TypeTSIG uint16 = 250 ClassIN uint16 = 1 MaxMessageSize = 4096 @@ -337,7 +339,7 @@ func ValidateResponseWithRequest(reqHeader Header, reqQuestions []Question, resp return ErrIDMismatch } - if respHeader.Flags&0x8000 == 0 { + if respHeader.Flags&FlagQR == 0 { return ErrNotResponse } diff --git a/internal/dns/response.go b/internal/dns/response.go index d30d4a7..5b10304 100644 --- a/internal/dns/response.go +++ b/internal/dns/response.go @@ -80,7 +80,7 @@ func BuildResponse(req []byte, answers []Answer, rcode uint16) ([]byte, error) { } func responseFlags(reqFlags uint16, rcode uint16) uint16 { - return FlagQR | (reqFlags & FlagOpcode) | (reqFlags & FlagRD) | FlagRA | (rcode & 0x000F) + return FlagQR | (reqFlags & FlagOpcode) | (reqFlags & FlagRD) | FlagRA | (rcode & RcodeMask) } // MinimizeResponse strips authority/additional sections to reduce size. diff --git a/internal/dns/ttl.go b/internal/dns/ttl.go index f2d2cda..c3750db 100644 --- a/internal/dns/ttl.go +++ b/internal/dns/ttl.go @@ -2,62 +2,6 @@ package dns import "encoding/binary" -// RewriteAllRRsTTL rewrites the TTL field for all resource records -// (answer/authority/additional) to the provided value. -// This is used to serve cached packets with a TTL reflecting remaining lifetime. -// -// OPT (EDNS0) and TSIG are pseudo-RRs; their "TTL" fields are not TTLs and must not be rewritten. -func RewriteAllRRsTTL(msg []byte, ttl uint32) error { - h, err := ReadHeader(msg) - if err != nil { - return err - } - off := HeaderLen - for i := 0; i < int(h.QDCount); i++ { - next, err := SkipName(msg, off) - if err != nil { - return err - } - if len(msg) < next+4 { - return ErrShortBuffer - } - off = next + 4 // QTYPE(2) + QCLASS(2) - } - - rewriteRR := func(count uint16) error { - for i := 0; i < int(count); i++ { - next, err := SkipName(msg, off) - if err != nil { - return err - } - if len(msg) < next+10 { - return ErrShortBuffer - } - rrType := binary.BigEndian.Uint16(msg[next : next+2]) - if rrType != TypeOPT && rrType != 250 { - binary.BigEndian.PutUint32(msg[next+4:next+8], ttl) - } - rdlen := int(binary.BigEndian.Uint16(msg[next+8 : next+10])) - off = next + 10 + rdlen - if off > len(msg) { - return ErrShortBuffer - } - } - return nil - } - - if err := rewriteRR(h.ANCount); err != nil { - return err - } - if err := rewriteRR(h.NSCount); err != nil { - return err - } - if err := rewriteRR(h.ARCount); err != nil { - return err - } - return nil -} - // RewriteTTLsAtOffsets rewrites TTL fields at provided offsets. // Offsets must point at the start of the 4-byte TTL field. func RewriteTTLsAtOffsets(msg []byte, ttl uint32, offsets []uint16) { @@ -103,7 +47,7 @@ func CollectTTLOffsets(msg []byte) ([]uint16, error) { return ErrShortBuffer } rrType := binary.BigEndian.Uint16(msg[next : next+2]) - if rrType != TypeOPT && rrType != 250 { + if rrType != TypeOPT && rrType != TypeTSIG { ttlOff := next + 4 if ttlOff+4 <= len(msg) { offsets = append(offsets, uint16(ttlOff)) diff --git a/internal/dns/util.go b/internal/dns/util.go index 406fdd6..4304362 100644 --- a/internal/dns/util.go +++ b/internal/dns/util.go @@ -11,41 +11,6 @@ func NormalizeName(name string) string { return asciiLowerIfNeeded(name) } -// JoinLabels joins a slice of domain name labels with dots. -// Returns "." if the labels slice is empty. -func JoinLabels(labels []string) string { - if len(labels) == 0 { - return "." - } - return strings.Join(labels, ".") -} - -// SplitLabels splits a domain name into its constituent labels. -// Returns an empty slice for empty names or root (".") names. -func SplitLabels(name string) []string { - if name == "" || name == "." { - return nil - } - name = strings.TrimSuffix(name, ".") - - dotCount := strings.Count(name, ".") - labels := make([]string, 0, dotCount+1) - - start := 0 - for i := 0; i < len(name); i++ { - if name[i] == '.' { - if i > start { - labels = append(labels, name[start:i]) - } - start = i + 1 - } - } - if start < len(name) { - labels = append(labels, name[start:]) - } - return labels -} - // IsSubdomain checks if child is a subdomain of parent. // Both names should be normalized (lowercase, no trailing dot). func IsSubdomain(child, parent string) bool { @@ -161,16 +126,3 @@ func BuildQueryIntoWithEDNS(buf []byte, id uint16, name string, qtype, qclass ui return off, nil } - -// BuildQuery constructs a DNS query message for the given name, type, and class. -// It uses a maximum-size buffer to avoid calculation errors, as DNS messages -// over UDP are limited to 512 bytes per RFC 1035. -// Returns the serialized query as a byte slice. -func BuildQuery(id uint16, name string, qtype, qclass uint16) ([]byte, error) { - buf := make([]byte, MaxMessageSize) - n, err := BuildQueryInto(buf, id, name, qtype, qclass) - if err != nil { - return nil, err - } - return buf[:n], nil -} diff --git a/internal/resolver/recursive.go b/internal/resolver/recursive.go index 88683f9..8039453 100644 --- a/internal/resolver/recursive.go +++ b/internal/resolver/recursive.go @@ -457,7 +457,7 @@ func (r *Recursive) resolveIterative(ctx context.Context, reqHeader dns.Header, } if debugEnabled { - rcode := respMsg.Header.Flags & 0x000F + rcode := respMsg.Header.Flags & dns.RcodeMask kind := "empty" if len(respMsg.Answers) > 0 { kind = "answer" @@ -506,7 +506,7 @@ func (r *Recursive) resolveIterative(ctx context.Context, reqHeader dns.Header, return minimized, cleanup, nil } - if (respMsg.Header.Flags & 0x000F) == dns.RcodeNXDomain { + if (respMsg.Header.Flags & dns.RcodeMask) == dns.RcodeNXDomain { respMsg.Release() segMin := r.tracers.iterMinimize.TraceNested(sampled) minimized, _ := minimizeAndSetID(resp, clientID, true) diff --git a/internal/resolver/utils.go b/internal/resolver/utils.go index 64bb32f..3de517c 100644 --- a/internal/resolver/utils.go +++ b/internal/resolver/utils.go @@ -97,7 +97,7 @@ func servfailFromRequest(req []byte) ([]byte, bool) { resp := make([]byte, qEnd) copy(resp, req[:qEnd]) - hdr.Flags = dns.FlagQR | (hdr.Flags & dns.FlagOpcode) | (hdr.Flags & dns.FlagRD) | dns.FlagRA | (dns.RcodeServer & 0x000F) + hdr.Flags = dns.FlagQR | (hdr.Flags & dns.FlagOpcode) | (hdr.Flags & dns.FlagRD) | dns.FlagRA | (dns.RcodeServer & dns.RcodeMask) hdr.QDCount = 1 hdr.ANCount = 0 hdr.NSCount = 0 @@ -134,7 +134,7 @@ func sleepOrDone(ctx context.Context, d time.Duration) bool { // - NXDOMAIN and NOERROR/NODATA negative caching using SOA.MINIMUM (RFC2308-ish). // It returns (ttl, true) when cacheable. func cacheTTLForResponse(fullResp []byte, msg dns.Message, q dns.Question) (time.Duration, bool) { - rcode := msg.Header.Flags & 0x000F + rcode := msg.Header.Flags & dns.RcodeMask q = q.Normalize() soaTTL, hasSOA := negativeTTLFromSOA(fullResp, msg.Authorities) diff --git a/internal/server/udp.go b/internal/server/udp.go index 0f52025..b7ef9c4 100644 --- a/internal/server/udp.go +++ b/internal/server/udp.go @@ -23,7 +23,7 @@ func (s *Server) maybeLogServfail(resp []byte, addr net.Addr) { if err != nil { return } - if (hdr.Flags & 0x000F) != dns.RcodeServer { + if (hdr.Flags & dns.RcodeMask) != dns.RcodeServer { return } s.logger.Debug("sending SERVFAIL", "id", hdr.ID, "bytes", len(resp), "addr", addr.String()) @@ -146,25 +146,11 @@ func (s *Server) udpReadLoop(ctx context.Context, writer *udpWriter, cacheResolv if !dns.IsValidRequest(b[:n]) { if hdr, err := dns.ReadHeader(b[:n]); err == nil { var formerr [dns.HeaderLen]byte - formerr[0] = byte(hdr.ID >> 8) - formerr[1] = byte(hdr.ID) - // 0x80 = QR=1 (response), 0x01 = RD; preserve OPCODE from request - opcode := byte((hdr.Flags & dns.FlagOpcode) >> 8) - if hdr.Flags&dns.FlagRD != 0 { - formerr[2] = 0x80 | 0x01 | opcode - } else { - formerr[2] = 0x80 | opcode + fmtHdr := dns.Header{ + ID: hdr.ID, + Flags: dns.FlagQR | (hdr.Flags & dns.FlagOpcode) | (hdr.Flags & dns.FlagRD) | (dns.RcodeFormat & dns.RcodeMask), } - // RCODE=1 (Format Error) - formerr[3] = 0x01 - formerr[4] = 0x00 - formerr[5] = 0x00 - formerr[6] = 0x00 - formerr[7] = 0x00 - formerr[8] = 0x00 - formerr[9] = 0x00 - formerr[10] = 0x00 - formerr[11] = 0x00 + _ = dns.WriteHeader(formerr[:], fmtHdr) if _, err := writer.conn.WriteTo(formerr[:], addr); err != nil { s.WriteErrors.Add(1) } diff --git a/internal/server/utils.go b/internal/server/utils.go index f4a741f..fdc4a87 100644 --- a/internal/server/utils.go +++ b/internal/server/utils.go @@ -30,7 +30,7 @@ func servfailFromRequestInPlace(req []byte) ([]byte, bool) { return nil, false } - hdr.Flags = dns.FlagQR | (hdr.Flags & dns.FlagOpcode) | (hdr.Flags & dns.FlagRD) | dns.FlagRA | (dns.RcodeServer & 0x000F) + hdr.Flags = dns.FlagQR | (hdr.Flags & dns.FlagOpcode) | (hdr.Flags & dns.FlagRD) | dns.FlagRA | (dns.RcodeServer & dns.RcodeMask) hdr.QDCount = 1 hdr.ANCount = 0 hdr.NSCount = 0