def __init__(self, app, conf): if not MODULE_DEPENDENCY_MET: # reraise the exception if the dependency wasn't met raise ImportError('dnspython is required for this module') self.app = app storage_domain = conf.get('storage_domain', 'example.com') self.storage_domain = ['.' + s for s in list_from_csv(storage_domain) if not s.startswith('.')] self.storage_domain += [s for s in list_from_csv(storage_domain) if s.startswith('.')] self.lookup_depth = int(conf.get('lookup_depth', '1')) nameservers = list_from_csv(conf.get('nameservers')) try: for i, server in enumerate(nameservers): ip_or_host, maybe_port = nameservers[i] = \ parse_socket_string(server, None) if not is_valid_ip(ip_or_host): raise ValueError if maybe_port is not None: int(maybe_port) except ValueError: raise ValueError('Invalid cname_lookup/nameservers configuration ' 'found. All nameservers must be valid IPv4 or ' 'IPv6, followed by an optional :<integer> port.') self.resolver = dns.resolver.Resolver() if nameservers: self.resolver.nameservers = [ip for (ip, port) in nameservers] self.resolver.nameserver_ports = { ip: int(port) for (ip, port) in nameservers if port is not None} self.memcache = None self.logger = get_logger(conf, log_route='cname-lookup')
def is_local_device(my_ips, my_port, dev_ip, dev_port): """ Return True if the provided dev_ip and dev_port are among the IP addresses specified in my_ips and my_port respectively. To support accurate locality determination in the server-per-port deployment, when my_port is None, only IP addresses are used for determining locality (dev_port is ignored). If dev_ip is a hostname then it is first translated to an IP address before checking it against my_ips. """ candidate_ips = [] if not is_valid_ip(dev_ip) and is_valid_hostname(dev_ip): try: # get the ip for this host; use getaddrinfo so that # it works for both ipv4 and ipv6 addresses addrinfo = socket.getaddrinfo(dev_ip, dev_port) for addr in addrinfo: family = addr[0] dev_ip = addr[4][0] # get the ip-address if family == socket.AF_INET6: dev_ip = expand_ipv6(dev_ip) candidate_ips.append(dev_ip) except socket.gaierror: return False else: if is_valid_ipv6(dev_ip): dev_ip = expand_ipv6(dev_ip) candidate_ips = [dev_ip] for dev_ip in candidate_ips: if dev_ip in my_ips and (my_port is None or dev_port == my_port): return True return False
def is_local_device(my_ips, my_port, dev_ip, dev_port): """ Return True if the provided dev_ip and dev_port are among the IP addresses specified in my_ips and my_port respectively. To support accurate locality determination in the server-per-port deployment, when my_port is None, only IP addresses are used for determining locality (dev_port is ignored). If dev_ip is a hostname then it is first translated to an IP address before checking it against my_ips. """ candidate_ips = [] if not is_valid_ip(dev_ip) and is_valid_hostname(dev_ip): try: # get the ip for this host; use getaddrinfo so that # it works for both ipv4 and ipv6 addresses addrinfo = socket.getaddrinfo(dev_ip, dev_port) for addr in addrinfo: family = addr[0] dev_ip = addr[4][0] # get the ip-address if family == socket.AF_INET6: dev_ip = expand_ipv6(dev_ip) candidate_ips.append(dev_ip) except socket.gaierror: return False else: if is_valid_ipv6(dev_ip): dev_ip = expand_ipv6(dev_ip) candidate_ips = [dev_ip] for dev_ip in candidate_ips: if dev_ip in my_ips and (my_port is None or dev_port == my_port): return True return False
def __call__(self, env, start_response): if not self.storage_domain: return self.app(env, start_response) if 'HTTP_HOST' in env: requested_host = env['HTTP_HOST'] else: requested_host = env['SERVER_NAME'] given_domain = wsgi_to_str(requested_host) port = '' if ':' in given_domain: given_domain, port = given_domain.rsplit(':', 1) if is_valid_ip(given_domain): return self.app(env, start_response) a_domain = given_domain if not self._domain_endswith_in_storage_domain(a_domain): if self.memcache is None: self.memcache = cache_from_env(env) error = True for tries in range(self.lookup_depth): found_domain = None if self.memcache: memcache_key = ''.join(['cname-', a_domain]) found_domain = self.memcache.get(memcache_key) if six.PY2 and found_domain: found_domain = found_domain.encode('utf-8') if found_domain is None: ttl, found_domain = lookup_cname(a_domain, self.resolver) if self.memcache and ttl > 0: memcache_key = ''.join(['cname-', given_domain]) self.memcache.set(memcache_key, found_domain, time=ttl) if not found_domain or found_domain == a_domain: # no CNAME records or we're at the last lookup error = True found_domain = None break elif self._domain_endswith_in_storage_domain(found_domain): # Found it! self.logger.info( _('Mapped %(given_domain)s to %(found_domain)s') % { 'given_domain': given_domain, 'found_domain': found_domain }) if port: env['HTTP_HOST'] = ':'.join( [str_to_wsgi(found_domain), port]) else: env['HTTP_HOST'] = str_to_wsgi(found_domain) error = False break else: # try one more deep in the chain self.logger.debug( _('Following CNAME chain for ' '%(given_domain)s to %(found_domain)s') % { 'given_domain': given_domain, 'found_domain': found_domain }) a_domain = found_domain if error: if found_domain: msg = 'CNAME lookup failed after %d tries' % \ self.lookup_depth else: msg = 'CNAME lookup failed to resolve to a valid domain' resp = HTTPBadRequest(request=Request(env), body=msg, content_type='text/plain') return resp(env, start_response) else: context = _CnameLookupContext(self.app, requested_host, env['HTTP_HOST']) return context.handle_request(env, start_response) return self.app(env, start_response)