Repository: miekg/dns
Pull request #1246 (2021-03-22 14:23:20)

OPT: add Z and SetZ methods
Fixes: #1169

Signed-off-by: Miek Gieben <miek@miek.nl>


miekg: please double check my bit mask, see https://tools.ietf.org/html/rfc6891#section-6.1.3

Patch:
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
index 2188e408..1a03dcc8 100644
--- a/.github/workflows/cifuzz.yml
+++ b/.github/workflows/cifuzz.yml
@@ -1,28 +1,28 @@
-name: CIFuzz
-on:
-  pull_request:
-    branches:
-      - master
-    paths:
-      - '**.go'
-jobs:
- Fuzzing:
-   runs-on: ubuntu-latest
-   steps:
-   - name: Build Fuzzers
-     uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
-     with:
-       oss-fuzz-project-name: 'go-dns'
-       dry-run: false
-   - name: Run Fuzzers
-     uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
-     with:
-       oss-fuzz-project-name: 'go-dns'
-       fuzz-seconds: 300
-       dry-run: false
-   - name: Upload Crash
-     uses: actions/upload-artifact@v1
-     if: failure()
-     with:
-       name: artifacts
-       path: ./out/artifacts
+#name: CIFuzz
+#on:
+#  pull_request:
+#    branches:
+#      - master
+#    paths:
+#      - '**.go'
+#jobs:
+# Fuzzing:
+#   runs-on: ubuntu-latest
+#   steps:
+#   - name: Build Fuzzers
+#     uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+#     with:
+#       oss-fuzz-project-name: 'go-dns'
+#       dry-run: false
+#   - name: Run Fuzzers
+#     uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+#     with:
+#       oss-fuzz-project-name: 'go-dns'
+#       fuzz-seconds: 300
+#       dry-run: false
+#   - name: Upload Crash
+#     uses: actions/upload-artifact@v1
+#     if: failure()
+#     with:
+#       name: artifacts
+#       path: ./out/artifacts
diff --git a/README.md b/README.md
index 3594492b..d5b78ef4 100644
--- a/README.md
+++ b/README.md
@@ -171,6 +171,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
 * 8080 - EdDSA for DNSSEC
 * 8499 - DNS Terminology
 * 8659 - DNS Certification Authority Authorization (CAA) Resource Record
+* 8914 - Extended DNS Errors
 * 8976 - Message Digest for DNS Zones (ZONEMD RR)
 
 ## Loosely Based Upon
diff --git a/dnssec.go b/dnssec.go
index 80d2be5a..7880d7a8 100644
--- a/dnssec.go
+++ b/dnssec.go
@@ -372,6 +372,8 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
 // Verify validates an RRSet with the signature and key. This is only the
 // cryptographic test, the signature validity period must be checked separately.
 // This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
+// It also checks that the Zone Key bit (RFC 4034 2.1.1) is set on the DNSKEY
+// and that the Protocol field is set to 3 (RFC 4034 2.1.2).
 func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
 	// First the easy checks
 	if !IsRRset(rrset) {
@@ -392,6 +394,12 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
 	if k.Protocol != 3 {
 		return ErrKey
 	}
+	// RFC 4034 2.1.1 If bit 7 has value 0, then the DNSKEY record holds some
+	// other type of DNS public key and MUST NOT be used to verify RRSIGs that
+	// cover RRsets.
+	if k.Flags&ZONE == 0 {
+		return ErrKey
+	}
 
 	// IsRRset checked that we have at least one RR and that the RRs in
 	// the set have consistent type, class, and name. Also check that type and
diff --git a/doc.go b/doc.go
index f7629ec3..5c83f82e 100644
--- a/doc.go
+++ b/doc.go
@@ -159,7 +159,7 @@ shows the options you have and what functions to call.
 TRANSACTION SIGNATURE
 
 An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
-The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512.
+The supported algorithms include: HmacSHA1, HmacSHA256 and HmacSHA512.
 
 Basic use pattern when querying with a TSIG name "axfr." (note that these key names
 must be fully qualified - as they are domain names) and the base64 secret
@@ -174,7 +174,7 @@ changes to the RRset after calling SetTsig() the signature will be incorrect.
 	c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
 	m := new(dns.Msg)
 	m.SetQuestion("miek.nl.", dns.TypeMX)
-	m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+	m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
 	...
 	// When sending the TSIG RR is calculated and filled in before sending
 
@@ -187,7 +187,7 @@ request an AXFR for miek.nl. with TSIG key named "axfr." and secret
 	m := new(dns.Msg)
 	t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
 	m.SetAxfr("miek.nl.")
-	m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+	m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
 	c, err := t.In(m, "176.58.119.54:53")
 	for r := range c { ... }
 
@@ -214,7 +214,7 @@ client must be configured with an implementation of the TsigProvider interface:
 	c.TsigProvider = new(Provider)
 	m := new(dns.Msg)
 	m.SetQuestion("miek.nl.", dns.TypeMX)
-	m.SetTsig(keyname, dns.HmacSHA1, 300, time.Now().Unix())
+	m.SetTsig(keyname, dns.HmacSHA256, 300, time.Now().Unix())
 	...
 	// TSIG RR is calculated by calling your Generate method
 
@@ -231,7 +231,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
 		if r.IsTsig() != nil {
 			if w.TsigStatus() == nil {
 				// *Msg r has an TSIG record and it was validated
-				m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+				m.SetTsig("axfr.", dns.HmacSHA256, 300, time.Now().Unix())
 			} else {
 				// *Msg r has an TSIG records and it was not validated
 			}
diff --git a/edns.go b/edns.go
index 1a87f4cb..844cf92b 100644
--- a/edns.go
+++ b/edns.go
@@ -22,6 +22,7 @@ const (
 	EDNS0COOKIE       = 0xa     // EDNS0 Cookie
 	EDNS0TCPKEEPALIVE = 0xb     // EDNS0 tcp keep alive (See RFC 7828)
 	EDNS0PADDING      = 0xc     // EDNS0 padding (See RFC 7830)
+	EDNS0EDE          = 0xf     // EDNS0 extended DNS errors (See RFC 8914)
 	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
@@ -73,6 +74,8 @@ func (rr *OPT) String() string {
 			s += "\n; LOCAL OPT: " + o.String()
 		case *EDNS0_PADDING:
 			s += "\n; PADDING: " + o.String()
+		case *EDNS0_EDE:
+			s += "\n; EDE: " + o.String()
 		}
 	}
 	return s
@@ -148,6 +151,16 @@ 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.
+func (rr *OPT) Z() uint16 {
+	return uint16(rr.Hdr.Ttl & 0x7FFF)
+}
+
+// SetZ sets the Z part of the OPT RR, note only the 15 least significant bits of z are used.
+func (rr *OPT) SetZ(z uint16) {
+	rr.Hdr.Ttl = rr.Hdr.Ttl&^0x7FFF | uint32(z&0x7FFF)
+}
+
 // EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
 type EDNS0 interface {
 	// Option returns the option code for the option.
@@ -673,3 +686,101 @@ func (e *EDNS0_PADDING) copy() EDNS0 {
 	copy(b, e.Padding)
 	return &EDNS0_PADDING{b}
 }
+
+// Extended DNS Error Codes (RFC 8914).
+const (
+	ExtendedErrorCodeOther uint16 = iota
+	ExtendedErrorCodeUnsupportedDNSKEYAlgorithm
+	ExtendedErrorCodeUnsupportedDSDigestType
+	ExtendedErrorCodeStaleAnswer
+	ExtendedErrorCodeForgedAnswer
+	ExtendedErrorCodeDNSSECIndeterminate
+	ExtendedErrorCodeDNSBogus
+	ExtendedErrorCodeSignatureExpired
+	ExtendedErrorCodeSignatureNotYetValid
+	ExtendedErrorCodeDNSKEYMissing
+	ExtendedErrorCodeRRSIGsMissing
+	ExtendedErrorCodeNoZoneKeyBitSet
+	ExtendedErrorCodeNSECMissing
+	ExtendedErrorCodeCachedError
+	ExtendedErrorCodeNotReady
+	ExtendedErrorCodeBlocked
+	ExtendedErrorCodeCensored
+	ExtendedErrorCodeFiltered
+	ExtendedErrorCodeProhibited
+	ExtendedErrorCodeStaleNXDOMAINAnswer
+	ExtendedErrorCodeNotAuthoritative
+	ExtendedErrorCodeNotSupported
+	ExtendedErrorCodeNoReachableAuthority
+	ExtendedErrorCodeNetworkError
+	ExtendedErrorCodeInvalidData
+)
+
+// ExtendedErrorCodeToString maps extended error info codes to a human readable
+// description.
+var ExtendedErrorCodeToString = map[uint16]string{
+	ExtendedErrorCodeOther:                      "Other",
+	ExtendedErrorCodeUnsupportedDNSKEYAlgorithm: "Unsupported DNSKEY Algorithm",
+	ExtendedErrorCodeUnsupportedDSDigestType:    "Unsupported DS Digest Type",
+	ExtendedErrorCodeStaleAnswer:                "Stale Answer",
+	ExtendedErrorCodeForgedAnswer:               "Forged Answer",
+	ExtendedErrorCodeDNSSECIndeterminate:        "DNSSEC Indeterminate",
+	ExtendedErrorCodeDNSBogus:                   "DNSSEC Bogus",
+	ExtendedErrorCodeSignatureExpired:           "Signature Expired",
+	ExtendedErrorCodeSignatureNotYetValid:       "Signature Not Yet Valid",
+	ExtendedErrorCodeDNSKEYMissing:              "DNSKEY Missing",
+	ExtendedErrorCodeRRSIGsMissing:              "RRSIGs Missing",
+	ExtendedErrorCodeNoZoneKeyBitSet:            "No Zone Key Bit Set",
+	ExtendedErrorCodeNSECMissing:                "NSEC Missing",
+	ExtendedErrorCodeCachedError:                "Cached Error",
+	ExtendedErrorCodeNotReady:                   "Not Ready",
+	ExtendedErrorCodeBlocked:                    "Blocked",
+	ExtendedErrorCodeCensored:                   "Censored",
+	ExtendedErrorCodeFiltered:                   "Filtered",
+	ExtendedErrorCodeProhibited:                 "Prohibited",
+	ExtendedErrorCodeStaleNXDOMAINAnswer:        "Stale NXDOMAIN Answer",
+	ExtendedErrorCodeNotAuthoritative:           "Not Authoritative",
+	ExtendedErrorCodeNotSupported:               "Not Supported",
+	ExtendedErrorCodeNoReachableAuthority:       "No Reachable Authority",
+	ExtendedErrorCodeNetworkError:               "Network Error",
+	ExtendedErrorCodeInvalidData:                "Invalid Data",
+}
+
+// StringToExtendedErrorCode is a map from human readable descriptions to
+// extended error info codes.
+var StringToExtendedErrorCode = reverseInt16(ExtendedErrorCodeToString)
+
+// EDNS0_EDE option is used to return additional information about the cause of
+// DNS errors.
+type EDNS0_EDE struct {
+	InfoCode  uint16
+	ExtraText string
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_EDE) Option() uint16 { return EDNS0EDE }
+func (e *EDNS0_EDE) copy() EDNS0    { return &EDNS0_EDE{e.InfoCode, e.ExtraText} }
+
+func (e *EDNS0_EDE) String() string {
+	info := strconv.FormatUint(uint64(e.InfoCode), 10)
+	if s, ok := ExtendedErrorCodeToString[e.InfoCode]; ok {
+		info += fmt.Sprintf(" (%s)", s)
+	}
+	return fmt.Sprintf("%s: (%s)", info, e.ExtraText)
+}
+
+func (e *EDNS0_EDE) pack() ([]byte, error) {
+	b := make([]byte, 2+len(e.ExtraText))
+	binary.BigEndian.PutUint16(b[0:], e.InfoCode)
+	copy(b[2:], []byte(e.ExtraText))
+	return b, nil
+}
+
+func (e *EDNS0_EDE) unpack(b []byte) error {
+	if len(b) < 2 {
+		return ErrBuf
+	}
+	e.InfoCode = binary.BigEndian.Uint16(b[0:])
+	e.ExtraText = string(b[2:])
+	return nil
+}
diff --git a/msg_helpers.go b/msg_helpers.go
index 47625ed0..472ada5d 100644
--- a/msg_helpers.go
+++ b/msg_helpers.go
@@ -460,6 +460,8 @@ func makeDataOpt(code uint16) EDNS0 {
 		return new(EDNS0_N3U)
 	case EDNS0PADDING:
 		return new(EDNS0_PADDING)
+	case EDNS0EDE:
+		return new(EDNS0_EDE)
 	default:
 		e := new(EDNS0_LOCAL)
 		e.Code = code
diff --git a/scan.go b/scan.go
index 39055bde..57be9882 100644
--- a/scan.go
+++ b/scan.go
@@ -150,6 +150,9 @@ func ReadRR(r io.Reader, file string) (RR, error) {
 // The text "; this is comment" is returned from Comment. Comments inside
 // the RR are returned concatenated along with the RR. Comments on a line
 // by themselves are discarded.
+//
+// Callers should not assume all returned data in an Resource Record is
+// syntactically correct, e.g. illegal base64 in RRSIGs will be returned as-is.
 type ZoneParser struct {
 	c *zlexer
 
diff --git a/svcb.go b/svcb.go
index ec0a76f4..64800daa 100644
--- a/svcb.go
+++ b/svcb.go
@@ -511,8 +511,13 @@ func (s *SVCBIPv4Hint) parse(b string) error {
 }
 
 func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
+	hint := make([]net.IP, len(s.Hint))
+	for i, ip := range s.Hint {
+		hint[i] = copyIP(ip)
+	}
+
 	return &SVCBIPv4Hint{
-		append([]net.IP(nil), s.Hint...),
+		Hint: hint,
 	}
 }
 
@@ -629,8 +634,13 @@ func (s *SVCBIPv6Hint) parse(b string) error {
 }
 
 func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
+	hint := make([]net.IP, len(s.Hint))
+	for i, ip := range s.Hint {
+		hint[i] = copyIP(ip)
+	}
+
 	return &SVCBIPv6Hint{
-		append([]net.IP(nil), s.Hint...),
+		Hint: hint,
 	}
 }
 


Test patch:
diff --git a/edns_test.go b/edns_test.go
index d18961ed..ab2a3b1f 100644
--- a/edns_test.go
+++ b/edns_test.go
@@ -133,3 +133,28 @@ func TestEDNS0_UL(t *testing.T) {
 		}
 	}
 }
+
+func TestZ(t *testing.T) {
+	e := &OPT{}
+	e.Hdr.Name = "."
+	e.Hdr.Rrtype = TypeOPT
+	e.SetVersion(8)
+	e.SetDo()
+	if e.Z() != 0 {
+		t.Errorf("expected Z of 0, got %d", e.Z())
+	}
+	e.SetZ(5)
+	if e.Z() != 5 {
+		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.Version() != 8 {
+		t.Errorf("expected version to still be 8, got %d", e.Version())
+	}
+	if !e.Do() {
+		t.Error("expected DO to be set")
+	}
+}