DTLS
History
The node:dtls module provides an implementation of the Datagram Transport
Layer Security (DTLS) protocol over UDP. DTLS provides TLS-equivalent
security guarantees for datagram-based communication, including
confidentiality, integrity, and authentication.
To use this module, it must be enabled at build time with the
--experimental-dtls configure flag and at runtime with the
--experimental-dtls CLI flag.
node --experimental-dtls app.mjs
import { listen, connect } from 'node:dtls';
const { listen, connect } = require('node:dtls');
When using the Permission Model, the --allow-net flag must be passed to
allow DTLS network operations. Without it, calling dtls.connect() or
dtls.listen() will throw an ERR_ACCESS_DENIED error.
node --permission --allow-fs-read=* --experimental-dtls index.mjs Error: Access to this API has been restricted. Use --allow-net to manage permissions. code: 'ERR_ACCESS_DENIED', permission: 'Net', }
Creating a DTLSEndpoint instance without connecting or listening
is permitted even without --allow-net, since no network I/O occurs until
dtls.connect() or dtls.listen() is called.
DTLS is designed for UDP transport and differs from TLS in several key ways:
- No stream guarantees: Messages may arrive out of order or be lost. DTLS preserves datagram semantics.
- One socket, many peers: A single UDP socket can serve multiple DTLS
sessions. The
DTLSEndpointmanages this multiplexing. - Cookie exchange: DTLS servers use a stateless cookie mechanism (HelloVerifyRequest) to prevent denial-of-service amplification attacks.
- Retransmission: DTLS handles handshake retransmission internally since UDP does not guarantee delivery.
dtls.listen(callback, options): DTLSEndpoint
FunctionDTLSSessionObjectnumberstring'0.0.0.0'.stringstring'SRTP_AES128_CM_SHA1_80:SRTP_AEAD_AES_128_GCM').booleanfalse.number1200.DTLSEndpointCreates a DTLS server bound to the specified address and port. The server uses automatic HMAC-based cookie exchange for DoS protection.
import { listen } from 'node:dtls'; import { readFileSync } from 'node:fs'; const endpoint = listen((session) => { session.onmessage = (data) => { console.log('Received:', data.toString()); session.send('pong'); }; session.onhandshake = (protocol) => { console.log('Handshake complete:', protocol); }; }, { cert: readFileSync('server-cert.pem'), key: readFileSync('server-key.pem'), port: 4433, }); console.log('DTLS server listening on', endpoint.address);
dtls.connect(host, port, options?): DTLSSession
stringnumberObjectbooleantrue, the server's certificate must
both chain to a trusted CA and match the expected identity (servername,
or host when servername is not set); otherwise the handshake is
aborted and session.opened rejects. When false, the certificate is not
verified. Default: true.stringhost argument. Set to '' to disable SNI.
SNI is never sent for IP address literals.string'0.0.0.0'.number0 (ephemeral).stringnumber1200.DTLSSessionConnects to a DTLS server. Returns a DTLSSession whose opened property
is a Promise that resolves when the handshake completes.
import { connect } from 'node:dtls'; import { readFileSync } from 'node:fs'; const session = connect('localhost', 4433, { ca: [readFileSync('ca-cert.pem')], }); await session.opened; session.send('hello'); session.onmessage = (data) => { console.log('Received:', data.toString()); };
Manages a UDP socket and multiplexes DTLS sessions.
Object{ address, family, port }The local address the endpoint is bound to.
DTLSEndpointStateShared state object with properties:
DTLSEndpoint.StatsThe statistics collected for this endpoint. Read only. The stats object is live and updated by the C++ internals as data flows through the endpoint.
When true, the endpoint rejects new incoming connections. Can be set
to implement backpressure.
endpoint.close(): Promise
PromiseGracefully closes the endpoint. All active sessions are closed with
close_notify alerts before the UDP socket is released.
endpoint.destroy(error?): void
Immediately destroys the endpoint without sending close_notify alerts.
endpoint[Symbol.asyncDispose](): void
Equivalent to calling endpoint.close().
A view of the collected statistics for an endpoint.
bigintbigintbigintbigintbigintbigintbigintbigintbigintbooleantrue if the stats object is still connected to the underlying endpoint.
Once the endpoint is destroyed, the stats become a stale snapshot.
Represents a DTLS association with a single remote peer.
session.send(data): number
Send application data to the peer. The data is encrypted by DTLS before
being sent over UDP. Can only be called after the handshake completes
(session.opened has resolved).
session.close(): Promise
PromiseInitiates a graceful DTLS shutdown by sending a close_notify alert.
session.destroy(error?): void
Immediately destroys the session without sending close_notify.
{ protocol } when the DTLS handshake completes.Object{ address, family, port }string'DTLSv1.2').Object{ name, standardName, version }DTLSSession.StatsThe statistics collected for this session. Read only. The stats object is live and updated as data flows through the session.
session.exportKeyingMaterial(length, label, context?): Buffer
Exports keying material from the DTLS session, as defined in RFC 5705. This is commonly used with DTLS-SRTP to derive encryption keys for media streams.
A view of the collected statistics for a session.
bigintbigintbigintclose() was called. Read only.bigintbigintbigintbigintbigintbigintbooleantrue if the stats object is still connected to the underlying session.
Once the session is destroyed, the stats become a stale snapshot.
Set to receive application data from the peer.
Set to receive error notifications.
Set to receive handshake completion notifications.
Set to receive TLS key log lines (for debugging with Wireshark).
session[Symbol.asyncDispose](): void
Equivalent to calling session.close().
DTLS-SRTP is used by WebRTC for media encryption. The DTLS handshake negotiates the SRTP protection profile and provides keying material.
import { listen, connect } from 'node:dtls'; import { readFileSync } from 'node:fs'; // Server with SRTP const server = listen((session) => { session.onhandshake = () => { console.log('SRTP profile:', session.srtpProfile); const keys = session.exportKeyingMaterial( 60, 'EXTRACTOR-dtls_srtp', ); console.log('SRTP keying material:', keys); }; }, { cert: readFileSync('server-cert.pem'), key: readFileSync('server-key.pem'), port: 5004, srtp: 'SRTP_AES128_CM_SHA1_80:SRTP_AEAD_AES_128_GCM', }); // Client with SRTP const session = connect('localhost', 5004, { rejectUnauthorized: false, srtp: 'SRTP_AEAD_AES_128_GCM:SRTP_AES128_CM_SHA1_80', }); await session.opened; console.log('Negotiated SRTP:', session.srtpProfile); const keys = session.exportKeyingMaterial(60, 'EXTRACTOR-dtls_srtp');
Since libuv does not currently support path MTU discovery, the DTLS module uses a conservative default MTU of 1200 bytes. This value works across virtually all network paths but may be suboptimal for local networks.
The MTU can be configured via the mtu option:
// For a local network where you know the path MTU const endpoint = listen(callback, { // ... mtu: 1400, });
The minimum allowed MTU is 256 bytes. The maximum is 65535.