lemon-tls 0.9.0 → 0.11.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.
@@ -18,14 +18,56 @@
18
18
  */
19
19
 
20
20
  import dgram from 'node:dgram';
21
+ import dns from 'node:dns';
22
+ import net from 'node:net';
23
+ import crypto from 'node:crypto';
21
24
  import { EventEmitter } from 'node:events';
22
- import DTLSSession from './dtls_session.js';
25
+ import DTLSSession, { SERVER_CID_LEN } from './dtls_session.js';
26
+ import { datagramCid } from './record.js';
27
+ import { localLists, versionsInRange, pskOptions } from './node_options.js';
23
28
 
24
29
 
25
30
  // ============================================================
26
31
  // DTLSSocket — single DTLS connection over UDP
27
32
  // ============================================================
28
33
 
34
+ // connectDTLS's own UDP socket (not exported): the DTLSSocket may replace it
35
+ // by one of the family its host name resolves to, and closes it when done.
36
+ const kOwnsTransport = Symbol('ownsTransport');
37
+ const kTransport = Symbol('transport');
38
+ // The server's: told when one of its connections takes a new address
39
+ // (setRemoteAddress), to find it there.
40
+ const kAddressMoved = Symbol('addressMoved');
41
+
42
+
43
+ // The UDP sockets this module binds itself (createDTLSServer, connectDTLS):
44
+ // 4 MB receive and send buffers, or socketBufferSize (bytes; 0 or false, the
45
+ // OS default). The default (208 KB on Linux) holds some 180 datagrams, and a
46
+ // burst bigger than one event-loop turn drains is dropped in the kernel,
47
+ // silently: a retransmission, and a cut in the peer's sending rate.
48
+ // The kernel caps the size at net.core.rmem_max / wmem_max. A socket handed
49
+ // in (new DTLSSocket(udp, ...)) is the caller's, and keeps its own.
50
+ const DEFAULT_SOCKET_BUFFER_SIZE = 4 << 20;
51
+ function checkedSocketBufferSize(v) {
52
+ if (v === undefined || v === null) return DEFAULT_SOCKET_BUFFER_SIZE;
53
+ if (v === 0 || v === false) return 0;
54
+ if (!Number.isInteger(v) || v < 0 || v > 0x7fffffff) throw new RangeError('socketBufferSize must be a positive integer (bytes), 0 or false, got ' + v);
55
+ return v;
56
+ }
57
+ function createUdpSocket(base, size) {
58
+ return dgram.createSocket(size > 0 ? Object.assign({}, base, { recvBufferSize: size, sendBufferSize: size }) : base);
59
+ }
60
+
61
+ // What a DTLSSocket — or the server, for each of its connections — sets
62
+ // itself. Every other option goes on to the DTLSSession as the caller gave
63
+ // it: these were hand-copied lists, and what one left out did not exist —
64
+ // createDTLSServer dropped ca, rejectUnauthorized, checkServerIdentity and
65
+ // cipherSuites, so a server with requestCert and a ca took ANY client
66
+ // certificate (a self-signed one included).
67
+ function sessionOptions(options, own) {
68
+ return Object.assign({}, options, own);
69
+ }
70
+
29
71
  function DTLSSocket(udpSocket, options) {
30
72
  if (!(this instanceof DTLSSocket)) return new DTLSSocket(udpSocket, options);
31
73
  options = options || {};
@@ -35,33 +77,70 @@ function DTLSSocket(udpSocket, options) {
35
77
 
36
78
  let remoteAddress = options.remoteAddress || null;
37
79
  let remotePort = options.remotePort || null;
80
+ let ownsTransport = options[kOwnsTransport] === true;
81
+
82
+ // A client given a host NAME: resolved once, here. It was used as the
83
+ // address as it was — every datagram sent to it paid a lookup of its own
84
+ // (dgram resolves a name per send), and every datagram back was dropped,
85
+ // its rinfo.address (an IP) never equal to the name: connectDTLS to
86
+ // 'localhost' never connected. What the session sends meanwhile — its
87
+ // ClientHello — waits for the address; what arrives meanwhile is no one's.
88
+ // A socket of our own (connectDTLS) resolves to IPv4 or IPv6, whichever the
89
+ // name has first, and gets a UDP socket of that family; a caller's socket
90
+ // keeps its family and the name must resolve to it.
91
+ let resolving = null;
92
+ if (!options.isServer && remoteAddress && net.isIP(remoteAddress) === 0) {
93
+ let name = remoteAddress;
94
+ remoteAddress = null;
95
+ resolving = [];
96
+ let family = ownsTransport ? 0 : (udpSocket && udpSocket.type === 'udp6' ? 6 : 4);
97
+ dns.lookup(name, { family: family }, function(err, address, fam) {
98
+ let held = resolving;
99
+ resolving = null;
100
+ // Closed meanwhile: the UDP socket may be gone, and nothing is owed.
101
+ if (session.state === 'closed') return;
102
+ if (err) { ev.emit('error', err); session.close(); return; }
103
+ if (ownsTransport && (fam === 6) !== (udpSocket.type === 'udp6')) {
104
+ detachTransport(udpSocket);
105
+ try { udpSocket.close(); } catch (e) {}
106
+ udpSocket = createUdpSocket({ type: fam === 6 ? 'udp6' : 'udp4' }, checkedSocketBufferSize(options.socketBufferSize));
107
+ attachTransport(udpSocket);
108
+ }
109
+ remoteAddress = address;
110
+ for (let i = 0; i < held.length; i++) transmit(held[i]);
111
+ });
112
+ }
38
113
 
39
- // Create DTLSSession
40
- let session = new DTLSSession({
41
- isServer: !!options.isServer,
42
- servername: options.servername,
43
- SNICallback: options.SNICallback,
44
- cert: options.cert,
45
- key: options.key,
46
- rejectUnauthorized: options.rejectUnauthorized,
47
- checkServerIdentity: options.checkServerIdentity,
48
- requestCert: options.requestCert,
49
- ca: options.ca,
50
- alpnProtocols: options.alpnProtocols,
51
- minVersion: options.minVersion,
52
- maxVersion: options.maxVersion,
53
- mtu: options.mtu,
54
- groups: options.groups,
55
- ticketKeys: options.ticketKeys,
56
- useCookies: options.useCookies,
57
- srtpProfiles: options.srtpProfiles,
58
- });
114
+ // External PSK as node:tls takes it, as TLSSocket does (pskOptions).
115
+ let session = new DTLSSession(sessionOptions(options, Object.assign({ isServer: !!options.isServer }, pskOptions(options, self))));
59
116
 
60
117
  // Wire session → UDP
118
+ // Datagrams handed to the UDP socket and not yet sent: dgram's send() is
119
+ // asynchronous, and a socket closed with one queued drops it. The last
120
+ // datagram of a failed connection is its alert — closed at once after it,
121
+ // the peer never learned why (the client's illegal_parameter, BoGo
122
+ // FragmentMessage*Mismatch). whenFlushed() waits for them.
123
+ // One callback for every send, not a closure per datagram. A send error
124
+ // comes to it (not as the UDP socket's 'error' event): this connection's,
125
+ // reported on it — on a server's shared socket, not the whole server's.
126
+ let pendingSends = 0, flushWaiters = [];
127
+ function onSent(err) {
128
+ if (err) ev.emit('error', err);
129
+ if (--pendingSends === 0 && flushWaiters.length > 0) {
130
+ let w = flushWaiters; flushWaiters = [];
131
+ for (let i = 0; i < w.length; i++) w[i]();
132
+ }
133
+ }
134
+ function transmit(data) {
135
+ udpSocket.send(data, 0, data.length, remotePort, remoteAddress, onSent);
136
+ pendingSends++; // counted once queued: a send that throws queued nothing
137
+ }
61
138
  session.on('packet', function(data) {
139
+ if (resolving) { resolving.push(data); return; }
62
140
  if (!remoteAddress || !remotePort) return;
63
- udpSocket.send(data, 0, data.length, remotePort, remoteAddress);
141
+ transmit(data);
64
142
  });
143
+ self.whenFlushed = function(fn) { if (pendingSends === 0) fn(); else flushWaiters.push(fn); };
65
144
 
66
145
  session.on('connect', function() {
67
146
  ev.emit('connect');
@@ -91,9 +170,22 @@ function DTLSSocket(udpSocket, options) {
91
170
  ev.emit('clienthello', data, message);
92
171
  });
93
172
 
173
+ // An alert received (the peer's warnings among them), and the Connection
174
+ // IDs agreed or moved: as DTLSSession reports them.
175
+ session.on('alert', function(a) { ev.emit('alert', a); });
176
+ session.on('connectionId', function(ids) { ev.emit('connectionId', ids); });
177
+
94
178
  /**
95
179
  * Feed an incoming UDP datagram (filtered by remote address/port).
96
180
  * Called by the UDP socket's message handler or by DTLSServer.
181
+ *
182
+ * With Connection IDs agreed, a datagram of this connection may come from
183
+ * another address (a NAT that rebound the peer's port; the server finds
184
+ * the connection by its CID). It is taken, and when it holds the newest
185
+ * authenticated record yet the new address is reported:
186
+ * 'remoteAddressChange' { address, port }. It is not taken over by itself
187
+ * — RFC 9146 §6 (and RFC 9147 §9) forbid it without a way of knowing the
188
+ * peer is there; an application that has one calls setRemoteAddress.
97
189
  */
98
190
  function feedDatagram(data, rinfo) {
99
191
  // Update remote address on first datagram (server side)
@@ -102,22 +194,48 @@ function DTLSSocket(udpSocket, options) {
102
194
  remotePort = rinfo.port;
103
195
  }
104
196
 
105
- session.feedDatagram(data);
197
+ let newest = session.feedDatagram(data);
198
+ if (newest && rinfo && (rinfo.address !== remoteAddress || rinfo.port !== remotePort) && session.getConnectionIds()) {
199
+ ev.emit('remoteAddressChange', { address: rinfo.address, port: rinfo.port });
200
+ }
106
201
  }
107
202
 
108
- // If not server, bind to UDP messages directly
109
- if (!options.isServer && udpSocket) {
110
- udpSocket.on('message', function(msg, rinfo) {
111
- if (remoteAddress && (rinfo.address !== remoteAddress || rinfo.port !== remotePort)) return;
112
- feedDatagram(msg, rinfo);
113
- });
203
+ /** Send to another address from now on — the peer's new one, as the
204
+ * application knows it (see feedDatagram). */
205
+ function setRemoteAddress(address, port) {
206
+ if (net.isIP(address) === 0 || !Number.isInteger(port) || port <= 0 || port > 65535) throw new TypeError('setRemoteAddress: an IP address and a port');
207
+ let previous = { address: remoteAddress, port: remotePort };
208
+ remoteAddress = address;
209
+ remotePort = port;
210
+ if (typeof options[kAddressMoved] === 'function') options[kAddressMoved](previous, { address: address, port: port });
114
211
  }
115
212
 
213
+ // A client reads its UDP socket itself (a server's connections are fed by
214
+ // the server). A socket of our own also reports its errors here — it had
215
+ // no 'error' listener, and one (a failed bind) took the process down.
216
+ function onMessage(msg, rinfo) {
217
+ if (resolving) return;
218
+ if (remoteAddress && (rinfo.address !== remoteAddress || rinfo.port !== remotePort)) return;
219
+ feedDatagram(msg, rinfo);
220
+ }
221
+ function onTransportError(err) { ev.emit('error', err); session.close(); }
222
+ function attachTransport(u) {
223
+ u.on('message', onMessage);
224
+ if (ownsTransport) u.on('error', onTransportError);
225
+ }
226
+ function detachTransport(u) {
227
+ u.removeListener('message', onMessage);
228
+ u.removeListener('error', onTransportError);
229
+ }
230
+ if (!options.isServer && udpSocket) attachTransport(udpSocket);
231
+ self[kTransport] = function() { return udpSocket; };
232
+
116
233
  // ---- Public API ----
117
234
 
118
235
  self.send = function(data) { session.send(data); };
119
236
  self.close = function() { session.close(); };
120
237
  self.feedDatagram = feedDatagram;
238
+ self.setRemoteAddress = setRemoteAddress;
121
239
 
122
240
  self.on = function(name, fn) { ev.on(name, fn); };
123
241
  self.off = function(name, fn) { ev.off(name, fn); };
@@ -130,6 +248,11 @@ function DTLSSocket(udpSocket, options) {
130
248
 
131
249
  self.getNegotiationResult = function() { return session.getNegotiationResult(); };
132
250
  self.getALPN = function() { return session.getALPN(); };
251
+ self.getEchStatus = function() { return session.getEchStatus(); };
252
+ self.isEchBackend = function() { return session.isEchBackend(); };
253
+ /** External PSK: the identity (a Buffer, as TLSSocket's), or null. */
254
+ self.getPskIdentity = function() { let id = session.getPskIdentity(); return id ? Buffer.from(id) : null; };
255
+ self.getConnectionIds = function() { return session.getConnectionIds(); };
133
256
  self.getPeerCertificate = function() { return session.getPeerCertificate(); };
134
257
 
135
258
  /** TLS fingerprints, { hash, raw } or null (see TLSSession): JA4 and JA5
@@ -154,6 +277,14 @@ function DTLSSocket(udpSocket, options) {
154
277
  return v == null ? undefined : v;
155
278
  };
156
279
 
280
+ /** The last session ('session' event's Buffer) — pass it as `session` to resume. */
281
+ self.getSession = function() { return session.getSession(); };
282
+ /** The path MTU (largest datagram written); see DTLSSession.setMTU. */
283
+ self.setMTU = function(mtu) { session.setMTU(mtu); };
284
+ self.getMTU = function() { return session.getMTU(); };
285
+ /** Where each flight's retransmission timer starts; see DTLSSession. */
286
+ self.setInitialRetransmitTimeout = function(ms) { session.setInitialRetransmitTimeout(ms); };
287
+
157
288
  /** Whether this connection resumed an earlier session. */
158
289
  self.isSessionReused = function() {
159
290
  return session.isSessionReused ? session.isSessionReused() : false;
@@ -196,77 +327,170 @@ function createDTLSServer(options, connectionListener) {
196
327
  options = {};
197
328
  }
198
329
  options = options || {};
330
+ // Checked here, once, as createServer does: not on each client's hello.
331
+ localLists(options, versionsInRange(options.minVersion, options.maxVersion, true));
332
+ // Connection IDs: one per connection, drawn here — a single one given for
333
+ // the server would be every connection's, and find none of them.
334
+ if (options.connectionId !== undefined && options.connectionId !== null && typeof options.connectionId !== 'boolean') {
335
+ throw new TypeError('createDTLSServer: connectionId is true or false (each connection is given its own)');
336
+ }
337
+ let cidOn = options.connectionId === true;
338
+ let socketBufferSize = checkedSocketBufferSize(options.socketBufferSize);
339
+ let byCid = {}; // CID (hex) → DTLSSocket
199
340
 
200
341
  let ev = new EventEmitter();
201
342
  let udpSocket = null;
202
343
  let connections = {}; // 'addr:port' → DTLSSocket
203
344
 
204
- function listen(port, address, callback) {
205
- if (typeof address === 'function') { callback = address; address = undefined; }
206
-
207
- udpSocket = dgram.createSocket('udp4');
208
-
209
- udpSocket.on('message', function(msg, rinfo) {
210
- let key = rinfo.address + ':' + rinfo.port;
211
-
212
- if (!(key in connections)) {
213
- // New client
214
- let socket = new DTLSSocket(udpSocket, {
215
- isServer: true,
216
- remoteAddress: rinfo.address,
217
- remotePort: rinfo.port,
218
- cert: options.cert,
219
- key: options.key,
220
- requestCert: options.requestCert,
221
- SNICallback: options.SNICallback,
222
- alpnProtocols: options.alpnProtocols,
223
- minVersion: options.minVersion,
224
- maxVersion: options.maxVersion,
225
- mtu: options.mtu,
226
- groups: options.groups,
227
- ticketKeys: options.ticketKeys,
228
- useCookies: options.useCookies,
229
- srtpProfiles: options.srtpProfiles,
230
- });
231
-
232
- connections[key] = socket;
345
+ // One ticket key for the server, not one per connection: a ticket is
346
+ // opened by the connection that comes back, not the one that issued it.
347
+ // Each DTLSSocket drew its own, so no ticket of this server could ever
348
+ // be used (as TLSServer: tls_socket.js keeps one per server too).
349
+ let ticketKeys = options.ticketKeys ? Buffer.from(options.ticketKeys) : crypto.randomBytes(48);
233
350
 
234
- socket.on('close', function() {
235
- delete connections[key];
236
- });
351
+ // A ClientHello: an unprotected (epoch 0) handshake record whose message
352
+ // is type 1 — the first record of the datagram, as a ClientHello comes.
353
+ function isClientHello(msg) {
354
+ return msg.length > 25 && msg[0] === 22 && msg[3] === 0 && msg[4] === 0 && msg[13] === 1;
355
+ }
237
356
 
238
- socket.on('connect', function() {
239
- if (connectionListener) connectionListener(socket);
240
- ev.emit('connection', socket);
241
- });
357
+ function newConnection(key, rinfo) {
358
+ let cid = null;
359
+ if (cidOn) {
360
+ do { cid = crypto.randomBytes(SERVER_CID_LEN); } while (byCid[cid.toString('hex')]);
361
+ }
362
+ let socket = new DTLSSocket(udpSocket, sessionOptions(options, {
363
+ isServer: true,
364
+ remoteAddress: rinfo.address,
365
+ remotePort: rinfo.port,
366
+ ticketKeys: ticketKeys,
367
+ connectionId: cid || false,
368
+ // Moved (setRemoteAddress): found at its new address from now on —
369
+ // unless another connection holds it; its own records, with the CID,
370
+ // find it anyway (onDatagram), and the other is not ended on the
371
+ // application's word.
372
+ [kAddressMoved]: function(from, to) {
373
+ let next = to.address + ':' + to.port;
374
+ if (connections[key] === socket) delete connections[key];
375
+ if (replacing[key] === socket) delete replacing[key];
376
+ key = next;
377
+ if (!connections[key]) connections[key] = socket;
378
+ },
379
+ }));
380
+ if (cid) byCid[cid.toString('hex')] = socket;
381
+
382
+ socket.on('close', function() {
383
+ if (connections[key] === socket) delete connections[key];
384
+ if (replacing[key] === socket) delete replacing[key];
385
+ if (cid && byCid[cid.toString('hex')] === socket) delete byCid[cid.toString('hex')];
386
+ });
242
387
 
243
- socket.on('error', function(err) {
244
- ev.emit('clientError', err, socket);
245
- });
388
+ socket.on('connect', function() {
389
+ // A new association from the address of an existing one (below): it
390
+ // has completed its handshake — the client is there — and takes the
391
+ // old one's place.
392
+ if (replacing[key] === socket) {
393
+ let old = connections[key];
394
+ connections[key] = socket;
395
+ delete replacing[key];
396
+ if (old) old.close();
246
397
  }
247
-
248
- connections[key].feedDatagram(msg, rinfo);
398
+ if (connectionListener) connectionListener(socket);
399
+ ev.emit('connection', socket);
249
400
  });
250
401
 
251
- udpSocket.on('error', function(err) {
252
- ev.emit('error', err);
402
+ socket.on('error', function(err) {
403
+ ev.emit('clientError', err, socket);
253
404
  });
405
+ return socket;
406
+ }
254
407
 
255
- udpSocket.bind(port, address, function() {
256
- if (callback) callback();
257
- ev.emit('listening');
258
- });
408
+ // RFC 6347 §4.2.8 (and RFC 9147 §5.9 the same): a ClientHello from the
409
+ // address of an established association is a new one — the client
410
+ // restarted, or a NAT gave its port to another — and the server "SHOULD
411
+ // proceed with a new handshake but MUST NOT destroy the existing
412
+ // association until the client has demonstrated reachability", by
413
+ // completing the handshake. It went to the old session, which took it for
414
+ // a stray retransmission: the client was never answered while that entry
415
+ // lived — for good, if its close_notify was lost (a dtls_loss_test
416
+ // handshake in ~150 hung so, with no loss at all).
417
+ // So the new handshake runs beside the old association (replacing), and
418
+ // replaces it when it connects. Meanwhile every other datagram goes to
419
+ // both: a protected record opens only under the keys it was made with,
420
+ // and each session drops what it cannot open — and a spoofed ClientHello
421
+ // never touches the old one.
422
+ let replacing = {}; // 'addr:port' → the DTLSSocket of a new handshake there
423
+
424
+ function onDatagram(msg, rinfo) {
425
+ // A record with one of our CIDs is that connection's, whatever address
426
+ // it comes from — the peer moved, maybe onto an address another
427
+ // connection still holds (a NAT that gave the port on).
428
+ if (cidOn) {
429
+ let c = datagramCid(msg, SERVER_CID_LEN);
430
+ let owner = c ? byCid[Buffer.from(c).toString('hex')] : null;
431
+ if (owner) { owner.feedDatagram(msg, rinfo); return; }
432
+ }
433
+ let key = rinfo.address + ':' + rinfo.port;
434
+ let conn = connections[key];
435
+ if (!conn) {
436
+ conn = connections[key] = newConnection(key, rinfo);
437
+ conn.feedDatagram(msg, rinfo);
438
+ return;
439
+ }
440
+ if (isClientHello(msg) && conn.state === 'connected') {
441
+ if (!replacing[key]) replacing[key] = newConnection(key, rinfo);
442
+ replacing[key].feedDatagram(msg, rinfo);
443
+ return;
444
+ }
445
+ conn.feedDatagram(msg, rinfo);
446
+ if (replacing[key]) replacing[key].feedDatagram(msg, rinfo);
447
+ }
448
+
449
+ // listen(port[, address][, callback]). The address's family decides the
450
+ // socket's: an IPv6 one, an IPv6 socket. No address: '::', dual-stack —
451
+ // IPv4 clients too (as '::ffff:a.b.c.d'), as node:net and node:tls listen
452
+ // — or, on a host without IPv6, 0.0.0.0. It was IPv4 only, whatever the
453
+ // address: an IPv6 client could never reach a DTLS server.
454
+ function listen(port, address, callback) {
455
+ if (typeof address === 'function') { callback = address; address = undefined; }
456
+ let open = function(type, bindAddress, fallback) {
457
+ let u = createUdpSocket({ type: type, ipv6Only: false }, socketBufferSize);
458
+ let onBindError = function(err) {
459
+ u.removeListener('error', onBindError);
460
+ try { u.close(); } catch (e) {}
461
+ if (fallback && (err.code === 'EAFNOSUPPORT' || err.code === 'EADDRNOTAVAIL' || err.code === 'EPROTONOSUPPORT' || err.code === 'EINVAL')) { fallback(); return; }
462
+ ev.emit('error', err);
463
+ };
464
+ u.on('error', onBindError);
465
+ u.bind(port, bindAddress, function() {
466
+ u.removeListener('error', onBindError);
467
+ udpSocket = u;
468
+ u.on('message', onDatagram);
469
+ u.on('error', function(err) { ev.emit('error', err); });
470
+ if (callback) callback();
471
+ ev.emit('listening');
472
+ });
473
+ };
474
+ if (address !== undefined && address !== null) open(net.isIP(address) === 6 ? 'udp6' : 'udp4', address, null);
475
+ else open('udp6', '::', function() { open('udp4', undefined, null); });
259
476
  }
260
477
 
478
+ // Every connection's close_notify first, then the socket — once they have
479
+ // gone (whenFlushed): closed at once, it dropped them.
261
480
  function close(callback) {
262
- for (let key in connections) {
263
- connections[key].close();
264
- }
481
+ let open = Object.keys(connections).map(function(k) { return connections[k]; })
482
+ .concat(Object.keys(replacing).map(function(k) { return replacing[k]; }));
265
483
  connections = {};
266
- if (udpSocket) {
267
- udpSocket.close(callback);
268
- udpSocket = null;
269
- }
484
+ replacing = {};
485
+ byCid = {};
486
+ let sock = udpSocket;
487
+ udpSocket = null;
488
+ for (let i = 0; i < open.length; i++) open[i].close();
489
+ if (!sock) return;
490
+ let waiting = open.length + 1;
491
+ let done = function() { if (--waiting === 0) sock.close(callback); };
492
+ for (let i = 0; i < open.length; i++) open[i].whenFlushed(done);
493
+ done();
270
494
  }
271
495
 
272
496
  function address() {
@@ -292,28 +516,26 @@ function connectDTLS(options, callback) {
292
516
  let host = options.host || options.hostname || '127.0.0.1';
293
517
  let port = options.port || 4433;
294
518
 
295
- let udp = dgram.createSocket('udp4');
519
+ // An IPv6 literal needs an IPv6 socket; for a name, the DTLSSocket picks
520
+ // the family it resolves to (kOwnsTransport).
521
+ let udp = createUdpSocket({ type: net.isIP(host) === 6 ? 'udp6' : 'udp4' }, checkedSocketBufferSize(options.socketBufferSize));
296
522
 
297
- let socket = new DTLSSocket(udp, {
523
+ // Every option goes on to the session (sessionOptions), as given: the
524
+ // client's certificate, ca, cipherSuites, ... — this list too had dropped
525
+ // whatever it did not name.
526
+ let socket = new DTLSSocket(udp, sessionOptions(options, {
298
527
  isServer: false,
299
528
  remoteAddress: host,
300
529
  remotePort: port,
301
530
  servername: options.servername || host,
302
- rejectUnauthorized: options.rejectUnauthorized,
303
- checkServerIdentity: options.checkServerIdentity,
304
- requestCert: options.requestCert,
305
- ca: options.ca,
306
- alpnProtocols: options.alpnProtocols,
307
- minVersion: options.minVersion,
308
- maxVersion: options.maxVersion,
309
- mtu: options.mtu,
310
- groups: options.groups,
311
- });
531
+ [kOwnsTransport]: true,
532
+ }));
312
533
 
313
534
  if (callback) socket.on('connect', callback);
314
535
 
536
+ // After what is still queued has gone — the closing alert above all.
315
537
  socket.on('close', function() {
316
- try { udp.close(); } catch(e) {}
538
+ socket.whenFlushed(function() { try { socket[kTransport]().close(); } catch(e) {} });
317
539
  });
318
540
 
319
541
  return socket;