From c9b62b42159623401d82cb7867895d3aa917a670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20V=C4=8Del=C3=A1k?= Date: Fri, 3 Jan 2020 13:41:45 +0100 Subject: [PATCH] APL record support (#1058) * APL record: add structure and code point * APL record: add wire format support * APL record: add presentation format support * APL record: add isDuplicate implementation * APL record: add copy implementation * APL record: add len implementation * APL record: run go generate * APL record: fix condition checking for equality * APL record: use switches to map family to address length * APL record: check bounds of individual fields rather than whole header * APL record: stylistic changes * APL record: remove APLPrefix methods from public interface * APL record: update README * APL record: additional cleanup for code review * APL record: change return type from pointer to struct * APL record: refactor of pack and unpack to eliminate extra variables --- README.md | 1 + duplicate_generate.go | 10 ++ msg_generate.go | 4 + msg_helpers.go | 120 ++++++++++++++++ msg_helpers_test.go | 312 +++++++++++++++++++++++++++++++++++++++++- parse_test.go | 134 ++++++++++++++++++ scan_rr.go | 66 +++++++++ types.go | 95 +++++++++++++ types_generate.go | 8 ++ zduplicate.go | 17 +++ zmsg.go | 19 +++ ztypes.go | 17 +++ 12 files changed, 802 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a6e1cc3..126fe62c 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository. * 2915 - NAPTR record * 2929 - DNS IANA Considerations * 3110 - RSASHA1 DNS keys +* 3123 - APL record * 3225 - DO bit (DNSSEC OK) * 340{1,2,3} - NAPTR record * 3445 - Limiting the scope of (DNS)KEY diff --git a/duplicate_generate.go b/duplicate_generate.go index 75f086c2..834779e0 100644 --- a/duplicate_generate.go +++ b/duplicate_generate.go @@ -111,6 +111,16 @@ func main() { continue } + if st.Tag(i) == `dns:"apl"` { + o3(`for i := 0; i < len(r1.%s); i++ { + if !r1.%s[i].equals(&r2.%s[i]) { + return false + } + }`) + + continue + } + o3(`for i := 0; i < len(r1.%s); i++ { if r1.%s[i] != r2.%s[i] { return false diff --git a/msg_generate.go b/msg_generate.go index 92b2955e..334aaf66 100644 --- a/msg_generate.go +++ b/msg_generate.go @@ -112,6 +112,8 @@ return off, err o("off, err = packDataNsec(rr.%s, msg, off)\n") case `dns:"domain-name"`: o("off, err = packDataDomainNames(rr.%s, msg, off, compression, false)\n") + case `dns:"apl"`: + o("off, err = packDataApl(rr.%s, msg, off)\n") default: log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) } @@ -236,6 +238,8 @@ return off, err o("rr.%s, off, err = unpackDataNsec(msg, off)\n") case `dns:"domain-name"`: o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") + case `dns:"apl"`: + o("rr.%s, off, err = unpackDataApl(msg, off)\n") default: log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) } diff --git a/msg_helpers.go b/msg_helpers.go index 43f54b30..98fadc31 100644 --- a/msg_helpers.go +++ b/msg_helpers.go @@ -688,3 +688,123 @@ func packDataDomainNames(names []string, msg []byte, off int, compression compre } return off, nil } + +func packDataApl(data []APLPrefix, msg []byte, off int) (int, error) { + var err error + for i := range data { + off, err = packDataAplPrefix(&data[i], msg, off) + if err != nil { + return len(msg), err + } + } + return off, nil +} + +func packDataAplPrefix(p *APLPrefix, msg []byte, off int) (int, error) { + if len(p.Network.IP) != len(p.Network.Mask) { + return len(msg), &Error{err: "address and mask lengths don't match"} + } + + var err error + prefix, _ := p.Network.Mask.Size() + addr := p.Network.IP.Mask(p.Network.Mask)[:(prefix+7)/8] + + switch len(p.Network.IP) { + case net.IPv4len: + off, err = packUint16(1, msg, off) + case net.IPv6len: + off, err = packUint16(2, msg, off) + default: + err = &Error{err: "unrecognized address family"} + } + if err != nil { + return len(msg), err + } + + off, err = packUint8(uint8(prefix), msg, off) + if err != nil { + return len(msg), err + } + + var n uint8 + if p.Negation { + n = 0x80 + } + adflen := uint8(len(addr)) & 0x7f + off, err = packUint8(n|adflen, msg, off) + if err != nil { + return len(msg), err + } + + if off+len(addr) > len(msg) { + return len(msg), &Error{err: "overflow packing APL prefix"} + } + off += copy(msg[off:], addr) + + return off, nil +} + +func unpackDataApl(msg []byte, off int) ([]APLPrefix, int, error) { + var result []APLPrefix + for off < len(msg) { + prefix, end, err := unpackDataAplPrefix(msg, off) + if err != nil { + return nil, len(msg), err + } + off = end + result = append(result, prefix) + } + return result, off, nil +} + +func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) { + family, off, err := unpackUint16(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + prefix, off, err := unpackUint8(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + nlen, off, err := unpackUint8(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + + var ip []byte + switch family { + case 1: + ip = make([]byte, net.IPv4len) + case 2: + ip = make([]byte, net.IPv6len) + default: + return APLPrefix{}, len(msg), &Error{err: "unrecognized APL address family"} + } + if int(prefix) > 8*len(ip) { + return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"} + } + + afdlen := int(nlen & 0x7f) + if (int(prefix)+7)/8 != afdlen { + return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"} + } + if off+afdlen > len(msg) { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"} + } + off += copy(ip, msg[off:off+afdlen]) + if prefix%8 > 0 { + last := ip[afdlen-1] + zero := uint8(0xff) >> (prefix % 8) + if last&zero > 0 { + return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"} + } + } + + return APLPrefix{ + Negation: (nlen & 0x80) != 0, + Network: net.IPNet{ + IP: ip, + Mask: net.CIDRMask(int(prefix), 8*len(ip)), + }, + }, off, nil +} diff --git a/msg_helpers_test.go b/msg_helpers_test.go index 9b66d228..4ce3cd17 100644 --- a/msg_helpers_test.go +++ b/msg_helpers_test.go @@ -1,6 +1,10 @@ package dns -import "testing" +import ( + "bytes" + "net" + "testing" +) // TestPacketDataNsec tests generated using fuzz.go and with a message pack // containing the following bytes: 0000\x00\x00000000\x00\x002000000\x0060000\x00\x130000000000000000000" @@ -153,3 +157,309 @@ func BenchmarkUnpackString(b *testing.B) { } }) } + +func TestPackDataAplPrefix(t *testing.T) { + tests := []struct { + name string + negation bool + ip net.IP + mask net.IPMask + expect []byte + }{ + { + "1:192.0.2.0/24", + false, + net.ParseIP("192.0.2.0").To4(), + net.CIDRMask(24, 32), + []byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2}, + }, + { + "2:2001:db8:cafe::0/48", + false, + net.ParseIP("2001:db8:cafe::"), + net.CIDRMask(48, 128), + []byte{0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe}, + }, + { + "!2:2001:db8::/32", + true, + net.ParseIP("2001:db8::"), + net.CIDRMask(32, 128), + []byte{0x00, 0x02, 0x20, 0x84, 0x20, 0x01, 0x0d, 0xb8}, + }, + { + "normalize 1:198.51.103.255/22", + false, + net.ParseIP("198.51.103.255").To4(), + net.CIDRMask(22, 32), + []byte{0x00, 0x01, 0x16, 0x03, 198, 51, 100}, // 1:198.51.100.0/22 + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ap := &APLPrefix{ + Negation: tt.negation, + Network: net.IPNet{IP: tt.ip, Mask: tt.mask}, + } + out := make([]byte, 16) + off, err := packDataAplPrefix(ap, out, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if !bytes.Equal(tt.expect, out[:off]) { + t.Fatalf("expected output %02x, got %02x", tt.expect, out[:off]) + } + }) + } +} + +func TestPackDataAplPrefix_Failures(t *testing.T) { + tests := []struct { + name string + ip net.IP + mask net.IPMask + }{ + { + "family mismatch", + net.ParseIP("2001:db8::"), + net.CIDRMask(24, 32), + }, + { + "unrecognized family", + []byte{0x42}, + []byte{0xff}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ap := &APLPrefix{Network: net.IPNet{IP: tt.ip, Mask: tt.mask}} + msg := make([]byte, 16) + off, err := packDataAplPrefix(ap, msg, 0) + if err == nil { + t.Fatal("expected error, got none") + } + if off != len(msg) { + t.Fatalf("expected %d, got %d", len(msg), off) + } + }) + } +} + +func TestPackDataAplPrefix_BufferBounds(t *testing.T) { + ap := &APLPrefix{ + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("2001:db8::"), + Mask: net.CIDRMask(32, 128), + }, + } + wire := []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8} + + t.Run("small", func(t *testing.T) { + msg := make([]byte, len(wire)) + _, err := packDataAplPrefix(ap, msg, 1) // offset + if err == nil { + t.Fatal("expected error, got none") + } + }) + + t.Run("exact fit", func(t *testing.T) { + msg := make([]byte, len(wire)) + off, err := packDataAplPrefix(ap, msg, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if !bytes.Equal(wire, msg[:off]) { + t.Fatalf("expected %02x, got %02x", wire, msg[:off]) + } + }) +} + +func TestPackDataApl(t *testing.T) { + in := []APLPrefix{ + APLPrefix{ + Negation: true, + Network: net.IPNet{ + IP: net.ParseIP("198.51.0.0").To4(), + Mask: net.CIDRMask(16, 32), + }, + }, + APLPrefix{ + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("2001:db8:beef::"), + Mask: net.CIDRMask(48, 128), + }, + }, + } + expect := []byte{ + // 1:192.51.0.0/16 + 0x00, 0x01, 0x10, 0x82, 0xc6, 0x33, + // 2:2001:db8:beef::0/48 + 0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xbe, 0xef, + } + + msg := make([]byte, 32) + off, err := packDataApl(in, msg, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if !bytes.Equal(expect, msg[:off]) { + t.Fatalf("expected %02x, got %02x", expect, msg[:off]) + } +} + +func TestUnpackDataAplPrefix(t *testing.T) { + tests := []struct { + name string + wire []byte + negation bool + ip net.IP + mask net.IPMask + }{ + { + "1:192.0.2.0/24", + []byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2}, + false, + net.ParseIP("192.0.2.0").To4(), + net.CIDRMask(24, 32), + }, + { + "2:2001:db8::/32", + []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8}, + false, + net.ParseIP("2001:db8::"), + net.CIDRMask(32, 128), + }, + { + "!2:2001:db8:8000::/33", + []byte{0x00, 0x02, 0x21, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80}, + true, + net.ParseIP("2001:db8:8000::"), + net.CIDRMask(33, 128), + }, + { + "1:0.0.0.0/0", + []byte{0x00, 0x01, 0x00, 0x00}, + false, + net.ParseIP("0.0.0.0").To4(), + net.CIDRMask(0, 32), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, off, err := unpackDataAplPrefix(tt.wire, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if off != len(tt.wire) { + t.Fatalf("expected offset %d, got %d", len(tt.wire), off) + } + if got.Negation != tt.negation { + t.Errorf("expected negation %v, got %v", tt.negation, got.Negation) + } + if !bytes.Equal(got.Network.IP, tt.ip) { + t.Errorf("expected IP %02x, got %02x", tt.ip, got.Network.IP) + } + if !bytes.Equal(got.Network.Mask, tt.mask) { + t.Errorf("expected mask %02x, got %02x", tt.mask, got.Network.Mask) + } + }) + } +} + +func TestUnpackDataAplPrefix_Errors(t *testing.T) { + tests := []struct { + name string + wire []byte + }{ + { + "incomplete header", + []byte{0x00, 0x01, 0x18}, + }, + { + "unrecognized family", + []byte{0x00, 0x03, 0x00, 0x00}, + }, + { + "prefix length exceeded", + []byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0}, + }, + { + "address with extra byte", + []byte{0x00, 0x01, 0x10, 0x03, 192, 0, 2}, + }, + { + "incomplete buffer", + []byte{0x00, 0x01, 0x10, 0x02, 192}, + }, + { + "extra bits set", + []byte{0x00, 0x01, 22, 0x03, 192, 0, 2}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := unpackDataAplPrefix(tt.wire, 0) + if err == nil { + t.Fatal("expected error, got none") + } + }) + } +} + +func TestUnpackDataApl(t *testing.T) { + wire := []byte{ + // 2:2001:db8:cafe:4200:0/56 + 0x00, 0x02, 0x38, 0x07, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x42, + // 1:192.0.2.0/24 + 0x00, 0x01, 0x18, 0x03, 192, 0, 2, + // !1:192.0.2.128/25 + 0x00, 0x01, 0x19, 0x84, 192, 0, 2, 128, + } + expect := []APLPrefix{ + { + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("2001:db8:cafe:4200::"), + Mask: net.CIDRMask(56, 128), + }, + }, + { + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("192.0.2.0").To4(), + Mask: net.CIDRMask(24, 32), + }, + }, + { + Negation: true, + Network: net.IPNet{ + IP: net.ParseIP("192.0.2.128").To4(), + Mask: net.CIDRMask(25, 32), + }, + }, + } + + got, off, err := unpackDataApl(wire, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if off != len(wire) { + t.Fatalf("expected offset %d, got %d", len(wire), off) + } + if len(got) != len(expect) { + t.Fatalf("expected %d prefixes, got %d", len(expect), len(got)) + } + for i, exp := range expect { + if got[i].Negation != exp.Negation { + t.Errorf("[%d] expected negation %v, got %v", i, exp.Negation, got[i].Negation) + } + if !bytes.Equal(got[i].Network.IP, exp.Network.IP) { + t.Errorf("[%d] expected IP %02x, got %02x", i, exp.Network.IP, got[i].Network.IP) + } + if !bytes.Equal(got[i].Network.Mask, exp.Network.Mask) { + t.Errorf("[%d] expected mask %02x, got %02x", i, exp.Network.Mask, got[i].Network.Mask) + } + } +} diff --git a/parse_test.go b/parse_test.go index f139b2fe..46ab6b40 100644 --- a/parse_test.go +++ b/parse_test.go @@ -1584,3 +1584,137 @@ func TestNULLRecord(t *testing.T) { t.Fatalf("Expected packet to contain NULL record") } } + +func TestParseAPL(t *testing.T) { + tests := []struct { + name string + in string + expect string + }{ + { + "v4", + ". APL 1:192.0.2.0/24", + ".\t3600\tIN\tAPL\t1:192.0.2.0/24", + }, + { + "v6", + ". APL 2:2001:db8::/32", + ".\t3600\tIN\tAPL\t2:2001:db8::/32", + }, + { + "null v6", + ". APL 2:::/0", + ".\t3600\tIN\tAPL\t2:::/0", + }, + { + "null v4", + ". APL 1:0.0.0.0/0", + ".\t3600\tIN\tAPL\t1:0.0.0.0/0", + }, + { + "full v6", + ". APL 2:::/0", + ".\t3600\tIN\tAPL\t2:::/0", + }, + { + "full v4", + ". APL 1:192.0.2.1/32", + ".\t3600\tIN\tAPL\t1:192.0.2.1/32", + }, + { + "full v6", + ". APL 2:2001:0db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128", + ".\t3600\tIN\tAPL\t2:2001:db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128", + }, + { + "v4in6", + ". APL 2:::ffff:192.0.2.0/120", + ".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120", + }, + { + "v4in6 v6 syntax", + ". APL 2:::ffff:c000:0200/120", + ".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120", + }, + { + "negate", + ". APL !1:192.0.2.0/24", + ".\t3600\tIN\tAPL\t!1:192.0.2.0/24", + }, + { + "multiple", + ". APL 1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::0/48", + ".\t3600\tIN\tAPL\t1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::/48", + }, + { + "no address", + ". APL", + ".\t3600\tIN\tAPL\t", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + rr, err := NewRR(tc.in) + if err != nil { + t.Fatalf("failed to parse RR: %s", err) + } + + got := rr.String() + if got != tc.expect { + t.Errorf("expected %q, got %q", tc.expect, got) + } + }) + } +} + +func TestParseAPLErrors(t *testing.T) { + tests := []struct { + name string + in string + }{ + { + "unexpected", + `. APL ""`, + }, + { + "unrecognized family", + ". APL 3:0.0.0.0/0", + }, + { + "malformed family", + ". APL foo:0.0.0.0/0", + }, + { + "malformed address", + ". APL 1:192.0.2/16", + }, + { + "extra bits", + ". APL 2:2001:db8::/0", + }, + { + "address mismatch v2", + ". APL 1:2001:db8::/64", + }, + { + "address mismatch v6", + ". APL 2:192.0.2.1/32", + }, + { + "no prefix", + ". APL 1:192.0.2.1", + }, + { + "no family", + ". APL 0.0.0.0/0", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := NewRR(tc.in) + if err == nil { + t.Fatal("expected error, got none") + } + }) + } +} diff --git a/scan_rr.go b/scan_rr.go index 93b24a69..6c37b2e2 100644 --- a/scan_rr.go +++ b/scan_rr.go @@ -1696,3 +1696,69 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError { return nil } + +func (rr *APL) parse(c *zlexer, o string) *ParseError { + var prefixes []APLPrefix + + for { + l, _ := c.Next() + if l.value == zNewline || l.value == zEOF { + break + } + if l.value == zBlank && prefixes != nil { + continue + } + if l.value != zString { + return &ParseError{"", "unexpected APL field", l} + } + + // Expected format: [!]afi:address/prefix + + colon := strings.IndexByte(l.token, ':') + if colon == -1 { + return &ParseError{"", "missing colon in APL field", l} + } + + family, cidr := l.token[:colon], l.token[colon+1:] + + var negation bool + if family != "" && family[0] == '!' { + negation = true + family = family[1:] + } + + afi, err := strconv.ParseUint(family, 10, 16) + if err != nil { + return &ParseError{"", "failed to parse APL family: " + err.Error(), l} + } + var addrLen int + switch afi { + case 1: + addrLen = net.IPv4len + case 2: + addrLen = net.IPv6len + default: + return &ParseError{"", "unrecognized APL family", l} + } + + ip, subnet, err := net.ParseCIDR(cidr) + if err != nil { + return &ParseError{"", "failed to parse APL address: " + err.Error(), l} + } + if !ip.Equal(subnet.IP) { + return &ParseError{"", "extra bits in APL address", l} + } + + if len(subnet.IP) != addrLen { + return &ParseError{"", "address mismatch with the APL family", l} + } + + prefixes = append(prefixes, APLPrefix{ + Negation: negation, + Network: *subnet, + }) + } + + rr.Prefixes = prefixes + return nil +} diff --git a/types.go b/types.go index 95fce25d..a6048cb1 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,7 @@ package dns import ( + "bytes" "fmt" "net" "strconv" @@ -61,6 +62,7 @@ const ( TypeCERT uint16 = 37 TypeDNAME uint16 = 39 TypeOPT uint16 = 41 // EDNS + TypeAPL uint16 = 42 TypeDS uint16 = 43 TypeSSHFP uint16 = 44 TypeRRSIG uint16 = 46 @@ -1353,6 +1355,88 @@ func (rr *CSYNC) len(off int, compression map[string]struct{}) int { return l } +// APL RR. See RFC 3123. +type APL struct { + Hdr RR_Header + Prefixes []APLPrefix `dns:"apl"` +} + +// APLPrefix is an address prefix hold by an APL record. +type APLPrefix struct { + Negation bool + Network net.IPNet +} + +// String returns presentation form of the APL record. +func (rr *APL) String() string { + var sb strings.Builder + sb.WriteString(rr.Hdr.String()) + for i, p := range rr.Prefixes { + if i > 0 { + sb.WriteByte(' ') + } + sb.WriteString(p.str()) + } + return sb.String() +} + +// str returns presentation form of the APL prefix. +func (p *APLPrefix) str() string { + var sb strings.Builder + if p.Negation { + sb.WriteByte('!') + } + + switch len(p.Network.IP) { + case net.IPv4len: + sb.WriteByte('1') + case net.IPv6len: + sb.WriteByte('2') + } + + sb.WriteByte(':') + + switch len(p.Network.IP) { + case net.IPv4len: + sb.WriteString(p.Network.IP.String()) + case net.IPv6len: + // add prefix for IPv4-mapped IPv6 + if v4 := p.Network.IP.To4(); v4 != nil { + sb.WriteString("::ffff:") + } + sb.WriteString(p.Network.IP.String()) + } + + sb.WriteByte('/') + + prefix, _ := p.Network.Mask.Size() + sb.WriteString(strconv.Itoa(prefix)) + + return sb.String() +} + +// equals reports whether two APL prefixes are identical. +func (a *APLPrefix) equals(b *APLPrefix) bool { + return a.Negation == b.Negation && + bytes.Equal(a.Network.IP, b.Network.IP) && + bytes.Equal(a.Network.Mask, b.Network.Mask) +} + +// copy returns a copy of the APL prefix. +func (p *APLPrefix) copy() APLPrefix { + return APLPrefix{ + Negation: p.Negation, + Network: copyNet(p.Network), + } +} + +// len returns size of the prefix in wire format. +func (p *APLPrefix) len() int { + // 4-byte header and the network address prefix (see Section 4 of RFC 3123) + prefix, _ := p.Network.Mask.Size() + return 4 + (prefix+7)/8 +} + // TimeToString translates the RRSIG's incep. and expir. times to the // string representation used when printing the record. // It takes serial arithmetic (RFC 1982) into account. @@ -1409,6 +1493,17 @@ func copyIP(ip net.IP) net.IP { return p } +// copyNet returns a copy of a subnet. +func copyNet(n net.IPNet) net.IPNet { + m := make(net.IPMask, len(n.Mask)) + copy(m, n.Mask) + + return net.IPNet{ + IP: copyIP(n.IP), + Mask: m, + } +} + // SplitN splits a string into N sized string chunks. // This might become an exported function once. func splitN(s string, n int) []string { diff --git a/types_generate.go b/types_generate.go index 255f6134..a06619a4 100644 --- a/types_generate.go +++ b/types_generate.go @@ -179,6 +179,8 @@ func main() { o("for _, x := range rr.%s { l += domainNameLen(x, off+l, compression, false) }\n") case `dns:"txt"`: o("for _, x := range rr.%s { l += len(x) + 1 }\n") + case `dns:"apl"`: + o("for _, x := range rr.%s { l += x.len() }\n") default: log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) } @@ -260,6 +262,12 @@ func main() { fields = append(fields, f) continue } + if t == "APLPrefix" { + fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i := range rr.%s {\n %s[i] = rr.%s[i].copy()\n}\n", + f, t, f, f, f, f) + fields = append(fields, f) + continue + } fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", f, t, f, f, f) fields = append(fields, f) diff --git a/zduplicate.go b/zduplicate.go index 74389162..a58a8c0c 100644 --- a/zduplicate.go +++ b/zduplicate.go @@ -52,6 +52,23 @@ func (r1 *ANY) isDuplicate(_r2 RR) bool { return true } +func (r1 *APL) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*APL) + if !ok { + return false + } + _ = r2 + if len(r1.Prefixes) != len(r2.Prefixes) { + return false + } + for i := 0; i < len(r1.Prefixes); i++ { + if !r1.Prefixes[i].equals(&r2.Prefixes[i]) { + return false + } + } + return true +} + func (r1 *AVC) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*AVC) if !ok { diff --git a/zmsg.go b/zmsg.go index c4cf4757..02a5dfa4 100644 --- a/zmsg.go +++ b/zmsg.go @@ -36,6 +36,14 @@ func (rr *ANY) pack(msg []byte, off int, compression compressionMap, compress bo return off, nil } +func (rr *APL) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDataApl(rr.Prefixes, msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *AVC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { off, err = packStringTxt(rr.Txt, msg, off) if err != nil { @@ -1127,6 +1135,17 @@ func (rr *ANY) unpack(msg []byte, off int) (off1 int, err error) { return off, nil } +func (rr *APL) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Prefixes, off, err = unpackDataApl(msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *AVC) unpack(msg []byte, off int) (off1 int, err error) { rdStart := off _ = rdStart diff --git a/ztypes.go b/ztypes.go index f7ec8352..1cbd6d3f 100644 --- a/ztypes.go +++ b/ztypes.go @@ -13,6 +13,7 @@ var TypeToRR = map[uint16]func() RR{ TypeAAAA: func() RR { return new(AAAA) }, TypeAFSDB: func() RR { return new(AFSDB) }, TypeANY: func() RR { return new(ANY) }, + TypeAPL: func() RR { return new(APL) }, TypeAVC: func() RR { return new(AVC) }, TypeCAA: func() RR { return new(CAA) }, TypeCDNSKEY: func() RR { return new(CDNSKEY) }, @@ -87,6 +88,7 @@ var TypeToString = map[uint16]string{ TypeAAAA: "AAAA", TypeAFSDB: "AFSDB", TypeANY: "ANY", + TypeAPL: "APL", TypeATMA: "ATMA", TypeAVC: "AVC", TypeAXFR: "AXFR", @@ -169,6 +171,7 @@ func (rr *A) Header() *RR_Header { return &rr.Hdr } func (rr *AAAA) Header() *RR_Header { return &rr.Hdr } func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr } func (rr *ANY) Header() *RR_Header { return &rr.Hdr } +func (rr *APL) Header() *RR_Header { return &rr.Hdr } func (rr *AVC) Header() *RR_Header { return &rr.Hdr } func (rr *CAA) Header() *RR_Header { return &rr.Hdr } func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr } @@ -262,6 +265,13 @@ func (rr *ANY) len(off int, compression map[string]struct{}) int { l := rr.Hdr.len(off, compression) return l } +func (rr *APL) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Prefixes { + l += x.len() + } + return l +} func (rr *AVC) len(off int, compression map[string]struct{}) int { l := rr.Hdr.len(off, compression) for _, x := range rr.Txt { @@ -673,6 +683,13 @@ func (rr *AFSDB) copy() RR { func (rr *ANY) copy() RR { return &ANY{rr.Hdr} } +func (rr *APL) copy() RR { + Prefixes := make([]APLPrefix, len(rr.Prefixes)) + for i := range rr.Prefixes { + Prefixes[i] = rr.Prefixes[i].copy() + } + return &APL{rr.Hdr, Prefixes} +} func (rr *AVC) copy() RR { Txt := make([]string, len(rr.Txt)) copy(Txt, rr.Txt)