[asterisk-commits] russell: branch russell/dtls r206080 - in /team/russell/dtls: configs/ includ...
SVN commits to the Asterisk project
asterisk-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 asterisk-commits
mailing list