Browse Source

DNS checking API

Sebastian Lohff 2 years ago
parent
commit
10580c4b4b
6 changed files with 222 additions and 2 deletions
  1. 129
    0
      api/dnshelper.py
  2. 5
    2
      api/views.py
  3. 1
    0
      domains/urls.py
  4. 8
    0
      domains/views.py
  5. BIN
      static/img/loader.gif
  6. 79
    0
      templates/domains/domain_check.html

+ 129
- 0
api/dnshelper.py View File

@@ -0,0 +1,129 @@
1
+import dns.name
2
+import dns.message
3
+import dns.query
4
+import dns.resolver
5
+
6
+#from collections import defaultdict
7
+# FIXME: DNS timeouts
8
+
9
+def compareRecords(rrset, expected):
10
+	result = {
11
+		"nameMissing": [],
12
+		"rrMissing": [],
13
+		"rrExtra": [],
14
+	}
15
+
16
+	for domain, rrtype, content in expected:
17
+		for rrrec in rrset:
18
+			if domain == rrrec.name.to_text() and dns.rdatatype.from_text(rrtype) == rrrec.rdtype:
19
+				for name in content:
20
+					if name not in map(lambda _x: _x.to_text(), rrrec.items):
21
+						# record missing
22
+						result["rrMissing"].append((domain, rrtype, name))
23
+
24
+				for item in rrrec.items:
25
+					if item.to_text() not in content:
26
+						# superfluous record
27
+						result["rrExtra"].append((domain, rrtype, item.to_text()))
28
+
29
+				break
30
+		else:
31
+			# domain +  rr nicht in nameserver
32
+			result["nameMissing"].append((domain, rrtype))
33
+
34
+	success = not any(len(_x) > 0 for _x in result.values())
35
+	print("NARF", success, result)
36
+
37
+	return success, result
38
+
39
+
40
+def dnsQuery(domain, rrType, nameserverIp):
41
+	dname = dns.name.from_text(domain)
42
+	req = dns.message.make_query(dname, dns.rdatatype.from_text(rrType))
43
+	resp = dns.query.udp(req, nameserverIp, timeout=2.0)
44
+
45
+	if resp.rcode() != dns.rcode.NXDOMAIN:
46
+		rrset = resp.answer + resp.authority + resp.additional
47
+		return True, rrset
48
+	else:
49
+		return False, []
50
+
51
+
52
+def checkDomain(domain, tldNameserver, nameservers):
53
+	print(domain, tldNameserver, nameservers)
54
+	result = []
55
+
56
+	# build record set
57
+	nsRecords = [(domain, "NS", list(ns.name for ns in nameservers))]
58
+	glueRecords = []
59
+	for ns in nameservers:
60
+		if ns.name.endswith("." + domain):
61
+			if ns.glueIPv4 or ns.glueIPv6:
62
+				if ns.glueIPv4:
63
+					glueRecords.append((ns.name, "A", [ns.glueIPv4]))
64
+				if ns.glueIPv6:
65
+					glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
66
+			else:
67
+				result.append(("err", "Nameserver %s is under domain %s, but has no glue entries." % (ns.name, domain)))
68
+
69
+	print(nsRecords, glueRecords)
70
+
71
+	# 1. TLD nameserver
72
+	try:
73
+		found, rrset = dnsQuery(domain, "ANY", tldNameserver)
74
+		if found:
75
+			#print(rrset, nsRecords, glueRecords)
76
+			success, errors = compareRecords(rrset, nsRecords + glueRecords)
77
+			if success:
78
+				result.append(("succ", "All records present in TLD nameserver"))
79
+			else:
80
+				result.append(("err", "Record mismatch between TLD nameserver and WHOIS database", errors))
81
+		else:
82
+			result.append(("err", "Domain %s not found in TLD nameserver" % (domain,)))
83
+	except (dns.exception.Timeout, OSError):
84
+		result.append(("err", "TLD nameserver is currently not reachable"))
85
+
86
+	# find other records...
87
+
88
+	# 2. your nameservers
89
+	for ns in nameservers:
90
+		addr = None
91
+		if ns.glueIPv4:
92
+			addr = ns.glueIPv4
93
+		elif ns.glueIPv6:
94
+			addr = ns.glueIPv6
95
+		else:
96
+			for rrType in ("A", "AAAA"):
97
+				try:
98
+					r = dns.resolver.Resolver()
99
+					r.timeout = 2.0
100
+					q = r.query(ns.name, rdtype=dns.rdatatype.from_text(rrType))
101
+					addr = q.response.answer[0].items[0].address
102
+				except (dns.exception.DNSException, OSError):
103
+					pass
104
+
105
+		if addr:
106
+			err = False
107
+			errDict = {"nameMissing": [], "rrMissing": [], "rrExtra": []}
108
+			try:
109
+				for rec in (nsRecords + glueRecords):
110
+					found, rrset = dnsQuery(rec[0], rec[1], addr)
111
+
112
+					success, errors = compareRecords(rrset, nsRecords + glueRecords)
113
+					if not success:
114
+						err = True
115
+						for k in errors.keys():
116
+							errDict[k].extend(errors[k])
117
+
118
+				if not err:
119
+					result.append(("succ", "Nameserver %s is configured correctly" % ns.name))
120
+				else:
121
+					print(" ==> ", errDict, addr)
122
+					result.append(("err", "Nameserver %s recordset does not match the database" % (ns.name,), errDict))
123
+			except (dns.exception.DNSException, OSError):
124
+				result.append(("err", "Nameserver %s is not reachable (via %s)" % (ns.name, addr)))
125
+				
126
+		else:
127
+			result.append(("err", "Can't resolv an ip address for nameserver %s" % ns.name))
128
+
129
+	return result

+ 5
- 2
api/views.py View File

@@ -7,6 +7,7 @@ from django.db.models import Q
7 7
 from whoisdb.models import ASBlock, ASNumber, InetNum
8 8
 from domains.models import Domain, ReverseZone
9 9
 from dnmgmt.settings import TLD_NAMESERVERS
10
+from .dnshelper import checkDomain as helperCheckDomain
10 11
 
11 12
 import ipaddress
12 13
 
@@ -187,7 +188,8 @@ def checkDomain(request):
187 188
 
188 189
 		ret["success"] = True
189 190
 		ret["domain"] = domain.name
190
-		ret["result"] = checkDomain(domain.name, TLD_NAMESERVERS, domain.nameservers.all())
191
+		# FIXME: change this if we ever have more than one...
192
+		ret["result"] = helperCheckDomain(domain.name, TLD_NAMESERVERS[0], domain.nameservers.all())
191 193
 	except Domain.DoesNotExist:
192 194
 		ret["errorMsg"] = "Domain does not exist"
193 195
 
@@ -210,7 +212,8 @@ def checkRzone(request):
210 212
 			raise ReverseZone.DoesNotExist()
211 213
 
212 214
 		ret["success"] = True
213
-		ret["result"] = checkDomain(rzone.name, TLD_NAMESERVERS, rzone.nameservers.all())
215
+		# FIXME: change this if we ever have more than one...
216
+		ret["result"] = helperCheckDomain(rzone.name, TLD_NAMESERVERS[0], rzone.nameservers.all())
214 217
 	except Domain.DoesNotExist:
215 218
 		ret["errorMsg"] = "ReverseZone does not exist"
216 219
 

+ 1
- 0
domains/urls.py View File

@@ -6,6 +6,7 @@ urlpatterns = [
6 6
 	url(r'^$', domains_views.overview, name='overview'),
7 7
 
8 8
 	url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'),
9
+	url(r'domain/check/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainCheck.as_view(), name='domain-check'),
9 10
 	url(r'domain/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail.as_view(), name='domain-show'),
10 11
 	url(r'domain/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainEdit.as_view(), name='domain-edit'),
11 12
 	url(r'domain/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDelete.as_view(), name='domain-delete'),

+ 8
- 0
domains/views.py View File

@@ -34,6 +34,14 @@ class DomainCreate(LoginRequiredMixin, CreateView):
34 34
 		return kwargs
35 35
 
36 36
 
37
+class DomainCheck(LoginRequiredMixin, DetailView):
38
+	model = Domain
39
+	slug_field = "name"
40
+	slug_url_kwarg = "domain"
41
+	context_object_name = "domain"
42
+
43
+	template_name = "domains/domain_check.html"
44
+
37 45
 class DomainDetail(LoginRequiredMixin, DetailView):
38 46
 	model = Domain
39 47
 	slug_field = "name"

BIN
static/img/loader.gif View File


+ 79
- 0
templates/domains/domain_check.html View File

@@ -0,0 +1,79 @@
1
+{% extends "base.html" %}
2
+
3
+{% load staticfiles %}
4
+
5
+{% block content %}
6
+<div class="row">
7
+	<div class="col-sm-12">
8
+		<div class="panel panel-default">
9
+			<div class="panel-heading">Checking domain {{ domain.name }}</div>
10
+			<div class="panel-body">
11
+				<div id="dnscontent">
12
+
13
+				</div>
14
+			</div>
15
+		</div>
16
+	</div>
17
+</div>
18
+
19
+<script>
20
+function addMessage(div, level, message) {
21
+	$(div).html($(div).html() + ' <div role="alert" class="alert alert-'+level+' alert-dismissible"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> '+message+'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>');
22
+}
23
+
24
+function addRecord(d, key, title) {
25
+	var msg = "";
26
+	if(d[key].length > 0) {
27
+		msg += "\n<p>" + title + ":<ul>";
28
+		for(var i=0; i<d[key].length; i++)
29
+			msg += "<li>" + d[key][i].join(" ") + "</li>";
30
+		msg += "</ul></p>";
31
+	}
32
+
33
+	return msg;
34
+}
35
+
36
+$(document).ready(function() {
37
+	$("#dnscontent").text("Running API request...");
38
+	$.get({
39
+		url: "{% url "api:domain-check" %}",
40
+		data: {domain: "{{ domain.name }}"},
41
+	beforeSend: function() {
42
+		$("#dnscontent").html('<img src="{% static "img/loader.gif" %}">');
43
+	},
44
+	success: function(data) {
45
+		$("#dnscontent").html("<p>Checked domain: " + data.domain + "</p>");
46
+		if(data.success) {
47
+			for(message of data.result) {
48
+				var level = "success";
49
+				if(message[0] == "err")
50
+					level = "danger";
51
+
52
+				var msg = message[1];
53
+				if(message.length > 2) {
54
+					var d = message[2];
55
+
56
+
57
+					msg += addRecord(d, "nameMissing", "Missing names");
58
+					msg += addRecord(d, "rrExtra", "Extra records");
59
+					msg += addRecord(d, "rrMissing", "Missing records");
60
+
61
+					//console.log(d);
62
+					//if(d["rrMissing"].length > 0) {
63
+					//	msg += "\n<p>Missing records:<ul>";
64
+					//	for(var i=0; i<d["rrMissing"].length; i++)
65
+					//		msg += "<li>" + d["rrMissing"][i].join(" ") + "</li>";
66
+					//	msg += "</ul></p>";
67
+					//}
68
+				}
69
+				addMessage("#dnscontent", level, msg);
70
+			}
71
+		} else {
72
+			addMessage("#dnscontent", "danger", "Could not get DNS data from API!");
73
+		}
74
+	}
75
+	});
76
+});
77
+</script>
78
+{% endblock %}
79
+

Loading…
Cancel
Save