Add negative response handling (RFC 2308)

Synthesize an SOA record for the zone, return it for direct SOA
queries, and include it in the authority section of empty responses
so resolvers can negative-cache properly. RCODE stays NOERROR rather
than NXDOMAIN, since mDNS can't reliably prove non-existence.
This commit is contained in:
2026-08-05 11:01:18 +02:00
parent be2477d326
commit fc8b0bfffd
+14 -7
View File
@@ -14,8 +14,15 @@ domain = sys.argv[1]
port = int(sys.argv[2]) port = int(sys.argv[2])
ttl = 120 ttl = 120
timeout = 2 timeout = 2
negative_ttl = 10
ipv6_ula_network = ipaddress.ip_network('fc00::/7') ipv6_ula_network = ipaddress.ip_network('fc00::/7')
def soa_record():
return dns.RRHeader(name=domain, type=dns.SOA, ttl=negative_ttl, payload=dns.Record_SOA(
mname=domain, rname="hostmaster." + domain,
serial=1, refresh=1200, retry=180, expire=1209600, minimum=negative_ttl
))
class DynamicResolver(object): class DynamicResolver(object):
def __init__(self): def __init__(self):
self.zeroconf = Zeroconf(ip_version=IPVersion.All) self.zeroconf = Zeroconf(ip_version=IPVersion.All)
@@ -28,7 +35,7 @@ class DynamicResolver(object):
def _doDynamicResponse(self, query): def _doDynamicResponse(self, query):
if query.type == dns.SOA: if query.type == dns.SOA:
return defer.succeed(([], [], [])) return defer.succeed(([soa_record()], [], []))
localname = str(query.name)[:-len(domain)] + "local." localname = str(query.name)[:-len(domain)] + "local."
@@ -50,18 +57,18 @@ class DynamicResolver(object):
#txt_ans, _, _ = txt(service) #txt_ans, _, _ = txt(service)
#srv_ans, _, a_ans = srv(service) #srv_ans, _, a_ans = srv(service)
#additional += a_ans + txt_ans + srv_ans #additional += a_ans + txt_ans + srv_ans
return answers, [], additional return answers, ([] if answers else [soa_record()]), additional
def txt(localname): def txt(localname):
if localname.endswith('._device-info._tcp.local.'): if localname.endswith('._device-info._tcp.local.'):
info = ServiceInfo(localname, localname) info = ServiceInfo(localname, localname)
info.request(self.zeroconf, timeout*1000) info.request(self.zeroconf, timeout*1000)
if not info.text: if not info.text:
return [], [], [] return [], [soa_record()], []
else: else:
info = self.zeroconf.get_service_info(localname, localname, timeout*1000) info = self.zeroconf.get_service_info(localname, localname, timeout*1000)
if info is None: if info is None:
return [], [], [] return [], [soa_record()], []
order = [] order = []
i = 0 i = 0
@@ -81,7 +88,7 @@ class DynamicResolver(object):
def srv(localname): def srv(localname):
info = self.zeroconf.get_service_info(localname, localname, timeout*1000) info = self.zeroconf.get_service_info(localname, localname, timeout*1000)
if info is None: if info is None:
return [], [], [] return [], [soa_record()], []
answers = [dns.RRHeader(name=localname[:-6] + domain, ttl=ttl, type=dns.SRV, payload=dns.Record_SRV( answers = [dns.RRHeader(name=localname[:-6] + domain, ttl=ttl, type=dns.SRV, payload=dns.Record_SRV(
info.priority, info.weight, info.port, info.server[:-6] + domain info.priority, info.weight, info.port, info.server[:-6] + domain
@@ -128,7 +135,7 @@ class DynamicResolver(object):
addr addr
)) for addr in l.addrs] )) for addr in l.addrs]
return answers, [], [] return answers, ([] if answers else [soa_record()]), []
d = defer.Deferred() d = defer.Deferred()
@@ -146,7 +153,7 @@ class DynamicResolver(object):
return d return d
else: else:
print("Unsupported request", query) print("Unsupported request", query)
d.callback(([], [], [])) d.callback(([], [soa_record()], []))
return d return d
def query(self, query, timeout=None): def query(self, query, timeout=None):