BIO_s_dgram_pair
authorHugo Landau <hlandau@openssl.org>
Tue, 31 May 2022 19:22:40 +0000 (20:22 +0100)
committerPauli <pauli@openssl.org>
Fri, 23 Sep 2022 01:59:13 +0000 (11:59 +1000)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18442)

14 files changed:
crypto/bio/bio_err.c
crypto/bio/bss_dgram_pair.c [new file with mode: 0644]
crypto/bio/build.info
crypto/err/openssl.txt
doc/build.info
doc/man3/BIO_s_dgram_pair.pod [new file with mode: 0644]
doc/man3/BIO_sendmmsg.pod
include/openssl/bio.h.in
include/openssl/bioerr.h
test/bio_dgram_test.c
test/build.info
test/threadstest.c
util/libcrypto.num
util/other.syms

index 7f8ae17245f4543346a5a0c0b3fa01d14cf5d6b7..6fe295ee52f2d073e365c26a852f3d609055af28 100644 (file)
@@ -78,6 +78,8 @@ static const ERR_STRING_DATA BIO_str_reasons[] = {
     {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_WSASTARTUP), "WSAStartup"},
     {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_LOCAL_ADDR_NOT_AVAILABLE),
      "local address not available"},
+    {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_PEER_ADDR_NOT_AVAILABLE),
+     "peer address not available"},
     {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_NON_FATAL),
      "non-fatal or transient error"},
     {ERR_PACK(ERR_LIB_BIO, 0, BIO_R_PORT_MISMATCH),
diff --git a/crypto/bio/bss_dgram_pair.c b/crypto/bio/bss_dgram_pair.c
new file mode 100644 (file)
index 0000000..f2d6ecb
--- /dev/null
@@ -0,0 +1,1136 @@
+/*
+ * Copyright 2022 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
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include "bio_local.h"
+#include "internal/cryptlib.h"
+
+#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
+
+/* ===========================================================================
+ * Byte-wise ring buffer which supports pushing and popping blocks of multiple
+ * bytes at a time.
+ */
+struct ring_buf {
+    unsigned char *start; /* start of buffer, never changes */
+    size_t len; /* size of buffer allocation in bytes */
+    size_t count; /* number of bytes currently pushed */
+    /*
+     * These index into start. Where idx[0] == idx[1], the buffer is full
+     * (if count is nonzero) and empty otherwise.
+     */
+    size_t idx[2]; /* 0: head, 1: tail */
+};
+
+static int ring_buf_init(struct ring_buf *r, size_t nbytes)
+{
+    r->start = OPENSSL_malloc(nbytes);
+    if (r->start == NULL)
+        return 0;
+
+    r->len = nbytes;
+    r->idx[0] = r->idx[1] = r->count = 0;
+    return 1;
+}
+
+static void ring_buf_destroy(struct ring_buf *r)
+{
+    OPENSSL_free(r->start);
+    r->start    = NULL;
+    r->len      = 0;
+    r->count    = 0;
+}
+
+/*
+ * Get a pointer to the next place to write data to be pushed to the ring buffer
+ * (idx=0), or the next data to be popped from the ring buffer (idx=1). The
+ * pointer is written to *buf and the maximum number of bytes which can be
+ * read/written are written to *len. After writing data to the buffer, call
+ * ring_buf_push/pop() with the number of bytes actually read/written, which
+ * must not exceed the returned length.
+ */
+static void ring_buf_head_tail(struct ring_buf *r, int idx, uint8_t **buf, size_t *len)
+{
+    size_t max_len = r->len - r->idx[idx];
+
+    if (idx == 0 && max_len > r->len - r->count)
+        max_len = r->len - r->count;
+    if (idx == 1 && max_len > r->count)
+        max_len = r->count;
+
+    *buf = (uint8_t *)r->start + r->idx[idx];
+    *len = max_len;
+}
+
+#define ring_buf_head(r, buf, len) ring_buf_head_tail((r), 0, (buf), (len))
+#define ring_buf_tail(r, buf, len) ring_buf_head_tail((r), 1, (buf), (len))
+
+/*
+ * Commit bytes to the ring buffer previously filled after a call to
+ * ring_buf_head().
+ */
+static void ring_buf_push_pop(struct ring_buf *r, int idx, size_t num_bytes)
+{
+    size_t new_idx;
+
+    /* A single push/pop op cannot wrap around, though it can reach the end.
+     * If the caller adheres to the convention of using the length returned
+     * by ring_buf_head/tail(), this cannot happen.
+     */
+    if (!ossl_assert(num_bytes <= r->len - r->idx[idx]))
+        return;
+
+    /*
+     * Must not overfill the buffer, or pop more than is in the buffer either.
+     */
+    if (!ossl_assert(idx != 0 ? num_bytes <= r->count
+                              : num_bytes + r->count <= r->len))
+        return;
+
+    /* Update the index. */
+    new_idx = r->idx[idx] + num_bytes;
+    if (new_idx == r->len)
+        new_idx = 0;
+
+    r->idx[idx] = new_idx;
+    if (idx != 0)
+        r->count -= num_bytes;
+    else
+        r->count += num_bytes;
+}
+
+#define ring_buf_push(r, num_bytes) ring_buf_push_pop((r), 0, (num_bytes))
+#define ring_buf_pop(r, num_bytes) ring_buf_push_pop((r), 1, (num_bytes))
+
+static void ring_buf_clear(struct ring_buf *r)
+{
+    r->idx[0] = r->idx[1] = r->count = 0;
+}
+
+/* ===========================================================================
+ * BIO_s_dgram_pair is documented in BIO_s_dgram_pair(3).
+ *
+ * INTERNAL DATA STRUCTURE
+ *
+ * This is managed internally by using a bytewise ring buffer which supports
+ * pushing and popping spans of multiple bytes at once. The ring buffer stores
+ * internal packets which look like this:
+ *
+ *   struct dgram_hdr hdr;
+ *   uint8_t data[];
+ *
+ * The header contains the length of the data and metadata such as
+ * source/destination addresses.
+ *
+ * The datagram pair BIO is designed to support both traditional
+ * BIO_read/BIO_write (likely to be used by applications) as well as
+ * BIO_recvmmsg/BIO_sendmmsg.
+ */
+struct bio_dgram_pair_st;
+static int dgram_pair_write(BIO *bio, const char *buf, int sz_);
+static int dgram_pair_read(BIO *bio, char *buf, int sz_);
+static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr);
+static int dgram_pair_init(BIO *bio);
+static int dgram_pair_free(BIO *bio);
+static int dgram_pair_sendmmsg(BIO *b, BIO_MSG *msg, size_t stride,
+                               size_t num_msg, uint64_t flags,
+                               size_t *num_processed);
+static int dgram_pair_recvmmsg(BIO *b, BIO_MSG *msg, size_t stride,
+                               size_t num_msg, uint64_t flags,
+                               size_t *num_processed);
+
+static int dgram_pair_ctrl_destroy_bio_pair(BIO *bio1);
+static size_t dgram_pair_read_inner(struct bio_dgram_pair_st *b, uint8_t *buf,
+                                    size_t sz);
+
+#define BIO_MSG_N(array, n) (*(BIO_MSG *)((char *)(array) + (n)*stride))
+
+static const BIO_METHOD dgram_pair_method = {
+    BIO_TYPE_DGRAM_PAIR,
+    "BIO dgram pair",
+    bwrite_conv,
+    dgram_pair_write,
+    bread_conv,
+    dgram_pair_read,
+    NULL, /* dgram_pair_puts */
+    NULL, /* dgram_pair_gets */
+    dgram_pair_ctrl,
+    dgram_pair_init,
+    dgram_pair_free,
+    NULL, /* dgram_pair_callback_ctrl */
+    dgram_pair_sendmmsg,
+    dgram_pair_recvmmsg,
+};
+
+const BIO_METHOD *BIO_s_dgram_pair(void)
+{
+    return &dgram_pair_method;
+}
+
+struct dgram_hdr {
+    size_t len; /* payload length in bytes, not including this struct */
+    BIO_ADDR src_addr, dst_addr; /* family == 0: not present */
+};
+
+struct bio_dgram_pair_st {
+    /* The other half of the BIO pair. */
+    BIO *peer;
+    /* Writes are directed to our own ringbuf and reads to our peer. */
+    struct ring_buf rbuf;
+    /* Requested size of rbuf buffer in bytes once we initialize. */
+    size_t req_buf_len;
+    /* Largest possible datagram size */
+    size_t mtu;
+    /* Capability flags. */
+    uint32_t cap;
+    /*
+     * This lock protects updates to our rbuf. Since writes are directed to our
+     * own rbuf, this means we use this lock for writes and our peer's lock for
+     * reads.
+     */
+    CRYPTO_RWLOCK *lock;
+    unsigned int no_trunc          : 1; /* Reads fail if they would truncate */
+    unsigned int local_addr_enable : 1; /* Can use BIO_MSG->local? */
+    unsigned int role              : 1; /* Determines lock order */
+};
+
+#define MIN_BUF_LEN (1024)
+
+static int dgram_pair_init(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = OPENSSL_zalloc(sizeof(*b));
+
+    if (b == NULL)
+        return 0;
+
+    b->req_buf_len = 17*1024; /* default buffer size */
+    b->mtu         = 1472;    /* conservative default MTU */
+
+    b->lock = CRYPTO_THREAD_lock_new();
+    if (b->lock == NULL) {
+        OPENSSL_free(b);
+        return 0;
+    }
+
+    bio->ptr = b;
+    return 1;
+}
+
+static int dgram_pair_free(BIO *bio)
+{
+    struct bio_dgram_pair_st *b;
+
+    if (bio == NULL)
+        return 0;
+
+    b = bio->ptr;
+    if (!ossl_assert(b != NULL))
+        return 0;
+
+    /* We are being freed. Disconnect any peer and destroy buffers. */
+    dgram_pair_ctrl_destroy_bio_pair(bio);
+
+    CRYPTO_THREAD_lock_free(b->lock);
+    OPENSSL_free(b);
+    return 1;
+}
+
+/* BIO_make_bio_pair (BIO_C_MAKE_BIO_PAIR) */
+static int dgram_pair_ctrl_make_bio_pair(BIO *bio1, BIO *bio2)
+{
+    struct bio_dgram_pair_st *b1, *b2;
+
+    /* peer must be non-NULL. */
+    if (bio1 == NULL || bio2 == NULL) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+        return 0;
+    }
+
+    /* Ensure the BIO we have been passed is actually a dgram pair BIO. */
+    if (bio1->method != &dgram_pair_method || bio2->method != &dgram_pair_method) {
+        ERR_raise_data(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT,
+                       "both BIOs must be BIO_dgram_pair");
+        return 0;
+    }
+
+    b1 = bio1->ptr;
+    b2 = bio2->ptr;
+
+    if (!ossl_assert(b1 != NULL && b2 != NULL)) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_UNINITIALIZED);
+        return 0;
+    }
+
+    /*
+     * This ctrl cannot be used to associate a BIO pair half which is already
+     * associated.
+     */
+    if (b1->peer != NULL || b2->peer != NULL) {
+        ERR_raise_data(ERR_LIB_BIO, BIO_R_IN_USE,
+                       "cannot associate a BIO_dgram_pair which is already in use");
+        return 0;
+    }
+
+    if (!ossl_assert(b1->req_buf_len >= MIN_BUF_LEN
+                        && b2->req_buf_len >= MIN_BUF_LEN)) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_UNINITIALIZED);
+        return 0;
+    }
+
+    if (b1->rbuf.len != b1->req_buf_len)
+        if (ring_buf_init(&b1->rbuf, b1->req_buf_len) == 0) {
+            ERR_raise(ERR_LIB_BIO, ERR_R_MALLOC_FAILURE);
+            return 0;
+        }
+
+    if (b2->rbuf.len != b2->req_buf_len)
+        if (ring_buf_init(&b2->rbuf, b2->req_buf_len) == 0) {
+            ERR_raise(ERR_LIB_BIO, ERR_R_MALLOC_FAILURE);
+            ring_buf_destroy(&b1->rbuf);
+            return 0;
+        }
+
+    b1->peer    = bio2;
+    b2->peer    = bio1;
+    b1->role    = 0;
+    b2->role    = 1;
+    bio1->init  = 1;
+    bio2->init  = 1;
+    return 1;
+}
+
+/* BIO_destroy_bio_pair (BIO_C_DESTROY_BIO_PAIR) */
+static int dgram_pair_ctrl_destroy_bio_pair(BIO *bio1)
+{
+    BIO *bio2;
+    struct bio_dgram_pair_st *b1 = bio1->ptr, *b2;
+
+    /* If we already don't have a peer, treat this as a no-op. */
+    if (b1->peer == NULL)
+        return 1;
+
+    bio2 = b1->peer;
+    b2 = bio2->ptr;
+
+    /* Invariants. */
+    if (!ossl_assert(b1->peer == bio2 && b2->peer == bio1))
+        return 0;
+
+    /* Free buffers. */
+    ring_buf_destroy(&b1->rbuf);
+    ring_buf_destroy(&b2->rbuf);
+
+    bio1->init = 0;
+    bio2->init = 0;
+    b1->peer = NULL;
+    b2->peer = NULL;
+    return 1;
+}
+
+/* BIO_eof (BIO_CTRL_EOF) */
+static int dgram_pair_ctrl_eof(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+
+    if (!ossl_assert(b != NULL))
+        return -1;
+
+    /* If we have no peer, we can never read anything */
+    if (b->peer == NULL)
+        return 1;
+
+    peerb = b->peer->ptr;
+    if (!ossl_assert(peerb != NULL))
+        return -1;
+
+    /*
+     * Since we are emulating datagram semantics, never indicate EOF so long as
+     * we have a peer.
+     */
+    return 0;
+}
+
+/* BIO_set_write_buf_size (BIO_C_SET_WRITE_BUF_SIZE) */
+static int dgram_pair_ctrl_set_write_buf_size(BIO *bio, size_t len)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    /* Changing buffer sizes is not permitted while a peer is connected. */
+    if (b->peer != NULL) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_IN_USE);
+        return 0;
+    }
+
+    /* Enforce minimum size. */
+    if (len < MIN_BUF_LEN)
+        len = MIN_BUF_LEN;
+
+    /*
+     * We have no peer yet, therefore the ring buffer should not have been
+     * allocated yet.
+     */
+    if (!ossl_assert(b->rbuf.start == NULL))
+        return 0;
+
+    b->req_buf_len = len;
+    return 1;
+}
+
+/* BIO_reset (BIO_CTRL_RESET) */
+static int dgram_pair_ctrl_reset(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    ring_buf_clear(&b->rbuf);
+    return 1;
+}
+
+/* BIO_pending (BIO_CTRL_PENDING) (Threadsafe) */
+static size_t dgram_pair_ctrl_pending(BIO *bio)
+{
+    size_t saved_idx, saved_count;
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+    struct dgram_hdr hdr;
+    size_t l;
+
+    /* Safe to check; peer may not change during this call */
+    if (b->peer == NULL)
+        return 0;
+
+    peerb = b->peer->ptr;
+
+    if (CRYPTO_THREAD_write_lock(peerb->lock) == 0)
+        return 0;
+
+    saved_idx   = peerb->rbuf.idx[1];
+    saved_count = peerb->rbuf.count;
+
+    l = dgram_pair_read_inner(peerb, (uint8_t *)&hdr, sizeof(hdr));
+
+    peerb->rbuf.idx[1] = saved_idx;
+    peerb->rbuf.count  = saved_count;
+
+    CRYPTO_THREAD_unlock(peerb->lock);
+
+    if (!ossl_assert(l == 0 || l == sizeof(hdr)))
+        return 0;
+
+    return l > 0 ? hdr.len : 0;
+}
+
+/* BIO_get_write_guarantee (BIO_C_GET_WRITE_GUARANTEE) (Threadsafe) */
+static size_t dgram_pair_ctrl_get_write_guarantee(BIO *bio)
+{
+    size_t l;
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    if (CRYPTO_THREAD_read_lock(b->lock) == 0)
+        return 0;
+
+    l = b->rbuf.len - b->rbuf.count;
+    if (l >= sizeof(struct dgram_hdr))
+        l -= sizeof(struct dgram_hdr);
+
+    /*
+     * If the amount of buffer space would not be enough to accommodate the
+     * worst-case size of a datagram, report no space available.
+     */
+    if (l < b->mtu)
+        l = 0;
+
+    CRYPTO_THREAD_unlock(b->lock);
+    return l;
+}
+
+/* BIO_dgram_get_local_addr_cap (BIO_CTRL_DGRAM_GET_LOCAL_ADDR_CAP) */
+static int dgram_pair_ctrl_get_local_addr_cap(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+
+    if (b->peer == NULL)
+        return 0;
+
+    peerb = b->peer->ptr;
+
+    return (~peerb->cap & (BIO_DGRAM_CAP_HANDLES_SRC_ADDR
+                          | BIO_DGRAM_CAP_PROVIDES_DST_ADDR)) == 0;
+}
+
+/* BIO_dgram_get_effective_caps (BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS) */
+static int dgram_pair_ctrl_get_effective_caps(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+
+    if (b->peer == NULL)
+        return 0;
+
+    peerb = b->peer->ptr;
+
+    return peerb->cap;
+}
+
+/* BIO_dgram_get_caps (BIO_CTRL_DGRAM_GET_CAPS) */
+static uint32_t dgram_pair_ctrl_get_caps(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    return b->cap;
+}
+
+/* BIO_dgram_set_caps (BIO_CTRL_DGRAM_SET_CAPS) */
+static int dgram_pair_ctrl_set_caps(BIO *bio, uint32_t caps)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    b->cap = caps;
+    return 1;
+}
+
+/* BIO_dgram_get_local_addr_enable (BIO_CTRL_DGRAM_GET_LOCAL_ADDR_ENABLE) */
+static int dgram_pair_ctrl_get_local_addr_enable(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    return b->local_addr_enable;
+}
+
+/* BIO_dgram_set_local_addr_enable (BIO_CTRL_DGRAM_SET_LOCAL_ADDR_ENABLE) */
+static int dgram_pair_ctrl_set_local_addr_enable(BIO *bio, int enable)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    if (dgram_pair_ctrl_get_local_addr_cap(bio) == 0)
+        return 0;
+
+    b->local_addr_enable = (enable != 0 ? 1 : 0);
+    return 1;
+}
+
+/* BIO_dgram_get_mtu (BIO_CTRL_DGRAM_GET_MTU) */
+static int dgram_pair_ctrl_get_mtu(BIO *bio)
+{
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    return b->mtu;
+}
+
+/* BIO_dgram_set_mtu (BIO_CTRL_DGRAM_SET_MTU) */
+static int dgram_pair_ctrl_set_mtu(BIO *bio, size_t mtu)
+{
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+
+    b->mtu = mtu;
+
+    if (b->peer != NULL) {
+        peerb = b->peer->ptr;
+        peerb->mtu = mtu;
+    }
+
+    return 1;
+}
+
+/* Partially threadsafe (some commands) */
+static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+    long ret = 1;
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    if (!ossl_assert(b != NULL))
+        return 0;
+
+    switch (cmd) {
+    /*
+     * BIO_set_write_buf_size: Set the size of the ring buffer used for storing
+     * datagrams. No more writes can be performed once the buffer is filled up,
+     * until reads are performed. This cannot be used after a peer is connected.
+     */
+    case BIO_C_SET_WRITE_BUF_SIZE: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_set_write_buf_size(bio, (size_t)num);
+        break;
+
+    /*
+     * BIO_get_write_buf_size: Get ring buffer size.
+     */
+    case BIO_C_GET_WRITE_BUF_SIZE: /* Non-threadsafe */
+        ret = (long)b->req_buf_len;
+        break;
+
+    /*
+     * BIO_make_bio_pair: this is usually used by BIO_new_dgram_pair, though it
+     * may be used manually after manually creating each half of a BIO pair
+     * using BIO_new. This only needs to be called on one of the BIOs.
+     */
+    case BIO_C_MAKE_BIO_PAIR: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_make_bio_pair(bio, (BIO *)ptr);
+        break;
+
+    /*
+     * BIO_destroy_bio_pair: Manually disconnect two halves of a BIO pair so
+     * that they are no longer peers.
+     */
+    case BIO_C_DESTROY_BIO_PAIR: /* Non-threadsafe */
+        dgram_pair_ctrl_destroy_bio_pair(bio);
+        break;
+
+    /*
+     * BIO_reset: Clear all data which was written to this side of the pair.
+     */
+    case BIO_CTRL_RESET: /* Non-threadsafe */
+        dgram_pair_ctrl_reset(bio);
+        break;
+
+    /*
+     * BIO_get_write_guarantee: Any BIO_write providing a buffer less than or
+     * equal to this value is guaranteed to succeed.
+     */
+    case BIO_C_GET_WRITE_GUARANTEE: /* Threadsafe */
+        ret = (long)dgram_pair_ctrl_get_write_guarantee(bio);
+        break;
+
+    /* BIO_pending: Bytes available to read. */
+    case BIO_CTRL_PENDING: /* Threadsafe */
+        ret = (long)dgram_pair_ctrl_pending(bio);
+        break;
+
+    /* BIO_flush: No-op. */
+    case BIO_CTRL_FLUSH: /* Threadsafe */
+        break;
+
+    /* BIO_dgram_get_no_trunc */
+    case BIO_CTRL_DGRAM_GET_NO_TRUNC: /* Non-threadsafe */
+        ret = (long)b->no_trunc;
+        break;
+
+    /* BIO_dgram_set_no_trunc */
+    case BIO_CTRL_DGRAM_SET_NO_TRUNC: /* Non-threadsafe */
+        b->no_trunc = (num > 0);
+        break;
+
+    /* BIO_dgram_get_local_addr_enable */
+    case BIO_CTRL_DGRAM_GET_LOCAL_ADDR_ENABLE: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_get_local_addr_enable(bio);
+        break;
+
+    /* BIO_dgram_set_local_addr_enable */
+    case BIO_CTRL_DGRAM_SET_LOCAL_ADDR_ENABLE: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_set_local_addr_enable(bio, num);
+        break;
+
+    /* BIO_dgram_get_local_addr_cap: Can local addresses be supported? */
+    case BIO_CTRL_DGRAM_GET_LOCAL_ADDR_CAP: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_get_local_addr_cap(bio);
+        break;
+
+    /* BIO_dgram_get_effective_caps */
+    case BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_get_effective_caps(bio);
+        break;
+
+    /* BIO_dgram_get_caps */
+    case BIO_CTRL_DGRAM_GET_CAPS: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_get_caps(bio);
+        break;
+
+    /* BIO_dgram_set_caps */
+    case BIO_CTRL_DGRAM_SET_CAPS: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_set_caps(bio, (uint32_t)num);
+        break;
+
+    /* BIO_dgram_get_mtu */
+    case BIO_CTRL_DGRAM_GET_MTU: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_get_mtu(bio);
+        break;
+
+    /* BIO_dgram_set_mtu */
+    case BIO_CTRL_DGRAM_SET_MTU: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_set_mtu(bio, (uint32_t)num);
+        break;
+
+    /*
+     * BIO_eof: Returns whether this half of the BIO pair is empty of data to
+     * read.
+     */
+    case BIO_CTRL_EOF: /* Non-threadsafe */
+        ret = (long)dgram_pair_ctrl_eof(bio);
+        break;
+
+    default:
+        ret = 0;
+        break;
+    }
+
+    return ret;
+}
+
+int BIO_new_bio_dgram_pair(BIO **pbio1, size_t writebuf1,
+                           BIO **pbio2, size_t writebuf2)
+{
+    int ret = 0;
+    long r;
+    BIO *bio1 = NULL, *bio2 = NULL;
+
+    bio1 = BIO_new(BIO_s_dgram_pair());
+    if (bio1 == NULL)
+        goto err;
+
+    bio2 = BIO_new(BIO_s_dgram_pair());
+    if (bio2 == NULL)
+        goto err;
+
+    if (writebuf1 > 0) {
+        r = BIO_set_write_buf_size(bio1, writebuf1);
+        if (r == 0)
+            goto err;
+    }
+
+    if (writebuf2 > 0) {
+        r = BIO_set_write_buf_size(bio2, writebuf2);
+        if (r == 0)
+            goto err;
+    }
+
+    r = BIO_make_bio_pair(bio1, bio2);
+    if (r == 0)
+        goto err;
+
+    ret = 1;
+err:
+    if (ret == 0) {
+        BIO_free(bio1);
+        bio1 = NULL;
+        BIO_free(bio2);
+        bio2 = NULL;
+    }
+
+    *pbio1 = bio1;
+    *pbio2 = bio2;
+    return ret;
+}
+
+/* Must hold peer write lock */
+static size_t dgram_pair_read_inner(struct bio_dgram_pair_st *b, uint8_t *buf, size_t sz)
+{
+    size_t total_read = 0;
+
+    /*
+     * We repeat pops from the ring buffer for as long as we have more
+     * application *buffer to fill until we fail. We may not be able to pop
+     * enough data to fill the buffer in one operation if the ring buffer wraps
+     * around, but there may still be more data available.
+     */
+    while (sz > 0) {
+        uint8_t *src_buf = NULL;
+        size_t src_len = 0;
+
+        /*
+         * There are two BIO instances, each with a ringbuf. We read from the
+         * peer ringbuf and write to our own ringbuf.
+         */
+        ring_buf_tail(&b->rbuf, &src_buf, &src_len);
+        if (src_len == 0)
+            break;
+
+        if (src_len > sz)
+            src_len = sz;
+
+        if (buf != NULL)
+            memcpy(buf, src_buf, src_len);
+
+        ring_buf_pop(&b->rbuf, src_len);
+
+        buf         += src_len;
+        total_read  += src_len;
+        sz          -= src_len;
+    }
+
+    return total_read;
+}
+
+/*
+ * Must hold peer write lock. Returns number of bytes processed or negated BIO
+ * response code.
+ */
+static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
+                                           BIO_ADDR *local, BIO_ADDR *peer,
+                                           int is_multi)
+{
+    size_t l, trunc = 0, saved_idx, saved_count;
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+    struct dgram_hdr hdr;
+
+    if (!is_multi)
+        BIO_clear_retry_flags(bio);
+
+    if (!bio->init)
+        return -BIO_R_UNINITIALIZED;
+
+    if (!ossl_assert(b != NULL && b->peer != NULL))
+        return -BIO_R_TRANSFER_ERROR;
+
+    peerb = b->peer->ptr;
+    if (!ossl_assert(peerb != NULL && peerb->rbuf.start != NULL))
+        return -BIO_R_TRANSFER_ERROR;
+
+    if (sz > 0 && buf == NULL)
+        return -BIO_R_INVALID_ARGUMENT;
+
+    /* If the caller wants to know the local address, it must be enabled */
+    if (local != NULL && b->local_addr_enable == 0)
+        return -BIO_R_LOCAL_ADDR_NOT_AVAILABLE;
+
+    /* Read the header. */
+    saved_idx   = peerb->rbuf.idx[1];
+    saved_count = peerb->rbuf.count;
+    l = dgram_pair_read_inner(peerb, (uint8_t *)&hdr, sizeof(hdr));
+    if (l == 0) {
+        /* Buffer was empty. */
+        if (!is_multi)
+            BIO_set_retry_read(bio);
+        return -BIO_R_NON_FATAL;
+    }
+
+    if (!ossl_assert(l == sizeof(hdr)))
+        /*
+         * This should not be possible as headers (and their following payloads)
+         * should always be written atomically.
+         */
+        return -BIO_R_BROKEN_PIPE;
+
+    if (sz > hdr.len) {
+        sz = hdr.len;
+    } else if (sz < hdr.len) {
+        /* Truncation is occurring. */
+        trunc = hdr.len - sz;
+        if (b->no_trunc) {
+            /* Restore original state. */
+            peerb->rbuf.idx[1] = saved_idx;
+            peerb->rbuf.count  = saved_count;
+            return -BIO_R_NON_FATAL;
+        }
+    }
+
+    l = dgram_pair_read_inner(peerb, (uint8_t *)buf, sz);
+    if (!ossl_assert(l == sz))
+        /* We were somehow not able to read the entire datagram. */
+        return -BIO_R_TRANSFER_ERROR;
+
+    /*
+     * If the datagram was truncated due to an inadequate buffer, discard the
+     * remainder.
+     */
+    if (trunc > 0 && !ossl_assert(dgram_pair_read_inner(peerb, NULL, trunc) == trunc))
+        /* We were somehow not able to read/skip the entire datagram. */
+        return -BIO_R_TRANSFER_ERROR;
+
+    if (local != NULL)
+        *local = hdr.dst_addr;
+    if (peer != NULL)
+        *peer  = hdr.src_addr;
+
+    return (ossl_ssize_t)l;
+}
+
+/* Threadsafe */
+static int dgram_pair_lock_both_write(struct bio_dgram_pair_st *a,
+                                      struct bio_dgram_pair_st *b)
+{
+    struct bio_dgram_pair_st *x, *y;
+
+    x = (a->role == 1) ? a : b;
+    y = (a->role == 1) ? b : a;
+
+    if (!ossl_assert(a->role != b->role))
+        return 0;
+
+    if (!ossl_assert(a != b && x != y))
+        return 0;
+
+    if (CRYPTO_THREAD_write_lock(x->lock) == 0)
+        return 0;
+
+    if (CRYPTO_THREAD_write_lock(y->lock) == 0) {
+        CRYPTO_THREAD_unlock(x->lock);
+        return 0;
+    }
+
+    return 1;
+}
+
+static void dgram_pair_unlock_both(struct bio_dgram_pair_st *a,
+                                   struct bio_dgram_pair_st *b)
+{
+    CRYPTO_THREAD_unlock(a->lock);
+    CRYPTO_THREAD_unlock(b->lock);
+}
+
+/* Threadsafe */
+static int dgram_pair_read(BIO *bio, char *buf, int sz_)
+{
+    int ret;
+    ossl_ssize_t l;
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+
+    if (sz_ < 0) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+        return -1;
+    }
+
+    if (b->peer == NULL) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_BROKEN_PIPE);
+        return -1;
+    }
+
+    peerb = b->peer->ptr;
+
+    /*
+     * For BIO_read we have to acquire both locks because we touch the retry
+     * flags on the local bio. (This is avoided in the recvmmsg case as it does
+     * not touch the retry flags.)
+     */
+    if (dgram_pair_lock_both_write(peerb, b) == 0) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
+        return -1;
+    }
+
+    l = dgram_pair_read_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
+    if (l < 0) {
+        ERR_raise(ERR_LIB_BIO, -l);
+        ret = -1;
+    } else {
+        ret = (int)l;
+    }
+
+    dgram_pair_unlock_both(peerb, b);
+    return ret;
+}
+
+/* Threadsafe */
+static int dgram_pair_recvmmsg(BIO *bio, BIO_MSG *msg,
+                               size_t stride, size_t num_msg,
+                               uint64_t flags,
+                               size_t *num_processed)
+{
+    int ret;
+    ossl_ssize_t l;
+    BIO_MSG *m;
+    size_t i;
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+
+    if (num_msg == 0) {
+        *num_processed = 0;
+        return 1;
+    }
+
+    if (b->peer == NULL) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_BROKEN_PIPE);
+        *num_processed = 0;
+        return 0;
+    }
+
+    peerb = b->peer->ptr;
+
+    if (CRYPTO_THREAD_write_lock(peerb->lock) == 0) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
+        *num_processed = 0;
+        return 0;
+    }
+
+    for (i = 0; i < num_msg; ++i) {
+        m = &BIO_MSG_N(msg, i);
+        l = dgram_pair_read_actual(bio, m->data, m->data_len,
+                                   m->local, m->peer, 1);
+        if (l < 0) {
+            *num_processed = i;
+            if (i > 0) {
+                ret = 1;
+            } else {
+                ERR_raise(ERR_LIB_BIO, -l);
+                ret = 0;
+            }
+            goto out;
+        }
+
+        m->data_len = l;
+        m->flags    = 0;
+    }
+
+    *num_processed = i;
+    ret = 1;
+out:
+    CRYPTO_THREAD_unlock(peerb->lock);
+    return ret;
+}
+
+/* Must hold local write lock */
+static size_t dgram_pair_write_inner(struct bio_dgram_pair_st *b, const uint8_t *buf, size_t sz)
+{
+    size_t total_written = 0;
+
+    /*
+     * We repeat pushes to the ring buffer for as long as we have data until we
+     * fail. We may not be able to push in one operation if the ring buffer
+     * wraps around, but there may still be more room for data.
+     */
+    while (sz > 0) {
+        size_t dst_len;
+        uint8_t *dst_buf;
+
+        /*
+         * There are two BIO instances, each with a ringbuf. We write to our own
+         * ringbuf and read from the peer ringbuf.
+         */
+        ring_buf_head(&b->rbuf, &dst_buf, &dst_len);
+        if (dst_len == 0)
+            break;
+
+        if (dst_len > sz)
+            dst_len = sz;
+
+        memcpy(dst_buf, buf, dst_len);
+        ring_buf_push(&b->rbuf, dst_len);
+
+        buf             += dst_len;
+        sz              -= dst_len;
+        total_written   += dst_len;
+    }
+
+    return total_written;
+}
+
+/*
+ * Must hold local write lock. Returns number of bytes processed or negated BIO
+ * response code.
+ */
+static ossl_ssize_t dgram_pair_write_actual(BIO *bio, const char *buf, size_t sz,
+                                            const BIO_ADDR *local, const BIO_ADDR *peer,
+                                            int is_multi)
+{
+    static const BIO_ADDR zero_addr = {0};
+    size_t saved_idx, saved_count;
+    struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+    struct dgram_hdr hdr = {0};
+
+    if (!is_multi)
+        BIO_clear_retry_flags(bio);
+
+    if (!bio->init)
+        return -BIO_R_UNINITIALIZED;
+
+    if (!ossl_assert(b != NULL && b->peer != NULL && b->rbuf.start != NULL))
+        return -BIO_R_TRANSFER_ERROR;
+
+    if (sz > 0 && buf == NULL)
+        return -BIO_R_INVALID_ARGUMENT;
+
+    if (local != NULL && b->local_addr_enable == 0)
+        return -BIO_R_LOCAL_ADDR_NOT_AVAILABLE;
+
+    peerb = b->peer->ptr;
+    if (peer != NULL && (peerb->cap & BIO_DGRAM_CAP_HANDLES_DST_ADDR) == 0)
+        return -BIO_R_PEER_ADDR_NOT_AVAILABLE;
+
+    hdr.len = sz;
+    hdr.dst_addr = (peer != NULL ? *peer : zero_addr);
+    hdr.src_addr = (local != NULL ? *local : zero_addr);
+
+    saved_idx   = b->rbuf.idx[0];
+    saved_count = b->rbuf.count;
+    if (dgram_pair_write_inner(b, (const uint8_t *)&hdr, sizeof(hdr)) != sizeof(hdr)
+            || dgram_pair_write_inner(b, (const uint8_t *)buf, sz) != sz) {
+        /*
+         * We were not able to push the header and the entirety of the payload
+         * onto the ring buffer, so abort and roll back the ring buffer state.
+         */
+        b->rbuf.idx[0] = saved_idx;
+        b->rbuf.count  = saved_count;
+        if (!is_multi)
+            BIO_set_retry_write(bio);
+        return -BIO_R_NON_FATAL;
+    }
+
+    return sz;
+}
+
+/* Threadsafe */
+static int dgram_pair_write(BIO *bio, const char *buf, int sz_)
+{
+    int ret;
+    ossl_ssize_t l;
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    if (sz_ < 0) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+        return -1;
+    }
+
+    if (CRYPTO_THREAD_write_lock(b->lock) == 0) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
+        return -1;
+    }
+
+    l = dgram_pair_write_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
+    if (l < 0) {
+        ERR_raise(ERR_LIB_BIO, -l);
+        ret = -1;
+    } else {
+        ret = (int)l;
+    }
+
+    CRYPTO_THREAD_unlock(b->lock);
+    return ret;
+}
+
+/* Threadsafe */
+static int dgram_pair_sendmmsg(BIO *bio, BIO_MSG *msg,
+                               size_t stride, size_t num_msg,
+                               uint64_t flags, size_t *num_processed)
+{
+    ossl_ssize_t ret, l;
+    BIO_MSG *m;
+    size_t i;
+    struct bio_dgram_pair_st *b = bio->ptr;
+
+    if (num_msg == 0) {
+        *num_processed = 0;
+        return 1;
+    }
+
+    if (CRYPTO_THREAD_write_lock(b->lock) == 0) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
+        *num_processed = 0;
+        return 0;
+    }
+
+    for (i = 0; i < num_msg; ++i) {
+        m = &BIO_MSG_N(msg, i);
+        l = dgram_pair_write_actual(bio, m->data, m->data_len,
+                                    m->local, m->peer, 1);
+        if (l < 0) {
+            *num_processed = i;
+            if (i > 0) {
+                ret = 1;
+            } else {
+                ERR_raise(ERR_LIB_BIO, -l);
+                ret = 0;
+            }
+            goto out;
+        }
+
+        m->flags = 0;
+    }
+
+    *num_processed = i;
+    ret = 1;
+out:
+    CRYPTO_THREAD_unlock(b->lock);
+    return ret;
+}
+
+#endif
index b203ed5e63fe3325632cd3397eb49246b5d5ea6e..2102038c74d9a970712339bd3929774714b28b6f 100644 (file)
@@ -11,7 +11,7 @@ SOURCE[../../libcrypto]=\
 SOURCE[../../libcrypto]=\
         bss_null.c bss_mem.c bss_bio.c bss_fd.c bss_file.c \
         bss_sock.c bss_conn.c bss_acpt.c bss_dgram.c \
-        bss_log.c bss_core.c
+        bss_log.c bss_core.c bss_dgram_pair.c
 
 # Filters
 SOURCE[../../libcrypto]=\
index 693b7bc9608e7c1b4baea0d6706c3b0bc46f3c6e..dbb2daa3502cb2db102fa69a324b15aa0c3a11d3 100644 (file)
@@ -146,12 +146,14 @@ BIO_R_LOCAL_ADDR_NOT_AVAILABLE:111:local addr not available
 BIO_R_LOOKUP_RETURNED_NOTHING:142:lookup returned nothing
 BIO_R_MALFORMED_HOST_OR_SERVICE:130:malformed host or service
 BIO_R_NBIO_CONNECT_ERROR:110:nbio connect error
+BIO_R_NON_FATAL:112:non fatal
 BIO_R_NO_ACCEPT_ADDR_OR_SERVICE_SPECIFIED:143:\
        no accept addr or service specified
 BIO_R_NO_HOSTNAME_OR_SERVICE_SPECIFIED:144:no hostname or service specified
 BIO_R_NO_PORT_DEFINED:113:no port defined
 BIO_R_NO_SUCH_FILE:128:no such file
 BIO_R_PORT_MISMATCH:150:port mismatch
+BIO_R_PEER_ADDR_NOT_AVAILABLE:114:peer addr not available
 BIO_R_TFO_DISABLED:106:tfo disabled
 BIO_R_TFO_NO_KERNEL_SUPPORT:108:tfo no kernel support
 BIO_R_TRANSFER_ERROR:104:transfer error
index 6b6e8378386bcaf22d1712728322e195c1891111..b90ad11eae1bef8964d4b71ec7282cd1096f5ab3 100644 (file)
@@ -651,6 +651,10 @@ DEPEND[html/man3/BIO_s_core.html]=man3/BIO_s_core.pod
 GENERATE[html/man3/BIO_s_core.html]=man3/BIO_s_core.pod
 DEPEND[man/man3/BIO_s_core.3]=man3/BIO_s_core.pod
 GENERATE[man/man3/BIO_s_core.3]=man3/BIO_s_core.pod
+DEPEND[html/man3/BIO_s_dgram_pair.html]=man3/BIO_s_dgram_pair.pod
+GENERATE[html/man3/BIO_s_dgram_pair.html]=man3/BIO_s_dgram_pair.pod
+DEPEND[man/man3/BIO_s_dgram_pair.3]=man3/BIO_s_dgram_pair.pod
+GENERATE[man/man3/BIO_s_dgram_pair.3]=man3/BIO_s_dgram_pair.pod
 DEPEND[html/man3/BIO_s_fd.html]=man3/BIO_s_fd.pod
 GENERATE[html/man3/BIO_s_fd.html]=man3/BIO_s_fd.pod
 DEPEND[man/man3/BIO_s_fd.3]=man3/BIO_s_fd.pod
@@ -2914,6 +2918,7 @@ html/man3/BIO_s_accept.html \
 html/man3/BIO_s_bio.html \
 html/man3/BIO_s_connect.html \
 html/man3/BIO_s_core.html \
+html/man3/BIO_s_dgram_pair.html \
 html/man3/BIO_s_fd.html \
 html/man3/BIO_s_file.html \
 html/man3/BIO_s_mem.html \
@@ -3514,6 +3519,7 @@ man/man3/BIO_s_accept.3 \
 man/man3/BIO_s_bio.3 \
 man/man3/BIO_s_connect.3 \
 man/man3/BIO_s_core.3 \
+man/man3/BIO_s_dgram_pair.3 \
 man/man3/BIO_s_fd.3 \
 man/man3/BIO_s_file.3 \
 man/man3/BIO_s_mem.3 \
diff --git a/doc/man3/BIO_s_dgram_pair.pod b/doc/man3/BIO_s_dgram_pair.pod
new file mode 100644 (file)
index 0000000..b0f52af
--- /dev/null
@@ -0,0 +1,222 @@
+=pod
+
+=head1 NAME
+
+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 - datagram pair BIO
+
+=head1 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);
+
+=head1 DESCRIPTION
+
+BIO_s_dgram_pair() returns the method for a BIO datagram pair. A BIO datagram
+pair is similar to a BIO pair (see L<BIO_s_bio(3)>) but has datagram semantics.
+Broadly, this means that the length of the buffer passed to a write call will
+match that retrieved by a read call. If the buffer passed to a read call is too
+short, the datagram is truncated or the read fails, depending on how the BIO is
+configured.
+
+The BIO datagram pair attaches certain metadata to each write, such as source
+and destination addresses. This information may be retrieved on read.
+
+A typical application of a BIO datagram pair is to allow an application to keep
+all datagram network I/O requested by libssl under application control.
+
+The BIO datagram pair is designed to support multithreaded use where certain
+restrictions are observed; see THREADING.
+
+The BIO datagram pair allows each half of a pair to signal to the other half
+whether they support certain capabilities; see CAPABILITY INDICATION.
+
+BIO_new_bio_dgram_pair() combines the calls to L<BIO_new(3)>,
+L<BIO_make_bio_pair(3)> and L<BIO_set_write_buf_size(3)> to create a connected
+pair of BIOs B<bio1>, B<bio2> with write buffer sizes B<writebuf1> and
+B<writebuf2>. If either size is zero then the default size is used.
+
+L<BIO_make_bio_pair(3)> may be used to join two datagram pair BIOs into a pair.
+The two BIOs must both use the method returned by BIO_s_dgram_pair() and neither
+of the BIOs may currently be associated in a pair.
+
+L<BIO_destroy_bio_pair(3)> destroys the association between two connected BIOs.
+Freeing either half of the pair will automatically destroy the association.
+
+L<BIO_reset(3)> clears any data in the write buffer of the given BIO. This means
+that the opposite BIO in the pair will no longer have any data waiting to be
+read.
+
+The BIO maintains a fixed size internal write buffer. When the buffer is full,
+further writes will fail until the buffer is drained via calls to
+L<BIO_read(3)>. The size of the buffer can be changed using
+L<BIO_set_write_buf_size(3)> and queried using L<BIO_get_write_buf_size(3)>.
+
+Note that the write buffer is partially consumed by metadata stored internally
+which is attached to each datagram, such as source and destination addresses.
+The size of this overhead is undefined and may change between releases.
+
+The standard L<BIO_ctrl_pending(3)> call has modified behaviour and returns the
+size of the next datagram waiting to be read in bytes. An application can use
+this function to ensure it provides an adequate buffer to a subsequent read
+call. If no datagram is waiting to be read, zero is returned.
+
+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.
+
+L<BIO_eof(3)> returns 1 only if the given BIO datagram pair BIO is not currently
+connected to a peer BIO.
+
+L<BIO_get_write_guarantee(3)> and L<BIO_ctrl_get_write_guarantee(3)> return how
+large a datagram the next call to L<BIO_write(3)> can accept. If there is not
+enough space in the write buffer to accept another datagram equal in size to the
+configured MTU, zero is returned (see below). This is intended to avoid a
+situation where an application attempts to read a datagram from a network
+intending to write it to a BIO datagram pair, but where the received datagram
+ends up being too large to write to the BIO datagram pair.
+
+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. If no-truncate mode is disabled (the default), the
+datagram will be silently truncated. This default behaviour maintains
+compatibility with the semantics of the Berkeley sockets API.
+
+BIO_dgram_set_mtu() and BIO_dgram_get_mtu() may be used to set an informational
+MTU value on the BIO datagram pair. If BIO_dgram_set_mtu() is used on a BIO
+which is currently part of a BIO datagram pair, the MTU value is set on both
+halves of the pair. The value does not affect the operation of the BIO datagram
+pair (except for BIO_get_write_guarantee(); see above) but may be used by other
+code to determine a requested MTU. When a BIO datagram pair BIO is created, the
+MTU is set to an unspecified but valid value.
+
+L<BIO_flush(3)> is a no-op.
+
+=head1 NOTES
+
+The halves of a BIO datagram pair have independent lifetimes and must be
+separately freed.
+
+=head1 THREADING
+
+L<BIO_recvmmsg(3)>, L<BIO_sendmmsg(3)>, L<BIO_read(3)>, L<BIO_write(3)>,
+L<BIO_pending(3)>, L<BIO_get_write_guarantee(3)> and L<BIO_flush(3)> may be used
+by multiple threads simultaneously on the same BIO datagram pair. Specific
+L<BIO_ctrl(3)> operations (namely BIO_CTRL_PENDING, BIO_CTRL_FLUSH and
+BIO_C_GET_WRITE_GUARANTEE) may also be used. Invoking any other BIO call, or any
+other L<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.
+
+=head1 CAPABILITY INDICATION
+
+The BIO datagram pair can be used to enqueue datagrams which have source and
+destination addresses attached. It is important that the component consuming one
+side of a BIO datagram pair understand whether the other side of the pair will
+honour any source and destination addresses it attaches to each datagram. For
+example, if datagrams are queued with destination addresses set but simply read
+by simple calls to L<BIO_read(3)>, the destination addresses will be discarded.
+
+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 will be provided by the writer. These capability flags should
+be set via a call to BIO_dgram_set_caps(), and these capabilities will be
+reflected in the value returned by BIO_dgram_get_effective_caps() on the
+opposite BIO. If necessary, the capability value previously set can be retrieved
+using BIO_dgram_get_caps(). Note that BIO_dgram_set_caps() on a given BIO
+controls the capabilities advertised to the peer, and
+BIO_dgram_get_effective_caps() on a given BIO determines the capabilities
+advertised by the peer of that BIO.
+
+The following capabilities are available:
+
+=over 4
+
+=item B<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.
+
+=item B<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.
+
+=item B<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.
+
+=item B<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.
+
+=back
+
+If a caller attempts to specify a destination address (for example, using
+L<BIO_sendmmsg(3)>) and the peer has not advertised the
+B<BIO_DGRAM_CAP_HANDLES_DST_ADDR> capability, the operation fails. Thus,
+capability negotiation is mandatory.
+
+If a caller attempts 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 L<BIO_dgram_set_local_addr_enable(3)>.
+
+If a caller attempts to enable local address support using
+L<BIO_dgram_set_local_addr_enable(3)> and L<BIO_dgram_get_local_addr_cap(3)>
+does not return 1 (meaning that the peer has not advertised both the
+B<BIO_DGRAM_CAP_HANDLES_SRC_ADDR> and the B<BIO_DGRAM_CAP_PROVIDES_DST_ADDR>
+capability), the operation fails.
+
+B<BIO_DGRAM_CAP_PROVIDES_SRC_ADDR> and B<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 of the BIO datagram pair. However, these capability flags do not
+affect the behaviour of the BIO datagram pair.
+
+=head1 RETURN VALUES
+
+BIO_new_bio_dgram_pair() returns 1 on success, with the new BIOs available in
+B<bio1> and B<bio2>, or 0 on failure, with NULL pointers stored into the
+locations for B<bio1> and B<bio2>. Check the error stack for more information.
+
+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.
+
+=head1 SEE ALSO
+
+L<BIO_s_bio(3)>, L<bio(7)>
+
+=head1 COPYRIGHT
+
+Copyright 2022 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
+L<https://www.openssl.org/source/license.html>.
+
+=cut
index 6940847e22cfbc1e98402d851deece73947402a9..c8789ba0ada4b4098a032e85f222ce4f346e617b 100644 (file)
@@ -158,6 +158,11 @@ particular may be noted:
 The I<local> field was set to a non-NULL value, but local address support is not
 available or not enabled on the BIO.
 
+=item B<BIO_R_PEER_ADDR_NOT_AVAILABLE>
+
+The I<peer> field was set to a non-NULL value, but peer address support is not
+available on the BIO.
+
 =item B<BIO_R_UNSUPPORTED_METHOD>
 
 The BIO_sendmmsg() or BIO_recvmmsg() method is not supported on the BIO.
index 2bbfc7571461bf9c3d0929d6237d543202fef62c..e413bb41cb81f6b6e603892ddaed7beec62ad31b 100644 (file)
@@ -68,6 +68,7 @@ extern "C" {
 #  define BIO_TYPE_DGRAM_SCTP    (24|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
 # endif
 # define BIO_TYPE_CORE_TO_PROV   (25|BIO_TYPE_SOURCE_SINK)
+# define BIO_TYPE_DGRAM_PAIR     (26|BIO_TYPE_SOURCE_SINK)
 
 #define BIO_TYPE_START           128
 
@@ -175,6 +176,17 @@ extern "C" {
 # define BIO_CTRL_DGRAM_GET_LOCAL_ADDR_CAP      82
 # define BIO_CTRL_DGRAM_GET_LOCAL_ADDR_ENABLE   83
 # define BIO_CTRL_DGRAM_SET_LOCAL_ADDR_ENABLE   84
+# define BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS      85
+# define BIO_CTRL_DGRAM_GET_CAPS                86
+# define BIO_CTRL_DGRAM_SET_CAPS                87
+# define BIO_CTRL_DGRAM_GET_NO_TRUNC            88
+# define BIO_CTRL_DGRAM_SET_NO_TRUNC            89
+
+# define BIO_DGRAM_CAP_NONE                 0U
+# define BIO_DGRAM_CAP_HANDLES_SRC_ADDR     (1U << 0)
+# define BIO_DGRAM_CAP_HANDLES_DST_ADDR     (1U << 1)
+# define BIO_DGRAM_CAP_PROVIDES_SRC_ADDR    (1U << 2)
+# define BIO_DGRAM_CAP_PROVIDES_DST_ADDR    (1U << 3)
 
 # ifndef OPENSSL_NO_KTLS
 #  define BIO_get_ktls_send(b)         \
@@ -607,6 +619,20 @@ int BIO_ctrl_reset_read_request(BIO *b);
          (int)BIO_ctrl((b), BIO_CTRL_DGRAM_GET_LOCAL_ADDR_ENABLE, 0, (char *)(penable))
 # define BIO_dgram_set_local_addr_enable(b, enable) \
          (int)BIO_ctrl((b), BIO_CTRL_DGRAM_SET_LOCAL_ADDR_ENABLE, (enable), NULL)
+# define BIO_dgram_get_effective_caps(b) \
+         (uint32_t)BIO_ctrl((b), BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS, 0, NULL)
+# define BIO_dgram_get_caps(b) \
+         (uint32_t)BIO_ctrl((b), BIO_CTRL_DGRAM_GET_CAPS, 0, NULL)
+# define BIO_dgram_set_caps(b, caps) \
+         (int)BIO_ctrl((b), BIO_CTRL_DGRAM_SET_CAPS, (long)(caps), NULL)
+# define BIO_dgram_get_no_trunc(b) \
+         (unsigned int)BIO_ctrl((b), BIO_CTRL_DGRAM_GET_NO_TRUNC, 0, NULL)
+# define BIO_dgram_set_no_trunc(b, enable) \
+         (int)BIO_ctrl((b), BIO_CTRL_DGRAM_SET_NO_TRUNC, (enable), NULL)
+# define BIO_dgram_get_mtu(b) \
+         (unsigned int)BIO_ctrl((b), BIO_CTRL_DGRAM_GET_MTU, 0, NULL)
+# define BIO_dgram_set_mtu(b, mtu) \
+         (int)BIO_ctrl((b), BIO_CTRL_DGRAM_SET_MTU, (mtu), NULL)
 
 /* ctrl macros for BIO_f_prefix */
 # define BIO_set_prefix(b,p) BIO_ctrl((b), BIO_CTRL_SET_PREFIX, 0, (void *)(p))
@@ -702,6 +728,7 @@ const BIO_METHOD *BIO_f_nbio_test(void);
 const BIO_METHOD *BIO_f_prefix(void);
 const BIO_METHOD *BIO_s_core(void);
 # ifndef OPENSSL_NO_DGRAM
+const BIO_METHOD *BIO_s_dgram_pair(void);
 const BIO_METHOD *BIO_s_datagram(void);
 int BIO_dgram_non_fatal_error(int error);
 BIO *BIO_new_dgram(int fd, int close_flag);
@@ -824,6 +851,11 @@ BIO *BIO_new_fd(int fd, int close_flag);
 
 int BIO_new_bio_pair(BIO **bio1, size_t writebuf1,
                      BIO **bio2, size_t writebuf2);
+# ifndef OPENSSL_NO_DGRAM
+int BIO_new_bio_dgram_pair(BIO **bio1, size_t writebuf1,
+                           BIO **bio2, size_t writebuf2);
+# endif
+
 /*
  * If successful, returns 1 and in *bio1, *bio2 two BIO pair endpoints.
  * Otherwise returns 0 and sets *bio1 and *bio2 to NULL. Size 0 uses default
index 72b0b1ffa5617db409a90b18420ed6db674d2576..e4fdb64974fc064e7486d67c61212f364b3c507b 100644 (file)
 # define BIO_R_LOOKUP_RETURNED_NOTHING                    142
 # define BIO_R_MALFORMED_HOST_OR_SERVICE                  130
 # define BIO_R_NBIO_CONNECT_ERROR                         110
+# define BIO_R_NON_FATAL                                  112
 # define BIO_R_NO_ACCEPT_ADDR_OR_SERVICE_SPECIFIED        143
 # define BIO_R_NO_HOSTNAME_OR_SERVICE_SPECIFIED           144
 # define BIO_R_NO_PORT_DEFINED                            113
 # define BIO_R_NO_SUCH_FILE                               128
+# define BIO_R_NULL_PARAMETER                             115 /* unused */
 # define BIO_R_TFO_DISABLED                               106
 # define BIO_R_TFO_NO_KERNEL_SUPPORT                      108
 # define BIO_R_TRANSFER_ERROR                             104
@@ -64,7 +66,7 @@
 # define BIO_R_UNSUPPORTED_PROTOCOL_FAMILY                131
 # define BIO_R_WRITE_TO_READ_ONLY_BIO                     126
 # define BIO_R_WSASTARTUP                                 122
-# define BIO_R_NON_FATAL                                  149
 # define BIO_R_PORT_MISMATCH                              150
+# define BIO_R_PEER_ADDR_NOT_AVAILABLE                    151
 
 #endif
index 7f147283b998d107e34760e2a367c4c4a65d9814..4dd2893c1b31b4216f0e22d0ca31ad761f8d7f62 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2022 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
@@ -9,6 +9,7 @@
 
 #include <string.h>
 #include <openssl/bio.h>
+#include <openssl/rand.h>
 #include "testutil.h"
 #include "internal/sockets.h"
 
@@ -450,6 +451,299 @@ static int test_bio_dgram(int idx)
                                bio_dgram_cases[idx].local);
 }
 
+static int random_data(const uint32_t *key, uint8_t *data, size_t data_len, size_t offset)
+{
+    int ret = 0, outl;
+    EVP_CIPHER_CTX *ctx = NULL;
+    EVP_CIPHER *cipher = NULL;
+    static const uint8_t zeroes[2048];
+    uint32_t counter[4] = {0};
+
+    counter[0] = (uint32_t)offset;
+
+    ctx = EVP_CIPHER_CTX_new();
+    if (ctx == NULL)
+        goto err;
+
+    cipher = EVP_CIPHER_fetch(NULL, "ChaCha20", NULL);
+    if (cipher == NULL)
+        goto err;
+
+    if (EVP_EncryptInit_ex2(ctx, cipher, (uint8_t *)key, (uint8_t *)counter, NULL) == 0)
+        goto err;
+
+    while (data_len > 0) {
+        outl = data_len > sizeof(zeroes) ? (int)sizeof(zeroes) : (int)data_len;
+        if (EVP_EncryptUpdate(ctx, data, &outl, zeroes, outl) != 1)
+            goto err;
+
+        data     += outl;
+        data_len -= outl;
+    }
+
+    ret = 1;
+err:
+    EVP_CIPHER_CTX_free(ctx);
+    EVP_CIPHER_free(cipher);
+    return ret;
+}
+
+static int test_bio_dgram_pair(void)
+{
+    int testresult = 0, blen, mtu1, mtu2, r;
+    BIO *bio1 = NULL, *bio2 = NULL;
+    uint8_t scratch[2048 + 4], scratch2[2048];
+    uint32_t key[8];
+    size_t i, num_dgram, num_processed = 0;
+    BIO_MSG msgs[2] = {0}, rmsgs[2] = {0};
+    BIO_ADDR *addr1 = NULL, *addr2 = NULL, *addr3 = NULL, *addr4 = NULL;
+    struct in_addr in_local;
+    size_t total = 0;
+    const uint32_t ref_caps = BIO_DGRAM_CAP_HANDLES_SRC_ADDR
+                            | BIO_DGRAM_CAP_HANDLES_DST_ADDR
+                            | BIO_DGRAM_CAP_PROVIDES_SRC_ADDR
+                            | BIO_DGRAM_CAP_PROVIDES_DST_ADDR;
+
+    in_local.s_addr = ntohl(0x7f000001);
+
+    for (i = 0; i < OSSL_NELEM(key); ++i)
+        key[i] = test_random();
+
+    if (!TEST_int_eq(BIO_new_bio_dgram_pair(&bio1, 0, &bio2, 0), 1))
+        goto err;
+
+    mtu1 = BIO_dgram_get_mtu(bio1);
+    if (!TEST_int_ge(mtu1, 1280))
+        goto err;
+
+    mtu2 = BIO_dgram_get_mtu(bio2);
+    if (!TEST_int_ge(mtu2, 1280))
+        goto err;
+
+    if (!TEST_int_eq(mtu1, mtu2))
+        goto err;
+
+    if (!TEST_int_le(mtu1, sizeof(scratch) - 4))
+        goto err;
+
+    for (i = 0;; ++i) {
+        if (!TEST_int_eq(random_data(key, scratch, sizeof(scratch), i), 1))
+            goto err;
+
+        blen = (*(uint32_t*)scratch) % mtu1;
+        r = BIO_write(bio1, scratch + 4, blen);
+        if (r == -1)
+            break;
+
+        if (!TEST_int_eq(r, blen))
+            goto err;
+
+        total += blen;
+        if (!TEST_size_t_lt(total, 1 * 1024 * 1024))
+            goto err;
+    }
+
+    /*
+     * Should be able to fit at least 9 datagrams in default write buffer size
+     * in worst case
+     */
+    if (!TEST_int_ge(i, 9))
+        goto err;
+
+    /* Check we read back the same data */
+    num_dgram = i;
+    for (i = 0; i < num_dgram; ++i) {
+        if (!TEST_int_eq(random_data(key, scratch, sizeof(scratch), i), 1))
+            goto err;
+
+        blen = (*(uint32_t*)scratch) % mtu1;
+        r = BIO_read(bio2, scratch2, sizeof(scratch2));
+        if (!TEST_int_eq(r, blen))
+            goto err;
+
+        if (!TEST_mem_eq(scratch + 4, blen, scratch2, blen))
+            goto err;
+    }
+
+    /* Should now be out of data */
+    if (!TEST_int_eq(BIO_read(bio2, scratch2, sizeof(scratch2)), -1))
+        goto err;
+
+    /* sendmmsg/recvmmsg */
+    if (!TEST_int_eq(random_data(key, scratch, sizeof(scratch), 0), 1))
+        goto err;
+
+    msgs[0].data     = scratch;
+    msgs[0].data_len = 19;
+    msgs[1].data     = scratch + 19;
+    msgs[1].data_len = 46;
+
+    if (!TEST_true(BIO_sendmmsg(bio1, msgs, sizeof(BIO_MSG), OSSL_NELEM(msgs), 0,
+                                &num_processed))
+        || !TEST_size_t_eq(num_processed, 2))
+        goto err;
+
+    rmsgs[0].data       = scratch2;
+    rmsgs[0].data_len   = 64;
+    rmsgs[1].data       = scratch2 + 64;
+    rmsgs[1].data_len   = 64;
+    if (!TEST_true(BIO_recvmmsg(bio2, rmsgs, sizeof(BIO_MSG), OSSL_NELEM(rmsgs), 0,
+                                &num_processed))
+        || !TEST_size_t_eq(num_processed, 2))
+        goto err;
+
+    if (!TEST_mem_eq(rmsgs[0].data, rmsgs[0].data_len, scratch, 19))
+        goto err;
+
+    if (!TEST_mem_eq(rmsgs[1].data, rmsgs[1].data_len, scratch + 19, 46))
+        goto err;
+
+    /* sendmmsg/recvmmsg with peer */
+    addr1 = BIO_ADDR_new();
+    if (!TEST_ptr(addr1))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_rawmake(addr1, AF_INET, &in_local,
+                                      sizeof(in_local), 1234), 1))
+        goto err;
+
+    addr2 = BIO_ADDR_new();
+    if (!TEST_ptr(addr2))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_rawmake(addr2, AF_INET, &in_local,
+                                      sizeof(in_local), 2345), 1))
+        goto err;
+
+    addr3 = BIO_ADDR_new();
+    if (!TEST_ptr(addr3))
+        goto err;
+
+    addr4 = BIO_ADDR_new();
+    if (!TEST_ptr(addr4))
+        goto err;
+
+    msgs[0].peer = addr1;
+
+    /* fails due to lack of caps on peer */
+    if (!TEST_false(BIO_sendmmsg(bio1, msgs, sizeof(BIO_MSG), OSSL_NELEM(msgs),
+                                 0, &num_processed))
+        || !TEST_size_t_eq(num_processed, 0))
+        goto err;
+
+    if (!TEST_int_eq(BIO_dgram_set_caps(bio2, ref_caps), 1))
+        goto err;
+
+    if (!TEST_int_eq(BIO_dgram_get_caps(bio2), ref_caps))
+        goto err;
+
+    if (!TEST_int_eq(BIO_dgram_get_effective_caps(bio1), ref_caps))
+        goto err;
+
+    if (!TEST_int_eq(BIO_dgram_get_effective_caps(bio2), 0))
+        goto err;
+
+    if (!TEST_int_eq(BIO_dgram_set_caps(bio1, ref_caps), 1))
+        goto err;
+
+    /* succeeds with cap now available */
+    if (!TEST_true(BIO_sendmmsg(bio1, msgs, sizeof(BIO_MSG), 1, 0, &num_processed))
+        || !TEST_size_t_eq(num_processed, 1))
+        goto err;
+
+    /* enable local addr support */
+    if (!TEST_int_eq(BIO_dgram_set_local_addr_enable(bio2, 1), 1))
+        goto err;
+
+    rmsgs[0].data       = scratch2;
+    rmsgs[0].data_len   = 64;
+    rmsgs[0].peer       = addr3;
+    rmsgs[0].local      = addr4;
+    if (!TEST_true(BIO_recvmmsg(bio2, rmsgs, sizeof(BIO_MSG), OSSL_NELEM(rmsgs), 0,
+                                &num_processed))
+        || !TEST_size_t_eq(num_processed, 1))
+        goto err;
+
+    if (!TEST_mem_eq(rmsgs[0].data, rmsgs[0].data_len, msgs[0].data, 19))
+        goto err;
+
+    /* We didn't set the source address so this should be zero */
+    if (!TEST_int_eq(BIO_ADDR_family(addr3), 0))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_family(addr4), AF_INET))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_rawport(addr4), 1234))
+        goto err;
+
+    /* test source address */
+    msgs[0].local = addr2;
+
+    if (!TEST_int_eq(BIO_dgram_set_local_addr_enable(bio1, 1), 1))
+        goto err;
+
+    if (!TEST_true(BIO_sendmmsg(bio1, msgs, sizeof(BIO_MSG), 1, 0, &num_processed))
+        || !TEST_size_t_eq(num_processed, 1))
+        goto err;
+
+    rmsgs[0].data       = scratch2;
+    rmsgs[0].data_len   = 64;
+    if (!TEST_true(BIO_recvmmsg(bio2, rmsgs, sizeof(BIO_MSG), OSSL_NELEM(rmsgs), 0, &num_processed))
+        || !TEST_size_t_eq(num_processed, 1))
+        goto err;
+
+    if (!TEST_mem_eq(rmsgs[0].data, rmsgs[0].data_len,
+                     msgs[0].data, msgs[0].data_len))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_family(addr3), AF_INET))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_rawport(addr3), 2345))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_family(addr4), AF_INET))
+        goto err;
+
+    if (!TEST_int_eq(BIO_ADDR_rawport(addr4), 1234))
+        goto err;
+
+    /* test truncation, pending */
+    r = BIO_write(bio1, scratch, 64);
+    if (!TEST_int_eq(r, 64))
+        goto err;
+
+    memset(scratch2, 0, 64);
+    if (!TEST_int_eq(BIO_dgram_set_no_trunc(bio2, 1), 1))
+        goto err;
+
+    if (!TEST_int_eq(BIO_read(bio2, scratch2, 32), -1))
+        goto err;
+
+    if (!TEST_int_eq(BIO_pending(bio2), 64))
+        goto err;
+
+    if (!TEST_int_eq(BIO_dgram_set_no_trunc(bio2, 0), 1))
+        goto err;
+
+    if (!TEST_int_eq(BIO_read(bio2, scratch2, 32), 32))
+        goto err;
+
+    if (!TEST_mem_eq(scratch, 32, scratch2, 32))
+        goto err;
+
+    testresult = 1;
+err:
+    BIO_free(bio1);
+    BIO_free(bio2);
+    BIO_ADDR_free(addr1);
+    BIO_ADDR_free(addr2);
+    BIO_ADDR_free(addr3);
+    BIO_ADDR_free(addr4);
+    return testresult;
+}
+
 #endif /* !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK) */
 
 int setup_tests(void)
@@ -461,6 +755,8 @@ int setup_tests(void)
 
 #if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
     ADD_ALL_TESTS(test_bio_dgram, OSSL_NELEM(bio_dgram_cases));
+    ADD_TEST(test_bio_dgram_pair);
 #endif
+
     return 1;
 }
index 661076c2f829c095e18b8a3e87fd8aa09f208d74..69c4f12c57d4ea3636ced6acfc2e919dc50f95c6 100644 (file)
@@ -63,7 +63,7 @@ IF[{- !$disabled{tests} -}]
           keymgmt_internal_test hexstr_test provider_status_test defltfips_test \
           bio_readbuffer_test user_property_test pkcs7_test upcallstest \
           provfetchtest prov_config_test rand_test ca_internals_test \
-          bio_tfo_test membio_test list_test fips_version_test
+          bio_tfo_test membio_test bio_dgram_test list_test fips_version_test
 
   IF[{- !$disabled{'deprecated-3.0'} -}]
     PROGRAMS{noinst}=enginetest
@@ -404,6 +404,10 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[membio_test]=../include ../apps/include ..
   DEPEND[membio_test]=../libcrypto libtestutil.a
 
+  SOURCE[bio_dgram_test]=bio_dgram_test.c
+  INCLUDE[bio_dgram_test]=../include ../apps/include ..
+  DEPEND[bio_dgram_test]=../libcrypto libtestutil.a
+
   SOURCE[params_api_test]=params_api_test.c
   INCLUDE[params_api_test]=../include ../apps/include
   DEPEND[params_api_test]=../libcrypto libtestutil.a
index 250b12d037f3cb62f1fd56b8843046ff161691fd..505b2cf07e76b7477370952deb33a543f1c4c784 100644 (file)
@@ -24,6 +24,7 @@
 #include <openssl/rsa.h>
 #include <openssl/aes.h>
 #include <openssl/err.h>
+#include <openssl/rand.h>
 #include "internal/tsan_assist.h"
 #include "internal/nelem.h"
 #include "testutil.h"
@@ -44,6 +45,8 @@ static const char *default_provider[] = { "default", NULL };
 static const char *fips_provider[] = { "fips", NULL };
 static const char *fips_and_default_providers[] = { "default", "fips", NULL };
 
+static CRYPTO_RWLOCK *global_lock;
+
 #ifdef TSAN_REQUIRES_LOCKING
 static CRYPTO_RWLOCK *tsan_lock;
 #endif
@@ -266,6 +269,19 @@ static void multi_intialise(void)
     memset(multi_provider, 0, sizeof(multi_provider));
 }
 
+static void multi_set_success(int ok)
+{
+    if (CRYPTO_THREAD_write_lock(global_lock) == 0) {
+        /* not synchronized, but better than not reporting failure */
+        multi_success = ok;
+        return;
+    }
+
+    multi_success = ok;
+
+    CRYPTO_THREAD_unlock(global_lock);
+}
+
 static void thead_teardown_libctx(void)
 {
     OSSL_PROVIDER **p;
@@ -407,7 +423,7 @@ static void thread_general_worker(void)
     EVP_CIPHER_free(ciph);
     EVP_PKEY_free(pkey);
     if (!testresult)
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static void thread_multi_simple_fetch(void)
@@ -417,7 +433,7 @@ static void thread_multi_simple_fetch(void)
     if (md != NULL)
         EVP_MD_free(md);
     else
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static EVP_PKEY *shared_evp_pkey = NULL;
@@ -466,7 +482,7 @@ static void thread_shared_evp_pkey(void)
  err:
     EVP_PKEY_CTX_free(ctx);
     if (!success)
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static void thread_provider_load_unload(void)
@@ -475,7 +491,7 @@ static void thread_provider_load_unload(void)
 
     if (!TEST_ptr(deflt)
             || !TEST_true(OSSL_PROVIDER_available(multi_libctx, "default")))
-        multi_success = 0;
+        multi_set_success(0);
 
     OSSL_PROVIDER_unload(deflt);
 }
@@ -532,7 +548,7 @@ static void thread_downgrade_shared_evp_pkey(void)
      * downgrading
      */
     if (EVP_PKEY_get0_RSA(shared_evp_pkey) == NULL)
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static int test_multi_downgrade_shared_pkey(void)
@@ -588,7 +604,7 @@ static void test_multi_load_worker(void)
 
     if (!TEST_ptr(prov = OSSL_PROVIDER_load(multi_libctx, multi_load_provider))
             || !TEST_true(OSSL_PROVIDER_unload(prov)))
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static int test_multi_default(void)
@@ -644,7 +660,7 @@ static void test_obj_create_one(void)
     if (!TEST_int_ne(id, 0)
             || !TEST_true(id = OBJ_create(oid, sn, ln))
             || !TEST_true(OBJ_add_sigid(id, NID_sha3_256, NID_rsa)))
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static int test_obj_add(void)
@@ -657,7 +673,7 @@ static int test_obj_add(void)
 static void test_lib_ctx_load_config_worker(void)
 {
     if (!TEST_int_eq(OSSL_LIB_CTX_load_config(multi_libctx, config_file), 1))
-        multi_success = 0;
+        multi_set_success(0);
 }
 
 static int test_lib_ctx_load_config(void)
@@ -667,6 +683,64 @@ static int test_lib_ctx_load_config(void)
                            1, default_provider);
 }
 
+#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
+static BIO *multi_bio1, *multi_bio2;
+
+static void test_bio_dgram_pair_worker(void)
+{
+    ossl_unused int r;
+    int ok = 0;
+    uint8_t ch = 0;
+    uint8_t scratch[64];
+    BIO_MSG msg = {0};
+    size_t num_processed = 0;
+
+    if (!TEST_int_eq(RAND_bytes_ex(multi_libctx, &ch, 1, 64), 1))
+        goto err;
+
+    msg.data     = scratch;
+    msg.data_len = sizeof(scratch);
+
+    /*
+     * We do not test for failure here as recvmmsg may fail if no sendmmsg
+     * has been called yet. The purpose of this code is to exercise tsan.
+     */
+    if (ch & 2)
+        r = BIO_sendmmsg(ch & 1 ? multi_bio2 : multi_bio1, &msg,
+                         sizeof(BIO_MSG), 1, 0, &num_processed);
+    else
+        r = BIO_recvmmsg(ch & 1 ? multi_bio2 : multi_bio1, &msg,
+                         sizeof(BIO_MSG), 1, 0, &num_processed);
+
+    ok = 1;
+err:
+    if (ok == 0)
+        multi_set_success(0);
+}
+
+static int test_bio_dgram_pair(void)
+{
+    int r;
+    BIO *bio1 = NULL, *bio2 = NULL;
+
+    r = BIO_new_bio_dgram_pair(&bio1, 0, &bio2, 0);
+    if (!TEST_int_eq(r, 1))
+        goto err;
+
+    multi_bio1 = bio1;
+    multi_bio2 = bio2;
+
+    r  = thread_run_test(&test_bio_dgram_pair_worker,
+                         MAXIMUM_THREADS, &test_bio_dgram_pair_worker,
+                         1, default_provider);
+
+err:
+    BIO_free(bio1);
+    BIO_free(bio2);
+    return r;
+}
+#endif
+
 typedef enum OPTION_choice {
     OPT_ERR = -1,
     OPT_EOF = 0,
@@ -713,6 +787,9 @@ int setup_tests(void)
     if (!TEST_ptr(privkey))
         return 0;
 
+    if (!TEST_ptr(global_lock = CRYPTO_THREAD_lock_new()))
+        return 0;
+
 #ifdef TSAN_REQUIRES_LOCKING
     if (!TEST_ptr(tsan_lock = CRYPTO_THREAD_lock_new()))
         return 0;
@@ -736,6 +813,9 @@ int setup_tests(void)
     ADD_TEST(test_multi_load_unload_provider);
     ADD_TEST(test_obj_add);
     ADD_TEST(test_lib_ctx_load_config);
+#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
+    ADD_TEST(test_bio_dgram_pair);
+#endif
     return 1;
 }
 
@@ -745,4 +825,5 @@ void cleanup_tests(void)
 #ifdef TSAN_REQUIRES_LOCKING
     CRYPTO_THREAD_lock_free(tsan_lock);
 #endif
+    CRYPTO_THREAD_lock_free(global_lock);
 }
index 82e937f60a2171baa785b67a699d352f3bd84e64..50be3df6de8adf94784e2d95622f9e2a8e545400 100644 (file)
@@ -5459,3 +5459,5 @@ BIO_err_is_non_fatal                    ? 3_1_0   EXIST::FUNCTION:SOCK
 X509_get_default_cert_uri               ?      3_1_0   EXIST::FUNCTION:
 X509_get_default_cert_uri_env           ?      3_1_0   EXIST::FUNCTION:
 X509_get_default_cert_path_env          ?      3_1_0   EXIST::FUNCTION:
+BIO_s_dgram_pair                        ?      3_1_0   EXIST::FUNCTION:DGRAM
+BIO_new_bio_dgram_pair                  ?      3_1_0   EXIST::FUNCTION:DGRAM
index 77e2a9e0c6c50991d1d81333fed2b0c2e4113669..36ce26ee361c4351bd9cf98810bb5c760ffa077d 100644 (file)
@@ -148,6 +148,13 @@ BIO_destroy_bio_pair                    define
 BIO_dgram_get_local_addr_cap            define
 BIO_dgram_get_local_addr_enable         define
 BIO_dgram_set_local_addr_enable         define
+BIO_dgram_set_no_trunc                  define
+BIO_dgram_get_no_trunc                  define
+BIO_dgram_get_caps                      define
+BIO_dgram_set_caps                      define
+BIO_dgram_get_effective_caps            define
+BIO_dgram_get_mtu                       define
+BIO_dgram_set_mtu                       define
 BIO_do_accept                           define
 BIO_do_connect                          define
 BIO_do_handshake                        define