@lakutata/netif 0.0.0-stage → 3.1.4

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.
Files changed (93) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +299 -2
  3. package/README.zh-CN.md +229 -0
  4. package/binding.gyp +43 -0
  5. package/dist/cjs/exceptions/NetIfException.d.ts +17 -0
  6. package/dist/cjs/exceptions/NetIfException.js +24 -0
  7. package/dist/cjs/exceptions/NetIfNotEnforcedException.d.ts +12 -0
  8. package/dist/cjs/exceptions/NetIfNotEnforcedException.js +19 -0
  9. package/dist/cjs/exceptions/NetIfNotFoundException.d.ts +12 -0
  10. package/dist/cjs/exceptions/NetIfNotFoundException.js +19 -0
  11. package/dist/cjs/exceptions/NetIfNotSupportedException.d.ts +12 -0
  12. package/dist/cjs/exceptions/NetIfNotSupportedException.js +19 -0
  13. package/dist/cjs/exceptions/NetIfTimeoutException.d.ts +11 -0
  14. package/dist/cjs/exceptions/NetIfTimeoutException.js +18 -0
  15. package/dist/cjs/exports/Netif.d.ts +11 -0
  16. package/dist/cjs/exports/Netif.js +26 -0
  17. package/dist/cjs/lib/Addon.d.ts +5 -0
  18. package/dist/cjs/lib/Addon.js +9 -0
  19. package/dist/cjs/lib/Attachment.d.ts +152 -0
  20. package/dist/cjs/lib/Attachment.js +415 -0
  21. package/dist/cjs/lib/Enforcement.d.ts +22 -0
  22. package/dist/cjs/lib/Enforcement.js +45 -0
  23. package/dist/cjs/lib/Functions.d.ts +66 -0
  24. package/dist/cjs/lib/Functions.js +97 -0
  25. package/dist/cjs/lib/InterfaceListener.d.ts +92 -0
  26. package/dist/cjs/lib/InterfaceListener.js +99 -0
  27. package/dist/cjs/lib/InterfaceUDPSocket.d.ts +119 -0
  28. package/dist/cjs/lib/InterfaceUDPSocket.js +245 -0
  29. package/dist/cjs/lib/Internal.d.ts +10 -0
  30. package/dist/cjs/lib/Internal.js +9 -0
  31. package/dist/cjs/lib/Native.d.ts +54 -0
  32. package/dist/cjs/lib/Native.js +83 -0
  33. package/dist/cjs/lib/NetworkInterface.d.ts +313 -0
  34. package/dist/cjs/lib/NetworkInterface.js +391 -0
  35. package/dist/cjs/lib/Registry.d.ts +26 -0
  36. package/dist/cjs/lib/Registry.js +103 -0
  37. package/dist/cjs/lib/Snapshot.d.ts +17 -0
  38. package/dist/cjs/lib/Snapshot.js +84 -0
  39. package/dist/cjs/lib/Types.d.ts +63 -0
  40. package/dist/cjs/lib/Types.js +2 -0
  41. package/dist/cjs/package.json +1 -0
  42. package/dist/esm/exceptions/NetIfException.js +20 -0
  43. package/dist/esm/exceptions/NetIfNotEnforcedException.js +15 -0
  44. package/dist/esm/exceptions/NetIfNotFoundException.js +15 -0
  45. package/dist/esm/exceptions/NetIfNotSupportedException.js +15 -0
  46. package/dist/esm/exceptions/NetIfTimeoutException.js +14 -0
  47. package/dist/esm/exports/Netif.js +9 -0
  48. package/dist/esm/lib/Addon.js +10 -0
  49. package/dist/esm/lib/Attachment.js +410 -0
  50. package/dist/esm/lib/Enforcement.js +41 -0
  51. package/dist/esm/lib/Functions.js +92 -0
  52. package/dist/esm/lib/InterfaceListener.js +95 -0
  53. package/dist/esm/lib/InterfaceUDPSocket.js +241 -0
  54. package/dist/esm/lib/Internal.js +6 -0
  55. package/dist/esm/lib/Native.js +75 -0
  56. package/dist/esm/lib/NetworkInterface.js +386 -0
  57. package/dist/esm/lib/Registry.js +100 -0
  58. package/dist/esm/lib/Snapshot.js +79 -0
  59. package/dist/esm/lib/Types.js +1 -0
  60. package/dist/types/exceptions/NetIfException.d.ts +17 -0
  61. package/dist/types/exceptions/NetIfNotEnforcedException.d.ts +12 -0
  62. package/dist/types/exceptions/NetIfNotFoundException.d.ts +12 -0
  63. package/dist/types/exceptions/NetIfNotSupportedException.d.ts +12 -0
  64. package/dist/types/exceptions/NetIfTimeoutException.d.ts +11 -0
  65. package/dist/types/exports/Netif.d.ts +11 -0
  66. package/dist/types/lib/Addon.d.ts +5 -0
  67. package/dist/types/lib/Attachment.d.ts +152 -0
  68. package/dist/types/lib/Enforcement.d.ts +22 -0
  69. package/dist/types/lib/Functions.d.ts +66 -0
  70. package/dist/types/lib/InterfaceListener.d.ts +92 -0
  71. package/dist/types/lib/InterfaceUDPSocket.d.ts +119 -0
  72. package/dist/types/lib/Internal.d.ts +10 -0
  73. package/dist/types/lib/Native.d.ts +54 -0
  74. package/dist/types/lib/NetworkInterface.d.ts +313 -0
  75. package/dist/types/lib/Registry.d.ts +26 -0
  76. package/dist/types/lib/Snapshot.d.ts +17 -0
  77. package/dist/types/lib/Types.d.ts +63 -0
  78. package/native/Interfaces.cpp +34 -0
  79. package/native/Interfaces.h +47 -0
  80. package/native/Sockets.h +36 -0
  81. package/native/Watcher.h +27 -0
  82. package/native/addon.cpp +195 -0
  83. package/native/darwin/Interfaces.cpp +150 -0
  84. package/native/darwin/Sockets.cpp +44 -0
  85. package/native/darwin/Watcher.cpp +113 -0
  86. package/native/linux/Interfaces.cpp +133 -0
  87. package/native/linux/RouteNetlink.h +38 -0
  88. package/native/linux/Sockets.cpp +89 -0
  89. package/native/linux/Watcher.cpp +70 -0
  90. package/native/windows/Interfaces.cpp +78 -0
  91. package/native/windows/Sockets.cpp +56 -0
  92. package/native/windows/Watcher.cpp +53 -0
  93. package/package.json +81 -4
@@ -0,0 +1,415 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Attachment = void 0;
4
+ exports.codeOf = codeOf;
5
+ const node_events_1 = require("node:events");
6
+ const node_fs_1 = require("node:fs");
7
+ const NetIfNotEnforcedException_js_1 = require("../exceptions/NetIfNotEnforcedException.js");
8
+ const Enforcement_js_1 = require("./Enforcement.js");
9
+ const Internal_js_1 = require("./Internal.js");
10
+ const Native_js_1 = require("./Native.js");
11
+ const RETRY_FIRST = 1000;
12
+ const RETRY_LAST = 30000;
13
+ //A port 0 shared by the sockets of the families: chosen by the first one, taken by another program for the next one
14
+ const PORT_ATTEMPTS = 3;
15
+ /**
16
+ * The code of the error of the system an error carries
17
+ */
18
+ function codeOf(error) {
19
+ return error?.code ?? error?.errno;
20
+ }
21
+ /**
22
+ * Whether an error is the one of an address gone or not usable yet: the next reading of the interfaces settles it
23
+ */
24
+ function vanished(error) {
25
+ const code = codeOf(error);
26
+ return code === 'EADDRNOTAVAIL' || code === 'ENODEV' || code === 'ENXIO';
27
+ }
28
+ /**
29
+ * The sockets of an interface kept matching it (the base of the listeners and the UDP sockets): on Linux sockets bound
30
+ * to the interface, one by family, made again when the interface is plugged back (a new index); elsewhere (and on Linux
31
+ * without the permission to bind) a socket by address of the interface, made and closed as its addresses change. The
32
+ * guarantee that the system refuses what comes through another interface (`enforced`), followed; with `strict` the
33
+ * sockets are closed while it is lost. The changes of the interface are followed one at a time.
34
+ */
35
+ class Attachment extends node_events_1.EventEmitter {
36
+ #requestedPort;
37
+ #endpoints = new Map();
38
+ #ownsInterface;
39
+ #port;
40
+ //Linux: the sockets bound to the interface until the process may not (then by address)
41
+ #bound = process.platform === 'linux';
42
+ #enforcement;
43
+ #paused = false;
44
+ #closed = false;
45
+ #stopObserving;
46
+ #syncing = Promise.resolve();
47
+ #again = false;
48
+ #retry;
49
+ #retryDelay = RETRY_FIRST;
50
+ #alive;
51
+ /**
52
+ * Internal: the options checked.
53
+ * @param networkInterface The interface.
54
+ * @param options The port, the families and `strict`.
55
+ * @param ownsInterface Whether the interface is closed with it.
56
+ */
57
+ constructor(networkInterface, options, ownsInterface) {
58
+ super();
59
+ if (!Number.isInteger(options?.port) || options.port < 0 || options.port > 65535)
60
+ throw new TypeError(`${String(options?.port)} is not a port`);
61
+ const family = options.family ?? 'both';
62
+ if (!['IPv4', 'IPv6', 'both'].includes(family))
63
+ throw new TypeError(`${String(family)} is not a family ('IPv4', 'IPv6' or 'both')`);
64
+ this.networkInterface = networkInterface;
65
+ this.families = family === 'both' ? ['IPv4', 'IPv6'] : [family];
66
+ this.strict = options.strict ?? false;
67
+ this.#requestedPort = options.port;
68
+ this.#port = options.port || undefined;
69
+ this.#ownsInterface = ownsInterface;
70
+ this.#enforcement = this.#enforcementNow();
71
+ }
72
+ /**
73
+ * Whether the system refuses, through another interface, what is sent to the addresses of the interface: always on
74
+ * Linux (the sockets are bound to the interface), on Windows while every other interface receives with the strong
75
+ * host model and does not forward, on macOS when it is set to (`net.inet.ip.check_interface` 2, forwarding off).
76
+ */
77
+ get enforced() {
78
+ return this.#enforcement.enforced;
79
+ }
80
+ /**
81
+ * Why what comes through another interface is not refused, `undefined` while it is.
82
+ */
83
+ get reason() {
84
+ return this.#enforcement.reason;
85
+ }
86
+ /**
87
+ * The port, `undefined` until a port 0 was given one by the system.
88
+ */
89
+ get port() {
90
+ return this.#port;
91
+ }
92
+ /**
93
+ * Whether the interface is listened on now (it exists, and has an address where its addresses are listened on).
94
+ */
95
+ get listening() {
96
+ return this.#endpoints.size > 0;
97
+ }
98
+ /**
99
+ * The addresses it can be reached at now. On Linux the sockets bound to the interface also get what is sent to
100
+ * another address of the machine through the interface.
101
+ */
102
+ get addresses() {
103
+ if (this.#port === undefined || !this.#endpoints.size)
104
+ return [];
105
+ const families = new Set([...this.#endpoints.values()].map((endpoint) => endpoint.family));
106
+ const addresses = this.#bound
107
+ ? this.networkInterface.addresses.filter((address) => families.has(address.family))
108
+ : [...this.#endpoints.values()].map((endpoint) => endpoint.address);
109
+ return addresses.map((address) => {
110
+ const listening = { address: address.address, family: address.family, port: this.#port };
111
+ if (address.family === 'IPv6')
112
+ listening.scopeid = address.scopeid ?? 0;
113
+ return listening;
114
+ });
115
+ }
116
+ /**
117
+ * Whether it was closed.
118
+ */
119
+ get closed() {
120
+ return this.#closed;
121
+ }
122
+ /**
123
+ * Whether the sockets are bound to the interface (Linux), not to its addresses
124
+ */
125
+ get boundToInterface() {
126
+ return this.#bound;
127
+ }
128
+ /**
129
+ * The sockets now
130
+ */
131
+ endpoints() {
132
+ return [...this.#endpoints.values()];
133
+ }
134
+ /**
135
+ * A socket made, before it is counted (its settings applied: the broadcasts, the multicast groups)
136
+ */
137
+ prepare(_endpoint) {
138
+ //Nothing by default
139
+ }
140
+ /**
141
+ * A socket closed, no longer counted
142
+ */
143
+ released(_endpoint) {
144
+ //Nothing by default
145
+ }
146
+ /**
147
+ * Internal: the first binding, which the method that made it waits for.
148
+ * @returns It.
149
+ */
150
+ async start() {
151
+ if (this.strict && !this.#enforcement.enforced) {
152
+ this.#finish();
153
+ throw new NetIfNotEnforcedException_js_1.NetIfNotEnforcedException(`The interface cannot be listened on strictly: ${this.#enforcement.reason}`);
154
+ }
155
+ this.#stopObserving = this.networkInterface[Internal_js_1.OBSERVE]((event) => {
156
+ if (event === 'close') {
157
+ void this.close();
158
+ return;
159
+ }
160
+ this.#sync();
161
+ });
162
+ try {
163
+ await this.#enqueue(() => this.#reconcile(true));
164
+ }
165
+ catch (error) {
166
+ this.#finish();
167
+ throw error;
168
+ }
169
+ if (this.strict && !this.#enforcement.enforced) {
170
+ this.#finish();
171
+ throw new NetIfNotEnforcedException_js_1.NetIfNotEnforcedException(`The interface cannot be listened on strictly: ${this.#enforcement.reason}`);
172
+ }
173
+ this.#keepAlive();
174
+ return this;
175
+ }
176
+ /**
177
+ * Stops: the sockets closed (the listener does not wait for the connections established, which are left to the
178
+ * server).
179
+ */
180
+ async close() {
181
+ if (this.#closed)
182
+ return;
183
+ this.#finish();
184
+ if (this.#ownsInterface)
185
+ this.networkInterface.close();
186
+ this.#emit('close');
187
+ }
188
+ #emit(event, ...args) {
189
+ this.emit(event, ...args);
190
+ }
191
+ #finish() {
192
+ this.#closed = true;
193
+ this.#stopObserving?.();
194
+ this.#stopObserving = undefined;
195
+ clearTimeout(this.#retry);
196
+ clearInterval(this.#alive);
197
+ this.#retry = undefined;
198
+ this.#alive = undefined;
199
+ this.#closeAll();
200
+ }
201
+ #closeAll() {
202
+ const endpoints = [...this.#endpoints.values()];
203
+ this.#endpoints.clear();
204
+ for (const endpoint of endpoints) {
205
+ this.closeHandle(endpoint.handle);
206
+ this.released(endpoint);
207
+ }
208
+ }
209
+ /**
210
+ * The interfaces were read again: the sockets made or closed to match them
211
+ */
212
+ #sync() {
213
+ if (this.#closed || this.#again)
214
+ return;
215
+ this.#again = true;
216
+ void this.#enqueue(async () => {
217
+ this.#again = false;
218
+ if (this.#closed)
219
+ return;
220
+ try {
221
+ await this.#reconcile(false);
222
+ }
223
+ catch (error) {
224
+ this.failed(error);
225
+ }
226
+ this.#keepAlive();
227
+ });
228
+ }
229
+ /**
230
+ * A reconciliation run after the ones queued: its promise (the queue goes on whatever its result)
231
+ */
232
+ #enqueue(task) {
233
+ const run = this.#syncing.then(task);
234
+ this.#syncing = run.catch(() => undefined);
235
+ return run;
236
+ }
237
+ /**
238
+ * While nothing is bound (the interface missing, no address, a retry pending), it keeps the process alive as a
239
+ * listening server does
240
+ */
241
+ #keepAlive() {
242
+ if (this.#closed || this.#endpoints.size) {
243
+ clearInterval(this.#alive);
244
+ this.#alive = undefined;
245
+ }
246
+ else if (!this.#alive) {
247
+ this.#alive = setInterval(() => undefined, 0x7fffffff);
248
+ }
249
+ }
250
+ /**
251
+ * An error of a binding again, or of a socket: the error event, the binding retried a little later
252
+ */
253
+ failed(error) {
254
+ if (this.#closed)
255
+ return;
256
+ this.#emit('error', error);
257
+ if (this.#retry)
258
+ return;
259
+ this.#retry = setTimeout(() => {
260
+ this.#retry = undefined;
261
+ this.#sync();
262
+ }, this.#retryDelay);
263
+ this.#retryDelay = Math.min(this.#retryDelay * 2, RETRY_LAST);
264
+ }
265
+ #enforcementNow() {
266
+ if (this.#bound)
267
+ return { enforced: true };
268
+ return (0, Enforcement_js_1.addressEnforcement)(this.networkInterface.index ?? 0, this.families);
269
+ }
270
+ /**
271
+ * The guarantee read again: its event when it changed; with strict, the sockets closed while it is lost
272
+ */
273
+ #checkEnforcement() {
274
+ const now = this.#enforcementNow();
275
+ const changed = now.enforced !== this.#enforcement.enforced;
276
+ this.#enforcement = now;
277
+ if (!changed)
278
+ return;
279
+ this.#emit('enforced', now.enforced);
280
+ if (!this.strict)
281
+ return;
282
+ if (!now.enforced) {
283
+ this.#paused = true;
284
+ this.#closeAll();
285
+ this.#emit('error', new NetIfNotEnforcedException_js_1.NetIfNotEnforcedException(`The listening stopped: ${now.reason}`));
286
+ }
287
+ else {
288
+ this.#paused = false;
289
+ }
290
+ }
291
+ #wanted(state) {
292
+ if (!state || this.#paused)
293
+ return [];
294
+ if (this.#bound)
295
+ return this.families.map((family) => ({ key: `bound|${family}|${state.index}`, family: family }));
296
+ return state.addresses
297
+ .filter((address) => this.families.includes(address.family))
298
+ .map((address) => ({ key: `address|${address.family}|${address.address}|${address.scopeid ?? ''}`, family: address.family, address: address }));
299
+ }
300
+ /**
301
+ * The sockets made and closed to match the interface; on the first time an error is thrown, later it is retried
302
+ */
303
+ async #reconcile(first) {
304
+ this.#checkEnforcement();
305
+ for (let attempt = 1;; attempt++) {
306
+ const state = this.networkInterface.info;
307
+ const wanted = this.#wanted(state);
308
+ const keys = new Set(wanted.map((entry) => entry.key));
309
+ for (const [key, endpoint] of [...this.#endpoints]) {
310
+ if (keys.has(key))
311
+ continue;
312
+ this.closeHandle(endpoint.handle);
313
+ this.#endpoints.delete(key);
314
+ this.released(endpoint);
315
+ }
316
+ let opened = false;
317
+ let byAddress = false;
318
+ try {
319
+ for (const entry of wanted) {
320
+ if (this.#closed)
321
+ return;
322
+ if (this.#endpoints.has(entry.key))
323
+ continue;
324
+ const result = await this.#open(entry, state);
325
+ if (result === 'opened')
326
+ opened = true;
327
+ if (result === 'by address') {
328
+ byAddress = true;
329
+ break;
330
+ }
331
+ }
332
+ }
333
+ catch (error) {
334
+ //A port 0 the system gave the first socket, taken for the next one: all of them again on another port
335
+ if (codeOf(error) === 'EADDRINUSE' && !this.#requestedPort && this.#endpoints.size && attempt < PORT_ATTEMPTS && first) {
336
+ this.#closeAll();
337
+ this.#port = undefined;
338
+ continue;
339
+ }
340
+ throw error;
341
+ }
342
+ //Linux without the permission to bind to the interface: the addresses bound instead, at once
343
+ if (byAddress)
344
+ continue;
345
+ this.#retryDelay = RETRY_FIRST;
346
+ if (opened)
347
+ this.#emit('listening');
348
+ return;
349
+ }
350
+ }
351
+ async #open(entry, state) {
352
+ let handle;
353
+ if (this.#bound) {
354
+ let bound;
355
+ try {
356
+ bound = (0, Native_js_1.nativeBindSocket)(state.name, entry.family === 'IPv4' ? 4 : 6, this.stream, this.#port ?? 0, 511);
357
+ }
358
+ catch (error) {
359
+ const code = codeOf(error);
360
+ if (code === 'EPERM' || code === 'EACCES') {
361
+ //Linux before 5.7 without CAP_NET_RAW: by address, without the guarantee
362
+ if (this.strict)
363
+ throw new NetIfNotEnforcedException_js_1.NetIfNotEnforcedException(`The process may not bind a socket to ${state.name}. ${Enforcement_js_1.LINUX_PERMISSION}`, { cause: error });
364
+ this.#bound = false;
365
+ this.#closeAll();
366
+ this.#enforcement = { enforced: false, reason: Enforcement_js_1.LINUX_PERMISSION };
367
+ this.#emit('enforced', false);
368
+ return 'by address';
369
+ }
370
+ //IPv6 off in the system: IPv4 only for 'both'
371
+ if (code === 'EAFNOSUPPORT' && this.families.length > 1)
372
+ return 'skipped';
373
+ if (vanished(error))
374
+ return 'skipped';
375
+ throw error;
376
+ }
377
+ try {
378
+ handle = await this.openHandle({ fd: bound.fd }, entry.family);
379
+ }
380
+ catch (error) {
381
+ try {
382
+ (0, node_fs_1.closeSync)(bound.fd);
383
+ }
384
+ catch {
385
+ //Owned by the socket already
386
+ }
387
+ throw error;
388
+ }
389
+ }
390
+ else {
391
+ const address = entry.address;
392
+ //An IPv6 address with its scope: the index on Windows, the name of the interface elsewhere (libuv)
393
+ const scope = address.family === 'IPv6' && address.scopeid ? `%${process.platform === 'win32' ? address.scopeid : state.name}` : '';
394
+ try {
395
+ handle = await this.openHandle({ host: `${address.address}${scope}`, port: this.#port ?? 0 }, entry.family);
396
+ }
397
+ catch (error) {
398
+ if (vanished(error))
399
+ return 'skipped';
400
+ throw error;
401
+ }
402
+ }
403
+ if (this.#closed) {
404
+ this.closeHandle(handle);
405
+ return 'skipped';
406
+ }
407
+ if (this.#port === undefined)
408
+ this.#port = this.portOf(handle);
409
+ const endpoint = { key: entry.key, family: entry.family, handle: handle, address: entry.address };
410
+ this.prepare(endpoint);
411
+ this.#endpoints.set(entry.key, endpoint);
412
+ return 'opened';
413
+ }
414
+ }
415
+ exports.Attachment = Attachment;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A family of addresses
3
+ */
4
+ export type Family = 'IPv4' | 'IPv6';
5
+ /**
6
+ * Whether the system refuses the connections and the datagrams reaching the addresses of an interface through another
7
+ * interface, and why not
8
+ */
9
+ export interface Enforcement {
10
+ enforced: boolean;
11
+ reason?: string;
12
+ }
13
+ /**
14
+ * Why a socket of Linux could not be bound to the interface
15
+ */
16
+ export declare const LINUX_PERMISSION: string;
17
+ /**
18
+ * Whether the system refuses, through the other interfaces, the packets for the addresses of the interface of the index
19
+ * when they are listened on by address: the strong host model of Windows (every other interface receiving with it and
20
+ * not forwarding) and of macOS (net.inet.ip.check_interface set to 2, forwarding off); never on Linux
21
+ */
22
+ export declare function addressEnforcement(index: number, families: Family[]): Enforcement;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LINUX_PERMISSION = void 0;
4
+ exports.addressEnforcement = addressEnforcement;
5
+ const Native_js_1 = require("./Native.js");
6
+ /**
7
+ * Why a socket of Linux could not be bound to the interface
8
+ */
9
+ exports.LINUX_PERMISSION = 'Linux accepts the packets of an address through any interface, and binding a socket to an interface (SO_BINDTODEVICE) needs the capability CAP_NET_RAW before Linux 5.7: give it to the process (setcap cap_net_raw+ep on its executable, AmbientCapabilities=CAP_NET_RAW in its systemd unit) or run a newer kernel';
10
+ /**
11
+ * Whether the system refuses, through the other interfaces, the packets for the addresses of the interface of the index
12
+ * when they are listened on by address: the strong host model of Windows (every other interface receiving with it and
13
+ * not forwarding) and of macOS (net.inet.ip.check_interface set to 2, forwarding off); never on Linux
14
+ */
15
+ function addressEnforcement(index, families) {
16
+ if (process.platform !== 'win32' && process.platform !== 'darwin') {
17
+ if (process.platform === 'linux')
18
+ return { enforced: false, reason: exports.LINUX_PERMISSION };
19
+ return { enforced: false, reason: `The interfaces of ${process.platform} are listened on by address, with no guarantee about the other interfaces` };
20
+ }
21
+ for (const family of families) {
22
+ const weak = (0, Native_js_1.nativeWeakReception)(index, family === 'IPv4' ? 4 : 6);
23
+ if (!weak)
24
+ continue;
25
+ const version = family === 'IPv4' ? 'ipv4' : 'ipv6';
26
+ if (process.platform === 'win32') {
27
+ const setting = weak.setting === 'WeakHostReceive' ? 'weakhostreceive' : 'forwarding';
28
+ return {
29
+ enforced: false,
30
+ reason: `Windows accepts ${family} packets for the addresses of the interface through the interface "${weak.interface}": its ${weak.setting} is enabled. Disable it as an administrator: netsh interface ${version} set interface "${weak.interface}" ${setting}=disabled`
31
+ };
32
+ }
33
+ if (weak.setting.endsWith('forwarding')) {
34
+ return {
35
+ enforced: false,
36
+ reason: `macOS forwards ${family} (${weak.setting} is 1, as while a virtual machine or a container shares the network), and does not check the interface packets come through then`
37
+ };
38
+ }
39
+ return {
40
+ enforced: false,
41
+ reason: `macOS accepts the ${family} packets of an address through any interface (${weak.setting} is not 2). Make it refuse them: sudo sysctl -w ${weak.setting}=2, with forwarding off`
42
+ };
43
+ }
44
+ return { enforced: true };
45
+ }
@@ -0,0 +1,66 @@
1
+ import { type Server } from 'node:net';
2
+ import { InterfaceListener, type ListenOptions } from './InterfaceListener.js';
3
+ import { type InterfaceSelector } from './NetworkInterface.js';
4
+ import { type NetworkInterfaceInfo } from './Types.js';
5
+ /**
6
+ * The network interfaces of the machine, the ones without an address too, with their state: their name, index,
7
+ * hardware address, whether they are enabled and connected, their usable addresses. More than `os.networkInterfaces()`,
8
+ * which lists only the interfaces with an address and no state.
9
+ * @returns The interfaces, in the order of the system.
10
+ * @example
11
+ * ```typescript
12
+ * import {listInterfaces, type NetworkInterfaceInfo} from '@lakutata/netif'
13
+ *
14
+ * for (const entry of listInterfaces()) {
15
+ * console.log(entry.name, entry.running ? 'connected' : 'not connected', entry.addresses.map(address => address.cidr))
16
+ * }
17
+ * const connected: NetworkInterfaceInfo[] = listInterfaces().filter(entry => entry.running && !entry.loopback)
18
+ * ```
19
+ */
20
+ export declare function listInterfaces(): NetworkInterfaceInfo[];
21
+ /**
22
+ * Listens on an interface for a server: `new NetworkInterface(selector).listen(server, options)` in one call, the
23
+ * interface followed until the listener is closed.
24
+ * @param selector The interface: its name, `{mac}` or `{index}`.
25
+ * @param server A `net.Server` or a server built on one (`http`, `https`, `http2`, `tls`), which must not listen itself.
26
+ * @param options The port, the families, `strict` and the backlog.
27
+ * @returns The listener.
28
+ * @throws NetIfNotEnforcedException With `strict`, where the system cannot refuse the connections coming through another
29
+ * interface.
30
+ * @throws NetIfNotFoundException An index or a hardware address that cannot be found.
31
+ * @throws NetIfException The system refused the first listening, its code as `errno`.
32
+ * @example
33
+ * ```typescript
34
+ * import {createServer} from 'node:http'
35
+ * import {type InterfaceListener, listenOn} from '@lakutata/netif'
36
+ *
37
+ * const listener: InterfaceListener = await listenOn('eth1', createServer((request, response) => response.end('ok')), {port: 8080})
38
+ * console.log(listener.addresses)
39
+ * ```
40
+ */
41
+ export declare function listenOn(selector: InterfaceSelector, server: Server, options: ListenOptions): Promise<InterfaceListener>;
42
+ /**
43
+ * Listens on several interfaces for a server: `listenOn()` for each of them, one listener by interface, the interfaces
44
+ * followed until their listeners are closed. When one of them cannot be listened on, the ones listened on already are
45
+ * closed and the error is thrown. Several interfaces need a port: 0 lets the system choose one for the first
46
+ * interface, which may not exist yet. With it, the package implements `IInterfaceListening` of `@lakutata/core`: the
47
+ * entrypoints of lakutata listen on interfaces through it.
48
+ * @param selectors An interface (its name, `{mac}` or `{index}`), or several.
49
+ * @param server A `net.Server` or a server built on one (`http`, `https`, `http2`, `tls`), which must not listen itself.
50
+ * @param options The port, the families, `strict` and the backlog, the same for every interface.
51
+ * @returns The listeners, in the order of the interfaces.
52
+ * @throws TypeError No interface, or a port 0 with several of them.
53
+ * @throws NetIfNotEnforcedException With `strict`, where the system cannot refuse the connections coming through another
54
+ * interface.
55
+ * @throws NetIfException The system refused a first listening, its code as `errno`.
56
+ * @example
57
+ * ```typescript
58
+ * import {createServer} from 'node:http'
59
+ * import {type InterfaceListener, listenOnAll} from '@lakutata/netif'
60
+ *
61
+ * const server = createServer((request, response) => response.end('ok'))
62
+ * const listeners: InterfaceListener[] = await listenOnAll(['eth1', {mac: '52:54:00:12:34:56'}], server, {port: 8080})
63
+ * for (const listener of listeners) listener.on('error', error => console.error(error))
64
+ * ```
65
+ */
66
+ export declare function listenOnAll(selectors: InterfaceSelector | InterfaceSelector[], server: Server, options: ListenOptions): Promise<InterfaceListener[]>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listInterfaces = listInterfaces;
4
+ exports.listenOn = listenOn;
5
+ exports.listenOnAll = listenOnAll;
6
+ const InterfaceListener_js_1 = require("./InterfaceListener.js");
7
+ const NetworkInterface_js_1 = require("./NetworkInterface.js");
8
+ const Snapshot_js_1 = require("./Snapshot.js");
9
+ /**
10
+ * The network interfaces of the machine, the ones without an address too, with their state: their name, index,
11
+ * hardware address, whether they are enabled and connected, their usable addresses. More than `os.networkInterfaces()`,
12
+ * which lists only the interfaces with an address and no state.
13
+ * @returns The interfaces, in the order of the system.
14
+ * @example
15
+ * ```typescript
16
+ * import {listInterfaces, type NetworkInterfaceInfo} from '@lakutata/netif'
17
+ *
18
+ * for (const entry of listInterfaces()) {
19
+ * console.log(entry.name, entry.running ? 'connected' : 'not connected', entry.addresses.map(address => address.cidr))
20
+ * }
21
+ * const connected: NetworkInterfaceInfo[] = listInterfaces().filter(entry => entry.running && !entry.loopback)
22
+ * ```
23
+ */
24
+ function listInterfaces() {
25
+ return (0, Snapshot_js_1.readInterfaces)().interfaces;
26
+ }
27
+ /**
28
+ * Listens on an interface for a server: `new NetworkInterface(selector).listen(server, options)` in one call, the
29
+ * interface followed until the listener is closed.
30
+ * @param selector The interface: its name, `{mac}` or `{index}`.
31
+ * @param server A `net.Server` or a server built on one (`http`, `https`, `http2`, `tls`), which must not listen itself.
32
+ * @param options The port, the families, `strict` and the backlog.
33
+ * @returns The listener.
34
+ * @throws NetIfNotEnforcedException With `strict`, where the system cannot refuse the connections coming through another
35
+ * interface.
36
+ * @throws NetIfNotFoundException An index or a hardware address that cannot be found.
37
+ * @throws NetIfException The system refused the first listening, its code as `errno`.
38
+ * @example
39
+ * ```typescript
40
+ * import {createServer} from 'node:http'
41
+ * import {type InterfaceListener, listenOn} from '@lakutata/netif'
42
+ *
43
+ * const listener: InterfaceListener = await listenOn('eth1', createServer((request, response) => response.end('ok')), {port: 8080})
44
+ * console.log(listener.addresses)
45
+ * ```
46
+ */
47
+ async function listenOn(selector, server, options) {
48
+ const networkInterface = new NetworkInterface_js_1.NetworkInterface(selector);
49
+ try {
50
+ return await new InterfaceListener_js_1.InterfaceListener(networkInterface, server, options, true).start();
51
+ }
52
+ catch (error) {
53
+ networkInterface.close();
54
+ throw error;
55
+ }
56
+ }
57
+ /**
58
+ * Listens on several interfaces for a server: `listenOn()` for each of them, one listener by interface, the interfaces
59
+ * followed until their listeners are closed. When one of them cannot be listened on, the ones listened on already are
60
+ * closed and the error is thrown. Several interfaces need a port: 0 lets the system choose one for the first
61
+ * interface, which may not exist yet. With it, the package implements `IInterfaceListening` of `@lakutata/core`: the
62
+ * entrypoints of lakutata listen on interfaces through it.
63
+ * @param selectors An interface (its name, `{mac}` or `{index}`), or several.
64
+ * @param server A `net.Server` or a server built on one (`http`, `https`, `http2`, `tls`), which must not listen itself.
65
+ * @param options The port, the families, `strict` and the backlog, the same for every interface.
66
+ * @returns The listeners, in the order of the interfaces.
67
+ * @throws TypeError No interface, or a port 0 with several of them.
68
+ * @throws NetIfNotEnforcedException With `strict`, where the system cannot refuse the connections coming through another
69
+ * interface.
70
+ * @throws NetIfException The system refused a first listening, its code as `errno`.
71
+ * @example
72
+ * ```typescript
73
+ * import {createServer} from 'node:http'
74
+ * import {type InterfaceListener, listenOnAll} from '@lakutata/netif'
75
+ *
76
+ * const server = createServer((request, response) => response.end('ok'))
77
+ * const listeners: InterfaceListener[] = await listenOnAll(['eth1', {mac: '52:54:00:12:34:56'}], server, {port: 8080})
78
+ * for (const listener of listeners) listener.on('error', error => console.error(error))
79
+ * ```
80
+ */
81
+ async function listenOnAll(selectors, server, options) {
82
+ const list = Array.isArray(selectors) ? selectors : [selectors];
83
+ if (!list.length)
84
+ throw new TypeError('listenOnAll() takes an interface at least');
85
+ if (list.length > 1 && options?.port === 0)
86
+ throw new TypeError('Several interfaces need a port: 0 lets the system choose one for the first interface, which may not exist yet');
87
+ const listeners = [];
88
+ try {
89
+ for (const selector of list)
90
+ listeners.push(await listenOn(selector, server, options));
91
+ }
92
+ catch (error) {
93
+ await Promise.all(listeners.map((listener) => listener.close()));
94
+ throw error;
95
+ }
96
+ return listeners;
97
+ }