summaryrefslogtreecommitdiff
path: root/ddns-update-rfc2136
diff options
context:
space:
mode:
Diffstat (limited to 'ddns-update-rfc2136')
-rwxr-xr-xddns-update-rfc2136147
1 files changed, 147 insertions, 0 deletions
diff --git a/ddns-update-rfc2136 b/ddns-update-rfc2136
new file mode 100755
index 0000000..6bd07ce
--- /dev/null
+++ b/ddns-update-rfc2136
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+
+import argparse
+import ipaddress
+import netifaces
+import logging
+import logging.handlers
+import os.path
+import socket
+import subprocess
+import sys
+
+
+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 get_interface_addrs(iface, addrtype):
+ try:
+ iaddrs = netifaces.ifaddresses(iface)
+ i = iaddrs[addrtype]
+ log.debug("interface {} addrs of type {}: {}".format(iface,addrtype,i))
+ except ValueError as e:
+ log.debug("Exception class: {}".format(e.__class__))
+ log.error("No interface named {} found".format(iface))
+ sys.exit(1)
+ except KeyError as e:
+ log.debug("Exception class: {}".format(e.__class__))
+ log.error("No address of type {} found on interface {}".format(addrtype, iface))
+ sys.exit(1)
+ except Exception as e:
+ log.debug("Exception class: {}".format(e.__class__))
+ log.error(str(e))
+ sys.exit(1)
+
+ return i
+
+def build_nsupdate_cmd(server, zone, record, rrtype, ttl, addr):
+ log.debug("BNC: server: {}".format(server))
+ log.debug("BNC: zone: {}".format(zone))
+ log.debug("BNC: record: {}".format(record))
+ log.debug("BNC: rrtype: {}".format(rrtype))
+ log.debug("BNC: ttl: {}".format(ttl))
+ log.debug("BNC: addr: {}".format(addr))
+
+ cmdstr = "server {}\n".format(server)
+ cmdstr += "zone {}\n".format(zone)
+ cmdstr += "update delete {} {}\n".format(record, rrtype)
+ cmdstr += "update add {} {} {} {}\n".format(record, ttl, rrtype, addr)
+ cmdstr += "send\n"
+ cmdstr += "quit\n"
+
+ return cmdstr
+
+def main():
+ ap = argparse.ArgumentParser(description="Update dynamic DNS records using RFC2136")
+ ap.add_argument("interface", help="interface to obtain IP from (for IPv6, takes the numerically first global address on the interface)")
+ ap.add_argument("record", help="DNS record to update")
+ ap.add_argument("zone", help="Zone name to update (e.g. example.com)")
+ ap.add_argument("server", help="Server to update (IP or FQDN)")
+ ap.add_argument("--v4", help="Update IPv4 A record", action="store_true", default=False)
+ ap.add_argument("--v6", help="Update IPv6 AAAA record (default)", action="store_true", default=True)
+ ap.add_argument("--addr4", help="Update A record with provided IP rather than detected IP (causes 'interface' to be ignored)")
+ ap.add_argument("--addr6", help="Update AAAA record with provided IP rather than detected IP (causes 'interface' to be ignored)")
+ ap.add_argument("--ttl", help="TTL to assign to record (default 300)", default=300)
+ ap.add_argument("--keyfile", help="Alternate location for key config file (default /etc/ddns-RECORD.key where RECORD is the record name provided as the argument)")
+ 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)
+
+ if args.keyfile:
+ keyfile = args.keyfile
+ else:
+ keyfile = "/etc/ddns-{}.key".format(args.record)
+ log.debug("KEYFILE: {}".format(keyfile))
+
+ if args.v4:
+ addrtype = netifaces.AF_INET
+ else:
+ addrtype = netifaces.AF_INET6
+
+ ip = False
+ if args.addr4:
+ ip = build_ip(args.addr4)
+
+ if args.addr6:
+ ip = build_ip(args.addr6)
+
+ try:
+ if not os.path.exists(keyfile):
+ raise Exception("missing key file {}".format(keyfile))
+
+ if ip:
+ addr = ip
+ log.debug("Manual IP: {}".format(addr))
+ else:
+ addrarr = get_interface_addrs(args.interface, addrtype)
+ addr = addrarr.pop(0)['addr']
+ log.debug("Found IP: {}".format(addr))
+
+ rrtype = "AAAA"
+ if args.v4:
+ rrtype = "A"
+
+ nsupdatecmd = build_nsupdate_cmd(args.server, args.zone, args.record, rrtype, args.ttl, addr)
+
+ nsupdate = subprocess.run(
+ "nsupdate -k {}".format(keyfile),
+ shell=True,
+ check=True,
+ input = bytes(nsupdatecmd,'utf-8'),
+ capture_output = True
+ )
+
+ log.debug("nsupdate return code: {}".format(nsupdate.returncode))
+
+ except subprocess.CalledProcessError as e:
+ log.debug("Exception class: {}".format(e.__class__))
+ log.error("Error executing: " + e.cmd)
+ log.error("nsupdate stderr: " + e.stderr.decode("utf-8").strip())
+ 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("ddns-update-rfc2136")
+ lh = logging.StreamHandler()
+ lf = logging.Formatter(fmt='%(name)s: %(levelname)s: %(message)s')
+ lh.setFormatter(lf)
+ log.addHandler(lh)
+ main()
+ sys.exit(0)