Skip to content

Commit 8da9c26

Browse files
authored
Add files via upload
1 parent fd09ccd commit 8da9c26

2 files changed

Lines changed: 103 additions & 20 deletions

File tree

PyWWW/dns.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ def main():
687687
description="DNS Query Script: strict TXID validation + IPv6 + dig/nslookup styles"
688688
)
689689
parser.add_argument('--dns-server', type=str,
690-
help='DNS server address (IPv4, IPv6, IPv6%zone, or hostname)')
690+
help='DNS server address (IPv4, IPv6, IPv6%%zone, or hostname)')
691691
parser.add_argument('--domain', type=str, help='Domain to look up (e.g., example.com)')
692692
parser.add_argument('--record-type', type=str, default='A',
693693
help='DNS record type (A, AAAA, MX, CNAME, NS, TXT)')

PyWWW/dnsd.py

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -418,49 +418,83 @@ def exchange_with_tc_fallback(server_ip, wire_query, timeout=2, port=53):
418418
# -----------------------------
419419
# Iterative resolution (with bailiwick glue)
420420
# -----------------------------
421-
def iterative_resolve(qname, qtype, timeout=2, max_steps=25, edns_size=None, do=False):
421+
def iterative_resolve(qname, qtype, timeout=2, max_steps=25, edns_size=None, do=False, overall_timeout=4.0):
422422
"""
423423
Returns a DNS response message (bytes) from the last server contacted.
424424
Follows referrals iteratively from the root.
425+
426+
Changes:
427+
- Enforces an overall deadline (overall_timeout seconds total)
428+
- Stops early on terminal responses:
429+
* RCODE != 0 (e.g., NXDOMAIN)
430+
* SOA in authority with no answers (NODATA / negative)
431+
- For resolving NS hostnames, tries A before AAAA (IPv4 first)
432+
- Shares overall timeout into recursive NS-name lookups (prevents runaway time)
425433
"""
434+
deadline = time.time() + float(overall_timeout)
435+
426436
next_servers = list(ROOT_SERVERS)
427437
random.shuffle(next_servers)
428438
last_msg = None
429439

430440
for _step in range(max_steps):
431441
if not next_servers:
432442
break
443+
if time.time() >= deadline:
444+
break
433445

434446
server = next_servers.pop(0)
435447

436448
_tid, wire = build_query(qname, qtype, rd=False, edns_size=edns_size, do=do)
449+
450+
# cap per-try timeout by remaining budget
451+
remaining = deadline - time.time()
452+
if remaining <= 0:
453+
break
454+
per_try = min(float(timeout), max(0.05, remaining))
455+
437456
try:
438-
resp = exchange_with_tc_fallback(server, wire, timeout=timeout, port=53)
457+
resp = exchange_with_tc_fallback(server, wire, timeout=per_try, port=53)
439458
except Exception:
440459
continue
441460

442461
last_msg = resp
443-
parsed = parse_sections(resp)
444462

445-
# If got answers, done
463+
try:
464+
parsed = parse_sections(resp)
465+
except Exception:
466+
# if parsing fails, try next server
467+
continue
468+
469+
# ---- Terminal conditions ----
470+
# Any upstream error (NXDOMAIN, SERVFAIL, etc.)
471+
if parsed["rcode"] != 0:
472+
return resp
473+
474+
# Got direct answers
446475
if parsed["answers"]:
447476
return resp
448477

449-
# Referral: authority NS + additional glue
478+
# Authoritative negative (NODATA): SOA in authority
479+
if any(rr["type"] == QTYPE["SOA"] for rr in parsed["authority"]):
480+
return resp
481+
482+
# ---- Referral processing: authority NS + additional glue ----
450483
ns_names = []
451-
bailiwick_zone = None
452484
for rr in parsed["authority"]:
453485
if rr["type"] == QTYPE["NS"]:
454-
bailiwick_zone = rr["name"] # delegation zone
455486
nsn, _ = decode_name(resp, rr["rdata_off"])
456-
ns_names.append(nsn)
487+
ns_names.append(_dnsname_norm(nsn))
457488

489+
# Prefer glue that matches the referred NS hostnames (even if out-of-bailiwick)
458490
glue_ips = []
459-
if bailiwick_zone:
491+
if ns_names:
492+
ns_set = set(ns_names)
460493
for rr in parsed["additional"]:
461494
if rr["type"] not in (QTYPE["A"], QTYPE["AAAA"]):
462495
continue
463-
if not _is_in_bailiwick(rr.get("name"), bailiwick_zone):
496+
owner = _dnsname_norm(rr.get("name"))
497+
if owner not in ns_set:
464498
continue
465499
ip = rr_ip_from_additional(rr)
466500
if ip:
@@ -471,29 +505,52 @@ def iterative_resolve(qname, qtype, timeout=2, max_steps=25, edns_size=None, do=
471505
next_servers = glue_ips + next_servers
472506
continue
473507

474-
# No usable glue: resolve NS hostnames (AAAA first, then A)
508+
# ---- No usable glue: resolve NS hostnames ----
475509
if ns_names:
476510
random.shuffle(ns_names)
477511
resolved_ns_ips = []
478512

513+
# try a few NS names
479514
for nsn in ns_names[:3]:
480515
resolved_ns_ips = []
481-
for qt in (QTYPE["AAAA"], QTYPE["A"]):
516+
517+
# IMPORTANT CHANGE: prefer IPv4 first, then IPv6
518+
for qt in (QTYPE["A"], QTYPE["AAAA"]):
519+
# Keep NS-name lookups cheaper + share overall budget
520+
remaining = deadline - time.time()
521+
if remaining <= 0:
522+
break
523+
482524
ns_resp = iterative_resolve(
483-
nsn, qt, timeout=timeout, max_steps=max_steps,
484-
edns_size=edns_size, do=do
525+
nsn, qt,
526+
timeout=min(float(timeout), 0.5),
527+
max_steps=10,
528+
edns_size=edns_size,
529+
do=do,
530+
overall_timeout=max(0.1, remaining)
485531
)
486532
if not ns_resp:
487533
continue
488-
ns_parsed = parse_sections(ns_resp)
534+
535+
try:
536+
ns_parsed = parse_sections(ns_resp)
537+
except Exception:
538+
continue
539+
540+
# If NS-name lookup got an error, ignore and try next
541+
if ns_parsed["rcode"] != 0:
542+
continue
543+
489544
for rr in ns_parsed["answers"]:
490545
if rr["type"] == QTYPE["A"] and rr["rdlen"] == 4:
491546
b = bytearray(rr["rdata"])
492547
resolved_ns_ips.append("%d.%d.%d.%d" % (b[0], b[1], b[2], b[3]))
493548
elif rr["type"] == QTYPE["AAAA"] and rr["rdlen"] == 16:
494549
resolved_ns_ips.append(socket.inet_ntop(socket.AF_INET6, rr["rdata"]))
550+
495551
if resolved_ns_ips:
496552
break
553+
497554
if resolved_ns_ips:
498555
break
499556

@@ -502,6 +559,7 @@ def iterative_resolve(qname, qtype, timeout=2, max_steps=25, edns_size=None, do=
502559
next_servers = resolved_ns_ips + next_servers
503560
continue
504561

562+
# nothing else to try; return what we got
505563
return resp
506564

507565
return last_msg
@@ -635,20 +693,45 @@ def min_ttl_from_answers(resp_msg):
635693
return 30
636694

637695

696+
def _query_question_wire(query_msg):
697+
"""
698+
Return the raw Question section (QNAME/QTYPE/QCLASS) from a client QUERY message.
699+
This excludes any EDNS OPT or other additional records that may be present.
700+
"""
701+
_tid, _flags, qd, _an, _ns, _ar, _tc, _rcode = parse_header(query_msg)
702+
off = 12
703+
off2 = skip_questions(query_msg, off, qd)
704+
return query_msg[12:off2]
705+
706+
638707
def make_servfail(query_wire):
708+
"""
709+
Return a minimal SERVFAIL response that echoes the Question section only.
710+
Avoids dig's 'extra bytes at end' warning.
711+
"""
639712
if len(query_wire) < 12:
713+
# minimal fallback (rare)
640714
return b"\x00\x00" + struct.pack("!H", 0x8002) + b"\x00\x01\x00\x00\x00\x00\x00\x00"
715+
641716
tid = query_wire[:2]
642-
hdr = tid + struct.pack("!H", 0x8002) + struct.pack("!HHHH", 1, 0, 0, 0)
643-
return hdr + query_wire[12:]
717+
flags = 0x8000 | 0x0002 # QR=1, RCODE=2 (SERVFAIL)
718+
qwire = _query_question_wire(query_wire)
719+
hdr = tid + struct.pack("!H", flags) + struct.pack("!HHHH", 1, 0, 0, 0)
720+
return hdr + qwire
644721

645722

646723
def make_nxdomain(query_wire):
724+
"""
725+
Return a minimal NXDOMAIN response that echoes the Question section only.
726+
"""
647727
if len(query_wire) < 12:
648728
return b"\x00\x00" + struct.pack("!H", 0x8003) + b"\x00\x01\x00\x00\x00\x00\x00\x00"
729+
649730
tid = query_wire[:2]
650-
hdr = tid + struct.pack("!H", 0x8003) + struct.pack("!HHHH", 1, 0, 0, 0)
651-
return hdr + query_wire[12:]
731+
flags = 0x8000 | 0x0003 # QR=1, RCODE=3 (NXDOMAIN)
732+
qwire = _query_question_wire(query_wire)
733+
hdr = tid + struct.pack("!H", flags) + struct.pack("!HHHH", 1, 0, 0, 0)
734+
return hdr + qwire
652735

653736

654737
def _rewrite_response_for_client(resp, client_tid_bytes, client_query_flags):

0 commit comments

Comments
 (0)