1 # ldd - DNS implementation in Python
2 # Copyright (C) 2006 Fredrik Tolf <fredrik@dolda2000.com>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27 class error(Exception):
28 def __init__(self, text):
34 class servfail(error):
36 error.__init__(self, "SERVFAIL")
38 class unreachable(error):
39 def __init__(self, server):
40 error.__init__(self, "could not reach server: " + str(server))
42 def resolvecnames(pkt, res = None):
43 if res is None: res = default
45 cnrr = pkt.getanswer(q.name, rec.rtypebyname("CNAME"))
47 if pkt.getanswer(cnrr.data["priname"], q.rtype) is None:
49 resp = res.squery(cnrr.data["priname"], q.rtype)
54 anrr = resp.getanswer(cnrr.data["priname"], q.rtype)
59 def resolveadditional(pkt, rr, res = None):
60 if res is None: res = default
62 if isinstance(rr.data[name], dn.domainname):
63 for rtype in ["A", "AAAA"]:
64 if pkt.getanswer(rr.data[name], rtype) is not None:
67 resp = res.squery(rr.data[name], rtype)
72 anrr = resp.getanswer(rr.data[name], rtype)
77 def extractaddrinfo(packet, name):
79 for rr in packet.anlist + packet.adlist:
80 if rr.head.name == name:
81 if rr.head.istype("A"):
82 ret += [(socket.AF_INET, socket.inet_ntop(socket.AF_INET, rr.data["address"]))]
83 elif rr.head.istype("AAAA"):
84 ret += [(socket.AF_INET6, socket.inet_ntop(socket.AF_INET6, rr.data["address"]))]
87 def resolve(packet, nameserver, recurse, retries = 3, timeout = 2000, hops = 0, cnameres = None, verbose = False, visited = None):
88 if cnameres is None: cnameres = default
89 if visited is None: visited = set()
90 visited |= set([nameserver])
91 sk = socket.socket(nameserver[0], socket.SOCK_DGRAM)
93 for i in range(retries):
94 sk.sendto(packet.encode(), nameserver[1:])
96 p.register(sk.fileno(), select.POLLIN)
98 if (sk.fileno(), select.POLLIN) in fds:
101 raise unreachable(nameserver)
105 resp = proto.decodepacket(ret)
106 except proto.malformedpacket, inst:
107 raise error(str(inst))
108 if resp.qid != packet.qid:
109 raise error("got response with wrong qid(?!)")
110 if "resp" not in resp.flags:
111 raise error("got query in response")
112 if resp.rescode != 0:
113 if resp.rescode == proto.SERVFAIL:
115 if resp.rescode == proto.NXDOMAIN:
117 raise error("non-successful response (" + str(resp.rescode) + ")")
119 resolvecnames(resp, cnameres)
120 if not recurse or resp.hasanswers():
122 if not resp.hasanswers() and "auth" in resp.flags:
125 raise error("too many levels deep")
126 for rr in resp.aulist:
128 print (hops * " ") + "Checking " + str(rr)
129 if rr.head.istype("NS"):
131 print (hops * " ") + "Will try " + str(rr)
132 ai = extractaddrinfo(resp, rr.data["nsname"])
135 print (hops * " ") + "Resolving nameservers for " + str(rr.data["nsname"])
136 resolveadditional(resp, rr)
137 ai = extractaddrinfo(resp, rr.data["nsname"])
142 print (hops * " ") + "Will not try " + str(ns) + " again"
145 print (hops * " ") + "Trying " + str(ns)
147 resp2 = resolve(packet, ns, recurse, retries, timeout, hops + 1, verbose = verbose, visited = visited)
150 print (hops * " ") + "Could not reach " + str(ns)
154 print (hops * " ") + "Got None"
156 if "auth" in resp2.flags:
160 print (hops * " ") + "Got " + str(resp2.hasanswers()) + " (" + austr + ")"
161 if resp2 is not None and resp2.hasanswers():
163 if resp2 is not None and not resp2.hasanswers() and "auth" in resp2.flags:
168 def __init__(self, nameserver, recurse, nsrecurse = True, retries = 3, timeout = 2000, verbose = False):
169 self.nameserver = nameserver
170 self.recurse = recurse
171 self.nsrecurse = nsrecurse
172 self.retries = retries
173 self.timeout = timeout
174 self.verbose = verbose
176 def resolve(self, packet):
177 return resolve(packet, self.nameserver, self.recurse, self.retries, self.timeout, verbose = self.verbose)
179 def squery(self, name, rtype):
180 packet = proto.packet()
182 if self.nsrecurse: packet.setflags(["recurse"])
183 except AttributeError: pass
184 packet.addq(rec.rrhead(name, rtype))
185 return self.resolve(packet)
187 class multiresolver(resolver):
188 def __init__(self, resolvers):
189 self.rl = [{"res": res, "qs": []} for res in resolvers]
190 self.lastclean = int(time.time())
193 now = int(time.time())
194 if now - self.lastclean < 60:
200 if now - q["time"] < 1800:
204 def resolve(self, packet):
212 score = float(sum([q["s"] for q in r["qs"]])) / len(r["qs"])
215 c = random.random() * ts
223 res = r["res"].resolve(packet)
225 r["qs"] = r["qs"][:10] + [{"time": int(time.time()), "s": 0}]
227 r["qs"] = r["qs"][:10] + [{"time": int(time.time()), "s": 1}]
230 class sysresolver(resolver):
231 def __init__(self, conffile = "/etc/resolv.conf"):
234 a = open(conffile, "r")
235 for line in (l.strip() for l in a):
240 if c == "nameserver":
242 socket.inet_pton(socket.AF_INET, line)
243 except socket.error: pass
245 nslist += [(socket.AF_INET, line, 53)]
247 socket.inet_pton(socket.AF_INET6, line)
248 except socket.error: pass
250 nslist += [(socket.AF_INET6, line, 53)]
251 if c == "domain" or c == "search": # How do these differ?
252 prelist += line.split()
256 rl += [resolver(ns, False, True)]
257 self.resolver = multiresolver(rl)
259 for prefix in prelist:
260 pp = dn.fromstring(prefix)
264 def resolve(self, packet):
265 res = self.resolver.resolve(packet)
268 def squery(self, name, rtype):
269 if type(name) == str:
270 name = dn.fromstring(name)
272 namelist = [name + prefix for prefix in self.prelist] + [name + dn.fromstring(".")]
275 for name in namelist:
276 packet = proto.packet()
277 packet.setflags(["recurse"])
278 packet.addq(rec.rrhead(name, rtype))
279 res = self.resolve(packet)
284 sysres = sysresolver()
285 rootresolvers = {"a": resolver((socket.AF_INET, "198.41.0.4", 53), True, False),
286 "b": resolver((socket.AF_INET, "192.228.79.201", 53), True, False),
287 "c": resolver((socket.AF_INET, "192.33.4.12", 53), True, False),
288 "d": resolver((socket.AF_INET, "128.8.10.90", 53), True, False),
289 "e": resolver((socket.AF_INET, "192.203.230.10", 53), True, False),
290 "f": resolver((socket.AF_INET, "192.5.5.241", 53), True, False),
291 "g": resolver((socket.AF_INET, "192.112.36.4", 53), True, False),
292 "h": resolver((socket.AF_INET, "128.63.2.53", 53), True, False),
293 "i": resolver((socket.AF_INET, "192.36.148.17", 53), True, False),
294 "j": resolver((socket.AF_INET, "192.58.128.30", 53), True, False),
295 "k": resolver((socket.AF_INET, "193.0.14.129", 53), True, False),
296 "l": resolver((socket.AF_INET, "198.32.64.12", 53), True, False),
297 "m": resolver((socket.AF_INET, "202.12.27.33", 53), True, False)
299 rootres = multiresolver(rootresolvers.values())