dns/dnsmessage: don't include bytes after name.Length in the compression map

This improves the performance of name compression and makes
the name.Data[name.Length:] not included in the compression
map, it is unnecessary and might cause issues (i.e. reusing
the Name struct, without using the NewName function).

goos: linux
goarch: amd64
pkg: golang.org/x/net/dns/dnsmessage
cpu: Intel(R) Core(TM) i5-4200M CPU @ 2.50GHz
             │    before     │                after                 │
             │    sec/op     │    sec/op     vs base                │
Pack-4         15.672µ ± 13%   5.470µ ± 14%  -65.10% (p=0.000 n=10)
AppendPack-4   15.144µ ± 12%   5.330µ ± 10%  -64.80% (p=0.000 n=10)
geomean         15.41µ         5.400µ        -64.95%

             │    before    │                after                 │
             │     B/op     │     B/op      vs base                │
Pack-4         6.051Ki ± 0%   1.285Ki ± 0%  -78.76% (p=0.000 n=10)
AppendPack-4    5684.0 ± 0%     804.0 ± 0%  -85.86% (p=0.000 n=10)
geomean        5.795Ki        1.005Ki       -82.67%

             │   before   │               after                │
             │ allocs/op  │ allocs/op   vs base                │
Pack-4         21.00 ± 0%   11.00 ± 0%  -47.62% (p=0.000 n=10)
AppendPack-4   20.00 ± 0%   10.00 ± 0%  -50.00% (p=0.000 n=10)
geomean        20.49        10.49       -48.82%

Change-Id: Idf40d5d4790d37eb7253214f089eff859a937c60
GitHub-Last-Rev: a3182830e27086a0e12e116c7f7916468eb1edf2
GitHub-Pull-Request: golang/net#190
Reviewed-on: https://go-review.googlesource.com/c/net/+/522817
Run-TryBot: Mateusz Poliwczak <mpoliwczak34@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
diff --git a/dns/dnsmessage/message.go b/dns/dnsmessage/message.go
index cd997ba..9ddf2c2 100644
--- a/dns/dnsmessage/message.go
+++ b/dns/dnsmessage/message.go
@@ -1961,6 +1961,8 @@
 		return append(msg, 0), nil
 	}
 
+	var nameAsStr string
+
 	// Emit sequence of counted strings, chopping at dots.
 	for i, begin := 0, 0; i < int(n.Length); i++ {
 		// Check for the end of the segment.
@@ -1991,7 +1993,7 @@
 		// segment. A pointer is two bytes with the two most significant
 		// bits set to 1 to indicate that it is a pointer.
 		if (i == 0 || n.Data[i-1] == '.') && compression != nil {
-			if ptr, ok := compression[string(n.Data[i:])]; ok {
+			if ptr, ok := compression[string(n.Data[i:n.Length])]; ok {
 				// Hit. Emit a pointer instead of the rest of
 				// the domain.
 				return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil
@@ -2000,7 +2002,12 @@
 			// Miss. Add the suffix to the compression table if the
 			// offset can be stored in the available 14 bytes.
 			if len(msg) <= int(^uint16(0)>>2) {
-				compression[string(n.Data[i:])] = len(msg) - compressionOff
+				if nameAsStr == "" {
+					// allocate n.Data on the heap once, to avoid allocating it
+					// multiple times (for next labels).
+					nameAsStr = string(n.Data[:n.Length])
+				}
+				compression[nameAsStr[i:]] = len(msg) - compressionOff
 			}
 		}
 	}
diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go
index 1b7f3cb..ee42feb 100644
--- a/dns/dnsmessage/message_test.go
+++ b/dns/dnsmessage/message_test.go
@@ -1813,3 +1813,37 @@
 		t.Fatalf("p.SkipAllAuthorities(): unexpected success in Answer section")
 	}
 }
+
+func TestBuilderNameCompressionWithNonZeroedName(t *testing.T) {
+	b := NewBuilder(nil, Header{})
+	b.EnableCompression()
+	if err := b.StartQuestions(); err != nil {
+		t.Fatalf("b.StartQuestions() unexpected error: %v", err)
+	}
+
+	name := MustNewName("go.dev.")
+	if err := b.Question(Question{Name: name}); err != nil {
+		t.Fatalf("b.Question() unexpected error: %v", err)
+	}
+
+	// Character that is not part of the name (name.Data[:name.Length]),
+	// shouldn't affect the compression algorithm.
+	name.Data[name.Length] = '1'
+	if err := b.Question(Question{Name: name}); err != nil {
+		t.Fatalf("b.Question() unexpected error: %v", err)
+	}
+
+	msg, err := b.Finish()
+	if err != nil {
+		t.Fatalf("b.Finish() unexpected error: %v", err)
+	}
+
+	expect := []byte{
+		0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, // header
+		2, 'g', 'o', 3, 'd', 'e', 'v', 0, 0, 0, 0, 0, // question 1
+		0xC0, 12, 0, 0, 0, 0, // question 2
+	}
+	if !bytes.Equal(msg, expect) {
+		t.Fatalf("b.Finish() = %v, want: %v", msg, expect)
+	}
+}