@k03mad/dns-leak 2.0.2 → 3.0.1
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/README.md +10 -20
- package/app/api/CloudPing.js +66 -0
- package/app/api/IPLeak.js +10 -10
- package/app/api/IPWhois.js +4 -4
- package/app/api/NextDNS.js +38 -0
- package/app/api/_index.js +4 -0
- package/app/helpers/spinner.js +2 -2
- package/app/helpers/text.js +55 -8
- package/app/run.js +42 -18
- package/package.json +3 -2
- package/app/api/index.js +0 -2
package/README.md
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# DNS leak test
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
— [ipleak.net](https://
|
|
5
|
-
— [ipwhois.io](https://ipwhois.io
|
|
3
|
+
Using API/tools:\
|
|
4
|
+
— [ipleak.net](https://airvpn.org/forums/topic/14737-api)\
|
|
5
|
+
— [ipwhois.io](https://ipwhois.io/documentation)\
|
|
6
|
+
— [cloudping.cloud](https://www.cloudping.cloud/cdn)\
|
|
7
|
+
— [nextdns.io](https://test.nextdns.io/)
|
|
6
8
|
|
|
7
9
|
## Global
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
npm i @k03mad/dns-leak -g
|
|
13
|
+
```
|
|
11
14
|
|
|
15
|
+
```bash
|
|
12
16
|
dns-leak
|
|
13
17
|
```
|
|
14
18
|
|
|
@@ -19,21 +23,7 @@ npm i @k03mad/dns-leak
|
|
|
19
23
|
```
|
|
20
24
|
|
|
21
25
|
```js
|
|
22
|
-
import {IPLeak, IPWhois} from '@k03mad/dns-leak';
|
|
23
|
-
|
|
24
|
-
// default params (details at the ./app/api folder)
|
|
25
|
-
const LeakApi = new IPLeak();
|
|
26
|
-
const WhoisApi = new IPWhois();
|
|
27
|
-
|
|
28
|
-
// get current external ip info
|
|
29
|
-
await WhoisApi.getIpInfo();
|
|
30
|
-
await LeakApi.getIpInfo();
|
|
31
|
-
// get other ip info
|
|
32
|
-
await WhoisApi.getIpInfo({ip: '8.8.8.8'});
|
|
33
|
-
await LeakApi.getIpInfo({ip: '8.8.8.8'});
|
|
34
|
-
|
|
35
|
-
// dns leak check with one request (one request — fewer dns ips)
|
|
36
|
-
await LeakApi.getDnsInfoOnce();
|
|
37
|
-
// dns leak check with multi requests
|
|
38
|
-
await LeakApi.getDnsInfoMulti();
|
|
26
|
+
import {CloudPing, IPLeak, IPWhois, NextDNS} from '@k03mad/dns-leak';
|
|
39
27
|
```
|
|
28
|
+
|
|
29
|
+
Details at [./app/api](/app/api) folder
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {request} from '@k03mad/request';
|
|
2
|
+
|
|
3
|
+
/** */
|
|
4
|
+
export default class CloudPing {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} [opts]
|
|
8
|
+
* @param {number} [opts.requestsRps] parallel requests rps
|
|
9
|
+
*/
|
|
10
|
+
constructor({
|
|
11
|
+
requestsRps = 2,
|
|
12
|
+
} = {}) {
|
|
13
|
+
this._requestsRps = requestsRps;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** */
|
|
17
|
+
get _endpoints() {
|
|
18
|
+
return {
|
|
19
|
+
|
|
20
|
+
/** */
|
|
21
|
+
edge: () => 'https://edge.feitsui.com/',
|
|
22
|
+
|
|
23
|
+
/** */
|
|
24
|
+
locations: () => 'https://www.cloudping.cloud/cloudfront-edge-locations.json',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @returns {Promise<string>}
|
|
30
|
+
*/
|
|
31
|
+
async getCurrentIataCode() {
|
|
32
|
+
const testEndpoint = this._endpoints.edge();
|
|
33
|
+
|
|
34
|
+
const {headers} = await request(testEndpoint, {}, {
|
|
35
|
+
rps: this._requestsRps,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return headers['x-amz-cf-pop'];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @returns {Promise<object>}
|
|
43
|
+
*/
|
|
44
|
+
async getAllLocations() {
|
|
45
|
+
const locationsEndpoint = this._endpoints.locations();
|
|
46
|
+
|
|
47
|
+
const {body} = await request(locationsEndpoint, {}, {
|
|
48
|
+
rps: this._requestsRps,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return body.nodes;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @returns {Promise<object>}
|
|
56
|
+
*/
|
|
57
|
+
async getCurrentLocation() {
|
|
58
|
+
const [iata, locations] = await Promise.all([
|
|
59
|
+
this.getCurrentIataCode(),
|
|
60
|
+
this.getAllLocations(),
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
return {...locations[iata.replace(/\d.+/, '')], iata};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
package/app/api/IPLeak.js
CHANGED
|
@@ -11,29 +11,26 @@ export default class IPLeak {
|
|
|
11
11
|
/**
|
|
12
12
|
* @param {object} [opts]
|
|
13
13
|
* @param {number} [opts.dnsRequestsCount] dns leak multi requests count with one session
|
|
14
|
-
* @param {number} [opts.dnsRequestsRps] dns leak requests rps
|
|
15
14
|
* @param {number} [opts.dnsRequestsWaitBeforeLastMs] dns leak multi requests wait before the last request (with all ips gathered)
|
|
16
15
|
* @param {number} [opts.dnsSessionStringLength] dns leak session string length, only works with 40 characters for now
|
|
17
16
|
* @param {number} [opts.dnsUniqStringLength] dns leak unique string length for subdomain
|
|
18
17
|
* @param {number} [opts.ipRequestsCacheExpireMs] ip info requests cache ttl ms for same ip
|
|
19
|
-
* @param {number} [opts.
|
|
18
|
+
* @param {number} [opts.requestsRps] parallel requests rps
|
|
20
19
|
*/
|
|
21
20
|
constructor({
|
|
22
21
|
dnsRequestsCount = 30,
|
|
23
|
-
dnsRequestsRps = 2,
|
|
24
22
|
dnsRequestsWaitBeforeLastMs = 2000,
|
|
25
23
|
dnsSessionStringLength = 40,
|
|
26
24
|
dnsUniqStringLength = 20,
|
|
27
25
|
ipRequestsCacheExpireMs = 3_600_000,
|
|
28
|
-
|
|
26
|
+
requestsRps = 2,
|
|
29
27
|
} = {}) {
|
|
30
28
|
this._dnsRequestsCount = dnsRequestsCount;
|
|
31
|
-
this._dnsRequestsRps = dnsRequestsRps;
|
|
32
29
|
this._dnsRequestsWaitBeforeLastMs = dnsRequestsWaitBeforeLastMs;
|
|
33
30
|
this._dnsSessionStringLength = dnsSessionStringLength;
|
|
34
31
|
this._dnsUniqStringLength = dnsUniqStringLength;
|
|
35
32
|
this._ipRequestsCacheExpireMs = ipRequestsCacheExpireMs;
|
|
36
|
-
this.
|
|
33
|
+
this._requestsRps = requestsRps;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
/** */
|
|
@@ -71,7 +68,7 @@ export default class IPLeak {
|
|
|
71
68
|
|
|
72
69
|
const {body} = await requestCache(ipEndpoint, {}, {
|
|
73
70
|
expire: this._ipRequestsCacheExpireMs,
|
|
74
|
-
rps: this.
|
|
71
|
+
rps: this._requestsRps,
|
|
75
72
|
});
|
|
76
73
|
|
|
77
74
|
return body;
|
|
@@ -86,7 +83,11 @@ export default class IPLeak {
|
|
|
86
83
|
async getDnsInfoOnce({session = this._dnsSessionString, uniqString = this._dnsUniqString} = {}) {
|
|
87
84
|
const dnsEndpoint = this._endpoints.dns(session, uniqString);
|
|
88
85
|
|
|
89
|
-
const {body} = await request(dnsEndpoint, {}, {
|
|
86
|
+
const {body} = await request(dnsEndpoint, {}, {
|
|
87
|
+
queueBy: session,
|
|
88
|
+
rps: this._requestsRps,
|
|
89
|
+
});
|
|
90
|
+
|
|
90
91
|
return body;
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -97,7 +98,7 @@ export default class IPLeak {
|
|
|
97
98
|
* @returns {Promise<object>}
|
|
98
99
|
*/
|
|
99
100
|
async getDnsInfoMulti({isSpinnerEnabled, session = this._dnsSessionString} = {}) {
|
|
100
|
-
const spinnerName = '
|
|
101
|
+
const spinnerName = 'DNS info';
|
|
101
102
|
const arrayFromLen = Array.from({length: this._dnsRequestsCount - 1});
|
|
102
103
|
|
|
103
104
|
spinner.start(spinnerName, isSpinnerEnabled);
|
|
@@ -107,7 +108,6 @@ export default class IPLeak {
|
|
|
107
108
|
spinner.count(spinnerName, this._dnsRequestsCount);
|
|
108
109
|
}));
|
|
109
110
|
|
|
110
|
-
spinner.text(spinnerName, 'Wait for last request');
|
|
111
111
|
await sleep(this._dnsRequestsWaitBeforeLastMs);
|
|
112
112
|
const info = await this.getDnsInfoOnce({session});
|
|
113
113
|
|
package/app/api/IPWhois.js
CHANGED
|
@@ -6,14 +6,14 @@ export default class IPWhois {
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {object} [opts]
|
|
8
8
|
* @param {number} [opts.ipRequestsCacheExpireMs] ip info requests cache ttl ms for same ip
|
|
9
|
-
* @param {number} [opts.
|
|
9
|
+
* @param {number} [opts.requestsRps] parallel requests rps
|
|
10
10
|
*/
|
|
11
11
|
constructor({
|
|
12
12
|
ipRequestsCacheExpireMs = 3_600_000,
|
|
13
|
-
|
|
13
|
+
requestsRps = 2,
|
|
14
14
|
} = {}) {
|
|
15
15
|
this._ipRequestsCacheExpireMs = ipRequestsCacheExpireMs;
|
|
16
|
-
this.
|
|
16
|
+
this._requestsRps = requestsRps;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** */
|
|
@@ -35,7 +35,7 @@ export default class IPWhois {
|
|
|
35
35
|
|
|
36
36
|
const {body} = await requestCache(ipEndpoint, {}, {
|
|
37
37
|
expire: this._ipRequestsCacheExpireMs,
|
|
38
|
-
rps: this.
|
|
38
|
+
rps: this._requestsRps,
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
return body;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {request} from '@k03mad/request';
|
|
2
|
+
|
|
3
|
+
/** */
|
|
4
|
+
export default class NextDNS {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} [opts]
|
|
8
|
+
* @param {number} [opts.requestsRps] parallel requests rps
|
|
9
|
+
*/
|
|
10
|
+
constructor({
|
|
11
|
+
requestsRps = 2,
|
|
12
|
+
} = {}) {
|
|
13
|
+
this._requestsRps = requestsRps;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** */
|
|
17
|
+
get _endpoints() {
|
|
18
|
+
return {
|
|
19
|
+
|
|
20
|
+
/** */
|
|
21
|
+
test: () => 'https://test.nextdns.io/',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @returns {Promise<object>}
|
|
27
|
+
*/
|
|
28
|
+
async getTest() {
|
|
29
|
+
const testEndpoint = this._endpoints.test();
|
|
30
|
+
|
|
31
|
+
const {body} = await request(testEndpoint, {}, {
|
|
32
|
+
rps: this._requestsRps,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return body;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
package/app/helpers/spinner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
|
|
3
|
-
import {bar} from './text.js';
|
|
3
|
+
import {bar, info} from './text.js';
|
|
4
4
|
|
|
5
5
|
export const spinner = {};
|
|
6
6
|
|
|
@@ -26,7 +26,7 @@ export const start = (name, active) => {
|
|
|
26
26
|
* @param {string} msg
|
|
27
27
|
*/
|
|
28
28
|
export const text = (name, msg) => {
|
|
29
|
-
spinner[name].instance.text = bar(msg)
|
|
29
|
+
spinner[name].instance.text = `${info(name)} ${bar(msg)}`;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/**
|
package/app/helpers/text.js
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import clm from 'country-locale-map';
|
|
2
3
|
|
|
3
|
-
const {
|
|
4
|
+
const {blue, gray, green, magenta, yellow} = chalk;
|
|
5
|
+
const SEPARATOR = ' :: ';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @param {string} msg
|
|
7
9
|
*/
|
|
8
|
-
export const header = msg =>
|
|
10
|
+
export const header = msg => magenta.bold.bgBlackBright(` ${msg}`.padEnd(25, ' '));
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* @param {string} msg
|
|
12
14
|
*/
|
|
13
15
|
export const bar = msg => yellow(msg);
|
|
14
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} msg
|
|
19
|
+
*/
|
|
20
|
+
export const info = msg => gray(msg);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} msg
|
|
24
|
+
*/
|
|
25
|
+
export const org = msg => green(msg);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} msg
|
|
29
|
+
*/
|
|
30
|
+
export const address = msg => blue(msg);
|
|
31
|
+
|
|
15
32
|
/**
|
|
16
33
|
* @param {object} [ipInfo]
|
|
17
34
|
* @param {object} [ipInfo.connection]
|
|
@@ -32,23 +49,23 @@ export const formatIpInfo = ({
|
|
|
32
49
|
let output = '';
|
|
33
50
|
|
|
34
51
|
if (ip) {
|
|
35
|
-
output += `${
|
|
52
|
+
output += `${address(ip)}\n`;
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
if (connection?.org) {
|
|
39
|
-
output += `${
|
|
56
|
+
output += `${org(connection.org)} `;
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
if (connection?.isp && !connection?.org?.includes(connection?.isp)) {
|
|
43
60
|
if (connection?.org) {
|
|
44
|
-
output +=
|
|
61
|
+
output += org('/ ');
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
output += `${
|
|
64
|
+
output += `${org(connection.isp)} `;
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
if (connection?.domain) {
|
|
51
|
-
output +=
|
|
68
|
+
output += info(`(${connection.domain})`);
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
output += '\n';
|
|
@@ -63,7 +80,37 @@ export const formatIpInfo = ({
|
|
|
63
80
|
region,
|
|
64
81
|
city,
|
|
65
82
|
]),
|
|
66
|
-
].filter(Boolean).join(
|
|
83
|
+
].filter(Boolean).join(SEPARATOR);
|
|
84
|
+
|
|
85
|
+
return output;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {object} [opts]
|
|
90
|
+
* @param {string} [opts.iata]
|
|
91
|
+
* @param {string} [opts.city]
|
|
92
|
+
* @param {string} [opts.country]
|
|
93
|
+
*/
|
|
94
|
+
export const formatLocationInfo = ({city, country, iata}) => {
|
|
95
|
+
let output = '';
|
|
96
|
+
|
|
97
|
+
if (iata) {
|
|
98
|
+
output += `${org(iata)}\n`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (country) {
|
|
102
|
+
const {emoji} = clm.getCountryByName(country);
|
|
103
|
+
|
|
104
|
+
if (emoji) {
|
|
105
|
+
output += `${emoji} `;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
output += country;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (city) {
|
|
112
|
+
output += SEPARATOR + city;
|
|
113
|
+
}
|
|
67
114
|
|
|
68
115
|
return output;
|
|
69
116
|
};
|
package/app/run.js
CHANGED
|
@@ -1,37 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {IPLeak, IPWhois} from './api/
|
|
2
|
+
import {CloudPing, IPLeak, IPWhois, NextDNS} from './api/_index.js';
|
|
3
3
|
import {log} from './helpers/log.js';
|
|
4
4
|
import * as spinner from './helpers/spinner.js';
|
|
5
|
-
import {formatIpInfo, header} from './helpers/text.js';
|
|
5
|
+
import {formatIpInfo, formatLocationInfo, header} from './helpers/text.js';
|
|
6
6
|
|
|
7
7
|
const LeakApi = new IPLeak();
|
|
8
|
+
const NextApi = new NextDNS();
|
|
8
9
|
const WhoisApi = new IPWhois();
|
|
10
|
+
const CloudPingApi = new CloudPing();
|
|
9
11
|
|
|
10
|
-
const
|
|
12
|
+
const [leak, next, whois, location] = await Promise.all([
|
|
13
|
+
LeakApi.getDnsInfoMulti({isSpinnerEnabled: true}),
|
|
14
|
+
NextApi.getTest(),
|
|
15
|
+
WhoisApi.getIpInfo(),
|
|
16
|
+
CloudPingApi.getCurrentLocation(),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const spinnerName = 'IP info';
|
|
20
|
+
spinner.start(spinnerName, true);
|
|
21
|
+
|
|
22
|
+
const dnsIps = [...new Set([...Object.keys(leak.ip), next.resolver])];
|
|
23
|
+
|
|
24
|
+
const dnsData = await Promise.all(dnsIps.map(async ip => {
|
|
25
|
+
const data = await WhoisApi.getIpInfo({ip});
|
|
26
|
+
spinner.count(spinnerName, dnsIps.length);
|
|
27
|
+
return data;
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
spinner.stop(spinnerName);
|
|
11
31
|
|
|
12
32
|
log(
|
|
13
33
|
'',
|
|
14
34
|
header('IP'),
|
|
15
35
|
'',
|
|
16
|
-
formatIpInfo(
|
|
36
|
+
formatIpInfo(whois),
|
|
17
37
|
'',
|
|
18
38
|
header('DNS'),
|
|
19
39
|
'',
|
|
40
|
+
...dnsData
|
|
41
|
+
.sort((a, b) => a?.ip?.localeCompare(b?.ip))
|
|
42
|
+
.flatMap(data => [formatIpInfo(data), '']),
|
|
20
43
|
);
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
const
|
|
45
|
+
if (next.ecs) {
|
|
46
|
+
const data = await WhoisApi.getIpInfo({ip: next.ecs.replace(/\/.+/, '')});
|
|
47
|
+
data.ip += ` (${next.ecs})`;
|
|
24
48
|
|
|
25
|
-
|
|
49
|
+
log(
|
|
50
|
+
header('DNS ECS'),
|
|
51
|
+
'',
|
|
52
|
+
formatIpInfo(data),
|
|
53
|
+
'',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
26
56
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
spinner.stop(currentIpInfo.ip);
|
|
34
|
-
|
|
35
|
-
dnsData
|
|
36
|
-
.sort((a, b) => a?.ip?.localeCompare(b?.ip))
|
|
37
|
-
.forEach(data => log(formatIpInfo(data), ''));
|
|
57
|
+
log(
|
|
58
|
+
header('CLOUDFRONT CDN'),
|
|
59
|
+
'',
|
|
60
|
+
formatLocationInfo(location),
|
|
61
|
+
);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k03mad/dns-leak",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "DNS leak test",
|
|
5
5
|
"maintainers": [
|
|
6
6
|
"Kirill Molchanov <k03.mad@gmail.com"
|
|
7
7
|
],
|
|
8
|
-
"main": "app/api/
|
|
8
|
+
"main": "app/api/_index.js",
|
|
9
9
|
"bin": "app/run.js",
|
|
10
10
|
"repository": "k03mad/dns-leak",
|
|
11
11
|
"license": "MIT",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@k03mad/request": "3.2.0",
|
|
18
18
|
"chalk": "5.3.0",
|
|
19
|
+
"country-locale-map": "1.8.15",
|
|
19
20
|
"nanoid": "5.0.2",
|
|
20
21
|
"nanoid-dictionary": "5.0.0-beta.1",
|
|
21
22
|
"ora": "7.0.1"
|
package/app/api/index.js
DELETED