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