Repository: miekg/dns
Pull request #1639 (2025-03-10 17:33:56)

Add support for Compact Answers OK flag in EDNS
Compact Answers OK flag is a signal that a queryer or server understand and can validate Compact Denial of Existance answers allowing RCODE NXDOMAIN restoration with single NSEC/NSEC3.
Flag is allocated by IANA: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-13 Compact Denial of Existance: https://www.iana.org/go/draft-ietf-dnsop-compact-denial-of-existence-07

Thanks for you pull request, do note the following:

* If your PR introduces backward incompatible changes it will very likely not be merged.

* We support the last two major Go versions, if your PR uses features from a too new Go version, it
    will not be merged.


miekg: oh wow a whole new bit assigned. wow :)
miekg: lgtm, you can update the README and add this draft to the list of RFC. Will change the reference once this is an RFC.
miekg: Pieter Lexis made this comment on mastodon though:

> [@miek](https://mastodon.cloud/@miek) I'm not on my PC now (no GH access(. But I think the Z() and SetZ() functions needs their bitshifts altered as well in this PR?
Chreo: > Pieter Lexis made this comment on mastodon though:
> 
> > [@miek](https://mastodon.cloud/@miek) I'm not on my PC now (no GH access(. But I think the Z() and SetZ() functions needs their bitshifts altered as well in this PR?

Thanks! and yeah, SetZ for EDNS clobbers CO flag unless modified. Fixed i latest version

Patch:
diff --git a/README.md b/README.md
index 8c957f6a..5fbfb17e 100644
--- a/README.md
+++ b/README.md
@@ -194,6 +194,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
 * 9461 - Service Binding Mapping for DNS Servers
 * 9462 - Discovery of Designated Resolvers
 * 9460 - SVCB and HTTPS Records
+* Draft - Compact Denial of Existence in DNSSEC
 
 ## Loosely Based Upon
 
diff --git a/edns.go b/edns.go
index 0447fd82..91793b90 100644
--- a/edns.go
+++ b/edns.go
@@ -27,6 +27,7 @@ const (
 	EDNS0LOCALSTART   = 0xFDE9  // Beginning of range reserved for local/experimental use (See RFC 6891)
 	EDNS0LOCALEND     = 0xFFFE  // End of range reserved for local/experimental use (See RFC 6891)
 	_DO               = 1 << 15 // DNSSEC OK
+	_CO               = 1 << 14 // Compact Answers OK
 )
 
 // makeDataOpt is used to unpack the EDNS0 option(s) from a message.
@@ -75,7 +76,11 @@ type OPT struct {
 func (rr *OPT) String() string {
 	s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; "
 	if rr.Do() {
-		s += "flags: do; "
+		if rr.Co() {
+			s += "flags: do, co; "
+		} else {
+			s += "flags: do; "
+		}
 	} else {
 		s += "flags:; "
 	}
@@ -195,14 +200,34 @@ func (rr *OPT) SetDo(do ...bool) {
 	}
 }
 
-// Z returns the Z part of the OPT RR as a uint16 with only the 15 least significant bits used.
+// Co returns the value of the CO (Compact Answers OK) bit.
+func (rr *OPT) Co() bool {
+	return rr.Hdr.Ttl&_CO == _CO
+}
+
+// SetCo sets the CO (Compact Answers OK) bit.
+// If we pass an argument, set the CO bit to that value.
+// It is possible to pass 2 or more arguments, but they will be ignored.
+func (rr *OPT) SetCo(co ...bool) {
+	if len(co) == 1 {
+		if co[0] {
+			rr.Hdr.Ttl |= _CO
+		} else {
+			rr.Hdr.Ttl &^= _CO
+		}
+	} else {
+		rr.Hdr.Ttl |= _CO
+	}
+}
+
+// Z returns the Z part of the OPT RR as a uint16 with only the 14 least significant bits used.
 func (rr *OPT) Z() uint16 {
-	return uint16(rr.Hdr.Ttl & 0x7FFF)
+	return uint16(rr.Hdr.Ttl & 0x3FFF)
 }
 
-// SetZ sets the Z part of the OPT RR, note only the 15 least significant bits of z are used.
+// SetZ sets the Z part of the OPT RR, note only the 14 least significant bits of z are used.
 func (rr *OPT) SetZ(z uint16) {
-	rr.Hdr.Ttl = rr.Hdr.Ttl&^0x7FFF | uint32(z&0x7FFF)
+	rr.Hdr.Ttl = rr.Hdr.Ttl&^0x3FFF | uint32(z&0x3FFF)
 }
 
 // EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.


Test patch:
diff --git a/edns_test.go b/edns_test.go
index b7c15f7e..b9853dd2 100644
--- a/edns_test.go
+++ b/edns_test.go
@@ -56,6 +56,39 @@ func TestOPTTtl(t *testing.T) {
 		t.Errorf("DO bit should be non-zero")
 	}
 
+	// CO (Compact ANswers OK) flag tests follow the same pattern as DO tests
+	// verify that invoking SetCo() sets CO=1
+	e.SetCo()
+	if !e.Co() {
+		t.Errorf("CO bit should be non-zero")
+	}
+
+	// verify that using SetCo(true) works when CO=1
+	e.SetCo(true)
+	if !e.Co() {
+		t.Errorf("CO bit should still be non-zero")
+	}
+	// verify that we can use SetCo(false) to set CO=0
+	e.SetCo(false)
+	if e.Co() {
+		t.Errorf("CO bit should be zero")
+	}
+	// verify that if we call SetCo(false) when CO=0 that it is unchanged
+	e.SetCo(false)
+	if e.Co() {
+		t.Errorf("CO bit should still be zero")
+	}
+	// verify that using SetCo(true) works for CO=0
+	e.SetCo(true)
+	if !e.Co() {
+		t.Errorf("CO bit should be non-zero")
+	}
+	// verify that using SetCo() works for CO=1
+	e.SetCo()
+	if !e.Co() {
+		t.Errorf("CO bit should be non-zero")
+	}
+
 	if e.Version() != 0 {
 		t.Errorf("version should be non-zero")
 	}
@@ -141,6 +174,7 @@ func TestZ(t *testing.T) {
 	e.Hdr.Rrtype = TypeOPT
 	e.SetVersion(8)
 	e.SetDo()
+	e.SetCo()
 	if e.Z() != 0 {
 		t.Errorf("expected Z of 0, got %d", e.Z())
 	}
@@ -149,8 +183,8 @@ func TestZ(t *testing.T) {
 		t.Errorf("expected Z of 5, got %d", e.Z())
 	}
 	e.SetZ(0xFFFF)
-	if e.Z() != 0x7FFF {
-		t.Errorf("expected Z of 0x7FFFF, got %d", e.Z())
+	if e.Z() != 0x3FFF {
+		t.Errorf("expected Z of 0x3FFFF, got %d", e.Z())
 	}
 	if e.Version() != 8 {
 		t.Errorf("expected version to still be 8, got %d", e.Version())