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)
>       }
> }

Attachment: signature.asc
Description: PGP signature

_______________________________________________
DNSOP mailing list
[email protected]
https://www.ietf.org/mailman/listinfo/dnsop

Reply via email to