From 302b9c00d04fb3d5f9d60c9146d7eaf1efd34022 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 10 Apr 2018 02:56:43 +0200 Subject: [PATCH 01/12] Fixed len computation when size just goes beyond 14 bits --- length_test.go | 19 +++++++++++++++++++ msg.go | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/length_test.go b/length_test.go index 5324c22c9..31c279619 100644 --- a/length_test.go +++ b/length_test.go @@ -172,3 +172,22 @@ func TestMsgCompressLengthLargeRecords(t *testing.T) { t.Fatalf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", msg.Question[0].Name, len(msg.Answer), predicted, len(buf)) } } + +func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) { + msg := new(Msg) + msg.Compress = true + msg.SetQuestion("redis.service.consul.", TypeSRV) + for i := 0; i < 900; i++ { + target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) + msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) + msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) + predicted := msg.Len() + buf, err := msg.Pack() + if err != nil { + t.Error(err) + } + if predicted != len(buf) { + t.Fatalf("predicted compressed length is wrong for %d records: predicted %s (len=%d) %d, actual %d", i, msg.Question[0].Name, len(msg.Answer), predicted, len(buf)) + } + } +} diff --git a/msg.go b/msg.go index 276e6b027..b58b326bc 100644 --- a/msg.go +++ b/msg.go @@ -971,7 +971,6 @@ func compressionLenSlice(len int, c map[string]int, rs []RR) int { // track this length, and the global length in len, while taking compression into account for both. x := r.len() l += x - len += x k, ok := compressionLenSearch(c, r.Header().Name) if ok { @@ -992,6 +991,7 @@ func compressionLenSlice(len int, c map[string]int, rs []RR) int { if len < maxCompressionOffset { compressionLenHelperType(c, r) } + len += x } return l } From 6c75d8245382ddce282554459de38fee1c425330 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 10 Apr 2018 19:06:46 +0200 Subject: [PATCH 02/12] Added bouds checks around 14bits --- length_test.go | 24 ++++++++++++++++++ msg.go | 30 ++++++++++++++-------- zcompress.go | 68 +++++++++++++++++++++++++------------------------- 3 files changed, 77 insertions(+), 45 deletions(-) diff --git a/length_test.go b/length_test.go index 31c279619..bd5fd97cf 100644 --- a/length_test.go +++ b/length_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "net" + "strings" "testing" ) @@ -173,6 +174,29 @@ func TestMsgCompressLengthLargeRecords(t *testing.T) { } } +func TestMsgCompressLengthLargeRecordsWithPaddingPermutation(t *testing.T) { + msg := new(Msg) + msg.Compress = true + msg.SetQuestion("my.service.acme.", TypeSRV) + + for i := 0; i < 250; i++ { + target := fmt.Sprintf("host-redis-x-%d.test.acme.com.node.dc1.consul.", i) + msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) + msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.x.%d.", i)}) + } + for labelSize := 1; labelSize < 63; labelSize++ { + msg.SetQuestion(fmt.Sprintf("my.%s.service.acme.", strings.Repeat("x", labelSize)), TypeSRV) + predicted := msg.Len() + buf, err := msg.Pack() + if err != nil { + t.Error(err) + } + if predicted != len(buf) { + t.Fatalf("padding= %d ; predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", labelSize, msg.Question[0].Name, len(msg.Answer), predicted, len(buf)) + } + } +} + func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) { msg := new(Msg) msg.Compress = true diff --git a/msg.go b/msg.go index b58b326bc..d8a69fc59 100644 --- a/msg.go +++ b/msg.go @@ -931,7 +931,7 @@ func compressedLen(dns *Msg, compress bool) int { compression := map[string]int{} for _, r := range dns.Question { l += r.len() - compressionLenHelper(compression, r.Name) + compressionLenHelper(compression, r.Name, l) } l += compressionLenSlice(l, compression, dns.Answer) l += compressionLenSlice(l, compression, dns.Ns) @@ -962,7 +962,7 @@ func compressedLen(dns *Msg, compress bool) int { return l } -func compressionLenSlice(len int, c map[string]int, rs []RR) int { +func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { var l int for _, r := range rs { if r == nil { @@ -970,40 +970,48 @@ func compressionLenSlice(len int, c map[string]int, rs []RR) int { } // track this length, and the global length in len, while taking compression into account for both. x := r.len() + initLen := lenp l += x + compressedSize := 0 k, ok := compressionLenSearch(c, r.Header().Name) if ok { l += 1 - k - len += 1 - k + lenp += 1 - k + compressedSize = len(r.Header().Name) - k + 1 } - if len < maxCompressionOffset { - compressionLenHelper(c, r.Header().Name) + if initLen+compressedSize < maxCompressionOffset { + compressionLenHelper(c, r.Header().Name, initLen+compressedSize) } k, ok = compressionLenSearchType(c, r) if ok { l += 1 - k - len += 1 - k + lenp += 1 - k + compressedSize += len(r.Header().Name) - k + 1 } - if len < maxCompressionOffset { - compressionLenHelperType(c, r) + if initLen+compressedSize < maxCompressionOffset { + compressionLenHelperType(c, r, initLen+compressedSize) } - len += x + lenp += x } return l } // Put the parts of the name in the compression map. -func compressionLenHelper(c map[string]int, s string) { +func compressionLenHelper(c map[string]int, s string, currentLen int) { pref := "" lbs := Split(s) for j := len(lbs) - 1; j >= 0; j-- { pref = s[lbs[j]:] if _, ok := c[pref]; !ok { - c[pref] = len(pref) + lenAdded := len(pref) + offsetOfLabel := currentLen + len(s) - len(pref) + 6 + if offsetOfLabel < maxCompressionOffset { + c[pref] = lenAdded + } } } } diff --git a/zcompress.go b/zcompress.go index c2503204d..a75bb38b7 100644 --- a/zcompress.go +++ b/zcompress.go @@ -2,71 +2,71 @@ package dns -func compressionLenHelperType(c map[string]int, r RR) { +func compressionLenHelperType(c map[string]int, r RR, currentLen int) { switch x := r.(type) { case *AFSDB: - compressionLenHelper(c, x.Hostname) + compressionLenHelper(c, x.Hostname, currentLen) case *CNAME: - compressionLenHelper(c, x.Target) + compressionLenHelper(c, x.Target, currentLen) case *DNAME: - compressionLenHelper(c, x.Target) + compressionLenHelper(c, x.Target, currentLen) case *HIP: for i := range x.RendezvousServers { - compressionLenHelper(c, x.RendezvousServers[i]) + compressionLenHelper(c, x.RendezvousServers[i], currentLen) } case *KX: - compressionLenHelper(c, x.Exchanger) + compressionLenHelper(c, x.Exchanger, currentLen) case *LP: - compressionLenHelper(c, x.Fqdn) + compressionLenHelper(c, x.Fqdn, currentLen) case *MB: - compressionLenHelper(c, x.Mb) + compressionLenHelper(c, x.Mb, currentLen) case *MD: - compressionLenHelper(c, x.Md) + compressionLenHelper(c, x.Md, currentLen) case *MF: - compressionLenHelper(c, x.Mf) + compressionLenHelper(c, x.Mf, currentLen) case *MG: - compressionLenHelper(c, x.Mg) + compressionLenHelper(c, x.Mg, currentLen) case *MINFO: - compressionLenHelper(c, x.Rmail) - compressionLenHelper(c, x.Email) + compressionLenHelper(c, x.Rmail, currentLen) + compressionLenHelper(c, x.Email, currentLen) case *MR: - compressionLenHelper(c, x.Mr) + compressionLenHelper(c, x.Mr, currentLen) case *MX: - compressionLenHelper(c, x.Mx) + compressionLenHelper(c, x.Mx, currentLen) case *NAPTR: - compressionLenHelper(c, x.Replacement) + compressionLenHelper(c, x.Replacement, currentLen) case *NS: - compressionLenHelper(c, x.Ns) + compressionLenHelper(c, x.Ns, currentLen) case *NSAPPTR: - compressionLenHelper(c, x.Ptr) + compressionLenHelper(c, x.Ptr, currentLen) case *NSEC: - compressionLenHelper(c, x.NextDomain) + compressionLenHelper(c, x.NextDomain, currentLen) case *PTR: - compressionLenHelper(c, x.Ptr) + compressionLenHelper(c, x.Ptr, currentLen) case *PX: - compressionLenHelper(c, x.Map822) - compressionLenHelper(c, x.Mapx400) + compressionLenHelper(c, x.Map822, currentLen) + compressionLenHelper(c, x.Mapx400, currentLen) case *RP: - compressionLenHelper(c, x.Mbox) - compressionLenHelper(c, x.Txt) + compressionLenHelper(c, x.Mbox, currentLen) + compressionLenHelper(c, x.Txt, currentLen) case *RRSIG: - compressionLenHelper(c, x.SignerName) + compressionLenHelper(c, x.SignerName, currentLen) case *RT: - compressionLenHelper(c, x.Host) + compressionLenHelper(c, x.Host, currentLen) case *SIG: - compressionLenHelper(c, x.SignerName) + compressionLenHelper(c, x.SignerName, currentLen) case *SOA: - compressionLenHelper(c, x.Ns) - compressionLenHelper(c, x.Mbox) + compressionLenHelper(c, x.Ns, currentLen) + compressionLenHelper(c, x.Mbox, currentLen) case *SRV: - compressionLenHelper(c, x.Target) + compressionLenHelper(c, x.Target, currentLen) case *TALINK: - compressionLenHelper(c, x.PreviousName) - compressionLenHelper(c, x.NextName) + compressionLenHelper(c, x.PreviousName, currentLen) + compressionLenHelper(c, x.NextName, currentLen) case *TKEY: - compressionLenHelper(c, x.Algorithm) + compressionLenHelper(c, x.Algorithm, currentLen) case *TSIG: - compressionLenHelper(c, x.Algorithm) + compressionLenHelper(c, x.Algorithm, currentLen) } } From 9452f40faefd2464218c8d364057cc1fc6ba29db Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 16 Apr 2018 10:28:43 +0200 Subject: [PATCH 03/12] Len() always right including when around 14bits boudaries --- compress_generate.go | 23 +++++--- length_test.go | 64 +++++++++++++++++++++ msg.go | 58 ++++++++++--------- zcompress.go | 130 ++++++++++++++++++++++--------------------- 4 files changed, 172 insertions(+), 103 deletions(-) diff --git a/compress_generate.go b/compress_generate.go index 87fb36f68..9bd346427 100644 --- a/compress_generate.go +++ b/compress_generate.go @@ -101,7 +101,8 @@ Names: // compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names - fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR) {\n") + fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR, initLen int) int {\n") + fmt.Fprint(b, "currentLen := initLen\n") fmt.Fprint(b, "switch x := r.(type) {\n") for _, name := range domainTypes { o := scope.Lookup(name) @@ -109,7 +110,9 @@ Names: fmt.Fprintf(b, "case *%s:\n", name) for i := 1; i < st.NumFields(); i++ { - out := func(s string) { fmt.Fprintf(b, "compressionLenHelper(c, x.%s)\n", st.Field(i).Name()) } + out := func(s string) { + fmt.Fprintf(b, "currentLen += compressionLenHelper(c, x.%s, currentLen)\n", st.Field(i).Name()) + } if _, ok := st.Field(i).Type().(*types.Slice); ok { switch st.Tag(i) { @@ -118,8 +121,8 @@ Names: case `dns:"cdomain-name"`: // For HIP we need to slice over the elements in this slice. fmt.Fprintf(b, `for i := range x.%s { - compressionLenHelper(c, x.%s[i]) - } + currentLen += compressionLenHelper(c, x.%s[i], currentLen) +} `, st.Field(i).Name(), st.Field(i).Name()) } continue @@ -133,11 +136,11 @@ Names: } } } - fmt.Fprintln(b, "}\n}\n\n") + fmt.Fprintln(b, "}\nreturn currentLen - initLen\n}\n\n") // compressionLenSearchType - search cdomain-tags types for compressible names. - fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool) {\n") + fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool, int) {\n") fmt.Fprint(b, "switch x := r.(type) {\n") for _, name := range cdomainTypes { o := scope.Lookup(name) @@ -147,7 +150,7 @@ Names: j := 1 for i := 1; i < st.NumFields(); i++ { out := func(s string, j int) { - fmt.Fprintf(b, "k%d, ok%d := compressionLenSearch(c, x.%s)\n", j, j, st.Field(i).Name()) + fmt.Fprintf(b, "k%d, ok%d, sz%d := compressionLenSearch(c, x.%s)\n", j, j, j, st.Field(i).Name()) } // There are no slice types with names that can be compressed. @@ -160,13 +163,15 @@ Names: } k := "k1" ok := "ok1" + sz := "sz1" for i := 2; i < j; i++ { k += fmt.Sprintf(" + k%d", i) ok += fmt.Sprintf(" && ok%d", i) + sz += fmt.Sprintf(" + sz%d", i) } - fmt.Fprintf(b, "return %s, %s\n", k, ok) + fmt.Fprintf(b, "return %s, %s, %s\n", k, ok, sz) } - fmt.Fprintln(b, "}\nreturn 0, false\n}\n\n") + fmt.Fprintln(b, "}\nreturn 0, false, 0\n}\n\n") // gofmt res, err := format.Source(b.Bytes()) diff --git a/length_test.go b/length_test.go index bd5fd97cf..27a13804b 100644 --- a/length_test.go +++ b/length_test.go @@ -80,6 +80,70 @@ func TestMsgLength(t *testing.T) { } } +func TestCompressionLenHelper(t *testing.T) { + c := make(map[string]int) + compressionLenHelper(c, "example.com", 12) + if c["example.com"] != 11 { + t.Errorf("bad %d", c["example.com"]) + } + if c["com"] != 3 { + t.Errorf("bad %d", c["com"]) + } + + // Test boundaries + c = make(map[string]int) + // foo label starts at 16379 + // com label starts at 16384 + compressionLenHelper(c, "foo.com", 16379) + if c["foo.com"] != 7 { + t.Errorf("bad %d", c["foo.com"]) + } + // com label is accessible + if c["com"] != 3 { + t.Errorf("bad %d", c["com"]) + } + + c = make(map[string]int) + // foo label starts at 16379 + // com label starts at 16385 => outside range + compressionLenHelper(c, "foo.com", 16380) + if c["foo.com"] != 7 { + t.Errorf("bad %d", c["foo.com"]) + } + // com label is NOT accessible + if c["com"] != 0 { + t.Errorf("bad %d", c["com"]) + } + + c = make(map[string]int) + compressionLenHelper(c, "example.com", 16375) + if c["example.com"] != 11 { + t.Errorf("bad %d", c["example.com"]) + } + // com starts AFTER 16384 + if c["com"] != 3 { + t.Errorf("bad %d", c["com"]) + } + + c = make(map[string]int) + compressionLenHelper(c, "example.com", 16376) + if c["example.com"] != 11 { + t.Errorf("bad %d", c["example.com"]) + } + // com starts AFTER 16384 + if c["com"] != 0 { + t.Errorf("bad %d", c["com"]) + } +} + +func TestCompressionLenSearch(t *testing.T) { + c := make(map[string]int) + compressed, ok, fullSize := compressionLenSearch(c, "a.b.org.") + if compressed != 0 || ok || fullSize != 14 { + panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize)) + } +} + func TestMsgLength2(t *testing.T) { // Serialized replies var testMessages = []string{ diff --git a/msg.go b/msg.go index d8a69fc59..75013c23a 100644 --- a/msg.go +++ b/msg.go @@ -963,77 +963,75 @@ func compressedLen(dns *Msg, compress bool) int { } func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { - var l int + initLen := lenp for _, r := range rs { if r == nil { continue } - // track this length, and the global length in len, while taking compression into account for both. + // TmpLen is to track len of record at 14bits boudaries, 6 bytes is the raw overhead + tmpLen := lenp + 6 x := r.len() - initLen := lenp - l += x - compressedSize := 0 - - k, ok := compressionLenSearch(c, r.Header().Name) + // track this length, and the global length in len, while taking compression into account for both. + k, ok, sz := compressionLenSearch(c, r.Header().Name) if ok { - l += 1 - k - lenp += 1 - k - compressedSize = len(r.Header().Name) - k + 1 - } - - if initLen+compressedSize < maxCompressionOffset { - compressionLenHelper(c, r.Header().Name, initLen+compressedSize) + x += 1 - k } + tmpLen += sz - k, ok = compressionLenSearchType(c, r) + tmpLen += compressionLenHelper(c, r.Header().Name, tmpLen) + k, ok, added := compressionLenSearchType(c, r) if ok { - l += 1 - k - lenp += 1 - k - compressedSize += len(r.Header().Name) - k + 1 - } - - if initLen+compressedSize < maxCompressionOffset { - compressionLenHelperType(c, r, initLen+compressedSize) + x += 1 - k } + tmpLen += added + tmpLen += compressionLenHelperType(c, r, tmpLen) lenp += x } - return l + return lenp - initLen } -// Put the parts of the name in the compression map. -func compressionLenHelper(c map[string]int, s string, currentLen int) { +// Put the parts of the name in the compression map, return the size added in payload +func compressionLenHelper(c map[string]int, s string, currentLen int) int { + addedSize := 0 pref := "" lbs := Split(s) for j := len(lbs) - 1; j >= 0; j-- { pref = s[lbs[j]:] if _, ok := c[pref]; !ok { lenAdded := len(pref) - offsetOfLabel := currentLen + len(s) - len(pref) + 6 + numLabelsBefore := len(lbs) - j - 1 + offsetOfLabel := currentLen + len(s) - len(pref) + numLabelsBefore*2 if offsetOfLabel < maxCompressionOffset { c[pref] = lenAdded + addedSize += 2 + lenAdded } } } + return addedSize } // Look for each part in the compression map and returns its length, // keep on searching so we get the longest match. -func compressionLenSearch(c map[string]int, s string) (int, bool) { +// Will return the size of compression found, whether a match has been +// found and the size of record if added in payload +func compressionLenSearch(c map[string]int, s string) (int, bool, int) { off := 0 end := false if s == "" { // don't bork on bogus data - return 0, false + return 0, false, 0 } + fullSize := 0 for { if _, ok := c[s[off:]]; ok { - return len(s[off:]), true + return len(s[off:]), true, fullSize + off } if end { break } + fullSize += 2 off, end = NextLabel(s, off) } - return 0, false + return 0, false, fullSize + len(s) } // Copy returns a new RR which is a deep-copy of r. diff --git a/zcompress.go b/zcompress.go index a75bb38b7..f01901677 100644 --- a/zcompress.go +++ b/zcompress.go @@ -2,117 +2,119 @@ package dns -func compressionLenHelperType(c map[string]int, r RR, currentLen int) { +func compressionLenHelperType(c map[string]int, r RR, initLen int) int { + currentLen := initLen switch x := r.(type) { case *AFSDB: - compressionLenHelper(c, x.Hostname, currentLen) + currentLen += compressionLenHelper(c, x.Hostname, currentLen) case *CNAME: - compressionLenHelper(c, x.Target, currentLen) + currentLen += compressionLenHelper(c, x.Target, currentLen) case *DNAME: - compressionLenHelper(c, x.Target, currentLen) + currentLen += compressionLenHelper(c, x.Target, currentLen) case *HIP: for i := range x.RendezvousServers { - compressionLenHelper(c, x.RendezvousServers[i], currentLen) + currentLen += compressionLenHelper(c, x.RendezvousServers[i], currentLen) } case *KX: - compressionLenHelper(c, x.Exchanger, currentLen) + currentLen += compressionLenHelper(c, x.Exchanger, currentLen) case *LP: - compressionLenHelper(c, x.Fqdn, currentLen) + currentLen += compressionLenHelper(c, x.Fqdn, currentLen) case *MB: - compressionLenHelper(c, x.Mb, currentLen) + currentLen += compressionLenHelper(c, x.Mb, currentLen) case *MD: - compressionLenHelper(c, x.Md, currentLen) + currentLen += compressionLenHelper(c, x.Md, currentLen) case *MF: - compressionLenHelper(c, x.Mf, currentLen) + currentLen += compressionLenHelper(c, x.Mf, currentLen) case *MG: - compressionLenHelper(c, x.Mg, currentLen) + currentLen += compressionLenHelper(c, x.Mg, currentLen) case *MINFO: - compressionLenHelper(c, x.Rmail, currentLen) - compressionLenHelper(c, x.Email, currentLen) + currentLen += compressionLenHelper(c, x.Rmail, currentLen) + currentLen += compressionLenHelper(c, x.Email, currentLen) case *MR: - compressionLenHelper(c, x.Mr, currentLen) + currentLen += compressionLenHelper(c, x.Mr, currentLen) case *MX: - compressionLenHelper(c, x.Mx, currentLen) + currentLen += compressionLenHelper(c, x.Mx, currentLen) case *NAPTR: - compressionLenHelper(c, x.Replacement, currentLen) + currentLen += compressionLenHelper(c, x.Replacement, currentLen) case *NS: - compressionLenHelper(c, x.Ns, currentLen) + currentLen += compressionLenHelper(c, x.Ns, currentLen) case *NSAPPTR: - compressionLenHelper(c, x.Ptr, currentLen) + currentLen += compressionLenHelper(c, x.Ptr, currentLen) case *NSEC: - compressionLenHelper(c, x.NextDomain, currentLen) + currentLen += compressionLenHelper(c, x.NextDomain, currentLen) case *PTR: - compressionLenHelper(c, x.Ptr, currentLen) + currentLen += compressionLenHelper(c, x.Ptr, currentLen) case *PX: - compressionLenHelper(c, x.Map822, currentLen) - compressionLenHelper(c, x.Mapx400, currentLen) + currentLen += compressionLenHelper(c, x.Map822, currentLen) + currentLen += compressionLenHelper(c, x.Mapx400, currentLen) case *RP: - compressionLenHelper(c, x.Mbox, currentLen) - compressionLenHelper(c, x.Txt, currentLen) + currentLen += compressionLenHelper(c, x.Mbox, currentLen) + currentLen += compressionLenHelper(c, x.Txt, currentLen) case *RRSIG: - compressionLenHelper(c, x.SignerName, currentLen) + currentLen += compressionLenHelper(c, x.SignerName, currentLen) case *RT: - compressionLenHelper(c, x.Host, currentLen) + currentLen += compressionLenHelper(c, x.Host, currentLen) case *SIG: - compressionLenHelper(c, x.SignerName, currentLen) + currentLen += compressionLenHelper(c, x.SignerName, currentLen) case *SOA: - compressionLenHelper(c, x.Ns, currentLen) - compressionLenHelper(c, x.Mbox, currentLen) + currentLen += compressionLenHelper(c, x.Ns, currentLen) + currentLen += compressionLenHelper(c, x.Mbox, currentLen) case *SRV: - compressionLenHelper(c, x.Target, currentLen) + currentLen += compressionLenHelper(c, x.Target, currentLen) case *TALINK: - compressionLenHelper(c, x.PreviousName, currentLen) - compressionLenHelper(c, x.NextName, currentLen) + currentLen += compressionLenHelper(c, x.PreviousName, currentLen) + currentLen += compressionLenHelper(c, x.NextName, currentLen) case *TKEY: - compressionLenHelper(c, x.Algorithm, currentLen) + currentLen += compressionLenHelper(c, x.Algorithm, currentLen) case *TSIG: - compressionLenHelper(c, x.Algorithm, currentLen) + currentLen += compressionLenHelper(c, x.Algorithm, currentLen) } + return currentLen - initLen } -func compressionLenSearchType(c map[string]int, r RR) (int, bool) { +func compressionLenSearchType(c map[string]int, r RR) (int, bool, int) { switch x := r.(type) { case *AFSDB: - k1, ok1 := compressionLenSearch(c, x.Hostname) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Hostname) + return k1, ok1, sz1 case *CNAME: - k1, ok1 := compressionLenSearch(c, x.Target) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Target) + return k1, ok1, sz1 case *MB: - k1, ok1 := compressionLenSearch(c, x.Mb) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Mb) + return k1, ok1, sz1 case *MD: - k1, ok1 := compressionLenSearch(c, x.Md) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Md) + return k1, ok1, sz1 case *MF: - k1, ok1 := compressionLenSearch(c, x.Mf) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Mf) + return k1, ok1, sz1 case *MG: - k1, ok1 := compressionLenSearch(c, x.Mg) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Mg) + return k1, ok1, sz1 case *MINFO: - k1, ok1 := compressionLenSearch(c, x.Rmail) - k2, ok2 := compressionLenSearch(c, x.Email) - return k1 + k2, ok1 && ok2 + k1, ok1, sz1 := compressionLenSearch(c, x.Rmail) + k2, ok2, sz2 := compressionLenSearch(c, x.Email) + return k1 + k2, ok1 && ok2, sz1 + sz2 case *MR: - k1, ok1 := compressionLenSearch(c, x.Mr) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Mr) + return k1, ok1, sz1 case *MX: - k1, ok1 := compressionLenSearch(c, x.Mx) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Mx) + return k1, ok1, sz1 case *NS: - k1, ok1 := compressionLenSearch(c, x.Ns) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Ns) + return k1, ok1, sz1 case *PTR: - k1, ok1 := compressionLenSearch(c, x.Ptr) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Ptr) + return k1, ok1, sz1 case *RT: - k1, ok1 := compressionLenSearch(c, x.Host) - return k1, ok1 + k1, ok1, sz1 := compressionLenSearch(c, x.Host) + return k1, ok1, sz1 case *SOA: - k1, ok1 := compressionLenSearch(c, x.Ns) - k2, ok2 := compressionLenSearch(c, x.Mbox) - return k1 + k2, ok1 && ok2 + k1, ok1, sz1 := compressionLenSearch(c, x.Ns) + k2, ok2, sz2 := compressionLenSearch(c, x.Mbox) + return k1 + k2, ok1 && ok2, sz1 + sz2 } - return 0, false + return 0, false, 0 } From 80580bd48181973cdcbf014c5f61941afa9b434d Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 16 Apr 2018 15:41:05 +0200 Subject: [PATCH 04/12] Avoid splitting into labels when not applicable --- msg.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/msg.go b/msg.go index 75013c23a..4f145ab5d 100644 --- a/msg.go +++ b/msg.go @@ -990,9 +990,15 @@ func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { return lenp - initLen } -// Put the parts of the name in the compression map, return the size added in payload +// Put the parts of the name in the compression map, return the size in bytes added in payload func compressionLenHelper(c map[string]int, s string, currentLen int) int { - addedSize := 0 + if currentLen >= maxCompressionOffset { + return 0 + } + if _, ok := c[s]; ok { + return 0 + } + addedSizeBytes := 0 pref := "" lbs := Split(s) for j := len(lbs) - 1; j >= 0; j-- { @@ -1000,14 +1006,14 @@ func compressionLenHelper(c map[string]int, s string, currentLen int) int { if _, ok := c[pref]; !ok { lenAdded := len(pref) numLabelsBefore := len(lbs) - j - 1 - offsetOfLabel := currentLen + len(s) - len(pref) + numLabelsBefore*2 + offsetOfLabel := currentLen + len(s) - lenAdded + numLabelsBefore*2 if offsetOfLabel < maxCompressionOffset { c[pref] = lenAdded - addedSize += 2 + lenAdded + addedSizeBytes += 2 + lenAdded } } } - return addedSize + return addedSizeBytes } // Look for each part in the compression map and returns its length, From a4e1a80fb7bbeab0e409717db55124a800caf6f4 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 16 Apr 2018 19:17:57 +0200 Subject: [PATCH 05/12] Fixed comments --- msg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msg.go b/msg.go index 4f145ab5d..1bb846f31 100644 --- a/msg.go +++ b/msg.go @@ -992,7 +992,7 @@ func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { // Put the parts of the name in the compression map, return the size in bytes added in payload func compressionLenHelper(c map[string]int, s string, currentLen int) int { - if currentLen >= maxCompressionOffset { + if currentLen+3 >= maxCompressionOffset { return 0 } if _, ok := c[s]; ok { From 3a2f2f6114e1815afa9fa4500ffaeecdc4334691 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Thu, 19 Apr 2018 00:15:18 +0200 Subject: [PATCH 06/12] Added comments in code --- msg.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/msg.go b/msg.go index 1bb846f31..85ba7186c 100644 --- a/msg.go +++ b/msg.go @@ -974,6 +974,8 @@ func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { // track this length, and the global length in len, while taking compression into account for both. k, ok, sz := compressionLenSearch(c, r.Header().Name) if ok { + // Size of x is reduced by k, but we add 1 since k includes the '.' and label descriptor take 2 bytes + // so, basically x:= x - k - 1 + 2 x += 1 - k } tmpLen += sz @@ -992,7 +994,8 @@ func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { // Put the parts of the name in the compression map, return the size in bytes added in payload func compressionLenHelper(c map[string]int, s string, currentLen int) int { - if currentLen+3 >= maxCompressionOffset { + if currentLen > maxCompressionOffset { + // We won't be able to add any label that could be re-used later anyway return 0 } if _, ok := c[s]; ok { @@ -1006,7 +1009,9 @@ func compressionLenHelper(c map[string]int, s string, currentLen int) int { if _, ok := c[pref]; !ok { lenAdded := len(pref) numLabelsBefore := len(lbs) - j - 1 + // 2 bytes per label before this label to take into account offsetOfLabel := currentLen + len(s) - lenAdded + numLabelsBefore*2 + // If first byte label is within the first 14bits, it might be re-used later if offsetOfLabel < maxCompressionOffset { c[pref] = lenAdded addedSizeBytes += 2 + lenAdded @@ -1034,6 +1039,7 @@ func compressionLenSearch(c map[string]int, s string) (int, bool, int) { if end { break } + // Each label descriptor takes 2 bytes, add it fullSize += 2 off, end = NextLabel(s, off) } From 7edff1b0982142965f9ab97232e0040491b6d59c Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Fri, 20 Apr 2018 10:25:17 +0200 Subject: [PATCH 07/12] Added new test cases --- length_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/length_test.go b/length_test.go index 27a13804b..93d52869e 100644 --- a/length_test.go +++ b/length_test.go @@ -142,6 +142,28 @@ func TestCompressionLenSearch(t *testing.T) { if compressed != 0 || ok || fullSize != 14 { panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize)) } + c["org."] = 3 + compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.") + if compressed != 4 || !ok || fullSize != 8 { + panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize)) + } + c["b.org."] = 5 + compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.") + if compressed != 6 || !ok || fullSize != 4 { + panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize)) + } + // Not found long compression + c["x.b.org."] = 5 + compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.") + if compressed != 6 || !ok || fullSize != 4 { + panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize)) + } + // Found long compression + c["a.b.org."] = 5 + compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.") + if compressed != 8 || !ok || fullSize != 0 { + panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize)) + } } func TestMsgLength2(t *testing.T) { From d3b59f85d035e9975b90bcd3418a4056bc611a49 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 30 Apr 2018 12:39:45 +0200 Subject: [PATCH 08/12] Fixed computation of Len() for SRV and all kind of records --- compress_generate.go | 5 +++ length_test.go | 90 ++++++++++++++++++++++++++++++++++++++------ msg.go | 80 +++++++++++++++++++++++---------------- zcompress.go | 35 +++++++++++++++++ 4 files changed, 166 insertions(+), 44 deletions(-) diff --git a/compress_generate.go b/compress_generate.go index 9bd346427..9a136c414 100644 --- a/compress_generate.go +++ b/compress_generate.go @@ -111,6 +111,7 @@ Names: fmt.Fprintf(b, "case *%s:\n", name) for i := 1; i < st.NumFields(); i++ { out := func(s string) { + fmt.Fprintf(b, "currentLen -= len(x.%s) + 1\n", st.Field(i).Name()) fmt.Fprintf(b, "currentLen += compressionLenHelper(c, x.%s, currentLen)\n", st.Field(i).Name()) } @@ -121,6 +122,10 @@ Names: case `dns:"cdomain-name"`: // For HIP we need to slice over the elements in this slice. fmt.Fprintf(b, `for i := range x.%s { + currentLen -= len(x.%s[i]) + 1 +} +`, st.Field(i).Name(), st.Field(i).Name()) + fmt.Fprintf(b, `for i := range x.%s { currentLen += compressionLenHelper(c, x.%s[i], currentLen) } `, st.Field(i).Name(), st.Field(i).Name()) diff --git a/length_test.go b/length_test.go index 93d52869e..66521a5f2 100644 --- a/length_test.go +++ b/length_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "net" + "reflect" "strings" "testing" ) @@ -53,6 +54,7 @@ func TestMsgCompressLength(t *testing.T) { func TestMsgLength(t *testing.T) { makeMsg := func(question string, ans, ns, e []RR) *Msg { msg := new(Msg) + msg.Compress = true msg.SetQuestion(Fqdn(question), TypeANY) msg.Answer = append(msg.Answer, ans...) msg.Ns = append(msg.Ns, ns...) @@ -83,10 +85,10 @@ func TestMsgLength(t *testing.T) { func TestCompressionLenHelper(t *testing.T) { c := make(map[string]int) compressionLenHelper(c, "example.com", 12) - if c["example.com"] != 11 { + if c["example.com"] != 12 { t.Errorf("bad %d", c["example.com"]) } - if c["com"] != 3 { + if c["com"] != 20 { t.Errorf("bad %d", c["com"]) } @@ -95,11 +97,11 @@ func TestCompressionLenHelper(t *testing.T) { // foo label starts at 16379 // com label starts at 16384 compressionLenHelper(c, "foo.com", 16379) - if c["foo.com"] != 7 { + if c["foo.com"] != 16379 { t.Errorf("bad %d", c["foo.com"]) } // com label is accessible - if c["com"] != 3 { + if c["com"] != 16383 { t.Errorf("bad %d", c["com"]) } @@ -107,7 +109,7 @@ func TestCompressionLenHelper(t *testing.T) { // foo label starts at 16379 // com label starts at 16385 => outside range compressionLenHelper(c, "foo.com", 16380) - if c["foo.com"] != 7 { + if c["foo.com"] != 16380 { t.Errorf("bad %d", c["foo.com"]) } // com label is NOT accessible @@ -117,17 +119,17 @@ func TestCompressionLenHelper(t *testing.T) { c = make(map[string]int) compressionLenHelper(c, "example.com", 16375) - if c["example.com"] != 11 { + if c["example.com"] != 16375 { t.Errorf("bad %d", c["example.com"]) } // com starts AFTER 16384 - if c["com"] != 3 { + if c["com"] != 16383 { t.Errorf("bad %d", c["com"]) } c = make(map[string]int) compressionLenHelper(c, "example.com", 16376) - if c["example.com"] != 11 { + if c["example.com"] != 16376 { t.Errorf("bad %d", c["example.com"]) } // com starts AFTER 16384 @@ -246,7 +248,7 @@ func TestMsgCompressLengthLargeRecords(t *testing.T) { msg.SetQuestion("my.service.acme.", TypeSRV) j := 1 for i := 0; i < 250; i++ { - target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", j, i) + target := fmt.Sprintf("host-redis-1-%d.test.acme.com.node.dc1.consul.", i) msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", j, i)}) } @@ -260,6 +262,72 @@ func TestMsgCompressLengthLargeRecords(t *testing.T) { } } +func TestCompareCompressionMapsForANY(t *testing.T) { + msg := new(Msg) + msg.Compress = true + msg.SetQuestion("a.service.acme.", TypeANY) + // Be sure to have more than 14bits + for i := 0; i < 2000; i++ { + target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i) + msg.Answer = append(msg.Answer, &AAAA{Hdr: RR_Header{Name: target, Rrtype: TypeAAAA, Class: ClassINET, Ttl: 0x3c}, AAAA: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, byte(i / 255), byte(i % 255)}}) + msg.Answer = append(msg.Answer, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}}) + if msg.Len() > 16384 { + break + } + } + for labelSize := 0; labelSize < 63; labelSize++ { + msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeANY) + + compressionFake := make(map[string]int) + lenFake := compressedLenWithCompressionMap(msg, compressionFake) + + compressionReal := make(map[string]int) + buf, err := msg.packBufferWithCompressionMap(nil, compressionReal) + if err != nil { + t.Fatal(err) + } + if lenFake != len(buf) { + t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf)) + } + if !reflect.DeepEqual(compressionFake, compressionReal) { + t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n*** Real:= %v\n\n***Fake:= %v", labelSize, compressionReal, compressionFake) + } + } +} + +func TestCompareCompressionMapsForSRV(t *testing.T) { + msg := new(Msg) + msg.Compress = true + msg.SetQuestion("a.service.acme.", TypeSRV) + // Be sure to have more than 14bits + for i := 0; i < 2000; i++ { + target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i) + msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: ClassINET, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) + msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}}) + if msg.Len() > 16384 { + break + } + } + for labelSize := 0; labelSize < 63; labelSize++ { + msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeAAAA) + + compressionFake := make(map[string]int) + lenFake := compressedLenWithCompressionMap(msg, compressionFake) + + compressionReal := make(map[string]int) + buf, err := msg.packBufferWithCompressionMap(nil, compressionReal) + if err != nil { + t.Fatal(err) + } + if lenFake != len(buf) { + t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf)) + } + if !reflect.DeepEqual(compressionFake, compressionReal) { + t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n*** Real:= %v\n\n***Fake:= %v", labelSize, compressionReal, compressionFake) + } + } +} + func TestMsgCompressLengthLargeRecordsWithPaddingPermutation(t *testing.T) { msg := new(Msg) msg.Compress = true @@ -268,7 +336,7 @@ func TestMsgCompressLengthLargeRecordsWithPaddingPermutation(t *testing.T) { for i := 0; i < 250; i++ { target := fmt.Sprintf("host-redis-x-%d.test.acme.com.node.dc1.consul.", i) msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) - msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.x.%d.", i)}) + msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.x.%d.", i)}) } for labelSize := 1; labelSize < 63; labelSize++ { msg.SetQuestion(fmt.Sprintf("my.%s.service.acme.", strings.Repeat("x", labelSize)), TypeSRV) @@ -290,7 +358,7 @@ func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) { for i := 0; i < 900; i++ { target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) - msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) + msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) predicted := msg.Len() buf, err := msg.Pack() if err != nil { diff --git a/msg.go b/msg.go index 85ba7186c..6de5b2432 100644 --- a/msg.go +++ b/msg.go @@ -694,15 +694,18 @@ func (dns *Msg) Pack() (msg []byte, err error) { // PackBuffer packs a Msg, using the given buffer buf. If buf is too small // a new buffer is allocated. func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) { - // We use a similar function in tsig.go's stripTsig. - var ( - dh Header - compression map[string]int - ) - + var compression map[string]int if dns.Compress { compression = make(map[string]int) // Compression pointer mappings } + return dns.packBufferWithCompressionMap(msg, compression) +} + +func (dns *Msg) packBufferWithCompressionMap(buf []byte, compression map[string]int) (msg []byte, err error) { + // We use a similar function in tsig.go's stripTsig. + var ( + dh Header + ) if dns.Rcode < 0 || dns.Rcode > 0xFFF { return nil, ErrRcode @@ -922,23 +925,27 @@ func (dns *Msg) String() string { // than packing it, measuring the size and discarding the buffer. func (dns *Msg) Len() int { return compressedLen(dns, dns.Compress) } +func compressedLenWithCompressionMap(dns *Msg, compression map[string]int) int { + l := 12 // Message header is always 12 bytes + for _, r := range dns.Question { + compressionLenHelper(compression, r.Name, l) + l += r.len() + } + l += compressionLenSlice(l, compression, dns.Answer, 0) + l += compressionLenSlice(l, compression, dns.Ns, 0) + l += compressionLenSlice(l, compression, dns.Extra, 0) + return l +} + // compressedLen returns the message length when in compressed wire format // when compress is true, otherwise the uncompressed length is returned. func compressedLen(dns *Msg, compress bool) int { // We always return one more than needed. - l := 12 // Message header is always 12 bytes if compress { compression := map[string]int{} - for _, r := range dns.Question { - l += r.len() - compressionLenHelper(compression, r.Name, l) - } - l += compressionLenSlice(l, compression, dns.Answer) - l += compressionLenSlice(l, compression, dns.Ns) - l += compressionLenSlice(l, compression, dns.Extra) - - return l + return compressedLenWithCompressionMap(dns, compression) } + l := 12 // Message header is always 12 bytes for _, r := range dns.Question { l += r.len() @@ -962,32 +969,34 @@ func compressedLen(dns *Msg, compress bool) int { return l } -func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { +func compressionLenSlice(lenp int, c map[string]int, rs []RR, padding int) int { initLen := lenp for _, r := range rs { if r == nil { continue } // TmpLen is to track len of record at 14bits boudaries, 6 bytes is the raw overhead - tmpLen := lenp + 6 + // 10 bytes for Signalization + 2 bytes for pointer + tmpLen := lenp + padding + x := r.len() // track this length, and the global length in len, while taking compression into account for both. - k, ok, sz := compressionLenSearch(c, r.Header().Name) + k, ok, _ := compressionLenSearch(c, r.Header().Name) if ok { // Size of x is reduced by k, but we add 1 since k includes the '.' and label descriptor take 2 bytes // so, basically x:= x - k - 1 + 2 x += 1 - k } - tmpLen += sz tmpLen += compressionLenHelper(c, r.Header().Name, tmpLen) - k, ok, added := compressionLenSearchType(c, r) + k, ok, _ = compressionLenSearchType(c, r) if ok { x += 1 - k } - tmpLen += added - tmpLen += compressionLenHelperType(c, r, tmpLen) lenp += x + tmpLen = lenp + tmpLen += compressionLenHelperType(c, r, tmpLen) + } return lenp - initLen } @@ -1001,24 +1010,29 @@ func compressionLenHelper(c map[string]int, s string, currentLen int) int { if _, ok := c[s]; ok { return 0 } - addedSizeBytes := 0 + initLen := currentLen pref := "" + prev := s lbs := Split(s) - for j := len(lbs) - 1; j >= 0; j-- { + for j := 0; j < len(lbs); j++ { pref = s[lbs[j]:] + currentLen += len(prev) - len(pref) + prev = pref if _, ok := c[pref]; !ok { - lenAdded := len(pref) - numLabelsBefore := len(lbs) - j - 1 - // 2 bytes per label before this label to take into account - offsetOfLabel := currentLen + len(s) - lenAdded + numLabelsBefore*2 // If first byte label is within the first 14bits, it might be re-used later - if offsetOfLabel < maxCompressionOffset { - c[pref] = lenAdded - addedSizeBytes += 2 + lenAdded + if currentLen < maxCompressionOffset { + c[pref] = currentLen + } + } else { + added := currentLen - initLen + if j > 0 { + // We added a new PTR + added += 2 } + return added } } - return addedSizeBytes + return currentLen - initLen } // Look for each part in the compression map and returns its length, diff --git a/zcompress.go b/zcompress.go index f01901677..a2c09dd48 100644 --- a/zcompress.go +++ b/zcompress.go @@ -6,67 +6,102 @@ func compressionLenHelperType(c map[string]int, r RR, initLen int) int { currentLen := initLen switch x := r.(type) { case *AFSDB: + currentLen -= len(x.Hostname) + 1 currentLen += compressionLenHelper(c, x.Hostname, currentLen) case *CNAME: + currentLen -= len(x.Target) + 1 currentLen += compressionLenHelper(c, x.Target, currentLen) case *DNAME: + currentLen -= len(x.Target) + 1 currentLen += compressionLenHelper(c, x.Target, currentLen) case *HIP: + for i := range x.RendezvousServers { + currentLen -= len(x.RendezvousServers[i]) + 1 + } for i := range x.RendezvousServers { currentLen += compressionLenHelper(c, x.RendezvousServers[i], currentLen) } case *KX: + currentLen -= len(x.Exchanger) + 1 currentLen += compressionLenHelper(c, x.Exchanger, currentLen) case *LP: + currentLen -= len(x.Fqdn) + 1 currentLen += compressionLenHelper(c, x.Fqdn, currentLen) case *MB: + currentLen -= len(x.Mb) + 1 currentLen += compressionLenHelper(c, x.Mb, currentLen) case *MD: + currentLen -= len(x.Md) + 1 currentLen += compressionLenHelper(c, x.Md, currentLen) case *MF: + currentLen -= len(x.Mf) + 1 currentLen += compressionLenHelper(c, x.Mf, currentLen) case *MG: + currentLen -= len(x.Mg) + 1 currentLen += compressionLenHelper(c, x.Mg, currentLen) case *MINFO: + currentLen -= len(x.Rmail) + 1 currentLen += compressionLenHelper(c, x.Rmail, currentLen) + currentLen -= len(x.Email) + 1 currentLen += compressionLenHelper(c, x.Email, currentLen) case *MR: + currentLen -= len(x.Mr) + 1 currentLen += compressionLenHelper(c, x.Mr, currentLen) case *MX: + currentLen -= len(x.Mx) + 1 currentLen += compressionLenHelper(c, x.Mx, currentLen) case *NAPTR: + currentLen -= len(x.Replacement) + 1 currentLen += compressionLenHelper(c, x.Replacement, currentLen) case *NS: + currentLen -= len(x.Ns) + 1 currentLen += compressionLenHelper(c, x.Ns, currentLen) case *NSAPPTR: + currentLen -= len(x.Ptr) + 1 currentLen += compressionLenHelper(c, x.Ptr, currentLen) case *NSEC: + currentLen -= len(x.NextDomain) + 1 currentLen += compressionLenHelper(c, x.NextDomain, currentLen) case *PTR: + currentLen -= len(x.Ptr) + 1 currentLen += compressionLenHelper(c, x.Ptr, currentLen) case *PX: + currentLen -= len(x.Map822) + 1 currentLen += compressionLenHelper(c, x.Map822, currentLen) + currentLen -= len(x.Mapx400) + 1 currentLen += compressionLenHelper(c, x.Mapx400, currentLen) case *RP: + currentLen -= len(x.Mbox) + 1 currentLen += compressionLenHelper(c, x.Mbox, currentLen) + currentLen -= len(x.Txt) + 1 currentLen += compressionLenHelper(c, x.Txt, currentLen) case *RRSIG: + currentLen -= len(x.SignerName) + 1 currentLen += compressionLenHelper(c, x.SignerName, currentLen) case *RT: + currentLen -= len(x.Host) + 1 currentLen += compressionLenHelper(c, x.Host, currentLen) case *SIG: + currentLen -= len(x.SignerName) + 1 currentLen += compressionLenHelper(c, x.SignerName, currentLen) case *SOA: + currentLen -= len(x.Ns) + 1 currentLen += compressionLenHelper(c, x.Ns, currentLen) + currentLen -= len(x.Mbox) + 1 currentLen += compressionLenHelper(c, x.Mbox, currentLen) case *SRV: + currentLen -= len(x.Target) + 1 currentLen += compressionLenHelper(c, x.Target, currentLen) case *TALINK: + currentLen -= len(x.PreviousName) + 1 currentLen += compressionLenHelper(c, x.PreviousName, currentLen) + currentLen -= len(x.NextName) + 1 currentLen += compressionLenHelper(c, x.NextName, currentLen) case *TKEY: + currentLen -= len(x.Algorithm) + 1 currentLen += compressionLenHelper(c, x.Algorithm, currentLen) case *TSIG: + currentLen -= len(x.Algorithm) + 1 currentLen += compressionLenHelper(c, x.Algorithm, currentLen) } return currentLen - initLen From 51c5108a43d4f89c1bb838d899741c3ad033ff11 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 30 Apr 2018 12:52:54 +0200 Subject: [PATCH 09/12] Fixed Sign that was relying on non-copy for Unit tests --- sig0.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sig0.go b/sig0.go index f31e9e684..b4feae596 100644 --- a/sig0.go +++ b/sig0.go @@ -30,7 +30,7 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { rr.Labels = 0 buf := make([]byte, m.Len()+rr.len()) - mbuf, err := m.PackBuffer(buf) + mbuf, err := m.packBufferWithCompressionMap(buf, nil) if err != nil { return nil, err } From 0d606ada1643acf985c7b32ac90c723b888bbc8a Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 30 Apr 2018 13:06:52 +0200 Subject: [PATCH 10/12] Removed unused padding --- msg.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/msg.go b/msg.go index 6de5b2432..9e0291be6 100644 --- a/msg.go +++ b/msg.go @@ -931,9 +931,9 @@ func compressedLenWithCompressionMap(dns *Msg, compression map[string]int) int { compressionLenHelper(compression, r.Name, l) l += r.len() } - l += compressionLenSlice(l, compression, dns.Answer, 0) - l += compressionLenSlice(l, compression, dns.Ns, 0) - l += compressionLenSlice(l, compression, dns.Extra, 0) + l += compressionLenSlice(l, compression, dns.Answer) + l += compressionLenSlice(l, compression, dns.Ns) + l += compressionLenSlice(l, compression, dns.Extra) return l } @@ -969,15 +969,14 @@ func compressedLen(dns *Msg, compress bool) int { return l } -func compressionLenSlice(lenp int, c map[string]int, rs []RR, padding int) int { +func compressionLenSlice(lenp int, c map[string]int, rs []RR) int { initLen := lenp for _, r := range rs { if r == nil { continue } - // TmpLen is to track len of record at 14bits boudaries, 6 bytes is the raw overhead - // 10 bytes for Signalization + 2 bytes for pointer - tmpLen := lenp + padding + // TmpLen is to track len of record at 14bits boudaries + tmpLen := lenp x := r.len() // track this length, and the global length in len, while taking compression into account for both. From e471e7f6f9b8a3f183ec114ba21cfab059444dd0 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Fri, 11 May 2018 11:11:35 +0200 Subject: [PATCH 11/12] Fixed typo in PackBuffer() function --- msg.go | 2 +- sig0.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/msg.go b/msg.go index 9e0291be6..4993087de 100644 --- a/msg.go +++ b/msg.go @@ -698,7 +698,7 @@ func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) { if dns.Compress { compression = make(map[string]int) // Compression pointer mappings } - return dns.packBufferWithCompressionMap(msg, compression) + return dns.packBufferWithCompressionMap(buf, compression) } func (dns *Msg) packBufferWithCompressionMap(buf []byte, compression map[string]int) (msg []byte, err error) { diff --git a/sig0.go b/sig0.go index b4feae596..f31e9e684 100644 --- a/sig0.go +++ b/sig0.go @@ -30,7 +30,7 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { rr.Labels = 0 buf := make([]byte, m.Len()+rr.len()) - mbuf, err := m.packBufferWithCompressionMap(buf, nil) + mbuf, err := m.PackBuffer(buf) if err != nil { return nil, err } From 33f7d9fba067d311b0df2c3083252b26d940f461 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Fri, 11 May 2018 11:19:49 +0200 Subject: [PATCH 12/12] Added comment about packBufferWithCompressionMap() for testing purposes --- msg.go | 1 + 1 file changed, 1 insertion(+) diff --git a/msg.go b/msg.go index 4993087de..27a54b61d 100644 --- a/msg.go +++ b/msg.go @@ -701,6 +701,7 @@ func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) { return dns.packBufferWithCompressionMap(buf, compression) } +// packBufferWithCompressionMap is ONLY for testing purposes func (dns *Msg) packBufferWithCompressionMap(buf []byte, compression map[string]int) (msg []byte, err error) { // We use a similar function in tsig.go's stripTsig. var (