@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.
- package/LICENSE +23 -0
- package/README.md +299 -2
- package/README.zh-CN.md +229 -0
- package/binding.gyp +43 -0
- package/dist/cjs/exceptions/NetIfException.d.ts +17 -0
- package/dist/cjs/exceptions/NetIfException.js +24 -0
- package/dist/cjs/exceptions/NetIfNotEnforcedException.d.ts +12 -0
- package/dist/cjs/exceptions/NetIfNotEnforcedException.js +19 -0
- package/dist/cjs/exceptions/NetIfNotFoundException.d.ts +12 -0
- package/dist/cjs/exceptions/NetIfNotFoundException.js +19 -0
- package/dist/cjs/exceptions/NetIfNotSupportedException.d.ts +12 -0
- package/dist/cjs/exceptions/NetIfNotSupportedException.js +19 -0
- package/dist/cjs/exceptions/NetIfTimeoutException.d.ts +11 -0
- package/dist/cjs/exceptions/NetIfTimeoutException.js +18 -0
- package/dist/cjs/exports/Netif.d.ts +11 -0
- package/dist/cjs/exports/Netif.js +26 -0
- package/dist/cjs/lib/Addon.d.ts +5 -0
- package/dist/cjs/lib/Addon.js +9 -0
- package/dist/cjs/lib/Attachment.d.ts +152 -0
- package/dist/cjs/lib/Attachment.js +415 -0
- package/dist/cjs/lib/Enforcement.d.ts +22 -0
- package/dist/cjs/lib/Enforcement.js +45 -0
- package/dist/cjs/lib/Functions.d.ts +66 -0
- package/dist/cjs/lib/Functions.js +97 -0
- package/dist/cjs/lib/InterfaceListener.d.ts +92 -0
- package/dist/cjs/lib/InterfaceListener.js +99 -0
- package/dist/cjs/lib/InterfaceUDPSocket.d.ts +119 -0
- package/dist/cjs/lib/InterfaceUDPSocket.js +245 -0
- package/dist/cjs/lib/Internal.d.ts +10 -0
- package/dist/cjs/lib/Internal.js +9 -0
- package/dist/cjs/lib/Native.d.ts +54 -0
- package/dist/cjs/lib/Native.js +83 -0
- package/dist/cjs/lib/NetworkInterface.d.ts +313 -0
- package/dist/cjs/lib/NetworkInterface.js +391 -0
- package/dist/cjs/lib/Registry.d.ts +26 -0
- package/dist/cjs/lib/Registry.js +103 -0
- package/dist/cjs/lib/Snapshot.d.ts +17 -0
- package/dist/cjs/lib/Snapshot.js +84 -0
- package/dist/cjs/lib/Types.d.ts +63 -0
- package/dist/cjs/lib/Types.js +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/exceptions/NetIfException.js +20 -0
- package/dist/esm/exceptions/NetIfNotEnforcedException.js +15 -0
- package/dist/esm/exceptions/NetIfNotFoundException.js +15 -0
- package/dist/esm/exceptions/NetIfNotSupportedException.js +15 -0
- package/dist/esm/exceptions/NetIfTimeoutException.js +14 -0
- package/dist/esm/exports/Netif.js +9 -0
- package/dist/esm/lib/Addon.js +10 -0
- package/dist/esm/lib/Attachment.js +410 -0
- package/dist/esm/lib/Enforcement.js +41 -0
- package/dist/esm/lib/Functions.js +92 -0
- package/dist/esm/lib/InterfaceListener.js +95 -0
- package/dist/esm/lib/InterfaceUDPSocket.js +241 -0
- package/dist/esm/lib/Internal.js +6 -0
- package/dist/esm/lib/Native.js +75 -0
- package/dist/esm/lib/NetworkInterface.js +386 -0
- package/dist/esm/lib/Registry.js +100 -0
- package/dist/esm/lib/Snapshot.js +79 -0
- package/dist/esm/lib/Types.js +1 -0
- package/dist/types/exceptions/NetIfException.d.ts +17 -0
- package/dist/types/exceptions/NetIfNotEnforcedException.d.ts +12 -0
- package/dist/types/exceptions/NetIfNotFoundException.d.ts +12 -0
- package/dist/types/exceptions/NetIfNotSupportedException.d.ts +12 -0
- package/dist/types/exceptions/NetIfTimeoutException.d.ts +11 -0
- package/dist/types/exports/Netif.d.ts +11 -0
- package/dist/types/lib/Addon.d.ts +5 -0
- package/dist/types/lib/Attachment.d.ts +152 -0
- package/dist/types/lib/Enforcement.d.ts +22 -0
- package/dist/types/lib/Functions.d.ts +66 -0
- package/dist/types/lib/InterfaceListener.d.ts +92 -0
- package/dist/types/lib/InterfaceUDPSocket.d.ts +119 -0
- package/dist/types/lib/Internal.d.ts +10 -0
- package/dist/types/lib/Native.d.ts +54 -0
- package/dist/types/lib/NetworkInterface.d.ts +313 -0
- package/dist/types/lib/Registry.d.ts +26 -0
- package/dist/types/lib/Snapshot.d.ts +17 -0
- package/dist/types/lib/Types.d.ts +63 -0
- package/native/Interfaces.cpp +34 -0
- package/native/Interfaces.h +47 -0
- package/native/Sockets.h +36 -0
- package/native/Watcher.h +27 -0
- package/native/addon.cpp +195 -0
- package/native/darwin/Interfaces.cpp +150 -0
- package/native/darwin/Sockets.cpp +44 -0
- package/native/darwin/Watcher.cpp +113 -0
- package/native/linux/Interfaces.cpp +133 -0
- package/native/linux/RouteNetlink.h +38 -0
- package/native/linux/Sockets.cpp +89 -0
- package/native/linux/Watcher.cpp +70 -0
- package/native/windows/Interfaces.cpp +78 -0
- package/native/windows/Sockets.cpp +56 -0
- package/native/windows/Watcher.cpp +53 -0
- package/package.json +81 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Server as HTTPServer } from 'node:http';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
import { NetIfNotSupportedException } from '../exceptions/NetIfNotSupportedException.js';
|
|
4
|
+
import { Attachment } from './Attachment.js';
|
|
5
|
+
/**
|
|
6
|
+
* Bun 1.4: whether its http server is an https one (Bun's https.Server is its http.Server, which answers a connection
|
|
7
|
+
* given to it in plain HTTP even when it was made with keys: the connections of an https server cannot be given to it).
|
|
8
|
+
* An internal symbol tells: its TLS options, null for a plain server; undefined when Bun has no such symbol.
|
|
9
|
+
*/
|
|
10
|
+
function bunTLS(server) {
|
|
11
|
+
const symbol = Object.getOwnPropertySymbols(server).find((candidate) => candidate.description === 'tls');
|
|
12
|
+
if (!symbol)
|
|
13
|
+
return undefined;
|
|
14
|
+
return !!server[symbol];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The listening of a server on a network interface: `NetworkInterface.listen()`. The listener accepts the connections
|
|
18
|
+
* and gives them to the server (`server.emit('connection', socket)`), which handles them as its own: the server itself
|
|
19
|
+
* does not listen (its `address()` is `null`, its `close()` does not stop the listener; use the ones of the listener).
|
|
20
|
+
* The interface is listened on again when its addresses change and when it is unplugged and plugged back; the
|
|
21
|
+
* connections established are left to the server. It implements `IInterfaceListener` of `@lakutata/core`.
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import {createServer} from 'node:http'
|
|
25
|
+
* import {type InterfaceListener, NetworkInterface} from '@lakutata/netif'
|
|
26
|
+
*
|
|
27
|
+
* const server = createServer((request, response) => response.end('ok'))
|
|
28
|
+
* const listener: InterfaceListener = await new NetworkInterface('eth1').listen(server, {port: 8080})
|
|
29
|
+
* console.log(listener.enforced, listener.addresses)
|
|
30
|
+
* listener.on('error', error => console.error(error))
|
|
31
|
+
* await listener.close()
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class InterfaceListener extends Attachment {
|
|
35
|
+
#backlog;
|
|
36
|
+
/**
|
|
37
|
+
* Internal: made by `NetworkInterface.listen()` and `listenOn()`; `start()` listens.
|
|
38
|
+
* @param networkInterface The interface.
|
|
39
|
+
* @param server The server.
|
|
40
|
+
* @param options The options of `listen()`.
|
|
41
|
+
* @param ownsInterface Whether the interface is closed with the listener (`listenOn()`).
|
|
42
|
+
*/
|
|
43
|
+
constructor(networkInterface, server, options, ownsInterface = false) {
|
|
44
|
+
if (!server || typeof server.emit !== 'function')
|
|
45
|
+
throw new TypeError('listen() takes a net.Server (http, https, http2, tls…)');
|
|
46
|
+
if (process.versions.bun && server instanceof HTTPServer && bunTLS(server) !== false) {
|
|
47
|
+
throw new NetIfNotSupportedException(bunTLS(server)
|
|
48
|
+
? 'The https server of Bun answers the connections given to it without TLS: in Bun, listen with http2.createSecureServer({key, cert, allowHTTP1: true}) or a tls.Server instead'
|
|
49
|
+
: 'Whether this server of Bun uses TLS cannot be told, and the https server of Bun answers the connections given to it without TLS: in Bun, listen with an http server of a known version, http2.createSecureServer() or a tls.Server');
|
|
50
|
+
}
|
|
51
|
+
super(networkInterface, options, ownsInterface);
|
|
52
|
+
this.stream = true;
|
|
53
|
+
this.server = server;
|
|
54
|
+
this.#backlog = options.backlog ?? 511;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Listens on the target: a server of the listener, the connections it accepts given to the server of the user
|
|
58
|
+
*/
|
|
59
|
+
openHandle(target, _family) {
|
|
60
|
+
const settings = this.server;
|
|
61
|
+
const inner = createServer({ allowHalfOpen: !!settings.allowHalfOpen, pauseOnConnect: !!settings.pauseOnConnect, noDelay: true }, (socket) => {
|
|
62
|
+
this.server.emit('connection', socket);
|
|
63
|
+
});
|
|
64
|
+
if (typeof this.server.maxConnections === 'number')
|
|
65
|
+
inner.maxConnections = this.server.maxConnections;
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const failed = (error) => {
|
|
68
|
+
inner.off('listening', listened);
|
|
69
|
+
inner.close();
|
|
70
|
+
reject(error);
|
|
71
|
+
};
|
|
72
|
+
const listened = () => {
|
|
73
|
+
inner.off('error', failed);
|
|
74
|
+
//Later errors of the socket: the ones of the listener
|
|
75
|
+
inner.on('error', (error) => this.failed(error));
|
|
76
|
+
resolve(inner);
|
|
77
|
+
};
|
|
78
|
+
inner.once('error', failed);
|
|
79
|
+
inner.once('listening', listened);
|
|
80
|
+
if ('fd' in target) {
|
|
81
|
+
inner.listen({ fd: target.fd, backlog: this.#backlog });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
inner.listen({ host: target.host, port: target.port, backlog: this.#backlog, exclusive: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
portOf(handle) {
|
|
89
|
+
const address = handle.address();
|
|
90
|
+
return address && typeof address === 'object' ? address.port : undefined;
|
|
91
|
+
}
|
|
92
|
+
closeHandle(handle) {
|
|
93
|
+
handle.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { createSocket } from 'node:dgram';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import { NetIfException } from '../exceptions/NetIfException.js';
|
|
4
|
+
import { NetIfNotSupportedException } from '../exceptions/NetIfNotSupportedException.js';
|
|
5
|
+
import { Attachment } from './Attachment.js';
|
|
6
|
+
import { nativeMembership } from './Native.js';
|
|
7
|
+
/**
|
|
8
|
+
* Whether an IPv4 address is in the network of an address of the interface
|
|
9
|
+
*/
|
|
10
|
+
function sameNetwork(destination, address) {
|
|
11
|
+
const toNumber = (text) => text.split('.').reduce((value, part) => value * 256 + Number(part), 0);
|
|
12
|
+
const mask = toNumber(address.netmask);
|
|
13
|
+
return (toNumber(destination) & mask) >>> 0 === (toNumber(address.address) & mask) >>> 0 || destination === '255.255.255.255';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A UDP socket of a network interface: `NetworkInterface.createUDPSocket()`. Its methods and events are the ones of a
|
|
17
|
+
* `dgram` socket. On Linux its sockets (one by family) are bound to the interface: they receive what comes through it,
|
|
18
|
+
* its broadcasts and the multicast of the groups joined there, whatever its addresses, and send through it. On Windows
|
|
19
|
+
* and macOS a socket is bound to each address of the interface, again when they change. The datagrams that arrive while
|
|
20
|
+
* the socket is bound again are lost (UDP never guarantees their delivery).
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import {type InterfaceUDPSocket, NetworkInterface} from '@lakutata/netif'
|
|
24
|
+
*
|
|
25
|
+
* const lan: NetworkInterface = new NetworkInterface('eth1')
|
|
26
|
+
* const socket: InterfaceUDPSocket = await lan.createUDPSocket({port: 5353})
|
|
27
|
+
* socket.addMembership('224.0.0.251')
|
|
28
|
+
* socket.on('message', (message, remote) => console.log(`${remote.address}:${remote.port}`, message.length))
|
|
29
|
+
* socket.send('hello', 5353, '224.0.0.251')
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class InterfaceUDPSocket extends Attachment {
|
|
33
|
+
#broadcast;
|
|
34
|
+
#groups;
|
|
35
|
+
#descriptors;
|
|
36
|
+
//By address: the socket of each family that joined the groups (on every socket, a datagram of a group would be
|
|
37
|
+
//received once by each of them)
|
|
38
|
+
#members;
|
|
39
|
+
/**
|
|
40
|
+
* Internal: made by `NetworkInterface.createUDPSocket()`; `start()` binds it.
|
|
41
|
+
* @param networkInterface The interface.
|
|
42
|
+
* @param options The options of `createUDPSocket()`.
|
|
43
|
+
* @param ownsInterface Whether the interface is closed with the socket.
|
|
44
|
+
*/
|
|
45
|
+
constructor(networkInterface, options, ownsInterface = false) {
|
|
46
|
+
super(networkInterface, options, ownsInterface);
|
|
47
|
+
this.stream = false;
|
|
48
|
+
this.#broadcast = false;
|
|
49
|
+
this.#groups = new Set();
|
|
50
|
+
this.#descriptors = new WeakMap();
|
|
51
|
+
//By address: the socket of each family that joined the groups (on every socket, a datagram of a group would be
|
|
52
|
+
//received once by each of them)
|
|
53
|
+
this.#members = new Map();
|
|
54
|
+
}
|
|
55
|
+
openHandle(target, family) {
|
|
56
|
+
const socket = createSocket({ type: family === 'IPv4' ? 'udp4' : 'udp6' });
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const failed = (error) => {
|
|
59
|
+
try {
|
|
60
|
+
socket.close();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
//Not bound
|
|
64
|
+
}
|
|
65
|
+
reject(error);
|
|
66
|
+
};
|
|
67
|
+
const bound = () => {
|
|
68
|
+
socket.off('error', failed);
|
|
69
|
+
socket.on('error', (error) => this.failed(error));
|
|
70
|
+
socket.on('message', (message, remote) => {
|
|
71
|
+
this.emit('message', message, remote);
|
|
72
|
+
});
|
|
73
|
+
resolve(socket);
|
|
74
|
+
};
|
|
75
|
+
socket.once('error', failed);
|
|
76
|
+
if ('fd' in target) {
|
|
77
|
+
this.#descriptors.set(socket, target.fd);
|
|
78
|
+
socket.bind({ fd: target.fd }, bound);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
socket.bind({ address: target.host, port: target.port, exclusive: true }, bound);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
portOf(handle) {
|
|
86
|
+
return handle.address().port;
|
|
87
|
+
}
|
|
88
|
+
closeHandle(handle) {
|
|
89
|
+
try {
|
|
90
|
+
handle.close();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
//Closed already
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A socket made: the broadcasts and the groups of the socket applied to it
|
|
98
|
+
*/
|
|
99
|
+
prepare(endpoint) {
|
|
100
|
+
if (endpoint.family === 'IPv4' && this.#broadcast)
|
|
101
|
+
endpoint.handle.setBroadcast(true);
|
|
102
|
+
this.#join(endpoint);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A socket closed: the groups it joined for its family joined by another one
|
|
106
|
+
*/
|
|
107
|
+
released(endpoint) {
|
|
108
|
+
if (this.#members.get(endpoint.family) !== endpoint)
|
|
109
|
+
return;
|
|
110
|
+
this.#members.delete(endpoint.family);
|
|
111
|
+
const next = this.endpoints().find((candidate) => candidate.family === endpoint.family);
|
|
112
|
+
if (next)
|
|
113
|
+
this.#join(next);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The groups of the family of a socket joined on it, unless another socket of the family has them already
|
|
117
|
+
*/
|
|
118
|
+
#join(endpoint) {
|
|
119
|
+
if (this.#members.has(endpoint.family))
|
|
120
|
+
return;
|
|
121
|
+
this.#members.set(endpoint.family, endpoint);
|
|
122
|
+
for (const group of this.#groups) {
|
|
123
|
+
if ((isIP(group) === 4 ? 'IPv4' : 'IPv6') !== endpoint.family)
|
|
124
|
+
continue;
|
|
125
|
+
try {
|
|
126
|
+
this.#membership(endpoint, group, true);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.failed(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
#membership(endpoint, group, join) {
|
|
134
|
+
if (this.boundToInterface) {
|
|
135
|
+
nativeMembership(this.#descriptors.get(endpoint.handle), group, this.networkInterface.index ?? 0, join);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
//Windows: the group on the interface of the address of the socket (IPv6: by the index of the interface)
|
|
139
|
+
const via = endpoint.family === 'IPv4' ? endpoint.address.address : `::%${this.networkInterface.index ?? 0}`;
|
|
140
|
+
if (join) {
|
|
141
|
+
endpoint.handle.addMembership(group, via);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
endpoint.handle.dropMembership(group, via);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Sends a datagram through the interface, from the socket of the family of the address (from the address of the
|
|
149
|
+
* interface in the network of the destination, where its addresses are bound).
|
|
150
|
+
* @param message The content.
|
|
151
|
+
* @param port The port of the destination.
|
|
152
|
+
* @param address The address of the destination (a broadcast address with `setBroadcast(true)`, a multicast group).
|
|
153
|
+
* @param callback Called once it is sent, with the error if it could not be; without it, the error is an `error`
|
|
154
|
+
* event.
|
|
155
|
+
*/
|
|
156
|
+
send(message, port, address, callback) {
|
|
157
|
+
const family = isIP(address.split('%')[0]) === 6 ? 'IPv6' : 'IPv4';
|
|
158
|
+
const endpoints = this.endpoints().filter((endpoint) => endpoint.family === family);
|
|
159
|
+
let chosen = endpoints[0];
|
|
160
|
+
if (!this.boundToInterface && endpoints.length > 1) {
|
|
161
|
+
if (family === 'IPv4') {
|
|
162
|
+
chosen = endpoints.find((endpoint) => sameNetwork(address, endpoint.address)) ?? chosen;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const linkLocal = address.toLowerCase().startsWith('fe80:');
|
|
166
|
+
chosen = endpoints.find((endpoint) => endpoint.address.address.startsWith('fe80:') === linkLocal) ?? chosen;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!chosen) {
|
|
170
|
+
const error = new NetIfException(`No ${family} socket is bound to the interface now: it does not exist${this.boundToInterface ? '' : ` or has no ${family} address`}`);
|
|
171
|
+
if (callback) {
|
|
172
|
+
queueMicrotask(() => callback(error));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
queueMicrotask(() => {
|
|
176
|
+
this.emit('error', error);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
chosen.handle.send(message, port, address, (error, bytes) => {
|
|
182
|
+
if (callback) {
|
|
183
|
+
callback(error, bytes);
|
|
184
|
+
}
|
|
185
|
+
else if (error) {
|
|
186
|
+
this.emit('error', error);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Whether the datagrams may be sent to broadcast addresses (`SO_BROADCAST`), kept when the socket is bound again.
|
|
192
|
+
* Where the addresses are bound (macOS, and Linux without the permission to bind to the interface) the broadcasts can
|
|
193
|
+
* be sent, not received.
|
|
194
|
+
* @param flag Whether they may.
|
|
195
|
+
*/
|
|
196
|
+
setBroadcast(flag) {
|
|
197
|
+
this.#broadcast = flag;
|
|
198
|
+
for (const endpoint of this.endpoints()) {
|
|
199
|
+
if (endpoint.family === 'IPv4')
|
|
200
|
+
endpoint.handle.setBroadcast(flag);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Joins a multicast group on the interface, kept when the socket is bound again: its datagrams are received.
|
|
205
|
+
* @param group The address of the group: `'224.0.0.251'`, `'ff02::fb'`.
|
|
206
|
+
* @throws NetIfNotSupportedException Where the addresses are bound on macOS and Linux: a socket bound to an address
|
|
207
|
+
* receives no multicast.
|
|
208
|
+
* @throws NetIfException The system refused, its code as `errno`.
|
|
209
|
+
*/
|
|
210
|
+
addMembership(group) {
|
|
211
|
+
if (!isIP(group))
|
|
212
|
+
throw new TypeError(`${group} is not the address of a multicast group`);
|
|
213
|
+
if (!this.boundToInterface && process.platform !== 'win32') {
|
|
214
|
+
throw new NetIfNotSupportedException(`A UDP socket bound to the addresses of an interface receives no multicast on ${process.platform === 'darwin' ? 'macOS' : process.platform}: only Linux binds a socket to the interface itself`);
|
|
215
|
+
}
|
|
216
|
+
if (this.#groups.has(group))
|
|
217
|
+
return;
|
|
218
|
+
this.#groups.add(group);
|
|
219
|
+
const family = isIP(group) === 4 ? 'IPv4' : 'IPv6';
|
|
220
|
+
const member = this.#members.get(family);
|
|
221
|
+
if (member)
|
|
222
|
+
this.#membership(member, group, true);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Leaves a multicast group joined.
|
|
226
|
+
* @param group The address of the group.
|
|
227
|
+
*/
|
|
228
|
+
dropMembership(group) {
|
|
229
|
+
if (!this.#groups.delete(group))
|
|
230
|
+
return;
|
|
231
|
+
const member = this.#members.get(isIP(group) === 4 ? 'IPv4' : 'IPv6');
|
|
232
|
+
if (!member)
|
|
233
|
+
return;
|
|
234
|
+
try {
|
|
235
|
+
this.#membership(member, group, false);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
//Left already (the socket made again since)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The key of the internal method of a NetworkInterface its listeners follow it by: the observer given is called with
|
|
3
|
+
* 'update' after each reading of the interfaces, with 'close' when the NetworkInterface is closed; the function returned
|
|
4
|
+
* removes it
|
|
5
|
+
*/
|
|
6
|
+
export const OBSERVE = Symbol('lakutata.netif.observe');
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getSystemErrorName } from 'node:util';
|
|
2
|
+
import { addon } from './Addon.js';
|
|
3
|
+
import { NetIfException } from '../exceptions/NetIfException.js';
|
|
4
|
+
/**
|
|
5
|
+
* The error of a call of the addon as an exception of the package: the code of the error of the system as its `errno`
|
|
6
|
+
* (the name of the POSIX error, the number of the Windows one)
|
|
7
|
+
*/
|
|
8
|
+
function exception(error) {
|
|
9
|
+
if (!(error instanceof Error))
|
|
10
|
+
return new NetIfException(String(error));
|
|
11
|
+
const thrown = new NetIfException(error.message, { cause: error });
|
|
12
|
+
const number = error.errno;
|
|
13
|
+
if (typeof number === 'number') {
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
thrown.errno = number;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
try {
|
|
19
|
+
thrown.errno = getSystemErrorName(-number);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
thrown.errno = number;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return thrown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Calls a function of the addon, its errors turned into exceptions of the package
|
|
30
|
+
*/
|
|
31
|
+
function call(run) {
|
|
32
|
+
try {
|
|
33
|
+
return run(addon);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw exception(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The interfaces of the machine as the addon reads them
|
|
41
|
+
*/
|
|
42
|
+
export function nativeList() {
|
|
43
|
+
return call((functions) => functions.list());
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Starts the notifications of the changes of the interfaces: changed() called at each one (once by thread)
|
|
47
|
+
*/
|
|
48
|
+
export function nativeWatch(changed) {
|
|
49
|
+
call((functions) => functions.watch(changed));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stops the notifications
|
|
53
|
+
*/
|
|
54
|
+
export function nativeUnwatch() {
|
|
55
|
+
call((functions) => functions.unwatch());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A socket bound to the interface of the name (Linux): its descriptor and port
|
|
59
|
+
*/
|
|
60
|
+
export function nativeBindSocket(name, family, stream, port, backlog) {
|
|
61
|
+
return call((functions) => functions.bindSocket(name, family, stream, port, backlog));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The socket of the descriptor joins or leaves the multicast group on the interface of the index (Linux)
|
|
65
|
+
*/
|
|
66
|
+
export function nativeMembership(fd, group, index, join) {
|
|
67
|
+
call((functions) => functions.membership(fd, group, index, join));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The setting that makes the system accept, through another interface, the packets for the addresses of the interface of
|
|
71
|
+
* the index; null when it refuses them (the strong host model of Windows and macOS)
|
|
72
|
+
*/
|
|
73
|
+
export function nativeWeakReception(index, family) {
|
|
74
|
+
return call((functions) => functions.weakReception(index, family));
|
|
75
|
+
}
|