msg_truncate.go raw

   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  }
 118