dargslan-dns-resolver 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dargslan_dns_resolver-1.0.0/PKG-INFO +66 -0
- dargslan_dns_resolver-1.0.0/README.md +45 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver/__init__.py +269 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver/cli.py +55 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver.egg-info/PKG-INFO +66 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver.egg-info/SOURCES.txt +9 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver.egg-info/dependency_links.txt +1 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver.egg-info/entry_points.txt +2 -0
- dargslan_dns_resolver-1.0.0/dargslan_dns_resolver.egg-info/top_level.txt +1 -0
- dargslan_dns_resolver-1.0.0/pyproject.toml +31 -0
- dargslan_dns_resolver-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dargslan-dns-resolver
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: DNS resolver tester — measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues
|
|
5
|
+
Author-email: Dargslan <info@dargslan.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://dargslan.com
|
|
8
|
+
Project-URL: Documentation, https://dargslan.com/blog
|
|
9
|
+
Project-URL: Repository, https://github.com/Dargslan
|
|
10
|
+
Project-URL: Free Cheat Sheets, https://dargslan.com/cheat-sheets
|
|
11
|
+
Project-URL: Linux & DevOps Books, https://dargslan.com/books
|
|
12
|
+
Keywords: dns,resolver,dnssec,nameserver,linux,sysadmin,devops,network
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# dargslan-dns-resolver
|
|
23
|
+
|
|
24
|
+
**DNS Resolver Tester** — Measure resolution time, compare resolvers, test DNSSEC, and detect DNS configuration issues. Zero external dependencies.
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/dargslan-dns-resolver/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install dargslan-dns-resolver
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## CLI Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dargslan-dns report # Full DNS report
|
|
39
|
+
dargslan-dns resolve google.com # Quick resolution test
|
|
40
|
+
dargslan-dns dig google.com MX # Dig-style query
|
|
41
|
+
dargslan-dns compare 8.8.8.8 1.1.1.1 # Compare resolvers
|
|
42
|
+
dargslan-dns dnssec google.com # DNSSEC validation test
|
|
43
|
+
dargslan-dns reverse 8.8.8.8 # Reverse lookup
|
|
44
|
+
dargslan-dns config # Show resolver config
|
|
45
|
+
dargslan-dns json # JSON output
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Python API
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from dargslan_dns_resolver import DNSResolver
|
|
52
|
+
dr = DNSResolver()
|
|
53
|
+
result = dr.resolve("dargslan.com")
|
|
54
|
+
comparison = dr.compare_resolvers(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
|
|
55
|
+
dr.print_report()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## More from Dargslan
|
|
59
|
+
|
|
60
|
+
- [Dargslan.com](https://dargslan.com) — Linux & DevOps eBook Store
|
|
61
|
+
- [Free Cheat Sheets](https://dargslan.com/cheat-sheets)
|
|
62
|
+
- [Blog & Tutorials](https://dargslan.com/blog)
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# dargslan-dns-resolver
|
|
2
|
+
|
|
3
|
+
**DNS Resolver Tester** — Measure resolution time, compare resolvers, test DNSSEC, and detect DNS configuration issues. Zero external dependencies.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/dargslan-dns-resolver/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install dargslan-dns-resolver
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## CLI Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
dargslan-dns report # Full DNS report
|
|
18
|
+
dargslan-dns resolve google.com # Quick resolution test
|
|
19
|
+
dargslan-dns dig google.com MX # Dig-style query
|
|
20
|
+
dargslan-dns compare 8.8.8.8 1.1.1.1 # Compare resolvers
|
|
21
|
+
dargslan-dns dnssec google.com # DNSSEC validation test
|
|
22
|
+
dargslan-dns reverse 8.8.8.8 # Reverse lookup
|
|
23
|
+
dargslan-dns config # Show resolver config
|
|
24
|
+
dargslan-dns json # JSON output
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Python API
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from dargslan_dns_resolver import DNSResolver
|
|
31
|
+
dr = DNSResolver()
|
|
32
|
+
result = dr.resolve("dargslan.com")
|
|
33
|
+
comparison = dr.compare_resolvers(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
|
|
34
|
+
dr.print_report()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## More from Dargslan
|
|
38
|
+
|
|
39
|
+
- [Dargslan.com](https://dargslan.com) — Linux & DevOps eBook Store
|
|
40
|
+
- [Free Cheat Sheets](https://dargslan.com/cheat-sheets)
|
|
41
|
+
- [Blog & Tutorials](https://dargslan.com/blog)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dargslan-dns-resolver — DNS Resolver Tester
|
|
3
|
+
|
|
4
|
+
Measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues.
|
|
5
|
+
Zero external dependencies — uses socket and subprocess.
|
|
6
|
+
|
|
7
|
+
Homepage: https://dargslan.com
|
|
8
|
+
Books: https://dargslan.com/books
|
|
9
|
+
Blog: https://dargslan.com/blog
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.0.0"
|
|
13
|
+
__author__ = "Dargslan"
|
|
14
|
+
__url__ = "https://dargslan.com"
|
|
15
|
+
|
|
16
|
+
import socket
|
|
17
|
+
import subprocess
|
|
18
|
+
import time
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
WELL_KNOWN_RESOLVERS = {
|
|
25
|
+
'8.8.8.8': 'Google DNS',
|
|
26
|
+
'8.8.4.4': 'Google DNS (secondary)',
|
|
27
|
+
'1.1.1.1': 'Cloudflare DNS',
|
|
28
|
+
'1.0.0.1': 'Cloudflare DNS (secondary)',
|
|
29
|
+
'9.9.9.9': 'Quad9 DNS',
|
|
30
|
+
'208.67.222.222': 'OpenDNS',
|
|
31
|
+
'208.67.220.220': 'OpenDNS (secondary)',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DNSResolver:
|
|
36
|
+
"""Test DNS resolver performance and configuration."""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def _run(self, args, timeout=10):
|
|
42
|
+
try:
|
|
43
|
+
result = subprocess.run(args, capture_output=True, text=True, timeout=timeout)
|
|
44
|
+
return result.stdout.strip(), result.returncode
|
|
45
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
46
|
+
return '', 1
|
|
47
|
+
|
|
48
|
+
def get_system_resolvers(self):
|
|
49
|
+
resolvers = []
|
|
50
|
+
try:
|
|
51
|
+
with open('/etc/resolv.conf', 'r') as f:
|
|
52
|
+
for line in f:
|
|
53
|
+
line = line.strip()
|
|
54
|
+
if line.startswith('nameserver'):
|
|
55
|
+
parts = line.split()
|
|
56
|
+
if len(parts) >= 2:
|
|
57
|
+
ip = parts[1]
|
|
58
|
+
name = WELL_KNOWN_RESOLVERS.get(ip, '')
|
|
59
|
+
resolvers.append({'ip': ip, 'name': name})
|
|
60
|
+
except (FileNotFoundError, PermissionError):
|
|
61
|
+
pass
|
|
62
|
+
return resolvers
|
|
63
|
+
|
|
64
|
+
def resolve(self, hostname, record_type='A'):
|
|
65
|
+
start = time.time()
|
|
66
|
+
try:
|
|
67
|
+
if record_type == 'A':
|
|
68
|
+
results = socket.getaddrinfo(hostname, None, socket.AF_INET)
|
|
69
|
+
ips = list(set(r[4][0] for r in results))
|
|
70
|
+
elif record_type == 'AAAA':
|
|
71
|
+
results = socket.getaddrinfo(hostname, None, socket.AF_INET6)
|
|
72
|
+
ips = list(set(r[4][0] for r in results))
|
|
73
|
+
else:
|
|
74
|
+
ips = []
|
|
75
|
+
|
|
76
|
+
elapsed = (time.time() - start) * 1000
|
|
77
|
+
return {
|
|
78
|
+
'hostname': hostname,
|
|
79
|
+
'type': record_type,
|
|
80
|
+
'results': ips,
|
|
81
|
+
'time_ms': round(elapsed, 2),
|
|
82
|
+
'success': True,
|
|
83
|
+
}
|
|
84
|
+
except socket.gaierror as e:
|
|
85
|
+
elapsed = (time.time() - start) * 1000
|
|
86
|
+
return {
|
|
87
|
+
'hostname': hostname,
|
|
88
|
+
'type': record_type,
|
|
89
|
+
'error': str(e),
|
|
90
|
+
'time_ms': round(elapsed, 2),
|
|
91
|
+
'success': False,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def dig(self, hostname, record_type='A', server=None):
|
|
95
|
+
args = ['dig', '+noall', '+answer', '+stats', hostname, record_type]
|
|
96
|
+
if server:
|
|
97
|
+
args.insert(1, f'@{server}')
|
|
98
|
+
|
|
99
|
+
start = time.time()
|
|
100
|
+
output, rc = self._run(args)
|
|
101
|
+
elapsed = (time.time() - start) * 1000
|
|
102
|
+
|
|
103
|
+
records = []
|
|
104
|
+
query_time = None
|
|
105
|
+
for line in output.split('\n'):
|
|
106
|
+
if line.startswith(';; Query time:'):
|
|
107
|
+
match = re.search(r'(\d+)\s+msec', line)
|
|
108
|
+
if match:
|
|
109
|
+
query_time = int(match.group(1))
|
|
110
|
+
elif line.strip() and not line.startswith(';'):
|
|
111
|
+
records.append(line.strip())
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
'hostname': hostname,
|
|
115
|
+
'type': record_type,
|
|
116
|
+
'server': server,
|
|
117
|
+
'records': records,
|
|
118
|
+
'query_time_ms': query_time,
|
|
119
|
+
'total_time_ms': round(elapsed, 2),
|
|
120
|
+
'success': rc == 0,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def benchmark_resolver(self, server, domains=None, rounds=3):
|
|
124
|
+
if domains is None:
|
|
125
|
+
domains = ['google.com', 'github.com', 'amazon.com', 'cloudflare.com', 'wikipedia.org']
|
|
126
|
+
|
|
127
|
+
times = []
|
|
128
|
+
for domain in domains:
|
|
129
|
+
for _ in range(rounds):
|
|
130
|
+
result = self.dig(domain, server=server)
|
|
131
|
+
if result.get('query_time_ms') is not None:
|
|
132
|
+
times.append(result['query_time_ms'])
|
|
133
|
+
elif result.get('total_time_ms'):
|
|
134
|
+
times.append(result['total_time_ms'])
|
|
135
|
+
|
|
136
|
+
if not times:
|
|
137
|
+
return {'server': server, 'error': 'No successful queries'}
|
|
138
|
+
|
|
139
|
+
times.sort()
|
|
140
|
+
return {
|
|
141
|
+
'server': server,
|
|
142
|
+
'name': WELL_KNOWN_RESOLVERS.get(server, ''),
|
|
143
|
+
'queries': len(times),
|
|
144
|
+
'avg_ms': round(sum(times) / len(times), 2),
|
|
145
|
+
'min_ms': round(times[0], 2),
|
|
146
|
+
'max_ms': round(times[-1], 2),
|
|
147
|
+
'p50_ms': round(times[len(times) // 2], 2),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
def compare_resolvers(self, servers=None):
|
|
151
|
+
if servers is None:
|
|
152
|
+
servers = ['8.8.8.8', '1.1.1.1', '9.9.9.9']
|
|
153
|
+
results = []
|
|
154
|
+
for server in servers:
|
|
155
|
+
results.append(self.benchmark_resolver(server, rounds=2))
|
|
156
|
+
results.sort(key=lambda x: x.get('avg_ms', 9999))
|
|
157
|
+
return results
|
|
158
|
+
|
|
159
|
+
def check_dnssec(self, hostname):
|
|
160
|
+
output, rc = self._run(['dig', '+dnssec', '+short', hostname, 'A'])
|
|
161
|
+
has_rrsig = 'RRSIG' in output or bool(re.search(r'\bRRSIG\b', output))
|
|
162
|
+
|
|
163
|
+
output2, _ = self._run(['dig', '+noall', '+comments', hostname, 'A'])
|
|
164
|
+
ad_flag = 'flags:' in output2 and ' ad' in output2
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
'hostname': hostname,
|
|
168
|
+
'dnssec_signed': has_rrsig,
|
|
169
|
+
'ad_flag': ad_flag,
|
|
170
|
+
'validated': ad_flag,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
def reverse_lookup(self, ip):
|
|
174
|
+
try:
|
|
175
|
+
hostname = socket.gethostbyaddr(ip)
|
|
176
|
+
return {'ip': ip, 'hostname': hostname[0], 'aliases': hostname[1]}
|
|
177
|
+
except (socket.herror, socket.gaierror) as e:
|
|
178
|
+
return {'ip': ip, 'error': str(e)}
|
|
179
|
+
|
|
180
|
+
def check_resolv_conf(self):
|
|
181
|
+
info = {'resolvers': self.get_system_resolvers()}
|
|
182
|
+
content = ''
|
|
183
|
+
try:
|
|
184
|
+
with open('/etc/resolv.conf', 'r') as f:
|
|
185
|
+
content = f.read()
|
|
186
|
+
except (FileNotFoundError, PermissionError):
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
for line in content.split('\n'):
|
|
190
|
+
line = line.strip()
|
|
191
|
+
if line.startswith('search ') or line.startswith('domain '):
|
|
192
|
+
info['search_domains'] = line.split()[1:]
|
|
193
|
+
if line.startswith('options '):
|
|
194
|
+
info['options'] = line.split()[1:]
|
|
195
|
+
|
|
196
|
+
is_symlink = os.path.islink('/etc/resolv.conf')
|
|
197
|
+
info['is_symlink'] = is_symlink
|
|
198
|
+
if is_symlink:
|
|
199
|
+
info['symlink_target'] = os.readlink('/etc/resolv.conf')
|
|
200
|
+
|
|
201
|
+
return info
|
|
202
|
+
|
|
203
|
+
def audit(self):
|
|
204
|
+
issues = []
|
|
205
|
+
resolvers = self.get_system_resolvers()
|
|
206
|
+
|
|
207
|
+
if not resolvers:
|
|
208
|
+
issues.append({'severity': 'critical', 'message': 'No DNS resolvers configured'})
|
|
209
|
+
elif len(resolvers) < 2:
|
|
210
|
+
issues.append({'severity': 'warning', 'message': 'Only one DNS resolver configured — no redundancy'})
|
|
211
|
+
|
|
212
|
+
for r in resolvers:
|
|
213
|
+
result = self.resolve('google.com')
|
|
214
|
+
if not result['success']:
|
|
215
|
+
issues.append({'severity': 'critical', 'message': f"Resolver {r['ip']} failed to resolve google.com"})
|
|
216
|
+
elif result['time_ms'] > 500:
|
|
217
|
+
issues.append({'severity': 'warning', 'message': f"DNS resolution slow ({result['time_ms']}ms)"})
|
|
218
|
+
|
|
219
|
+
return issues
|
|
220
|
+
|
|
221
|
+
def print_report(self, domains=None):
|
|
222
|
+
resolvers = self.get_system_resolvers()
|
|
223
|
+
resolv_info = self.check_resolv_conf()
|
|
224
|
+
issues = self.audit()
|
|
225
|
+
|
|
226
|
+
print(f"\n{'='*60}")
|
|
227
|
+
print(f" DNS Resolver Report")
|
|
228
|
+
print(f"{'='*60}")
|
|
229
|
+
|
|
230
|
+
print(f"\n System Resolvers ({len(resolvers)}):")
|
|
231
|
+
for r in resolvers:
|
|
232
|
+
name = f" ({r['name']})" if r['name'] else ''
|
|
233
|
+
print(f" {r['ip']}{name}")
|
|
234
|
+
|
|
235
|
+
if resolv_info.get('search_domains'):
|
|
236
|
+
print(f" Search Domains: {' '.join(resolv_info['search_domains'])}")
|
|
237
|
+
if resolv_info.get('is_symlink'):
|
|
238
|
+
print(f" resolv.conf -> {resolv_info.get('symlink_target', '?')}")
|
|
239
|
+
|
|
240
|
+
test_domains = domains or ['google.com', 'github.com', 'dargslan.com']
|
|
241
|
+
print(f"\n Resolution Tests:")
|
|
242
|
+
for d in test_domains:
|
|
243
|
+
r = self.resolve(d)
|
|
244
|
+
if r['success']:
|
|
245
|
+
print(f" {d}: {', '.join(r['results'][:3])} ({r['time_ms']}ms)")
|
|
246
|
+
else:
|
|
247
|
+
print(f" {d}: FAILED — {r.get('error','')}")
|
|
248
|
+
|
|
249
|
+
print(f"\n Resolver Comparison:")
|
|
250
|
+
comparison = self.compare_resolvers(rounds=1)
|
|
251
|
+
for c in comparison:
|
|
252
|
+
if c.get('error'):
|
|
253
|
+
print(f" {c['server']:>15s}: FAILED")
|
|
254
|
+
else:
|
|
255
|
+
name = f" ({c['name']})" if c.get('name') else ''
|
|
256
|
+
print(f" {c['server']:>15s}{name}: avg={c['avg_ms']}ms min={c['min_ms']}ms max={c['max_ms']}ms")
|
|
257
|
+
|
|
258
|
+
if issues:
|
|
259
|
+
print(f"\n Issues ({len(issues)}):")
|
|
260
|
+
for i in issues:
|
|
261
|
+
print(f" [{i['severity'].upper():8s}] {i['message']}")
|
|
262
|
+
|
|
263
|
+
print(f"\n{'='*60}")
|
|
264
|
+
print(f" More tools: https://dargslan.com")
|
|
265
|
+
print(f" eBooks: https://dargslan.com/books")
|
|
266
|
+
print(f"{'='*60}\n")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
__all__ = ["DNSResolver"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""CLI for dargslan-dns-resolver — https://dargslan.com"""
|
|
2
|
+
import argparse
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
parser = argparse.ArgumentParser(
|
|
6
|
+
description="Dargslan DNS Resolver — DNS resolver tester",
|
|
7
|
+
epilog="More tools at https://dargslan.com | eBooks: https://dargslan.com/books",
|
|
8
|
+
)
|
|
9
|
+
parser.add_argument("command", nargs="?", default="report",
|
|
10
|
+
choices=["report", "resolve", "dig", "compare", "dnssec", "reverse", "config", "issues", "json"],
|
|
11
|
+
help="Command (default: report)")
|
|
12
|
+
parser.add_argument("targets", nargs="*", help="Hostnames or IPs")
|
|
13
|
+
parser.add_argument("-s", "--server", help="DNS server to use")
|
|
14
|
+
parser.add_argument("-t", "--type", default="A", help="Record type (A, AAAA, MX, etc.)")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
from dargslan_dns_resolver import DNSResolver
|
|
18
|
+
dr = DNSResolver()
|
|
19
|
+
import json
|
|
20
|
+
|
|
21
|
+
targets = [t for t in args.targets if t not in ('report','resolve','dig','compare','dnssec','reverse','config','issues','json')]
|
|
22
|
+
|
|
23
|
+
if args.command == 'report': dr.print_report(targets or None)
|
|
24
|
+
elif args.command == 'resolve':
|
|
25
|
+
for t in targets or ['google.com']:
|
|
26
|
+
r = dr.resolve(t, args.type)
|
|
27
|
+
if r['success']:
|
|
28
|
+
print(f" {t}: {', '.join(r['results'])} ({r['time_ms']}ms)")
|
|
29
|
+
else:
|
|
30
|
+
print(f" {t}: FAILED — {r.get('error','')}")
|
|
31
|
+
elif args.command == 'dig':
|
|
32
|
+
for t in targets or ['google.com']:
|
|
33
|
+
r = dr.dig(t, args.type, server=args.server)
|
|
34
|
+
for rec in r['records']: print(f" {rec}")
|
|
35
|
+
if r.get('query_time_ms'): print(f" Query time: {r['query_time_ms']}ms")
|
|
36
|
+
elif args.command == 'compare':
|
|
37
|
+
for c in dr.compare_resolvers(targets or None):
|
|
38
|
+
if c.get('error'): print(f" {c['server']}: FAILED")
|
|
39
|
+
else: print(f" {c['server']:>15s}: avg={c['avg_ms']}ms")
|
|
40
|
+
elif args.command == 'dnssec':
|
|
41
|
+
for t in targets or ['google.com']:
|
|
42
|
+
r = dr.check_dnssec(t)
|
|
43
|
+
print(f" {t}: signed={r['dnssec_signed']} validated={r['validated']}")
|
|
44
|
+
elif args.command == 'reverse':
|
|
45
|
+
for t in targets:
|
|
46
|
+
r = dr.reverse_lookup(t)
|
|
47
|
+
print(f" {t}: {r.get('hostname', r.get('error',''))}")
|
|
48
|
+
elif args.command == 'config':
|
|
49
|
+
c = dr.check_resolv_conf()
|
|
50
|
+
for r in c['resolvers']: print(f" Resolver: {r['ip']} {r.get('name','')}")
|
|
51
|
+
elif args.command == 'issues':
|
|
52
|
+
for i in dr.audit(): print(f" [{i['severity'].upper()}] {i['message']}")
|
|
53
|
+
elif args.command == 'json': print(json.dumps(dr.audit(), indent=2))
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__": main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dargslan-dns-resolver
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: DNS resolver tester — measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues
|
|
5
|
+
Author-email: Dargslan <info@dargslan.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://dargslan.com
|
|
8
|
+
Project-URL: Documentation, https://dargslan.com/blog
|
|
9
|
+
Project-URL: Repository, https://github.com/Dargslan
|
|
10
|
+
Project-URL: Free Cheat Sheets, https://dargslan.com/cheat-sheets
|
|
11
|
+
Project-URL: Linux & DevOps Books, https://dargslan.com/books
|
|
12
|
+
Keywords: dns,resolver,dnssec,nameserver,linux,sysadmin,devops,network
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# dargslan-dns-resolver
|
|
23
|
+
|
|
24
|
+
**DNS Resolver Tester** — Measure resolution time, compare resolvers, test DNSSEC, and detect DNS configuration issues. Zero external dependencies.
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/dargslan-dns-resolver/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install dargslan-dns-resolver
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## CLI Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dargslan-dns report # Full DNS report
|
|
39
|
+
dargslan-dns resolve google.com # Quick resolution test
|
|
40
|
+
dargslan-dns dig google.com MX # Dig-style query
|
|
41
|
+
dargslan-dns compare 8.8.8.8 1.1.1.1 # Compare resolvers
|
|
42
|
+
dargslan-dns dnssec google.com # DNSSEC validation test
|
|
43
|
+
dargslan-dns reverse 8.8.8.8 # Reverse lookup
|
|
44
|
+
dargslan-dns config # Show resolver config
|
|
45
|
+
dargslan-dns json # JSON output
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Python API
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from dargslan_dns_resolver import DNSResolver
|
|
52
|
+
dr = DNSResolver()
|
|
53
|
+
result = dr.resolve("dargslan.com")
|
|
54
|
+
comparison = dr.compare_resolvers(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
|
|
55
|
+
dr.print_report()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## More from Dargslan
|
|
59
|
+
|
|
60
|
+
- [Dargslan.com](https://dargslan.com) — Linux & DevOps eBook Store
|
|
61
|
+
- [Free Cheat Sheets](https://dargslan.com/cheat-sheets)
|
|
62
|
+
- [Blog & Tutorials](https://dargslan.com/blog)
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
dargslan_dns_resolver/__init__.py
|
|
4
|
+
dargslan_dns_resolver/cli.py
|
|
5
|
+
dargslan_dns_resolver.egg-info/PKG-INFO
|
|
6
|
+
dargslan_dns_resolver.egg-info/SOURCES.txt
|
|
7
|
+
dargslan_dns_resolver.egg-info/dependency_links.txt
|
|
8
|
+
dargslan_dns_resolver.egg-info/entry_points.txt
|
|
9
|
+
dargslan_dns_resolver.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dargslan_dns_resolver
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dargslan-dns-resolver"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "DNS resolver tester — measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.7"
|
|
12
|
+
authors = [{name = "Dargslan", email = "info@dargslan.com"}]
|
|
13
|
+
keywords = ["dns", "resolver", "dnssec", "nameserver", "linux", "sysadmin", "devops", "network"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: System Administrators",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: System :: Networking",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://dargslan.com"
|
|
25
|
+
Documentation = "https://dargslan.com/blog"
|
|
26
|
+
Repository = "https://github.com/Dargslan"
|
|
27
|
+
"Free Cheat Sheets" = "https://dargslan.com/cheat-sheets"
|
|
28
|
+
"Linux & DevOps Books" = "https://dargslan.com/books"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
dargslan-dns = "dargslan_dns_resolver.cli:main"
|