1919 from urllib import urlencode
2020
2121# -----------------------------
22- # Root server IPs (a subset)
22+ # Root server IPs
2323# -----------------------------
2424ROOT_SERVERS = [
2525 "198.41.0.4" , # a.root-servers.net
2626 "199.9.14.201" , # b.root-servers.net
2727 "192.33.4.12" , # c.root-servers.net
2828 "199.7.91.13" , # d.root-servers.net
2929 "192.203.230.10" , # e.root-servers.net
30+ "192.5.5.241" , # f.root-servers.net
31+ "192.112.36.4" , # g.root-servers.net
32+ "198.97.190.53" , # h.root-servers.net
33+ "192.36.148.17" , # i.root-servers.net
34+ "192.58.128.30" , # j.root-servers.net
35+ "193.0.14.129" , # k.root-servers.net
36+ "199.7.83.42" , # l.root-servers.net
37+ "202.12.27.33" , # m.root-servers.net
3038]
3139
3240QTYPE = {"A" : 1 , "NS" : 2 , "CNAME" : 5 , "MX" : 15 , "TXT" : 16 , "AAAA" : 28 }
@@ -217,16 +225,19 @@ def parse_sections(msg):
217225 }
218226
219227def rr_ip_from_additional (rr ):
220- # A glue only (AAAA glue requires IPv6 socket support; intentionally skipped)
221228 if rr ["type" ] == QTYPE ["A" ] and rr ["rdlen" ] == 4 :
222229 b = bytearray (rr ["rdata" ])
223230 return "%d.%d.%d.%d" % (b [0 ], b [1 ], b [2 ], b [3 ])
231+ if rr ["type" ] == QTYPE ["AAAA" ] and rr ["rdlen" ] == 16 :
232+ return socket .inet_ntop (socket .AF_INET6 , rr ["rdata" ])
224233 return None
225234
226235def udp_exchange (server_ip , wire_query , timeout = 2 , port = 53 ):
227- s = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
236+ family = socket .AF_INET6 if ":" in server_ip else socket .AF_INET
237+ s = socket .socket (family , socket .SOCK_DGRAM )
228238 s .settimeout (float (timeout ))
229239 try :
240+ # For AF_INET6, address tuple can be (ip, port) and Python fills flowinfo/scopeid
230241 s .sendto (wire_query , (server_ip , int (port )))
231242 data , _ = s .recvfrom (4096 )
232243 return data
@@ -279,21 +290,34 @@ def iterative_resolve(qname, qtype, timeout=2, max_steps=25):
279290 next_servers = glue_ips + next_servers
280291 continue
281292
282- # No glue: resolve NS name A using recursion of *this* iterative resolver
293+ # No glue: resolve NS name (try AAAA first, then A)
283294 if ns_names :
284295 random .shuffle (ns_names )
285296 resolved_ns_ips = []
297+
286298 for nsn in ns_names [:3 ]:
287- ns_resp = iterative_resolve (nsn , QTYPE ["A" ], timeout = timeout , max_steps = max_steps )
288- if not ns_resp :
289- continue
290- ns_parsed = parse_sections (ns_resp )
291- for a_rr in ns_parsed ["answers" ]:
292- if a_rr ["type" ] == QTYPE ["A" ] and a_rr ["rdlen" ] == 4 :
293- b = bytearray (a_rr ["rdata" ])
294- resolved_ns_ips .append ("%d.%d.%d.%d" % (b [0 ], b [1 ], b [2 ], b [3 ]))
299+ resolved_ns_ips = []
300+
301+ for qt in (QTYPE ["AAAA" ], QTYPE ["A" ]):
302+ ns_resp = iterative_resolve (nsn , qt , timeout = timeout , max_steps = max_steps )
303+ if not ns_resp :
304+ continue
305+
306+ ns_parsed = parse_sections (ns_resp )
307+
308+ for rr in ns_parsed ["answers" ]:
309+ if rr ["type" ] == QTYPE ["A" ] and rr ["rdlen" ] == 4 :
310+ b = bytearray (rr ["rdata" ])
311+ resolved_ns_ips .append ("%d.%d.%d.%d" % (b [0 ], b [1 ], b [2 ], b [3 ]))
312+ elif rr ["type" ] == QTYPE ["AAAA" ] and rr ["rdlen" ] == 16 :
313+ resolved_ns_ips .append (socket .inet_ntop (socket .AF_INET6 , rr ["rdata" ]))
314+
315+ if resolved_ns_ips :
316+ break # prefer AAAA if it worked
317+
295318 if resolved_ns_ips :
296- break
319+ break # first NS that yields IPs
320+
297321 if resolved_ns_ips :
298322 random .shuffle (resolved_ns_ips )
299323 next_servers = resolved_ns_ips + next_servers
@@ -397,8 +421,20 @@ def handle_query_wire(query_wire, client_addr=None):
397421 CACHE .put (key , resp , ttl = ttl )
398422 return resp
399423
424+ def _bind_family (bind_ip ):
425+ return socket .AF_INET6 if ":" in bind_ip else socket .AF_INET
426+
400427def udp_server (bind_ip = "127.0.0.1" , port = 5353 ):
401- s = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
428+ fam = _bind_family (bind_ip )
429+ s = socket .socket (fam , socket .SOCK_DGRAM )
430+
431+ # Optional dual-stack (may be ignored on Android)
432+ if fam == socket .AF_INET6 :
433+ try :
434+ s .setsockopt (socket .IPPROTO_IPV6 , socket .IPV6_V6ONLY , 0 )
435+ except Exception :
436+ pass
437+
402438 s .bind ((bind_ip , port ))
403439 print ("UDP DNS stub listening on %s:%d" % (bind_ip , port ))
404440 while True :
@@ -410,8 +446,17 @@ def udp_server(bind_ip="127.0.0.1", port=5353):
410446 s .sendto (resp , addr )
411447
412448def tcp_server (bind_ip = "127.0.0.1" , port = 5353 ):
413- ss = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
449+ fam = _bind_family (bind_ip )
450+ ss = socket .socket (fam , socket .SOCK_STREAM )
414451 ss .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
452+
453+ # Optional dual-stack (may be ignored on Android)
454+ if fam == socket .AF_INET6 :
455+ try :
456+ ss .setsockopt (socket .IPPROTO_IPV6 , socket .IPV6_V6ONLY , 0 )
457+ except Exception :
458+ pass
459+
415460 ss .bind ((bind_ip , port ))
416461 ss .listen (50 )
417462 print ("TCP DNS stub listening on %s:%d" % (bind_ip , port ))
@@ -489,7 +534,7 @@ def run_stub(bind_ip="127.0.0.1", port=5353):
489534
490535 args = parser .parse_args ()
491536
492- # Apply runtime config
537+ # Apply runtime config (top-level assignments; no 'global' needed)
493538 UPSTREAM_TIMEOUT = float (args .upstream_timeout )
494539 if args .timeout is not None :
495540 UPSTREAM_TIMEOUT = float (args .timeout )
0 commit comments