From 0d87441d53356f9f675ede19b7bc0f72b7a214e7 Mon Sep 17 00:00:00 2001 From: crazydi4mond <255249920+crazydi4mond@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:40:42 +0200 Subject: [PATCH 1/2] test(dns): add NULL and CAA encode/decode unit tests - Add round-trip, identity, error-path, and wire-format tests - Add resolver filtering note for NULL record in README - Add CAA flags comment in EncodeRDataCAA - Update client library doc with new record type options --- README.md | 2 +- dns/dns.go | 1 + dns/dns_test.go | 96 ++++++++++++++++++++++++++++++++++++++++++ docs/client-library.md | 2 +- 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b002a90..7259542 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,7 @@ VayDNS supports multiple DNS record types for downstream data encoding. Both cli | Type | Description | Capacity | | ---- | ----------- | -------- | | `txt` | TXT record (default). Highest capacity, compatible with dnstt. | Bounded by UDP payload (~1200 bytes) | -| `null` | NULL record. Raw binary payload in a single RR. | Bounded by UDP payload | +| `null` | NULL record. Raw binary payload in a single RR. Some recursive resolvers may filter or refuse to relay NULL records. | Bounded by UDP payload | | `cname` | CNAME record. Data encoded as a DNS name under the tunnel domain. | Bounded by 255-byte DNS name limit | | `ns` | NS record. Same encoding as CNAME. | Same as CNAME | | `mx` | MX record. 2-byte preference header + name encoding. | Same as CNAME | diff --git a/dns/dns.go b/dns/dns.go index 909e054..57eb1a3 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -726,6 +726,7 @@ func DecodeRDataCAA(p []byte) ([]byte, error) { func EncodeRDataCAA(p []byte) []byte { const tag = "issue" rdata := make([]byte, 2+len(tag)+len(p)) + // rdata[0] = 0 (flags; bit 7 is the "critical" flag per RFC 8659 §4.1) rdata[1] = byte(len(tag)) copy(rdata[2:], tag) copy(rdata[2+len(tag):], p) diff --git a/dns/dns_test.go b/dns/dns_test.go index 4c90b9e..59fe22f 100644 --- a/dns/dns_test.go +++ b/dns/dns_test.go @@ -805,6 +805,102 @@ func TestEncodeDecodeRDataAAAA(t *testing.T) { } } +func TestEncodeDecodeRDataNULL(t *testing.T) { + for _, p := range [][]byte{ + {}, + {0x00}, + {0x01, 0x02, 0x03}, + bytes.Repeat([]byte{0xab}, 100), + bytes.Repeat([]byte{0xff}, 1000), + } { + rdata := EncodeRDataNULL(p) + decoded, err := DecodeRDataNULL(rdata) + if err != nil { + t.Errorf("DecodeRDataNULL(%x): %v", rdata, err) + continue + } + if !bytes.Equal(decoded, p) { + t.Errorf("NULL round-trip failed for len=%d: got len=%d", len(p), len(decoded)) + } + } +} + +func TestRDataNULLIdentity(t *testing.T) { + // NULL encode/decode should be identity — no framing overhead. + p := []byte{0x01, 0x02, 0x03} + if !bytes.Equal(EncodeRDataNULL(p), p) { + t.Error("EncodeRDataNULL should return input unchanged") + } + decoded, _ := DecodeRDataNULL(p) + if !bytes.Equal(decoded, p) { + t.Error("DecodeRDataNULL should return input unchanged") + } +} + +func TestDecodeRDataCAA(t *testing.T) { + for _, test := range []struct { + desc string + p []byte + decoded []byte + err error + }{ + {"empty input", []byte{}, nil, io.ErrUnexpectedEOF}, + {"single byte", []byte{0x00}, nil, io.ErrUnexpectedEOF}, + {"tag length exceeds data", []byte{0x00, 0x05, 'a'}, nil, io.ErrUnexpectedEOF}, + {"tag only, no value", []byte{0x00, 0x05, 'i', 's', 's', 'u', 'e'}, []byte{}, nil}, + {"tag + value", []byte{0x00, 0x05, 'i', 's', 's', 'u', 'e', 0xaa, 0xbb}, []byte{0xaa, 0xbb}, nil}, + {"zero-length tag", []byte{0x00, 0x00, 0x01, 0x02}, []byte{0x01, 0x02}, nil}, + {"flags byte ignored", []byte{0x80, 0x05, 'i', 's', 's', 'u', 'e', 0xff}, []byte{0xff}, nil}, + } { + decoded, err := DecodeRDataCAA(test.p) + if err != test.err { + t.Errorf("%s: got err %v, want %v", test.desc, err, test.err) + continue + } + if err == nil && !bytes.Equal(decoded, test.decoded) { + t.Errorf("%s: got %x, want %x", test.desc, decoded, test.decoded) + } + } +} + +func TestEncodeRDataCAA(t *testing.T) { + p := []byte{0x01, 0x02, 0x03} + rdata := EncodeRDataCAA(p) + // Expected: flags(0) + tagLen(5) + "issue" + payload + expected := append([]byte{0x00, 0x05, 'i', 's', 's', 'u', 'e'}, p...) + if !bytes.Equal(rdata, expected) { + t.Errorf("EncodeRDataCAA(%x) = %x, want %x", p, rdata, expected) + } +} + +func TestEncodeRDataCAAEmpty(t *testing.T) { + rdata := EncodeRDataCAA([]byte{}) + // Even with empty payload, should have flags + tagLen + tag. + if len(rdata) != 7 { + t.Errorf("EncodeRDataCAA(empty) length = %d, want 7", len(rdata)) + } +} + +func TestRDataCAARoundTrip(t *testing.T) { + for _, p := range [][]byte{ + {}, + {0x00}, + {0x01, 0x02, 0x03}, + bytes.Repeat([]byte{0xab}, 100), + bytes.Repeat([]byte{0xff}, 1000), + } { + rdata := EncodeRDataCAA(p) + decoded, err := DecodeRDataCAA(rdata) + if err != nil { + t.Errorf("CAA round-trip decode error for len=%d: %v", len(p), err) + continue + } + if !bytes.Equal(decoded, p) { + t.Errorf("CAA round-trip failed for len=%d: got len=%d", len(p), len(decoded)) + } + } +} + func TestReadRRMXCompression(t *testing.T) { // DNS message with MX answer using compression pointer in exchange name. msg := []byte{ diff --git a/docs/client-library.md b/docs/client-library.md index 25514ec..391b6b0 100644 --- a/docs/client-library.md +++ b/docs/client-library.md @@ -84,7 +84,7 @@ ts.ClientIDSize = 1 // smaller ClientID ts.MaxQnameLen = 101 // QNAME length constraint ts.MaxNumLabels = 2 // label count constraint ts.RPS = 200 // rate limit queries/second -ts.RecordType = "cname" // DNS record type for downstream data (default: "txt") +ts.RecordType = "cname" // DNS record type for downstream data: txt, null, cname, a, aaaa, mx, ns, srv, caa (default: "txt") // Session options t.IdleTimeout = 60 * time.Second From a0ff70110d5e96686ab8f4c8e1c44cd09be07a75 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:43:53 +0200 Subject: [PATCH 2/2] chore(main): release 0.2.8 (#65) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 054e8bc..e7cf9b3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.2.7" + ".": "0.2.8" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fb45f67..5b48bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.2.8](https://github.com/net2share/vaydns/compare/v0.2.7...v0.2.8) (2026-04-10) + + +### Features + +* add -v flag for printing version ([#71](https://github.com/net2share/vaydns/issues/71)) ([3533148](https://github.com/net2share/vaydns/commit/35331484bce1b628372dc37accae4e96b507841f)) +* add NULL and CAA record type support and fix server EDNS mtu advertisement ([#70](https://github.com/net2share/vaydns/issues/70)) ([8354db2](https://github.com/net2share/vaydns/commit/8354db2a080ce9543594bc4e71592f33e6489d82)) + ## [0.2.7](https://github.com/net2share/vaydns/compare/v0.2.6...v0.2.7) (2026-04-01)