Skip to content

ext/openssl: add a dtls:// stream transport. Open as Draft.#22638

Draft
GianfriAur wants to merge 18 commits into
php:masterfrom
GianfriAur:feature/openssl-dtls-stream
Draft

ext/openssl: add a dtls:// stream transport. Open as Draft.#22638
GianfriAur wants to merge 18 commits into
php:masterfrom
GianfriAur:feature/openssl-dtls-stream

Conversation

@GianfriAur

@GianfriAur GianfriAur commented Jul 8, 2026

Copy link
Copy Markdown

Adds a dtls:// stream transport (with the dtlsv1.2:// alias) to ext/openssl:
DTLS 1.2 over UDP from the stream API, both client and server. It is the datagram
counterpart of tls://, with the same ssl context options where they apply and
the same Openssl\Session object.

// client
$ctx = stream_context_create(['ssl' => ['cafile' => '/path/ca.pem']]);
$c = stream_socket_client('dtls://198.51.100.1:4433', $errno, $errstr, 5,
        STREAM_CLIENT_CONNECT, $ctx);
fwrite($c, $datagram);
$reply = fread($c, 1500);

// server
$ctx = stream_context_create(['ssl' => ['local_cert' => '/path/server.pem']]);
$s = stream_socket_server('dtls://0.0.0.0:4433', $errno, $errstr,
        STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx);
$peer = stream_socket_accept($s, 5);

Design. No core changes: it only registers the schemes in ext/openssl,
without touching xp_ssl.c or the stream layer. It is a separate file
(xp_dtls.c) because DTLS is UDP while xp_ssl.c is TCP, and to avoid risking a
regression on the TLS path; it still follows xp_ssl.c's patterns and style and
reuses its shared primitives (Openssl\Session). The server accept uses
DTLSv1_listen(): a stateless cookie exchange (RFC 6347 §4.2.1) against
amplification and spoofing, with a constant-time compare. It is the stable API
present in every released OpenSSL and stays valid for 1.2 even when OpenSSL caps it
in favour of the new listener API for 1.3. The server is single-peer (one peer per
bind); multi-peer is future work.

Features (client and server): peer verification (verify_peer,
verify_peer_name, peer_name, cafile/capath, peer_fingerprint), mutual
authentication (local_cert with passphrase), session resumption
(session_data on the client; an external cache via session_new_cb/
session_get_cb/session_remove_cb plus session_id_context/no_ticket/
session_timeout on the server), keying material export (RFC 5705) and Path MTU
(dtls_link_mtu). The session, session_reused, protocol and cipher are exposed
in stream_get_meta_data()['crypto'].

Tests. A .phpt suite under ext/openssl/tests/dtls_* (client against
openssl s_server, PHP server and client against each other, server against
openssl s_client): handshake and round-trip, verification by CA/name/fingerprint,
mutual auth, robustness against bogus datagrams, keying material, MTU, and full
client- and server-side resumption.

Separate follow-ups (outside the scheme-only scope, they touch the core or
OpenSSL's listener API): a multi-peer server, factoring the code
shared between xp_ssl.c and xp_dtls.c into an xp_common, and enable_crypto
on udp://. There is also a small TODO: php_openssl_dtls_parse_ip_address()
duplicates parse_ip_address_ex() from xp_socket.c.

Discussed on internals: https://externals.io/message/131514.

TODO before removing Draft status

  • DTLS 1.3 support (planned for this PR, not implemented yet)
  • xp_common with shared methods
  • enable_crypto (override udp://, like tls:// over tcp://)
  • UPGRADING and NEWS
  • php.net documentation
  • Rebase on master, green suite, coding standards

@bukka bukka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm the amount of duplication is quite big. I think this is more a good PoC but it's very far from anything that we could merge. We might need to re-architecture the whole xp_ssl for this. I will need to do more research and thinking to see what would be the best way.

Comment thread ext/openssl/xp_dtls.c Outdated
return -1;
}

dtlssock->s.socket = php_network_connect_socket_to_host(host, (unsigned short)portno,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right approach. We should re-use (and possibly extend if needed) the underlaying udp stream so it works in the same way as tls that is build on top tcp.

Thinking about it, we should really go with enable_crypto support from the beginning as it will mirror better the TLS code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, great. I’ve already made an attempts. I’ll work on it over the next few days.

Comment thread ext/openssl/xp_dtls.c
return -1;
}
for (;;) {
int ret = DTLSv1_listen(ssl, client_addr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not use DTLSv1_listen

@GianfriAur

Copy link
Copy Markdown
Author

Hmm the amount of duplication is quite big. I think this is more a good PoC but it's very far from anything that we could merge. We might need to re-architecture the whole xp_ssl for this. I will need to do more research and thinking to see what would be the best way.

Okay, do you want me to try drafting an xp_common file?

@bukka

bukka commented Jul 13, 2026

Copy link
Copy Markdown
Member

Okay, do you want me to try drafting an xp_common file?

Yeah I think it would be actually better.

@GianfriAur

Copy link
Copy Markdown
Author

Honestly I'm not a big fan of adding is_dgram to php_netstream_data_t, but I couldn't make dtls:// reuse the socket transport the clean way tls:// reuses it for tcp://.

It comes down to an asymmetry in the generic socket transport. tls:// gets the reuse for free because TCP is the default socket type there — the comment in xp_socket.c even spells it out:
/* Note: the test here for php_stream_udp_socket_ops is important, because we want the default to be TCP sockets so that the openssl extension can re-use this code. */

That said, I'm not thrilled with it, if anyone has a cleaner idea, it's very welcome.

@GianfriAur GianfriAur force-pushed the feature/openssl-dtls-stream branch from 058e4af to afa54fe Compare July 13, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants