Hi Stephane. Thanks a lot for your implementations! I have a modified dig version with support for rrserial in: https://gitlab.isc.org/huguei/bind9/-/tree/rrserial
; <<>> DiG 9.17.14 <<>> @200.1.122.30 dateserial.example.com txt +rrserial +nsid
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34574
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; NSID: 63 6c 6e 73 64 74 65 73 74 ("clnsdtest")
; RRSERIAL: 78 49 7a 79 ("2018081401")
;; QUESTION SECTION:
;dateserial.example.com. IN TXT
;; ANSWER SECTION:
dateserial.example.com. 43200 IN TXT "Test zone for RRSERIAL record"
[ ... ]
Also, I can reproduce the bug with NXDOMAINs with the NSD
implementation :( Thanks for the notice, I'll look into it ASAP!
I plan to keep track to these prototypes and scripts in some wiki too.
Best,
Hugo
On 14:30 18/06, Stephane Bortzmeyer wrote:
> On Mon, Jun 14, 2021 at 10:03:22AM -0400,
> Hugo Salgado <[email protected]> wrote
> a message of 55 lines which said:
>
> > In the case of NXDOMAIN, the reason for not adding RRSERIAL is
> > because the response already has the SOA in the AUTHORITY, which
> > would make it redundant.
>
> OK, I see. Here are two implementations of the client part, in Python
> and in Go, using this algorithm (RRSERIAL if NOERROR or SERVFAIL, and
> the SOA record if NXDOMAIN).
>
> Python :
>
> % ./test-rrserial.py 200.1.122.30 dateserial.example.com TXT
> dateserial.example.com. 43200 IN TXT "Test zone for RRSERIAL record"
> Serial of the answer is 2018081401
>
> % ./test-rrserial.py 200.1.122.30 dateserial.example.com LOC
> No value for dateserial.example.com/LOC
> Serial found from SOA record: 2018081401
>
> Go :
>
> % ./test-rrserial 200.1.122.30 incserial.example.com MX
> incserial.example.com. 43200 IN MX 10
> mail.incserial.example.com.
> EDNS rrserial found, "1"
>
> % ./test-rrserial 200.1.122.30 incserial.example.com SSHFP
> Empty answer received
> EDNS rrserial found, "1"
>
> Note that you can do it with dig alone but the display of unknown data
> is less pretty:
>
> % dig +ednsopt=65024 @200.1.122.30 dateserial.example.com
> ...
> ;; OPT PSEUDOSECTION:
> ; EDNS: version: 0, flags: do; udp: 4096
> ; OPT=65024: 78 49 7a 79 ("xIzy")
>
> Note also there is a bug in your server: when the name does not exist,
> it sends incorrect messages. dig says "Message parser reports
> malformed message packet" and the Python library "DNS message is
> malformed". And the Go library:
>
> % ./test-rrserial 200.1.122.30 doesnotexist.incserial.example.com
> Error in query: dns: overflowing header size
> #!/usr/bin/env python3
>
> """Simple Python program to implement IETF draft
> draft-ietf-dnsop-rrserial (hereafter "the draft")
>
> If you don't know Python:
> pip install dnspython
> ./test-rrserial.py 200.1.122.30 dateserial.example.com
>
> """
>
> import struct
> import sys
>
> # DNSpython https://www.dnspython.org/ We probably require 2.0 or higher.
> import dns.message
> import dns.query
> import dns.edns
> import dns.rdatatype
>
> # Temporary value in the draft
> dns.edns.RRSERIAL = 65024
>
> def print_soa(response):
> found = False
> for rr in response.authority:
> if rr.rdtype == dns.rdatatype.SOA:
> found = True
> print("Serial found from SOA record: %s" % rr[0].serial)
> if not found:
> print("Negative answer without a SOA record :-(")
>
> def print_rrserial(response):
> found = False
> for opt in response.options:
> if opt.otype == dns.edns.RRSERIAL:
> found = True
> print("Serial of the answer is %s" % struct.unpack(">I", opt.data)[0])
> if not found:
> print("No EDNS serial option in answer")
>
> # TODO: it would probably be better (more pythonic) to inherit from
> # dns.edns.GenericOption and provide a parser.
> opts = [dns.edns.GenericOption(dns.edns.RRSERIAL, b'')]
>
> if len(sys.argv) != 3 and len(sys.argv) != 4:
> raise Exception("Usage: %s server qname qtype [for instance '200.1.122.30
> dateserial.example.com AAAA')" % sys.argv[0])
> server = sys.argv[1]
> qname = sys.argv[2]
> if len(sys.argv) == 4:
> qtype = dns.rdatatype.from_text(sys.argv[3])
> else:
> qtype = dns.rdatatype.AAAA
> message = dns.message.make_query(qname, qtype, options=opts)
> response = dns.query.udp(message, server)
> if response.rcode() == dns.rcode.NOERROR:
> data = False
> for rr in response.answer:
> if rr.rdtype == qtype:
> data = True
> print(rr)
> if not data:
> print("No value for %s/%s" % (qname, dns.rdatatype.to_text(qtype)))
> print_soa(response)
> else:
> print_rrserial(response)
> elif response.rcode() == dns.rcode.NXDOMAIN:
> print("%s not found" % qname)
> print_soa(response)
> elif response.rcode() == dns.rcode.SERVFAIL:
> print("%s failed" % qname)
> print_rrserial(response)
> else:
> print("Unknown return code %s" % response.rcode())
>
>
> // Test program to experiment the IETF draft draft-ietf-dnsop-rrserial
> (hereafter "the draft").
> // Depends on godns <https://miek.nl/2014/august/16/go-dns-package/>
> <https://github.com/miekg/dns>.
>
> // If you don't know Go:
> // go get github.com/miekg/dns
> // go build test-rrserial.go
>
> package main
>
> import (
> "encoding/binary"
> "fmt"
> "github.com/miekg/dns"
> "os"
> "strings"
> )
>
> const otype = 65024
>
> func print_rrserial(msg *dns.Msg) {
> opt := msg.IsEdns0()
> for _, v := range opt.Option {
> switch v := v.(type) {
> case *dns.EDNS0_LOCAL:
> if v.Option() == otype {
> serial := binary.BigEndian.Uint32(v.Data)
> fmt.Printf("EDNS rrserial found, \"%d\"\n",
> serial)
> }
> default:
> continue
> }
> }
> }
>
> func main() {
> if len(os.Args) != 3 && len(os.Args) != 4 {
> fmt.Printf("%s SERVER QNAME [QTYPE]\n", os.Args[0])
> os.Exit(1)
> }
> ns := os.Args[1]
> qname := dns.Fqdn(os.Args[2])
> qtype := dns.TypeAAAA
> ok := true
> if len(os.Args) == 4 {
> if qtype, ok = dns.StringToType[strings.ToUpper(os.Args[3])];
> ok {
> // Good value
> } else {
> fmt.Printf("%s is not a known record type\n",
> strings.ToUpper(os.Args[3]))
> os.Exit(1)
> }
> }
> m := new(dns.Msg)
> m.Question = make([]dns.Question, 1)
> c := new(dns.Client)
> o := new(dns.OPT)
> o.Hdr.Name = "."
> o.Hdr.Rrtype = dns.TypeOPT
> o.SetUDPSize(4096)
> e := new(dns.EDNS0_LOCAL)
> e.Code = otype
> e.Data = []byte{}
> o.Option = append(o.Option, e)
> m.Extra = append(m.Extra, o)
> m.Question[0] = dns.Question{qname, qtype, dns.ClassINET}
> in, _, err := c.Exchange(m, ns+":53")
> if err == nil && in != nil {
> if in.Rcode == dns.RcodeRefused {
> fmt.Printf("Query refused (may be %s is a resolver, we
> do not ask for recursion)\n", ns)
> os.Exit(1)
> } else if in.Rcode == dns.RcodeFormatError {
> fmt.Printf("%s claims our format is incorrect, it is
> wrong\n", ns)
> os.Exit(1)
> } else if in.Rcode == dns.RcodeNameError {
> fmt.Printf("%s not found\n", qname)
> gotSoa := false
> serial := uint32(0)
> for _, rsoa := range in.Ns {
> switch rsoa.(type) {
> case *dns.SOA:
> serial = rsoa.(*dns.SOA).Serial
> gotSoa = true
> default:
> continue
> }
> }
> if !gotSoa {
> fmt.Printf("No SOA in the answer :-(\n")
> os.Exit(1)
> } else {
> fmt.Printf("Serial number in SOA %d\n", serial)
> }
> os.Exit(0)
> } else if in.Rcode == dns.RcodeServerFailure {
> fmt.Printf("Server failure\n")
> print_rrserial(in)
> os.Exit(1)
> } else if in.Rcode != dns.RcodeSuccess {
> fmt.Printf("Wrong return code %d\n", in.Rcode)
> os.Exit(1)
> }
> if len(in.Answer) > 0 {
> for _, rec := range in.Answer {
> fmt.Printf("%s\n", rec)
> }
> } else if len(in.Answer) == 0 {
> fmt.Printf("Empty answer received\n")
> }
> print_rrserial(in)
> } else {
> if err != nil {
> fmt.Printf("Error in query: %s\n", err)
> } else if in == nil {
> fmt.Printf("No answer received\n")
> }
> os.Exit(1)
> }
> }
signature.asc
Description: PGP signature
_______________________________________________ DNSOP mailing list [email protected] https://www.ietf.org/mailman/listinfo/dnsop
