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
This commit is contained in:
parent
e636c10380
commit
c9b62b4215
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
120
msg_helpers.go
120
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
134
parse_test.go
134
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
66
scan_rr.go
66
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
|
||||
}
|
||||
|
|
95
types.go
95
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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
19
zmsg.go
19
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
|
||||
|
|
17
ztypes.go
17
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)
|
||||
|
|
Loading…
Reference in New Issue