SSL_GET_CONN_CLOSE_INFO(3SSL)

πŸ’– SSL_GET_CONN_CLOSE_INFO πŸ’– (section 3SSL, the OpenSSL girlies)

okay bestie so this whole page is about a big ol' family of names and macros, and they all live in the OpenSSL 3SSL manual, section 3. here's the full squad, no one left behind:

✨ NAME ✨

SSL_get_conn_close_info, SSL_CONN_CLOSE_FLAG_LOCAL, SSL_CONN_CLOSE_FLAG_TRANSPORT, OSSL_QUIC_ERR_NO_ERROR, OSSL_QUIC_ERR_INTERNAL_ERROR, OSSL_QUIC_ERR_CONNECTION_REFUSED, OSSL_QUIC_ERR_FLOW_CONTROL_ERROR, OSSL_QUIC_ERR_STREAM_LIMIT_ERROR, OSSL_QUIC_ERR_STREAM_STATE_ERROR, OSSL_QUIC_ERR_FINAL_SIZE_ERROR, OSSL_QUIC_ERR_FRAME_ENCODING_ERROR, OSSL_QUIC_ERR_TRANSPORT_PARAMETER_ERROR, OSSL_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR, OSSL_QUIC_ERR_PROTOCOL_VIOLATION, OSSL_QUIC_ERR_INVALID_TOKEN, OSSL_QUIC_ERR_APPLICATION_ERROR, OSSL_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED, OSSL_QUIC_ERR_KEY_UPDATE_ERROR, OSSL_QUIC_ERR_AEAD_LIMIT_REACHED, OSSL_QUIC_ERR_NO_VIABLE_PATH, OSSL_QUIC_ERR_CRYPTO_ERR_BEGIN, OSSL_QUIC_ERR_CRYPTO_ERR_END, OSSL_QUIC_ERR_CRYPTO_ERR, OSSL_QUIC_LOCAL_ERR_IDLE_TIMEOUT

β€” and what do they all do? they get information about why a QUIC connection was closed. periodt. πŸ¦‹


🌸 SYNOPSIS 🌸

okay so first you gotta pull in the header, obviously:

#include <openssl/ssl.h>

#define SSL_CONN_CLOSE_FLAG_LOCAL
#define SSL_CONN_CLOSE_FLAG_TRANSPORT

typedef struct ssl_conn_close_info_st {
    uint64_t error_code, frame_type;
    char     *reason;
    size_t   reason_len;
    uint32_t flags;
} SSL_CONN_CLOSE_INFO;

int SSL_get_conn_close_info(SSL *ssl, SSL_CONN_CLOSE_INFO *info,
                            size_t info_len);

#define OSSL_QUIC_ERR_NO_ERROR                  0x00
#define OSSL_QUIC_ERR_INTERNAL_ERROR            0x01
#define OSSL_QUIC_ERR_CONNECTION_REFUSED        0x02
#define OSSL_QUIC_ERR_FLOW_CONTROL_ERROR        0x03
#define OSSL_QUIC_ERR_STREAM_LIMIT_ERROR        0x04
#define OSSL_QUIC_ERR_STREAM_STATE_ERROR        0x05
#define OSSL_QUIC_ERR_FINAL_SIZE_ERROR          0x06
#define OSSL_QUIC_ERR_FRAME_ENCODING_ERROR      0x07
#define OSSL_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08
#define OSSL_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09
#define OSSL_QUIC_ERR_PROTOCOL_VIOLATION        0x0A
#define OSSL_QUIC_ERR_INVALID_TOKEN             0x0B
#define OSSL_QUIC_ERR_APPLICATION_ERROR         0x0C
#define OSSL_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED    0x0D
#define OSSL_QUIC_ERR_KEY_UPDATE_ERROR          0x0E
#define OSSL_QUIC_ERR_AEAD_LIMIT_REACHED        0x0F
#define OSSL_QUIC_ERR_NO_VIABLE_PATH            0x10

/* Inclusive range for handshake-specific errors. */
#define OSSL_QUIC_ERR_CRYPTO_ERR_BEGIN          0x0100
#define OSSL_QUIC_ERR_CRYPTO_ERR_END            0x01FF

#define OSSL_QUIC_ERR_CRYPTO_ERR(X)

#define OSSL_QUIC_LOCAL_ERR_IDLE_TIMEOUT

look at that block. it's giving organized. πŸ’…


πŸŽ€ DESCRIPTION πŸŽ€

so the star of the show, SSL_get_conn_close_info(), hands you the tea on why and how a QUIC connection got closed. like, it's not just "it's gone bestie", it tells you the whole story.

all that closure info gets written straight into *info, and *info absolutely must be non-NULL (no cap, it will not forgive you). and info_len? you have to set that to sizeof(*info). no improvising.

here's every field it fills in for you:

error_code

this is a 62-bit QUIC error code. it's either a 62-bit application error code (if SSL_CONN_CLOSE_FLAG_TRANSPORT is not set in flags) or a 62-bit standard QUIC transport error code (if SSL_CONN_CLOSE_FLAG_TRANSPORT is set in flags). two moods, one field. ✨

frame_type

if SSL_CONN_CLOSE_FLAG_TRANSPORT is set, this may be set to a QUIC frame type number that caused the connection to be closed. it may also be 0 if no frame type was specified as the cause. if SSL_CONN_CLOSE_FLAG_TRANSPORT is not set, it's just 0. so either way it's a number, the vibe is just different.

reason

if it's non-NULL, this is intended to be a UTF-8 textual string that briefly describes why the connection got closed. the length of the reason string in bytes lives in reason_len.

okay important safety moment bestie 🚨 β€” while OpenSSL guarantees that if this is non-NULL the string will be zero terminated, this buffer may have come from the (untrusted) peer, so it may also contain zero bytes somewhere else in there. so please please please use reason_len. fr fr.

and while the QUIC protocol intends for this to be a UTF-8 string, there is no guarantee that it actually is one when the string came from the peer. so like, don't trust it blindly. πŸ™ˆ

SSL_CONN_CLOSE_FLAG_LOCAL

if flags has SSL_CONN_CLOSE_FLAG_LOCAL set, that means the connection closure was triggered locally. that could be because the application asked for it (like, if SSL_CONN_CLOSE_FLAG_TRANSPORT is unset), or β€” if SSL_CONN_CLOSE_FLAG_TRANSPORT is set β€” because of logic internal to the QUIC implementation (for example, if the peer goes and does a protocol violation, or an idle timeout happens).

if it's unset, the connection closure was triggered remotely. someone else's fault, basically.

SSL_CONN_CLOSE_FLAG_TRANSPORT

if flags has SSL_CONN_CLOSE_FLAG_TRANSPORT set, the closure was triggered for QUIC protocol reasons. otherwise, it was triggered by the local or remote application. two very different origin stories.


also, the OSSL_QUIC_ERR macro definitions give you the QUIC transport error codes exactly as defined by RFC 9000. and the OSSL_QUIC_ERR_CRYPTO_ERR() macro converts a TLS alert code into a QUIC transport error code by mapping it into the range reserved for such codes by RFC 9000. that range starts at OSSL_QUIC_ERR_CRYPTO_ERR_BEGIN and ends at OSSL_QUIC_ERR_CRYPTO_ERR_END, inclusive. iykyk πŸ”₯


🌈 NON-STANDARD TRANSPORT ERROR CODES 🌈

plot twist bestie: some conditions that can cause QUIC connection termination are not signalled on the wire, so they don't get standard error codes. rude honestly. OpenSSL flags these for you via SSL_get_conn_close_info() by setting SSL_CONN_CLOSE_FLAG_TRANSPORT and using one of these error values instead. heads up: these codes are specific to OpenSSL, and they cannot be sent over the wire, because they sit above 2**62.

OSSL_QUIC_LOCAL_ERR_IDLE_TIMEOUT

the connection was terminated immediately due to the idle timeout expiring. it just... ran out of time. πŸ’€


πŸ‘‘ RETURN VALUES πŸ‘‘

SSL_get_conn_close_info() returns 1 on success and 0 on failure. simple. iconic.

it fails if you call it on a QUIC connection SSL object that hasn't been terminated yet. it also fails if you call it on a QUIC stream SSL object, or on a non-QUIC SSL object. so read the room β€” wrong object, no info. 🫢


πŸ¦‹ SEE ALSO πŸ¦‹


⭐ HISTORY ⭐

this function was added in OpenSSL 3.2. new-ish girl on the block, already slaying.


Copyright 2002-2024 The OpenSSL Project Authors. All Rights Reserved.

Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.


and that's the whole page bestie, every last detail, ate and left no crumbs πŸ“πŸ’… β€” this was OpenSSL version 4.0.3, dated 2026-09-29, from the SSL_GET_CONN_CLOSE_INFO(3SSL) page. go close some connections with confidence, mwah 🧚✨

πŸ“¦ where this page comes from