#!/usr/bin/env python3 import argparse import base64 import dns.resolver import ipaddress import logging import logging.handlers import requests import socket import sys import urllib HE_DNS_API_HOST = "dyn.dns.he.net" def build_ip(ip_string): try: ip = ipaddress.ip_address(ip_string) except ValueError: log.error("manually specified address is invalid format: %s" % ip_string) sys.exit(1) except: log.error(str(e)) sys.exit(1) return ip def haveIPv6(): log.debug("inside haveIPv6()") haveIPv6 = True try: s = socket.socket(socket.AF_INET6) s.connect((HE_DNS_API_HOST,80)) except socket.gaierror as e: log.debug("Exception {}".format(e.__class__)) log.error("Testing IPv6 connection: " + str(e)) sys.exit(1) except OSError as e: log.debug("Exception {}".format(e.__class__)) log.debug(str(e)) haveIPv6 = False except TimeoutError as e: log.error("Testing IPv6 connection: " + str(e)) sys.exit(1) except Exception as e: log.debug("Exception {}".format(e.__class__)) log.error(str(e)) haveIPv6 = False return haveIPv6 def main(): ap = argparse.ArgumentParser(description="Update Hurricane Electric DNS dynamic record") ap.add_argument("record", help="DNS record to update") ap.add_argument("--v4", help="Update IPv4 A record (default)", action="store_true", default=True) ap.add_argument("--v6", help="Update IPv6 AAAA record", action="store_true", default=False) ap.add_argument("--addr4", help="Update A record with provided IP rather than detected IP") ap.add_argument("--addr6", help="Update AAAA record with provided IP rather than detected IP") ap.add_argument("--key", help="HE DDNS key for record (by default read from /etc/he-dns-secret)") ap.add_argument("--debug", help="Enable debug logging", action="store_true", default=False) args = ap.parse_args() if args.debug: log.setLevel(logging.DEBUG) else: log.setLevel(logging.ERROR) # Handle manual addressing if args.addr4: ip = build_ip(args.addr4) if args.addr6: ip = build_ip(args.addr6) if args.v6: if not haveIPv6 and not args.addr6: log.error("no IPv6 detected and --v6 specified without --addr6 (try --debug?)") sys.exit(1) try: if args.v6: ans = dns.resolver.resolve(HE_DNS_API_HOST, "AAAA")[0] log.debug("DNS resolved: {}".format(ans.to_text())) apihost = "[{}]".format(ans.to_text()) else: ans = dns.resolver.resolve(HE_DNS_API_HOST, "A")[0] log.debug("DNS resolved: {}".format(ans.to_text())) apihost = ans.to_text() # build the API URL to be called url = "http://{}/nic/update?hostname={}".format(apihost, args.record) if args.addr4 or args.addr6: url += "&myip={}".format(ip) log.debug("API request URL: " + url) # make the HTTP request authstr = base64.b64encode(bytes("%s:%s" % ( args.record, "yEuFfO74TskIX2NG0Yod" ), "ascii")) log.debug("auth string: %s" % authstr.decode("utf-8")) req = urllib.request.Request(url) req.add_header("Host", HE_DNS_API_HOST) req.add_header("Authorization", "Basic %s" % authstr.decode("utf-8")) with urllib.request.urlopen(req) as response: this_page = response.read() log.debug("HE response: " + this_page.decode("utf-8")) res = this_page.decode("utf-8").split()[0] if res == "good": return True elif res == "badauth": raise Exception("HE badauth: wrong key or record not configured for dynamic updates") elif res == "nochg": return True else: raise Exception("HE returned {}: unknown response code".format(res)) except dns.resolver.NXDOMAIN as e: log.debug("Exception class: {}".format(e.__class__)) log.error(str(e)) sys.exit(1) except Exception as e: log.debug("Exception class: {}".format(e.__class__)) log.error(str(e)) sys.exit(1) if __name__ == "__main__": log = logging.getLogger("he-dyndns") lh = logging.StreamHandler() lf = logging.Formatter(fmt='%(name)s: %(levelname)s: %(message)s') lh.setFormatter(lf) log.addHandler(lh) main() sys.exit(0)