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.mjsWhen 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<Function><Object><number><string>'0.0.0.0'
.<string>
|
<Buffer>
|
<string[]>
|
<Buffer[]><string><string[]>
|
<Buffer><string>'SRTP_AES128_CM_SHA1_80:SRTP_AEAD_AES_128_GCM'
).<boolean>false
.<number>1200
.Creates 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<string><number><Object><string>
|
<Buffer>
|
<string[]>
|
<Buffer[]><boolean>true
.<string>'0.0.0.0'
.<number>0
(ephemeral).<string[]>
|
<Buffer><string><number>1200
.Connects 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.
Shared state object with properties:
<DTLSEndpoint.Stats>The 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<Promise>Gracefully closes the endpoint. All active sessions are closed with
close_notify alerts before the UDP socket is released.
endpoint.destroy(error?): voidImmediately destroys the endpoint without sending close_notify alerts.
endpoint[Symbol.asyncDispose](): voidEquivalent to calling endpoint.close().
A view of the collected statistics for an endpoint.
<bigint><bigint><bigint><bigint><bigint><bigint><bigint><bigint><bigint><boolean>true 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<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<Promise>Initiates a graceful DTLS shutdown by sending a close_notify alert.
session.destroy(error?): voidImmediately destroys the session without sending close_notify.
{ protocol }
when the DTLS handshake completes.<Object>{ address, family, port }<string>'DTLSv1.2'
).<Object>{ name, standardName, version }<string>
|
<undefined><string>
|
<undefined><string>
|
<undefined><DTLSSession.Stats>The 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<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.
<bigint><bigint><bigint>close()
was called. Read only.<bigint><bigint><bigint><bigint><bigint><bigint><boolean>true if the stats object is still connected to the underlying session.
Once the session is destroyed, the stats become a stale snapshot.
<Buffer>Set to receive application data from the peer.
<Error>Set to receive error notifications.
<string>Set to receive handshake completion notifications.
<string>Set to receive TLS key log lines (for debugging with Wireshark).
session[Symbol.asyncDispose](): voidEquivalent 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.