From 91ddf2b3504535ad83fce7705aa8a9280e199b31 Mon Sep 17 00:00:00 2001 From: blu Date: Wed, 22 Oct 2025 21:46:18 -0500 Subject: [PATCH] add ipv6 resource records --- resource_record.go | 31 ++++++++++++++++++++++++++++++- resource_record_test.go | 30 ++++++++++++++++++++++++++++++ types.go | 7 +++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/resource_record.go b/resource_record.go index dc4c514..c7f70de 100644 --- a/resource_record.go +++ b/resource_record.go @@ -26,7 +26,7 @@ func (a *A) Encode(bytes []byte, offsets *map[string]uint16) ([]byte, error) { return nil, fmt.Errorf("A record: cannot encode non-IPv4 address %s", a.Address.String()) } - return append(bytes, a.Address.To4()...), nil + return append(bytes, ipv4...), nil } func (a A) String() string { @@ -751,6 +751,33 @@ func (rt RT) String() string { return fmt.Sprintf("%d %s", rt.Preference, rt.IntermediateHost) } +func (a *AAAA) Decode(buf []byte, offset int, rdlength int) (int, error) { + bytes, offset, err := getSlice(buf, offset, rdlength) + if err != nil { + return offset, fmt.Errorf("AAAA record: failed to read address data: %w", err) + } + + a.Address = net.IP(bytes) + if a.Address.To16() == nil { + return offset, fmt.Errorf("AAAA record: decoded data is not a valid IPv6 address: %v", bytes) + } + return offset, nil +} + +func (a *AAAA) Encode(bytes []byte, offsets *map[string]uint16) ([]byte, error) { + ipv6 := a.Address.To16() + // XXX: ipv6 encodes ipv4 so need to check its not ipv4 + if a.Address.To4() != nil || ipv6 == nil { + return nil, fmt.Errorf("AAAA record: cannot encode non-IPv6 address %s", a.Address.String()) + } + + return append(bytes, ipv6...), nil +} + +func (a AAAA) String() string { + return a.Address.String() +} + func (opt *OPT) Decode(buf []byte, offset int, rdlength int) (int, error) { // s := offset opt.Options = make([]EDNSOption, 0) @@ -915,6 +942,8 @@ func (r *ResourceRecord) Decode(buf []byte, offset int) (int, error) { r.RData = &ISDN{} case 21: r.RData = &RT{} + case 28: + r.RData = &AAAA{} case 41: r.RData = &OPT{} default: diff --git a/resource_record_test.go b/resource_record_test.go index a5499be..3f88753 100644 --- a/resource_record_test.go +++ b/resource_record_test.go @@ -516,3 +516,33 @@ func TestResourceRecordEncode(t *testing.T) { }) } } + +func TestAAAARecord(t *testing.T) { + addr := net.ParseIP("2001:db8::1") + rdataBytes := []byte(addr) + aaaa := &AAAA{} + + offset, err := aaaa.Decode([]byte{}, 0, 16) + assert.Error(t, err, "Decode should fail with empty buffer") + assert.True(t, errors.Is(err, &BufferOverflowError{})) + + offset, err = aaaa.Decode(rdataBytes, 0, 16) + assert.NoError(t, err) + assert.Equal(t, 16, offset) + assert.Equal(t, addr, aaaa.Address) + + _, err = aaaa.Decode([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 0, 15) + assert.Error(t, err) + assert.Contains(t, err.Error(), "AAAA record:") + + aaaaEncode := &AAAA{Address: addr} + encoded := encodeRData(t, aaaaEncode) + assert.Equal(t, rdataBytes, encoded) + + ipv4 := net.ParseIP("192.168.1.1") + aaaaEncodeInvalid := &AAAA{Address: ipv4} + _, err = aaaaEncodeInvalid.Encode([]byte{}, &map[string]uint16{}) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "cannot encode non-IPv6 address") +} diff --git a/types.go b/types.go index 0275f1b..74ccd33 100644 --- a/types.go +++ b/types.go @@ -92,6 +92,7 @@ const ( X25Type = 19 ISDNType = 20 RTType = 21 + AAAAType = 28 OPTType = 41 @@ -145,6 +146,8 @@ func (t DNSType) String() string { return "ISDN" case RTType: return "RT" + case AAAAType: + return "AAAA" case OPTType: return "OPT" case AXFRType: @@ -375,6 +378,10 @@ type RT struct { IntermediateHost string } +type AAAA struct { + Address net.IP +} + type EDNSOption struct { Code uint16 Data []byte -- 2.49.1