1package dns 2 3import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 "testing" 9) 10 11const maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x" 12 13var ( 14 longDomain = maxPrintableLabel[:53] + strings.TrimSuffix( 15 strings.Join([]string{".", ".", ".", ".", "."}, maxPrintableLabel[:49]), ".") 16 17 reChar = regexp.MustCompile(`.`) 18 i = -1 19 maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string { 20 if i++; i >= 32 { 21 i = 0 22 } 23 return fmt.Sprintf("\\%03d", i) 24 }) 25 26 // These are the longest possible domain names in presentation format. 27 longestDomain = maxPrintableLabel[:61] + strings.Join([]string{".", ".", ".", "."}, maxPrintableLabel) 28 longestUnprintableDomain = maxUnprintableLabel[:61*4] + strings.Join([]string{".", ".", ".", "."}, maxUnprintableLabel) 29) 30 31func TestPackNoSideEffect(t *testing.T) { 32 m := new(Msg) 33 m.SetQuestion(Fqdn("example.com."), TypeNS) 34 35 a := new(Msg) 36 o := &OPT{ 37 Hdr: RR_Header{ 38 Name: ".", 39 Rrtype: TypeOPT, 40 }, 41 } 42 o.SetUDPSize(DefaultMsgSize) 43 44 a.Extra = append(a.Extra, o) 45 a.SetRcode(m, RcodeBadVers) 46 47 a.Pack() 48 if a.Rcode != RcodeBadVers { 49 t.Errorf("after pack: Rcode is expected to be BADVERS") 50 } 51} 52 53func TestPackExtendedBadCookie(t *testing.T) { 54 m := new(Msg) 55 m.SetQuestion(Fqdn("example.com."), TypeNS) 56 57 a := new(Msg) 58 a.SetReply(m) 59 o := &OPT{ 60 Hdr: RR_Header{ 61 Name: ".", 62 Rrtype: TypeOPT, 63 }, 64 } 65 o.SetUDPSize(DefaultMsgSize) 66 a.Extra = append(a.Extra, o) 67 68 a.SetRcode(m, RcodeBadCookie) 69 70 edns0 := a.IsEdns0() 71 if edns0 == nil { 72 t.Fatal("Expected OPT RR") 73 } 74 // SetExtendedRcode is only called as part of `Pack()`, hence at this stage, 75 // the OPT RR is not set yet. 76 if edns0.ExtendedRcode() == RcodeBadCookie&0xFFFFFFF0 { 77 t.Errorf("ExtendedRcode is expected to not be BADCOOKIE before Pack") 78 } 79 80 a.Pack() 81 82 edns0 = a.IsEdns0() 83 if edns0 == nil { 84 t.Fatal("Expected OPT RR") 85 } 86 87 if edns0.ExtendedRcode() != RcodeBadCookie&0xFFFFFFF0 { 88 t.Errorf("ExtendedRcode is expected to be BADCOOKIE after Pack") 89 } 90} 91 92func TestUnPackExtendedRcode(t *testing.T) { 93 m := new(Msg) 94 m.SetQuestion(Fqdn("example.com."), TypeNS) 95 96 a := new(Msg) 97 a.SetReply(m) 98 o := &OPT{ 99 Hdr: RR_Header{ 100 Name: ".", 101 Rrtype: TypeOPT, 102 }, 103 } 104 o.SetUDPSize(DefaultMsgSize) 105 a.Extra = append(a.Extra, o) 106 107 a.SetRcode(m, RcodeBadCookie) 108 109 packed, err := a.Pack() 110 if err != nil { 111 t.Fatalf("Could not unpack %v", a) 112 } 113 114 unpacked := new(Msg) 115 if err := unpacked.Unpack(packed); err != nil { 116 t.Fatalf("Failed to unpack message") 117 } 118 119 if unpacked.Rcode != RcodeBadCookie { 120 t.Fatalf("Rcode should be matching RcodeBadCookie (%d), got (%d)", RcodeBadCookie, unpacked.Rcode) 121 } 122} 123 124func TestUnpackDomainName(t *testing.T) { 125 var cases = []struct { 126 label string 127 input string 128 expectedOutput string 129 expectedError string 130 }{ 131 {"empty domain", 132 "\x00", 133 ".", 134 ""}, 135 {"long label", 136 "?" + maxPrintableLabel + "\x00", 137 maxPrintableLabel + ".", 138 ""}, 139 {"unprintable label", 140 "?" + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel, 141 func(escape string) string { 142 n, _ := strconv.ParseInt(escape[1:], 10, 8) 143 return string(rune(n)) 144 }) + "\x00", 145 maxUnprintableLabel + ".", 146 ""}, 147 {"long domain", 148 "5" + strings.Replace(longDomain, ".", "1", -1) + "\x00", 149 longDomain + ".", 150 ""}, 151 {"compression pointer", 152 // an unrealistic but functional test referencing an offset _inside_ a label 153 "\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05", 154 "foo.\\003com\\000.example.com.", 155 ""}, 156 {"too long domain", 157 "6" + "x" + strings.Replace(longDomain, ".", "1", -1) + "\x00", 158 "", 159 ErrLongDomain.Error()}, 160 {"too long by pointer", 161 // a matryoshka doll name to get over 255 octets after expansion via internal pointers 162 string([]byte{ 163 // 11 length values, first to last 164 40, 37, 34, 31, 28, 25, 22, 19, 16, 13, 0, 165 // 12 filler values 166 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 167 // 10 pointers, last to first 168 192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1, 169 }), 170 "", 171 ErrLongDomain.Error()}, 172 {"long by pointer", 173 // a matryoshka doll name _not_ exceeding 255 octets after expansion 174 string([]byte{ 175 // 11 length values, first to last 176 37, 34, 31, 28, 25, 22, 19, 16, 13, 10, 0, 177 // 9 filler values 178 120, 120, 120, 120, 120, 120, 120, 120, 120, 179 // 10 pointers, last to first 180 192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1, 181 }), 182 "" + 183 (`\"\031\028\025\022\019\016\013\010\000xxxxxxxxx` + 184 `\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003\192\002.`) + 185 (`\031\028\025\022\019\016\013\010\000xxxxxxxxx` + 186 `\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003.`) + 187 (`\028\025\022\019\016\013\010\000xxxxxxxxx` + 188 `\192\010\192\009\192\008\192\007\192\006\192\005\192\004.`) + 189 (`\025\022\019\016\013\010\000xxxxxxxxx` + 190 `\192\010\192\009\192\008\192\007\192\006\192\005.`) + 191 `\022\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007\192\006.` + 192 `\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007.` + 193 `\016\013\010\000xxxxxxxxx\192\010\192\009\192\008.` + 194 `\013\010\000xxxxxxxxx\192\010\192\009.` + 195 `\010\000xxxxxxxxx\192\010.` + 196 `\000xxxxxxxxx.`, 197 ""}, 198 {"truncated name", "\x07example\x03", "", "dns: buffer size too small"}, 199 {"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"}, 200 {"compression pointer cycle (too many)", "\xC0\x00", "", "dns: too many compression pointers"}, 201 {"compression pointer cycle (too long)", 202 "\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04", 203 "", 204 ErrLongDomain.Error()}, 205 {"forward compression pointer", "\x02\xC0\xFF\xC0\x01", "", ErrBuf.Error()}, 206 {"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"}, 207 {"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"}, 208 } 209 for _, test := range cases { 210 output, idx, err := UnpackDomainName([]byte(test.input), 0) 211 if test.expectedOutput != "" && output != test.expectedOutput { 212 t.Errorf("%s: expected %s, got %s", test.label, test.expectedOutput, output) 213 } 214 if test.expectedError == "" && err != nil { 215 t.Errorf("%s: expected no error, got %d %v", test.label, idx, err) 216 } else if test.expectedError != "" && (err == nil || err.Error() != test.expectedError) { 217 t.Errorf("%s: expected error %s, got %d %v", test.label, test.expectedError, idx, err) 218 } 219 } 220} 221 222func TestPackDomainNameCompressionMap(t *testing.T) { 223 expected := map[string]struct{}{ 224 `www\.this.is.\131an.example.org.`: {}, 225 `is.\131an.example.org.`: {}, 226 `\131an.example.org.`: {}, 227 `example.org.`: {}, 228 `org.`: {}, 229 } 230 231 msg := make([]byte, 256) 232 for _, compress := range []bool{true, false} { 233 compression := make(map[string]int) 234 235 _, err := PackDomainName(`www\.this.is.\131an.example.org.`, msg, 0, compression, compress) 236 if err != nil { 237 t.Fatalf("PackDomainName failed: %v", err) 238 } 239 240 if !compressionMapsEqual(expected, compression) { 241 t.Errorf("expected compression maps to be equal\n%s", compressionMapsDifference(expected, compression)) 242 } 243 } 244} 245 246func TestPackDomainNameNSECTypeBitmap(t *testing.T) { 247 ownername := "some-very-long-ownername.com." 248 msg := &Msg{ 249 Compress: true, 250 Answer: []RR{ 251 &NS{ 252 Hdr: RR_Header{ 253 Name: ownername, 254 Rrtype: TypeNS, 255 Class: ClassINET, 256 }, 257 Ns: "ns1.server.com.", 258 }, 259 &NSEC{ 260 Hdr: RR_Header{ 261 Name: ownername, 262 Rrtype: TypeNSEC, 263 Class: ClassINET, 264 }, 265 NextDomain: "a.com.", 266 TypeBitMap: []uint16{TypeNS, TypeNSEC}, 267 }, 268 }, 269 } 270 271 // Pack msg and then unpack into msg2 272 buf, err := msg.Pack() 273 if err != nil { 274 t.Fatalf("msg.Pack failed: %v", err) 275 } 276 277 var msg2 Msg 278 if err := msg2.Unpack(buf); err != nil { 279 t.Fatalf("msg2.Unpack failed: %v", err) 280 } 281 282 if !IsDuplicate(msg.Answer[1], msg2.Answer[1]) { 283 t.Error("message differs after packing and unpacking") 284 285 // Print NSEC RR for both cases 286 t.Logf("expected: %v", msg.Answer[1]) 287 t.Logf("got: %v", msg2.Answer[1]) 288 } 289} 290 291func TestPackUnpackManyCompressionPointers(t *testing.T) { 292 m := new(Msg) 293 m.Compress = true 294 m.SetQuestion("example.org.", TypeNS) 295 296 for domain := "a."; len(domain) < maxDomainNameWireOctets; domain += "a." { 297 m.Answer = append(m.Answer, &NS{Hdr: RR_Header{Name: domain, Rrtype: TypeNS, Class: ClassINET}, Ns: "example.org."}) 298 299 b, err := m.Pack() 300 if err != nil { 301 t.Fatalf("Pack failed for %q and %d records with: %v", domain, len(m.Answer), err) 302 } 303 304 var m2 Msg 305 if err := m2.Unpack(b); err != nil { 306 t.Fatalf("Unpack failed for %q and %d records with: %v", domain, len(m.Answer), err) 307 } 308 } 309} 310 311func TestLenDynamicA(t *testing.T) { 312 for _, rr := range []RR{ 313 testRR("example.org. A"), 314 testRR("example.org. AAAA"), 315 testRR("example.org. L32"), 316 } { 317 msg := make([]byte, Len(rr)) 318 off, err := PackRR(rr, msg, 0, nil, false) 319 if err != nil { 320 t.Fatalf("PackRR failed for %T: %v", rr, err) 321 } 322 if off != len(msg) { 323 t.Errorf("Len(rr) wrong for %T: Len(rr) = %d, PackRR(rr) = %d", rr, len(msg), off) 324 } 325 } 326} 327