[svn-commits] russell: branch russell/dtls r206080 - in /team/russell/dtls: configs/ includ...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Sat Jul 11 23:13:16 CDT 2009


Author: russell
Date: Sat Jul 11 23:13:13 2009
New Revision: 206080

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=206080
Log:
Add what I have so far for a DTLS API for Asterisk, and a test module for it.

The test module works.  It successfully makes a connection over DTLS,
sends a message, and verifies that it receives it on the other end.

Added:
    team/russell/dtls/configs/test_dtls.conf.sample   (with props)
    team/russell/dtls/include/asterisk/dtls.h   (with props)
    team/russell/dtls/main/dtls.c   (with props)
    team/russell/dtls/tests/test_dtls.c   (with props)

Added: team/russell/dtls/configs/test_dtls.conf.sample
URL: http://svn.asterisk.org/svn-view/asterisk/team/russell/dtls/configs/test_dtls.conf.sample?view=auto&rev=206080
==============================================================================
--- team/russell/dtls/configs/test_dtls.conf.sample (added)
+++ team/russell/dtls/configs/test_dtls.conf.sample Sat Jul 11 23:13:13 2009
@@ -1,0 +1,38 @@
+;
+; test_dtls - DTLS Functionality Testing
+;
+; Use absolute paths for filenames.
+;
+
+[general]
+
+;
+; Enable DTLS.  (Required)
+; Changing this to no would be quite silly for this module.
+;
+dtlsenable = yes
+
+;
+; Certificate Authority (CA) certificate (optional)
+;
+;dtlscafile = /var/lib/asterisk/keys/mycacert.pem
+
+;
+; Path to directory of CA certs (optional)
+;
+;dtlscapath = /var/lib/asterisk/ca
+
+;
+; Server private key (required)
+;
+;dtlsprivatekey = /var/lib/asterisk/keys/mycert.key
+
+;
+; Server certificate (required)
+;
+;dtlscertfile = /var/lib/asterisk/keys/mycert.pem
+
+;
+; DH Params (required)
+;
+;dtlsdhfile = /var/lib/asterisk/keys/dh.pem

Propchange: team/russell/dtls/configs/test_dtls.conf.sample
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/russell/dtls/configs/test_dtls.conf.sample
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/russell/dtls/configs/test_dtls.conf.sample
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: team/russell/dtls/include/asterisk/dtls.h
URL: http://svn.asterisk.org/svn-view/asterisk/team/russell/dtls/include/asterisk/dtls.h?view=auto&rev=206080
==============================================================================
--- team/russell/dtls/include/asterisk/dtls.h (added)
+++ team/russell/dtls/include/asterisk/dtls.h Sat Jul 11 23:13:13 2009
@@ -1,0 +1,175 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2009, Digium, Inc.
+ *
+ * Russell Bryant <russell at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief DTLS Support
+ * \author Russell Bryant <russell at digium.com>
+ */
+
+#ifndef __AST_DTLS_H__
+#define __AST_DTLS_H__
+
+#include "asterisk/network.h"
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/*! DTLS Configuration Handling @{ ********************************************/
+
+/*!
+ * \brief DTLS configuration
+ *
+ * An opaque type.
+ */
+struct ast_dtls_config;
+
+/*!
+ * \brief Allocate a DTLS configuration holder
+ *
+ * \retval non-NULL a pointer to the allocated configuration holder
+ * \retval NULL failure
+ */
+struct ast_dtls_config *ast_dtls_config_alloc(void);
+
+/*!
+ * \brief Increase ast_dtls_config reference count
+ *
+ * \param c the DTLS config
+ *
+ * \return a pointer to the ast_dtls_config
+ */
+#define ast_dtls_config_ref(c) ({ ao2_ref(c, +1); (c); })
+
+/*!
+ * \brief Decrease ast_dtls_config reference count
+ *
+ * \param c the DTLS config
+ *
+ * \return NULL, for conveniently clearing out now invalid pointers
+ */
+#define ast_dtls_config_unref(c) ({ ao2_ref(c, -1); (NULL); })
+
+unsigned int ast_dtls_config_get_enabled(const struct ast_dtls_config *config);
+
+/*!
+ * \brief Parse a DTLS configuration item.
+ *
+ * \param config DTLS configuration
+ * \param var option to set
+ * \param val value to set
+ *
+ * This is the list of items that are supported:
+ *  - dtlsbindaddr - Syntax: 0.0.0.0[:1234]
+ *  - dtlsbindport
+ *  - dtlsenable - yes/no, default yes
+ *  - dtlscafile
+ *  - dtlscapath
+ *  - dtlscertfile
+ *  - dtlsprivatekey
+ *  - dtlsdhfile
+ *
+ * \note To set a default port for a DTLS service, do something like this
+ *       before parsing user supplied configuration:
+ *       \code 
+ *             ast_dtls_config_parse(config, "dtlsbindport", "1234");
+ *       \endcode
+ *
+ * \retval 0 success
+ * \retval non-zero failure because of unsupported option or invalid format.
+ */
+int ast_dtls_config_parse(struct ast_dtls_config *config,
+		const char *var, const char *val);
+
+/*! @} ************************************************************************/
+
+/*! DTLS Connection Handling @{ ***********************************************/
+
+/*!
+ * \brief a DTLS connection handler
+ *
+ * This is an opaque type.  All of the magic is hidden from you.
+ */
+struct ast_dtls_handler;
+
+/*!
+ * \brief The type for the callback used to receive a packet
+ *
+ * \param sin_remote The address that the packet arrived from
+ * \param buf the packet data
+ * \param len the number of bytes in buf
+ * \param user_data user provided data from ast_dtls_handler_alloc()
+ *
+ * \return nothing.
+ */
+typedef void (*ast_dtls_recv_cb)(const struct sockaddr_in *sin_remote, const void *buf,
+		size_t len, void *user_data);
+
+/*!
+ * \brief Allocate a DTLS connection handler.
+ *
+ * \param config DTLS configuration
+ * \param recv_cb The callback to call when an application layer packet arrives.
+ * \param recv_user_data data to pass to the callback when a packet arrives
+ *
+ * \retval non-NULL a pointer to a DTLS connection handler
+ * \retval NULL error, a memory allocation error, or from invalid configuration
+ */
+struct ast_dtls_handler *ast_dtls_handler_alloc(struct ast_dtls_config *config,
+		ast_dtls_recv_cb recv_cb, void *recv_user_data);
+
+/*!
+ * \brief Send an application layer packet over DTLS
+ *
+ * \param handler the DTLS connection handler
+ * \param buf a buffer holding the data to send
+ * \param len the number of bytes in buf to send
+ * \param sin_remote the address and port number to send to
+ *
+ * \retval 0 success
+ * \retval -1 error
+ */
+int ast_dtls_send(struct ast_dtls_handler *handler,
+		const void *buf, size_t len, struct sockaddr_in *sin_remote);
+
+/*!
+ * \brief Increase ast_dtls_handler reference count
+ *
+ * \param h the DTLS connection handler
+ *
+ * \return a pointer to the ast_dtls_
+ */
+#define ast_dtls_handler_ref(h) ({ ao2_ref(h, +1); (h); })
+
+/*!
+ * \brief Decrease ast_dtls_handler reference count
+ *
+ * \param h the DTLS connection handler
+ *
+ * \return NULL, for conveniently clearing out now invalid pointers
+ */
+#define ast_dtls_handler_unref(h) ({ ao2_ref(h, -1); (NULL); })
+
+/*! @} ************************************************************************/
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __AST_DTLS_H__ */

Propchange: team/russell/dtls/include/asterisk/dtls.h
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/russell/dtls/include/asterisk/dtls.h
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/russell/dtls/include/asterisk/dtls.h
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: team/russell/dtls/main/dtls.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/russell/dtls/main/dtls.c?view=auto&rev=206080
==============================================================================
--- team/russell/dtls/main/dtls.c (added)
+++ team/russell/dtls/main/dtls.c Sat Jul 11 23:13:13 2009
@@ -1,0 +1,923 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2009, Digium, Inc.
+ *
+ * Russell Bryant <russell at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief DTLS Support via OpenSSL
+ *
+ * \author Russell Bryant <russell at digium.com>
+ *
+ * \ref DTLSsupport
+ *
+ * \extref http://www.openssl.org/
+ */
+
+/*!
+ * \page DTLSsupport DTLS Support
+ *
+ * \section DTLStodo TODO
+ *
+ * \todo Figure out how to handle timing out connections.
+ *
+ * \section DTLSlinks Useful Links
+ *
+ * The DTLS 1.0 RFC:
+ *  - http://www.rfc-editor.org/rfc/rfc4347.txt
+ *
+ * The Design and Implementation of DTLS:
+ *  - http://crypto.stanford.edu/~nagendra/papers/dtls.pdf
+ *
+ * Very useful implementation notes for a DTLS server serving multiple clients:
+ *  - http://www.net-snmp.org/wiki/index.php/DTLS_Implementation_Notes
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#ifdef HAVE_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/crypto.h>
+
+#include "asterisk/_private.h" /* ast_ssl_init() */
+
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/config.h"
+#include "asterisk/io.h"
+#include "asterisk/netsock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/dtls.h"
+
+/*!
+ * \brief DTLS service configuration
+ *
+ * \note I suppose this could use stringfields.  I haven't done it yet since the
+ *       benefits will be minor since there will be very few instances of this
+ *       object.  It's not like "sip_peer" or something where there may be
+ *       thousands.
+ */
+struct ast_dtls_config {
+	char bind_addr[32];
+	uint16_t bind_port;
+	char ca_cert[PATH_MAX];
+	char ca_path[PATH_MAX];
+	char private_key[PATH_MAX];
+	char server_cert[PATH_MAX];
+	char dh_params[PATH_MAX];
+	unsigned char enabled;
+};
+
+struct dtls_frame {
+	AST_LIST_ENTRY(dtls_frame) entry;
+	struct dtls_connection *conn;
+	size_t len;
+	unsigned char data[0];
+};
+
+enum dtls_alert {
+	/*! \brief There is a new frame to be sent out */
+	DTLS_ALERT_FRAME,
+	/*! \brief A request to stop the handler thread */
+	DTLS_ALERT_STOP,
+};
+
+/*!
+ * \note LOCKING ORDER: If you must lock a connection and the handler
+ *       at the same time, it's 1) connection, 2) handler.
+ */
+struct ast_dtls_handler {
+	pthread_t thread;
+	struct ast_dtls_config *config;
+	SSL_CTX *server_ctx;
+	SSL_CTX *client_ctx;
+	struct ao2_container *connections;
+	struct ast_netsock_list *netsock_list;
+	struct ast_netsock *netsock;
+	int alert_pipe[2];
+	struct io_context *io;
+	ast_dtls_recv_cb recv_cb;
+	void *recv_user_data;
+	AST_LIST_HEAD_NOLOCK(, dtls_frame) frame_q;
+	/*! \brief Set to non-zero when stopping thread */
+	unsigned char stop;
+};
+
+enum dtls_connection_state {
+	DTLS_CONN_STATE_CHILLIN,
+	DTLS_CONN_STATE_WANT_READ,
+};
+
+/*!
+ * \note LOCKING ORDER: If you must lock a connection and the handler
+ *       at the same time, it's 1) connection, 2) handler.
+ */
+struct dtls_connection {
+	enum dtls_connection_state state;
+	struct sockaddr_in sin_remote;
+	SSL *ssl;
+	BIO *bio_wr;
+	BIO *bio_rd;
+	AST_LIST_HEAD_NOLOCK(, dtls_frame) frame_q;
+};
+
+#ifdef LOW_MEMORY
+static const int NUM_CONN_BUCKETS = 61;
+#else
+static const int NUM_CONN_BUCKETS = 1567;
+#endif
+
+static const size_t UDP_MAX = 65535;
+
+AST_THREADSTORAGE(bio_buf);
+
+#define dtls_connection_ref(c) ({ ao2_ref(c, +1); (c); })
+
+#define dtls_connection_unref(c) ({ ao2_ref(c, -1); (NULL); })
+
+static int conn_hash(const void *obj, const int flags)
+{
+	const struct dtls_connection *conn = obj;
+
+	return conn->sin_remote.sin_addr.s_addr ^ conn->sin_remote.sin_port;
+}
+
+static int conn_cmp(void *obj, void *arg, int flags)
+{
+	const struct dtls_connection *conn1 = obj;
+	const struct dtls_connection *conn2 = arg;
+
+	return !inaddrcmp(&conn1->sin_remote, &conn2->sin_remote) ?
+				CMP_MATCH | CMP_STOP : 0;
+}
+
+static void conn_destructor(void *obj)
+{
+	struct dtls_connection *conn = obj;
+
+	ast_debug(3, "Destroying DTLS connection for %s:%u\n",
+			ast_inet_ntoa(conn->sin_remote.sin_addr),
+			ntohs(conn->sin_remote.sin_port));
+
+	/* If conn->ssl is non-NULL, the bios are connected and will be
+	 * freed by SSL_free() */
+
+	if (conn->ssl) {
+		SSL_free(conn->ssl);
+		conn->ssl = NULL;
+		return;
+	}
+
+	if (conn->bio_rd) {
+		BIO_free(conn->bio_rd);
+		conn->bio_rd = NULL;
+	}
+
+	if (conn->bio_wr) {
+		BIO_free(conn->bio_wr);
+		conn->bio_wr = NULL;
+	}
+}
+
+enum direction {
+	SEND,
+	RECV
+};
+
+static struct dtls_connection *dtls_find_connection(struct ast_dtls_handler *handler,
+				const struct sockaddr_in *sin, enum direction dir)
+{
+	struct dtls_connection *conn;
+	struct dtls_connection tmp_conn = {
+		.ssl = NULL,
+	};
+
+	tmp_conn.sin_remote = *sin;
+
+	if ((conn = ao2_find(handler->connections, &tmp_conn, OBJ_POINTER))) {
+		ast_debug(3, "Found existing connection for %s:%u\n",
+				ast_inet_ntoa(sin->sin_addr),
+				ntohs(sin->sin_port));
+		return conn;
+	}
+
+	ast_debug(3, "Allocating new connection for %s:%u\n",
+			ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+
+	if (!(conn = ao2_alloc(sizeof(*conn), conn_destructor))) {
+		return NULL;
+	}
+
+	conn->state = DTLS_CONN_STATE_CHILLIN;
+
+	conn->sin_remote = *sin;
+
+	if (!(conn->bio_rd = BIO_new(BIO_s_mem()))) {
+		goto return_error;
+	}
+
+	if (!(conn->bio_wr = BIO_new(BIO_s_mem()))) {
+		goto return_error;
+	}
+
+	BIO_set_mem_eof_return(conn->bio_rd, -1);
+	BIO_set_mem_eof_return(conn->bio_wr, -1);
+
+	if (!(conn->ssl = SSL_new((dir == SEND) ?
+				handler->client_ctx : handler->server_ctx))) {
+		goto return_error;
+	}
+
+	if (dir == SEND) {
+		SSL_set_connect_state(conn->ssl);
+	} else {
+		SSL_set_accept_state(conn->ssl);
+	}
+
+	SSL_set_bio(conn->ssl, conn->bio_rd, conn->bio_wr);
+
+	ao2_link(handler->connections, conn);
+
+	return conn;
+
+return_error:
+	conn = dtls_connection_unref(conn);
+
+	return NULL;
+}
+
+static int dtls_handler_write_alert(struct ast_dtls_handler *handler,
+		enum dtls_alert alert)
+{
+	if (write(handler->alert_pipe[1], &alert, sizeof(alert)) == -1) {
+		ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n",
+				strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct dtls_frame *dtls_frame_destroy(struct dtls_frame *dtls_fr)
+{
+	ast_free(dtls_fr);
+
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Queue a frame to a DTLS handler
+ *
+ * \pre handler is locked
+ * \post handler is still locked
+ */
+static void dtls_handler_queue_frame(struct ast_dtls_handler *handler,
+		struct dtls_frame *dtls_fr)
+{
+	AST_LIST_INSERT_TAIL(&handler->frame_q, dtls_fr, entry);
+	dtls_handler_write_alert(handler, DTLS_ALERT_FRAME);
+}
+
+int ast_dtls_send(struct ast_dtls_handler *handler,
+		const void *buf, size_t len, struct sockaddr_in *sin_remote)
+{
+	struct dtls_connection *conn;
+	struct dtls_frame *dtls_fr;
+	int res = 0;
+
+	if (!(conn = dtls_find_connection(handler, sin_remote, SEND))) {
+		return -1;
+	}
+
+	if (!(dtls_fr = ast_calloc(1, sizeof(*dtls_fr) + len))) {
+		res = -1;
+		goto return_unref;
+	}
+
+	dtls_fr->len = len;
+	memcpy(dtls_fr->data, buf, len);
+
+	dtls_fr->conn = conn;
+
+	ao2_lock(conn);
+
+	switch (conn->state) {
+	case DTLS_CONN_STATE_CHILLIN:
+		ao2_lock(handler);
+		dtls_handler_queue_frame(handler, dtls_fr);
+		ao2_unlock(handler);
+		break;
+	case DTLS_CONN_STATE_WANT_READ:
+		AST_LIST_INSERT_TAIL(&conn->frame_q, dtls_fr, entry);
+		break;
+	}
+
+	ao2_unlock(conn);
+
+return_unref:
+	conn = dtls_connection_unref(conn);
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Set connection state to CHILLIN
+ *
+ * \param conn the DTLS connection
+ *
+ * \pre conn is locked
+ * \post conn is still locked
+ * \post Frames from the connection frame queue have now been queued up
+ *       to the handler for transmission.
+ */
+static void dtls_connection_set_chillin(struct ast_dtls_handler *handler,
+		struct dtls_connection *conn)
+{
+	struct dtls_frame *dtls_fr;
+
+	ao2_lock(handler);
+
+	while ((dtls_fr = AST_LIST_REMOVE_HEAD(&conn->frame_q, entry))) {
+		dtls_handler_queue_frame(handler, dtls_fr);
+	}
+
+	ao2_unlock(handler);
+
+	conn->state = DTLS_CONN_STATE_CHILLIN;
+}
+
+/*!
+ * \internal
+ * \brief Handle pending writes from OpenSSL
+ *
+ * \param conn the associated connection
+ *
+ * \pre conn is locked
+ * \post conn is still locked
+ */
+static void dtls_handle_pending_writes(struct ast_dtls_handler *handler,
+		struct dtls_connection *conn)
+{
+	unsigned char *enc_buf;
+	int sockfd;
+
+	if (!(enc_buf = ast_threadstorage_get(&bio_buf, UDP_MAX))) {
+		ast_log(LOG_ERROR, "Failed to get thread local packet buffer\n");
+		return;
+	}
+
+	sockfd = ast_netsock_sockfd(handler->netsock);
+
+	while (BIO_ctrl_pending(conn->bio_wr) > 0) {
+		int res = BIO_read(conn->bio_wr, enc_buf, UDP_MAX);
+
+		if (res <= 0) {
+			break;
+		}
+
+		ast_debug(3, "About to sendto() '%d' bytes on sockfd '%d' to %s:%hu\n",
+				res, sockfd,
+				ast_inet_ntoa(conn->sin_remote.sin_addr),
+				htons(conn->sin_remote.sin_port));
+
+		res = sendto(sockfd, enc_buf, res, 0,
+				&conn->sin_remote, sizeof(conn->sin_remote));
+		if (res < 0) {
+			ast_log(LOG_WARNING, "sendto() failed: %s\n", strerror(errno));
+			break;
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief Move a connection into WANT_READ state
+ *
+ * \param handler the DTLS handler
+ * \param conn the DTLS connection
+ * \param dtls_fr an optional frame to put on the tail of the connection
+ *        frame queue
+ *
+ * \pre conn is locked.
+ *
+ * \post conn is still locked.
+ * \post All frames for this connection have been moved from the handler
+ *       frame queue back to the connection frame queue.
+ *
+ * \return nothing
+ */
+static void dtls_connection_want_read(struct ast_dtls_handler *handler,
+		struct dtls_connection *conn, struct dtls_frame *dtls_fr)
+{
+	struct dtls_frame *cur;
+
+	if (!AST_LIST_EMPTY(&conn->frame_q)) {
+		ast_log(LOG_ERROR, "The connection frame queue is supposed "
+				"to be empty when moving to WANT_READ state.\n");
+	}
+
+	if (dtls_fr) {
+		AST_LIST_INSERT_TAIL(&conn->frame_q, dtls_fr, entry);
+		ast_debug(3, "Moved frame to connection frame q\n");
+	}
+
+	ao2_lock(handler);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&handler->frame_q, cur, entry) {
+		if (cur->conn != conn) {
+			continue;
+		}
+
+		AST_LIST_REMOVE_CURRENT(entry);
+		AST_LIST_INSERT_TAIL(&conn->frame_q, cur, entry);
+		ast_debug(3, "Moved frame from handler frame q to "
+				"connection frame q\n");
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	ao2_unlock(handler);
+
+	conn->state = DTLS_CONN_STATE_WANT_READ;
+
+	ast_debug(3, "Connection state now WANT_READ\n");
+}
+
+/*!
+ * \internal
+ * \brief Handle frame from the network
+ *
+ * \pre conn is locked
+ * \post conn is still locked
+ */
+static void dtls_connection_incoming(struct ast_dtls_handler *handler,
+		struct dtls_connection *conn, unsigned char *enc_buf, size_t len)
+{
+	int res;
+	int try_again;
+	unsigned char *buf;
+
+	if ((res = BIO_write(conn->bio_rd, enc_buf, len)) != res) {
+		ast_log(LOG_WARNING, "Needed to write '%d' bytes into the BIO, "
+				"but only wrote '%d'\n", len, res);
+		return;
+	}
+
+	if (!(buf = ast_threadstorage_get(&bio_buf, UDP_MAX))) {
+		ast_log(LOG_ERROR, "Failed to get thread local packet buffer\n");
+		return;
+	}
+
+	do {
+		int ssl_res;
+
+		try_again = 0;
+
+		ssl_res = SSL_read(conn->ssl, buf, res);
+		if (ssl_res <= 0) {
+			int ssl_err = SSL_get_error(conn->ssl, res);
+			switch (ssl_err) {
+			case SSL_ERROR_NONE:
+				ast_debug(3, "SSL_read() says no error\n");
+				dtls_connection_set_chillin(handler, conn);
+				break;
+			case SSL_ERROR_ZERO_RETURN:
+				ast_debug(3, "SSL_read() says connection shut down\n");
+				break;
+			case SSL_ERROR_WANT_WRITE:
+				ast_debug(3, "SSL_read() wants write to occur.\n");
+				dtls_handle_pending_writes(handler, conn);
+				try_again = 1;
+				break;
+			case SSL_ERROR_WANT_READ:
+				ast_debug(3, "SSL_read() wants read to occur.\n");
+				dtls_connection_want_read(handler, conn, NULL);
+				break;
+			case SSL_ERROR_SYSCALL:
+				ast_log(LOG_WARNING, "SSL_read() reported syscall "
+						"error: %s\n", strerror(errno));
+				break;
+			default:
+				ast_log(LOG_WARNING, "SSL_read() returned '%d', "
+						"ssl_err: '%d'\n", ssl_res, ssl_err);
+				break;
+			}
+		} else { /* ssl_res > 0 */
+			/* This is an application level packet. */
+			ast_debug(3, "got app level data\n");
+			ao2_lock(handler);
+			handler->recv_cb(&conn->sin_remote, buf, ssl_res, handler->recv_user_data);;
+			ao2_unlock(handler);
+			dtls_connection_set_chillin(handler, conn);
+		}
+	} while (try_again);
+
+	dtls_handle_pending_writes(handler, conn);
+}
+
+static int dtls_socket_read(int *id, int fd, short events, void *data)
+{
+	struct ast_netsock *netsock = data;
+	struct ast_dtls_handler *handler;
+	struct dtls_connection *conn;
+	unsigned char *enc_buf;
+	struct sockaddr_in sin_remote = {
+		.sin_family = AF_INET,
+	};
+	socklen_t addr_len = sizeof(sin_remote);
+	int res;
+
+	handler = ast_netsock_data(netsock);
+
+	if (!(enc_buf = ast_threadstorage_get(&bio_buf, UDP_MAX))) {
+		ast_log(LOG_ERROR, "Failed to get thread local packet buffer\n");
+		return 1;
+	}
+
+	res = recvfrom(fd, enc_buf, UDP_MAX, 0, &sin_remote, &addr_len);
+	if (res < 0) {
+		ast_log(LOG_WARNING, "recvfrom() failed: %s\n", strerror(errno));
+		return 1;
+	}
+
+	ast_debug(3, "Received '%d' bytes from %s:%hu on sockfd '%d'\n", res,
+			ast_inet_ntoa(sin_remote.sin_addr),
+			ntohs(sin_remote.sin_port), fd);
+
+	if (!(conn = dtls_find_connection(handler, &sin_remote, RECV))) {
+		ast_log(LOG_ERROR, "Failed to find or allocate DTLS connection\n");
+		return -1;
+	}
+
+	ao2_lock(conn);
+	dtls_connection_incoming(handler, conn, enc_buf, res);
+	ao2_unlock(conn);
+
+	conn = dtls_connection_unref(conn);
+
+	/* Return non-zero to keep polling on this fd. */
+	return 1;
+}
+
+static void dtls_handler_dequeue_frame(struct ast_dtls_handler *handler)
+{
+	struct dtls_connection *conn;
+	struct dtls_frame *dtls_fr;
+	int try_again;
+
+	ao2_lock(handler);
+	if (!(dtls_fr = AST_LIST_REMOVE_HEAD(&handler->frame_q, entry))) {
+		ao2_unlock(handler);
+		ast_log(LOG_WARNING, "Alert pipe said ALERT_FRAME, "
+				"but frame queue empty\n");
+		return;
+	}
+	ao2_unlock(handler);
+
+	conn = dtls_connection_ref(dtls_fr->conn);
+
+	ao2_lock(conn);
+
+	do {
+		int res;
+
+		try_again = 0;
+
+		res = SSL_write(conn->ssl, dtls_fr->data, dtls_fr->len);
+		if (res <= 0) {
+			int ssl_res = SSL_get_error(conn->ssl, res);
+			switch (ssl_res) {
+			case SSL_ERROR_NONE:
+				ast_debug(3, "SSL_write() says no error\n");
+				break;
+			case SSL_ERROR_ZERO_RETURN:
+				ast_debug(3, "SSL_write() says connection shut down\n");
+				break;
+			case SSL_ERROR_WANT_WRITE:
+				ast_debug(3, "SSL_write() wants write to occur.\n");
+				dtls_handle_pending_writes(handler, conn);
+				try_again = 1;
+				break;
+			case SSL_ERROR_WANT_READ:
+				ast_debug(3, "SSL_write() wants read to occur.\n");
+				dtls_connection_want_read(handler, conn, dtls_fr);
+				dtls_fr = NULL;
+				break;
+			case SSL_ERROR_SYSCALL:
+				ast_log(LOG_WARNING, "SSL_write() reported syscall "
+						"error: %s\n", strerror(errno));
+				break;
+			default:
+				ast_log(LOG_WARNING, "SSL_write() returned '%d', "
+						"ssl_res: '%d'\n", res, ssl_res);
+				break;
+			}
+		}
+	} while (try_again);
+
+	dtls_handle_pending_writes(handler, conn);
+
+	if (dtls_fr) {
+		dtls_fr = dtls_frame_destroy(dtls_fr);
+	}
+
+	ao2_unlock(conn);
+
+	conn = dtls_connection_unref(conn);
+}
+
+static int dtls_alert_pipe_read(int *id, int fd, short events, void *data)
+{
+	struct ast_dtls_handler *handler = data;
+	enum dtls_alert alert;
+
+	if (read(fd, &alert, sizeof(alert)) == -1) {
+		ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
+		return 1;
+	}
+
+	if (alert == DTLS_ALERT_STOP) {
+		return 1;
+	}
+
+	if (alert != DTLS_ALERT_FRAME) {
+		ast_log(LOG_ERROR, "Unknown DTLS alert '%d'\n", alert);
+		return 1;
+	}
+
+	dtls_handler_dequeue_frame(handler);
+
+	/* Return non-zero to keep polling on this fd. */
+	return 1;
+}
+
+static void *dtls_handler_thread(void *data)
+{
+	struct ast_dtls_handler *handler = data;
+
+	while (!handler->stop) {
+		ast_io_wait(handler->io, -1);
+	}
+
+	return NULL;
+}
+
+static SSL_CTX *dtls_server_ssl_ctx_init(struct ast_dtls_config *config)
+{
+	SSL_CTX *server_ctx;
+
+	if (!(server_ctx = SSL_CTX_new(DTLSv1_server_method()))) {
+		return NULL;
+	}
+
+	if (!SSL_CTX_use_certificate_chain_file(server_ctx, config->server_cert)) {
+		ast_log(LOG_WARNING, "Failed to use '%s' as server cert\n",
+				config->server_cert);
+		goto return_error;
+	}
+
+	if (!SSL_CTX_use_PrivateKey_file(server_ctx, config->private_key, SSL_FILETYPE_PEM)) {
+		ast_log(LOG_WARNING, "Failed to use '%s' as private key\n",
+				config->private_key);
+		goto return_error;
+	}
+
+	if (!SSL_CTX_check_private_key(server_ctx)) {
+		ast_log(LOG_WARNING, "server cert and private key do not match\n");
+		goto return_error;
+	}
+
+	if (!SSL_CTX_load_verify_locations(server_ctx, S_OR(config->ca_cert, NULL),
+				S_OR(config->ca_path, NULL))) {
+		ast_log(LOG_WARNING, "problem loading CA certs - "
+				"cafile:'%s' capath:'%s' - %s\n",
+				config->ca_cert, config->ca_path,
+				ERR_reason_error_string(ERR_get_error()));
+		goto return_error;
+	}
+
+	SSL_CTX_set_verify(server_ctx, SSL_VERIFY_NONE, NULL);
+
+	SSL_CTX_set_read_ahead(server_ctx, 1);
+
+	return server_ctx;
+
+return_error:
+	SSL_CTX_free(server_ctx);
+	return NULL;
+}
+
+static SSL_CTX *dtls_client_ssl_ctx_init(struct ast_dtls_config *config)
+{
+	SSL_CTX *client_ctx;
+
+	if (!(client_ctx = SSL_CTX_new(DTLSv1_client_method()))) {
+		return NULL;
+	}
+
+	if (!SSL_CTX_load_verify_locations(client_ctx, S_OR(config->ca_cert, NULL),
+				S_OR(config->ca_path, NULL))) {
+		ast_log(LOG_WARNING, "problem loading CA certs - "
+				"cafile:'%s' capath:'%s' - %s\n",
+				config->ca_cert, config->ca_path,
+				ERR_reason_error_string(ERR_get_error()));
+		goto return_error;
+	}
+
+	SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE, NULL);
+
+	SSL_CTX_set_read_ahead(client_ctx, 1);
+
+	return client_ctx;
+
+return_error:
+	SSL_CTX_free(client_ctx);
+	return NULL;
+}
+
+static void handler_destructor(void *obj)
+{
+	struct ast_dtls_handler *handler = obj;
+
+	if (handler->thread != AST_PTHREADT_NULL) {
+		handler->stop = 1;
+		dtls_handler_write_alert(handler, DTLS_ALERT_STOP);
+		pthread_join(handler->thread, NULL);
+	}
+
+	if (handler->config) {
+		handler->config = ast_dtls_config_unref(handler->config);
+	}
+
+	if (handler->connections) {
+		ao2_ref(handler->connections, -1);
+		handler->connections = NULL;
+	}
+
+	if (handler->server_ctx) {
+		SSL_CTX_free(handler->server_ctx);
+		handler->server_ctx = NULL;
+	}
+
+	if (handler->client_ctx) {
+		SSL_CTX_free(handler->client_ctx);
+		handler->client_ctx = NULL;
+	}
+
+	if (handler->alert_pipe[0] > -1) {
+		close(handler->alert_pipe[0]);
+		handler->alert_pipe[0] = -1;
+	}
+
+	if (handler->alert_pipe[1] > -1) {
+		close(handler->alert_pipe[1]);
+		handler->alert_pipe[1] = -1;
+	}
+
+	if (handler->io) {
+		io_context_destroy(handler->io);
+		handler->io = NULL;
+	}
+
+	if (handler->netsock_list) {
+		ast_netsock_release(handler->netsock_list);
+		handler->netsock_list = NULL;
+	}
+}
+
+struct ast_dtls_handler *ast_dtls_handler_alloc(struct ast_dtls_config *config,
+		ast_dtls_recv_cb recv_cb, void *recv_user_data)
+{
+	struct ast_dtls_handler *handler;
+
+	if (!config->enabled) {
+		return NULL;
+	}
+
+	if (!(handler = ao2_alloc(sizeof(*handler), handler_destructor))) {
+		return NULL;
+	}
+
+	handler->thread = AST_PTHREADT_NULL;
+	handler->alert_pipe[0] = -1;
+	handler->alert_pipe[1] = -1;
+
+	handler->config = ast_dtls_config_ref(config);
+
+	if (!(handler->server_ctx = dtls_server_ssl_ctx_init(config))) {
+		goto return_unref;
+	}
+
+	if (!(handler->client_ctx = dtls_client_ssl_ctx_init(config))) {
+		goto return_unref;
+	}
+
+	if (!(handler->connections = ao2_container_alloc(NUM_CONN_BUCKETS,
+					conn_hash, conn_cmp))) {
+		goto return_unref;
+	}
+
+	if (!(handler->netsock_list = ast_netsock_list_alloc())) {
+		goto return_unref;
+	}
+
+	if (ast_netsock_init(handler->netsock_list)) {
+		goto return_unref;
+	}
+
+	if (!(handler->io = io_context_create())) {
+		goto return_unref;
+	}
+
+	if (!(handler->netsock = ast_netsock_bind(handler->netsock_list,
+					handler->io, config->bind_addr,
+					config->bind_port, 0, 0,
+					dtls_socket_read, handler))) {
+		ast_log(LOG_WARNING, "Failed to open DTLS socket, "
+				"check DTLS configuration values.\n");
+		goto return_unref;
+	}
+
+	if (pipe(handler->alert_pipe) == -1) {
+		ast_log(LOG_ERROR, "pipe() failed: %s\n", strerror(errno));
+		goto return_unref;
+	}
+
+	if (!ast_io_add(handler->io, handler->alert_pipe[0],
+				dtls_alert_pipe_read, POLLIN, handler)) {
+		ast_log(LOG_ERROR, "Failed to add alert pipe to I/O context\n");
+		goto return_unref;
+	}
+
+	handler->recv_cb = recv_cb;
+	handler->recv_user_data = recv_user_data;
+
+	if (ast_pthread_create(&handler->thread, NULL,
+				dtls_handler_thread, handler)) {
+		ast_log(LOG_WARNING, "Failed to create DTLS handler thread\n");
+		goto return_unref;
+	}
+
+	return handler;
+
+return_unref:
+	handler = ast_dtls_handler_unref(handler);
+
+	return NULL;
+}
+
+static void config_destructor(void *obj)
+{
+	/* Well, that was easy. */
+}
+
+struct ast_dtls_config *ast_dtls_config_alloc(void)
+{
+	return ao2_alloc(sizeof(struct ast_dtls_config), config_destructor);
+}
+
+unsigned int ast_dtls_config_get_enabled(const struct ast_dtls_config *config)
+{
+	return config->enabled;
+}
+
+int ast_dtls_config_parse(struct ast_dtls_config *config,
+			const char *var, const char *val)
+{
+	int res = 0;
+
+	CV_START(var, val);
+
+	CV_STR("dtlsbindaddr", config->bind_addr);
+	CV_UINT("dtlsbindport", config->bind_port);
+	CV_BOOL("dtlsenable", config->enabled);
+	CV_STR("dtlscafile", config->ca_cert);
+	CV_STR("dtlscapath", config->ca_path);
+	CV_STR("dtlscertfile", config->server_cert);
+	CV_STR("dtlsprivatekey", config->private_key);
+	CV_STR("dtlsdhfile", config->dh_params);
+
+	res = -1;
+
+	CV_END;
+
+	return res;
+}
+
+#endif /* HAVE_OPENSSL */

Propchange: team/russell/dtls/main/dtls.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/russell/dtls/main/dtls.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/russell/dtls/main/dtls.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: team/russell/dtls/tests/test_dtls.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/russell/dtls/tests/test_dtls.c?view=auto&rev=206080
==============================================================================
--- team/russell/dtls/tests/test_dtls.c (added)
+++ team/russell/dtls/tests/test_dtls.c Sat Jul 11 23:13:13 2009
@@ -1,0 +1,259 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2009, Digium, Inc.
+ *
+ * Russell Bryant <russell at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief DTLS basic functionality testing
+ *
+ * \author Russell Bryant <russell at digium.com>
+ */
+
+/*** MODULEINFO
+	<defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <signal.h>
+
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/config.h"
+#include "asterisk/netsock.h"
+#include "asterisk/dtls.h"
+
+static const char TEST_DTLS_CFG[] = "test_dtls.conf";
+
+static const uint16_t CLIENT_PORT = 7103;
+static const char CLIENT_PORT_STR[] = "7103";
+
+static const uint16_t SERVER_PORT = 7104;
+static const char SERVER_PORT_STR[] = "7104";
+
+AST_MUTEX_DEFINE_STATIC(test_lock);
+
+AST_MUTEX_DEFINE_STATIC(recv_lock);
+static ast_cond_t recv_cond;
+
+static struct ast_dtls_config *load_dtls_config(int client)
+{
+	struct ast_dtls_config *dtls_config;
+	struct ast_config *cfg;
+	struct ast_flags cfg_flags = { 0, };
+	struct ast_variable *var;
+
+	cfg = ast_config_load(TEST_DTLS_CFG, cfg_flags);
+	if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
+		return NULL;
+	}
+
+	if (!(dtls_config = ast_dtls_config_alloc())) {
+		goto return_cleanup;
+	}
+
+	ast_dtls_config_parse(dtls_config, "dtlsbindaddr", "127.0.0.1");
+	ast_dtls_config_parse(dtls_config, "dtlsbindport", client ?
+			CLIENT_PORT_STR : SERVER_PORT_STR);
+
+	for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+		if (ast_dtls_config_parse(dtls_config, var->name, var->value)) {
+			ast_log(LOG_WARNING, "Invalid DTLS option '%s' = '%s'\n",
+					var->name, var->value);
+		}
+	}
+
+return_cleanup:
+	ast_config_destroy(cfg);
+
+	return dtls_config;
+}
+
+static const char msg[] = "meepmeep";
+
+struct dtls_recv_args {
+	struct ast_cli_args *a;
+	int *success;
+};
+
+static void dtls_recv(const struct sockaddr_in *sin_remote, const void *buf, size_t len,
+		void *user_data)
+{
+	struct dtls_recv_args *args = user_data;
+
+	ast_cli(args->a->fd, " --> Ooh, got a message, '%s'\n", (const char *) buf);
+
+	if (!strcmp(buf, msg)) {
+		*args->success = 1;
+		ast_mutex_lock(&recv_lock);
+		ast_cond_signal(&recv_cond);
+		ast_mutex_unlock(&recv_lock);
+	}
+}
+
+static char *run_test(struct ast_cli_args *a)
+{
+	struct ast_dtls_config *dtls_config_client = NULL;
+	struct ast_dtls_config *dtls_config_server = NULL;
+	struct ast_dtls_handler *dtls_handler_client = NULL;
+	struct ast_dtls_handler *dtls_handler_server = NULL;
+	char *res = CLI_FAILURE;
+	int success = 0;
+	struct timeval timeout;
+	struct timespec ts;
+	struct sockaddr_in sin_remote = {
+		.sin_family = AF_INET,
+	};
+	struct dtls_recv_args recv_args = {
+		.a = a,
+		.success = &success,
+	};
+
+	ast_cli(a->fd, " --> Beginning DTLS functionality test\n");
+
+	if (!(dtls_config_client = load_dtls_config(1))) {
+		ast_cli(a->fd, " --> [FAILURE] Failed to load DTLS config.\n");
+		return CLI_FAILURE;
+	}
+
+	if (!(dtls_config_server = load_dtls_config(0))) {
+		ast_cli(a->fd, " --> [FAILURE] Failed to load DTLS config.\n");
+		goto return_cleanup;
+	}
+
+	if (!ast_dtls_config_get_enabled(dtls_config_client)) {
+		ast_cli(a->fd, " --> [FAILURE] DTLS is disabled for test_dtls "
+				"you silly goose.\n");
+		goto return_cleanup;
+	}
+
+	if (!ast_dtls_config_get_enabled(dtls_config_server)) {
+		ast_cli(a->fd, " --> [FAILURE] DTLS is disabled for test_dtls "
+				"you silly goose.\n");
+		goto return_cleanup;
+	}
+
+	if (!(dtls_handler_client = ast_dtls_handler_alloc(dtls_config_client,
+					dtls_recv, &recv_args))) {
+		ast_cli(a->fd, " --> [FAILURE] Failed to set up DTLS handler\n");
+		goto return_cleanup;
+	}
+
+	if (!(dtls_handler_server = ast_dtls_handler_alloc(dtls_config_server,
+					dtls_recv, &recv_args))) {
+		ast_cli(a->fd, " --> [FAILURE] Failed to set up DTLS handler\n");
+		goto return_cleanup;
+	}
+
+	inet_aton("127.0.0.1", &sin_remote.sin_addr);
+	sin_remote.sin_port = htons(SERVER_PORT);
+
+	timeout = ast_tvadd(ast_tvnow(), ast_tv(5, 0));
+
+	ts.tv_sec = timeout.tv_sec;
+	ts.tv_nsec = timeout.tv_usec * 1000;
+
+	ast_mutex_lock(&recv_lock);
+	ast_cli(a->fd, " --> Sending a secret message: '%s'\n", msg);
+	if (ast_dtls_send(dtls_handler_client, msg, sizeof(msg), &sin_remote)) {
+		ast_cli(a->fd, " --> [FAILURE] Failed to send message.\n");
+	} else {
+		ast_cond_timedwait(&recv_cond, &recv_lock, &ts);
+	}
+	ast_mutex_unlock(&recv_lock);
+
+	if (success) {
+		ast_cli(a->fd, " --> [SUCCESS] yay!\n");
+		res = CLI_SUCCESS;
+	} else {
+		ast_cli(a->fd, " --> [FAILURE] This test was full of fail.\n");
+		res = CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, " --> Test complete.\n");
+
+return_cleanup:
+	if (dtls_config_client) {
+		dtls_config_client = ast_dtls_config_unref(dtls_config_client);
+	}
+
+	if (dtls_config_server) {
+		dtls_config_server = ast_dtls_config_unref(dtls_config_server);
+	}
+
+	if (dtls_handler_client) {
+		dtls_handler_client = ast_dtls_handler_unref(dtls_handler_client);
+	}
+
+	if (dtls_handler_server) {
+		dtls_handler_server = ast_dtls_handler_unref(dtls_handler_server);
+	}
+
+	return res;
+}
+
+static char *handle_cli_dtls_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dtls test";
+		e->usage = ""
+			"Usage: dtls test"
+			"    Configure test_dtls.conf first!\n"
+			"";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	case CLI_HANDLER:
+	{
+		char *res;
+
+		ast_mutex_lock(&test_lock);
+		res = run_test(a);
+		ast_mutex_unlock(&test_lock);
+
+		return res;
+	}
+	}
+
+	ast_cli(a->fd, "OMGWTF!\n");
+
+	return CLI_FAILURE;
+}
+
+static struct ast_cli_entry cli_dtls[] = {
+	AST_CLI_DEFINE(handle_cli_dtls_test, "Test DTLS"),
+};
+
+static int load_module(void)
+{
+	int res;
+	ast_cond_init(&recv_cond, NULL);
+	res = ast_cli_register_multiple(cli_dtls, ARRAY_LEN(cli_dtls));
+	return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_cond_destroy(&recv_cond);
+	return ast_cli_unregister_multiple(cli_dtls, ARRAY_LEN(cli_dtls));
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DTLS functionality test");

Propchange: team/russell/dtls/tests/test_dtls.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/russell/dtls/tests/test_dtls.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/russell/dtls/tests/test_dtls.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the svn-commits mailing list