dyndns

Check-in [68e0d11ff8]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Can't cache, sigh--there's a per-request ID which changes. We want the client to be a standalone bit of python, so also drop the utils.py factoring.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:68e0d11ff8c983223a21898e896a7632d1836f5072166ac2e5f938f6e9177a9c
User & Date: ajv-899-334-8894@vsta.org 2017-05-13 16:51:25
Context
2017-06-15
04:45
More details in the doc check-in: 99d81d3502 user: ajv-899-334-8894@vsta.org tags: master, trunk
2017-05-13
16:51
Can't cache, sigh--there's a per-request ID which changes. We want the client to be a standalone bit of python, so also drop the utils.py factoring. check-in: 68e0d11ff8 user: ajv-899-334-8894@vsta.org tags: master, trunk
2017-05-12
16:28
Adapt logging to not be buried by stray/malicious queries. Drop debug stops. check-in: 5ccae59986 user: ajv-899-334-8894@vsta.org tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to client.py.

6
7
8
9
10
11
12
13










14
15
16
17
18
19
20
#  sign the request so it'll register our current address against
#  a hostname under the dynamic IP subzone.
#
# Invoked as:
#	client.py <server> <hostname> <password>
#
import socket, hashlib, sys, json, os
from utils import tohex, fold











def usage():
    sys.stderr.write("Usage is: %s <server> <port> <hostname> <password>\n" %
	(sys.argv[0],))
    sys.exit(1)

if __name__ == "__main__":







<
>
>
>
>
>
>
>
>
>
>







6
7
8
9
10
11
12

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#  sign the request so it'll register our current address against
#  a hostname under the dynamic IP subzone.
#
# Invoked as:
#	client.py <server> <hostname> <password>
#
import socket, hashlib, sys, json, os


# XOR a byte range into one half its size
def fold(buf):
    l = len(buf)/2
    return ''.join(chr(ord(c1) ^ ord(c2))
	for c1,c2 in zip(buf[:l], buf[l:]))

# Display bytes in hex
def tohex(buf):
    return ''.join(("%02x" % (ord(c),)) for c in buf)

def usage():
    sys.stderr.write("Usage is: %s <server> <port> <hostname> <password>\n" %
	(sys.argv[0],))
    sys.exit(1)

if __name__ == "__main__":

Changes to server.py.

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
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
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

# Dotted domain name helper
class DomainName(str):
    def __getattr__(self, item):
	return DomainName(item + '.' + self)

class DynDNS(object):
    def __init__(self, dom, ipaddr):

	# This is the domain and address we operate under
	D = self.D = DomainName(dom)
	self.Dsuff = D+'.'
	self.IP = ipaddr

	# Time To Live, pretty short as they are, after all, dynamic
	self.TTL = 60 * 1

	# We keep this so we can tickle the serial number
	# It starts at the current second and walks upward
	self.times = [
................................................................................
	    60 * 1,  # minimum
	]

	# We'll rebuild SOA each time self.times has its serial
	#  number updated
	self.set_soa()

	# We're probably going to be asked a *lot* more times
	#  than updated.  Cache responses per host.
	self.cache = {}

    # Build a SOA record, using the current serial number
    def set_soa(self):
	D = self.D
	self.soa_record = SOA(
	    mname=D.ns1,    # primary name server
	    rname=D.admin,  # email of the domain administrator
	    times=self.times
	)

    # New DNS table version, rev our serial in SOA
    def rev(self):
	self.cache.clear()
	self.times[0] += 1
	self.set_soa()

    # Construct an A query response for a dynamic address host
    def dns_response(self, data):
	global hosts

	# Parse request
	request = DNSRecord.parse(data)



	# We only answer 'A' records
	q = request.q
	qt = QTYPE[q.qtype]
	if qt != 'A':
	    return None

	# About our own domain
	qname = str(q.qname)
	if not qname.endswith(self.Dsuff):
	    return None
	lab = q.qname.label


	# Already calculated?
	host = lab[0]
	resp = self.cache.get(host)
	if resp is not None:
	    return resp

	# Only our registered hosts
	ip = hosts.get(host)
	if ip is None:
	    return None

	#
	# Assemble reply
	#
	reply = DNSRecord(DNSHeader(id=request.header.id,
	    qr=1, aa=1, ra=1), q=request.q)
	# IP address for this host
	TTL = self.TTL
	D = self.D



	reply.add_answer(RR(rname=qname,
	    rtype=QTYPE.A, rclass=1, ttl=TTL, rdata=A(self.IP)))

	# Nameserver (by convention, "ns1.dyn-dom.com")
	reply.add_answer(RR(rname=D,
	    rtype=QTYPE.NS, rclass=1, ttl=TTL, rdata=NS(D.ns1)))

	# Authority
	reply.add_answer(RR(rname=D,
	    rtype=QTYPE.SOA, rclass=1, ttl=TTL, rdata=self.soa_record))

	# Formatted answer, cached

	resp = (host, reply.pack())
	self.cache[host] = resp
	return resp

    def serve_dns(self):
	sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	sock.bind(('', 53))
	ignored = 0
	while True:
................................................................................
	# Register
	sys.stderr.write("Update %s: %s -> %s\n" %
	    (host, hosts.get(host, "(none)"), who[0]))
	hosts[host] = who[0]
	dyn_server.rev()

def usage():
    sys.stderr.write("Usage is: %s <domain> <ip-addr>\n" % (sys.argv[0],))
    sys.exit(1)

if __name__ == '__main__':
    if len(sys.argv) != 3:
	usage()

    print "Starting dynamic DNS server..."
    t = threading.Thread(target=serve_dyn)
    t.start()

    print "Starting nameserver..."
    dyn_server = DynDNS(sys.argv[1], sys.argv[2])
    dyn_server.serve_dns()







|




<







 







<
<
<
<











<









>

>
|


<
<

|



<

>
|

<
<
<
<
<
|
|







<


>
>
>
|
|
>



>




|
>

<







 







|



|







|

25
26
27
28
29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
..
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
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

# Dotted domain name helper
class DomainName(str):
    def __getattr__(self, item):
	return DomainName(item + '.' + self)

class DynDNS(object):
    def __init__(self, dom):

	# This is the domain and address we operate under
	D = self.D = DomainName(dom)
	self.Dsuff = D+'.'


	# Time To Live, pretty short as they are, after all, dynamic
	self.TTL = 60 * 1

	# We keep this so we can tickle the serial number
	# It starts at the current second and walks upward
	self.times = [
................................................................................
	    60 * 1,  # minimum
	]

	# We'll rebuild SOA each time self.times has its serial
	#  number updated
	self.set_soa()





    # Build a SOA record, using the current serial number
    def set_soa(self):
	D = self.D
	self.soa_record = SOA(
	    mname=D.ns1,    # primary name server
	    rname=D.admin,  # email of the domain administrator
	    times=self.times
	)

    # New DNS table version, rev our serial in SOA
    def rev(self):

	self.times[0] += 1
	self.set_soa()

    # Construct an A query response for a dynamic address host
    def dns_response(self, data):
	global hosts

	# Parse request
	request = DNSRecord.parse(data)
	# sys.stderr.write("req: %r\n" % (request,))

	# Query type
	# (We only really answer 'A' records.)
	q = request.q
	qt = QTYPE[q.qtype]



	# About our own domain?
	qname = str(q.qname)
	if not qname.endswith(self.Dsuff):
	    return None


	# Only our registered hosts
	lab = q.qname.label
	host = lab[0]





	ipaddr = hosts.get(host)
	if ipaddr is None:
	    return None

	#
	# Assemble reply
	#
	reply = DNSRecord(DNSHeader(id=request.header.id,
	    qr=1, aa=1, ra=1), q=request.q)

	TTL = self.TTL
	D = self.D

	# IP address?
	if qt == 'A':
	    reply.add_answer(RR(rname=qname,
		rtype=QTYPE.A, rclass=1, ttl=TTL, rdata=A(ipaddr)))

	# Nameserver (by convention, "ns1.dyn-dom.com")
	reply.add_answer(RR(rname=D,
	    rtype=QTYPE.NS, rclass=1, ttl=TTL, rdata=NS(D.ns1)))

	# Authority
	reply.add_answer(RR(rname=D,
	    rtype=QTYPE.SOA, rclass=1, ttl=TTL, rdata=self.soa_record))

	# Formatted answer
	# sys.stderr.write("reply: %r\n" % (reply,))
	resp = (host, reply.pack())

	return resp

    def serve_dns(self):
	sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	sock.bind(('', 53))
	ignored = 0
	while True:
................................................................................
	# Register
	sys.stderr.write("Update %s: %s -> %s\n" %
	    (host, hosts.get(host, "(none)"), who[0]))
	hosts[host] = who[0]
	dyn_server.rev()

def usage():
    sys.stderr.write("Usage is: %s <domain>\n" % (sys.argv[0],))
    sys.exit(1)

if __name__ == '__main__':
    if len(sys.argv) != 2:
	usage()

    print "Starting dynamic DNS server..."
    t = threading.Thread(target=serve_dyn)
    t.start()

    print "Starting nameserver..."
    dyn_server = DynDNS(sys.argv[1])
    dyn_server.serve_dns()