BIO_S_DGRAM_PAIR(3SSL)

✨ NAME ✨

okay bestie, gather round πŸ’– this page is all about the datagram pair BIO, and it's a whole squad of functions:

BIO_s_dgram_pair, BIO_new_bio_dgram_pair, BIO_dgram_set_no_trunc, BIO_dgram_get_no_trunc, BIO_dgram_get_effective_caps, BIO_dgram_get_caps, BIO_dgram_set_caps, BIO_dgram_set_mtu, BIO_dgram_get_mtu, BIO_dgram_set0_local_addr β€” datagram pair BIO.

(it's the BIO_S_DGRAM_PAIR page, from 3SSL, iykyk πŸ¦‹)


🌸 SYNOPSIS 🌸

#include <openssl/bio.h>

const BIO_METHOD *BIO_s_dgram_pair(void);

int BIO_new_bio_dgram_pair(BIO **bio1, size_t writebuf1,
                           BIO **bio2, size_t writebuf2);
int BIO_dgram_set_no_trunc(BIO *bio, int enable);
int BIO_dgram_get_no_trunc(BIO *bio);
uint32_t BIO_dgram_get_effective_caps(BIO *bio);
uint32_t BIO_dgram_get_caps(BIO *bio);
int BIO_dgram_set_caps(BIO *bio, uint32_t caps);
int BIO_dgram_set_mtu(BIO *bio, unsigned int mtu);
unsigned int BIO_dgram_get_mtu(BIO *bio);
int BIO_dgram_set0_local_addr(BIO *bio, BIO_ADDR *addr);

πŸ’… DESCRIPTION πŸ’…

so BIO_s_dgram_pair() hands you the method for a BIO datagram pair. it's giving BIO pair energy (peep BIO_s_bio(3)) but with datagram semantics instead. what does that actually mean? literally this: the length of the buffer you pass into a write call is gonna match what comes back out of a read call. no cap. and if the buffer you give to a read call is too short? then the datagram gets truncated, or the read just fails β€” depends how you configured the BIO. choose your fighter πŸ₯Š

the BIO datagram pair also attaches certain metadata to every write, like source and destination addresses. that info is retrievable on read. iconic.

a typical glow-up application of a BIO datagram pair: letting an application keep all the datagram network I/O that libssl asks for under the application's own control. main character energy fr.

the BIO datagram pair is designed to support multithreaded use, as long as you observe certain restrictions β€” go read THREADING below, bestie, don't skip it πŸ™ˆ

it also lets each half of a pair signal to the other half whether it supports certain capabilities β€” that's the CAPABILITY INDICATION section, keep scrolling πŸ’œ

BIO_new_bio_dgram_pair() is the combo deal: it bundles up the calls to BIO_new(3), BIO_make_bio_pair(3) and BIO_set_write_buf_size(3) to create a connected pair of BIOs bio1, bio2 with write buffer sizes writebuf1 and writebuf2. if either size is zero, the default size is used. slay.

BIO_make_bio_pair(3) can also be used to join two datagram pair BIOs into a pair. but heads up: both BIOs must use the method returned by BIO_s_dgram_pair(), and neither of them may currently already be associated in a pair. no double-dipping 🚫

BIO_destroy_bio_pair(3) destroys the association between two connected BIOs. and freeing either half of the pair will automatically destroy the association. it's a package deal πŸŽ€

BIO_reset(3) clears any data in the write buffer of the given BIO. so the opposite BIO in the pair will no longer have any data waiting to be read. poof, gone.

the BIO keeps a fixed size internal write buffer. when that buffer is full, further writes fail until the buffer is drained via calls to BIO_read(3). you can change the size with BIO_set_write_buf_size(3) and check it with BIO_get_write_buf_size(3).

⚠️ okay important: the write buffer is partially consumed by metadata stored internally and attached to each datagram β€” stuff like source and destination addresses. the size of this overhead is undefined and may change between releases. so don't go assuming numbers, bestie.

the standard BIO_ctrl_pending(3) call has modified behaviour here: it returns the size of the next datagram waiting to be read, in bytes. so you can use it to make sure you're handing an adequate buffer to the next read call. if there's no datagram waiting, you get zero back. πŸ“

this BIO does not support sending or receiving zero-length datagrams. passing a zero-length buffer to BIO_write is treated as a no-op. nothing happens. periodt.

BIO_eof(3) returns 1 only if the given BIO datagram pair BIO is not currently connected to a peer BIO.

BIO_get_write_guarantee(3) and BIO_ctrl_get_write_guarantee(3) return how large a datagram the next call to BIO_write(3) can accept. if there's not enough space in the write buffer to accept another datagram equal in size to the configured MTU, zero is returned (see below πŸ‘€). why does this exist? it's intended to avoid that cursed situation where an application tries to read a datagram from a network intending to write it to a BIO datagram pair, but the received datagram ends up being too large to write. tragedy avoided.

BIO_dgram_set_no_trunc() and BIO_ctrl_get_no_trunc() set and retrieve the truncation mode for the given half of a BIO datagram pair. when no-truncate mode is enabled, BIO_read() will fail if the buffer provided is inadequate to hold the next datagram to be read. when no-truncate mode is disabled (that's the default, btw), the datagram will be silently truncated 😭. this default behaviour keeps compatibility with the semantics of the Berkeley sockets API.

BIO_dgram_set_mtu() and BIO_dgram_get_mtu() can set an informational MTU value on the BIO datagram pair. if you use BIO_dgram_set_mtu() on a BIO that's currently part of a BIO datagram pair, the MTU value gets set on both halves of the pair. the value does not affect how the BIO datagram pair operates (except for BIO_get_write_guarantee(); see above πŸ‘†) but other code may use it to determine a requested MTU. when a BIO datagram pair BIO is created, the MTU is set to an unspecified but valid value.

BIO_dgram_set0_local_addr() sets the local BIO_ADDR to use when sending a datagram via a BIO datagram pair. that address then becomes the peer address when receiving on the other half of the pair. if the BIO is used in a call to BIO_sendmmsg(3) and a local address is explicitly specified there, then that explicitly specified local address takes precedence. also β€” the reference to the BIO_ADDR is passed to the BIO by this call and will be freed automatically when the BIO is freed. it's not yours anymore, bestie, let it go πŸ¦‹

BIO_flush(3) is a no-op. truly nothing. a vibe.


🌈 NOTES 🌈

the halves of a BIO datagram pair have independent lifetimes and must be separately freed. they're not codependent, they're just close πŸ’…


🧚 THREADING 🧚

these ones can be used by multiple threads simultaneously on the same BIO datagram pair:

some specific BIO_ctrl(3) operations can also be used β€” namely BIO_CTRL_PENDING, BIO_CTRL_FLUSH and BIO_C_GET_WRITE_GUARANTEE.

πŸ’€ but here's the cursed part: invoking any other BIO call, or any other BIO_ctrl(3) operation, on either half of a BIO datagram pair while any other BIO call is also in progress to either half of the same BIO datagram pair results in undefined behaviour. not "it might be flaky". undefined. behaviour. don't do it.

πŸ‘‘ CAPABILITY INDICATION πŸ‘‘

the BIO datagram pair can be used to enqueue datagrams that have source and destination addresses attached. and it's super important that the component consuming one side of a BIO datagram pair understands whether the other side of the pair will honour any source and destination addresses it attaches to each datagram. example time: if datagrams are queued with destination addresses set, but they're just read by simple calls to BIO_read(3), those destination addresses get discarded. just yeeted 😭

each half of a BIO datagram pair can have capability flags set on it, which indicate whether source and destination addresses will be honoured by the reader, and whether they'll be provided by the writer. you should set these capability flags via a call to BIO_dgram_set_caps(), and those capabilities will be reflected in the value returned by BIO_dgram_get_effective_caps() on the opposite BIO. if you ever need the previously set value, grab it with BIO_dgram_get_caps(). crucial detail, don't mix these up:

  • BIO_dgram_set_caps() on a given BIO controls the capabilities advertised to the peer.
  • BIO_dgram_get_effective_caps() on a given BIO determines the capabilities advertised by the peer of that BIO.

so there are these capabilities available ✨:

  • BIO_DGRAM_CAP_HANDLES_SRC_ADDR β€” the user of the datagram pair BIO promises to honour source addresses provided with datagrams written to the BIO pair.
  • BIO_DGRAM_CAP_HANDLES_DST_ADDR β€” the user of the datagram pair BIO promises to honour destination addresses provided with datagrams written to the BIO pair.
  • BIO_DGRAM_CAP_PROVIDES_SRC_ADDR β€” the user of the datagram pair BIO advertises the fact that it will provide source addressing information with future writes to the BIO pair, where available.
  • BIO_DGRAM_CAP_PROVIDES_DST_ADDR β€” the user of the datagram pair BIO advertises the fact that it will provide destination addressing information with future writes to the BIO pair, where available.

if a caller tries to specify a destination address (say, using BIO_sendmmsg(3)) and the peer has not advertised the BIO_DGRAM_CAP_HANDLES_DST_ADDR capability, the operation fails. so yeah, capability negotiation is mandatory. no skipping it πŸ“’

if a caller tries to specify a source address when writing, or requests a destination address when receiving, and local address support has not been enabled, the operation fails. see BIO_dgram_set_local_addr_enable(3).

and if a caller tries to enable local address support using BIO_dgram_set_local_addr_enable(3) and BIO_dgram_get_local_addr_cap(3) does not return 1 β€” which would mean the peer has not advertised both the BIO_DGRAM_CAP_HANDLES_SRC_ADDR capability and the BIO_DGRAM_CAP_PROVIDES_DST_ADDR capability β€” then the operation fails.

one more thing: BIO_DGRAM_CAP_PROVIDES_SRC_ADDR and BIO_DGRAM_CAP_PROVIDES_DST_ADDR indicate that the application using that half of a BIO datagram pair promises to provide source and destination addresses respectively when writing datagrams to that half. however, these capability flags do not affect the behaviour of the BIO datagram pair. they're a pinky promise, not a mechanism πŸ’œ


πŸ’– RETURN VALUES πŸ’–

BIO_new_bio_dgram_pair() returns 1 on success, with the new BIOs available in bio1 and bio2, or 0 on failure, with NULL pointers stored into the locations for bio1 and bio2. check the error stack for more info, bestie.

BIO_dgram_set_no_trunc(), BIO_dgram_set_caps() and BIO_dgram_set_mtu() return 1 on success and 0 on failure.

BIO_dgram_get_no_trunc() returns 1 if no-truncate mode is enabled on a BIO, or 0 if no-truncate mode is not enabled or not supported on a given BIO.

BIO_dgram_get_effective_caps() and BIO_dgram_get_caps() return zero if no capabilities are supported.

BIO_dgram_get_mtu() returns the MTU value configured on the BIO, or zero if the operation is not supported.

BIO_dgram_set0_local_addr() returns 1 on success and <= 0 otherwise.


πŸŽ€ SEE ALSO πŸŽ€

BIO_s_bio(3), bio(7)


⭐ HISTORY ⭐

these were added in OpenSSL 3.2:

  • BIO_s_dgram_pair()
  • BIO_new_bio_dgram_pair()
  • BIO_dgram_get_caps()
  • BIO_dgram_get_effective_caps()
  • BIO_dgram_get_no_trunc()
  • BIO_dgram_set_caps()
  • BIO_dgram_set_mtu()
  • BIO_dgram_set_no_trunc()

and BIO_dgram_set0_local_addr() was added in OpenSSL 3.5. late to the party but we love her 🫢

okay bestie, legal era, we gotta do this part, and honestly it's the shortest section so slay ✨

Copyright 2022-2025 The OpenSSL Project Authors. All Rights Reserved. πŸŽ€

So here's the vibe: this file is licensed under the Apache License 2.0 (the "License"). And no cap, you may not use this file except in compliance with the License. Like, that's the whole deal, fr fr. 🫢

If you wanna grab your own copy of the License, it's living right there in the file LICENSE in the source distribution, or you can pull up:

https://www.openssl.org/source/license.html
πŸ’€ not me reading license text for fun, but bestie please actually read it, iykyk. the law does not care about your vibes.

and that's the whole page, babe 🌸 you made it to the end, which is iconic of you honestly πŸ‘‘ this was BIO_S_DGRAM_PAIR(3SSL) from OpenSSL 4.0.3, stamped 2026-09-29 πŸ’… go forth and dgram-pair responsibly, mwah πŸ’œπŸ¦‹

πŸ“¦ where this page comes from