Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / tools / telemetry / third_party / webpagereplay / dnsproxy.py
bloba913d84119c75def1c7f522bf3c2fc5bcbc2553f
1 #!/usr/bin/env python
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.
16 import daemonserver
17 import errno
18 import logging
19 import socket
20 import SocketServer
21 import threading
22 import time
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):
34 pass
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()
45 self.dns_cache = {}
47 @staticmethod
48 def _IsIPAddress(hostname):
49 try:
50 socket.inet_aton(hostname)
51 return True
52 except socket.error:
53 return False
55 def __call__(self, hostname, rdtype=rdatatype.A):
56 """Return real IP for a host.
58 Args:
59 host: a hostname ending with a period (e.g. "www.google.com.")
60 rdtype: the query type (1 for 'A', 28 for 'AAAA')
61 Returns:
62 the IP address as a string (e.g. "192.168.25.2")
63 """
64 if self._IsIPAddress(hostname):
65 return hostname
66 self.dns_cache_lock.acquire()
67 ip = self.dns_cache.get(hostname)
68 self.dns_cache_lock.release()
69 if ip:
70 return ip
71 try:
72 answers = self.resolver.query(hostname, rdtype)
73 except resolver.NXDOMAIN:
74 return None
75 except resolver.NoNameservers:
76 logging.debug('_real_dns_lookup(%s) -> No nameserver.',
77 hostname)
78 return None
79 except (resolver.NoAnswer, resolver.Timeout) as ex:
80 logging.debug('_real_dns_lookup(%s) -> None (%s)',
81 hostname, ex.__class__.__name__)
82 return None
83 if answers:
84 ip = str(answers[0])
85 self.dns_cache_lock.acquire()
86 self.dns_cache[hostname] = ip
87 self.dns_cache_lock.release()
88 return ip
90 def ClearCache(self):
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):
104 ip = self.replay_ip
105 for f in self.filters:
106 ip = f(hostname, default_ip=ip)
107 return 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.
121 Args:
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.
133 Args:
134 host: a hostname ending with a period (e.g. "www.google.com.")
135 Returns:
136 IP address as a string or None (if lookup fails)
138 ip = default_ip
139 if host not in self.archive_hosts:
140 real_ip = self.real_dns_lookup(host)
141 if real_ip:
142 if ipaddr.IPAddress(real_ip).is_private:
143 ip = real_ip
144 else:
145 ip = None
146 return ip
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)
164 return default_ip
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
182 def handle(self):
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
188 support IPv6.
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]
194 self.domain = ''
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)
199 else:
200 logging.debug("DNS request with non-zero operation code: %s",
201 operation_code)
202 ip = self.server.dns_lookup(self.domain)
203 if ip is None:
204 logging.debug('dnsproxy: %s -> NXDOMAIN', self.domain)
205 response = self.get_dns_no_such_name_response()
206 else:
207 if ip == self.server.server_address[0]:
208 logging.debug('dnsproxy: %s -> %s (replay web proxy)', self.domain, ip)
209 else:
210 logging.debug('dnsproxy: %s -> %s', self.domain, ip)
211 response = self.get_dns_response(ip)
212 self.wfile.write(response)
214 @classmethod
215 def _domain(cls, wire_domain):
216 domain = ''
217 index = 0
218 length = ord(wire_domain[index])
219 while length:
220 domain += wire_domain[index + 1:index + length + 1] + '.'
221 index += length + 1
222 length = ord(wire_domain[index])
223 return domain
225 def get_dns_response(self, ip):
226 packet = ''
227 if self.domain:
228 packet = (
229 self.transaction_id +
230 self.flags +
231 '\x81\x80' + # standard query response, no error
232 self.qa_counts * 2 + '\x00\x00\x00\x00' + # Q&A counts
233 self.wire_domain +
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)
239 socket.inet_aton(ip)
241 return packet
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
261 # details.
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.
270 Args:
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.
276 try:
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))
283 raise
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])
289 def cleanup(self):
290 try:
291 self.shutdown()
292 self.server_close()
293 except KeyboardInterrupt, e:
294 pass
295 logging.info('Stopped DNS server')