#!/usr/bin/env python2 """DNS resolution with caching for PPF proxy testing.""" from __future__ import division import time from misc import _log import rocksock # Module-level config reference config = None # DNS cache: {hostname: (ip, timestamp)} cached_dns = {} DNS_CACHE_TTL = 3600 # 1 hour def set_config(cfg): """Set the config object (called from proxywatchd/ppf).""" global config config = cfg def socks4_resolve(srvname, server_port): """Resolve hostname to IP for SOCKS4 (which requires numeric IP). Caches results for DNS_CACHE_TTL seconds to avoid repeated lookups. Args: srvname: Hostname to resolve server_port: Port number (for resolution) Returns: IP address string, or False on failure """ now = time.time() # Check cache with TTL if srvname in cached_dns: ip, ts = cached_dns[srvname] if now - ts < DNS_CACHE_TTL: if config and config.watchd.debug: _log("using cached ip (%s) for %s" % (ip, srvname), "debug") return ip # Expired - fall through to re-resolve # Resolve hostname dns_fail = False try: af, sa = rocksock.resolve(rocksock.RocksockHostinfo(srvname, server_port), want_v4=True) if sa is not None: cached_dns[srvname] = (sa[0], now) return sa[0] else: dns_fail = True except rocksock.RocksockException as e: assert(e.get_errortype() == rocksock.RS_ET_GAI) dns_fail = True if dns_fail: _log("could not resolve connection target %s" % srvname, "ERROR") return False return srvname def clear_cache(): """Clear the DNS cache.""" global cached_dns cached_dns = {} def cache_stats(): """Return DNS cache statistics. Returns: dict with count, oldest_age, newest_age """ if not cached_dns: return {'count': 0, 'oldest_age': 0, 'newest_age': 0} now = time.time() ages = [now - ts for _, ts in cached_dns.values()] return { 'count': len(cached_dns), 'oldest_age': max(ages), 'newest_age': min(ages), }