diff --git a/msg_generate.go b/msg_generate.go index 86ed04fc..7e01e14d 100644 --- a/msg_generate.go +++ b/msg_generate.go @@ -153,7 +153,8 @@ if rr.%s != "-" { fallthrough case st.Tag(i) == `dns:"hex"`: o("off, err = packStringHex(rr.%s, msg, off)\n") - + case st.Tag(i) == `dns:"any"`: + o("off, err = packStringAny(rr.%s, msg, off)\n") case st.Tag(i) == `dns:"octet"`: o("off, err = packStringOctet(rr.%s, msg, off)\n") case st.Tag(i) == "": @@ -261,6 +262,8 @@ return rr, off, err o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") case `dns:"hex"`: o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") + case `dns:"any"`: + o("rr.%s, off, err = unpackStringAny(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") case `dns:"octet"`: o("rr.%s, off, err = unpackStringOctet(msg, off)\n") case "": diff --git a/msg_helpers.go b/msg_helpers.go index 36345e16..202f4c68 100644 --- a/msg_helpers.go +++ b/msg_helpers.go @@ -363,6 +363,22 @@ func packStringHex(s string, msg []byte, off int) (int, error) { return off, nil } +func unpackStringAny(msg []byte, off, end int) (string, int, error) { + if end > len(msg) { + return "", len(msg), &Error{err: "overflow unpacking anything"} + } + return string(msg[off:end]), end, nil +} + +func packStringAny(s string, msg []byte, off int) (int, error) { + if off+len(s) > len(msg) { + return len(msg), &Error{err: "overflow packing anything"} + } + copy(msg[off:off+len(s)], s) + off += len(s) + return off, nil +} + func unpackStringTxt(msg []byte, off int) ([]string, int, error) { txt, off, err := unpackTxt(msg, off) if err != nil { diff --git a/parse_test.go b/parse_test.go index 9eee7eb0..b5af01f4 100644 --- a/parse_test.go +++ b/parse_test.go @@ -341,7 +341,7 @@ func TestParseDirectiveMisc(t *testing.T) { func TestNSEC(t *testing.T) { nsectests := map[string]string{ - "nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F", + "nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F", "p2209hipbpnm681knjnu0m1febshlv4e.nl. IN NSEC3 1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM": "p2209hipbpnm681knjnu0m1febshlv4e.nl.\t3600\tIN\tNSEC3\t1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM", "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC", "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534", @@ -398,16 +398,16 @@ func TestQuotes(t *testing.T) { `t.example.com. IN TXT "a bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a bc\"", `t.example.com. IN TXT "a bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a\\010 bc\"", - `t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"", - `t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"", - `t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"", - `t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", - `t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"", - `t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"", - `t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", - `t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"", - `t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"", - `t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", + `t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"", + `t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"", + `t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"", + `t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", + `t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"", + `t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"", + `t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", + `t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"", + `t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\" \"aaa\"", + `t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", "cid.urn.arpa. NAPTR 100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.", "cid.urn.arpa. NAPTR 100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.", "cid.urn.arpa. NAPTR 100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.", @@ -1567,3 +1567,17 @@ func TestBad(t *testing.T) { } } } + +func TestNULLRecord(t *testing.T) { + // packet captured from iodine + packet := `8116840000010001000000000569627a6c700474657374046d69656b026e6c00000a0001c00c000a0001000000000005497f000001` + data, _ := hex.DecodeString(packet) + msg := new(Msg) + err := msg.Unpack(data) + if err != nil { + t.Fatalf("Failed to unpack NULL record") + } + if _, ok := msg.Answer[0].(*NULL); !ok { + t.Fatalf("Expected packet to contain NULL record") + } +} diff --git a/types.go b/types.go index 74d5e22a..2c02ea09 100644 --- a/types.go +++ b/types.go @@ -241,6 +241,17 @@ type ANY struct { func (rr *ANY) String() string { return rr.Hdr.String() } +// NULL RR. See RFC 1035. +type NULL struct { + Hdr RR_Header + Anything string `dns:"any"` +} + +func (rr *NULL) String() string { + // There is no presentation format; prefix string with a comment. + return ";" + rr.Hdr.String() + rr.Anything +} + // CNAME RR. See RFC 1034. type CNAME struct { Hdr RR_Header diff --git a/types_generate.go b/types_generate.go index 8c897ec1..02576b1d 100644 --- a/types_generate.go +++ b/types_generate.go @@ -193,6 +193,8 @@ func main() { fallthrough case st.Tag(i) == `dns:"hex"`: o("l += len(rr.%s)/2 + 1\n") + case st.Tag(i) == `dns:"any"`: + o("l += len(rr.%s)\n") case st.Tag(i) == `dns:"a"`: o("l += net.IPv4len // %s\n") case st.Tag(i) == `dns:"aaaa"`: diff --git a/zduplicate.go b/zduplicate.go index ba9863b2..87b9db2e 100644 --- a/zduplicate.go +++ b/zduplicate.go @@ -85,6 +85,8 @@ func isDuplicateRdata(r1, r2 RR) bool { return isDuplicateNSEC3(r1.(*NSEC3), r2.(*NSEC3)) case TypeNSEC3PARAM: return isDuplicateNSEC3PARAM(r1.(*NSEC3PARAM), r2.(*NSEC3PARAM)) + case TypeNULL: + return isDuplicateNULL(r1.(*NULL), r2.(*NULL)) case TypeOPENPGPKEY: return isDuplicateOPENPGPKEY(r1.(*OPENPGPKEY), r2.(*OPENPGPKEY)) case TypePTR: @@ -616,6 +618,13 @@ func isDuplicateNSEC3PARAM(r1, r2 *NSEC3PARAM) bool { return true } +func isDuplicateNULL(r1, r2 *NULL) bool { + if r1.Anything != r2.Anything { + return false + } + return true +} + func isDuplicateOPENPGPKEY(r1, r2 *OPENPGPKEY) bool { if r1.PublicKey != r2.PublicKey { return false diff --git a/zmsg.go b/zmsg.go index 3e93a782..9cd4f59e 100644 --- a/zmsg.go +++ b/zmsg.go @@ -802,6 +802,18 @@ func (rr *NSEC3PARAM) pack(msg []byte, off int, compression compressionMap, comp return headerEnd, off, nil } +func (rr *NULL) pack(msg []byte, off int, compression compressionMap, compress bool) (int, int, error) { + headerEnd, off, err := rr.Hdr.pack(msg, off, compression, compress) + if err != nil { + return headerEnd, off, err + } + off, err = packStringAny(rr.Anything, msg, off) + if err != nil { + return headerEnd, off, err + } + return headerEnd, off, nil +} + func (rr *OPENPGPKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (int, int, error) { headerEnd, off, err := rr.Hdr.pack(msg, off, compression, compress) if err != nil { @@ -2549,6 +2561,23 @@ func unpackNSEC3PARAM(h RR_Header, msg []byte, off int) (RR, int, error) { return rr, off, err } +func unpackNULL(h RR_Header, msg []byte, off int) (RR, int, error) { + rr := new(NULL) + rr.Hdr = h + if noRdata(h) { + return rr, off, nil + } + var err error + rdStart := off + _ = rdStart + + rr.Anything, off, err = unpackStringAny(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return rr, off, err + } + return rr, off, err +} + func unpackOPENPGPKEY(h RR_Header, msg []byte, off int) (RR, int, error) { rr := new(OPENPGPKEY) rr.Hdr = h @@ -3448,6 +3477,7 @@ var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){ TypeNSEC: unpackNSEC, TypeNSEC3: unpackNSEC3, TypeNSEC3PARAM: unpackNSEC3PARAM, + TypeNULL: unpackNULL, TypeOPENPGPKEY: unpackOPENPGPKEY, TypeOPT: unpackOPT, TypePTR: unpackPTR, diff --git a/ztypes.go b/ztypes.go index 71662b7b..f8885f11 100644 --- a/ztypes.go +++ b/ztypes.go @@ -54,6 +54,7 @@ var TypeToRR = map[uint16]func() RR{ TypeNSEC: func() RR { return new(NSEC) }, TypeNSEC3: func() RR { return new(NSEC3) }, TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) }, + TypeNULL: func() RR { return new(NULL) }, TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) }, TypeOPT: func() RR { return new(OPT) }, TypePTR: func() RR { return new(PTR) }, @@ -209,6 +210,7 @@ func (rr *NSAPPTR) Header() *RR_Header { return &rr.Hdr } func (rr *NSEC) Header() *RR_Header { return &rr.Hdr } func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr } func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr } +func (rr *NULL) Header() *RR_Header { return &rr.Hdr } func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr } func (rr *OPT) Header() *RR_Header { return &rr.Hdr } func (rr *PTR) Header() *RR_Header { return &rr.Hdr } @@ -473,6 +475,11 @@ func (rr *NSEC3PARAM) len(off int, compression map[string]struct{}) int { l += len(rr.Salt) / 2 return l } +func (rr *NULL) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Anything) + return l +} func (rr *OPENPGPKEY) len(off int, compression map[string]struct{}) int { l := rr.Hdr.len(off, compression) l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) @@ -783,6 +790,9 @@ func (rr *NSEC3) copy() RR { func (rr *NSEC3PARAM) copy() RR { return &NSEC3PARAM{rr.Hdr, rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt} } +func (rr *NULL) copy() RR { + return &NULL{rr.Hdr, rr.Anything} +} func (rr *OPENPGPKEY) copy() RR { return &OPENPGPKEY{rr.Hdr, rr.PublicKey} }