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)
        }
}
_______________________________________________
DNSOP mailing list
[email protected]
https://www.ietf.org/mailman/listinfo/dnsop

Reply via email to