d825c17e285cdfa3b2d721cd0946911ea8917b51
[apachelog.git] / apachelog / resolve.py
1 import re as _re
2 import socket as _socket
3
4
5 class Resolver (object):
6     """A simple reverse-DNS resolver.
7
8     Maintains a class-level cache of resolved IPs to avoid repeated
9     lookups on the same IP address.
10
11     Avoid hanging if we can't resolve a name.
12
13     >>> import socket
14     >>> if hasattr(_socket, 'setdefaulttimeout'):
15     ...     socket.setdefaulttimeout(5)  # set 5 second timeout
16
17     >>> r = Resolver()
18     >>> r.IP = {}  # clear cache of date from previous tests
19     >>> r.resolve('198.41.0.4')
20     'a.root-servers.net'
21     >>> r.IP
22     {'198.41.0.4': ('a.root-servers.net', [], ['198.41.0.4'])}
23
24     If you want to give shorter names to various DNS names, you can
25     add an entry to the class-level ``REGEXPS``.  The entry should use
26     your name as the key, and a list of matching regexps as the value.
27     You need to enable this enhanced resolution using the ``smart``
28     argument.
29
30     >>> r.resolve('66.249.68.33')
31     'crawl-66-249-68-33.googlebot.com'
32     >>> r = Resolver(smart=True)
33     >>> r.resolve('66.249.68.34')
34     'googlebot'
35     """
36     IP = {}
37
38     REGEXPS = {
39         'feedburner': [_re.compile('.*rate-limited-proxy-.*.google.com.*')],
40         }
41     for bot in [
42         'googlebot',
43         'yandex',
44         'baiduspider',
45         'msnbot',
46         ]:
47         REGEXPS[bot] = [_re.compile('.*{}.*'.format(bot))]
48
49     def __init__(self, smart=False):
50         self._smart = smart
51
52     def resolve(self, ip):
53         if ip not in self.IP:
54             try:
55                 self.IP[ip] = _socket.gethostbyaddr(ip)
56             except _socket.herror as e:
57                 self.IP[ip] = (ip, [], [ip])
58             except _socket.gaierror as e:
59                 self.IP[ip] = (ip, [], [ip])
60             else:
61                 if self._smart:
62                     self._smart_resolve(ip)
63         return self.IP[ip][0]
64
65     def _smart_resolve(self, ip):
66         x = self.IP[ip]
67         if self._smart:
68             for name,regexps in self.REGEXPS.items():
69                 for regexp in regexps:
70                     if regexp.match(self.IP[ip][0]):
71                         self.IP[ip] = (name, x[1], x[2])
72
73     def ips(self, name):
74         "Return a set of IP addresses used by a smart-resolved name."
75         ips = set()
76         for ip,values in self.IP.items():
77             if values[0] == name:
78                 for x in values[2]:
79                     ips.add(x)
80         return ips