@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.
@@ -22,7 +22,7 @@ jobs:
22
22
  uses: actions/checkout@v4
23
23
 
24
24
  - name: Install NodeJS
25
- uses: actions/setup-node@v3
25
+ uses: actions/setup-node@v4
26
26
  with:
27
27
  node-version-file: '.nvmrc'
28
28
 
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
- * @param {number} [ipRequestsRps]
10
- * @param {number} [ipRequestsCacheExpireMs]
11
- * @param {number} [dnsRequestsCount]
12
- * @param {number} [dnsRequestsRps]
13
- * @param {number} [dnsSessionStringLength]
14
- * @param {number} [dnsUniqStringLength]
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
- this._ipRequestsRps = ipRequestsRps;
25
- this._ipRequestsCacheExpireMs = ipRequestsCacheExpireMs;
28
+ ipRequestsCacheExpireMs = 3_600_000,
29
+ ipRequestsRps = 2,
30
+ } = {}) {
26
31
  this._dnsRequestsCount = dnsRequestsCount;
27
32
  this._dnsRequestsRps = dnsRequestsRps;
28
- this._dnsUniqStringLength = dnsUniqStringLength;
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
- info: (ip = '') => `https://ipleak.net/json/${ip}`,
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 {string} ip
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.info(ip);
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 {string} [session]
67
- * @param {string} [uniqString]
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 {string} [session]
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 arrayFromLen = Array.from({length: this._dnsRequestsCount});
83
- await Promise.all(arrayFromLen.map(() => this.getDnsInfoOnce(session)));
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
- const info = await this.getDnsInfoOnce(session);
115
+ spinner.stop(spinnerName);
86
116
  return info;
87
117
  }
88
118
 
@@ -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(...msg);
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,5 @@
1
+ /**
2
+ * @param {number} ms
3
+ * @returns {Promise<void>}
4
+ */
5
+ export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
@@ -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 {formatIpInfo} from './helpers/ip.js';
5
- import {log, logHeader} from './helpers/log.js';
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
- logHeader('IP');
12
- log(formatIpInfo(currentIpInfo));
13
-
14
- const dnsInfo = await api.getDnsInfoMulti();
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
- logHeader('DNS');
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
- const dnsData = await Promise.all(dnsIps.map(ip => api.getIpInfo(ip)));
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), '\n'));
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.0.0",
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.53.0",
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",