pubface 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/.eslintrc ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "rules": {
3
+ "indent": 0,
4
+ "no-await-in-loop": 0,
5
+ "require-atomic-updates": 0
6
+ },
7
+ "globals": {
8
+ "BigInt": true
9
+ },
10
+ "extends": ["nodemailer", "prettier"],
11
+ "parser": "@babel/eslint-parser",
12
+ "parserOptions": {
13
+ "ecmaVersion": 2018,
14
+ "sourceType": "script"
15
+ },
16
+ "plugins": ["@babel"]
17
+ }
package/.prettierrc.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ printWidth: 160,
3
+ tabWidth: 4,
4
+ singleQuote: true,
5
+ endOfLine: 'lf',
6
+ trailingComma: 'none',
7
+ arrowParens: 'avoid'
8
+ };
package/LICENSE.txt ADDED
@@ -0,0 +1,16 @@
1
+ Copyright (c) 2020-2021 Postal Systems OÜ
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # pubface
2
+
3
+ Detect public network interfaces for current machine.
4
+
5
+ ### Usage as a module
6
+
7
+ Install the dependency
8
+
9
+ ```
10
+ $ npm install pubface
11
+ ```
12
+
13
+ Use it to get an array of interfaces
14
+
15
+ ```js
16
+ const { resolvePublicInterfaces } = require('pubface');
17
+ ...
18
+ let interfaces = await resolvePublicInterfaces();
19
+ console.log(interfaces);
20
+ ```
21
+
22
+ ### Usage as a command
23
+
24
+ Install the command
25
+
26
+ ```
27
+ $ npm install -g pubface
28
+ ```
29
+
30
+ Use it to get an array of interfaces
31
+
32
+ ```
33
+ $ pubface
34
+ ```
35
+
36
+ ### Output
37
+
38
+ Output is an array of interfaces:
39
+
40
+ ```
41
+ [
42
+ {
43
+ "localAddress": "192.168.3.4",
44
+ "ip": "1.2.3.4",
45
+ "name": "ec2-1-2-3-4.eu-central-1.compute.amazonaws.com",
46
+ "family": "IPv4",
47
+ "defaultInterface": true
48
+ },
49
+ {
50
+ "localAddress": "10.240.128.227",
51
+ "ip": "101.102.103.104",
52
+ "name": "104-103-102-101.sta.estpak.ee",
53
+ "family": "IPv4"
54
+ }
55
+ ]
56
+ ```
57
+
58
+ - **localAddress** is the local IP address
59
+ - **ip** is the public IP address that servers see as your IP address when you make a connection
60
+ - **name** is the reverse record for **ip**
61
+ - **family** is either _IPv4_ or _IPv6_ depending on the **ip**
62
+ - **defaultInterface** is a boolean that indicates if this is the default interface used when making connections and not specifying a local address
63
+
64
+ ## License
65
+
66
+ **MIT**
package/bin/pubface.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const { resolvePublicInterfaces } = require('../index.js');
6
+ resolvePublicInterfaces()
7
+ .then(results => {
8
+ console.log(JSON.stringify(results, false, 2));
9
+ process.exit(0);
10
+ })
11
+ .catch(err => {
12
+ console.error(err);
13
+ process.exit(1);
14
+ });
package/index.js ADDED
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ const fetch = require('nodemailer/lib/fetch');
4
+ const packageData = require('./package.json');
5
+ const dns = require('dns').promises;
6
+ const os = require('os');
7
+ const net = require('net');
8
+
9
+ const RESOLV_URL = process.env.RESOLV_URL || 'https://api.nodemailer.com/';
10
+ const RESOLV_TIMEOUT = Number(process.env.RESOLV_TIMEOUT) || 5;
11
+
12
+ const RESOLV_TIMEOUT_SEC = RESOLV_TIMEOUT * 1000;
13
+ const DNS_CACHE = {};
14
+
15
+ async function updateDns() {
16
+ let now = new Date();
17
+
18
+ let url = new URL(RESOLV_URL);
19
+ if (net.isIPv4(url.hostname)) {
20
+ DNS_CACHE.AAAA = false;
21
+ DNS_CACHE.A = {
22
+ host: url.hostname,
23
+ expires: new Date(Date.now() + 10 * 60 * 1000)
24
+ };
25
+ return;
26
+ }
27
+
28
+ if (net.isIPv6(url.hostname)) {
29
+ DNS_CACHE.A = false;
30
+ DNS_CACHE.AAAA = {
31
+ host: url.hostname,
32
+ expires: new Date(Date.now() + 10 * 60 * 1000)
33
+ };
34
+ return;
35
+ }
36
+
37
+ let shouldCheckIPv4 = !DNS_CACHE.A || !DNS_CACHE.A.expires || !DNS_CACHE.A.expires < now;
38
+ let shouldCheckIPv6 = !DNS_CACHE.AAAA || !DNS_CACHE.AAAA.expires || !DNS_CACHE.AAAA.expires < now;
39
+
40
+ if (shouldCheckIPv4) {
41
+ try {
42
+ let results = await dns.resolve4(url.hostname);
43
+ if (results && results.length) {
44
+ DNS_CACHE.A = {
45
+ host: results[0],
46
+ expires: new Date(Date.now() + 10 * 60 * 1000)
47
+ };
48
+ }
49
+ } catch (err) {
50
+ if (!DNS_CACHE.A) {
51
+ DNS_CACHE.A = {};
52
+ }
53
+ DNS_CACHE.A.error = err;
54
+ }
55
+ }
56
+
57
+ if (shouldCheckIPv6) {
58
+ try {
59
+ let results = await dns.resolve6(url.hostname);
60
+ if (results && results.length) {
61
+ DNS_CACHE.AAAA = {
62
+ host: results[0],
63
+ expires: new Date(Date.now() + 10 * 60 * 1000)
64
+ };
65
+ }
66
+ } catch (err) {
67
+ if (!DNS_CACHE.AAAA) {
68
+ DNS_CACHE.AAAA = {};
69
+ }
70
+ DNS_CACHE.AAAA.error = err;
71
+ }
72
+ }
73
+ }
74
+
75
+ function getPublicInterfaces() {
76
+ let interfaces = os.networkInterfaces();
77
+ let publicInterfaces = { IPv4: [], IPv6: [] };
78
+ Object.keys(interfaces)
79
+ .flatMap(iface => interfaces[iface].filter(entry => !entry.internal).map(entry => Object.assign({ iface }, entry)))
80
+ .forEach(entry => {
81
+ if (Array.isArray(publicInterfaces[entry.family])) {
82
+ publicInterfaces[entry.family].push(entry);
83
+ }
84
+ });
85
+ return publicInterfaces;
86
+ }
87
+
88
+ async function timedFunction(prom, timeout, localAddress) {
89
+ return new Promise((resolve, reject) => {
90
+ setTimeout(() => {
91
+ let err = new Error('Resolving requested resource timed out');
92
+ if (localAddress) {
93
+ err._source = localAddress;
94
+ }
95
+ reject(err);
96
+ }, timeout).unref();
97
+ prom.then(resolve).catch(reject);
98
+ });
99
+ }
100
+
101
+ async function resolveIP(localAddress, family) {
102
+ let data = await new Promise((resolve, reject) => {
103
+ let req = fetch(RESOLV_URL, {
104
+ userAgent: `${packageData.name}/${packageData.version}`,
105
+ tls: {
106
+ host: DNS_CACHE[family] && DNS_CACHE[family].host,
107
+ rejectUnauthorized: false,
108
+ localAddress
109
+ }
110
+ });
111
+
112
+ let buf = [];
113
+ req.on('readable', () => {
114
+ let chunk;
115
+ while ((chunk = req.read()) !== null) {
116
+ buf.push(chunk);
117
+ }
118
+ });
119
+
120
+ req.on('error', err => {
121
+ reject(err);
122
+ });
123
+
124
+ req.on('end', () => {
125
+ try {
126
+ let data = JSON.parse(Buffer.concat(buf).toString());
127
+ resolve(data);
128
+ } catch (err) {
129
+ reject(err);
130
+ }
131
+ });
132
+ });
133
+
134
+ if (!data || !data.ip) {
135
+ throw new Error('No response from IP server');
136
+ }
137
+
138
+ try {
139
+ let name = await dns.reverse(data.ip);
140
+ if (name && name.length) {
141
+ data.name = name[0];
142
+ }
143
+ } catch (err) {
144
+ // can ignore this
145
+ }
146
+
147
+ return Object.assign({ localAddress }, data);
148
+ }
149
+
150
+ async function resolvePublicInterfaces() {
151
+ let interfaces = getPublicInterfaces();
152
+ let promises = [];
153
+
154
+ // update resolver IP4/6 addresses
155
+ await updateDns();
156
+
157
+ if (DNS_CACHE.A && DNS_CACHE.A.host) {
158
+ // default
159
+ promises.push(timedFunction(resolveIP(false, 'A'), RESOLV_TIMEOUT_SEC, false));
160
+ interfaces.IPv4.forEach(iface => {
161
+ promises.push(timedFunction(resolveIP(iface.address, 'A'), RESOLV_TIMEOUT_SEC, iface.address));
162
+ });
163
+ }
164
+
165
+ if (DNS_CACHE.AAAA && DNS_CACHE.AAAA.host) {
166
+ promises.push(timedFunction(resolveIP(false, 'AAAA'), RESOLV_TIMEOUT_SEC, false));
167
+ interfaces.IPv6.forEach(iface => {
168
+ promises.push(timedFunction(resolveIP(iface.address, 'AAAA'), RESOLV_TIMEOUT_SEC, iface.address));
169
+ });
170
+ }
171
+
172
+ let defaults = {};
173
+ let results = (await Promise.allSettled(promises))
174
+ .filter(entry => entry.status === 'fulfilled')
175
+ .map(entry => Object.assign(entry.value, { family: net.isIPv6(entry.value.ip || entry.value.localAddress) ? 'IPv6' : 'IPv4' }))
176
+ .filter(entry => {
177
+ if (!entry.localAddress) {
178
+ defaults[entry.family] = entry;
179
+ return false;
180
+ }
181
+ return true;
182
+ });
183
+
184
+ results.forEach(entry => {
185
+ if (defaults[entry.family] && defaults[entry.family].ip === entry.ip) {
186
+ entry.defaultInterface = true;
187
+ defaults[entry.family] = false;
188
+ }
189
+ });
190
+
191
+ if (defaults.IPv4) {
192
+ results.push(Object.assign(defaults.IPv4, { defaultInterface: true }));
193
+ }
194
+
195
+ if (defaults.IPv6) {
196
+ results.push(Object.assign(defaults.IPv6, { defaultInterface: true }));
197
+ }
198
+
199
+ results = results.sort((a, b) => {
200
+ if (a.family !== b.family) {
201
+ return a.family.localeCompare(b.family);
202
+ }
203
+ if (a.defaultInterface) {
204
+ return -1;
205
+ }
206
+ if (b.defaultInterface) {
207
+ return 1;
208
+ }
209
+ return (a.name || a.ip).localeCompare(b.name || b.ip);
210
+ });
211
+
212
+ return results;
213
+ }
214
+
215
+ module.exports = { resolvePublicInterfaces };
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "pubface",
3
+ "version": "1.0.0",
4
+ "description": "Resolve public network interfaces for current machine",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "Postal Systems OÜ",
11
+ "license": "MIT",
12
+ "bin": {
13
+ "pubface": "bin/pubface.js"
14
+ },
15
+ "dependencies": {
16
+ "nodemailer": "6.7.1"
17
+ }
18
+ }