Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b
Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/tls/openssl/tls_udp.c b/src/tls/openssl/tls_udp.c
new file mode 100644
index 0000000..4ec81a3
--- /dev/null
+++ b/src/tls/openssl/tls_udp.c
@@ -0,0 +1,883 @@
+/**
+ * @file openssl/tls_udp.c DTLS backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <sys/time.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_srtp.h>
+#include <re_udp.h>
+#include <re_tmr.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+#define DEBUG_MODULE "dtls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ MTU_DEFAULT = 1400,
+ MTU_FALLBACK = 548,
+};
+
+
+struct dtls_sock {
+ struct sa peer;
+ struct udp_helper *uh;
+ struct udp_sock *us;
+ struct hash *ht;
+ struct mbuf *mb;
+ dtls_conn_h *connh;
+ void *arg;
+ size_t mtu;
+};
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl; /* inheritance */
+#ifdef TLS_BIO_OPAQUE
+ BIO_METHOD *biomet;
+#endif
+ BIO *sbio_out;
+ BIO *sbio_in;
+ struct tmr tmr;
+ struct sa peer;
+ struct le he;
+ struct dtls_sock *sock;
+ dtls_estab_h *estabh;
+ dtls_recv_h *recvh;
+ dtls_close_h *closeh;
+ void *arg;
+ bool active;
+ bool up;
+};
+
+
+static int bio_create(BIO *b)
+{
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->init = 1;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 0);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->ptr = NULL;
+ b->init = 0;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ struct mbuf *mb;
+ enum {SPACE = 4};
+ int err;
+
+ mb = mbuf_alloc(SPACE + len);
+ if (!mb)
+ return -1;
+
+ mb->pos = SPACE;
+ (void)mbuf_write_mem(mb, (void *)buf, len);
+ mb->pos = SPACE;
+
+ err = udp_send_helper(tc->sock->us, &tc->peer, mb, tc->sock->uh);
+
+ mem_deref(mb);
+
+ return err ? -1 : len;
+}
+
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ (void)num;
+ (void)ptr;
+
+ switch (cmd) {
+
+ case BIO_CTRL_FLUSH:
+ /* The OpenSSL library needs this */
+ return 1;
+
+#if defined (BIO_CTRL_DGRAM_QUERY_MTU)
+ case BIO_CTRL_DGRAM_QUERY_MTU:
+ return tc ? tc->sock->mtu : MTU_DEFAULT;
+#endif
+
+#if defined (BIO_CTRL_DGRAM_GET_FALLBACK_MTU)
+ case BIO_CTRL_DGRAM_GET_FALLBACK_MTU:
+ return MTU_FALLBACK;
+#endif
+ }
+
+ return 0;
+}
+
+
+#ifdef TLS_BIO_OPAQUE
+
+static BIO_METHOD *bio_method_udp(void)
+{
+ BIO_METHOD *method;
+
+ method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "udp_send");
+ if (!method) {
+ DEBUG_WARNING("alloc: BIO_meth_new() failed\n");
+ ERR_clear_error();
+ return NULL;
+ }
+
+ BIO_meth_set_write(method, bio_write);
+ BIO_meth_set_ctrl(method, bio_ctrl);
+ BIO_meth_set_create(method, bio_create);
+ BIO_meth_set_destroy(method, bio_destroy);
+
+ return method;
+}
+
+#else
+
+static struct bio_method_st bio_udp_send = {
+ BIO_TYPE_SOURCE_SINK,
+ "udp_send",
+ bio_write,
+ 0,
+ 0,
+ 0,
+ bio_ctrl,
+ bio_create,
+ bio_destroy,
+ 0
+};
+
+#endif
+
+
+static void tls_close(struct tls_conn *tc)
+{
+ int r;
+
+ if (!tc->ssl)
+ return;
+
+ r = SSL_shutdown(tc->ssl);
+ if (r <= 0)
+ ERR_clear_error();
+
+ SSL_free(tc->ssl);
+ tc->ssl = NULL;
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ hash_unlink(&tc->he);
+ tmr_cancel(&tc->tmr);
+ tls_close(tc);
+
+#ifdef TLS_BIO_OPAQUE
+ if (tc->biomet)
+ BIO_meth_free(tc->biomet);
+#endif
+
+ mem_deref(tc->sock);
+}
+
+
+static void conn_close(struct tls_conn *tc, int err)
+{
+ tmr_cancel(&tc->tmr);
+ tls_close(tc);
+ tc->up = false;
+
+ if (tc->closeh)
+ tc->closeh(err, tc->arg);
+}
+
+
+#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined(DTLS_CTRL_GET_TIMEOUT)
+
+static void check_timer(struct tls_conn *tc);
+
+
+static void timeout(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ DEBUG_INFO("timeout\n");
+
+ if (0 <= DTLSv1_handle_timeout(tc->ssl)) {
+
+ check_timer(tc);
+ }
+ else {
+ ERR_clear_error();
+ conn_close(tc, ETIMEDOUT);
+ }
+}
+
+
+static void check_timer(struct tls_conn *tc)
+{
+ struct timeval tv = {0, 0};
+
+ if (1 == DTLSv1_get_timeout(tc->ssl, &tv)) {
+
+ tmr_start(&tc->tmr, tv.tv_sec * 1000 + tv.tv_usec / 1000,
+ timeout, tc);
+ }
+ else {
+ tmr_cancel(&tc->tmr);
+ }
+}
+
+#else
+
+static void check_timer(struct tls_conn *tc)
+{
+ (void)tc;
+}
+
+#endif
+
+
+static int tls_connect(struct tls_conn *tc)
+{
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_connect(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ tls_flush_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("connect error: %i\n", ssl_err);
+ return EPROTO;
+ }
+ }
+
+ check_timer(tc);
+
+ return 0;
+}
+
+
+static int tls_accept(struct tls_conn *tc)
+{
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_accept(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ tls_flush_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("accept error: %i\n", ssl_err);
+ return EPROTO;
+ }
+ }
+
+ check_timer(tc);
+
+ return 0;
+}
+
+
+static void conn_recv(struct tls_conn *tc, struct mbuf *mb)
+{
+ int err, r;
+
+ if (!tc->ssl)
+ return;
+
+ /* feed SSL data to the BIO */
+ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("receive bio write error: %i\n", r);
+ ERR_clear_error();
+ conn_close(tc, ENOMEM);
+ return;
+ }
+
+ if (SSL_state(tc->ssl) != SSL_ST_OK) {
+
+ if (tc->up) {
+ conn_close(tc, EPROTO);
+ return;
+ }
+
+ if (tc->active) {
+ err = tls_connect(tc);
+ }
+ else {
+ err = tls_accept(tc);
+ }
+
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+
+ DEBUG_INFO("%s: state=0x%04x\n",
+ tc->active ? "client" : "server",
+ SSL_state(tc->ssl));
+
+ /* TLS connection is established */
+ if (SSL_state(tc->ssl) != SSL_ST_OK)
+ return;
+
+ tc->up = true;
+
+ if (tc->estabh) {
+ uint32_t nrefs;
+
+ mem_ref(tc);
+
+ tc->estabh(tc->arg);
+
+ nrefs = mem_nrefs(tc);
+ mem_deref(tc);
+
+ /* check if connection was deref'd from handler */
+ if (nrefs == 1)
+ return;
+ }
+ }
+
+ mbuf_set_pos(mb, 0);
+
+ for (;;) {
+ int n;
+
+ if (mbuf_get_space(mb) < 4096) {
+ err = mbuf_resize(mb, mb->size + 8192);
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+ }
+
+ ERR_clear_error();
+
+ n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb));
+ if (n <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, n);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ conn_close(tc, ECONNRESET);
+ return;
+
+ default:
+ DEBUG_WARNING("read error: %i\n", ssl_err);
+ conn_close(tc, EPROTO);
+ return;
+ }
+
+ break;
+ }
+
+ mb->pos += n;
+ }
+
+ mbuf_set_end(mb, mb->pos);
+ mbuf_set_pos(mb, 0);
+
+ if (tc->recvh && mbuf_get_left(mb) > 0)
+ tc->recvh(mb, tc->arg);
+
+ return;
+}
+
+
+static int conn_alloc(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err = 0;
+
+ tc = mem_zalloc(sizeof(*tc), conn_destructor);
+ if (!tc)
+ return ENOMEM;
+
+ hash_append(sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc);
+
+ tc->sock = mem_ref(sock);
+ tc->peer = *peer;
+ tc->estabh = estabh;
+ tc->recvh = recvh;
+ tc->closeh = closeh;
+ tc->arg = arg;
+
+#ifdef TLS_BIO_OPAQUE
+ tc->biomet = bio_method_udp();
+ if (!tc->biomet) {
+ err = ENOMEM;
+ goto out;
+ }
+#endif
+
+ /* Connect the SSL socket */
+ tc->ssl = SSL_new(tls->ctx);
+ if (!tc->ssl) {
+ DEBUG_WARNING("ssl new failed: %i\n",
+ ERR_GET_REASON(ERR_get_error()));
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+ tc->sbio_in = BIO_new(BIO_s_mem());
+ if (!tc->sbio_in) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ tc->sbio_out = BIO_new(tc->biomet);
+#else
+ tc->sbio_out = BIO_new(&bio_udp_send);
+#endif
+ if (!tc->sbio_out) {
+ ERR_clear_error();
+ BIO_free(tc->sbio_in);
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_data(tc->sbio_out, tc);
+#else
+ tc->sbio_out->ptr = tc;
+#endif
+
+ SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out);
+
+ SSL_set_read_ahead(tc->ssl, 1);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * DTLS Connect
+ *
+ * @param ptc Pointer to allocated DTLS connection
+ * @param tls TLS Context
+ * @param sock DTLS Socket
+ * @param peer Peer address
+ * @param estabh Establish handler
+ * @param recvh Receive handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_connect(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err;
+
+ if (!ptc || !tls || !sock || !peer)
+ return EINVAL;
+
+ err = conn_alloc(&tc, tls, sock, peer, estabh, recvh, closeh, arg);
+ if (err)
+ return err;
+
+ tc->active = true;
+
+ err = tls_connect(tc);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * DTLS Accept
+ *
+ * @param ptc Pointer to allocated DTLS connection
+ * @param tls TLS Context
+ * @param sock DTLS Socket
+ * @param estabh Establish handler
+ * @param recvh Receive handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_accept(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err, r;
+
+ if (!ptc || !tls || !sock || !sock->mb)
+ return EINVAL;
+
+ err = conn_alloc(&tc, tls, sock, &sock->peer, estabh, recvh, closeh,
+ arg);
+ if (err)
+ return err;
+
+ tc->active = false;
+
+ r = BIO_write(tc->sbio_in, mbuf_buf(sock->mb),
+ (int)mbuf_get_left(sock->mb));
+ if (r <= 0) {
+ DEBUG_WARNING("accept bio write error: %i\n", r);
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = tls_accept(tc);
+ if (err)
+ goto out;
+
+ sock->mb = mem_deref(sock->mb);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * Send data on a DTLS connection
+ *
+ * @param tc DTLS connection
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_send(struct tls_conn *tc, struct mbuf *mb)
+{
+ int r;
+
+ if (!tc || !mb)
+ return EINVAL;
+
+ if (!tc->up || !tc->ssl)
+ return ENOTCONN;
+
+ ERR_clear_error();
+
+ r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("write error: %i\n", SSL_get_error(tc->ssl, r));
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Set handlers on a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ * @param estabh DTLS Connection Established handler
+ * @param recvh DTLS Connection Receive data handler
+ * @param closeh DTLS Connection Close handler
+ * @param arg Handler argument
+ */
+void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh,
+ dtls_recv_h *recvh, dtls_close_h *closeh, void *arg)
+{
+ if (!tc)
+ return;
+
+ tc->estabh = estabh;
+ tc->recvh = recvh;
+ tc->closeh = closeh;
+ tc->arg = arg;
+}
+
+
+/**
+ * Get the remote peer of a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ *
+ * @return Remote peer
+ */
+const struct sa *dtls_peer(const struct tls_conn *tc)
+{
+ return tc ? &tc->peer : NULL;
+}
+
+
+/**
+ * Set the remote peer of a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ * @param peer Peer address
+ */
+void dtls_set_peer(struct tls_conn *tc, const struct sa *peer)
+{
+ if (!tc || !peer)
+ return;
+
+ hash_unlink(&tc->he);
+ hash_append(tc->sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc);
+
+ tc->peer = *peer;
+}
+
+
+static void sock_destructor(void *arg)
+{
+ struct dtls_sock *sock = arg;
+
+ hash_clear(sock->ht);
+ mem_deref(sock->uh);
+ mem_deref(sock->us);
+ mem_deref(sock->ht);
+ mem_deref(sock->mb);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct tls_conn *tc = le->data;
+
+ return sa_cmp(&tc->peer, arg, SA_ALL);
+}
+
+
+static struct tls_conn *conn_lookup(struct dtls_sock *sock,
+ const struct sa *peer)
+{
+ return list_ledata(hash_lookup(sock->ht, sa_hash(peer, SA_ALL),
+ cmp_handler, (void *)peer));
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct dtls_sock *sock = arg;
+ struct tls_conn *tc;
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mb->buf[mb->pos];
+ if (b < 20 || b > 63)
+ return false;
+
+ tc = conn_lookup(sock, src);
+ if (tc) {
+ conn_recv(tc, mb);
+ return true;
+ }
+
+ if (sock->connh) {
+
+ mem_deref(sock->mb);
+ sock->mb = mem_ref(mb);
+ sock->peer = *src;
+
+ sock->connh(src, sock->arg);
+ }
+
+ return true;
+}
+
+
+/**
+ * Create DTLS Socket
+ *
+ * @param sockp Pointer to returned DTLS Socket
+ * @param laddr Local listen address (optional)
+ * @param us External UDP socket (optional)
+ * @param htsize Connection hash table size
+ * @param layer UDP protocol layer
+ * @param connh Connect handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr,
+ struct udp_sock *us, uint32_t htsize, int layer,
+ dtls_conn_h *connh, void *arg)
+{
+ struct dtls_sock *sock;
+ int err;
+
+ if (!sockp)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), sock_destructor);
+ if (!sock)
+ return ENOMEM;
+
+ if (us) {
+ sock->us = mem_ref(us);
+ }
+ else {
+ err = udp_listen(&sock->us, laddr, NULL, NULL);
+ if (err)
+ goto out;
+ }
+
+ err = udp_register_helper(&sock->uh, sock->us, layer,
+ NULL, recv_handler, sock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht, hash_valid_size(htsize));
+ if (err)
+ goto out;
+
+ sock->mtu = MTU_DEFAULT;
+ sock->connh = connh;
+ sock->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
+
+
+/**
+ * Get the underlying UDP socket of a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ *
+ * @return UDP Socket
+ */
+struct udp_sock *dtls_udp_sock(struct dtls_sock *sock)
+{
+ return sock ? sock->us : NULL;
+}
+
+
+/**
+ * Set MTU on a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ * @param mtu MTU value
+ */
+void dtls_set_mtu(struct dtls_sock *sock, size_t mtu)
+{
+ if (!sock)
+ return;
+
+ sock->mtu = mtu;
+}
+
+
+void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src,
+ struct mbuf *mb)
+{
+ struct sa addr;
+
+ if (!sock || !src || !mb)
+ return;
+
+ addr = *src;
+
+ recv_handler(&addr, mb, sock);
+}