| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2010 Google Inc. All Rights Reserved. |
| 3 # |
| 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 |
| 7 # |
| 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 # |
| 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. |
| 15 |
| 16 import daemonserver |
| 17 import errno |
| 18 import logging |
| 19 import socket |
| 20 import SocketServer |
| 21 import threading |
| 22 |
| 23 import third_party |
| 24 import dns.flags |
| 25 import dns.message |
| 26 import dns.rcode |
| 27 import dns.resolver |
| 28 import dns.rdatatype |
| 29 import ipaddr |
| 30 |
| 31 |
| 32 class DnsProxyException(Exception): |
| 33 pass |
| 34 |
| 35 |
| 36 class RealDnsLookup(object): |
| 37 def __init__(self, name_servers): |
| 38 if '127.0.0.1' in name_servers: |
| 39 raise DnsProxyException( |
| 40 'Invalid nameserver: 127.0.0.1 (causes an infinte loop)') |
| 41 self.resolver = dns.resolver.get_default_resolver() |
| 42 self.resolver.nameservers = name_servers |
| 43 self.dns_cache_lock = threading.Lock() |
| 44 self.dns_cache = {} |
| 45 |
| 46 def __call__(self, hostname, rdtype=dns.rdatatype.A): |
| 47 """Return real IP for a host. |
| 48 |
| 49 Args: |
| 50 host: a hostname ending with a period (e.g. "www.google.com.") |
| 51 rdtype: the query type (1 for 'A', 28 for 'AAAA') |
| 52 Returns: |
| 53 the IP address as a string (e.g. "192.168.25.2") |
| 54 """ |
| 55 self.dns_cache_lock.acquire() |
| 56 ip = self.dns_cache.get(hostname) |
| 57 self.dns_cache_lock.release() |
| 58 if ip: |
| 59 return ip |
| 60 try: |
| 61 answers = self.resolver.query(hostname, rdtype) |
| 62 except dns.resolver.NXDOMAIN: |
| 63 return None |
| 64 except (dns.resolver.NoAnswer, dns.resolver.Timeout) as ex: |
| 65 logging.debug('_real_dns_lookup(%s) -> None (%s)', |
| 66 hostname, ex.__class__.__name__) |
| 67 return None |
| 68 if answers: |
| 69 ip = str(answers[0]) |
| 70 self.dns_cache_lock.acquire() |
| 71 self.dns_cache[hostname] = ip |
| 72 self.dns_cache_lock.release() |
| 73 return ip |
| 74 |
| 75 def ClearCache(self): |
| 76 """Clearn the dns cache.""" |
| 77 self.dns_cache_lock.acquire() |
| 78 self.dns_cache.clear() |
| 79 self.dns_cache_lock.release() |
| 80 |
| 81 |
| 82 class PrivateIpDnsLookup(object): |
| 83 """Resolve private hosts to their real IPs and others to the Web proxy IP. |
| 84 |
| 85 Hosts in the given http_archive will resolve to the Web proxy IP without |
| 86 checking the real IP. |
| 87 |
| 88 This only supports IPv4 lookups. |
| 89 """ |
| 90 def __init__(self, web_proxy_ip, real_dns_lookup, http_archive): |
| 91 """Initialize PrivateIpDnsLookup. |
| 92 |
| 93 Args: |
| 94 web_proxy_ip: the IP address returned by __call__ for non-private hosts. |
| 95 real_dns_lookup: a function that resolves a host to an IP. |
| 96 http_archive: an instance of a HttpArchive |
| 97 Hosts is in the archive will always resolve to the web_proxy_ip |
| 98 """ |
| 99 self.web_proxy_ip = web_proxy_ip |
| 100 self.real_dns_lookup = real_dns_lookup |
| 101 self.http_archive = http_archive |
| 102 self.InitializeArchiveHosts() |
| 103 |
| 104 def __call__(self, host): |
| 105 """Return real IPv4 for private hosts and Web proxy IP otherwise. |
| 106 |
| 107 Args: |
| 108 host: a hostname ending with a period (e.g. "www.google.com.") |
| 109 Returns: |
| 110 IP address as a string or None (if lookup fails) |
| 111 """ |
| 112 ip = self.web_proxy_ip |
| 113 if host not in self.archive_hosts: |
| 114 real_ip = self.real_dns_lookup(host) |
| 115 if real_ip: |
| 116 if ipaddr.IPAddress(real_ip).is_private: |
| 117 ip = real_ip |
| 118 else: |
| 119 ip = None |
| 120 return ip |
| 121 |
| 122 def InitializeArchiveHosts(self): |
| 123 """Recompute the archive_hosts from the http_archive.""" |
| 124 self.archive_hosts = set('%s.' % req.host for req in self.http_archive) |
| 125 |
| 126 |
| 127 class UdpDnsHandler(SocketServer.DatagramRequestHandler): |
| 128 """Resolve DNS queries to localhost. |
| 129 |
| 130 Possible alternative implementation: |
| 131 http://howl.play-bow.org/pipermail/dnspython-users/2010-February/000119.html |
| 132 """ |
| 133 |
| 134 STANDARD_QUERY_OPERATION_CODE = 0 |
| 135 |
| 136 def handle(self): |
| 137 """Handle a DNS query. |
| 138 |
| 139 IPv6 requests (with rdtype AAAA) receive mismatched IPv4 responses |
| 140 (with rdtype A). To properly support IPv6, the http proxy would |
| 141 need both types of addresses. By default, Windows XP does not |
| 142 support IPv6. |
| 143 """ |
| 144 self.data = self.rfile.read() |
| 145 self.transaction_id = self.data[0] |
| 146 self.flags = self.data[1] |
| 147 self.qa_counts = self.data[4:6] |
| 148 self.domain = '' |
| 149 operation_code = (ord(self.data[2]) >> 3) & 15 |
| 150 if operation_code == self.STANDARD_QUERY_OPERATION_CODE: |
| 151 self.wire_domain = self.data[12:] |
| 152 self.domain = self._domain(self.wire_domain) |
| 153 else: |
| 154 logging.debug("DNS request with non-zero operation code: %s", |
| 155 operation_code) |
| 156 ip = self.server.dns_lookup(self.domain) |
| 157 if ip is None: |
| 158 logging.debug('dnsproxy: %s -> NXDOMAIN', self.domain) |
| 159 response = self.get_dns_no_such_name_response() |
| 160 else: |
| 161 if ip == self.server.server_address[0]: |
| 162 logging.debug('dnsproxy: %s -> %s (replay web proxy)', self.domain, ip) |
| 163 else: |
| 164 logging.debug('dnsproxy: %s -> %s', self.domain, ip) |
| 165 response = self.get_dns_response(ip) |
| 166 self.wfile.write(response) |
| 167 |
| 168 @classmethod |
| 169 def _domain(cls, wire_domain): |
| 170 domain = '' |
| 171 index = 0 |
| 172 length = ord(wire_domain[index]) |
| 173 while length: |
| 174 domain += wire_domain[index + 1:index + length + 1] + '.' |
| 175 index += length + 1 |
| 176 length = ord(wire_domain[index]) |
| 177 return domain |
| 178 |
| 179 def get_dns_response(self, ip): |
| 180 packet = '' |
| 181 if self.domain: |
| 182 packet = ( |
| 183 self.transaction_id + |
| 184 self.flags + |
| 185 '\x81\x80' + # standard query response, no error |
| 186 self.qa_counts * 2 + '\x00\x00\x00\x00' + # Q&A counts |
| 187 self.wire_domain + |
| 188 '\xc0\x0c' # pointer to domain name |
| 189 '\x00\x01' # resource record type ("A" host address) |
| 190 '\x00\x01' # class of the data |
| 191 '\x00\x00\x00\x3c' # ttl (seconds) |
| 192 '\x00\x04' + # resource data length (4 bytes for ip) |
| 193 socket.inet_aton(ip) |
| 194 ) |
| 195 return packet |
| 196 |
| 197 def get_dns_no_such_name_response(self): |
| 198 query_message = dns.message.from_wire(self.data) |
| 199 response_message = dns.message.make_response(query_message) |
| 200 response_message.flags |= dns.flags.AA | dns.flags.RA |
| 201 response_message.set_rcode(dns.rcode.NXDOMAIN) |
| 202 return response_message.to_wire() |
| 203 |
| 204 class DnsProxyServer(SocketServer.ThreadingUDPServer, |
| 205 daemonserver.DaemonServer): |
| 206 def __init__(self, dns_lookup=None, host='', port=53): |
| 207 """Initialize DnsProxyServer. |
| 208 |
| 209 Args: |
| 210 dns_lookup: a function that resolves a host to an IP address. |
| 211 host: a host string (name or IP) to bind the dns proxy and to which |
| 212 DNS requests will be resolved. |
| 213 port: an integer port on which to bind the proxy. |
| 214 """ |
| 215 try: |
| 216 SocketServer.ThreadingUDPServer.__init__( |
| 217 self, (host, port), UdpDnsHandler) |
| 218 except socket.error, (error_number, msg): |
| 219 if error_number == errno.EACCES: |
| 220 raise DnsProxyException( |
| 221 'Unable to bind DNS server on (%s:%s)' % (host, port)) |
| 222 raise |
| 223 self.dns_lookup = dns_lookup or (lambda host: self.server_address[0]) |
| 224 logging.info('Started DNS server on %s...', self.server_address) |
| 225 |
| 226 def cleanup(self): |
| 227 self.shutdown() |
| 228 logging.info('Shutdown DNS server') |
| OLD | NEW |