/
udp-helper.ts
181 lines (163 loc) · 4.28 KB
/
udp-helper.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import dgram from "node:dgram";
import { once } from "node:events";
import os from "node:os";
const DEFAULT_UNICAST_PORT = 6363;
const DEFAULT_MULTICAST_GROUP = "224.0.23.170";
const DEFAULT_MULTICAST_PORT = 56363;
export type SocketBufferOptions = Pick<dgram.SocketOptions, "recvBufferSize" | "sendBufferSize">;
export type AddressFamily = 4 | 6;
/** {@link openSocket} options. */
export interface OpenSocketOptions extends SocketBufferOptions {
/**
* IPv4 or IPv6.
* @defaultValue
* IPv4, unless hostname is a literal IPv6 address.
*/
family?: AddressFamily;
/** Bind options, such as local address and port. */
bind?: dgram.BindOptions;
}
/** Create a UDP socket and start listening on local endpoint. */
export async function openSocket({
family,
recvBufferSize,
sendBufferSize,
bind = {},
}: OpenSocketOptions): Promise<dgram.Socket> {
family ??= bind.address?.includes(":") ? 6 : 4;
const sock = dgram.createSocket({
type: `udp${family}`,
reuseAddr: true,
recvBufferSize,
sendBufferSize,
});
try {
sock.bind(bind);
await once(sock, "listening");
} catch (err: unknown) {
sock.close();
throw err;
}
return sock;
}
/** {@link connect} options. */
export interface ConnectOptions {
/** Remote address. */
host: string;
/** Remote port. */
port?: number;
}
/** Connect a UDP socket to remote endpoint. */
export async function connect(sock: dgram.Socket, {
host,
port = DEFAULT_UNICAST_PORT,
}: ConnectOptions): Promise<dgram.Socket> {
try {
sock.connect(port, host);
await once(sock, "connect");
} catch (err: unknown) {
sock.close();
throw err;
}
return sock;
}
/** {@link openUnicast} options. */
export interface UnicastOptions extends OpenSocketOptions, ConnectOptions {}
/** Create a UDP socket and connect to remote endpoint. */
export async function openUnicast(opts: UnicastOptions): Promise<dgram.Socket> {
if (!opts.family && opts.host.includes(":")) {
opts.family = 6;
}
const sock = await openSocket(opts);
return connect(sock, opts);
}
/**
* List network interfaces capable of IPv4 multicast.
* @returns IPv4 address of each network interface.
*/
export function listMulticastIntfs(): string[] {
return Object.values(os.networkInterfaces()).flatMap((addrs = []) => {
for (const addr of addrs) {
if (addr.family === "IPv4" && !addr.internal) {
return addr.address;
}
}
return [];
});
}
/** {@link openMulticastRx} and {@link openMulticastTx} options. */
export interface MulticastOptions extends SocketBufferOptions {
/** IPv4 address of local network interface. */
intf: string;
/**
* Multicast group address.
* @defaultValue 224.0.23.170
*/
group?: string;
/**
* Local and group port.
* @defaultValue 56363
*/
port?: number;
/**
* Multicast TTL.
* @defaultValue 1
*
* @remarks
* Changing this option is inadvisable except for unit testing.
*/
multicastTtl?: number;
/**
* MulticastLoopback flag.
* @defaultValue false
*
* @remarks
* Changing this option is inadvisable except for unit testing.
*/
multicastLoopback?: boolean;
}
/** Create a UDP socket and prepare for receiving multicast datagrams. */
export async function openMulticastRx(opts: MulticastOptions): Promise<dgram.Socket> {
const {
intf,
group = DEFAULT_MULTICAST_GROUP,
port = DEFAULT_MULTICAST_PORT,
multicastLoopback = false,
} = opts;
const sock = await openSocket({
...opts,
bind: { port },
});
try {
sock.setBroadcast(true);
sock.setMulticastLoopback(multicastLoopback);
sock.addMembership(group, intf);
} catch (err: unknown) {
sock.close();
throw err;
}
return sock;
}
/** Create a UDP socket and prepare for transmitting multicast datagrams. */
export async function openMulticastTx(opts: MulticastOptions): Promise<dgram.Socket> {
const {
intf,
group = DEFAULT_MULTICAST_GROUP,
port = DEFAULT_MULTICAST_PORT,
multicastTtl = 1,
} = opts;
const sock = await openSocket({
...opts,
bind: { address: intf, port },
});
try {
sock.setMulticastTTL(multicastTtl);
sock.setMulticastInterface(intf);
sock.connect(port, group);
await once(sock, "connect");
} catch (err: unknown) {
sock.close();
throw err;
}
return sock;
}