@k03mad/dns-leak 1.0.0 → 1.2.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/.github/workflows/eslint.yml +1 -1
- package/README.md +34 -1
- package/app/api/IPLeak.js +55 -25
- package/app/helpers/log.js +1 -10
- package/app/helpers/promise.js +5 -0
- package/app/helpers/spinner.js +49 -0
- package/app/helpers/{ip.js → text.js} +12 -2
- package/app/index.js +23 -9
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
1
|
# DNS leak test
|
|
2
2
|
|
|
3
|
-
Based on [ipleak.net](https://ipleak.net/)
|
|
3
|
+
Based on: [ipleak.net](https://ipleak.net/) / [API](https://airvpn.org/forums/topic/14737-api/)
|
|
4
|
+
|
|
5
|
+
## Global
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @k03mad/dns-leak -g
|
|
9
|
+
|
|
10
|
+
dns-leak
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @k03mad/dns-leak
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import IPLeak from '@k03mad/dns-leak';
|
|
21
|
+
|
|
22
|
+
// default params (details at the link below)
|
|
23
|
+
const api = new IPLeak();
|
|
24
|
+
|
|
25
|
+
// get current external ip info
|
|
26
|
+
await api.getIpInfo();
|
|
27
|
+
// get other ip info
|
|
28
|
+
await api.getIpInfo({ip: '8.8.8.8'});
|
|
29
|
+
|
|
30
|
+
// dns leak check with one request (one request — fewer dns ips)
|
|
31
|
+
await api.getDnsInfoOnce();
|
|
32
|
+
// dns leak check with multi requests
|
|
33
|
+
await api.getDnsInfoMulti();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
[Options and parameters with their default values](/app/api/IPLeak.js#L8)
|
package/app/api/IPLeak.js
CHANGED
|
@@ -2,37 +2,52 @@ import {request, requestCache} from '@k03mad/request';
|
|
|
2
2
|
import {customAlphabet} from 'nanoid';
|
|
3
3
|
import {lowercase, numbers} from 'nanoid-dictionary';
|
|
4
4
|
|
|
5
|
+
import {sleep} from '../helpers/promise.js';
|
|
6
|
+
import * as spinner from '../helpers/spinner.js';
|
|
7
|
+
|
|
5
8
|
/** */
|
|
6
9
|
export default class IPLeak {
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {number} [dnsRequestsCount]
|
|
12
|
-
* @param {number} [dnsRequestsRps]
|
|
13
|
-
* @param {number} [
|
|
14
|
-
* @param {number} [
|
|
12
|
+
* All args are not required, the default ones works fine
|
|
13
|
+
* @param {object} [opts]
|
|
14
|
+
* @param {number} [opts.dnsRequestsCount] dns leak multi requests count with one session
|
|
15
|
+
* @param {number} [opts.dnsRequestsRps] dns leak requests rps
|
|
16
|
+
* @param {number} [opts.dnsRequestsWaitBeforeLastMs] dns leak multi requests wait before the last request (with all ips gathered)
|
|
17
|
+
* @param {number} [opts.dnsSessionStringLength] dns leak session string length, only works with 40 characters for now
|
|
18
|
+
* @param {number} [opts.dnsUniqStringLength] dns leak unique string length for subdomain
|
|
19
|
+
* @param {number} [opts.ipRequestsCacheExpireMs] ip info requests cache ttl ms for same ip
|
|
20
|
+
* @param {number} [opts.ipRequestsRps] ip info requests rps
|
|
15
21
|
*/
|
|
16
|
-
constructor(
|
|
17
|
-
ipRequestsRps = 2,
|
|
18
|
-
ipRequestsCacheExpireMs = 3_600_000,
|
|
22
|
+
constructor({
|
|
19
23
|
dnsRequestsCount = 30,
|
|
20
24
|
dnsRequestsRps = 2,
|
|
25
|
+
dnsRequestsWaitBeforeLastMs = 2000,
|
|
21
26
|
dnsSessionStringLength = 40,
|
|
22
27
|
dnsUniqStringLength = 20,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
ipRequestsCacheExpireMs = 3_600_000,
|
|
29
|
+
ipRequestsRps = 2,
|
|
30
|
+
} = {}) {
|
|
26
31
|
this._dnsRequestsCount = dnsRequestsCount;
|
|
27
32
|
this._dnsRequestsRps = dnsRequestsRps;
|
|
28
|
-
this.
|
|
33
|
+
this._dnsRequestsWaitBeforeLastMs = dnsRequestsWaitBeforeLastMs;
|
|
29
34
|
this._dnsSessionStringLength = dnsSessionStringLength;
|
|
35
|
+
this._dnsUniqStringLength = dnsUniqStringLength;
|
|
36
|
+
this._ipRequestsCacheExpireMs = ipRequestsCacheExpireMs;
|
|
37
|
+
this._ipRequestsRps = ipRequestsRps;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
/** */
|
|
33
41
|
get _endpoints() {
|
|
34
42
|
return {
|
|
35
|
-
|
|
43
|
+
|
|
44
|
+
/** @param {string} ip */
|
|
45
|
+
ip: ip => `https://ipleak.net/json/${ip}`,
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} session
|
|
49
|
+
* @param {string} uniq
|
|
50
|
+
*/
|
|
36
51
|
dns: (session, uniq) => `https://${session}-${uniq}.ipleak.net/dnsdetection/`,
|
|
37
52
|
};
|
|
38
53
|
}
|
|
@@ -48,11 +63,12 @@ export default class IPLeak {
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
/**
|
|
51
|
-
* @param {
|
|
66
|
+
* @param {object} [opts]
|
|
67
|
+
* @param {string} [opts.ip]
|
|
52
68
|
* @returns {Promise<object>}
|
|
53
69
|
*/
|
|
54
|
-
async getIpInfo(ip) {
|
|
55
|
-
const ipEndpoint = this._endpoints.
|
|
70
|
+
async getIpInfo({ip = ''} = {}) {
|
|
71
|
+
const ipEndpoint = this._endpoints.ip(ip);
|
|
56
72
|
|
|
57
73
|
const {body} = await requestCache(ipEndpoint, {}, {
|
|
58
74
|
expire: this._ipRequestsCacheExpireMs,
|
|
@@ -63,11 +79,12 @@ export default class IPLeak {
|
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
/**
|
|
66
|
-
* @param {
|
|
67
|
-
* @param {string} [
|
|
82
|
+
* @param {object} [opts]
|
|
83
|
+
* @param {string} [opts.session]
|
|
84
|
+
* @param {string} [opts.uniqString]
|
|
68
85
|
* @returns {Promise<object>}
|
|
69
86
|
*/
|
|
70
|
-
async getDnsInfoOnce(session = this._dnsSessionString, uniqString = this._dnsUniqString) {
|
|
87
|
+
async getDnsInfoOnce({session = this._dnsSessionString, uniqString = this._dnsUniqString} = {}) {
|
|
71
88
|
const dnsEndpoint = this._endpoints.dns(session, uniqString);
|
|
72
89
|
|
|
73
90
|
const {body} = await request(dnsEndpoint, {}, {queueBy: session, rps: this._dnsRequestsRps});
|
|
@@ -75,14 +92,27 @@ export default class IPLeak {
|
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
/**
|
|
78
|
-
* @param {
|
|
95
|
+
* @param {object} [opts]
|
|
96
|
+
* @param {string} [opts.session]
|
|
97
|
+
* @param {boolean} [opts.isSpinnerEnabled]
|
|
79
98
|
* @returns {Promise<object>}
|
|
80
99
|
*/
|
|
81
|
-
async getDnsInfoMulti(session = this._dnsSessionString) {
|
|
82
|
-
const
|
|
83
|
-
|
|
100
|
+
async getDnsInfoMulti({isSpinnerEnabled, session = this._dnsSessionString} = {}) {
|
|
101
|
+
const spinnerName = 'dnsReq';
|
|
102
|
+
const arrayFromLen = Array.from({length: this._dnsRequestsCount - 1});
|
|
103
|
+
|
|
104
|
+
spinner.start(spinnerName, isSpinnerEnabled);
|
|
105
|
+
|
|
106
|
+
await Promise.all(arrayFromLen.map(async () => {
|
|
107
|
+
await this.getDnsInfoOnce({session});
|
|
108
|
+
spinner.count(spinnerName, this._dnsRequestsCount);
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
spinner.text(spinnerName, 'Wait for last request');
|
|
112
|
+
await sleep(this._dnsRequestsWaitBeforeLastMs);
|
|
113
|
+
const info = await this.getDnsInfoOnce({session});
|
|
84
114
|
|
|
85
|
-
|
|
115
|
+
spinner.stop(spinnerName);
|
|
86
116
|
return info;
|
|
87
117
|
}
|
|
88
118
|
|
package/app/helpers/log.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
const {bgBlackBright, magenta} = chalk;
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* @param {any} msg
|
|
7
3
|
*/
|
|
8
4
|
// eslint-disable-next-line no-console
|
|
9
|
-
export const log = (...msg) => console.log(
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {any} header
|
|
13
|
-
*/
|
|
14
|
-
export const logHeader = header => log(`\n${bgBlackBright(magenta(` ${header} `))}\n`);
|
|
5
|
+
export const log = (...msg) => msg.forEach(elem => console.log(elem));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
|
|
3
|
+
import {bar} from './text.js';
|
|
4
|
+
|
|
5
|
+
export const spinner = {};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} name
|
|
9
|
+
* @param {boolean} active
|
|
10
|
+
*/
|
|
11
|
+
export const start = (name, active) => {
|
|
12
|
+
if (active) {
|
|
13
|
+
spinner[name] = {
|
|
14
|
+
instance: ora().start(),
|
|
15
|
+
counter: 0,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return spinner[name].instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
spinner[name] = {instance: {stop: () => ''}};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} name
|
|
26
|
+
* @param {string} msg
|
|
27
|
+
*/
|
|
28
|
+
export const text = (name, msg) => {
|
|
29
|
+
spinner[name].instance.text = bar(msg);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} name
|
|
34
|
+
* @param {number} total
|
|
35
|
+
*/
|
|
36
|
+
export const count = (name, total) => {
|
|
37
|
+
spinner[name].counter++;
|
|
38
|
+
|
|
39
|
+
const len = `${String(spinner[name].counter).padStart(String(total).length, '0')}/${total}`;
|
|
40
|
+
const percent = `${String((spinner[name].counter * 100 / total).toFixed(0)).padStart(2, '0')}%`;
|
|
41
|
+
text(name, `${len} [${percent}]`);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} name
|
|
46
|
+
*/
|
|
47
|
+
export const stop = name => {
|
|
48
|
+
spinner[name].instance.stop();
|
|
49
|
+
};
|
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import clm from 'country-locale-map';
|
|
5
5
|
|
|
6
|
-
const {blue, green} = chalk;
|
|
6
|
+
const {bgBlackBright, blue, green, magenta, yellow} = chalk;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {any} msg
|
|
10
|
+
*/
|
|
11
|
+
export const header = msg => bgBlackBright(magenta(` ${msg} `));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {any} msg
|
|
15
|
+
*/
|
|
16
|
+
export const bar = msg => yellow(msg);
|
|
7
17
|
|
|
8
18
|
/**
|
|
9
19
|
* @param {object} [ipInfo]
|
|
@@ -14,7 +24,7 @@ const {blue, green} = chalk;
|
|
|
14
24
|
* @param {string} [ipInfo.isp_name]
|
|
15
25
|
* @param {string} [ipInfo.region_name]
|
|
16
26
|
*/
|
|
17
|
-
export const formatIpInfo = ({city_name, country_code, country_name, ip, isp_name, region_name}) => {
|
|
27
|
+
export const formatIpInfo = ({city_name, country_code, country_name, ip, isp_name, region_name} = {}) => {
|
|
18
28
|
let output = '';
|
|
19
29
|
|
|
20
30
|
if (ip) {
|
package/app/index.js
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import IPLeak from './api/IPLeak.js';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import {log} from './helpers/log.js';
|
|
5
|
+
import * as spinner from './helpers/spinner.js';
|
|
6
|
+
import {formatIpInfo, header} from './helpers/text.js';
|
|
6
7
|
|
|
7
8
|
const api = new IPLeak();
|
|
8
9
|
|
|
9
10
|
const currentIpInfo = await api.getIpInfo();
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
log(
|
|
13
|
+
'',
|
|
14
|
+
header('IP'),
|
|
15
|
+
'',
|
|
16
|
+
formatIpInfo(currentIpInfo),
|
|
17
|
+
'',
|
|
18
|
+
header('DNS'),
|
|
19
|
+
'',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const dnsInfo = await api.getDnsInfoMulti({isSpinnerEnabled: true});
|
|
15
23
|
const dnsIps = [...new Set(Object.keys(dnsInfo.ip))];
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
spinner.start(currentIpInfo.ip, true);
|
|
26
|
+
|
|
27
|
+
const dnsData = await Promise.all(dnsIps.map(async ip => {
|
|
28
|
+
const data = await api.getIpInfo({ip});
|
|
29
|
+
spinner.count(currentIpInfo.ip, dnsIps.length);
|
|
30
|
+
return data;
|
|
31
|
+
}));
|
|
18
32
|
|
|
19
|
-
|
|
33
|
+
spinner.stop(currentIpInfo.ip);
|
|
20
34
|
|
|
21
35
|
dnsData
|
|
22
36
|
.sort((a, b) => a?.ip?.localeCompare(b?.ip))
|
|
23
|
-
.forEach(data => log(formatIpInfo(data), '
|
|
37
|
+
.forEach(data => log(formatIpInfo(data), ''));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k03mad/dns-leak",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "DNS leak test",
|
|
5
5
|
"maintainers": [
|
|
6
6
|
"Kirill Molchanov <k03.mad@gmail.com"
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
"chalk": "5.3.0",
|
|
19
19
|
"country-locale-map": "1.8.15",
|
|
20
20
|
"nanoid": "5.0.2",
|
|
21
|
-
"nanoid-dictionary": "5.0.0-beta.1"
|
|
21
|
+
"nanoid-dictionary": "5.0.0-beta.1",
|
|
22
|
+
"ora": "7.0.1"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@k03mad/eslint-config": "13.3.1",
|
|
25
26
|
"@microsoft/eslint-formatter-sarif": "3.0.0",
|
|
26
|
-
"eslint": "8.
|
|
27
|
+
"eslint": "8.52.0",
|
|
27
28
|
"eslint-plugin-import": "2.29.0",
|
|
28
29
|
"eslint-plugin-jsdoc": "46.8.2",
|
|
29
30
|
"eslint-plugin-n": "16.2.0",
|