TLS_READ(3)
π TLS_READ(3) β the libtls connection handbook, bestie edition π
okay so this is the libtls era β the library that lives in libtls, libssl and libcrypto and lets you actually use a TLS connection. think of this page as your group chat with the handshake. pull up a chair, grab a boba, and let's get into it π§β¨
β¨ NAME β¨
tls_read, tls_write, tls_handshake, tls_error, tls_close β literally the whole "use a TLS connection" starter pack π
πΈ SYNOPSIS πΈ
first up, the include. you're pulling in libtls, libssl, libcrypto, and yes bestie you need #include <tls.h>:
library "libtls" libssl libcrypto #include <tls.h>
ssize_t
tls_read(struct tls *ctx, void *buf, size_t buflen);
ssize_t
tls_write(struct tls *ctx, const void *buf, size_t buflen);
int
tls_handshake(struct tls *ctx);
const char *
tls_error(struct tls *ctx);
int
tls_close(struct tls *ctx);
no cap, that's the entire cast of characters π keep ctx close, she's the main character in every single one.
π¦ DESCRIPTION π¦
tls_read()slurpsbuflenbytes of data from the socket intobuf, and hands you back how much data it actually read. simple as that πtls_write()pushesbuflenbytes frombufout to the socket, and returns how much got written. iconic, no notes β¨tls_handshake()does the TLS handshake on purpose, explicitly. and honestly? you only need to call this if you genuinely require the handshake to be finished before you move on β becausetls_read()andtls_write()already do the handshake automatically whenever it's needed. yes she does the work for you π«Άtls_error()is your gossip source. call it to grab a string with more deets about the most recent error relating to a context πtls_close()closes the connection when you're done. key detail, don't skip: only the TLS layer gets shut down, so you are responsible for closing the file descriptors β unless the connection was made withtls_connect(3)ortls_connect_servername(3). once it's closed,ctxcan go totls_free(3)π
π RETURN VALUES π
tls_read() and tls_write() return a size on success, or -1 on error. tls_handshake() and tls_close() return 0 on success, or -1 on error. that part's chill π
but here's the part you actually need to internalize β tls_read(), tls_write(), tls_handshake(), and tls_close() all have two special return values:
TLS_WANT_POLLINβ the underlying read file descriptor needs to be readable to keep going πΈTLS_WANT_POLLOUTβ the underlying write file descriptor needs to be writeable to keep going πΈ
so what do you do with that energy?
- blocking file descriptors β just repeat the same function call immediately.
- non-blocking file descriptors β repeat the same function call once the required condition has actually been met.
β οΈ no cap, this one's important: callers of these functions cannot rely on the value of the globalerrno. to stop you from mishandling error conditions,tls_read(),tls_write(),tls_handshake(), andtls_close()all explicitly clearerrno. don't trust it, bestie π
and tls_error()? it returns NULL if no error happened with ctx during or since the last call to tls_handshake(), tls_read(), tls_write(), tls_close(), or tls_reset(3) involving ctx β or if memory allocation failed while it was trying to assemble the string describing the most recent error related to ctx. so NULL is either "we're so fine" or "we couldn't even build the complaint" π
π§ EXAMPLES π§
β¨ TLS writes on a blocking file descriptor β¨
this example is giving "just keep going" energy:
...
while (len > 0) {
ssize_t ret;
ret = tls_write(ctx, buf, len);
if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
continue;
if (ret == -1)
errx(1, "tls_write: %s", tls_error(ctx));
buf += ret;
len -= ret;
}
...
see how TLS_WANT_POLLIN and TLS_WANT_POLLOUT just get continued? blocking mode said "we try again immediately" π
π TLS writes on a non-blocking file descriptor using poll(2) π
and this one is the full glow-up β actually paying attention to what the fd wants:
...
pfd[0].fd = fd;
pfd[0].events = POLLIN|POLLOUT;
while (len > 0) {
nready = poll(pfd, 1, 0);
if (nready == -1)
err(1, "poll");
if ((pfd[0].revents & (POLLERR|POLLNVAL)))
errx(1, "bad fd %d", pfd[0].fd);
if ((pfd[0].revents & (pfd[0].events|POLLHUP))) {
ssize_t ret;
ret = tls_write(ctx, buf, len);
if (ret == TLS_WANT_POLLIN)
pfd[0].events = POLLIN;
else if (ret == TLS_WANT_POLLOUT)
pfd[0].events = POLLOUT;
else if (ret == -1)
errx(1, "tls_write: %s", tls_error(ctx));
else {
buf += ret;
len -= ret;
}
}
}
...
notice how the TLS_WANT_POLLIN / TLS_WANT_POLLOUT results literally rewrite pfd[0].events? that's the whole trick, iykyk π
π₯ SEE ALSO π₯
the rest of the friend group, go say hi:
tls_accept_socket(3)tls_configure(3)tls_conn_version(3)tls_connect(3)tls_init(3)tls_ocsp_process_response(3)
β HISTORY β
tls_read(), tls_write(), tls_error(), and tls_close() appeared in OpenBSD 5.6 and got their final names in OpenBSD 5.7 β glow-up era fr fr π
tls_handshake() appeared in OpenBSD 5.9. late to the party but she showed up gorgeous β¨
π AUTHORS π
Joel Sing <jsing@openbsd.org> with contributions from Bob Beck <beck@openbsd.org> β absolute icons, we love them π«Ά
π CAVEATS π
okay so this is the do not do this section and i need you locked in:
the function tls_error() returns an internal pointer. it must NOT be freed by the application β or you get a double free error π and that pointer will become invalid when the next error occurs with ctx.
consequently, if your application might need that message at a later time, it has to copy the string before calling the next libtls function involving ctx. because if you don't? a segmentation fault, or reading through to unintended data, is the likely result. and we are not doing that to ourselves ππ
that's the whole page bestie β go forth and handle your TLS_WANT_POLLIN properly π
Debian πΈ July 7, 2025 β yeah that's the version and date, dropping it here in the cutest possible sign-off, mwah ππ
π¦ where this page comes from
- alpine 3.24: libressl-doc 4.3.1-r0 Β· custom
- arch rolling: libressl 4.4.0-1 Β· ISC AND custom:OpenSSL
- Copyright (c) 2014, 2015 Ted Unangst <tedu@openbsd.org>
- Copyright (c) 2015 Doug Hogan <doug@openbsd.org>
- Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
- Copyright (c) 2015 Bob Beck <beck@openbsd.org>
- Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms