msg_truncate.go (3478B)
1 package dns 2 3 // Truncate ensures the reply message will fit into the requested buffer 4 // size by removing records that exceed the requested size. 5 // 6 // It will first check if the reply fits without compression and then with 7 // compression. If it won't fit with compression, Truncate then walks the 8 // record adding as many records as possible without exceeding the 9 // requested buffer size. 10 // 11 // If the message fits within the requested size without compression, 12 // Truncate will set the message's Compress attribute to false. It is 13 // the caller's responsibility to set it back to true if they wish to 14 // compress the payload regardless of size. 15 // 16 // The TC bit will be set if any records were excluded from the message. 17 // If the TC bit is already set on the message it will be retained. 18 // TC indicates that the client should retry over TCP. 19 // 20 // According to RFC 2181, the TC bit should only be set if not all of the 21 // "required" RRs can be included in the response. Unfortunately, we have 22 // no way of knowing which RRs are required so we set the TC bit if any RR 23 // had to be omitted from the response. 24 // 25 // The appropriate buffer size can be retrieved from the requests OPT 26 // record, if present, and is transport specific otherwise. dns.MinMsgSize 27 // should be used for UDP requests without an OPT record, and 28 // dns.MaxMsgSize for TCP requests without an OPT record. 29 func (dns *Msg) Truncate(size int) { 30 if dns.IsTsig() != nil { 31 // To simplify this implementation, we don't perform 32 // truncation on responses with a TSIG record. 33 return 34 } 35 36 // RFC 6891 mandates that the payload size in an OPT record 37 // less than 512 (MinMsgSize) bytes must be treated as equal to 512 bytes. 38 // 39 // For ease of use, we impose that restriction here. 40 if size < MinMsgSize { 41 size = MinMsgSize 42 } 43 44 l := msgLenWithCompressionMap(dns, nil) // uncompressed length 45 if l <= size { 46 // Don't waste effort compressing this message. 47 dns.Compress = false 48 return 49 } 50 51 dns.Compress = true 52 53 edns0 := dns.popEdns0() 54 if edns0 != nil { 55 // Account for the OPT record that gets added at the end, 56 // by subtracting that length from our budget. 57 // 58 // The EDNS(0) OPT record must have the root domain and 59 // it's length is thus unaffected by compression. 60 size -= Len(edns0) 61 } 62 63 compression := make(map[string]struct{}) 64 65 l = headerSize 66 for _, r := range dns.Question { 67 l += r.len(l, compression) 68 } 69 70 var numAnswer int 71 if l < size { 72 l, numAnswer = truncateLoop(dns.Answer, size, l, compression) 73 } 74 75 var numNS int 76 if l < size { 77 l, numNS = truncateLoop(dns.Ns, size, l, compression) 78 } 79 80 var numExtra int 81 if l < size { 82 _, numExtra = truncateLoop(dns.Extra, size, l, compression) 83 } 84 85 // See the function documentation for when we set this. 86 dns.Truncated = dns.Truncated || len(dns.Answer) > numAnswer || 87 len(dns.Ns) > numNS || len(dns.Extra) > numExtra 88 89 dns.Answer = dns.Answer[:numAnswer] 90 dns.Ns = dns.Ns[:numNS] 91 dns.Extra = dns.Extra[:numExtra] 92 93 if edns0 != nil { 94 // Add the OPT record back onto the additional section. 95 dns.Extra = append(dns.Extra, edns0) 96 } 97 } 98 99 func truncateLoop(rrs []RR, size, l int, compression map[string]struct{}) (int, int) { 100 for i, r := range rrs { 101 if r == nil { 102 continue 103 } 104 105 l += r.len(l, compression) 106 if l > size { 107 // Return size, rather than l prior to this record, 108 // to prevent any further records being added. 109 return size, i 110 } 111 if l == size { 112 return l, i + 1 113 } 114 } 115 116 return l, len(rrs) 117 }