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 +17 -0
- package/.prettierrc.js +8 -0
- package/LICENSE.txt +16 -0
- package/README.md +66 -0
- package/bin/pubface.js +14 -0
- package/index.js +215 -0
- package/package.json +18 -0
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
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
|
+
}
|