From fc8b0bfffd89da4c24cd81b3581c073476ee159d Mon Sep 17 00:00:00 2001 From: Franck Zoccolo Date: Wed, 5 Aug 2026 11:01:18 +0200 Subject: [PATCH] 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. --- proxy.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/proxy.py b/proxy.py index 7d32907..536a7ea 100644 --- a/proxy.py +++ b/proxy.py @@ -14,8 +14,15 @@ domain = sys.argv[1] port = int(sys.argv[2]) ttl = 120 timeout = 2 +negative_ttl = 10 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): def __init__(self): self.zeroconf = Zeroconf(ip_version=IPVersion.All) @@ -28,7 +35,7 @@ class DynamicResolver(object): def _doDynamicResponse(self, query): if query.type == dns.SOA: - return defer.succeed(([], [], [])) + return defer.succeed(([soa_record()], [], [])) localname = str(query.name)[:-len(domain)] + "local." @@ -50,18 +57,18 @@ class DynamicResolver(object): #txt_ans, _, _ = txt(service) #srv_ans, _, a_ans = srv(service) #additional += a_ans + txt_ans + srv_ans - return answers, [], additional + return answers, ([] if answers else [soa_record()]), additional def txt(localname): if localname.endswith('._device-info._tcp.local.'): info = ServiceInfo(localname, localname) info.request(self.zeroconf, timeout*1000) if not info.text: - return [], [], [] + return [], [soa_record()], [] else: info = self.zeroconf.get_service_info(localname, localname, timeout*1000) if info is None: - return [], [], [] + return [], [soa_record()], [] order = [] i = 0 @@ -81,8 +88,8 @@ class DynamicResolver(object): def srv(localname): info = self.zeroconf.get_service_info(localname, localname, timeout*1000) if info is None: - return [], [], [] - + return [], [soa_record()], [] + 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 ))] @@ -128,7 +135,7 @@ class DynamicResolver(object): addr )) for addr in l.addrs] - return answers, [], [] + return answers, ([] if answers else [soa_record()]), [] d = defer.Deferred() @@ -146,7 +153,7 @@ class DynamicResolver(object): return d else: print("Unsupported request", query) - d.callback(([], [], [])) + d.callback(([], [soa_record()], [])) return d def query(self, query, timeout=None):