import csv import ipaddress import threading from collections import defaultdict from datetime import datetime from time import sleep import sys import requests from lexicon.client import Client from lexicon.config import ConfigResolver from oshipka.util.process import process_exp_backoff from sensitive import CLOUDFLARE_USERNAME, CLOUDFLARE_AUTH_KEY SLEEP_SECONDS = 60 * 30 # 30 min DEFAULT_TTL = 5 * 60 ipv4_sites = [ "http://www.icanhazip.com", "http://ipecho.net/plain", "http://checkip.amazonaws.com", "http://ipinfo.io/ip", "http://ifconfig.me" ] ipv6_sites = [ "https://diagnostic.opendns.com/myip", "https://ifconfig.co/ip", ] ip_checkers = ipv4_sites + ipv6_sites dns_to_check = [] results = [] real = defaultdict(dict) def test_ip(ip): try: ipaddress_info = ipaddress.ip_address(ip) except ValueError as e: print(e) return None if ipaddress_info.version == 4: version = 'A' elif ipaddress_info.version == 6: version = 'AAAA' else: return None return { "addr": ipaddress_info.exploded, "version": version, } def fetch_ip(url): try: resp = requests.get(url) ip = resp.text.strip() ip_info = test_ip(ip) if ip_info: results.append(ip_info) except Exception as e: print("Error fetching {}: {}".format(url, e)) tso, tsd = [], [] def check_observed(): for url in ip_checkers: t = threading.Thread(target=fetch_ip, args=(url,)) tso.append(t) t.start() def check_dns(): for domain, ipv in dns_to_check: t = threading.Thread(target=get_dns, args=(domain, ipv,)) tsd.append(t) t.start() def get_resolver_results(domain, ipv): dns_config = ConfigResolver() dns_config.with_dict({ 'provider_name': 'cloudflare', 'action': 'list', 'type': ipv, 'domain': domain, 'cloudflare': { 'auth_username': CLOUDFLARE_USERNAME, 'auth_token': CLOUDFLARE_AUTH_KEY, } }) client = Client(dns_config) return client.execute() def set_resolver_results(domain, ipv, new): dns_config = ConfigResolver() dns_config.with_dict({ 'provider_name': 'cloudflare', 'action': 'create', 'type': ipv, 'domain': domain, 'name': domain, 'content': new, 'ttl': DEFAULT_TTL, 'cloudflare': { 'auth_username': CLOUDFLARE_USERNAME, 'auth_token': CLOUDFLARE_AUTH_KEY, } }) client = Client(dns_config) return client.execute() def get_dns(domain, ipv): results = process_exp_backoff(get_resolver_results, func_args=[domain, ipv]) res = [x.get('content') for x in results if x.get('name') == domain] if len(res) == 1: print("Real {}: {}".format(ipv, res[0])) real[domain][ipv] = res[0] def set_dns(domain, ipv, new): return process_exp_backoff(set_resolver_results, func_args=[domain, ipv, new]) def get_uniq(_list): if len(_list) > 1 and len(set(_list)) == 1: return _list[0] def main(): with open('dns_addresses.csv') as csvf: csvreader = csv.reader(csvf) for row in csvreader: dns_to_check.append(row) check_dns() check_observed() for t in tso: t.join() for t in tsd: t.join() for domain, version in dns_to_check: ip_obs = get_uniq([x['addr'] for x in results if x['version'] == version]) print("{}/{}: {}".format(domain, version, ip_obs)) ip_real = real.get(domain).get(version) sys.stdout.flush() if ip_obs == ip_real: print('{}/{} is same!'.format(domain, version)) else: print("{}/{} diff on dns: real: {}, obs: {}".format(domain, version, ip_real, ip_obs)) if set_dns(domain, version, ip_real): print("update successful {}/{} -> {}".format(domain, version, ip_real)) else: print("update failed: {}/{} on dns is {}, but obs is {}".format( domain, version, ip_real, ip_obs )) if __name__ == "__main__": while True: print("{}: Waking up".format(datetime.utcnow())) sys.stdout.flush() main() print("{}: Sleeping for {}".format(datetime.utcnow(), SLEEP_SECONDS)) sys.stdout.flush() sleep(SLEEP_SECONDS)