Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.2.7"
".": "0.2.8"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)


Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 1 addition & 0 deletions dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
96 changes: 96 additions & 0 deletions dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion docs/client-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading