summaryrefslogtreecommitdiff
path: root/rfc2136/ddns-update-rfc2136
blob: 6bd07ce9e5b239094a7af89ef39cbc040bfb6ac3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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)