Merge pull request #64 from nickbroon/refactor

ipfixlolib DTLS code in own file
master
Oliver Gasser 2017-12-08 15:09:00 +01:00 committed by GitHub
commit e1078c7611
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1014 additions and 981 deletions

View File

@ -537,10 +537,13 @@ ENDIF(CONNECTION_FILTER)
OPTION(SUPPORT_DTLS "Enables/Disables encryption support for IPFIX messages." OFF)
IF (SUPPORT_DTLS)
FIND_PACKAGE(OpenSSL)
FIND_PACKAGE(OpenSSL 1.0.0)
IF (NOT OPENSSL_FOUND)
MESSAGE(FATAL_ERROR "Could not find openssl. Please install the library or turn off SUPPORT_DTLS")
ENDIF (NOT OPENSSL_FOUND)
IF (NOT (${OPENSSL_VERSION} VERSION_LESS 1.1.0))
MESSAGE(FATAL_ERROR "openssl version must be less than 1.1.0")
ENDIF (NOT (${OPENSSL_VERSION} VERSION_LESS 1.1.0))
INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(vermont ${OPENSSL_LIBRARIES})
ADD_DEFINITIONS(-DSUPPORT_DTLS)

View File

@ -32,9 +32,7 @@ The following packages are optional:
- libczmq-dev (for receiving IPFIX reports over ZMQ)
==> cmake option SUPPORT_ZMQ
For DTLS support, OpenSSL 1.0.0 or higher is required. It is recommended
to build OpenSSL based on the latest CVS revision. See DTLS instructions below.
For DTLS support, OpenSSL 1.0.0 is required.
## BUILDING AND INSTALLATION
@ -83,64 +81,18 @@ $ make install
### BUILDING WITH DTLS-OVER-UDP SUPPORT
VERMONT's DTLS support is based on OpenSSL version 1.0.0 (and maybe higher).
VERMONT's DTLS support is based on OpenSSL version 1.0.0. OpenSSL 1.1.0 is not currently supported.
Since the DTLS implementation in OpenSSL is fairly new and not as mature as
the TLS/SSL implementation, you should use the latest version of OpenSSL which
you can get from http://openssl.org/source/.
At the time of writing (July 2010), the latest version is 1.0.0a.
In order to compile VERMONT with DTLS-over-UDP support set the following option:
``` shell
$ wget http://openssl.org/source/openssl-1.0.0a.tar.gz
$ tar xzf openssl-1.0.0a.tar.gz
$ cd openssl-1.0.0a/
$ cmake -DSUPPORT_DTLS=YES
```
If you want to profit from the most recent bugfixes, you can check out the
sources from the OpenSSL CVS repository instead:
``` shell
$ cvs -z9 -d anonymous@cvs.openssl.org:/openssl-cvs co openssl
$ cd openssl/
If CMake does not find OPENSSL you can explicitly specify the include and library paths:
```
In order to avoid incompatibilities with other packages of your distribution,
you probably do not want the new version of OpenSSL to become the default
OpenSSL library on your system. Therefore, it is recommended to install the
new version in a local directory by using the --prefix option of the config
script.
To build OpenSSL and install it into a built/ subdirectory within the OpenSSL
source directory, call the following commands:
``` shell
$ ./config -d no-dso no-shared --prefix=`pwd`/built
$ make
$ make install
cmake -DSUPPORT_DTLS=YES -DCMAKE_INCLUDE_PATH=/path/to/openssl/include -DCMAKE_LIBRARY_PATH=/path/to/openssl/lib
```
The configure option "no-dso" turns off the use of shared-library methods which
avoids linking problems related to libdl on the Linux platform.
With the option "no-shared", only static libraries are built which makes it
easier to link VERMONT to the correct version of OpenSSL.
In order to compile VERMONT with DTLS-over-UDP support, change into the root
of VERMONT's source directory and execute cmake with the OpenSSL include and
library paths (replace "/path/to/openssl" by your OpenSSL source directory):
``` shell
$ cmake -DSUPPORT_DTLS=YES -DCMAKE_INCLUDE_PATH=/path/to/openssl/built/include -DCMAKE_LIBRARY_PATH=/path/to/openssl/built/lib
```
On 64 bit platforms, the library path might be different (mind the "64" at the
very end!):
``` shell
$ cmake -DSUPPORT_DTLS=YES -DCMAKE_INCLUDE_PATH=/path/to/openssl/built/include -DCMAKE_LIBRARY_PATH=/path/to/openssl/built/lib64
```
If you have previously built VERMONT with OpenSSL located in another
directory, you might need to manually remove the file CMakeCache.txt before
calling cmake.
### BUILDING WITH DTLS-OVER-SCTP SUPPORT
At the time of writing (July 2010), DTLS over SCTP can be used on FreeBSD only!

View File

@ -18,10 +18,16 @@
#
ADD_LIBRARY(ipfixlolib
set(ipfixlolib_SOURCES
encoding.c
ipfixlolib.c
ipfix_names.c
)
if (SUPPORT_DTLS)
set(ipfixlolib_SOURCES ${ipfixlolib_SOURCES} ipfixlolib_dtls.c)
endif()
ADD_LIBRARY(ipfixlolib ${ipfixlolib_SOURCES})
add_cppcheck(ipfixlolib STYLE POSSIBLE_ERROR)

View File

@ -40,6 +40,7 @@
*/
#include "ipfixlolib.h"
#include "ipfixlolib_private.h"
#include "encoding.h"
#include "common/msg.h"
#include <netinet/in.h>
@ -52,6 +53,10 @@
#include <stdbool.h>
#include <inttypes.h>
#ifdef SUPPORT_DTLS
#include "ipfixlolib_dtls_private.h"
#endif
#ifdef __linux__
/* Copied from linux/in.h */
#define IP_MTU 14
@ -67,25 +72,11 @@ extern "C" {
# error OpenSSL built without SCTP support. Rebuild OpenSSL with SCTP support or turn off SUPPORT_DTLS_OVER_SCTP
#endif
#ifdef SUPPORT_DTLS
static int ensure_exporter_set_up_for_dtls(ipfix_exporter *exporter);
static void deinit_openssl_ctx(ipfix_exporter *exporter);
static int setup_dtls_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col, ipfix_dtls_connection *con);
static int dtls_send(ipfix_exporter *exporter, ipfix_receiving_collector *col, const struct iovec *iov, int iovcnt);
static int dtls_connect(ipfix_receiving_collector *col, ipfix_dtls_connection *con);
static void dtls_shutdown_and_cleanup(ipfix_dtls_connection *con);
static void dtls_fail_connection(ipfix_dtls_connection *con);
#endif
#ifdef SUPPORT_SCTP
static int init_send_sctp_socket(struct sockaddr_storage serv_addr, char *vrf_name);
#ifdef SUPPORT_DTLS_OVER_SCTP
static void handle_sctp_event(BIO *bio, void *context, void *buf);
#endif
#endif
static int init_send_udp_socket(struct sockaddr_storage serv_addr, char *vrf_name);
static int enable_pmtu_discovery(int s);
static int ipfix_find_template(ipfix_exporter *exporter, uint16_t template_id);
static void ipfix_update_header(ipfix_exporter *p_exporter, ipfix_receiving_collector *collector, ipfix_sendbuffer *sendbuf);
static int ipfix_init_sendbuffer(export_protocol_version export_protocol, ipfix_sendbuffer **sendbufn);
static int ipfix_reset_sendbuffer(ipfix_sendbuffer *sendbuf);
static int ipfix_deinit_sendbuffer(ipfix_sendbuffer **sendbuf);
@ -100,583 +91,18 @@ static int ipfix_update_template_sendbuffer(ipfix_exporter *exporter);
static int ipfix_send_templates(ipfix_exporter* exporter);
static int ipfix_send_data(ipfix_exporter* exporter);
static int ipfix_new_file(ipfix_receiving_collector* recvcoll);
static void update_exporter_max_message_size(ipfix_exporter *exporter);
static int update_collector_mtu(ipfix_exporter *exporter, ipfix_receiving_collector *col);
static int get_mtu(const int s);
static int ipfix_enterprise_flag_set(uint16_t id);
#ifdef SUPPORT_DTLS
/* A separate SSL_CTX object is created for every ipfix_exporter.
* Returns 0 on success, -1 on error
* */
static int ensure_exporter_set_up_for_dtls(ipfix_exporter *e) {
ensure_openssl_init();
if (e->ssl_ctx) return 0;
/* This SSL_CTX object will be freed in deinit_openssl_ctx() */
if ( ! (e->ssl_ctx=SSL_CTX_new(DTLSv1_client_method())) ) {
msg(MSG_FATAL, "Failed to create SSL context");
msg_openssl_errors();
return -1;
}
SSL_CTX_set_read_ahead(e->ssl_ctx,1);
if ( (e->ca_file || e->ca_path) &&
! SSL_CTX_load_verify_locations(e->ssl_ctx,e->ca_file,e->ca_path) ) {
msg(MSG_FATAL,"SSL_CTX_load_verify_locations() failed.");
msg_openssl_errors();
return -1;
}
/* Load our own certificate */
if (e->certificate_chain_file) {
if (!SSL_CTX_use_certificate_chain_file(e->ssl_ctx, e->certificate_chain_file)) {
msg(MSG_FATAL,"Unable to load certificate chain file %s",e->certificate_chain_file);
msg_openssl_errors();
return -1;
}
if (!SSL_CTX_use_PrivateKey_file(e->ssl_ctx, e->private_key_file, SSL_FILETYPE_PEM)) {
msg(MSG_FATAL,"Unable to load private key file %s",e->private_key_file);
msg_openssl_errors();
return -1;
}
if (!SSL_CTX_check_private_key(e->ssl_ctx)) {
msg(MSG_FATAL,"Private key and certificate do not match");
msg_openssl_errors();
return -1;
}
DPRINTF("We successfully loaded our certificate.");
} else {
DPRINTF("We do NOT have a certificate.");
}
/* We leave the certificate_authorities list of the Certificate Request
* empty. See RFC 4346 7.4.4. Certificate request. */
return 0;
}
static void deinit_openssl_ctx(ipfix_exporter *e) {
if (e->ssl_ctx) { SSL_CTX_free(e->ssl_ctx); }
e->ssl_ctx = NULL;
}
static int dtls_verify_peer_cb(void *context, const char* dnsname) {
const ipfix_receiving_collector *col =
(const ipfix_receiving_collector *) context;
return strcasecmp(col->peer_fqdn,dnsname) ? 0 : 1;
}
static int dtls_get_replacement_connection_ready(
ipfix_exporter *exporter,
ipfix_receiving_collector *col) {
int ret;
if (!col->dtls_replacement.ssl) {
/* No SSL object has been created yet. Let's open a socket and
* setup a new SSL object. */
DPRINTF("Setting up replacement connection.");
if (setup_dtls_connection(exporter,col,&col->dtls_replacement)) {
return -1;
}
}
ret = dtls_connect(col,&col->dtls_replacement);
if (ret == 1) {
DPRINTF("Replacement connection setup successful.");
return 1; /* SUCCESS */
}
if (ret == 0) {
if (col->dtls_connect_timeout &&
(time(NULL) - col->dtls_replacement.last_reconnect_attempt_time > col->dtls_connect_timeout)) {
msg(MSG_ERROR,"DTLS replacement connection setup taking too long.");
dtls_fail_connection(&col->dtls_replacement);
} else {
DPRINTF("Replacement connection setup still ongoing.");
return 0;
}
}
return -1;
}
static int dtls_send_templates(
ipfix_exporter *exporter,
ipfix_receiving_collector *col) {
if (exporter->template_sendbuffer->committed_data_length == 0)
return 0;
ipfix_update_header(exporter, col,
exporter->template_sendbuffer);
col->messages_sent++;
DPRINTF("Sending templates over DTLS.");
return dtls_send(exporter,col,
exporter->template_sendbuffer->entries,
exporter->template_sendbuffer->current);
}
static void dtls_swap_connections(ipfix_dtls_connection *a, ipfix_dtls_connection *b) {
ipfix_dtls_connection tmp;
memcpy(&tmp,a,sizeof(tmp));
memcpy(a,b,sizeof(*b));
memcpy(b,&tmp,sizeof(*b));
}
/* This function pushes the connection setup forward if we are in state C_NEW.
* It also sets up the replacement connection if it's time to replace the
* current connection with a new one.
* If we are in state C_DISCONNECT then it sets up a new main connection.
*
* Return values:
* returns 0 on success
* returns 1 if a connection setup procedure is still ongoing. Then, this
* function should be called again after a short period of time.
* returns -1 on error.
* Note that success does not mean that we are connected because the
* connection setup might still be ongoing. Therefore you still have
* to check the state member of ipfix_receiving_collector to determine
* if we are connected. */
static int dtls_manage_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col) {
int ret, rc = 0;
if (col->state == C_CONNECTED) {
if( col->protocol == DTLS_OVER_SCTP ||
col->dtls_max_connection_lifetime == 0 ||
time(NULL) - col->connect_time < col->dtls_max_connection_lifetime)
return 0;
/* Alright, the connection is already very old and needs to be
* replaced. Let's get the replacement / backup connection ready. */
ret = dtls_get_replacement_connection_ready(exporter, col);
rc = 1;
if (ret == 1) { /* Connection setup completed */
rc = 0;
DPRINTF("Swapping connections.");
dtls_swap_connections(&col->dtls_main,&col->dtls_replacement);
DPRINTF("Shutting down old DTLS connection.");
dtls_shutdown_and_cleanup(&col->dtls_replacement);
col->connect_time = time(NULL);
ret = dtls_send_templates(exporter, col);
/* We do not need to check the return value because
* dtls_send_templates already shuts down the DTLS connection
* in case of failure.
* It also sets state to C_DISCONNECTED in this case. */
}
/* We ignore all other return values of dtls_get_replacement_connection_ready() */
}
if (col->state == C_NEW) {
rc = 1;
/* Connection setup is still ongoing. Let's push it forward. */
ret = dtls_connect(col,&col->dtls_main);
if (ret == 1) {
/* SUCCESS */
col->state = C_CONNECTED;
col->connect_time = time(NULL);
if (update_collector_mtu(exporter, col)) {
/* update_collector_mtu calls remove_collector
in case of failure which in turn sets
col->state to C_UNUSED. */
return -1;
}
ret = dtls_send_templates(exporter, col);
/* dtls_send (inside dtls_send_templates) calls
* dtls_fail_connection() and sets col->state
* in case of failure. */
if (ret >= 0) return 0; else return -1;
} else if (ret == -1) {
/* Failure
* dtls_connect() cleaned up SSL object already.
* Remember that the socket is now part of the DTLS connection
* abstraction. dtls_connect() closed the socket as well. */
col->state = C_DISCONNECTED;
return -1;
}
}
if (col->state == C_DISCONNECTED) {
/* Wait dtls_connect_timeout seconds before
re-initiating DTLS connection */
if (time(NULL) < col->last_reconnect_attempt_time +
col->dtls_connect_timeout)
return 1;
rc = 1;
if (setup_dtls_connection(exporter,col,&col->dtls_main)) {
/* col->state stays in C_DISCONNECTED in this case
* setup_dtls_connection() does not alter it. */
return -1;
}
col->state = C_NEW;
col->last_reconnect_attempt_time = time(NULL);
}
return rc;
}
/* Return values:
* -1 failure
* 0 no failure but not yet connected. You need to call dtls_connect again
* next time
* 1 yes. now we're connected. Don't call dtls_connect again. */
static int dtls_connect(ipfix_receiving_collector *col, ipfix_dtls_connection *con) {
int ret, error;
ret = SSL_connect(con->ssl);
error = SSL_get_error(con->ssl,ret);
if (error == SSL_ERROR_NONE) {
msg_openssl_return_code(MSG_DEBUG,"SSL_connect()",ret,error);
msg(MSG_INFO, "Successfully (re)connected to %s-over-DTLS collector.", col->protocol == DTLS_OVER_SCTP ? "SCTP" : "UDP");
msg(MSG_INFO,"TLS Cipher: %s",SSL_get_cipher_name(con->ssl));
DPRINTF("DTLS handshake succeeded. We are now connected.");
if (col->peer_fqdn) { /* We need to verify the identity of our peer */
if (verify_ssl_peer(con->ssl,&dtls_verify_peer_cb,col)) {
DPRINTF("Peer authentication successful.");
} else {
msg(MSG_ERROR,"Peer authentication failed. Shutting down connection.");
dtls_fail_connection(con);
return -1;
}
}
return 1;
} else if (error == SSL_ERROR_WANT_READ) {
msg_openssl_return_code(MSG_DEBUG,"SSL_connect()",ret,error);
return 0;
} else {
msg_openssl_return_code(MSG_ERROR,"SSL_connect()",ret,error);
dtls_fail_connection(con);
return -1;
}
}
/* Return values:
* n>0: sent n bytes
* 0: Could not send due to OpenSSL returning SSL_ERROR_WANT_READ
* -1: Recoverable error
* -2: Bad Error. DTLS connection has been shutdown already.
* You should set state to C_DISCONNECT
* -3: Message too long. You should query the current MTU
* estimate in this case and update the corresponding
* property of the collector.
*/
static int dtls_send_helper( ipfix_dtls_connection *con,
const struct iovec *iov, int iovcnt) {
int len, error, i;
char sendbuf[IPFIX_MAX_PACKETSIZE];
char *sendbufcur = sendbuf;
int maxsendbuflen = sizeof(sendbuf);
/* Collect data form iovecs */
for (i=0;i<iovcnt;i++) {
if (sendbufcur + iov[i].iov_len > sendbuf + maxsendbuflen) {
msg(MSG_FATAL, "sendbuffer for dtls_send too small.");
return -1;
}
memcpy(sendbufcur,iov[i].iov_base,iov[i].iov_len);
sendbufcur+=iov[i].iov_len;
}
len = SSL_write(con->ssl, sendbuf, sendbufcur - sendbuf);
error = SSL_get_error(con->ssl,len);
#ifdef DEBUG
char buf[32];
snprintf(buf,sizeof(buf),"SSL_write(%d bytes of data)",(int) (sendbufcur - sendbuf) );
msg_openssl_return_code(MSG_DEBUG,buf,len,error);
#endif
switch (error) {
case SSL_ERROR_NONE:
if (len!=sendbufcur - sendbuf) {
msg(MSG_FATAL, "len!=sendbuflen when calling SSL_write()");
return -1;
}
return sendbufcur - sendbuf; /* SUCCESS */
case SSL_ERROR_WANT_READ:
return 0;
case SSL_ERROR_SYSCALL:
if (errno == EMSGSIZE) {
return -3;
}
/* fall through */
default:
msg_openssl_return_code(MSG_ERROR,"SSL_write()",len,error);
dtls_fail_connection(con);
return -2;
}
}
/* Return values:
* -1 error
* 0 could not write because OpenSSL returned SSL_ERROR_WANT_READ
* n>0 number of bytes written
*/
static int dtls_send(
ipfix_exporter *exporter,
ipfix_receiving_collector *col,
const struct iovec *iov, int iovcnt) {
int len;
/* DTLS negotiation has to be finished before we can send data.
* Drop out of this function if we are not yet connected. */
if (col->state != C_CONNECTED) {
return -1;
}
len = dtls_send_helper(&col->dtls_main, iov, iovcnt);
if (len == -2) {
col->state = C_DISCONNECTED;
return -1;
} else if (len == -3) {
update_collector_mtu(exporter,col);
return -1;
}
return len;
}
#ifdef SUPPORT_DTLS_OVER_SCTP
/* Return values:
* -1 error
* 0 could not write because OpenSSL returned SSL_ERROR_WANT_READ
* n>0 number of bytes written
*/
static int dtls_over_sctp_send(
ipfix_exporter *exporter,
ipfix_receiving_collector *col,
const struct iovec *iov, int iovcnt, uint32_t pr_value) {
struct sctp_sndrcvinfo sinfo;
memset(&sinfo, 0, sizeof(struct sctp_sndrcvinfo));
sinfo.sinfo_timetolive = pr_value; // pr_value; FIXME
BIO_ctrl(SSL_get_wbio(col->dtls_main.ssl), BIO_CTRL_DGRAM_SCTP_SET_SNDINFO, sizeof(struct sctp_sndrcvinfo), &sinfo);
return dtls_send(exporter,col,iov,iovcnt);
}
#endif
static int create_dtls_socket(ipfix_receiving_collector *col) {
int s, type, protocol;
#ifdef SUPPORT_DTLS_OVER_SCTP
struct sctp_event_subscribe event;
if (col->protocol == DTLS_OVER_SCTP) {
/* SCTP case */
type = SOCK_STREAM;
protocol = IPPROTO_SCTP;
} else
#endif
{
/* UDP case */
type = SOCK_DGRAM;
protocol = 0;
}
if((s = socket(PF_INET, type, protocol)) < 0 ) {
msg(MSG_FATAL, "error opening socket, %s", strerror(errno));
return -1;
}
if (col->protocol == DTLS_OVER_UDP) {
if(enable_pmtu_discovery(s)) {
close(s);
return -1;
}
}
// set non-blocking
int flags;
flags = fcntl(s, F_GETFL);
flags |= O_NONBLOCK;
if(fcntl(s, F_SETFL, flags) == -1) {
msg(MSG_FATAL, "could not set socket non-blocking");
close(s);
return -1;
}
#ifdef SUPPORT_DTLS_OVER_SCTP
/* enable the reception of SCTP_SNDRCV information on a per
* message basis. */
memset(&event, 0, sizeof(event));
event.sctp_data_io_event = 1;
if (setsockopt(s, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event)) != 0) {
msg(MSG_ERROR, "SCTP: setsockopt() failed to enable sctp_data_io_event, %s", strerror(errno));
close(s);
return -1;
}
#endif
return s;
}
/* returns 0 on success and -1 on failure */
static int setup_dtls_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col, ipfix_dtls_connection *con) {
BIO *bio;
/* Resources allocated in this function. Those need to be freed in case of failure:
* - socket
* - SSL object
* - BIO
*/
#ifdef DEBUG
if (con->socket!=-1) {
msg(MSG_FATAL,"socket != -1");
close(con->socket);
con->socket = -1;
}
#endif
/* Create socket
create_dtls_socket() also activates PMTU Discovery. */
if ((con->socket = create_dtls_socket(col)) < 0) {
return -1;
}
DPRINTF("Created socket %d",con->socket);
/* ensure an SSL_CTX object is set up */
if (ensure_exporter_set_up_for_dtls(exporter)) {
close(con->socket);con->socket = -1;
return -1;
}
/* create SSL object */
if ( ! (con->ssl = SSL_new(exporter->ssl_ctx))) {
msg(MSG_FATAL, "Failed to create SSL object.");
msg_openssl_errors();
close(con->socket);con->socket = -1;
return -1;
}
/* Set verification parameters and cipherlist */
if (!col->peer_fqdn) {
SSL_set_cipher_list(con->ssl,"ALL"); // This includes anonymous ciphers
DPRINTF("We are NOT going to verify the certificates of the collectors b/c "
"the peerFqdn option is NOT set.");
} else {
if ( ! ((exporter->ca_file || exporter->ca_path) &&
exporter->certificate_chain_file) ) {
msg(MSG_ERROR,"Cannot verify certificates of collectors because prerequisites not met. "
"Prerequisites are: 1. CApath or CAfile or both set, "
"2. We have a certificate including the private key");
SSL_free(con->ssl);con->ssl = NULL;
close(con->socket);con->socket = -1;
return -1;
} else {
SSL_set_cipher_list(con->ssl,"DEFAULT");
SSL_set_verify(con->ssl,SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,0);
DPRINTF("We are going to request certificates from the collectors "
"and we are going to verify these b/c "
"the peerFqdn option is set");
}
}
/* create input/output abstraction for SSL object */
#ifdef SUPPORT_DTLS_OVER_SCTP
if (col->protocol == DTLS_OVER_SCTP) {
bio = BIO_new_dgram_sctp(con->socket,BIO_NOCLOSE);
BIO_dgram_sctp_notification_cb(bio, &handle_sctp_event,con);
}
else /* DTLS_OVER_UDP */
#endif
bio = BIO_new_dgram(con->socket,BIO_NOCLOSE);
if ( ! bio) {
msg(MSG_FATAL,"Failed to create datagram BIO.");
msg_openssl_errors();
SSL_free(con->ssl);con->ssl = NULL;
close(con->socket);con->socket = -1;
return -1;
}
#ifdef SUPPORT_DTLS_OVER_SCTP
if (col->protocol != DTLS_OVER_SCTP)
#endif
(void)BIO_ctrl(bio,BIO_CTRL_DGRAM_MTU_DISCOVER,0,0);
(void)BIO_ctrl_set_connected(bio,1,&col->addr); /* TODO: Explain, why are we doing this? */
SSL_set_bio(con->ssl,bio,bio);
// connect (non-blocking, i.e. handshake is initiated, not terminated)
if((connect(con->socket, (struct sockaddr*)&col->addr, sizeof(col->addr) ) == -1) && (errno != EINPROGRESS)) {
msg(MSG_FATAL, "connect failed, %s", strerror(errno));
SSL_free(con->ssl);con->ssl = NULL;
close(con->socket);con->socket = -1;
return -1;
}
DPRINTF("Set up SSL object.");
con->last_reconnect_attempt_time = time(NULL);
return 0;
}
static void dtls_shutdown_and_cleanup(ipfix_dtls_connection *con) {
int ret,error;
if (!con->ssl) return;
DPRINTF("Shutting down SSL connection.");
ret = SSL_shutdown(con->ssl);
error = SSL_get_error(con->ssl,ret);
#ifdef DEBUG
msg_openssl_return_code(MSG_DEBUG,"SSL_shutdown()",ret,error);
#endif
/* TODO: loop only if ret==-1 and error==WANT_READ or WANT_WRITE */
int i = 0;
while (ret != 1 && !(SSL_get_shutdown(con->ssl) & SSL_RECEIVED_SHUTDOWN)
&& ( (ret == 0 && error == SSL_ERROR_SYSCALL) ||
(ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)))) {
fd_set readfds;
struct timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(con->socket, &readfds);
ret = select(con->socket + 1,&readfds,NULL,NULL,&timeout);
DPRINTF("select returned: %d",ret);
DPRINTF("Calling SSL_shutdown()");
ret = SSL_shutdown(con->ssl);
error = SSL_get_error(con->ssl,ret);
msg_openssl_return_code(MSG_DEBUG,"SSL_shutdown()",ret,error);
if (i++ == 3) {
msg(MSG_ERROR,"Too many calls to select(). Breaking out.");
break;
}
}
/* Note: SSL_free() also frees associated sending and receiving BIOs */
SSL_free(con->ssl);
con->ssl = NULL;
con->last_reconnect_attempt_time = 0;
/* Close socket */
if ( con->socket != -1) {
DPRINTF("Closing socket");
ret = close(con->socket);
DPRINTF("close returned %d",ret);
con->socket = -1;
}
}
static void dtls_fail_connection(ipfix_dtls_connection *con) {
DPRINTF("Failing DTLS connection.");
dtls_shutdown_and_cleanup(con);
}
#endif /* SUPPORT_DTLS */
/*!
* \brief Should be called on a regular basis to push forward DTLS connection setup procedures.
*
* ipfixlolib depends on the user to call this function regularly
* if there is an ongoing connection setup procedure. This is
* necessary because<ul>
* <li>ipfixlolib is <em>not</em> allowed to block the calling thread,</li>
* <li>ipfixlolib does not run in a dedicated thread and</li>
* <li>a DTLS connection setup (i.e. handshake) may take an</li>
* extended period of time.
* </ul>
*
* \param exporter pointer to previously initialized exporter struct
* \return 1 this function should be called again after a short period of time
* \return 0 otherwise
* \sa ipfix_add_collector()
* \brief Should be called on a regular basis.
*/
int ipfix_beat(ipfix_exporter *exporter) {
int ret = 0;
#ifdef SUPPORT_DTLS
int i;
for (i = 0; i < exporter->collector_max_num; i++) {
ipfix_receiving_collector *col = &exporter->collector_arr[i];
// is the collector a valid target?
if (col->state != C_UNUSED) {
if (col->protocol == DTLS_OVER_UDP ||
col->protocol == DTLS_OVER_SCTP) {
if (dtls_manage_connection(exporter,col))
ret = 1;
}
}
}
return ipfix_dtls_advance_connections(exporter);
#else
return 0;
#endif
return ret;
}
/*
@ -755,7 +181,7 @@ static int init_send_udp_socket(struct sockaddr_storage serv_addr,
return s;
}
static int enable_pmtu_discovery(int s) {
int enable_pmtu_discovery(int s) {
#ifdef IP_MTU_DISCOVER
// Linux
const int optval = IP_PMTUDISC_DO;
@ -971,11 +397,7 @@ int ipfix_init_exporter(export_protocol_version export_protocol, uint32_t observ
tmp->collector_max_num = 0;
#ifdef SUPPORT_DTLS
tmp->ssl_ctx = NULL;
tmp->certificate_chain_file = NULL;
tmp->private_key_file = NULL;
tmp->ca_file = NULL;
tmp->ca_path = NULL;
ipfix_init_dtls_certificate(&tmp->certificate);
#endif
// initialize the sendbuffers
@ -1077,11 +499,7 @@ int ipfix_deinit_exporter(ipfix_exporter **exporter_p) {
ipfix_deinit_collector_array(&(exporter->collector_arr));
#ifdef SUPPORT_DTLS
deinit_openssl_ctx(exporter);
free( (void *) exporter->certificate_chain_file);
free( (void *) exporter->private_key_file);
free( (void *) exporter->ca_file);
free( (void *) exporter->ca_path);
ipfix_clear_dtls_certificate(&exporter->certificate);
#endif
// free own memory
@ -1091,7 +509,7 @@ int ipfix_deinit_exporter(ipfix_exporter **exporter_p) {
return 0;
}
static void update_exporter_max_message_size(ipfix_exporter *exporter) {
void update_exporter_max_message_size(ipfix_exporter *exporter) {
ipfix_receiving_collector *col;
int i;
uint16_t max_message_size;
@ -1142,7 +560,7 @@ static void update_exporter_max_message_size(ipfix_exporter *exporter) {
* Calls update_exporter_max_message_size()
* Calls remove_collector() if an error occurs.
*/
static int update_collector_mtu(ipfix_exporter *exporter,
int update_collector_mtu(ipfix_exporter *exporter,
ipfix_receiving_collector *col) {
if (col->protocol == UDP && col->mtu_mode == IPFIX_MTU_DISCOVER) {
int mtu = get_mtu(col->data_socket);
@ -1158,13 +576,13 @@ static int update_collector_mtu(ipfix_exporter *exporter,
int mtu = -1;
int mtu_ssl;
int mtu_bio;
if (col->dtls_main.ssl) {
mtu_ssl = col->dtls_main.ssl->d1->mtu;
if (col->dtls_connection.dtls_main.ssl) {
mtu_ssl = col->dtls_connection.dtls_main.ssl->d1->mtu;
DPRINTF("MTU got from SSL object: %d",mtu_ssl);
if (mtu_ssl > 0) {
mtu = mtu_ssl;
}
mtu_bio = BIO_ctrl(SSL_get_wbio(col->dtls_main.ssl),BIO_CTRL_DGRAM_QUERY_MTU,0,0);
mtu_bio = BIO_ctrl(SSL_get_wbio(col->dtls_connection.dtls_main.ssl),BIO_CTRL_DGRAM_QUERY_MTU,0,0);
DPRINTF("MTU got from BIO object: %d",mtu_bio);
if (mtu_bio > 0 && (mtu == -1 || mtu_bio < mtu)) mtu = mtu_bio;
}
@ -1225,7 +643,8 @@ static int add_collector_rawdir(ipfix_receiving_collector *collector, const char
return 0;
}
#endif
static void set_mtu_config(ipfix_receiving_collector *col,
void set_mtu_config(ipfix_receiving_collector *col,
ipfix_aux_config_udp *aux_config_udp) {
if (aux_config_udp && aux_config_udp->mtu>0) {
col->mtu_mode = IPFIX_MTU_FIXED;
@ -1236,68 +655,6 @@ static void set_mtu_config(ipfix_receiving_collector *col,
}
}
#ifdef SUPPORT_DTLS
static int add_collector_dtls(
ipfix_exporter *exporter,
ipfix_receiving_collector *col,
void *aux_config) {
col->dtls_replacement.socket = -1;
col->dtls_replacement.ssl = NULL;
col->dtls_max_connection_lifetime = 0;
col->dtls_connect_timeout = 30;
// we need aux_config for setting up a DTLS collector
if (!aux_config) {
return -1;
}
ipfix_aux_config_dtls *aux_config_dtls;
if (col->protocol == DTLS_OVER_SCTP) {
aux_config_dtls = &((ipfix_aux_config_dtls_over_sctp*)aux_config)->dtls;
} else if (col->protocol == DTLS_OVER_UDP) {
aux_config_dtls = &((ipfix_aux_config_dtls_over_udp*)aux_config)->dtls;
col->dtls_max_connection_lifetime = ((ipfix_aux_config_dtls_over_udp*)aux_config)->max_connection_lifetime;
ipfix_aux_config_udp *aux_config_udp;
aux_config_udp = &((ipfix_aux_config_dtls_over_udp*)aux_config)->udp;
/* Sets col->mtu_mode and col->mtu */
set_mtu_config(col,aux_config_udp);
} else {
return -1;
}
if (aux_config_dtls->peer_fqdn)
col->peer_fqdn = strdup(aux_config_dtls->peer_fqdn);
if (setup_dtls_connection(exporter,col,&col->dtls_main)) {
/* failure */
free( (void *) col->peer_fqdn);
col->peer_fqdn = NULL;
return -1;
}
col->state = C_NEW; /* By setting the state to C_NEW we are
basically allocation the slot. */
col->last_reconnect_attempt_time = time(NULL);
/* col->state must *not* be C_UNUSED when we call
update_collector_mtu(). That's why we call this function
after setting state to C_NEW. */
if (update_collector_mtu(exporter, col)) {
/* update_collector_mtu calls remove_collector
in case of failure which in turn sets
col->state to C_UNUSED and frees col->peer_fqdn. */
return -1;
}
/* We have to call update_exporter_max_message_size()
* because update_collector_mtu *only* calls
* update_exporter_max_message_size() if the MTU
* mode is IPFIX_MTU_DISCOVER. */
update_exporter_max_message_size(exporter);
/* Initiate connection setup */
dtls_manage_connection(exporter, col);
return 0;
}
#endif
/* Remaining protocols are
SCTP, UDP and TCP at the moment
*/
@ -1497,11 +854,11 @@ static void remove_collector(ipfix_receiving_collector *collector) {
#ifdef SUPPORT_DTLS
/* Shutdown DTLS connection */
if (collector->protocol == DTLS_OVER_UDP || collector->protocol == DTLS_OVER_SCTP) {
dtls_shutdown_and_cleanup(&collector->dtls_main);
dtls_shutdown_and_cleanup(&collector->dtls_replacement);
free( (void *) collector->peer_fqdn);
dtls_shutdown_and_cleanup(&collector->dtls_connection.dtls_main);
dtls_shutdown_and_cleanup(&collector->dtls_connection.dtls_replacement);
free( (void *) collector->dtls_connection.peer_fqdn);
}
collector->peer_fqdn = NULL;
collector->dtls_connection.peer_fqdn = NULL;
#endif
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
if (collector->protocol != RAWDIR) {
@ -1668,7 +1025,7 @@ int ipfix_remove_template(ipfix_exporter *exporter, uint16_t template_id) {
*
* Note: the first HEADER_USED_IOVEC_COUNT iovec struct are reserved for the header! These will be overwritten!
*/
static void ipfix_update_header(ipfix_exporter *p_exporter, ipfix_receiving_collector *collector, ipfix_sendbuffer *sendbuf)
void ipfix_update_header(ipfix_exporter *p_exporter, ipfix_receiving_collector *collector, ipfix_sendbuffer *sendbuf)
{
struct timeval now;
if (gettimeofday(&now, NULL)) {
@ -1892,11 +1249,11 @@ static int ipfix_init_collector_array(ipfix_receiving_collector **col, int col_c
c->packet_directory_path = NULL;
#endif
#ifdef SUPPORT_DTLS
c->dtls_main.socket = c->dtls_replacement.socket = -1;
c->dtls_main.ssl = c->dtls_replacement.ssl = NULL;
c->dtls_main.last_reconnect_attempt_time =
c->dtls_replacement.last_reconnect_attempt_time = 0;
c->peer_fqdn = NULL;
c->dtls_connection.dtls_main.socket = c->dtls_connection.dtls_replacement.socket = -1;
c->dtls_connection.dtls_main.ssl = c->dtls_connection.dtls_replacement.ssl = NULL;
c->dtls_connection.dtls_main.last_reconnect_attempt_time =
c->dtls_connection.dtls_replacement.last_reconnect_attempt_time = 0;
c->dtls_connection.peer_fqdn = NULL;
#endif
}
@ -3539,171 +2896,12 @@ int ipfix_set_sctp_reconnect_timer(ipfix_exporter *exporter, uint32_t timer) {
return 0;
}
/*!
* \brief Setup X.509 certificate used for authentication
*
* Set the the names of the files in which the X.509 certificate and the
* matching private key can be found. If private_key_file is NULL the
* certificate chain file will be searched for the private key. See OpenSSL
* man pages for more details
*
* The certificate can only be set once per exporter. Calling this function
* twice for the same exporter is an error. This function must not be called
* after the first DTLS connection has been set up.
*
* \param exporter pointer to previously initialized exporter struct
* \param certificate_chain_file name of file in which the certificate chain is
* stored in PEM format
* \param private_key_file name of file in which the private key is stored in
* PEM format. If NULL, the private key is read from certificate_chain_file.
* \return 0 success
* \return -1 failure
* \sa ipfix_set_ca_locations()
*/
int ipfix_set_dtls_certificate(ipfix_exporter *exporter,
const char *certificate_chain_file, const char *private_key_file) {
#ifdef SUPPORT_DTLS
if (exporter->ssl_ctx) {
msg(MSG_ERROR, "Too late to set certificate. SSL context already created.");
return -1;
}
if (exporter->certificate_chain_file) {
msg(MSG_ERROR, "Certificate can not be reset.");
return -1;
}
if ( ! certificate_chain_file) {
msg(MSG_ERROR, "ipfix_set_dtls_certificate called with bad parameters.");
return -1;
}
exporter->certificate_chain_file = strdup(certificate_chain_file);
if (private_key_file) {
exporter->private_key_file = strdup(private_key_file);
}
return 0;
#else /* SUPPORT_DTLS */
msg(MSG_FATAL, "Library compiled without DTLS support.");
return -1;
#endif /* SUPPORT_DTLS */
}
/*!
* \brief Set the locations of the CA certificates.
*
* See <tt>SSL_CTX_load_verify_locations(3)</tt> for more details. These
* locations can only be set once per exporter. Calling this function twice for
* the same exporter is an error.
*
* \param exporter pointer to previously initialized exporter struct
* \param ca_file All CA certificates found in this <em>file</em> are trusted
* for verification of the peer's certificate. This file has to be in PEM format.
* \param ca_path All CA certificates found in this <em>directory</em> are
* trusted for verification of the peer's certificate. See
* <tt>SSL_CTX_load_verify_locations(3)</tt> for details about how the files
* have to be named. All files need to be in PEM format.
* \return 0 success
* \return -1 failure
* \sa ipfix_set_dtls_certificate()
*/
int ipfix_set_ca_locations(ipfix_exporter *exporter, const char *ca_file, const char *ca_path) {
#ifdef SUPPORT_DTLS
if (exporter->ssl_ctx) {
msg(MSG_ERROR, "Too late to set CA locations. SSL context already created.");
return -1;
}
if (exporter->ca_file || exporter->ca_path) {
msg(MSG_ERROR, "CA locations can not be reset.");
return -1;
}
if (ca_file) exporter->ca_file = strdup(ca_file);
if (ca_path) exporter->ca_path = strdup(ca_path);
return 0;
#else /* SUPPORT_DTLS */
msg(MSG_FATAL, "Library compiled without DTLS support.");
return -1;
#endif
}
/* check if the enterprise bit in an ID is set */
static int ipfix_enterprise_flag_set(uint16_t id)
{
return bit_set(id, IPFIX_ENTERPRISE_BIT);
}
#ifdef SUPPORT_DTLS_OVER_SCTP
static void
handle_sctp_event(BIO *bio, void *context, void *buf)
{
#if 0
ipfix_dtls_connection *con = (ipfix_dtls_connection *) context;
#endif
struct sctp_assoc_change *sac;
struct sctp_send_failed *ssf;
struct sctp_paddr_change *spc;
struct sctp_remote_error *sre;
#ifdef SUPPORT_DTLS_OVER_SCTP
struct sctp_sender_dry_event *ssde;
#endif
union sctp_notification *snp;
char addrbuf[INET6_ADDRSTRLEN];
const char *ap;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
snp = buf;
switch (snp->sn_header.sn_type) {
case SCTP_ASSOC_CHANGE:
sac = &snp->sn_assoc_change;
msg(MSG_DEBUG,"SCTP Event: assoc_change: state=%hu, error=%hu, instr=%hu "
"outstr=%hu\n", sac->sac_state, sac->sac_error,
sac->sac_inbound_streams, sac->sac_outbound_streams);
break;
case SCTP_SEND_FAILED:
ssf = &snp->sn_send_failed;
msg(MSG_DEBUG,"SCTP Event: sendfailed: len=%hu err=%d\n", ssf->ssf_length,
ssf->ssf_error);
break;
case SCTP_PEER_ADDR_CHANGE:
spc = &snp->sn_paddr_change;
if (spc->spc_aaddr.ss_family == AF_INET) {
sin = (struct sockaddr_in *)&spc->spc_aaddr;
ap = inet_ntop(AF_INET, &sin->sin_addr,
addrbuf, INET6_ADDRSTRLEN);
} else {
sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
ap = inet_ntop(AF_INET6, &sin6->sin6_addr,
addrbuf, INET6_ADDRSTRLEN);
}
msg(MSG_DEBUG,"SCTP Event: intf_change: %s state=%d, error=%d\n", ap,
spc->spc_state, spc->spc_error);
break;
case SCTP_REMOTE_ERROR:
sre = &snp->sn_remote_error;
msg(MSG_DEBUG,"SCTP Event: remote_error: err=%hu len=%hu\n",
ntohs(sre->sre_error), ntohs(sre->sre_length));
break;
case SCTP_SHUTDOWN_EVENT:
msg(MSG_DEBUG,"SCTP Event: shutdown event\n");
break;
#ifdef SUPPORT_DTLS_OVER_SCTP
case SCTP_SENDER_DRY_EVENT:
ssde = &snp->sn_sender_dry_event;
msg(MSG_DEBUG,"SCTP Event: sender dry event\n");
break;
case SCTP_AUTHENTICATION_EVENT:
msg(MSG_DEBUG,"SCTP Event: authentication event\n");
break;
#endif
default:
msg(MSG_DEBUG,"SCTP Event: unknown type: %hu\n", snp->sn_header.sn_type);
break;
};
}
#endif
#ifdef __cplusplus
}
#endif

View File

@ -118,10 +118,7 @@ See ipfixlolib.h for details on how to use this library.
#include <netinet/sctp.h>
#endif
#ifdef SUPPORT_DTLS
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include "common/openssl/OpenSSL.h"
#include "ipfixlolib_dtls.h"
#endif
#ifdef LINUX_IF_H_FOUND
@ -230,10 +227,6 @@ typedef enum export_protocol_version {
#define IPFIX_MTU_MODE_DEFAULT IPFIX_MTU_FIXED
#endif
#ifdef SUPPORT_DTLS
#define IPFIX_DTLS_MAX_RECORD_LENGTH 16384
#endif
/* Struct containing an ipfix-header */
/* Header Format (see RFC 5101)
@ -408,44 +401,6 @@ enum ipfix_transport_protocol {
ZMQ /* Requires libczmq */
};
typedef struct {
uint16_t mtu; /*!< Maximum transmission unit (MTU).
If set to 0, PMTU discovery will be used.
(Only available on the Linux platform)
Applies to UDP and DTLS over UDP only. */
} ipfix_aux_config_udp;
typedef struct {
const char *peer_fqdn; /*!< The Fully Qualified Domain Name (FQDN) of the
peer. If set, the peer i.e. the Collector
<em>must</em> present a certificate of which
either the subject's Common Name (CN) or one of
the subject alternative names matches the FQDN.
There is no support for wildcard matching. For the
certificate verification to work, the user must
also call <tt>ipfix_set_ca_locations()</tt> in
advance to specify the locations of the root CA
certificates.
If set to NULL, anonymous cipher suites will be
added to the list of permissible cipher suites.
The identity of the peer will not be verified
then.*/
} ipfix_aux_config_dtls;
typedef struct {
ipfix_aux_config_dtls dtls; /*!< DTLS specific configuration */
ipfix_aux_config_udp udp; /*!< UDP specific configuration */
unsigned max_connection_lifetime; /*!< Time in seconds after which the DTLS
connection is replaced by a new one.
This mechanism aims to overcome the
dead peer problem.*/
} ipfix_aux_config_dtls_over_udp;
typedef struct {
ipfix_aux_config_dtls dtls; /*!< DTLS specific configuration */
} ipfix_aux_config_dtls_over_sctp;
/*
* These indicate, if a field is committed (i.e. can be used)
* unused or unclean (i.e. data is not complete yet)
@ -555,15 +510,6 @@ typedef struct {
template sets. */
} ipfix_sendbuffer;
#ifdef SUPPORT_DTLS
typedef struct {
int socket;
// uint16_t mtu;
SSL *ssl;
time_t last_reconnect_attempt_time;
} ipfix_dtls_connection;
#endif
/*
* A collector receiving messages from this exporter
*/
@ -589,18 +535,7 @@ typedef struct {
char* packet_directory_path; /*!< if protocol==RAWDIR: path to a directory to store packets in. Ignored otherwise. */
#endif
#ifdef SUPPORT_DTLS
/* Time in seconds after which a DTLS connection
* will be replaced by a new one. */
unsigned dtls_max_connection_lifetime;
unsigned dtls_connect_timeout;
ipfix_dtls_connection dtls_main;
ipfix_dtls_connection dtls_replacement;
time_t connect_time; /* point in time when the connection setup
succeeded. We need this to calculate the
age of a connection. If DTLS is used,
a connection rollover is performed when
a connection reaches a certain age.*/
const char *peer_fqdn;
ipfix_collector_dtls_connection dtls_connection;
#endif
char vrf_name[IFNAMSIZ];
} ipfix_receiving_collector;
@ -679,11 +614,7 @@ typedef struct {
int ipfix_lo_template_maxsize;
ipfix_lo_template *template_arr;
#ifdef SUPPORT_DTLS
SSL_CTX *ssl_ctx;
const char *certificate_chain_file;
const char *private_key_file;
const char *ca_file;
const char *ca_path;
ipfix_exporter_certificate certificate;
#endif
} ipfix_exporter;
@ -715,9 +646,6 @@ int ipfix_set_template_transmission_timer(ipfix_exporter *exporter, uint32_t tim
int ipfix_set_sctp_lifetime(ipfix_exporter *exporter, uint32_t lifetime);
int ipfix_set_sctp_reconnect_timer(ipfix_exporter *exporter, uint32_t timer);
int ipfix_set_dtls_certificate(ipfix_exporter *exporter, const char *certificate_chain_file, const char *private_key_file);
int ipfix_set_ca_locations(ipfix_exporter *exporter, const char *ca_file, const char *ca_path);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,13 @@
#ifndef IPFIXLOLIB_CONFIG_H
#define IPFIXLOLIB_CONFIG_H
#include <stdint.h>
typedef struct {
uint16_t mtu; /*!< Maximum transmission unit (MTU).
If set to 0, PMTU discovery will be used.
(Only available on the Linux platform)
Applies to UDP and DTLS over UDP only. */
} ipfix_aux_config_udp;
#endif

View File

@ -0,0 +1,793 @@
#include <fcntl.h>
#include "common/msg.h"
#include "common/openssl/OpenSSL.h"
#include "ipfixlolib_private.h"
#include "ipfixlolib_dtls.h"
#include "ipfixlolib_dtls_private.h"
#ifdef SUPPORT_SCTP
#ifdef SUPPORT_DTLS_OVER_SCTP
static void
handle_sctp_event(BIO *bio, void *context, void *buf)
{
#if 0
ipfix_dtls_connection *con = (ipfix_dtls_connection *) context;
#endif
struct sctp_assoc_change *sac;
struct sctp_send_failed *ssf;
struct sctp_paddr_change *spc;
struct sctp_remote_error *sre;
struct sctp_sender_dry_event *ssde;
union sctp_notification *snp;
char addrbuf[INET6_ADDRSTRLEN];
const char *ap;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
snp = buf;
switch (snp->sn_header.sn_type) {
case SCTP_ASSOC_CHANGE:
sac = &snp->sn_assoc_change;
msg(MSG_DEBUG,"SCTP Event: assoc_change: state=%hu, error=%hu, instr=%hu "
"outstr=%hu\n", sac->sac_state, sac->sac_error,
sac->sac_inbound_streams, sac->sac_outbound_streams);
break;
case SCTP_SEND_FAILED:
ssf = &snp->sn_send_failed;
msg(MSG_DEBUG,"SCTP Event: sendfailed: len=%hu err=%d\n", ssf->ssf_length,
ssf->ssf_error);
break;
case SCTP_PEER_ADDR_CHANGE:
spc = &snp->sn_paddr_change;
if (spc->spc_aaddr.ss_family == AF_INET) {
sin = (struct sockaddr_in *)&spc->spc_aaddr;
ap = inet_ntop(AF_INET, &sin->sin_addr,
addrbuf, INET6_ADDRSTRLEN);
} else {
sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
ap = inet_ntop(AF_INET6, &sin6->sin6_addr,
addrbuf, INET6_ADDRSTRLEN);
}
msg(MSG_DEBUG,"SCTP Event: intf_change: %s state=%d, error=%d\n", ap,
spc->spc_state, spc->spc_error);
break;
case SCTP_REMOTE_ERROR:
sre = &snp->sn_remote_error;
msg(MSG_DEBUG,"SCTP Event: remote_error: err=%hu len=%hu\n",
ntohs(sre->sre_error), ntohs(sre->sre_length));
break;
case SCTP_SHUTDOWN_EVENT:
msg(MSG_DEBUG,"SCTP Event: shutdown event\n");
break;
case SCTP_SENDER_DRY_EVENT:
ssde = &snp->sn_sender_dry_event;
msg(MSG_DEBUG,"SCTP Event: sender dry event\n");
break;
case SCTP_AUTHENTICATION_EVENT:
msg(MSG_DEBUG,"SCTP Event: authentication event\n");
break;
default:
msg(MSG_DEBUG,"SCTP Event: unknown type: %hu\n", snp->sn_header.sn_type);
break;
};
}
#endif /*SUPPORT_DTLS_OVER_SCTP */
#endif /* SUPPORT_SCTP */
/* A separate SSL_CTX object is created for every ipfix_exporter.
* Returns 0 on success, -1 on error
* */
static int ensure_exporter_set_up_for_dtls(ipfix_exporter_certificate *c) {
ensure_openssl_init();
if (c->ssl_ctx) return 0;
/* This SSL_CTX object will be freed in deinit_openssl_ctx() */
if ( ! (c->ssl_ctx=SSL_CTX_new(DTLSv1_client_method())) ) {
msg(MSG_FATAL, "Failed to create SSL context");
msg_openssl_errors();
return -1;
}
SSL_CTX_set_read_ahead(c->ssl_ctx,1);
if ( (c->ca_file || c->ca_path) &&
! SSL_CTX_load_verify_locations(c->ssl_ctx,c->ca_file,c->ca_path) ) {
msg(MSG_FATAL,"SSL_CTX_load_verify_locations() failed.");
msg_openssl_errors();
return -1;
}
/* Load our own certificate */
if (c->certificate_chain_file) {
if (!SSL_CTX_use_certificate_chain_file(c->ssl_ctx, c->certificate_chain_file)) {
msg(MSG_FATAL,"Unable to load certificate chain file %s",c->certificate_chain_file);
msg_openssl_errors();
return -1;
}
if (!SSL_CTX_use_PrivateKey_file(c->ssl_ctx, c->private_key_file, SSL_FILETYPE_PEM)) {
msg(MSG_FATAL,"Unable to load private key file %s",c->private_key_file);
msg_openssl_errors();
return -1;
}
if (!SSL_CTX_check_private_key(c->ssl_ctx)) {
msg(MSG_FATAL,"Private key and certificate do not match");
msg_openssl_errors();
return -1;
}
DPRINTF("We successfully loaded our certificate.");
} else {
DPRINTF("We do NOT have a certificate.");
}
/* We leave the certificate_authorities list of the Certificate Request
* empty. See RFC 4346 7.4.4. Certificate request. */
return 0;
}
void deinit_openssl_ctx(ipfix_exporter_certificate *c) {
if (c->ssl_ctx) { SSL_CTX_free(c->ssl_ctx); }
c->ssl_ctx = NULL;
}
static int create_dtls_socket(ipfix_receiving_collector *col) {
int s, type, protocol;
#ifdef SUPPORT_DTLS_OVER_SCTP
struct sctp_event_subscribe event;
if (col->protocol == DTLS_OVER_SCTP) {
/* SCTP case */
type = SOCK_STREAM;
protocol = IPPROTO_SCTP;
} else
#endif
{
/* UDP case */
type = SOCK_DGRAM;
protocol = 0;
}
if((s = socket(PF_INET, type, protocol)) < 0 ) {
msg(MSG_FATAL, "error opening socket, %s", strerror(errno));
return -1;
}
if (col->protocol == DTLS_OVER_UDP) {
if(enable_pmtu_discovery(s)) {
close(s);
return -1;
}
}
// set non-blocking
int flags;
flags = fcntl(s, F_GETFL);
flags |= O_NONBLOCK;
if(fcntl(s, F_SETFL, flags) == -1) {
msg(MSG_FATAL, "could not set socket non-blocking");
close(s);
return -1;
}
#ifdef SUPPORT_DTLS_OVER_SCTP
/* enable the reception of SCTP_SNDRCV information on a per
* message basis. */
memset(&event, 0, sizeof(event));
event.sctp_data_io_event = 1;
if (setsockopt(s, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(event)) != 0) {
msg(MSG_ERROR, "SCTP: setsockopt() failed to enable sctp_data_io_event, %s", strerror(errno));
close(s);
return -1;
}
#endif
return s;
}
/* returns 0 on success and -1 on failure */
int setup_dtls_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col, ipfix_dtls_connection *con) {
BIO *bio;
/* Resources allocated in this function. Those need to be freed in case of failure:
* - socket
* - SSL object
* - BIO
*/
#ifdef DEBUG
if (con->socket!=-1) {
msg(MSG_FATAL,"socket != -1");
close(con->socket);
con->socket = -1;
}
#endif
/* Create socket
create_dtls_socket() also activates PMTU Discovery. */
if ((con->socket = create_dtls_socket(col)) < 0) {
return -1;
}
DPRINTF("Created socket %d",con->socket);
/* ensure an SSL_CTX object is set up */
if (ensure_exporter_set_up_for_dtls(&exporter->certificate)) {
close(con->socket);con->socket = -1;
return -1;
}
/* create SSL object */
if ( ! (con->ssl = SSL_new(exporter->certificate.ssl_ctx))) {
msg(MSG_FATAL, "Failed to create SSL object.");
msg_openssl_errors();
close(con->socket);con->socket = -1;
return -1;
}
/* Set verification parameters and cipherlist */
if (!col->dtls_connection.peer_fqdn) {
SSL_set_cipher_list(con->ssl,"ALL"); // This includes anonymous ciphers
DPRINTF("We are NOT going to verify the certificates of the collectors b/c "
"the peerFqdn option is NOT set.");
} else {
if ( ! ((exporter->certificate.ca_file || exporter->certificate.ca_path) &&
exporter->certificate.certificate_chain_file) ) {
msg(MSG_ERROR,"Cannot verify certificates of collectors because prerequisites not met. "
"Prerequisites are: 1. CApath or CAfile or both set, "
"2. We have a certificate including the private key");
SSL_free(con->ssl);con->ssl = NULL;
close(con->socket);con->socket = -1;
return -1;
} else {
SSL_set_cipher_list(con->ssl,"DEFAULT");
SSL_set_verify(con->ssl,SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,0);
DPRINTF("We are going to request certificates from the collectors "
"and we are going to verify these b/c "
"the peerFqdn option is set");
}
}
/* create input/output abstraction for SSL object */
#ifdef SUPPORT_DTLS_OVER_SCTP
if (col->protocol == DTLS_OVER_SCTP) {
bio = BIO_new_dgram_sctp(con->socket,BIO_NOCLOSE);
BIO_dgram_sctp_notification_cb(bio, &handle_sctp_event,con);
}
else /* DTLS_OVER_UDP */
#endif
bio = BIO_new_dgram(con->socket,BIO_NOCLOSE);
if ( ! bio) {
msg(MSG_FATAL,"Failed to create datagram BIO.");
msg_openssl_errors();
SSL_free(con->ssl);con->ssl = NULL;
close(con->socket);con->socket = -1;
return -1;
}
#ifdef SUPPORT_DTLS_OVER_SCTP
if (col->protocol != DTLS_OVER_SCTP)
#endif
(void)BIO_ctrl(bio,BIO_CTRL_DGRAM_MTU_DISCOVER,0,0);
(void)BIO_ctrl_set_connected(bio,1,&col->addr); /* TODO: Explain, why are we doing this? */
SSL_set_bio(con->ssl,bio,bio);
// connect (non-blocking, i.e. handshake is initiated, not terminated)
if((connect(con->socket, (struct sockaddr*)&col->addr, sizeof(col->addr) ) == -1) && (errno != EINPROGRESS)) {
msg(MSG_FATAL, "connect failed, %s", strerror(errno));
SSL_free(con->ssl);con->ssl = NULL;
close(con->socket);con->socket = -1;
return -1;
}
DPRINTF("Set up SSL object.");
con->last_reconnect_attempt_time = time(NULL);
return 0;
}
static int dtls_verify_peer_cb(void *context, const char* dnsname) {
const ipfix_receiving_collector *col =
(const ipfix_receiving_collector *) context;
return strcasecmp(col->dtls_connection.peer_fqdn,dnsname) ? 0 : 1;
}
static void dtls_fail_connection(ipfix_dtls_connection *con) {
DPRINTF("Failing DTLS connection.");
dtls_shutdown_and_cleanup(con);
}
/* Return values:
* -1 failure
* 0 no failure but not yet connected. You need to call dtls_connect again
* next time
* 1 yes. now we're connected. Don't call dtls_connect again. */
static int dtls_connect(ipfix_receiving_collector *col, ipfix_dtls_connection *con) {
int ret, error;
ret = SSL_connect(con->ssl);
error = SSL_get_error(con->ssl,ret);
if (error == SSL_ERROR_NONE) {
msg_openssl_return_code(MSG_DEBUG,"SSL_connect()",ret,error);
msg(MSG_INFO, "Successfully (re)connected to %s-over-DTLS collector.", col->protocol == DTLS_OVER_SCTP ? "SCTP" : "UDP");
msg(MSG_INFO,"TLS Cipher: %s",SSL_get_cipher_name(con->ssl));
DPRINTF("DTLS handshake succeeded. We are now connected.");
if (col->dtls_connection.peer_fqdn) { /* We need to verify the identity of our peer */
if (verify_ssl_peer(con->ssl,&dtls_verify_peer_cb,col)) {
DPRINTF("Peer authentication successful.");
} else {
msg(MSG_ERROR,"Peer authentication failed. Shutting down connection.");
dtls_fail_connection(con);
return -1;
}
}
return 1;
} else if (error == SSL_ERROR_WANT_READ) {
msg_openssl_return_code(MSG_DEBUG,"SSL_connect()",ret,error);
return 0;
} else {
msg_openssl_return_code(MSG_ERROR,"SSL_connect()",ret,error);
dtls_fail_connection(con);
return -1;
}
}
static int dtls_get_replacement_connection_ready(
ipfix_exporter *exporter,
ipfix_receiving_collector *col) {
int ret;
if (!col->dtls_connection.dtls_replacement.ssl) {
/* No SSL object has been created yet. Let's open a socket and
* setup a new SSL object. */
DPRINTF("Setting up replacement connection.");
if (setup_dtls_connection(exporter,col,&col->dtls_connection.dtls_replacement)) {
return -1;
}
}
ret = dtls_connect(col,&col->dtls_connection.dtls_replacement);
if (ret == 1) {
DPRINTF("Replacement connection setup successful.");
return 1; /* SUCCESS */
}
if (ret == 0) {
if (col->dtls_connection.dtls_connect_timeout &&
(time(NULL) - col->dtls_connection.dtls_replacement.last_reconnect_attempt_time >
col->dtls_connection.dtls_connect_timeout)) {
msg(MSG_ERROR,"DTLS replacement connection setup taking too long.");
dtls_fail_connection(&col->dtls_connection.dtls_replacement);
} else {
DPRINTF("Replacement connection setup still ongoing.");
return 0;
}
}
return -1;
}
static void dtls_swap_connections(ipfix_dtls_connection *a, ipfix_dtls_connection *b) {
ipfix_dtls_connection tmp;
memcpy(&tmp,a,sizeof(tmp));
memcpy(a,b,sizeof(*b));
memcpy(b,&tmp,sizeof(*b));
}
void dtls_shutdown_and_cleanup(ipfix_dtls_connection *con) {
int ret,error;
if (!con->ssl) return;
DPRINTF("Shutting down SSL connection.");
ret = SSL_shutdown(con->ssl);
error = SSL_get_error(con->ssl,ret);
#ifdef DEBUG
msg_openssl_return_code(MSG_DEBUG,"SSL_shutdown()",ret,error);
#endif
/* TODO: loop only if ret==-1 and error==WANT_READ or WANT_WRITE */
int i = 0;
while (ret != 1 && !(SSL_get_shutdown(con->ssl) & SSL_RECEIVED_SHUTDOWN)
&& ( (ret == 0 && error == SSL_ERROR_SYSCALL) ||
(ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)))) {
fd_set readfds;
struct timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(con->socket, &readfds);
ret = select(con->socket + 1,&readfds,NULL,NULL,&timeout);
DPRINTF("select returned: %d",ret);
DPRINTF("Calling SSL_shutdown()");
ret = SSL_shutdown(con->ssl);
error = SSL_get_error(con->ssl,ret);
msg_openssl_return_code(MSG_DEBUG,"SSL_shutdown()",ret,error);
if (i++ == 3) {
msg(MSG_ERROR,"Too many calls to select(). Breaking out.");
break;
}
}
/* Note: SSL_free() also frees associated sending and receiving BIOs */
SSL_free(con->ssl);
con->ssl = NULL;
con->last_reconnect_attempt_time = 0;
/* Close socket */
if ( con->socket != -1) {
DPRINTF("Closing socket");
ret = close(con->socket);
DPRINTF("close returned %d",ret);
con->socket = -1;
}
}
/* This function pushes the connection setup forward if we are in state C_NEW.
* It also sets up the replacement connection if it's time to replace the
* current connection with a new one.
* If we are in state C_DISCONNECT then it sets up a new main connection.
*
* Return values:
* returns 0 on success
* returns 1 if a connection setup procedure is still ongoing. Then, this
* function should be called again after a short period of time.
* returns -1 on error.
* Note that success does not mean that we are connected because the
* connection setup might still be ongoing. Therefore you still have
* to check the state member of ipfix_receiving_collector to determine
* if we are connected. */
int dtls_manage_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col) {
int ret, rc = 0;
if (col->state == C_CONNECTED) {
if( col->protocol == DTLS_OVER_SCTP ||
col->dtls_connection.dtls_max_connection_lifetime == 0 ||
time(NULL) - col->dtls_connection.connect_time <
col->dtls_connection.dtls_max_connection_lifetime)
return 0;
/* Alright, the connection is already very old and needs to be
* replaced. Let's get the replacement / backup connection ready. */
ret = dtls_get_replacement_connection_ready(exporter, col);
rc = 1;
if (ret == 1) { /* Connection setup completed */
rc = 0;
DPRINTF("Swapping connections.");
dtls_swap_connections(&col->dtls_connection.dtls_main, &col->dtls_connection.dtls_replacement);
DPRINTF("Shutting down old DTLS connection.");
dtls_shutdown_and_cleanup(&col->dtls_connection.dtls_replacement);
col->dtls_connection.connect_time = time(NULL);
ret = dtls_send_templates(exporter, col);
/* We do not need to check the return value because
* dtls_send_templates already shuts down the DTLS connection
* in case of failure.
* It also sets state to C_DISCONNECTED in this case. */
}
/* We ignore all other return values of dtls_get_replacement_connection_ready() */
}
if (col->state == C_NEW) {
rc = 1;
/* Connection setup is still ongoing. Let's push it forward. */
ret = dtls_connect(col,&col->dtls_connection.dtls_main);
if (ret == 1) {
/* SUCCESS */
col->state = C_CONNECTED;
col->dtls_connection.connect_time = time(NULL);
if (update_collector_mtu(exporter, col)) {
/* update_collector_mtu calls remove_collector
in case of failure which in turn sets
col->state to C_UNUSED. */
return -1;
}
ret = dtls_send_templates(exporter, col);
/* dtls_send (inside dtls_send_templates) calls
* dtls_fail_connection() and sets col->state
* in case of failure. */
if (ret >= 0) return 0; else return -1;
} else if (ret == -1) {
/* Failure
* dtls_connect() cleaned up SSL object already.
* Remember that the socket is now part of the DTLS connection
* abstraction. dtls_connect() closed the socket as well. */
col->state = C_DISCONNECTED;
return -1;
}
}
if (col->state == C_DISCONNECTED) {
/* Wait dtls_connect_timeout seconds before
re-initiating DTLS connection */
if (time(NULL) < col->last_reconnect_attempt_time +
col->dtls_connection.dtls_connect_timeout)
return 1;
rc = 1;
if (setup_dtls_connection(exporter,col,&col->dtls_connection.dtls_main)) {
/* col->state stays in C_DISCONNECTED in this case
* setup_dtls_connection() does not alter it. */
return -1;
}
col->state = C_NEW;
col->last_reconnect_attempt_time = time(NULL);
}
return rc;
}
/* Return values:
* n>0: sent n bytes
* 0: Could not send due to OpenSSL returning SSL_ERROR_WANT_READ
* -1: Recoverable error
* -2: Bad Error. DTLS connection has been shutdown already.
* You should set state to C_DISCONNECT
* -3: Message too long. You should query the current MTU
* estimate in this case and update the corresponding
* property of the collector.
*/
static int dtls_send_helper( ipfix_dtls_connection *con,
const struct iovec *iov, int iovcnt) {
int len, error, i;
char sendbuf[IPFIX_MAX_PACKETSIZE];
char *sendbufcur = sendbuf;
int maxsendbuflen = sizeof(sendbuf);
/* Collect data form iovecs */
for (i=0;i<iovcnt;i++) {
if (sendbufcur + iov[i].iov_len > sendbuf + maxsendbuflen) {
msg(MSG_FATAL, "sendbuffer for dtls_send too small.");
return -1;
}
memcpy(sendbufcur,iov[i].iov_base,iov[i].iov_len);
sendbufcur+=iov[i].iov_len;
}
len = SSL_write(con->ssl, sendbuf, sendbufcur - sendbuf);
error = SSL_get_error(con->ssl,len);
#ifdef DEBUG
char buf[32];
snprintf(buf,sizeof(buf),"SSL_write(%d bytes of data)",(int) (sendbufcur - sendbuf) );
msg_openssl_return_code(MSG_DEBUG,buf,len,error);
#endif
switch (error) {
case SSL_ERROR_NONE:
if (len!=sendbufcur - sendbuf) {
msg(MSG_FATAL, "len!=sendbuflen when calling SSL_write()");
return -1;
}
return sendbufcur - sendbuf; /* SUCCESS */
case SSL_ERROR_WANT_READ:
return 0;
case SSL_ERROR_SYSCALL:
if (errno == EMSGSIZE) {
return -3;
}
/* fall through */
default:
msg_openssl_return_code(MSG_ERROR,"SSL_write()",len,error);
dtls_fail_connection(con);
return -2;
}
}
/* Return values:
* -1 error
* 0 could not write because OpenSSL returned SSL_ERROR_WANT_READ
* n>0 number of bytes written
*/
int dtls_send(ipfix_exporter *exporter, ipfix_receiving_collector *col,
const struct iovec *iov, int iovcnt) {
int len;
/* DTLS negotiation has to be finished before we can send data.
* Drop out of this function if we are not yet connected. */
if (col->state != C_CONNECTED) {
return -1;
}
len = dtls_send_helper(&col->dtls_connection.dtls_main, iov, iovcnt);
if (len == -2) {
col->state = C_DISCONNECTED;
return -1;
} else if (len == -3) {
update_collector_mtu(exporter,col);
return -1;
}
return len;
}
int dtls_send_templates(ipfix_exporter *exporter, ipfix_receiving_collector *col) {
if (exporter->template_sendbuffer->committed_data_length == 0)
return 0;
ipfix_update_header(exporter, col,
exporter->template_sendbuffer);
col->messages_sent++;
DPRINTF("Sending templates over DTLS.");
return dtls_send(exporter,col,
exporter->template_sendbuffer->entries,
exporter->template_sendbuffer->current);
}
int add_collector_dtls(
ipfix_exporter *exporter,
ipfix_receiving_collector *col,
void *aux_config) {
col->dtls_connection.dtls_replacement.socket = -1;
col->dtls_connection.dtls_replacement.ssl = NULL;
col->dtls_connection.dtls_max_connection_lifetime = 0;
col->dtls_connection.dtls_connect_timeout = 30;
// we need aux_config for setting up a DTLS collector
if (!aux_config) {
return -1;
}
ipfix_aux_config_dtls *aux_config_dtls;
if (col->protocol == DTLS_OVER_SCTP) {
aux_config_dtls = &((ipfix_aux_config_dtls_over_sctp*)aux_config)->dtls;
} else if (col->protocol == DTLS_OVER_UDP) {
aux_config_dtls = &((ipfix_aux_config_dtls_over_udp*)aux_config)->dtls;
col->dtls_connection.dtls_max_connection_lifetime =
((ipfix_aux_config_dtls_over_udp*)aux_config)->max_connection_lifetime;
ipfix_aux_config_udp *aux_config_udp;
aux_config_udp = &((ipfix_aux_config_dtls_over_udp*)aux_config)->udp;
/* Sets col->mtu_mode and col->mtu */
set_mtu_config(col,aux_config_udp);
} else {
return -1;
}
if (aux_config_dtls->peer_fqdn)
col->dtls_connection.peer_fqdn = strdup(aux_config_dtls->peer_fqdn);
if (setup_dtls_connection(exporter,col,&col->dtls_connection.dtls_main)) {
/* failure */
free( (void *) col->dtls_connection.peer_fqdn);
col->dtls_connection.peer_fqdn = NULL;
return -1;
}
col->state = C_NEW; /* By setting the state to C_NEW we are
basically allocation the slot. */
col->last_reconnect_attempt_time = time(NULL);
/* col->state must *not* be C_UNUSED when we call
update_collector_mtu(). That's why we call this function
after setting state to C_NEW. */
if (update_collector_mtu(exporter, col)) {
/* update_collector_mtu calls remove_collector
in case of failure which in turn sets
col->state to C_UNUSED and frees col->peer_fqdn. */
return -1;
}
/* We have to call update_exporter_max_message_size()
* because update_collector_mtu *only* calls
* update_exporter_max_message_size() if the MTU
* mode is IPFIX_MTU_DISCOVER. */
update_exporter_max_message_size(exporter);
/* Initiate connection setup */
dtls_manage_connection(exporter, col);
return 0;
}
void ipfix_init_dtls_certificate(ipfix_exporter_certificate *certificate) {
certificate->ssl_ctx = NULL;
certificate->certificate_chain_file = NULL;
certificate->private_key_file = NULL;
certificate->ca_file = NULL;
certificate->ca_path = NULL;
}
void ipfix_clear_dtls_certificate(ipfix_exporter_certificate *certificate) {
deinit_openssl_ctx(certificate);
free( (void *) certificate->certificate_chain_file);
free( (void *) certificate->private_key_file);
free( (void *) certificate->ca_file);
free( (void *) certificate->ca_path);
}
/*!
* \brief Setup X.509 certificate used for authentication
*
* Set the the names of the files in which the X.509 certificate and the
* matching private key can be found. If private_key_file is NULL the
* certificate chain file will be searched for the private key. See OpenSSL
* man pages for more details
*
* The certificate can only be set once per exporter. Calling this function
* twice for the same exporter is an error. This function must not be called
* after the first DTLS connection has been set up.
*
* \param exporter pointer to previously initialized exporter struct
* \param certificate_chain_file name of file in which the certificate chain is
* stored in PEM format
* \param private_key_file name of file in which the private key is stored in
* PEM format. If NULL, the private key is read from certificate_chain_file.
* \return 0 success
* \return -1 failure
* \sa ipfix_set_ca_locations()
*/
int ipfix_set_dtls_certificate(ipfix_exporter_certificate *certificate,
const char *certificate_chain_file, const char *private_key_file) {
if (certificate->ssl_ctx) {
msg(MSG_ERROR, "Too late to set certificate. SSL context already created.");
return -1;
}
if (certificate->certificate_chain_file) {
msg(MSG_ERROR, "Certificate can not be reset.");
return -1;
}
if ( ! certificate_chain_file) {
msg(MSG_ERROR, "ipfix_set_dtls_certificate called with bad parameters.");
return -1;
}
certificate->certificate_chain_file = strdup(certificate_chain_file);
if (private_key_file) {
certificate->private_key_file = strdup(private_key_file);
}
return 0;
}
/*!
* \brief Set the locations of the CA certificates.
*
* See <tt>SSL_CTX_load_verify_locations(3)</tt> for more details. These
* locations can only be set once per exporter. Calling this function twice for
* the same exporter is an error.
*
* \param exporter pointer to previously initialized exporter struct
* \param ca_file All CA certificates found in this <em>file</em> are trusted
* for verification of the peer's certificate. This file has to be in PEM format.
* \param ca_path All CA certificates found in this <em>directory</em> are
* trusted for verification of the peer's certificate. See
* <tt>SSL_CTX_load_verify_locations(3)</tt> for details about how the files
* have to be named. All files need to be in PEM format.
* \return 0 success
* \return -1 failure
* \sa ipfix_set_dtls_certificate()
*/
int ipfix_set_ca_locations(ipfix_exporter_certificate *certificate, const char *ca_file, const char *ca_path) {
if (certificate->ssl_ctx) {
msg(MSG_ERROR, "Too late to set CA locations. SSL context already created.");
return -1;
}
if (certificate->ca_file || certificate->ca_path) {
msg(MSG_ERROR, "CA locations can not be reset.");
return -1;
}
if (ca_file) certificate->ca_file = strdup(ca_file);
if (ca_path) certificate->ca_path = strdup(ca_path);
return 0;
}
/*!
* \brief Should be called on a regular basis to push forward DTLS connection setup procedures.
*
* ipfixlolib depends on the user to call this function regularly
* if there is an ongoing connection setup procedure. This is
* necessary because<ul>
* <li>ipfixlolib is <em>not</em> allowed to block the calling thread,</li>
* <li>ipfixlolib does not run in a dedicated thread and</li>
* <li>a DTLS connection setup (i.e. handshake) may take an</li>
* extended period of time.
* </ul>
*
* \param exporter pointer to previously initialized exporter struct
* \return 1 this function should be called again after a short period of time
* \return 0 otherwise
* \sa ipfix_add_collector()
*/
int ipfix_dtls_advance_connections(ipfix_exporter *exporter) {
int ret = 0;
for (int i = 0; i < exporter->collector_max_num; i++) {
ipfix_receiving_collector *col = &exporter->collector_arr[i];
// is the collector a valid target?
if (col->state != C_UNUSED) {
if (col->protocol == DTLS_OVER_UDP ||
col->protocol == DTLS_OVER_SCTP) {
if (dtls_manage_connection(exporter,col))
ret = 1;
}
}
}
return ret;
}
#ifdef SUPPORT_DTLS_OVER_SCTP
/* Return values:
* -1 error
* 0 could not write because OpenSSL returned SSL_ERROR_WANT_READ
* n>0 number of bytes written
*/
static int dtls_over_sctp_send(
ipfix_exporter *exporter,
ipfix_receiving_collector *col,
const struct iovec *iov, int iovcnt, uint32_t pr_value) {
struct sctp_sndrcvinfo sinfo;
memset(&sinfo, 0, sizeof(struct sctp_sndrcvinfo));
sinfo.sinfo_timetolive = pr_value; // pr_value; FIXME
BIO_ctrl(SSL_get_wbio(col->dtls_main.ssl), BIO_CTRL_DGRAM_SCTP_SET_SNDINFO, sizeof(struct sctp_sndrcvinfo), &sinfo);
return dtls_send(exporter,col,iov,iovcnt);
}
#endif

View File

@ -0,0 +1,87 @@
#ifndef IPFIXLOLIB_DTLS_H
#define IPFIXLOLIB_DTLS_H
#include <time.h>
#include <openssl/ssl.h>
#include "ipfixlolib_config.h"
#ifdef __cplusplus
extern "C" {
#endif
#define IPFIX_DTLS_MAX_RECORD_LENGTH 16384
typedef struct {
int socket;
// uint16_t mtu;
SSL *ssl;
time_t last_reconnect_attempt_time;
} ipfix_dtls_connection;
typedef struct {
/* Time in seconds after which a DTLS connection
* will be replaced by a new one. */
unsigned dtls_max_connection_lifetime;
unsigned dtls_connect_timeout;
ipfix_dtls_connection dtls_main;
ipfix_dtls_connection dtls_replacement;
time_t connect_time; /* point in time when the connection setup
succeeded. We need this to calculate the
age of a connection. If DTLS is used,
a connection rollover is performed when
a connection reaches a certain age.*/
const char *peer_fqdn;
} ipfix_collector_dtls_connection;
typedef struct {
const char *peer_fqdn; /*!< The Fully Qualified Domain Name (FQDN) of the
peer. If set, the peer i.e. the Collector
<em>must</em> present a certificate of which
either the subject's Common Name (CN) or one of
the subject alternative names matches the FQDN.
There is no support for wildcard matching. For the
certificate verification to work, the user must
also call <tt>ipfix_set_ca_locations()</tt> in
advance to specify the locations of the root CA
certificates.
If set to NULL, anonymous cipher suites will be
added to the list of permissible cipher suites.
The identity of the peer will not be verified
then.*/
} ipfix_aux_config_dtls;
typedef struct {
ipfix_aux_config_dtls dtls; /*!< DTLS specific configuration */
ipfix_aux_config_udp udp; /*!< UDP specific configuration */
unsigned max_connection_lifetime; /*!< Time in seconds after which the DTLS
connection is replaced by a new one.
This mechanism aims to overcome the
dead peer problem.*/
} ipfix_aux_config_dtls_over_udp;
typedef struct {
ipfix_aux_config_dtls dtls; /*!< DTLS specific configuration */
} ipfix_aux_config_dtls_over_sctp;
typedef struct {
SSL_CTX *ssl_ctx;
const char *certificate_chain_file;
const char *private_key_file;
const char *ca_file;
const char *ca_path;
} ipfix_exporter_certificate;
void ipfix_init_dtls_certificate(ipfix_exporter_certificate *certificate);
void ipfix_clear_dtls_certificate(ipfix_exporter_certificate *certificate);
int ipfix_set_dtls_certificate(ipfix_exporter_certificate *certificate,
const char *certificate_chain_file, const char *private_key_file);
int ipfix_set_ca_locations(ipfix_exporter_certificate *certificate,
const char *ca_file, const char *ca_path);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,16 @@
#ifndef IPFIXLOLIB_DTLS_PRIVATE_H
#define IPFIXLOLIB_DTLS_PRIVATE_H
#include "ipfixlolib.h"
#include "ipfixlolib_dtls.h"
int dtls_manage_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col);
void deinit_openssl_ctx(ipfix_exporter_certificate *certificate);
int setup_dtls_connection(ipfix_exporter *exporter, ipfix_receiving_collector *col, ipfix_dtls_connection *con);
void dtls_shutdown_and_cleanup(ipfix_dtls_connection *con);
int dtls_send_templates(ipfix_exporter *exporter, ipfix_receiving_collector *col);
int dtls_send(ipfix_exporter *exporter, ipfix_receiving_collector *col, const struct iovec *iov, int iovcnt);
int add_collector_dtls(ipfix_exporter *exporter, ipfix_receiving_collector *col, void *aux_config);
int ipfix_dtls_advance_connections(ipfix_exporter *exporter);
#endif

View File

@ -0,0 +1,13 @@
#ifndef IPFIXLOLIB_PRIVATE_H
#define IPFIXLOLIB_PRIVATE_H
#include "ipfixlolib.h"
#include "ipfixlolib_config.h"
int enable_pmtu_discovery(int s);
int update_collector_mtu(ipfix_exporter *exporter, ipfix_receiving_collector *col);
void ipfix_update_header(ipfix_exporter *p_exporter, ipfix_receiving_collector *collector, ipfix_sendbuffer *sendbuf);
void set_mtu_config(ipfix_receiving_collector *col, ipfix_aux_config_udp *aux_config_udp);
void update_exporter_max_message_size(ipfix_exporter *exporter);
#endif

View File

@ -18,6 +18,7 @@
*
*/
#include "common/ipfixlolib/ipfixlolib_dtls.h"
#include "IpfixExporterCfg.h"
IpfixExporterCfg::IpfixExporterCfg(XMLElement* elem)

View File

@ -62,7 +62,8 @@ class IpfixReceiverDtlsUdpIpV4 : public IpfixReceiver, Sensor {
IpfixReceiverDtlsUdpIpV4(int port, const std::string ipAddr = "",
const std::string &certificateChainFile = "", const std::string &privateKeyFile = "",
const std::string &caFile = "", const std::string &caPath = "",
const std::set<string> &peerFqdns = std::set<string>(), const uint32_t buffer = 0);
const std::set<string> &peerFqdns = std::set<string>(), const uint32_t buffer = 0,
unsigned int moduleId = 0);
virtual ~IpfixReceiverDtlsUdpIpV4();
virtual void run();

View File

@ -21,6 +21,9 @@
#include "IpfixSender.hpp"
#include "common/ipfixlolib/ipfix.h"
#ifdef SUPPORT_DTLS
#include "common/ipfixlolib/ipfixlolib_dtls.h"
#endif
#include "common/msg.h"
#include "common/Time.h"
@ -94,14 +97,18 @@ IpfixSender::IpfixSender(uint32_t observationDomainId, uint32_t maxRecordRate,
private_key_file = privateKeyFile.c_str();
/* Private key will be searched for in the certificate chain file if
* no private key file is set */
#ifdef SUPPORT_DTLS
if (certificate_chain_file || private_key_file)
ipfix_set_dtls_certificate(ipfixExporter,
ipfix_set_dtls_certificate(&ipfixExporter->certificate,
certificate_chain_file, private_key_file);
#endif
if ( ! caFile.empty() ) ca_file = caFile.c_str();
if ( ! caPath.empty() ) ca_path = caPath.c_str();
#ifdef SUPPORT_DTLS
if (ca_file || ca_path)
ipfix_set_ca_locations(ipfixExporter, ca_file, ca_path);
ipfix_set_ca_locations(&ipfixExporter->certificate, ca_file, ca_path);
#endif
msg(MSG_DEBUG, "IpfixSender: running");
return;
@ -770,13 +777,11 @@ void IpfixSender::onSendRecordsTimeout(void) {
sendRecords(Always);
}
}
void IpfixSender::onBeatTimeout(void) {
ipfix_beat(ipfixExporter);
}
void IpfixSender::onTimeout(void* dataPtr)
{
onSendRecordsTimeout();
onBeatTimeout();
ipfix_beat(ipfixExporter);
registerBeatTimeout();
}

View File

@ -95,7 +95,6 @@ private:
void removeRecordReferences();
void registerTimeout();
void onSendRecordsTimeout(void);
void onBeatTimeout(void);
void registerBeatTimeout();
TemplateInfo::TemplateId getUnusedTemplateId();

View File

@ -54,3 +54,5 @@ IF (JOURNALD_FOUND)
TARGET_LINK_LIBRARIES(example_code ${JOURNALD_LIBRARIES})
TARGET_LINK_LIBRARIES(example_code_2 ${JOURNALD_LIBRARIES})
ENDIF (JOURNALD_FOUND)
ADD_TEST(example_2 example_code_2)

View File

@ -12,6 +12,7 @@
#include <stdio.h>
#include "common/ipfixlolib/ipfixlolib.h"
#include "common/ipfixlolib/ipfixlolib_config.h"
#include "common/ipfixlolib/encoding.h" // because we need htonll()
#include "common/msg.h"

View File

@ -15,6 +15,10 @@
#include <stdio.h>
#include <getopt.h>
#include "common/ipfixlolib/ipfixlolib.h"
#ifdef SUPPORT_DTLS
#include "common/ipfixlolib/ipfixlolib_dtls.h"
#endif
#include "common/ipfixlolib/ipfixlolib_config.h"
#include "common/ipfixlolib/encoding.h"
#include "common/msg.h"
@ -66,7 +70,6 @@ int parse_command_line_arguments(int argc, char **argv);
int add_collector(ipfix_exporter *exporter);
int get_sample_data1(meter_data *mdat);
int get_sample_data2(meter_data *mdat);
int set_config_on_exporter(ipfix_exporter *exporter);
void print_usage(char *argv0);
@ -80,6 +83,7 @@ int add_collector(ipfix_exporter *exporter) {
ipfix_aux_config_udp acu = {
.mtu = myconfig.mtu
};
#ifdef SUPPORT_DTLS
ipfix_aux_config_dtls_over_udp acdou = {
.udp = { .mtu = myconfig.mtu},
.dtls = { .peer_fqdn = myconfig.peer_fqdn}
@ -87,12 +91,15 @@ int add_collector(ipfix_exporter *exporter) {
ipfix_aux_config_dtls_over_sctp acdos = {
.dtls = { .peer_fqdn = myconfig.peer_fqdn}
};
#endif
if (myconfig.transport_protocol == UDP) {
aux_config = &acu;
#ifdef SUPPORT_DTLS
} else if (myconfig.transport_protocol == DTLS_OVER_UDP) {
aux_config = &acdou;
} else if (myconfig.transport_protocol == DTLS_OVER_SCTP) {
aux_config = &acdos;
#endif
}
/* The type of the 5th parameter to ipfix_add_collector() depends
* on the transport protocol that has been chose. */
@ -150,6 +157,16 @@ int get_sample_data2(meter_data *mdat) {
return 0;
}
int set_config_on_exporter(ipfix_exporter *exporter) {
int ret = 0;
#ifdef SUPPORT_DTLS
ret = ipfix_set_ca_locations(&exporter->certificate, myconfig.ca_file, myconfig.ca_path);
if (ret) return ret;
if (myconfig.certificate_chain_file)
ret = ipfix_set_dtls_certificate(&exporter->certificate, myconfig.certificate_chain_file, myconfig.private_key_file);
#endif
return ret;
}
int main(int argc, char **argv)
{
@ -504,7 +521,7 @@ out:
/* if you no longer need the exporter: free resources */
ret=ipfix_remove_collector(my_exporter, myconfig.coll_ip4_addr, myconfig.coll_port);
ipfix_deinit_exporter(my_exporter);
ipfix_deinit_exporter(&my_exporter);
printf("Done.\n");
exit(EXIT_SUCCESS);
@ -605,15 +622,6 @@ int parse_command_line_arguments(int argc, char **argv) {
return 0;
}
int set_config_on_exporter(ipfix_exporter *exporter) {
int ret = 0;
ret = ipfix_set_ca_locations(exporter, myconfig.ca_file, myconfig.ca_path);
if (ret) return ret;
if (myconfig.certificate_chain_file)
ret = ipfix_set_dtls_certificate(exporter, myconfig.certificate_chain_file, myconfig.private_key_file);
return ret;
}
void print_usage(char *argv0) {
printf(
"Usage: %s [OPTIONS]\n"

View File

@ -20,6 +20,10 @@
#include <stdio.h>
#include "common/ipfixlolib/ipfixlolib.h"
#include "common/ipfixlolib/ipfixlolib_config.h"
#ifdef SUPPORT_DTLS
#include "common/ipfixlolib/ipfixlolib_dtls.h"
#endif
#include "common/ipfixlolib/ipfix.h"
#include "common/msg.h"
@ -73,6 +77,7 @@ void add_udp_collector(ipfix_exporter *exporter) {
}
}
#ifdef SUPPORT_DTLS
void add_dtls_over_udp_collector(ipfix_exporter *exporter) {
ipfix_aux_config_dtls_over_udp acu = {
.udp = { .mtu = MTU},
@ -85,6 +90,7 @@ void add_dtls_over_udp_collector(ipfix_exporter *exporter) {
while(ipfix_beat(exporter))
usleep(10000);
}
#endif
int main(int argc, char **argv) {
int i;

View File

@ -8,6 +8,7 @@
#include <stdio.h>
#include <assert.h>
#include "common/ipfixlolib/ipfixlolib.h"
#include "common/ipfixlolib/ipfixlolib_config.h"
#include "common/msg.h"
#define MY_SOURCE_ID 70538