2 # Copyright 2010 Google Inc. All Rights Reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
24 from third_party
.dns
import flags
25 from third_party
.dns
import message
26 from third_party
.dns
import rcode
27 from third_party
.dns
import resolver
28 from third_party
.dns
import rdatatype
29 from third_party
import ipaddr
33 class DnsProxyException(Exception):
37 class RealDnsLookup(object):
38 def __init__(self
, name_servers
):
39 if '127.0.0.1' in name_servers
:
40 raise DnsProxyException(
41 'Invalid nameserver: 127.0.0.1 (causes an infinte loop)')
42 self
.resolver
= resolver
.get_default_resolver()
43 self
.resolver
.nameservers
= name_servers
44 self
.dns_cache_lock
= threading
.Lock()
48 def _IsIPAddress(hostname
):
50 socket
.inet_aton(hostname
)
55 def __call__(self
, hostname
, rdtype
=rdatatype
.A
):
56 """Return real IP for a host.
59 host: a hostname ending with a period (e.g. "www.google.com.")
60 rdtype: the query type (1 for 'A', 28 for 'AAAA')
62 the IP address as a string (e.g. "192.168.25.2")
64 if self
._IsIPAddress
(hostname
):
66 self
.dns_cache_lock
.acquire()
67 ip
= self
.dns_cache
.get(hostname
)
68 self
.dns_cache_lock
.release()
72 answers
= self
.resolver
.query(hostname
, rdtype
)
73 except resolver
.NXDOMAIN
:
75 except resolver
.NoNameservers
:
76 logging
.debug('_real_dns_lookup(%s) -> No nameserver.',
79 except (resolver
.NoAnswer
, resolver
.Timeout
) as ex
:
80 logging
.debug('_real_dns_lookup(%s) -> None (%s)',
81 hostname
, ex
.__class
__.__name
__)
85 self
.dns_cache_lock
.acquire()
86 self
.dns_cache
[hostname
] = ip
87 self
.dns_cache_lock
.release()
91 """Clear the dns cache."""
92 self
.dns_cache_lock
.acquire()
93 self
.dns_cache
.clear()
94 self
.dns_cache_lock
.release()
97 class ReplayDnsLookup(object):
98 """Resolve DNS requests to replay host."""
99 def __init__(self
, replay_ip
, filters
=None):
100 self
.replay_ip
= replay_ip
101 self
.filters
= filters
or []
103 def __call__(self
, hostname
):
105 for f
in self
.filters
:
106 ip
= f(hostname
, default_ip
=ip
)
110 class PrivateIpFilter(object):
111 """Resolve private hosts to their real IPs and others to the Web proxy IP.
113 Hosts in the given http_archive will resolve to the Web proxy IP without
114 checking the real IP.
116 This only supports IPv4 lookups.
118 def __init__(self
, real_dns_lookup
, http_archive
):
119 """Initialize PrivateIpDnsLookup.
122 real_dns_lookup: a function that resolves a host to an IP.
123 http_archive: an instance of a HttpArchive
124 Hosts is in the archive will always resolve to the web_proxy_ip
126 self
.real_dns_lookup
= real_dns_lookup
127 self
.http_archive
= http_archive
128 self
.InitializeArchiveHosts()
130 def __call__(self
, host
, default_ip
):
131 """Return real IPv4 for private hosts and Web proxy IP otherwise.
134 host: a hostname ending with a period (e.g. "www.google.com.")
136 IP address as a string or None (if lookup fails)
139 if host
not in self
.archive_hosts
:
140 real_ip
= self
.real_dns_lookup(host
)
142 if ipaddr
.IPAddress(real_ip
).is_private
:
148 def InitializeArchiveHosts(self
):
149 """Recompute the archive_hosts from the http_archive."""
150 self
.archive_hosts
= set('%s.' % req
.host
.split(':')[0]
151 for req
in self
.http_archive
)
154 class DelayFilter(object):
155 """Add a delay to replayed lookups."""
157 def __init__(self
, is_record_mode
, delay_ms
):
158 self
.is_record_mode
= is_record_mode
159 self
.delay_ms
= int(delay_ms
)
161 def __call__(self
, host
, default_ip
):
162 if not self
.is_record_mode
:
163 time
.sleep(self
.delay_ms
* 1000.0)
166 def SetRecordMode(self
):
167 self
.is_record_mode
= True
169 def SetReplayMode(self
):
170 self
.is_record_mode
= False
173 class UdpDnsHandler(SocketServer
.DatagramRequestHandler
):
174 """Resolve DNS queries to localhost.
176 Possible alternative implementation:
177 http://howl.play-bow.org/pipermail/dnspython-users/2010-February/000119.html
180 STANDARD_QUERY_OPERATION_CODE
= 0
183 """Handle a DNS query.
185 IPv6 requests (with rdtype AAAA) receive mismatched IPv4 responses
186 (with rdtype A). To properly support IPv6, the http proxy would
187 need both types of addresses. By default, Windows XP does not
190 self
.data
= self
.rfile
.read()
191 self
.transaction_id
= self
.data
[0]
192 self
.flags
= self
.data
[1]
193 self
.qa_counts
= self
.data
[4:6]
195 operation_code
= (ord(self
.data
[2]) >> 3) & 15
196 if operation_code
== self
.STANDARD_QUERY_OPERATION_CODE
:
197 self
.wire_domain
= self
.data
[12:]
198 self
.domain
= self
._domain
(self
.wire_domain
)
200 logging
.debug("DNS request with non-zero operation code: %s",
202 ip
= self
.server
.dns_lookup(self
.domain
)
204 logging
.debug('dnsproxy: %s -> NXDOMAIN', self
.domain
)
205 response
= self
.get_dns_no_such_name_response()
207 if ip
== self
.server
.server_address
[0]:
208 logging
.debug('dnsproxy: %s -> %s (replay web proxy)', self
.domain
, ip
)
210 logging
.debug('dnsproxy: %s -> %s', self
.domain
, ip
)
211 response
= self
.get_dns_response(ip
)
212 self
.wfile
.write(response
)
215 def _domain(cls
, wire_domain
):
218 length
= ord(wire_domain
[index
])
220 domain
+= wire_domain
[index
+ 1:index
+ length
+ 1] + '.'
222 length
= ord(wire_domain
[index
])
225 def get_dns_response(self
, ip
):
229 self
.transaction_id
+
231 '\x81\x80' + # standard query response, no error
232 self
.qa_counts
* 2 + '\x00\x00\x00\x00' + # Q&A counts
234 '\xc0\x0c' # pointer to domain name
235 '\x00\x01' # resource record type ("A" host address)
236 '\x00\x01' # class of the data
237 '\x00\x00\x00\x3c' # ttl (seconds)
238 '\x00\x04' + # resource data length (4 bytes for ip)
243 def get_dns_no_such_name_response(self
):
244 query_message
= message
.from_wire(self
.data
)
245 response_message
= message
.make_response(query_message
)
246 response_message
.flags |
= flags
.AA | flags
.RA
247 response_message
.set_rcode(rcode
.NXDOMAIN
)
248 return response_message
.to_wire()
251 class DnsProxyServer(SocketServer
.ThreadingUDPServer
,
252 daemonserver
.DaemonServer
):
253 # Increase the request queue size. The default value, 5, is set in
254 # SocketServer.TCPServer (the parent of BaseHTTPServer.HTTPServer).
255 # Since we're intercepting many domains through this single server,
256 # it is quite possible to get more than 5 concurrent requests.
257 request_queue_size
= 256
259 # Allow sockets to be reused. See
260 # http://svn.python.org/projects/python/trunk/Lib/SocketServer.py for more
262 allow_reuse_address
= True
264 # Don't prevent python from exiting when there is thread activity.
265 daemon_threads
= True
267 def __init__(self
, host
='', port
=53, dns_lookup
=None):
268 """Initialize DnsProxyServer.
271 host: a host string (name or IP) to bind the dns proxy and to which
272 DNS requests will be resolved.
273 port: an integer port on which to bind the proxy.
274 dns_lookup: a list of filters to apply to lookup.
277 SocketServer
.ThreadingUDPServer
.__init
__(
278 self
, (host
, port
), UdpDnsHandler
)
279 except socket
.error
, (error_number
, msg
):
280 if error_number
== errno
.EACCES
:
281 raise DnsProxyException(
282 'Unable to bind DNS server on (%s:%s)' % (host
, port
))
284 self
.dns_lookup
= dns_lookup
or (lambda host
: self
.server_address
[0])
285 self
.server_port
= self
.server_address
[1]
286 logging
.warning('DNS server started on %s:%d', self
.server_address
[0],
287 self
.server_address
[1])
293 except KeyboardInterrupt, e
:
295 logging
.info('Stopped DNS server')