diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 202ae2366..4be16cd42 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,7 @@ updates: directory: "/" schedule: interval: "monthly" + groups: + all: + patterns: + - "*" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c0674c784..56a62f2b3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ 1.22.x, 1.23.x ] + go: [ 1.22.x, 1.23.x, 1.24.x ] steps: - name: Set up Go diff --git a/README.md b/README.md index 8d5a2a478..54471f5c2 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,8 @@ A not-so-up-to-date-list-that-may-be-actually-current: * https://github.com/wintbiit/NineDNS * https://linuxcontainers.org/incus/ * https://ifconfig.es - +* https://github.com/zmap/zdns +* https://framagit.org/bortzmeyer/check-soa Send pull request if you want to be listed here. @@ -184,6 +185,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository. * 7871 - EDNS0 Client Subnet * 7873 - Domain Name System (DNS) Cookies * 8080 - EdDSA for DNSSEC +* 8490 - DNS Stateful Operations * 8499 - DNS Terminology * 8659 - DNS Certification Authority Authorization (CAA) Resource Record * 8777 - DNS Reverse IP Automatic Multicast Tunneling (AMT) Discovery @@ -192,6 +194,9 @@ Example programs can be found in the `github.com/miekg/exdns` repository. * 9460 - Service Binding and Parameter Specification via the DNS * 9461 - Service Binding Mapping for DNS Servers * 9462 - Discovery of Designated Resolvers +* 9460 - SVCB and HTTPS Records +* 9606 - DNS Resolver Information +* Draft - Compact Denial of Existence in DNSSEC ## Loosely Based Upon diff --git a/dnssec.go b/dnssec.go index 1be87eae6..ffdafcebd 100644 --- a/dnssec.go +++ b/dnssec.go @@ -250,14 +250,6 @@ func (d *DS) ToCDS() *CDS { // zero, it is used as-is, otherwise the TTL of the RRset is used as the // OrigTTL. func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error { - if k == nil { - return ErrPrivKey - } - // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set - if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { - return ErrKey - } - h0 := rrset[0].Header() rr.Hdr.Rrtype = TypeRRSIG rr.Hdr.Name = h0.Name @@ -272,6 +264,18 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error { rr.Labels-- // wildcard, remove from label count } + return rr.signAsIs(k, rrset) +} + +func (rr *RRSIG) signAsIs(k crypto.Signer, rrset []RR) error { + if k == nil { + return ErrPrivKey + } + // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set + if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + return ErrKey + } + sigwire := new(rrsigWireFmt) sigwire.TypeCovered = rr.TypeCovered sigwire.Algorithm = rr.Algorithm @@ -370,9 +374,12 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { if rr.Algorithm != k.Algorithm { return ErrKey } - if !strings.EqualFold(rr.SignerName, k.Hdr.Name) { + + signerName := CanonicalName(rr.SignerName) + if !equal(signerName, k.Hdr.Name) { return ErrKey } + if k.Protocol != 3 { return ErrKey } @@ -384,9 +391,18 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { } // 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 - // class matches the RRSIG record. - if h0 := rrset[0].Header(); h0.Class != rr.Hdr.Class || h0.Rrtype != rr.TypeCovered { + // the set have consistent type, class, and name. Also check that type, + // class and name matches the RRSIG record. + // Also checks RFC 4035 5.3.1 the number of labels in the RRset owner + // name MUST be greater than or equal to the value in the RRSIG RR's Labels field. + // RFC 4035 5.3.1 Signer's Name MUST be the name of the zone that [contains the RRset]. + // Since we don't have SOA info, checking suffix may be the best we can do...? + if h0 := rrset[0].Header(); h0.Class != rr.Hdr.Class || + h0.Rrtype != rr.TypeCovered || + uint8(CountLabel(h0.Name)) < rr.Labels || + !equal(h0.Name, rr.Hdr.Name) || + !strings.HasSuffix(CanonicalName(h0.Name), signerName) { + return ErrRRset } @@ -400,7 +416,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { sigwire.Expiration = rr.Expiration sigwire.Inception = rr.Inception sigwire.KeyTag = rr.KeyTag - sigwire.SignerName = CanonicalName(rr.SignerName) + sigwire.SignerName = signerName // Create the desired binary blob signeddata := make([]byte, DefaultMsgSize) n, err := packSigWire(sigwire, signeddata) diff --git a/dnssec_test.go b/dnssec_test.go index 1511278c0..4f04e85f6 100644 --- a/dnssec_test.go +++ b/dnssec_test.go @@ -155,6 +155,117 @@ func TestSignVerify(t *testing.T) { } } +// Test if RRSIG.Verify() conforms to RFC 4035 Section 5.3.1 +func TestShouldNotVerifyInvalidSig(t *testing.T) { + // The RRSIG RR and the RRset MUST have the same owner name + rrNameMismatch := getSoa() + rrNameMismatch.Hdr.Name = "example.com." + + // ... and the same class + rrClassMismatch := getSoa() + rrClassMismatch.Hdr.Class = ClassCHAOS + + // The RRSIG RR's Type Covered field MUST equal the RRset's type. + rrTypeMismatch := getSoa() + rrTypeMismatch.Hdr.Rrtype = TypeA + + // The number of labels in the RRset owner name MUST be greater than + // or equal to the value in the RRSIG RR's Labels field. + rrLabelLessThan := getSoa() + rrLabelLessThan.Hdr.Name = "nl." + + // Time checks are done in ValidityPeriod + + // With this key + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + privkey, _ := key.Generate(512) + + normalSoa := getSoa() + + // Fill in the normal values of the Sig, before signing + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.TypeCovered = TypeSOA + sig.Labels = uint8(CountLabel(normalSoa.Hdr.Name)) + sig.OrigTtl = normalSoa.Hdr.Ttl + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.KeyTag = key.KeyTag() // Get the keyfrom the Key + sig.SignerName = key.Hdr.Name + sig.Algorithm = RSASHA256 + + for i, rr := range []RR{rrNameMismatch, rrClassMismatch, rrTypeMismatch, rrLabelLessThan} { + if i != 0 { // Just for the rrNameMismatch case, we need the name to mismatch + sig := sig.copy().(*RRSIG) + sig.SignerName = rr.Header().Name + sig.Hdr.Name = rr.Header().Name + key := key.copy().(*DNSKEY) + key.Hdr.Name = rr.Header().Name + } + + if err := sig.signAsIs(privkey.(*rsa.PrivateKey), []RR{rr}); err != nil { + t.Error("failure to sign the record:", err) + continue + } + + if err := sig.Verify(key, []RR{rr}); err == nil { + t.Error("should not validate: ", rr) + continue + } else { + t.Logf("expected failure: %v for RR name %s, class %d, type %d, rrsig labels %d", err, rr.Header().Name, rr.Header().Class, rr.Header().Rrtype, CountLabel(rr.Header().Name)) + } + } + + // The RRSIG RR's Signer's Name field MUST be the name of the zone that contains the RRset. + // The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner name, + // algorithm, and key tag for some DNSKEY RR in the zone's apex DNSKEY RRset. + sigMismatchName := sig.copy().(*RRSIG) + sigMismatchName.SignerName = "example.com." + soaMismatchName := getSoa() + soaMismatchName.Hdr.Name = "example.com." + keyMismatchName := key.copy().(*DNSKEY) + keyMismatchName.Hdr.Name = "example.com." + if err := sigMismatchName.signAsIs(privkey.(*rsa.PrivateKey), []RR{soaMismatchName}); err != nil { + t.Error("failure to sign the record:", err) + } else if err := sigMismatchName.Verify(keyMismatchName, []RR{soaMismatchName}); err == nil { + t.Error("should not validate: ", soaMismatchName, ", RRSIG's signer's name does not match the owner name") + } else { + t.Logf("expected failure: %v for signer %s and owner %s", err, sigMismatchName.SignerName, sigMismatchName.Hdr.Name) + } + + sigMismatchAlgo := sig.copy().(*RRSIG) + sigMismatchAlgo.Algorithm = RSASHA1 + sigMismatchKeyTag := sig.copy().(*RRSIG) + sigMismatchKeyTag.KeyTag = 12345 + for _, sigMismatch := range []*RRSIG{sigMismatchAlgo, sigMismatchKeyTag} { + if err := sigMismatch.Sign(privkey.(*rsa.PrivateKey), []RR{normalSoa}); err != nil { + t.Error("failure to sign the record:", err) + } else if err := sigMismatch.Verify(key, []RR{normalSoa}); err == nil { + t.Error("should not validate: ", normalSoa) + } else { + t.Logf("expected failure: %v for signer %s algo %d keytag %d", err, sigMismatch.SignerName, sigMismatch.Algorithm, sigMismatch.KeyTag) + } + } + + // The matching DNSKEY RR MUST have the Zone Flag bit (DNSKEY RDATA Flag bit 7) set. + keyZoneBitWrong := key.copy().(*DNSKEY) + keyZoneBitWrong.Flags = key.Flags &^ ZONE + if err := sig.Sign(privkey.(*rsa.PrivateKey), []RR{normalSoa}); err != nil { + t.Error("failure to sign the record:", err) + } else if err := sig.Verify(keyZoneBitWrong, []RR{normalSoa}); err == nil { + t.Error("should not validate: ", normalSoa) + } else { + t.Logf("expected failure: %v for key flags %d", err, keyZoneBitWrong.Flags) + } +} + func Test65534(t *testing.T) { t6 := new(RFC3597) t6.Hdr = RR_Header{"miek.nl.", 65534, ClassINET, 14400, 0} diff --git a/edns.go b/edns.go index c1bbdaae2..91793b906 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. @@ -58,7 +59,7 @@ func makeDataOpt(code uint16) EDNS0 { case EDNS0EDE: return new(EDNS0_EDE) case EDNS0ESU: - return &EDNS0_ESU{Code: EDNS0ESU} + return new(EDNS0_ESU) default: e := new(EDNS0_LOCAL) e.Code = code @@ -66,8 +67,7 @@ func makeDataOpt(code uint16) EDNS0 { } } -// OPT is the EDNS0 RR appended to messages to convey extra (meta) information. -// See RFC 6891. +// OPT is the EDNS0 RR appended to messages to convey extra (meta) information. See RFC 6891. type OPT struct { Hdr RR_Header Option []EDNS0 `dns:"opt"` @@ -76,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:; " } @@ -144,8 +148,6 @@ func (*OPT) parse(c *zlexer, origin string) *ParseError { func (rr *OPT) isDuplicate(r2 RR) bool { return false } -// return the old value -> delete SetVersion? - // Version returns the EDNS version used. Only zero is defined. func (rr *OPT) Version() uint8 { return uint8(rr.Hdr.Ttl & 0x00FF0000 >> 16) @@ -198,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. @@ -236,8 +258,8 @@ type EDNS0 interface { // e.Nsid = "AA" // o.Option = append(o.Option, e) type EDNS0_NSID struct { - Code uint16 // Always EDNS0NSID - Nsid string // This string needs to be hex encoded + Code uint16 // always EDNS0NSID + Nsid string // string needs to be hex encoded } func (e *EDNS0_NSID) pack() ([]byte, error) { @@ -275,7 +297,7 @@ func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid} // When packing it will apply SourceNetmask. If you need more advanced logic, // patches welcome and good luck. type EDNS0_SUBNET struct { - Code uint16 // Always EDNS0SUBNET + Code uint16 // always EDNS0SUBNET Family uint16 // 1 for IP, 2 for IP6 SourceNetmask uint8 SourceScope uint8 @@ -399,8 +421,8 @@ func (e *EDNS0_SUBNET) copy() EDNS0 { // // There is no guarantee that the Cookie string has a specific length. type EDNS0_COOKIE struct { - Code uint16 // Always EDNS0COOKIE - Cookie string // Hex-encoded cookie data + Code uint16 // always EDNS0COOKIE + Cookie string // hex encoded cookie data } func (e *EDNS0_COOKIE) pack() ([]byte, error) { @@ -430,7 +452,7 @@ func (e *EDNS0_COOKIE) copy() EDNS0 { return &EDNS0_COOKIE{e.Code, e.C // e.Lease = 120 // in seconds // o.Option = append(o.Option, e) type EDNS0_UL struct { - Code uint16 // Always EDNS0UL + Code uint16 // always EDNS0UL Lease uint32 KeyLease uint32 } @@ -469,7 +491,7 @@ func (e *EDNS0_UL) unpack(b []byte) error { // EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 // Implemented for completeness, as the EDNS0 type code is assigned. type EDNS0_LLQ struct { - Code uint16 // Always EDNS0LLQ + Code uint16 // always EDNS0LLQ Version uint16 Opcode uint16 Error uint16 @@ -515,7 +537,7 @@ func (e *EDNS0_LLQ) copy() EDNS0 { // EDNS0_DAU implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975. type EDNS0_DAU struct { - Code uint16 // Always EDNS0DAU + Code uint16 // always EDNS0DAU AlgCode []uint8 } @@ -539,7 +561,7 @@ func (e *EDNS0_DAU) copy() EDNS0 { return &EDNS0_DAU{e.Code, e.AlgCode} } // EDNS0_DHU implements the EDNS0 "DS Hash Understood" option. See RFC 6975. type EDNS0_DHU struct { - Code uint16 // Always EDNS0DHU + Code uint16 // always EDNS0DHU AlgCode []uint8 } @@ -563,7 +585,7 @@ func (e *EDNS0_DHU) copy() EDNS0 { return &EDNS0_DHU{e.Code, e.AlgCode} } // EDNS0_N3U implements the EDNS0 "NSEC3 Hash Understood" option. See RFC 6975. type EDNS0_N3U struct { - Code uint16 // Always EDNS0N3U + Code uint16 // always EDNS0N3U AlgCode []uint8 } @@ -588,7 +610,7 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } // EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314. type EDNS0_EXPIRE struct { - Code uint16 // Always EDNS0EXPIRE + Code uint16 // always EDNS0EXPIRE Expire uint32 Empty bool // Empty is used to signal an empty Expire option in a backwards compatible way, it's not used on the wire. } @@ -668,7 +690,7 @@ func (e *EDNS0_LOCAL) unpack(b []byte) error { // EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep // the TCP connection alive. See RFC 7828. type EDNS0_TCP_KEEPALIVE struct { - Code uint16 // Always EDNSTCPKEEPALIVE + Code uint16 // always EDNSTCPKEEPALIVE // Timeout is an idle timeout value for the TCP connection, specified in // units of 100 milliseconds, encoded in network byte order. If set to 0, @@ -839,13 +861,12 @@ func (e *EDNS0_EDE) unpack(b []byte) error { return nil } -// The EDNS0_ESU option for ENUM Source-URI Extension +// The EDNS0_ESU option for ENUM Source-URI Extension. type EDNS0_ESU struct { - Code uint16 + Code uint16 // always EDNS0ESU Uri string } -// Option implements the EDNS0 interface. func (e *EDNS0_ESU) Option() uint16 { return EDNS0ESU } func (e *EDNS0_ESU) String() string { return e.Uri } func (e *EDNS0_ESU) copy() EDNS0 { return &EDNS0_ESU{e.Code, e.Uri} } diff --git a/edns_test.go b/edns_test.go index b7c15f7e4..b9853dd2d 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()) diff --git a/go.mod b/go.mod index cb9c336d1..184179726 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,14 @@ module github.com/miekg/dns -go 1.19 +go 1.23.0 + +toolchain go1.24.2 require ( - golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 - golang.org/x/sys v0.22.0 - golang.org/x/tools v0.22.0 + golang.org/x/net v0.39.0 + golang.org/x/sync v0.13.0 + golang.org/x/sys v0.32.0 + golang.org/x/tools v0.32.0 ) -require golang.org/x/mod v0.18.0 // indirect +require golang.org/x/mod v0.24.0 // indirect diff --git a/go.sum b/go.sum index c835f72e0..681bb01ee 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,22 @@ -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= diff --git a/listen_no_reuseport.go b/listen_no_socket_options.go similarity index 61% rename from listen_no_reuseport.go rename to listen_no_socket_options.go index 8cebb2f17..9e4010bdc 100644 --- a/listen_no_reuseport.go +++ b/listen_no_socket_options.go @@ -3,9 +3,15 @@ package dns -import "net" +import ( + "fmt" + "net" +) -const supportsReusePort = false +const ( + supportsReusePort = false + supportsReuseAddr = false +) func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) { if reuseport || reuseaddr { @@ -15,8 +21,6 @@ func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, e return net.Listen(network, addr) } -const supportsReuseAddr = false - func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) { if reuseport || reuseaddr { // TODO(tmthrgd): return an error? @@ -24,3 +28,13 @@ func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, return net.ListenPacket(network, addr) } + +// this is just for test compatibility +func checkReuseport(fd uintptr) (bool, error) { + return false, fmt.Errorf("not supported") +} + +// this is just for test compatibility +func checkReuseaddr(fd uintptr) (bool, error) { + return false, fmt.Errorf("not supported") +} diff --git a/listen_reuseport.go b/listen_socket_options.go similarity index 66% rename from listen_reuseport.go rename to listen_socket_options.go index 41326f20b..35dfc9498 100644 --- a/listen_reuseport.go +++ b/listen_socket_options.go @@ -39,10 +39,40 @@ func reuseaddrControl(network, address string, c syscall.RawConn) error { return opErr } +func reuseaddrandportControl(network, address string, c syscall.RawConn) error { + err := reuseaddrControl(network, address, c) + if err != nil { + return err + } + + return reuseportControl(network, address, c) +} + +// this is just for test compatibility +func checkReuseport(fd uintptr) (bool, error) { + v, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT) + if err != nil { + return false, err + } + + return v == 1, nil +} + +// this is just for test compatibility +func checkReuseaddr(fd uintptr) (bool, error) { + v, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR) + if err != nil { + return false, err + } + + return v == 1, nil +} + func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) { var lc net.ListenConfig switch { case reuseaddr && reuseport: + lc.Control = reuseaddrandportControl case reuseport: lc.Control = reuseportControl case reuseaddr: @@ -56,6 +86,7 @@ func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, var lc net.ListenConfig switch { case reuseaddr && reuseport: + lc.Control = reuseaddrandportControl case reuseport: lc.Control = reuseportControl case reuseaddr: diff --git a/msg.go b/msg.go index 5fa7f9e83..d87b5323b 100644 --- a/msg.go +++ b/msg.go @@ -136,18 +136,19 @@ var OpcodeToString = map[int]string{ // RcodeToString maps Rcodes to strings. var RcodeToString = map[int]string{ - RcodeSuccess: "NOERROR", - RcodeFormatError: "FORMERR", - RcodeServerFailure: "SERVFAIL", - RcodeNameError: "NXDOMAIN", - RcodeNotImplemented: "NOTIMP", - RcodeRefused: "REFUSED", - RcodeYXDomain: "YXDOMAIN", // See RFC 2136 - RcodeYXRrset: "YXRRSET", - RcodeNXRrset: "NXRRSET", - RcodeNotAuth: "NOTAUTH", - RcodeNotZone: "NOTZONE", - RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 + RcodeSuccess: "NOERROR", + RcodeFormatError: "FORMERR", + RcodeServerFailure: "SERVFAIL", + RcodeNameError: "NXDOMAIN", + RcodeNotImplemented: "NOTIMP", + RcodeRefused: "REFUSED", + RcodeYXDomain: "YXDOMAIN", // See RFC 2136 + RcodeYXRrset: "YXRRSET", + RcodeNXRrset: "NXRRSET", + RcodeNotAuth: "NOTAUTH", + RcodeNotZone: "NOTZONE", + RcodeStatefulTypeNotImplemented: "DSOTYPENI", + RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 // RcodeBadVers: "BADVERS", RcodeBadKey: "BADKEY", RcodeBadTime: "BADTIME", @@ -874,7 +875,6 @@ func (dns *Msg) unpack(dh Header, msg []byte, off int) (err error) { // // println("dns: extra bytes in dns packet", off, "<", len(msg)) // } return err - } // Unpack unpacks a binary message to a Msg structure. diff --git a/reverse.go b/reverse.go index 28151af83..6f5b3ea70 100644 --- a/reverse.go +++ b/reverse.go @@ -23,9 +23,12 @@ var StringToAlgorithm = reverseInt8(AlgorithmToString) // StringToHash is a map of names to hash IDs. var StringToHash = reverseInt8(HashToString) -// StringToCertType is the reverseof CertTypeToString. +// StringToCertType is the reverse of CertTypeToString. var StringToCertType = reverseInt16(CertTypeToString) +// StringToStatefulType is the reverse of StatefulTypeToString. +var StringToStatefulType = reverseInt16(StatefulTypeToString) + // Reverse a map func reverseInt8(m map[uint8]string) map[string]uint8 { n := make(map[string]uint8, len(m)) diff --git a/scan.go b/scan.go index e26e8027a..31957b2ea 100644 --- a/scan.go +++ b/scan.go @@ -108,6 +108,8 @@ type ttlState struct { // origin for resolving relative domain names defaults to the DNS root (.). // Full zone file syntax is supported, including directives like $TTL and $ORIGIN. // All fields of the returned RR are set from the read data, except RR.Header().Rdlength which is set to 0. +// Is you need a partial resource record with no rdata - for instance - for dynamic updates, see the [ANY] +// documentation. func NewRR(s string) (RR, error) { if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline return ReadRR(strings.NewReader(s+"\n"), "") @@ -1316,6 +1318,13 @@ func toAbsoluteName(name, origin string) (absolute string, ok bool) { return origin, true } + // this can happen when we have a comment after a RR that has a domain, '... MX 20 ; this is wrong'. + // technically a newline can be in a domain name, but this is clearly an error and the newline only shows + // because of the scanning and the comment. + if name == "\n" { + return "", false + } + // require a valid domain name _, ok = IsDomainName(name) if !ok || name == "" { diff --git a/scan_rr.go b/scan_rr.go index c1a76995e..ac885f66f 100644 --- a/scan_rr.go +++ b/scan_rr.go @@ -1620,6 +1620,16 @@ func (rr *NINFO) parse(c *zlexer, o string) *ParseError { return nil } +// Uses the same format as TXT +func (rr *RESINFO) parse(c *zlexer, o string) *ParseError { + s, e := endingToTxtSlice(c, "bad RESINFO Resinfo") + if e != nil { + return e + } + rr.Txt = s + return nil +} + func (rr *URI) parse(c *zlexer, o string) *ParseError { l, _ := c.Next() i, e := strconv.ParseUint(l.token, 10, 16) diff --git a/scan_test.go b/scan_test.go index c4f7e7f4a..e46b3684e 100644 --- a/scan_test.go +++ b/scan_test.go @@ -226,6 +226,22 @@ func TestZoneParserAddressAAAA(t *testing.T) { } } +func TestZoneParserTargetBad(t *testing.T) { + records := []string{ + "bad.example.org. CNAME ; bad cname", + "bad.example.org. HTTPS 10 ; bad https", + "bad.example.org. MX 10 ; bad mx", + "bad.example.org. SRV 1 0 80 ; bad srv", + } + + for _, record := range records { + const expect = "bad " + if got, err := NewRR(record); err == nil || !strings.Contains(err.Error(), expect) { + t.Errorf("NewRR(%v) = %v, want err to contain %q", record, got, expect) + } + } +} + func TestZoneParserAddressBad(t *testing.T) { records := []string{ "1.bad.example.org. 600 IN A ::1", diff --git a/server.go b/server.go index 52b851315..b516d7107 100644 --- a/server.go +++ b/server.go @@ -226,6 +226,7 @@ type Server struct { // If NotifyStartedFunc is set it is called once the server has started listening. NotifyStartedFunc func() // DecorateReader is optional, allows customization of the process that reads raw DNS messages. + // The decorated reader must not mutate the data read from the conn. DecorateReader DecorateReader // DecorateWriter is optional, allows customization of the process that writes raw DNS messages. DecorateWriter DecorateWriter diff --git a/server_test.go b/server_test.go index 4fc2af329..a9e2339b4 100644 --- a/server_test.go +++ b/server_test.go @@ -996,6 +996,101 @@ func TestServerStartStopRace(t *testing.T) { wg.Wait() } +func TestSocketOptions(t *testing.T) { + if !supportsReuseAddr || !supportsReusePort { + t.Skip("reuseaddr or reuseport is not supported") + } + + testSocketOptions := func(t *testing.T, reuseAddr bool, reusePort bool) { + wait := make(chan struct{}) + + srv := &Server{ + Net: "udp", + Addr: ":0", + ReuseAddr: reuseAddr, + ReusePort: reusePort, + } + + srv.NotifyStartedFunc = func() { + defer close(wait) + + conn, ok := srv.PacketConn.(*net.UDPConn) + if !ok { + t.Errorf("unexpected conn type: %T", srv.PacketConn) + return + } + + syscallConn, err := conn.SyscallConn() + if err != nil { + t.Errorf("cannot cast UDP conn to syscall conn: %v", err) + return + + } + + err = syscallConn.Control(func(fd uintptr) { + actualReusePort, err := checkReuseport(fd) + if err != nil { + t.Errorf("cannot get SO_REUSEPORT socket option: %v", err) + return + } + + if actualReusePort != reusePort { + t.Errorf("SO_REUSEPORT is %v instead of %v", actualReusePort, reusePort) + } + + actualReuseAddr, err := checkReuseaddr(fd) + if err != nil { + t.Errorf("cannot get SO_REUSEADDR socket option: %v", err) + return + } + + if actualReuseAddr != reuseAddr { + t.Errorf("SO_REUSEADDR is %v instead of %v", actualReuseAddr, reusePort) + } + }) + if err != nil { + t.Errorf("cannot check socket options: %v", err) + } + } + + fin := make(chan error, 1) + go func() { + fin <- srv.ListenAndServe() + }() + + select { + case <-wait: + err := srv.Shutdown() + if err != nil { + t.Fatalf("cannot shutdown server: %v", err) + } + + err = <-fin + if err != nil { + t.Fatalf("listen adn serve: %v", err) + } + case err := <-fin: + t.Fatalf("listen adn serve: %v", err) + } + } + + t.Run("no socket options", func(t *testing.T) { + testSocketOptions(t, false, false) + }) + + t.Run("SO_REUSEPORT", func(t *testing.T) { + testSocketOptions(t, false, true) + }) + + t.Run("SO_REUSEADDR", func(t *testing.T) { + testSocketOptions(t, true, false) + }) + + t.Run("SO_REUSEADDR and SO_REUSEPORT", func(t *testing.T) { + testSocketOptions(t, true, true) + }) +} + func TestServerReuseport(t *testing.T) { if !supportsReusePort { t.Skip("reuseport is not supported") @@ -1329,7 +1424,6 @@ func TestResponseWriteSinglePacket(t *testing.T) { m.SetQuestion("miek.nl.", TypeTXT) m.Response = true err := rw.WriteMsg(m) - if err != nil { t.Fatalf("failed to write: %v", err) } diff --git a/sig0.go b/sig0.go index 2c4b10352..057bb5787 100644 --- a/sig0.go +++ b/sig0.go @@ -7,7 +7,6 @@ import ( "crypto/rsa" "encoding/binary" "math/big" - "strings" "time" ) @@ -151,7 +150,7 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error { } // If key has come from the DNS name compression might // have mangled the case of the name - if !strings.EqualFold(signername, k.Header().Name) { + if !equal(signername, k.Header().Name) { return &Error{err: "signer name doesn't match key name"} } sigend := offset diff --git a/svcb.go b/svcb.go index 310c7d11f..d1baeea99 100644 --- a/svcb.go +++ b/svcb.go @@ -214,11 +214,7 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue { } } -// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-08). -// -// NOTE: The HTTPS/SVCB RFCs are in the draft stage. -// The API, including constants and types related to SVCBKeyValues, may -// change in future versions in accordance with the latest drafts. +// SVCB RR. See RFC 9460. type SVCB struct { Hdr RR_Header Priority uint16 // If zero, Value must be empty or discarded by the user of this library @@ -226,12 +222,8 @@ type SVCB struct { Value []SVCBKeyValue `dns:"pairs"` } -// HTTPS RR. Everything valid for SVCB applies to HTTPS as well. +// HTTPS RR. See RFC 9460. Everything valid for SVCB applies to HTTPS as well. // Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols. -// -// NOTE: The HTTPS/SVCB RFCs are in the draft stage. -// The API, including constants and types related to SVCBKeyValues, may -// change in future versions in accordance with the latest drafts. type HTTPS struct { SVCB } diff --git a/types.go b/types.go index 7a34c14ca..f5067cd43 100644 --- a/types.go +++ b/types.go @@ -101,6 +101,7 @@ const ( TypeCAA uint16 = 257 TypeAVC uint16 = 258 TypeAMTRELAY uint16 = 260 + TypeRESINFO uint16 = 261 TypeTKEY uint16 = 249 TypeTSIG uint16 = 250 @@ -125,33 +126,35 @@ const ( ClassANY = 255 // Message Response Codes, see https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml - RcodeSuccess = 0 // NoError - No Error [DNS] - RcodeFormatError = 1 // FormErr - Format Error [DNS] - RcodeServerFailure = 2 // ServFail - Server Failure [DNS] - RcodeNameError = 3 // NXDomain - Non-Existent Domain [DNS] - RcodeNotImplemented = 4 // NotImp - Not Implemented [DNS] - RcodeRefused = 5 // Refused - Query Refused [DNS] - RcodeYXDomain = 6 // YXDomain - Name Exists when it should not [DNS Update] - RcodeYXRrset = 7 // YXRRSet - RR Set Exists when it should not [DNS Update] - RcodeNXRrset = 8 // NXRRSet - RR Set that should exist does not [DNS Update] - RcodeNotAuth = 9 // NotAuth - Server Not Authoritative for zone [DNS Update] - RcodeNotZone = 10 // NotZone - Name not contained in zone [DNS Update/TSIG] - RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3 - RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3 - RcodeBadKey = 17 // BADKEY - Key not recognized [TSIG] - RcodeBadTime = 18 // BADTIME - Signature out of time window [TSIG] - RcodeBadMode = 19 // BADMODE - Bad TKEY Mode [TKEY] - RcodeBadName = 20 // BADNAME - Duplicate key name [TKEY] - RcodeBadAlg = 21 // BADALG - Algorithm not supported [TKEY] - RcodeBadTrunc = 22 // BADTRUNC - Bad Truncation [TSIG] - RcodeBadCookie = 23 // BADCOOKIE - Bad/missing Server Cookie [DNS Cookies] + RcodeSuccess = 0 // NoError - No Error [DNS] + RcodeFormatError = 1 // FormErr - Format Error [DNS] + RcodeServerFailure = 2 // ServFail - Server Failure [DNS] + RcodeNameError = 3 // NXDomain - Non-Existent Domain [DNS] + RcodeNotImplemented = 4 // NotImp - Not Implemented [DNS] + RcodeRefused = 5 // Refused - Query Refused [DNS] + RcodeYXDomain = 6 // YXDomain - Name Exists when it should not [DNS Update] + RcodeYXRrset = 7 // YXRRSet - RR Set Exists when it should not [DNS Update] + RcodeNXRrset = 8 // NXRRSet - RR Set that should exist does not [DNS Update] + RcodeNotAuth = 9 // NotAuth - Server Not Authoritative for zone [DNS Update] + RcodeNotZone = 10 // NotZone - Name not contained in zone [DNS Update/TSIG] + RcodeStatefulTypeNotImplemented = 11 // DSOTypeNI - DSO-TYPE not implemented [DNS Stateful Operations] https://www.rfc-editor.org/rfc/rfc8490.html#section-10.2 + RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3 + RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3 + RcodeBadKey = 17 // BADKEY - Key not recognized [TSIG] + RcodeBadTime = 18 // BADTIME - Signature out of time window [TSIG] + RcodeBadMode = 19 // BADMODE - Bad TKEY Mode [TKEY] + RcodeBadName = 20 // BADNAME - Duplicate key name [TKEY] + RcodeBadAlg = 21 // BADALG - Algorithm not supported [TKEY] + RcodeBadTrunc = 22 // BADTRUNC - Bad Truncation [TSIG] + RcodeBadCookie = 23 // BADCOOKIE - Bad/missing Server Cookie [DNS Cookies] // Message Opcodes. There is no 3. - OpcodeQuery = 0 - OpcodeIQuery = 1 - OpcodeStatus = 2 - OpcodeNotify = 4 - OpcodeUpdate = 5 + OpcodeQuery = 0 + OpcodeIQuery = 1 + OpcodeStatus = 2 + OpcodeNotify = 4 + OpcodeUpdate = 5 + OpcodeStateful = 6 ) // Used in ZONEMD https://tools.ietf.org/html/rfc8976 @@ -178,6 +181,19 @@ const ( AMTRELAYHost = IPSECGatewayHost ) +// Stateful types as defined in RFC 8490. +const ( + StatefulTypeKeepAlive uint16 = iota + 1 + StatefulTypeRetryDelay + StatefulTypeEncryptionPadding +) + +var StatefulTypeToString = map[uint16]string{ + StatefulTypeKeepAlive: "KeepAlive", + StatefulTypeRetryDelay: "RetryDelay", + StatefulTypeEncryptionPadding: "EncryptionPadding", +} + // Header is the wire format for the DNS packet header. type Header struct { Id uint16 @@ -267,11 +283,20 @@ func (q *Question) String() (s string) { return s } -// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY -// is named "*" there. +// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY is named "*" there. +// The ANY records can be (ab)used to create resource records without any rdata, that +// can be used in dynamic update requests. Basic use pattern: +// +// a := &ANY{RR_Header{ +// Name: "example.org.", +// Rrtype: TypeA, +// Class: ClassINET, +// }} +// +// Results in an A record without rdata. type ANY struct { Hdr RR_Header - // Does not have any rdata + // Does not have any rdata. } func (rr *ANY) String() string { return rr.Hdr.String() } @@ -876,7 +901,7 @@ func (rr *LOC) String() string { lon = lon % LOC_HOURS s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lon)/1000, ew) - var alt = float64(rr.Altitude) / 100 + alt := float64(rr.Altitude) / 100 alt -= LOC_ALTITUDEBASE if rr.Altitude%100 != 0 { s += fmt.Sprintf("%.2fm ", alt) @@ -1508,6 +1533,15 @@ func (rr *ZONEMD) String() string { " " + rr.Digest } +// RESINFO RR. See RFC 9606. + +type RESINFO struct { + Hdr RR_Header + Txt []string `dns:"txt"` +} + +func (rr *RESINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } + // APL RR. See RFC 3123. type APL struct { Hdr RR_Header diff --git a/udp.go b/udp.go index f57c33f1d..d22671859 100644 --- a/udp.go +++ b/udp.go @@ -1,4 +1,5 @@ //go:build !windows && !darwin +// +build !windows,!darwin package dns diff --git a/udp_darwin.go b/udp_darwin.go index 8b7ba728a..1ea3cf952 100644 --- a/udp_darwin.go +++ b/udp_darwin.go @@ -1,4 +1,5 @@ //go:build darwin +// +build darwin package dns diff --git a/udp_windows.go b/udp_no_control.go similarity index 88% rename from udp_windows.go rename to udp_no_control.go index b3f290251..e71f49d5b 100644 --- a/udp_windows.go +++ b/udp_no_control.go @@ -1,8 +1,11 @@ //go:build windows +// +build windows // TODO(tmthrgd): Remove this Windows-specific code if go.dev/issue/7175 and // go.dev/issue/7174 are ever fixed. +// NOTICE(stek29): darwin supports PKTINFO in sendmsg, but it unbinds sockets, see https://github.com/miekg/dns/issues/724 + package dns import "net" diff --git a/update.go b/update.go index 16f9ee85a..2fef1461f 100644 --- a/update.go +++ b/update.go @@ -2,6 +2,7 @@ package dns // NameUsed sets the RRs in the prereq section to // "Name is in use" RRs. RFC 2136 section 2.4.4. +// See [ANY] on how to make RRs without rdata. func (u *Msg) NameUsed(rr []RR) { if u.Answer == nil { u.Answer = make([]RR, 0, len(rr)) @@ -41,6 +42,7 @@ func (u *Msg) Used(rr []RR) { // RRsetUsed sets the RRs in the prereq section to // "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. +// See [ANY] on how to make RRs without rdata. func (u *Msg) RRsetUsed(rr []RR) { if u.Answer == nil { u.Answer = make([]RR, 0, len(rr)) @@ -53,6 +55,7 @@ func (u *Msg) RRsetUsed(rr []RR) { // RRsetNotUsed sets the RRs in the prereq section to // "RRset does not exist" RRs. RFC 2136 section 2.4.3. +// See [ANY] on how to make RRs without rdata. func (u *Msg) RRsetNotUsed(rr []RR) { if u.Answer == nil { u.Answer = make([]RR, 0, len(rr)) @@ -64,6 +67,7 @@ func (u *Msg) RRsetNotUsed(rr []RR) { } // Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. +// See [ANY] on how to make RRs without rdata. func (u *Msg) Insert(rr []RR) { if len(u.Question) == 0 { panic("dns: empty question section") @@ -78,6 +82,7 @@ func (u *Msg) Insert(rr []RR) { } // RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. +// See [ANY] on how to make RRs without rdata. func (u *Msg) RemoveRRset(rr []RR) { if u.Ns == nil { u.Ns = make([]RR, 0, len(rr)) @@ -89,6 +94,7 @@ func (u *Msg) RemoveRRset(rr []RR) { } // RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 +// See [ANY] on how to make RRs without rdata. func (u *Msg) RemoveName(rr []RR) { if u.Ns == nil { u.Ns = make([]RR, 0, len(rr)) @@ -99,6 +105,7 @@ func (u *Msg) RemoveName(rr []RR) { } // Remove creates a dynamic update packet deletes RR from a RRSset, see RFC 2136 section 2.5.4 +// See [ANY] on how to make RRs without rdata. func (u *Msg) Remove(rr []RR) { if u.Ns == nil { u.Ns = make([]RR, 0, len(rr)) diff --git a/version.go b/version.go index 00c8629f2..936dc2124 100644 --- a/version.go +++ b/version.go @@ -3,7 +3,7 @@ package dns import "fmt" // Version is current version of this library. -var Version = v{1, 1, 62} +var Version = v{1, 1, 66} // v holds the version of this library. type v struct { diff --git a/xfr.go b/xfr.go index 5cfbb516a..97a642471 100644 --- a/xfr.go +++ b/xfr.go @@ -251,10 +251,13 @@ func (t *Transfer) ReadMsg() (*Msg, error) { if err := m.Unpack(p); err != nil { return nil, err } - if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil { + + if tp := t.tsigProvider(); tp != nil { // Need to work on the original message p, as that was used to calculate the tsig. err = TsigVerifyWithProvider(p, tp, t.tsigRequestMAC, t.tsigTimersOnly) - t.tsigRequestMAC = ts.MAC + if ts := m.IsTsig(); ts != nil { + t.tsigRequestMAC = ts.MAC + } } return m, err } diff --git a/xfr_test.go b/xfr_test.go index 04801a2ec..c580ceef7 100644 --- a/xfr_test.go +++ b/xfr_test.go @@ -2,6 +2,7 @@ package dns import ( "crypto/tls" + "errors" "testing" "time" ) @@ -221,6 +222,29 @@ func axfrTestingSuiteWithCustomTsig(t *testing.T, addrstr string, provider TsigP } } +func axfrTestingSuiteWithMsgNotSigned(t *testing.T, addrstr string, provider TsigProvider) { + tr := new(Transfer) + m := new(Msg) + var err error + tr.Conn, err = Dial("tcp", addrstr) + if err != nil { + t.Fatal("failed to dial", err) + } + tr.TsigProvider = provider + m.SetAxfr("miek.nl.") + + c, err := tr.In(m, addrstr) + if err != nil { + t.Fatal("failed to zone transfer in", err) + } + + for msg := range c { + if !errors.Is(msg.Error, ErrNoSig) { + t.Fatal("expecting ErrNoSig error") + } + } +} + func TestCustomTsigProvider(t *testing.T) { HandleFunc("miek.nl.", SingleEnvelopeXfrServer) defer HandleRemove("miek.nl.") @@ -235,3 +259,16 @@ func TestCustomTsigProvider(t *testing.T) { axfrTestingSuiteWithCustomTsig(t, addrstr, tsigSecretProvider(tsigSecret)) } + +func TestTSIGNotSigned(t *testing.T) { + HandleFunc("miek.nl.", SingleEnvelopeXfrServer) + defer HandleRemove("miek.nl.") + + s, addrstr, _, err := RunLocalTCPServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %s", err) + } + defer s.Shutdown() + + axfrTestingSuiteWithMsgNotSigned(t, addrstr, tsigSecretProvider(tsigSecret)) +} diff --git a/zduplicate.go b/zduplicate.go index 330c05395..ebd9e0297 100644 --- a/zduplicate.go +++ b/zduplicate.go @@ -957,6 +957,23 @@ func (r1 *PX) isDuplicate(_r2 RR) bool { return true } +func (r1 *RESINFO) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RESINFO) + if !ok { + return false + } + _ = r2 + if len(r1.Txt) != len(r2.Txt) { + return false + } + for i := 0; i < len(r1.Txt); i++ { + if r1.Txt[i] != r2.Txt[i] { + return false + } + } + return true +} + func (r1 *RFC3597) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*RFC3597) if !ok { diff --git a/zmsg.go b/zmsg.go index 5a6cf4c6a..cc09810fb 100644 --- a/zmsg.go +++ b/zmsg.go @@ -762,6 +762,14 @@ func (rr *PX) pack(msg []byte, off int, compression compressionMap, compress boo return off, nil } +func (rr *RESINFO) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringTxt(rr.Txt, msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *RFC3597) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { off, err = packStringHex(rr.Rdata, msg, off) if err != nil { @@ -2353,6 +2361,17 @@ func (rr *PX) unpack(msg []byte, off int) (off1 int, err error) { return off, nil } +func (rr *RESINFO) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Txt, off, err = unpackStringTxt(msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *RFC3597) unpack(msg []byte, off int) (off1 int, err error) { rdStart := off _ = rdStart diff --git a/ztypes.go b/ztypes.go index 11f13ecf9..cea79ae77 100644 --- a/ztypes.go +++ b/ztypes.go @@ -66,6 +66,7 @@ var TypeToRR = map[uint16]func() RR{ TypeOPT: func() RR { return new(OPT) }, TypePTR: func() RR { return new(PTR) }, TypePX: func() RR { return new(PX) }, + TypeRESINFO: func() RR { return new(RESINFO) }, TypeRKEY: func() RR { return new(RKEY) }, TypeRP: func() RR { return new(RP) }, TypeRRSIG: func() RR { return new(RRSIG) }, @@ -154,6 +155,7 @@ var TypeToString = map[uint16]string{ TypeOPT: "OPT", TypePTR: "PTR", TypePX: "PX", + TypeRESINFO: "RESINFO", TypeRKEY: "RKEY", TypeRP: "RP", TypeRRSIG: "RRSIG", @@ -238,6 +240,7 @@ func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr } func (rr *OPT) Header() *RR_Header { return &rr.Hdr } func (rr *PTR) Header() *RR_Header { return &rr.Hdr } func (rr *PX) Header() *RR_Header { return &rr.Hdr } +func (rr *RESINFO) Header() *RR_Header { return &rr.Hdr } func (rr *RFC3597) Header() *RR_Header { return &rr.Hdr } func (rr *RKEY) Header() *RR_Header { return &rr.Hdr } func (rr *RP) Header() *RR_Header { return &rr.Hdr } @@ -622,6 +625,14 @@ func (rr *PX) len(off int, compression map[string]struct{}) int { return l } +func (rr *RESINFO) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Txt { + l += len(x) + 1 + } + return l +} + func (rr *RFC3597) len(off int, compression map[string]struct{}) int { l := rr.Hdr.len(off, compression) l += len(rr.Rdata) / 2 @@ -1148,6 +1159,10 @@ func (rr *PX) copy() RR { } } +func (rr *RESINFO) copy() RR { + return &RESINFO{rr.Hdr, cloneSlice(rr.Txt)} +} + func (rr *RFC3597) copy() RR { return &RFC3597{rr.Hdr, rr.Rdata} }