Make the guide gateway IPv4-only to stop IPv6 stalls

Serve only A/ANY queries; answer AAAA and all other record types with NOERROR and
no data, for overrides and forwarded names alike. The console LAN has no IPv6
route, so returning a real AAAA made clients stall on an unreachable address.

This fixes the PS5 Payload Manager looping on "synchronizing repositories": its
dual-stack catalog host itsplk.github.io handed back an IPv6 address and the
manager's libcurl download (no timeout) hung on it indefinitely.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-10-11 15:02:22 +01:00
co-authored by Claude Opus 4.8
parent 58d9b0adaf
commit 4c22f8addf
2 changed files with 34 additions and 16 deletions
+23 -16
View File
@@ -218,30 +218,37 @@ def dns_response(packet, use_tcp=False):
return struct.pack("!HHHHHH", transaction_id, response_flags | 1, 0, 0, 0, 0)
question = packet[12:question_end]
is_exception = match_any(name, EXCEPTIONS)
address = None if is_exception else override_for(name)
# Default-deny: a name matching no rule returns NXDOMAIN.
if not is_exception and address is None:
return struct.pack("!HHHHHH", transaction_id, response_flags | 3, 1, 0, 0, 0) + question
noerror_nodata = struct.pack("!HHHHHH", transaction_id, response_flags, 1, 0, 0, 0) + question
# IPv4-only resolver. Only A (and ANY) queries are served; AAAA and any other
# record type get NOERROR with no data. The console LAN has no IPv6 route, so
# handing back an AAAA record would make clients stall on an unreachable IPv6
# address -- this is what left the PS5 Payload Manager looping on "synchronizing
# repositories" for the dual-stack itsplk.github.io host.
if qclass != 1 or qtype not in (1, 255):
return noerror_nodata
# Exceptions bypass overrides and are forwarded upstream (nanoDNS semantics).
if match_any(name, EXCEPTIONS):
if is_exception:
try:
return forward_query(packet, use_tcp=use_tcp)
except (OSError, ValueError) as error:
print(f"DNS upstream failure for {name}: {error}", flush=True)
return error_response(packet, 2) # SERVFAIL
address = override_for(name)
if address is None:
# Default-deny: a name matching no rule returns NXDOMAIN.
return struct.pack("!HHHHHH", transaction_id, response_flags | 3, 1, 0, 0, 0) + question
if qclass == 1 and qtype in (1, 255):
answer = (
b"\xc0\x0c"
+ struct.pack("!HHIH", 1, 1, 60, 4)
+ address.packed
)
return struct.pack("!HHHHHH", transaction_id, response_flags, 1, 1, 0, 0) + question + answer
# NOERROR with no data prevents IPv6 or unsupported record fallback.
return struct.pack("!HHHHHH", transaction_id, response_flags, 1, 0, 0, 0) + question
answer = (
b"\xc0\x0c"
+ struct.pack("!HHIH", 1, 1, 60, 4)
+ address.packed
)
return struct.pack("!HHHHHH", transaction_id, response_flags, 1, 1, 0, 0) + question + answer
class ThreadingUDPServer(socketserver.ThreadingUDPServer):