vermont/src/common/ipfixlolib/ipfixlolib.c

3573 lines
124 KiB
C

/* vim: set sts=4 sw=4 cindent nowrap: This modeline was added by Daniel Mentz */
/*
This file is part of the ipfixlolib.
Release under LGPL.
Header for encoding functions suitable for IPFIX
Changes by Daniel Mentz, 2009-01
Added support for DTLS over UDP and DTLS over SCTP
Changes by Gerhard Muenz, 2008-03
non-blocking SCTP socket
Changes by Alex Melnik, 2007-12
Added SCTP support
Corrected sequence number calculation
Changes by Gerhard Muenz, 2006-02-01
Changed and debugged sendbuffer structure and Co
Added new function for canceling data sets and deleting fields
Changes by Christoph Sommer, 2005-04-14
Modified ipfixlolib-nothread Rev. 80
Added support for DataTemplates (SetID 4)
Changes by Ronny T. Lampert, 2005-01
Changed 03-2005: Had to add a lot of casts for malloc() and friends
because of stricter C++ checking
Based upon the original
by Jan Petranek, University of Tuebingen
2004-11-12
jan@petranek.de
*/
#include "ipfixlolib.h"
#include "encoding.h"
#include "common/msg.h"
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef __linux__
/* Copied from linux/in.h */
#define IP_MTU 14
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define bit_set(data, bits) ((data & bits) == bits)
#if defined(SUPPORT_DTLS_OVER_SCTP) && !defined(OPENSSL_SCTP)
# 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_in serv_addr);
#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_in serv_addr);
static int enable_pmtu_discovery(int s);
static int ipfix_find_template(ipfix_exporter *exporter, uint16_t template_id);
static void ipfix_prepend_header(ipfix_exporter *p_exporter, int data_length, ipfix_sendbuffer *sendbuf);
static int ipfix_init_sendbuffer(ipfix_sendbuffer **sendbufn);
static int ipfix_reset_sendbuffer(ipfix_sendbuffer *sendbuf);
static int ipfix_deinit_sendbuffer(ipfix_sendbuffer **sendbuf);
static int ipfix_init_collector_array(ipfix_receiving_collector **col, int col_capacity);
static void remove_collector(ipfix_receiving_collector *collector);
static int ipfix_deinit_collector_array(ipfix_receiving_collector **col);
static int ipfix_init_send_socket(struct sockaddr_in serv_addr , enum ipfix_transport_protocol protocol);
static int ipfix_init_template_array(ipfix_exporter *exporter, int template_capacity);
static int ipfix_deinit_template(ipfix_exporter *exporter, ipfix_lo_template* templ);
static int ipfix_deinit_template_array(ipfix_exporter *exporter);
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_prepend_header(exporter,
exporter->template_sendbuffer->committed_data_length,
exporter->template_sendbuffer);
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 SCTP-over-DTLS collector.");
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;
int ret;
/* 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
ret = BIO_ctrl(bio,BIO_CTRL_DGRAM_MTU_DISCOVER,0,0);
/* Does not return useful value. But we still assign it
* to a variable to avoid compiler warnings. */
ret = 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);
#ifdef DEBUG
error = SSL_get_error(con->ssl,ret);
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()
*/
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 != T_UNUSED) {
if (col->protocol == DTLS_OVER_UDP ||
col->protocol == DTLS_OVER_SCTP) {
if (dtls_manage_connection(exporter,col))
ret = 1;
}
}
}
#endif
return ret;
}
/*
* Initializes a UDP-socket to send data to.
* Parameters:
* char* serv_ip4_addr IP-Address of the recipient (e.g. "123.123.123.123")
* serv_port the UDP port number of the server.
* Returns: a socket to write to. -1 on failure
*/
static int init_send_udp_socket(struct sockaddr_in serv_addr){
int s;
// create socket
if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
msg(MSG_FATAL, "error opening socket, %s", strerror(errno));
return -1;
}
// connect to server
if(connect(s, (struct sockaddr*)&serv_addr, sizeof(serv_addr) ) < 0) {
msg(MSG_FATAL, "connect failed, %s", strerror(errno));
/* clean up */
close(s);
return -1;
}
if(enable_pmtu_discovery(s)) {
close(s);
return -1;
}
return s;
}
static int enable_pmtu_discovery(int s) {
#ifdef IP_MTU_DISCOVER
// Linux
const int optval = IP_PMTUDISC_DO;
if (setsockopt(s,IPPROTO_IP,IP_MTU_DISCOVER,&optval,sizeof(optval))) {
msg(MSG_FATAL, "setsockopt(...,IP_MTU_DISCOVER,...) failed, %s", strerror(errno));
return -1;
}
#elif IP_DONTFRAG
// FreeBSD
const int optval = 1;
if (setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&optval,sizeof(optval))) {
msg(MSG_FATAL, "setsockopt(...,IP_DONTFRAG,...) failed, %s", strerror(errno));
return -1;
}
#endif
return 0;
}
static int get_mtu(const int s) {
#ifdef IP_MTU
int optval = 0;
socklen_t optlen = sizeof(optval);
if (getsockopt(s,IPPROTO_IP,IP_MTU,&optval,&optlen)) {
msg(MSG_FATAL, "getsockopt(...,IP_MTU,...) failed, %s", strerror(errno));
return -1;
} else
return optval;
#else
/* Should not be called if PMTU discovery is unsupported. */
return -1;
#endif
}
#ifdef SUPPORT_SCTP
/********************************************************************
** SCTP Extension Code:
*********************************************************************/
/*
* Initializes a SCTP socket to send data to.
* Parameters:
* char* serv_ip4_addr IP-Address of the recipient (e.g. "123.123.123.123")
* serv_port the SCTP port number of the server.
* Returns: a socket to write to. -1 on failure
*/
static int init_send_sctp_socket(struct sockaddr_in serv_addr){
int s;
//create socket:
DPRINTFL(MSG_VDEBUG, "Creating SCTP Socket ...");
if((s = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP)) < 0 ) {
msg(MSG_FATAL, "error opening SCTP socket, %s", strerror(errno));
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;
}
struct sctp_event_subscribe event;
memset(&event, 0, sizeof(event));
event.sctp_association_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;
}
// connect (non-blocking, i.e. handshake is initiated, not terminated)
if((connect(s, (struct sockaddr*)&serv_addr, sizeof(serv_addr) ) == -1) && (errno != EINPROGRESS)) {
msg(MSG_ERROR, "SCTP connect failed, %s", strerror(errno));
close(s);
return -1;
}
DPRINTFL(MSG_DEBUG, "SCTP Socket created");
return s;
}
/*
* modification of the original sctp_sendmsg to handle iovec structs
* Parameters:
* s : socket
* *vector : iovec struct containing the buffer to send
* v_len : length of the buffer
* *to : address where data is going to be sent
* tolen : length of the address
* ppid, flags, stream_no, timetolive, context : sctp parameters
*/
static int sctp_sendmsgv(int s, struct iovec *vector, int v_len, struct sockaddr *to,
socklen_t tolen, uint32_t ppid, uint32_t flags,
uint16_t stream_no, uint32_t timetolive, uint32_t context){
struct msghdr outmsg;
char outcmsg[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))];
struct cmsghdr *cmsg;
struct sctp_sndrcvinfo *sinfo;
char sendbuf[IPFIX_MAX_PACKETSIZE];
struct iovec iv;
outmsg.msg_name = to;
outmsg.msg_namelen = tolen;
/* Only IOV_MAX iovecs can be passed to sendmsg(). If we have more,
we copy all these iovecs into one buffer. */
if (v_len <= sysconf(_SC_IOV_MAX)) {
outmsg.msg_iov = vector;
outmsg.msg_iovlen = v_len;
} else {
int i;
int maxsendbuflen = sizeof(sendbuf);
char *sendbufcur = sendbuf;
/* Collect data form iovecs */
for (i=0;i<v_len;i++) {
if (sendbufcur + vector[i].iov_len > sendbuf + maxsendbuflen) {
msg(MSG_FATAL, "sendbuffer too small.");
return -1;
}
memcpy(sendbufcur,vector[i].iov_base,vector[i].iov_len);
sendbufcur+=vector[i].iov_len;
}
iv.iov_base = sendbuf;
iv.iov_len = sendbufcur - sendbuf;
outmsg.msg_iov = &iv;
outmsg.msg_iovlen = 1;
}
outmsg.msg_control = outcmsg;
outmsg.msg_controllen = sizeof(outcmsg);
outmsg.msg_flags = 0;
cmsg = CMSG_FIRSTHDR(&outmsg);
cmsg->cmsg_level = IPPROTO_SCTP;
cmsg->cmsg_type = SCTP_SNDRCV;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo));
outmsg.msg_controllen = cmsg->cmsg_len;
sinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
memset(sinfo, 0, sizeof(struct sctp_sndrcvinfo));
sinfo->sinfo_ppid = ppid;
sinfo->sinfo_flags = flags;
sinfo->sinfo_stream = stream_no;
sinfo->sinfo_timetolive = timetolive;
sinfo->sinfo_context = context;
return sendmsg(s, &outmsg, 0);
}
#endif /*SUPPORT_SCTP*/
/********************************************************************
//END of SCTP Extension Code:
*********************************************************************/
/*
* Initialize an exporter process
* Allocates all memory necessary.
* Parameters:
* sourceID The source ID, to which the exporter will be initialized to.
* exporter an ipfix_exporter* to be initialized
*/
/*!
* \brief Creates and initialize a new exporter.
* This function allocates memory for a new exporter struct and multiple buffers.
*
* Make sure to call <tt>ipfix_deinit_exporter()</tt> if the exporter is not needed any more in order to free the allocated memory.
*
* \param observation_domain_id the Observation Domain ID that will be included in every IPFIX Message Header
* \param exporter a pointer to a pointer to exporter struct
* \return 0 success
* \return -1 failure. Usually due to failed memory allocation requests.
* \sa ipfix_deinit_exporter()
*/
int ipfix_init_exporter(uint32_t observation_domain_id, ipfix_exporter **exporter)
{
ipfix_exporter *tmp;
int ret;
if(!(tmp=(ipfix_exporter *)malloc(sizeof(ipfix_exporter)))) {
goto out;
}
tmp->sequence_number = 0;
tmp->sn_increment = 0;
tmp->observation_domain_id=observation_domain_id;
tmp->max_message_size = UINT16_MAX;
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;
#endif
// initialize the sendbuffers
ret=ipfix_init_sendbuffer(&(tmp->data_sendbuffer));
if (ret != 0) {
msg(MSG_FATAL, "initializing data sendbuffer failed");
goto out1;
}
ret=ipfix_init_sendbuffer(&(tmp->template_sendbuffer));
if (ret != 0) {
msg(MSG_FATAL, "initializing template sendbuffer failed");
goto out2;
}
ret=ipfix_init_sendbuffer(&(tmp->sctp_template_sendbuffer));
if (ret != 0) {
msg(MSG_FATAL, "initializing sctp template sendbuffer failed");
goto out5;
}
// initialize the collectors to zero
ret=ipfix_init_collector_array( &(tmp->collector_arr), IPFIX_MAX_COLLECTORS);
if (ret !=0) {
msg(MSG_FATAL, "initializing collectors failed");
goto out3;
}
tmp->collector_max_num = IPFIX_MAX_COLLECTORS;
// initialize an array to hold the templates.
if(ipfix_init_template_array(tmp, IPFIX_MAX_TEMPLATES)) {
goto out4;
}
// we have not transmitted any templates yet!
tmp->last_template_transmission_time=0;
tmp->template_transmission_timer=IPFIX_DEFAULT_TEMPLATE_TIMER;
tmp->sctp_reconnect_timer=IPFIX_DEFAULT_SCTP_RECONNECT_TIMER;
tmp->sctp_lifetime=IPFIX_DEFAULT_SCTP_DATA_LIFETIME;
/* finally attach new exporter to the pointer we were given */
*exporter=tmp;
return 0;
out5:
ipfix_deinit_sendbuffer(&(tmp->sctp_template_sendbuffer));
out4:
ipfix_deinit_collector_array(&(tmp->collector_arr));
out3:
ipfix_deinit_sendbuffer(&(tmp->data_sendbuffer));
out2:
ipfix_deinit_sendbuffer(&(tmp->template_sendbuffer));
out1:
free(tmp);
out:
/* we have nothing to free */
return -1;
}
/*!
* \brief Cleanup previously initialized exporter and free memory.
*
* All Collectors which are associated with this Exporter are cleaned up as
* well.
*
* \param exporter pointer to exporter struct
* \return 0 success
* \sa ipfix_init_exporter()
*/
int ipfix_deinit_exporter(ipfix_exporter *exporter) {
// cleanup processes
int ret;
// close sockets etc.
// (currently, nothing to do)
// free all children
// deinitialize array to hold the templates.
ret=ipfix_deinit_template_array(exporter);
/* free ( (**exporter).template_arr); */
/* (**exporter).template_arr = NULL; */
// deinitialize the sendbuffers
ret=ipfix_deinit_sendbuffer(&(exporter->data_sendbuffer));
ret=ipfix_deinit_sendbuffer(&(exporter->template_sendbuffer));
ret=ipfix_deinit_sendbuffer(&(exporter->sctp_template_sendbuffer));
// find the collector in the exporter
int i=0;
for(i=0;i<exporter->collector_max_num;i++) {
if (exporter->collector_arr[i].state != C_UNUSED)
remove_collector(&exporter->collector_arr[i]);
}
// deinitialize the collectors
ret=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);
#endif
// free own memory
free(exporter);
exporter=NULL;
return 0;
}
static void update_exporter_max_message_size(ipfix_exporter *exporter) {
ipfix_receiving_collector *col;
int i;
uint16_t max_message_size;
max_message_size = UINT16_MAX;
for(i=0;i<exporter->collector_max_num;i++) {
col = &exporter->collector_arr[i];
if(col->state != C_UNUSED &&
(col->protocol == UDP || col->protocol == DTLS_OVER_UDP)) {
uint16_t maxsize = 0;
switch (col->protocol) {
case UDP:
/* IP header: 20 bytes
* UDP header: 8 bytes */
maxsize = col->mtu - (20 + 8);
break;
#ifdef SUPPORT_DTLS
case DTLS_OVER_UDP:
/* DTLS record header:
* ContentType: 1 byte
* ProtocolVersion: 2 bytes
* epoch: 2 bytes
* seqno: 6 bytes
* length: 2 bytes
* (assuming GenericBlockCipher, AES_256_CBC and SHA256)
* IV: 16 bytes
* MAC: 32 bytes
* padding: 15 bytes (worst case)
* padding_length: 1 byte */
maxsize = col->mtu - 77 - (20 + 8);
/* TODO: Find out maximum size of payload */
if (maxsize > IPFIX_DTLS_MAX_RECORD_LENGTH)
maxsize = IPFIX_DTLS_MAX_RECORD_LENGTH;
break;
#endif
default:
break;
}
if (max_message_size > maxsize)
max_message_size = maxsize;
}
}
exporter->max_message_size = max_message_size;
DPRINTF("New exporter max_message_size: %u",max_message_size);
}
/* Gets MTU estimate of collector.
* Calls update_exporter_max_message_size()
* Calls remove_collector() if an error occurs.
*/
static 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);
DPRINTF("get_mtu() returned %d",mtu);
if (mtu<0) {
remove_collector(col);
return -1;
}
col->mtu = mtu;
update_exporter_max_message_size(exporter);
#ifdef SUPPORT_DTLS
} else if (col->protocol == DTLS_OVER_UDP && col->mtu_mode == IPFIX_MTU_DISCOVER) {
int mtu = -1;
int mtu_ssl;
int mtu_bio;
if (col->dtls_main.ssl) {
mtu_ssl = col->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);
DPRINTF("MTU got from BIO object: %d",mtu_bio);
if (mtu_bio > 0 && (mtu == -1 || mtu_bio < mtu)) mtu = mtu_bio;
}
if (mtu>0) {
/* OpenSSL defines the MTU as the maximum payload length
* of UDP datagrams.
* We define MTU differently: the maximum size of an IP packet.
* The difference is 28 bytes (20 bytes for the IP header and
* 8 bytes for the UDP header) */
col->mtu = mtu + 20 + 8;
update_exporter_max_message_size(exporter);
} else {
DPRINTF("Unable to get MTU from SSL object.");
remove_collector(col);
return -1;
}
#endif
}
return 0;
}
static ipfix_receiving_collector *get_free_collector_slot(ipfix_exporter *exporter) {
ipfix_receiving_collector *collector;
int i;
for(i=0;i<exporter->collector_max_num;i++) {
collector = &exporter->collector_arr[i];
if(collector->state == C_UNUSED)
return collector;
}
return NULL;
}
static int add_collector_datafile(ipfix_receiving_collector *collector, const char *basename, uint32_t maxfilesize) {
collector->ipv4address[0] = '\0';
collector->port_number = 0;
collector->data_socket = -1;
memset(&(collector->addr), 0, sizeof(collector->addr));
collector->last_reconnect_attempt_time = 0;
collector->basename = strdup(basename);
collector->filenum = -1;
collector->maxfilesize = maxfilesize;
ipfix_new_file(collector);
collector->state = C_CONNECTED;
return 0;
}
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
static int add_collector_rawdir(ipfix_receiving_collector *collector,char *path) {
collector->ipv4address[0] = '\0';
collector->port_number = 0;
collector->data_socket = -1;
memset(&(collector->addr), 0, sizeof(collector->addr));
collector->last_reconnect_attempt_time = 0;
collector->packet_directory_path = strdup(coll_ip4_addr);
collector->packets_written = 0;
collector->state = C_CONNECTED;
return 0;
}
#endif
static void set_mtu_config(ipfix_receiving_collector *col,
ipfix_aux_config_udp *aux_config_udp) {
if (aux_config_udp->mtu>0) {
col->mtu_mode = IPFIX_MTU_FIXED;
col->mtu = aux_config_udp->mtu;
} else {
col->mtu_mode = IPFIX_MTU_MODE_DEFAULT;
col->mtu = IPFIX_MTU_DEFAULT;
}
}
#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;
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
*/
static int add_collector_remaining_protocols(
ipfix_exporter *exporter,
ipfix_receiving_collector *col,
void *aux_config) {
if (col->protocol == UDP) {
ipfix_aux_config_udp *aux_config_udp;
aux_config_udp = ((ipfix_aux_config_udp*)aux_config);
/* Sets col->mtu_mode and col->mtu */
set_mtu_config(col,aux_config_udp);
}
// call a separate function for opening the socket.
// This function can handle both UDP and SCTP sockets.
col->data_socket = ipfix_init_send_socket(col->addr, col->protocol);
// error handling, in case we were unable to open the port:
if(col->data_socket < 0 ) {
msg(MSG_ERROR, "add collector, initializing socket failed");
return -1;
}
// now, we may set the collector to valid;
if (col->protocol == UDP)
col->state = C_CONNECTED; /* UDP sockets are connected from the very
beginning. */
else
col->state = C_NEW; /* By setting the state to C_NEW we are
basically allocation the slot. */
/* 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. */
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);
col->last_reconnect_attempt_time = time(NULL);
return 0;
}
static int valid_transport_protocol(enum ipfix_transport_protocol p) {
switch(p) {
case DTLS_OVER_UDP:
#ifdef SUPPORT_DTLS
return 1;
#else
msg(MSG_FATAL, "Library compiled without DTLS support.");
return 0;
#endif
case DTLS_OVER_SCTP:
#ifdef SUPPORT_DTLS_OVER_SCTP
return 1;
#else
msg(MSG_FATAL, "Library compiled without DTLS over SCTP support.");
return 0;
#endif
case SCTP:
#ifdef SUPPORT_SCTP
return 1;
#else
msg(MSG_FATAL, "Library compiled without SCTP support.");
return 0;
#endif
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
case RAWDIR:
return 1;
#else
msg(MSG_FATAL, "Library compiled without RAWDIR support.");
return 0;
#endif
case TCP:
msg(MSG_FATAL, "Transport Protocol TCP not implemented");
return 0;
case UDP:
case DATAFILE:
return 1;
default:
msg(MSG_FATAL, "Transport Protocol not supported");
return 0;
}
}
/*!
* \brief Add a Collector to the given Exporter and trigger connection setup.
*
* Up to IPFIX_MAX_COLLECTORS Collectors may be added to a single Exporter.
* Data Records are sent to all Collectors in parallel. All active Templates
* are transmitted to a given Collector before Data Records are sent to the
* this Collector.
*
* If one of the DTLS variants is chosen as the transport protocol, a DTLS
* handshake with the Collector is initiated. This handshake procedure may
* take a while. Because this function never blocks the calling thread, it
* usually returns before the handshake is completed. In order to give this
* library a chance to complete the ongoing handshake, the user should call
* <tt>ipfix_beat()</tt> regularly (e.g., every 10 milliseconds).
*
* Depending on the transport protocol, additional configuration data
* must be passed via the <tt>aux_config</tt> parameter which in turn points to
* an instance of one of the following types. Check the documentation of the
* individual types in order to learn about import details which are specific
* to the chosen transport protocol.
* <table><tr><td><em>transport protocol</em></td><td><em>type of *aux_config</em></td></tr>
* <tr><td>RAWDIR</td><td>NULL</td></tr>
* <tr><td>SCTP</td><td>NULL</td></tr>
* <tr><td>UDP</td><td>ipfix_aux_config_udp</td></tr>
* <tr><td>DTLS_OVER_UDP</td><td>ipfix_aux_config_dtls_over_udp</td></tr>
* <tr><td>DTLS_OVER_SCTP</td><td>ipfix_aux_config_dtls_over_sctp</td></tr>
* </table>
* \param exporter pointer to previously initialized exporter struct
* \param coll_ip4_addr IP address of receiving Collector in dotted notation (e.g. "1.2.3.4")
* \param coll_port port number of receiving Collector
* \param proto transport protocol to use, either RAWDIR, SCTP, UDP,
* DTLS_OVER_UDP or DTLS_OVER_SCTP. See <tt>\ref ipfix_transport_protocol</tt> for
* more details.
* \param aux_config auxiliary configuration data. The type of the data structure depends on the
* transport protocol chosen. See above.
* \return 0 success
* \return -1 failure
* \sa ipfix_remove_collector()
*/
int ipfix_add_collector(ipfix_exporter *exporter, const char *coll_ip4_addr,
int coll_port, enum ipfix_transport_protocol proto, void *aux_config)
{
// check, if exporter is valid
if(exporter == NULL) {
msg(MSG_FATAL, "add_collector, exporter is NULL");
return -1;
}
if ( ! valid_transport_protocol(proto)) return -1;
// get free slot
ipfix_receiving_collector *collector = get_free_collector_slot(exporter);
if( ! collector) {
msg(MSG_FATAL, "no more free slots for new collectors available, limit %d reached",
exporter->collector_max_num
);
return -1;
}
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
/* It is the duty of add_collector_rawdir to set collector->state */
if (proto==RAWDIR) return add_collector_rawdir(collector,coll_ip4_addr);
#endif
if (proto==DATAFILE) return add_collector_datafile(collector, coll_ip4_addr, coll_port);
/*
FIXME: only a quick fix to make that work
Must be copied, else pointered data must be around forever
Better use binary/u_int32_t representation
*/
strncpy(collector->ipv4address, coll_ip4_addr, sizeof(collector->ipv4address));
/* strncpy does not null terminate the destination char array if the
* length of the source string is equal or greater then the maximum length
* (third parameter) */
collector->ipv4address[sizeof(collector->ipv4address)-1] = '\0';
collector->port_number = coll_port;
collector->protocol = proto;
memset(&(collector->addr), 0, sizeof(collector->addr));
collector->addr.sin_family = AF_INET;
collector->addr.sin_port = htons(coll_port);
collector->addr.sin_addr.s_addr = inet_addr(coll_ip4_addr);
#ifdef SUPPORT_DTLS
/* It is the duty of add_collector_dtls to set collector->state */
if (proto == DTLS_OVER_UDP || proto == DTLS_OVER_SCTP)
return add_collector_dtls(exporter, collector, aux_config);
#endif
return add_collector_remaining_protocols(exporter, collector, aux_config);
}
static void remove_collector(ipfix_receiving_collector *collector) {
DPRINTF("Removing 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);
}
collector->peer_fqdn = NULL;
#endif
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
if (collector->protocol != RAWDIR) {
#endif
if ( collector->data_socket != -1) {
DPRINTF("Closing data socket");
close ( collector->data_socket );
}
collector->data_socket = -1;
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
}
if (collector->protocol == RAWDIR) {
free(collector->packet_directory_path);
}
#endif
if (collector->protocol == DATAFILE) {
free(collector->basename);
}
collector->state = C_UNUSED;
}
/*!
* \brief Remove a Collector from the exporting process
*
* This call removes the given Collector from the set of Collectors. This
* includes shutting down the transport connection.
*
* \param exporter pointer to previously initialized exporter struct
* \param coll_ip4_addr IP address of receiving Collector in dotted notation (e.g. "1.2.3.4")
* \param coll_port port number of receiving Collector
* \return 0 success
* \return -1 failure
* \sa ipfix_add_collector()
*/
/*
*/
int ipfix_remove_collector(ipfix_exporter *exporter, const char *coll_ip4_addr, int coll_port) {
int i;
for(i=0;i<exporter->collector_max_num;i++) {
ipfix_receiving_collector *collector = &exporter->collector_arr[i];
if( ( strcmp( collector->ipv4address, coll_ip4_addr) == 0 )
&& collector->port_number == coll_port) {
remove_collector(collector);
return 0;
}
}
msg(MSG_ERROR, "remove_collector, exporter %s not found", coll_ip4_addr);
return -1;
}
/************************************************************************************/
/* Template management */
/************************************************************************************/
/*
* Helper function: Finds a template in the exporter
* Parameters:
* exporter: Exporter to search for the template
* template_id: ID of the template we search
* Returns: the index of the template in the exporter or -1 on failure.
*/
static int ipfix_find_template(
ipfix_exporter *exporter,
uint16_t template_id) {
DPRINTFL(MSG_VDEBUG, "ipfix_find_template with ID: %d",template_id);
int i=0;
for (;i<exporter->ipfix_lo_template_maxsize;i++) {
if(exporter->template_arr[i].state != T_UNUSED &&
exporter->template_arr[i].template_id == template_id) {
DPRINTFL(MSG_VDEBUG,
"ipfix_find_template with ID: %d, validity %d found at %d",
template_id, exporter->template_arr[i].state, i);
return i;
}
}
return -1;
}
static int ipfix_get_free_template_slot(ipfix_exporter *exporter) {
DPRINTFL(MSG_VDEBUG, "ipfix_get_free_template_slot");
int i=0;
for (;i<exporter->ipfix_lo_template_maxsize;i++) {
if(exporter->template_arr[i].state == T_UNUSED) {
DPRINTFL(MSG_VDEBUG, "ipfix_get_free_template_slot found at %d",i);
return i;
}
}
DPRINTFL(MSG_DEBUG, "ipfix_get_free_template_slot failed.");
return -1;
}
/*!
* \brief Remove a Template from the Exporting process but create a withdrawal message first
*
* This will free the templates data store!
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID of the template to remove
* \return 0 success
* \return -1 failure
* \sa ipfix_start_template()
*/
/*
*/
int ipfix_remove_template(ipfix_exporter *exporter, uint16_t template_id) {
int found_index = ipfix_find_template(exporter,template_id);
if (found_index < 0) {
msg(MSG_ERROR, "remove_template ID %u not found", template_id);
return -1;
}
if(exporter->template_arr[found_index].state == T_SENT){
DPRINTFL(MSG_VDEBUG,
"creating withdrawal msg for ID: %d, validity %d",
template_id, exporter->template_arr[found_index].state);
char *p_pos;
char *p_end;
// write the withdrawal message fields into the buffer
// beginning of the buffer
p_pos = exporter->template_arr[found_index].template_fields;
// end of the buffer since the WITHDRAWAL message for one template is always 8 byte
p_end = p_pos + 8;
// set ID is 2 for a template, 4 for a template with fixed fields:
// for withdrawal messages we keep the template set ID
p_pos += 2;
// write 8 to the length field
write_unsigned16 (&p_pos, p_end, 8);
// keep the template ID:
p_pos += 2;
// write 0 for the field count, since it indicates that this is a withdrawal message
write_unsigned16 (&p_pos, p_end, 0);
exporter->template_arr[found_index].fields_length = 8;
exporter->template_arr[found_index].field_count = 0;
exporter->template_arr[found_index].fixedfield_count = 0;
exporter->template_arr[found_index].fields_added = 0;
exporter->template_arr[found_index].state = T_WITHDRAWN;
DPRINTFL(MSG_VDEBUG, "... Withdrawn");
} else {
ipfix_deinit_template(exporter, &(exporter->template_arr[found_index]) );
}
return 0;
}
/************************************************************************************/
/* End of Template management */
/************************************************************************************/
/*
* Prepends an ipfix message header to the sendbuffer
*
* One could argue that this function should be called ipfix_update_header
* as the header is already there. The data it contains is just incorrect and
* needs to be updated. No new header is prepended.
*
* The ipfix message header is set according to:
* - the exporter ( Source ID and sequence number)
* - the length of the contained data
* - the current system time
* - the ipfix version number
*
* Note: the first HEADER_USED_IOVEC_COUNT iovec struct are reserved for the header! These will be overwritten!
*/
static void ipfix_prepend_header(ipfix_exporter *p_exporter, int data_length, ipfix_sendbuffer *sendbuf)
{
time_t export_time;
uint16_t total_length = 0;
// did the user set the data_length field?
if (data_length != 0) {
total_length = data_length + sizeof(ipfix_header);
} else {
// compute it on our own:
// sum up all lengths in the iovecs:
int i;
// start the loop with 1, as 0 is reserved for the header!
for (i = 1; i< sendbuf->current; i++) {
total_length += sendbuf->entries[i].iov_len;
}
// add the header length to the total length:
total_length += sizeof(ipfix_header);
}
// write the length into the header
(sendbuf->packet_header).length = htons(total_length);
// write version number and source ID and sequence number
(sendbuf->packet_header).version = htons(IPFIX_VERSION_NUMBER);
(sendbuf->packet_header).observation_domain_id = htonl(p_exporter->observation_domain_id);
(sendbuf->packet_header).sequence_number = htonl(p_exporter->sequence_number);
// get the export time:
export_time = time(NULL);
if(export_time == (time_t)-1) {
// survive
export_time=0;
msg(MSG_ERROR,"prepend_header, time() failed, using %d", export_time);
}
// global_last_export_time = (uint32_t) export_time;
(sendbuf->packet_header).export_time = htonl((uint32_t)export_time);
}
/*create a new filehandle and set recvcoll->fh, recvcoll->byteswritten, recvcoll->filenum
* to their new values
* returns the newly created filehandle*/
static int ipfix_new_file(ipfix_receiving_collector* recvcoll){
int f = -1;
if (recvcoll->fh > 0) close(recvcoll->fh);
recvcoll->filenum++;
recvcoll->bytes_written = 0;
/*11 == maximum length of uint32_t including terminating \0*/
char *filename = malloc(sizeof(char)*(strlen(recvcoll->basename)+11));
if(! filename){
msg(MSG_ERROR, "could not malloc filename\n");
goto out;
}
sprintf(filename, "%s%010d", recvcoll->basename, recvcoll->filenum);
while(1){
f = open(filename, O_WRONLY | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP);
if (f<0) {
if (errno == EEXIST){ //increase the filenumber and try again
recvcoll->filenum++; //if the current file already exists
msg(MSG_VDEBUG, "Skipping %s", filename);
sprintf(filename, "%s%010d", recvcoll->basename, recvcoll->filenum);
continue;
}
msg(MSG_ERROR, "could not open DATAFILE file %s", filename);
f = -1;
goto out;
}
break;
}
msg(MSG_INFO, "Created new file: %s", filename);
out:
free(filename);
recvcoll->fh = f;
return f;
}
/*
* Create and initialize an ipfix_sendbuffer for at most maxelements
* Parameters: ipfix_sendbuffer** sendbuf pointer to a pointer to an ipfix-sendbuffer
*/
static int ipfix_init_sendbuffer(ipfix_sendbuffer **sendbuf)
{
ipfix_sendbuffer *tmp;
// mallocate memory for the sendbuffer
if(!(tmp=(ipfix_sendbuffer *)malloc(sizeof(ipfix_sendbuffer)))) {
goto out;
}
tmp->current = HEADER_USED_IOVEC_COUNT; // leave the 0th field blank for the header
tmp->committed = HEADER_USED_IOVEC_COUNT;
tmp->marker = HEADER_USED_IOVEC_COUNT;
tmp->committed_data_length = 0;
// init and link packet header
memset(&(tmp->packet_header), 0, sizeof(ipfix_header));
tmp->entries[0].iov_len = sizeof(ipfix_header);
tmp->entries[0].iov_base = &(tmp->packet_header);
// initialize an ipfix_set_manager
(tmp->set_manager).set_counter = 0;
memset(&(tmp->set_manager).set_header_store, 0, sizeof((tmp->set_manager).set_header_store));
(tmp->set_manager).data_length = 0;
*sendbuf=tmp;
return 0;
//out1:
free(tmp);
out:
return -1;
}
/*
* reset ipfix_sendbuffer
* Resets the contents of an ipfix_sendbuffer, so the sendbuffer can again
* be filled with data.
* (Present headers are also purged).
*/
static int ipfix_reset_sendbuffer(ipfix_sendbuffer *sendbuf)
{
sendbuf->current = HEADER_USED_IOVEC_COUNT;
sendbuf->committed = HEADER_USED_IOVEC_COUNT;
sendbuf->marker = HEADER_USED_IOVEC_COUNT;
sendbuf->committed_data_length = 0;
memset(&(sendbuf->packet_header), 0, sizeof(ipfix_header));
// also reset the set_manager!
(sendbuf->set_manager).set_counter = 0;
memset(&(sendbuf->set_manager).set_header_store, 0, sizeof((sendbuf->set_manager).set_header_store));
(sendbuf->set_manager).data_length = 0;
return 0;
}
/*
* Deinitialize (free) an ipfix_sendbuffer
*/
static int ipfix_deinit_sendbuffer(ipfix_sendbuffer **sendbuf)
{
// free the sendbuffer itself:
free(*sendbuf);
*sendbuf = NULL;
return 0;
}
/*
* initialize array of collectors
* Allocates memory for an array of collectors
* Parameters:
* col: collector array to initialize
* col_capacity: maximum amount of collectors to store in this array
*/
static int ipfix_init_collector_array(ipfix_receiving_collector **col, int col_capacity)
{
int i;
ipfix_receiving_collector *tmp;
tmp=(ipfix_receiving_collector *)malloc((sizeof(ipfix_receiving_collector) * col_capacity));
if(!tmp) {
return -1;
}
for (i = 0; i< col_capacity; i++) {
ipfix_receiving_collector *c = &tmp[i];
c->state = C_UNUSED;
c->ipv4address[0] = '\0';
c->port_number = 0;
c->protocol = 0;
c->data_socket = -1;
c->last_reconnect_attempt_time = 0;
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
c->packet_directory_path = NULL:
c->packets_written = 0;
#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;
#endif
}
*col=tmp;
return 0;
}
/*
* deinitialize an array of collectors
* Parameters:
* col: collector array to clean up
*/
static int ipfix_deinit_collector_array(ipfix_receiving_collector **col)
{
free(*col);
*col=NULL;
return 0;
}
/*
* Initializes a send socket
* Parameters:
* serv_ip4_addr of the recipient in dot notation
* serv_port: port
* protocol: transport protocol
*/
static int ipfix_init_send_socket(struct sockaddr_in serv_addr, enum ipfix_transport_protocol protocol)
{
int sock = -1;
switch(protocol) {
case UDP:
case DTLS_OVER_UDP:
sock= init_send_udp_socket( serv_addr );
break;
#ifdef SUPPORT_SCTP
case SCTP:
case DTLS_OVER_SCTP:
sock= init_send_sctp_socket( serv_addr );
break;
#endif
default:
return -1; /* Should not occur since we check the transport
protocol in valid_transport_protocol()*/
}
return sock;
}
/*
* initialize array of templates
* Allocates memory for an array of templates
* Parameters:
* exporter: exporter, whose template array we'll initialize
* template_capacity: maximum amount of templates to store in this array
*/
static int ipfix_init_template_array(ipfix_exporter *exporter, int template_capacity)
{
int i;
// allocate the memory for template_capacity elements:
exporter->ipfix_lo_template_maxsize = template_capacity;
exporter->template_arr = (ipfix_lo_template*) malloc (template_capacity * sizeof(ipfix_lo_template) );
for(i = 0; i< template_capacity; i++) {
exporter->template_arr[i].state = T_UNUSED;
}
return 0;
}
/*
* deinitialize an array of templates
* Parameters:
* exporter: exporter, whose template store will be purged
*/
static int ipfix_deinit_template_array(ipfix_exporter *exporter)
{
/* FIXME: free all templates in the array!
This was our memory leak.
JanP, 2005-21-1
*/
int ret, i;
for(i=0; i< exporter->ipfix_lo_template_maxsize; i++) {
// if template was sent we need a withdrawal message first
if (exporter->template_arr[i].state == T_SENT){
ipfix_remove_template(exporter, exporter->template_arr[i].template_id );
}
}
// send all created withdrawal messages
ipfix_send_templates(exporter);
for(i=0; i< exporter->ipfix_lo_template_maxsize; i++) {
// try to free all templates:
ret = ipfix_deinit_template(exporter, &(exporter->template_arr[i]) );
// for debugging:
DPRINTFL(MSG_VDEBUG, "deinitialized template %i with success %i ", i, ret);
// end debugging
}
free(exporter->template_arr);
exporter->template_arr = NULL;
exporter->ipfix_lo_template_maxsize = 0;
return 0;
}
/*
* Updates the template sendbuffers
* will be called, after a template has been added or removed
*
* Watch out: By calling this function you undertake a commitment!
* The commitment is that you send out sctp_sendbuf to all SCTP collectors!
* This function alters the state of all templates that are
* in state T_COMMITED to state T_SENT. In the SCTP case templates
* will be copied to sctp_sendbuf only once and this is when this state
* transition takes place.
* So if you call this function and do not send out sctp_sendbuf afterwards
* the affected templates will never be sent because this state transition
* takes place only once.
*/
static int ipfix_update_template_sendbuffer (ipfix_exporter *exporter)
{
int i;
ipfix_sendbuffer* t_sendbuf = exporter->template_sendbuffer;
ipfix_sendbuffer* sctp_sendbuf = exporter->sctp_template_sendbuffer;
// clean the template sendbuffers
ipfix_reset_sendbuffer(t_sendbuf);
ipfix_reset_sendbuffer(sctp_sendbuf);
// place all valid templates into the template sendbuffer
// could be done just like put_data_field:
for (i = 0; i < exporter->ipfix_lo_template_maxsize; i++ ) {
switch (exporter->template_arr[i].state) {
case (T_TOBEDELETED):
// free memory and mark T_UNUSED
ipfix_deinit_template(exporter, &(exporter->template_arr[i]) );
break;
case (T_COMMITED): // send to SCTP and UDP collectors and mark as T_SENT
if (sctp_sendbuf->current >= IPFIX_MAX_SENDBUFSIZE-2 ) {
msg(MSG_ERROR, "SCTP template sendbuffer too small to handle more than %i entries", sctp_sendbuf->current);
return -1;
}
if (t_sendbuf->current >= IPFIX_MAX_SENDBUFSIZE-2 ) {
msg(MSG_ERROR, "UDP template sendbuffer too small to handle more than %i entries", t_sendbuf->current);
return -1;
}
sctp_sendbuf->entries[ sctp_sendbuf->current ].iov_base = exporter->template_arr[i].template_fields;
sctp_sendbuf->entries[ sctp_sendbuf->current ].iov_len = exporter->template_arr[i].fields_length;
sctp_sendbuf->current++;
sctp_sendbuf->committed_data_length += exporter->template_arr[i].fields_length;
t_sendbuf->entries[ t_sendbuf->current ].iov_base = exporter->template_arr[i].template_fields;
t_sendbuf->entries[ t_sendbuf->current ].iov_len = exporter->template_arr[i].fields_length;
t_sendbuf->current++;
t_sendbuf->committed_data_length += exporter->template_arr[i].fields_length;
exporter->template_arr[i].state = T_SENT;
break;
case (T_SENT): // only to UDP collectors
if (t_sendbuf->current >= IPFIX_MAX_SENDBUFSIZE-2 ) {
msg(MSG_ERROR, "UDP template sendbuffer too small to handle more than %i entries", t_sendbuf->current);
return -1;
}
t_sendbuf->entries[ t_sendbuf->current ].iov_base = exporter->template_arr[i].template_fields;
t_sendbuf->entries[ t_sendbuf->current ].iov_len = exporter->template_arr[i].fields_length;
t_sendbuf->current++;
t_sendbuf->committed_data_length += exporter->template_arr[i].fields_length;
break;
case (T_WITHDRAWN): // put the SCTP withdrawal message and mark T_TOBEDELETED
if (sctp_sendbuf->current >= IPFIX_MAX_SENDBUFSIZE-2 ) {
msg(MSG_ERROR, "SCTP template sendbuffer too small to handle more than %i entries", sctp_sendbuf->current);
return -1;
}
sctp_sendbuf->entries[ sctp_sendbuf->current ].iov_base = exporter->template_arr[i].template_fields;
sctp_sendbuf->entries[ sctp_sendbuf->current ].iov_len = exporter->template_arr[i].fields_length;
sctp_sendbuf->current++;
sctp_sendbuf->committed_data_length += exporter->template_arr[i].fields_length;
/* ASK: Why don't we just delete the template? */
exporter->template_arr[i].state = T_TOBEDELETED;
DPRINTFL(MSG_VDEBUG, "Withdrawal for template ID: %d added to sctp_sendbuffer", exporter->template_arr[i].template_id);
break;
default : // Do nothing : T_UNUSED or T_UNCLEAN
break;
}
} // end loop over all templates
// that's it!
return 0;
}
#ifdef SUPPORT_SCTP
/*
* function used by SCTP to reconnect to a collector, if connection
* was lost. After successful reconnection resend all active templates.
* i: index of the collector in the exporters collector_arr
*/
static int sctp_reconnect(ipfix_exporter *exporter , int i){
int bytes_sent, ret, error;
socklen_t len;
fd_set readfds;
struct timeval timeout;
time_t time_now = time(NULL);
struct sctp_status ss;
union sctp_notification snp;
struct sctp_assoc_change *sac;
struct msghdr msg;
struct iovec iv;
ssize_t r;
exporter->collector_arr[i].last_reconnect_attempt_time = time_now;
// error occurred while being connected?
if(exporter->collector_arr[i].state == C_CONNECTED) {
// the socket has not yet been closed
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
}
// create new socket if not yet done
if(exporter->collector_arr[i].data_socket < 0) {
exporter->collector_arr[i].data_socket = init_send_sctp_socket( exporter->collector_arr[i].addr );
if( exporter->collector_arr[i].data_socket < 0) {
msg(MSG_ERROR, "SCTP socket creation in reconnect failed, %s", strerror(errno));
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
exporter->collector_arr[i].state = C_NEW;
}
/* Determine whether socket is readable.
If it is readable, we can query the result of the connection
setup. The result can be either success of failure.
If the socket is not yet readable, the connection setup is
still ongoing.
*/
/* We don't want select() to wait but to return immediately.
Set timeout to 0. */
timeout.tv_sec = timeout.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(exporter->collector_arr[i].data_socket, &readfds);
ret = select(exporter->collector_arr[i].data_socket + 1,&readfds,NULL,NULL,&timeout);
if (ret == 0) {
// connection attempt not yet finished
msg(MSG_DEBUG, "waiting for socket to become readable...");
exporter->collector_arr[i].state = C_NEW;
return -1;
} else if (ret>0) {
// connected or connection setup failed.
msg(MSG_DEBUG, "socket is readable.");
} else {
// error
msg(MSG_ERROR, "select() failed: %s", strerror(errno));
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
/* Query pending error */
len = sizeof error;
if (getsockopt(exporter->collector_arr[i].data_socket, SOL_SOCKET,
SO_ERROR, &error, &len) != 0) {
msg(MSG_ERROR, "getsockopt(fd,SOL_SOCKET,SO_ERROR,...) failed: %s",
strerror(errno));
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
if (error) {
msg(MSG_ERROR, "SCTP connection setup failed: %s",
strerror(error));
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
/* Read from socket. Expect a SCTP_ASSOC_CHANGE with
sac_state==SCTP_COMM_UP */
iv.iov_base = &snp;
iv.iov_len = sizeof snp;
memset(&msg,0,sizeof(msg));
msg.msg_iov = &iv;
msg.msg_iovlen = 1;
if ((r = recvmsg(exporter->collector_arr[i].data_socket, &msg, 0))<0) {
msg(MSG_ERROR, "SCTP connection setup failed. recvmsg returned: %s",
strerror(error));
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
if (r==0) {
msg(MSG_ERROR, "SCTP connection setup failed. recvmsg returned 0");
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
if (!(msg.msg_flags & MSG_NOTIFICATION)) {
msg(MSG_ERROR, "SCTP connection setup failed. recvmsg unexpected user data.");
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
switch (snp.sn_header.sn_type) {
case SCTP_ASSOC_CHANGE:
sac = &snp.sn_assoc_change;
if (!sac->sac_state==SCTP_COMM_UP) {
msg(MSG_ERROR, "SCTP connection setup failed. "
"Received unexpected SCTP_ASSOC_CHANGE notification with state %d",
sac->sac_state);
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
msg(MSG_DEBUG,"Received SCTP_COMM_UP event.");
break;
default:
msg(MSG_ERROR, "SCTP connection setup failed. "
"Received unexpected notification of type %d",
snp.sn_header.sn_type);
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
/* Query SCTP status */
len = sizeof ss;
if (getsockopt(exporter->collector_arr[i].data_socket, IPPROTO_SCTP,
SCTP_STATUS, &ss, &len) != 0) {
msg(MSG_ERROR, "getsockopt(fd,IPPROTO_SCTP,SCTP_STATUS,...) failed: %s",
strerror(errno));
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
/* Make sure SCTP connection is in state ESTABLISHED */
if (ss.sstat_state != SCTP_ESTABLISHED) {
msg(MSG_ERROR, "SCTP socket not in state ESTABLISHED");
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
msg(MSG_INFO, "Successfully (re)connected to SCTP collector.");
//reconnected -> resend all active templates
ipfix_prepend_header(exporter,
exporter->template_sendbuffer->committed_data_length,
exporter->template_sendbuffer);
if((bytes_sent = sctp_sendmsgv(exporter->collector_arr[i].data_socket,
exporter->template_sendbuffer->entries,
exporter->template_sendbuffer->current,
(struct sockaddr*)&(exporter->collector_arr[i].addr),
sizeof(exporter->collector_arr[i].addr),
0,0, // payload protocol identifier, flags
0,//Stream Number
0,//packet lifetime in ms (0 = reliable, do not change for templates)
0 // context
)) == -1) {
msg(MSG_ERROR, "SCTP sending templates after reconnection failed, %s", strerror(errno));
close(exporter->collector_arr[i].data_socket);
exporter->collector_arr[i].data_socket = -1;
exporter->collector_arr[i].state = C_DISCONNECTED;
return -1;
}
msg(MSG_DEBUG, "%d template bytes sent to SCTP collector",bytes_sent);
// we are done
exporter->collector_arr[i].state = C_CONNECTED;
return 0;
}
#endif /*SUPPORT_SCTP*/
/*******************************************************************/
/* Transmission */
/*******************************************************************/
/*
* If necessary, sends all associated templates
* Parameters:
* exporter sending exporting process
* Return value: 1 on success, -1 on failure, 0 on no need to send.
*/
static int ipfix_send_templates(ipfix_exporter* exporter)
{
int i;
int bytes_sent;
int expired;
uint32_t n = 0;
// determine, if we need to send the template data:
time_t time_now = time(NULL);
// has the timer expired? (for UDP)
// Remember: This is a global timer for all collectors associated with a given exporter
expired = ( (time_now - exporter->last_template_transmission_time) > exporter->template_transmission_timer);
// update the sendbuffers
// Watch out: You undertake a commitment by calling this function
// See the definition of the function for more details.
ipfix_update_template_sendbuffer(exporter);
// send the sendbuffer to all collectors depending on their protocol
for (i = 0; i < exporter->collector_max_num; i++) {
ipfix_receiving_collector *col = &exporter->collector_arr[i];
// is the collector a valid target?
// T_UNUSED evaluates to 0 which in turn evaluates to false
// So basically we check if state is something *not* equal to T_UNUSED
if (col->state) {
#ifdef SUPPORT_DTLS
if (col->protocol == DTLS_OVER_UDP ||
col->protocol == DTLS_OVER_SCTP) {
/* ensure that we are connected i.e. DTLS handshake has been finished.
* This function does no harm if we are already connected. */
if (dtls_manage_connection(exporter,col) < 0)
/* continue if dtls_manage_connection failed */
continue;
/* dtls_manage_connection() might return success even if we're not yet connected.
* This might happen if OpenSSL is still waiting for data from the
* remote end and therefore returned SSL_ERROR_WANT_READ. */
if ( col->state != C_CONNECTED ) {
DPRINTF("We are not yet connected so we can't send templates.");
break;
}
}
#endif
switch(col->protocol){
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
char* packet_directory_path;
#endif
#ifdef SUPPORT_DTLS_OVER_SCTP
case DTLS_OVER_SCTP:
if (exporter->sctp_template_sendbuffer->committed_data_length > 0) {
// update the sendbuffer header, as we must set the export time & sequence number!
ipfix_prepend_header(exporter,
exporter->sctp_template_sendbuffer->committed_data_length,
exporter->sctp_template_sendbuffer);
dtls_over_sctp_send(exporter,col,
exporter->sctp_template_sendbuffer->entries,
exporter->sctp_template_sendbuffer->current,
0 //packet lifetime in ms (0 = reliable, do not change for templates)
);
}
break;
#endif
case DTLS_OVER_UDP:
case UDP:
if (expired && (exporter->template_sendbuffer->committed_data_length > 0)){
//Timer only used for UDP and DTLS over UDP
exporter->last_template_transmission_time = time_now;
// update the sendbuffer header, as we must set the export time & sequence number!
ipfix_prepend_header(exporter,
exporter->template_sendbuffer->committed_data_length,
exporter->template_sendbuffer);
#ifdef SUPPORT_DTLS
if (col->protocol == DTLS_OVER_UDP) {
dtls_send(exporter,col,
exporter->template_sendbuffer->entries,
exporter->template_sendbuffer->current);
} else {
#endif
if((bytes_sent = writev(col->data_socket,
exporter->template_sendbuffer->entries,
exporter->template_sendbuffer->current
)) == -1){
if (errno == EMSGSIZE) {
msg(MSG_ERROR,
"Unable to send templates to %s:%d b/c message is bigger than MTU. That is a severe problem.",
col->ipv4address,
col->port_number);
} else {
msg(MSG_ERROR,
"could not send to %s:%d errno: %s (UDP)",
col->ipv4address,
col->port_number,
strerror(errno));
}
} else {
msg(MSG_VDEBUG, "%d Template Bytes sent to UDP collector %s:%d",
bytes_sent, col->ipv4address, col->port_number);
}
#ifdef SUPPORT_DTLS
}
#endif
}
break;
#ifdef SUPPORT_SCTP
case SCTP:
switch (col->state){
case C_NEW: // try to connect to the new collector once per second
// once per second is not useful here, new collectors must be connected quickly
//if (time_now > col->last_reconnect_attempt_time) {
sctp_reconnect(exporter, i);
//}
break;
case C_DISCONNECTED: //reconnect attempt if reconnection time reached
if(exporter->sctp_reconnect_timer == 0) { // 0 = no more reconnection attempts
msg(MSG_ERROR, "reconnect failed, removing collector %s:%d (SCTP)", col->ipv4address, col->port_number);
remove_collector(col);
} else if ((time_now - col->last_reconnect_attempt_time) > exporter->sctp_reconnect_timer) {
sctp_reconnect(exporter, i);
}
break;
case C_CONNECTED:
if (exporter->sctp_template_sendbuffer->committed_data_length > 0) {
// update the sendbuffer header, as we must set the export time & sequence number!
ipfix_prepend_header(exporter,
exporter->sctp_template_sendbuffer->committed_data_length,
exporter->sctp_template_sendbuffer);
if((bytes_sent = sctp_sendmsgv(col->data_socket,
exporter->sctp_template_sendbuffer->entries,
exporter->sctp_template_sendbuffer->current,
(struct sockaddr*)&(col->addr),
sizeof(col->addr),
0,0, // payload protocol identifier, flags
0,//Stream Number
0,//packet lifetime in ms (0 = reliable, do not change for templates)
0 // context
)) == -1) {
// send failed
msg(MSG_ERROR, "could not send to %s:%d errno: %s (SCTP)",col->ipv4address, col->port_number, strerror(errno));
sctp_reconnect(exporter, i); //1st reconnect attempt
// if result is C_DISCONNECTED and sctp_reconnect_timer == 0, collector will
// be removed on the next call of ipfix_send_templates()
} else {
// send was successful
msg(MSG_VDEBUG, "%d template bytes sent to SCTP collector %s:%d",
bytes_sent, col->ipv4address, col->port_number);
}
} else DPRINTF("No Template to send to SCTP collector");
break;
default:
msg(MSG_FATAL, "Unknown collector socket state");
return -1;
}
break;
#endif
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
case RAWDIR:
ipfix_prepend_header(exporter,
exporter->template_sendbuffer->committed_data_length,
exporter->template_sendbuffer);
packet_directory_path = col->packet_directory_path;
char fnamebuf[1024];
sprintf(fnamebuf, "%s/%08d", packet_directory_path, col->packets_written++);
int f = creat(fnamebuf, S_IRWXU | S_IRWXG);
if(f<0)
msg(MSG_ERROR, "could not open RAWDIR file %s", fnamebuf);
else if(writev(f, exporter->template_sendbuffer->entries, exporter->template_sendbuffer->current)<0)
msg(MSG_ERROR, "could not write to RAWDIR file %s", fnamebuf);
close(f);
break;
#endif
case DATAFILE:
ipfix_prepend_header(exporter,
exporter->template_sendbuffer->committed_data_length,
exporter->template_sendbuffer);
if(col->bytes_written>0 && (col->bytes_written +
ntohs(exporter->template_sendbuffer->packet_header.length)
> (uint64_t)(col->maxfilesize) * 1024))
ipfix_new_file(col);
if (col->fh < 0) {
msg(MSG_ERROR, "invalid file handle for DATAFILE file (==0!)");
break;
}
if (exporter->template_sendbuffer->packet_header.length == 0) {
msg(MSG_ERROR, "packet size == 0!");
break;
}
if ((n = writev(col->fh, exporter->template_sendbuffer->entries,
exporter->template_sendbuffer->current)) < 0) {
msg(MSG_ERROR, "could not write to DATAFILE file");
break;
}
col->bytes_written += ntohs(exporter->template_sendbuffer->packet_header.length);
msg(MSG_DEBUG, "packet_header.length: %d \t bytes_written: %d \t Total: %llu",
ntohs(exporter->template_sendbuffer->packet_header.length), n,
col->bytes_written );
break;
default:
return -1; /* Should not occur since we check the transport
protocol in valid_transport_protocol()*/
}
}
} // end exporter loop
return 1;
}
/*
Send data to collectors
Sends all data committed via ipfix_put_data_field to this exporter.
Parameters:
exporter sending exporting process
Return value:
on success: 0
on failure: -1
*/
static int ipfix_send_data(ipfix_exporter* exporter)
{
int i;
int bytes_sent;
// send the current data_sendbuffer:
int data_length=0;
// is there data to send?
if (exporter->data_sendbuffer->committed_data_length > 0 ) {
data_length = exporter->data_sendbuffer->committed_data_length;
// prepend a header to the sendbuffer
ipfix_prepend_header(exporter, data_length, exporter->data_sendbuffer);
// send the sendbuffer to all collectors
for (i = 0; i < exporter->collector_max_num; i++) {
ipfix_receiving_collector *col = &exporter->collector_arr[i];
if (col->state == C_CONNECTED) {
#ifdef DEBUG
DPRINTFL(MSG_VDEBUG, "Sending to exporter %s", col->ipv4address);
// debugging output of data buffer:
/* Keep in mind that the IPFIX message header (16 bytes) is not included
in committed_data_length */
DPRINTFL(MSG_VDEBUG, "Sendbuffer contains %u bytes (Set headers + records)", exporter->data_sendbuffer->committed_data_length );
DPRINTFL(MSG_VDEBUG, "Sendbuffer contains %u fields (IPFIX Message header + set headers + records)", exporter->data_sendbuffer->committed );
int tested_length = 0;
int j;
/*int k;*/
for (j =0; j < exporter->data_sendbuffer->committed; j++) {
if(exporter->data_sendbuffer->entries[j].iov_len > 0 ) {
tested_length += exporter->data_sendbuffer->entries[j].iov_len;
}
}
/* Keep in mind that the IPFIX message header (16 bytes) is not included
in committed_data_length. So there should be a difference of 16 bytes
between tested_length and committed_data_length */
DPRINTFL(MSG_VDEBUG, "Total length of sendbuffer: %u bytes (IPFIX Message header + set headers + records)", tested_length );
#endif
switch(col->protocol){
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
char* packet_directory_path;
#endif
case UDP:
if((bytes_sent=writev( col->data_socket,
exporter->data_sendbuffer->entries,
exporter->data_sendbuffer->committed
)) == -1){
msg(MSG_ERROR, "could not send to %s:%d errno: %s (UDP)",col->ipv4address, col->port_number, strerror(errno));
if (errno == EMSGSIZE) {
msg(MSG_ERROR, "Updating MTU estimate for collector %s:%d",
col->ipv4address,
col->port_number);
/* If update_collector_mtu fails, it calls
remove_collector(). So keep in mind that
the collector might be gone (set to C_UNUSED)
after calling this function. */
update_collector_mtu(exporter, col);
}
}else{
msg(MSG_VDEBUG, "%d data bytes sent to UDP collector %s:%d",
bytes_sent, col->ipv4address, col->port_number);
}
break;
#ifdef SUPPORT_DTLS_OVER_SCTP
case DTLS_OVER_SCTP:
if((bytes_sent=dtls_over_sctp_send( exporter, col,
exporter->data_sendbuffer->entries,
exporter->data_sendbuffer->committed,
exporter->sctp_lifetime
)) == -1){
msg(MSG_VDEBUG, "could not send to %s:%d (DTLS over SCTP)",col->ipv4address, col->port_number);
}else{
msg(MSG_VDEBUG, "%d data bytes sent to DTLS over SCTP collector %s:%d",
bytes_sent, col->ipv4address, col->port_number);
}
break;
#endif
#ifdef SUPPORT_SCTP
case SCTP:
if((bytes_sent = sctp_sendmsgv(col->data_socket,
exporter->data_sendbuffer->entries,
exporter->data_sendbuffer->committed,
(struct sockaddr*)&(col->addr),
sizeof(col->addr),
0,0, // payload protocol identifier, flags
0,//Stream Number
exporter->sctp_lifetime,//packet lifetime in ms(0 = reliable )
0 // context
)) == -1) {
// send failed
msg(MSG_ERROR, "could not send to %s:%d errno: %s (SCTP)",col->ipv4address, col->port_number, strerror(errno));
// drop data and call sctp_reconnect
sctp_reconnect(exporter, i);
// if result is C_DISCONNECTED and sctp_reconnect_timer == 0, collector will
// be removed on the next call of ipfix_send_templates()
}
msg(MSG_VDEBUG, "%d data bytes sent to SCTP collector %s:%d",
bytes_sent, col->ipv4address, col->port_number);
break;
#endif
#ifdef SUPPORT_DTLS
case DTLS_OVER_UDP:
if((bytes_sent=dtls_send( exporter, col,
exporter->data_sendbuffer->entries,
exporter->data_sendbuffer->committed
)) == -1){
msg(MSG_VDEBUG, "could not send to %s:%d (DTLS over UDP)",col->ipv4address, col->port_number);
}else{
msg(MSG_VDEBUG, "%d data bytes sent to DTLS over UDP collector %s:%d",
bytes_sent, col->ipv4address, col->port_number);
}
break;
#endif
#ifdef IPFIXLOLIB_RAWDIR_SUPPORT
case RAWDIR:
packet_directory_path = col->packet_directory_path;
char fnamebuf[1024];
sprintf(fnamebuf, "%s/%08d", packet_directory_path, col->packets_written++);
int f = creat(fnamebuf, S_IRWXU | S_IRWXG);
if(f<0)
msg(MSG_ERROR, "could not open RAWDIR file %s", fnamebuf);
else if(writev(f, exporter->data_sendbuffer->entries, exporter->data_sendbuffer->committed)<0)
msg(MSG_ERROR, "could not write to RAWDIR file %s", fnamebuf);
close(f);
break;
#endif
case DATAFILE:
if(col->bytes_written>0 && (col->bytes_written +
ntohs(exporter->data_sendbuffer->packet_header.length)
> (uint64_t)(col->maxfilesize) * 1024))
ipfix_new_file(col);
if (col->fh < 0) {
msg(MSG_ERROR, "invalid file handle for DATAFILE file (==0!)");
break;
}
if (exporter->data_sendbuffer->packet_header.length == 0) {
msg(MSG_ERROR, "packet size == 0!");
break;
}
if ((bytes_sent = writev(col->fh, exporter->data_sendbuffer->entries,
exporter->data_sendbuffer->committed)) < 0) {
msg(MSG_ERROR, "could not write to DATAFILE file");
break;
}
col->bytes_written += ntohs(exporter->data_sendbuffer->packet_header.length);
msg(MSG_DEBUG, "packet_header.length: %d \t bytes_written: %d \t Total: %llu",
ntohs(exporter->data_sendbuffer->packet_header.length), bytes_sent,
col->bytes_written);
break;
default:
msg(MSG_FATAL, "Transport Protocol not supported");
break; /* Should not occur since we check the transport
protocol in valid_transport_protocol()*/
}
}
} // end exporter loop
// increment sequence number
exporter->sequence_number += exporter->sn_increment;
exporter->sn_increment = 0;
} // end if
// reset the sendbuffer
ipfix_reset_sendbuffer(exporter->data_sendbuffer);
return 0;
}
/*!
* \brief Send data to Collectors.
*
* Sends all data committed via <tt>ipfix_put_data_field</tt> to this exporter.
* If necessary, sends all associated templates.
*
* \param exporter pointer to previously initialized exporter struct
* \return >=0 success
* \return -1 failure
* \sa ipfix_start_template(), ipfix_start_data_set()
*/
/*
*/
int ipfix_send(ipfix_exporter *exporter)
{
int ret = 0;
if(ipfix_send_templates(exporter) < 0) {
msg(MSG_ERROR, "sending templates failed");
ret = -1;
}
if(ipfix_send_data(exporter) < 0) {
msg(MSG_ERROR, "sending data failed");
ret = -1;
}
return ret;
}
/*******************************************************************/
/* Generation of a data set */
/*******************************************************************/
/*!
* \brief Marks the beginning of a data set
*
* The set ID <em>must</em> match a previously defined template ID! It is the
* user's responsibility to make sure that the corresponding template has been
* defined using <tt>ipfix_start_template()</tt> and other functions in
* advance as the library will not perform any checks. There can only be one
* open data set at any given time. It is not possible to start multiple data
* sets in parallel.
*
* The user must also make sure that there is enough free space left in the
* IPFIX Message so that the Data Set Header (IPFIX_OVERHEAD_PER_SET == 4
* bytes) and at least a single Data Record fit inside. Use
* ipfix_get_remaining_space() to find out how many bytes can still by added
* without overshooting the maximum message size of an IPFIX Message.
*
* The <tt>template_id</tt> parameter has to be in <em>network byte order</em>.
* This is in contrast to the corresponding parameter of
* <tt>ipfix_start_template()</tt>
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID of the used template <em>(in network byte order)</em>
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>There is already an open data
* set</li><li>Insufficient buffer space</li></ul>
* \sa ipfix_end_data_set(), ipfix_put_data_field()
*/
/*
*/
int ipfix_start_data_set(ipfix_exporter *exporter, uint16_t template_id)
{
ipfix_set_manager *manager = &(exporter->data_sendbuffer->set_manager);
unsigned current = manager->set_counter;
// security check
if(exporter->data_sendbuffer->current != exporter->data_sendbuffer->committed) {
msg(MSG_ERROR, "start_data_set called twice.");
return -1;
}
// check, if there is enough space in the data set buffer
// the -1 is because, we expect, we want to add at least one data field.
if(exporter->data_sendbuffer->current >= IPFIX_MAX_SENDBUFSIZE-1 ) {
msg(MSG_ERROR, "start_data_set sendbuffer too small to handle more than %i entries",
exporter->data_sendbuffer->current
);
return -1;
}
// check if we do have space for another set header
if((current + 1) >= IPFIX_MAX_SETS_PER_PACKET ) {
msg(MSG_ERROR, "start_data_set set_header_store too small to handle more than %i entries",
current + 1
);
return -1;
}
// write the set id (=template id) to the data set buffer (length will be added by ipfix_end_data_set):
manager->set_header_store[current].set_id = template_id;
// link current set header in entries
exporter->data_sendbuffer->entries[exporter->data_sendbuffer->current].iov_base = &(manager->set_header_store[current]);
exporter->data_sendbuffer->entries[exporter->data_sendbuffer->current].iov_len = sizeof(ipfix_set_header);
exporter->data_sendbuffer->current++;
exporter->data_sendbuffer->marker = exporter->data_sendbuffer->current;
// initialize the counting of the record's data:
manager->data_length = 0;
return 0;
}
/*!
* \brief Get the number of bytes that can still be added to the current IPFIX
* message without overshooting the maximum message size.
*
* The maximum size of an IPFIX message may be limited by at least the
* following two factors:
* <ul>
* <li>The maximum transmission unit (MTU) if UDP is used as the transport
* protocol.</li>
* <li>The maximum DTLS record size if DTLS is used.</li>
* <li>The maximum message size imposed by IPFIX itself. This is due to the 16
* bit length field.</li>
* </ul>
* If an Exporter has multiple Collectors, then the maximum message size is
* determined by the collector that allows for the <em>smallest</em> message
* size. This is due to the fact that ipfixlolib uses a single send buffer for
* all Collectors. For example, if there is an SCTP Collector and a DTLS over
* UDP Collector, the maximum message size is usually determined by the DTLS
* over UDP Collector because its maximum message size is equal to the Maximum
* Transmission Unit (MTU) imposed by the IP layer minus the overhead caused by
* UDP and DTLS.
*
* It should be noted that there is an overhead of (IPFIX_OVERHEAD_PER_SET ==
* 4) bytes per Data Set. As a result, you should make sure that there is
* enough space for the Data Set header (IPFIX_OVERHEAD_PER_SET == 4 bytes) and
* for at least a single Data Record <em>before</em> calling
* ipfix_start_data_set().
*
* \param exporter pointer to previously initialized exporter struct
* @return remaining space in bytes. This value will never be negative. If the
* IPFIX message is already longer than the maximum length, 0 is returned.
* \sa ipfix_start_data_set(), ipfix_put_data_field()
*/
uint16_t ipfix_get_remaining_space(ipfix_exporter *exporter) {
int32_t space;
space = exporter->max_message_size
// 16 bytes for IPFIX header
- 16
- exporter->data_sendbuffer->committed_data_length;
if(exporter->data_sendbuffer->current != exporter->data_sendbuffer->committed) {
space -= sizeof(ipfix_set_header);
space -= exporter->data_sendbuffer->set_manager.data_length;
}
if (space < 0) space = 0;
if (space > UINT16_MAX) space = UINT16_MAX;
return space;
}
/*!
* \brief Add a data field to the send buffer.
*
* It is the user's responsibility to make sure that the size of the resulting
* IPFIX message does not exceed the maximum message size which is derived from
* the maximum transmission unit (MTU) and other factors. (See
* ipfix_get_remaining_space() for more details) Use
* <tt>ipfix_get_remaining_space()</tt> to determine how much space is still
* left.
*
* <em>No sanity checks of any kind are performed.</em> The user has to ensure
* that<ul>
* <li>the type of data matches the type required by the Information Element
* that has been defined for the corresponding field which includes converting
* integer values into network byte order,</li>
* <li>the length of data matches the length of the field as defined in the
* template.</li>
* </ul>
*
* \param exporter pointer to previously initialized exporter struct
* \param data pointer to data that should be added to the send buffer. Please
* note, that this <em>pointer</em> will be stored in the send buffer instead
* of copying the data. As a consequence, the data <em>must</em> stay at the
* given memory location until the IPFIX message has been sent via
* ipfix_send().
* \param length length of data pointed to by <tt>data</tt>
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>no open data set</li><li>send
* buffer too small</li></ul>
* \sa ipfix_get_remaining_space()
*/
int ipfix_put_data_field(ipfix_exporter *exporter,void *data, unsigned length) {
ipfix_sendbuffer *dsb = exporter->data_sendbuffer;
if(exporter->data_sendbuffer->current == exporter->data_sendbuffer->committed) {
msg(MSG_ERROR, "ipfix_put_data_field called but there is no started set.");
return -1;
}
if (dsb->current >= IPFIX_MAX_SENDBUFSIZE) {
msg(MSG_ERROR, "Sendbuffer too small to handle %i entries!\n", dsb->current );
return -1;
}
dsb->entries[ dsb->current ].iov_base = data;
dsb->entries[ dsb->current ].iov_len = length;
dsb->current++;
dsb->set_manager.data_length+= length;
return 0;
}
/*!
* \brief Marks the end of a data set
*
* The data is not transmitted until <tt>ipfix_send()</tt> is called.
*
* \param exporter pointer to previously initialized exporter struct
* \param number_of_records number of data records in this set (used to calculate the sequence number)
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>No open data set</li></ul>
* \sa ipfix_send()
*/
int ipfix_end_data_set(ipfix_exporter *exporter, uint16_t number_of_records)
{
ipfix_set_manager *manager = &(exporter->data_sendbuffer->set_manager);
unsigned current = manager->set_counter;
uint16_t record_length;
if(exporter->data_sendbuffer->current == exporter->data_sendbuffer->committed) {
msg(MSG_ERROR, "ipfix_end_data_set called but there is no started set to end.");
return -1;
}
// add number of data records to sequence number increment
exporter->sn_increment += number_of_records;
// calculate and store the total length of the set:
record_length = manager->data_length + sizeof(ipfix_set_header);
manager->set_header_store[current].length = htons(record_length);
// update the sendbuffer
exporter->data_sendbuffer->committed_data_length += record_length;
// now as we are finished with this set, increase set_counter
manager->set_counter++;
// update committed
exporter->data_sendbuffer->committed = exporter->data_sendbuffer->current;
exporter->data_sendbuffer->marker = exporter->data_sendbuffer->current;
return 0;
}
/*!
* \brief Cancel a previously started data set
*
* This call ends a previously started data set and discards all data that has
* been added since the call to <tt>ipfix_start_data_set()</tt>.
*
* \param exporter pointer to previously initialized exporter struct
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>no open data set i.e. no data set to cancel</li></ul>
* \sa ipfix_start_data_set()
*/
/*
*/
int ipfix_cancel_data_set(ipfix_exporter *exporter)
{
ipfix_set_manager *manager = &(exporter->data_sendbuffer->set_manager);
unsigned current = manager->set_counter;
int i;
// security check
if(exporter->data_sendbuffer->current == exporter->data_sendbuffer->committed) {
msg(MSG_ERROR, "cancel_data_set called but there is no set to cancel.");
return -1;
}
// clean set id and length:
manager->set_header_store[current].set_id = 0;
manager->data_length = 0;
// clean up entries
for(i=exporter->data_sendbuffer->committed; i<exporter->data_sendbuffer->current; i++) {
exporter->data_sendbuffer->entries[i].iov_base = NULL;
exporter->data_sendbuffer->entries[i].iov_len = 0;
}
exporter->data_sendbuffer->current = exporter->data_sendbuffer->committed;
exporter->data_sendbuffer->marker = exporter->data_sendbuffer->committed;
return 0;
}
/*!
* \brief Sets the data field marker to the current position in order to allow
* deletion of newly added fields
*
* \param exporter pointer to previously initialized exporter struct
* \return 0 This value is <em>always</em> returned.
* \sa ipfix_delete_data_fields_upto_marker()
*/
int ipfix_set_data_field_marker(ipfix_exporter *exporter)
{
exporter->data_sendbuffer->marker = exporter->data_sendbuffer->current;
return 0;
}
/*!
* \brief Delete recently added fields up to the marker
*
* \param exporter pointer to previously initialized exporter struct
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>no open data set</li></ul>
* \sa ipfix_set_data_field_marker()
*/
int ipfix_delete_data_fields_upto_marker(ipfix_exporter *exporter)
{
ipfix_set_manager *manager = &(exporter->data_sendbuffer->set_manager);
int i;
// security check
if(exporter->data_sendbuffer->current == exporter->data_sendbuffer->committed) {
msg(MSG_ERROR, "delete_data_fields_upto_marker called but there is no set.");
goto out;
}
// if marker is before current, clean the entries and set current back
if(exporter->data_sendbuffer->marker < exporter->data_sendbuffer->current) {
for(i=exporter->data_sendbuffer->marker; i<exporter->data_sendbuffer->current; i++) {
// decrease data_length
manager->data_length -= exporter->data_sendbuffer->entries[i].iov_len;
exporter->data_sendbuffer->entries[i].iov_base = NULL;
exporter->data_sendbuffer->entries[i].iov_len = 0;
}
exporter->data_sendbuffer->current = exporter->data_sendbuffer->marker;
}
return 0;
out:
return -1;
}
/*******************************************************************/
/* Generation of a data template set and option template set */
/*******************************************************************/
/*!
* \brief Marks the beginning of a Data Template Set and a Template Record
*
* ipfixlolib supports only one Data Template Record per Date Template Set. So
* this function basically starts a Data Template Set and one Data Template
* Record.
*
* Data Templates are a proprietary extension to IPFIX. See
* <tt>draft-dressler-ipfix-aggregation-00</tt> for details. Data Templates
* are like standard Templates except that they may specify fields with fixed
* values i.e. fields that share the same value for all Data Records. These
* values are transmitted once as part of the Data Template Record instead of
* repeatedly transmitting them with every Data Record.
*
* A Data Template consists of (in this order):
* <ul>
* <li>Field Specifiers (like in standard Template Records)</li>
* <li>Field Specifiers of the fixed-value fields</li>
* <li>Data values of the fixed-value fields</li>
* </ul>
*
* Use the following functions to add the Field Specifiers and Data values to
* the template:
* <ul>
* <li>ipfix_put_template_field()
* <li>ipfix_put_template_fixedfield()
* <li>ipfix_put_template_data()
* </ul>
*
* <em>Note:</em> It is not possible to start and define multiple Data
* Templates in parallel. ipfix_end_template() has to be called first before a
* new Data Template can be defined.
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID for this Template. Must be > 255.
* \param preceding
* \param field_count number of fields that will be added to this Data Template Record.
* It is considered an error if more or fewer fields are added.
* \param fixedfield_count number of fixed-value fields that will be added to this Data Template Record.
* It is considered an error if more or fewer fields are added.
* \return 0 success
* \return -1 failure. Reasons include:<ul><li><tt>template_id</tt> is not
* great than 255</li><li>maximum number of defined templates has been
* exceeded</li></ul>
* \sa ipfix_end_template(), ipfix_put_template_field(), ipfix_put_template_fixedfield(), ipfix_send()
*/
/*
* Allocate memory for a new template
* end_data_template will add this template to the exporter
*/
int ipfix_start_datatemplate (ipfix_exporter *exporter,
uint16_t template_id, uint16_t preceding, uint16_t field_count,
uint16_t fixedfield_count) {
/* Is this a regular Template (Set id == 2) or a Data Template (Set id == 4).
See draft-dressler-ipfix-aggregation-00 for more details on Data Templates.
Data Templates are (still) a proprietary extension to IPFIX. */
int datatemplate=(fixedfield_count || preceding) ? 1 : 0;
/* Make sure that template_id is > 255 */
if ( ! template_id > 255 ) {
msg(MSG_ERROR, "Template id has to be > 255. Start of template cancelled.");
return -1;
}
int found_index = ipfix_find_template(exporter, template_id);
// have we found a template?
if(found_index >= 0) {
// we must overwrite the old template.
// first, clean up the old template:
switch (exporter->template_arr[found_index].state){
case T_SENT:
// create a withdrawal message first
ipfix_remove_template(exporter, exporter->template_arr[found_index].template_id);
/* fall through */
case T_WITHDRAWN:
// send withdrawal messages
ipfix_send_templates(exporter);
/* fall through */
case T_COMMITED:
case T_UNCLEAN:
case T_TOBEDELETED:
// nothing to do, template can be deleted
ipfix_deinit_template(exporter, &(exporter->template_arr[found_index]));
break;
default:
DPRINTFL(MSG_VDEBUG, "template valid flag is T_UNUSED or invalid\n");
break;
}
} else {
/* allocate a new, free slot */
found_index = ipfix_get_free_template_slot(exporter);
if (found_index < 0) {
msg(MSG_ERROR,"Unable to find free template slot.");
return -1;
}
}
char *p_pos;
char *p_end;
// allocate memory for the template's field specifiers:
// 8 bytes for each field specifier, as one field specifier contains:
// field type, field length (2*2bytes)
// and an optional Enterprise Number (4 bytes)
// Also, reserve 4+4 bytes for the Set Header and the Template Record header
// In case of a Data Template, the Data Template Record header is 8 bytes long
// (instead of 4 bytes). The total overhead is 4+8=12 in this case.
exporter->template_arr[found_index].max_fields_length = 8 * (field_count + fixedfield_count) + (datatemplate ? 12 : 8);
exporter->template_arr[found_index].template_fields = (char*)malloc(exporter->template_arr[found_index].max_fields_length );
// initialize the rest:
exporter->template_arr[found_index].state = T_UNCLEAN;
exporter->template_arr[found_index].template_id = template_id;
exporter->template_arr[found_index].field_count = field_count;
exporter->template_arr[found_index].fixedfield_count = fixedfield_count;
exporter->template_arr[found_index].fields_added = 0;
// also, write the template header fields into the buffer (except the length field);
// beginning of the buffer
p_pos = exporter->template_arr[found_index].template_fields;
// end of the buffer
p_end = p_pos + exporter->template_arr[found_index].max_fields_length;
// ++ Start of Set Header
// set ID is 2 for a Template Set, 4 for a Data Template with fixed fields:
// see RFC 5101: 3.3.2 Set Header Format
write_unsigned16 (&p_pos, p_end, datatemplate ? 4 : 2);
// write 0 to the length field; this will be overwritten by end_template
write_unsigned16 (&p_pos, p_end, 0);
// ++ End of Set Header
// ++ Start of Template Record Header
// write the template ID: (has to be > 255)
write_unsigned16 (&p_pos, p_end, template_id);
// write the field count:
write_unsigned16 (&p_pos, p_end, field_count);
if (datatemplate) {
// write the fixedfield count:
write_unsigned16 (&p_pos, p_end, fixedfield_count);
// write the preceding:
write_unsigned16 (&p_pos, p_end, preceding);
}
// ++ End of Template Record Header
exporter->template_arr[found_index].fields_length = (datatemplate ? 12 : 8);
return 0;
}
/*!
* \brief Marks the beginning of an option template set. (Not yet implemented)
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id the template's ID (in host byte order)
* \param scope_length the option scope length (in host byte oder)
* \param option_length the option scope length (in host byte oder)
* \return -1 Not yet implemented. This value is <em>always</em> returned.
*/
int ipfix_start_optionstemplate(ipfix_exporter *exporter,
uint16_t template_id, uint16_t scope_length, uint16_t option_length)
{
msg(MSG_FATAL, "start_optionstemplate() not implemented");
return -1;
}
/*!
* \brief Add a field to the previously started template, options template, or data
* template.
*
* This function is called after <tt>ipfix_start_template()</tt>
* or <tt>ipfix_start_optionstemplate</tt>.
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id the ID specified on call to ipfix_start_template()
* \param type Information Element ID of the field
* \param length length of the field (in host byte order)
* \param enterprise_id enterprise number (in host byte order) or 0 for Information Elements registered at IANA
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>template ID
* unknown</li><li>number of fields exceeds the number that was announced when
* starting the template.</li></ul>
* \sa ipfix_start_template(), ipfix_start_optionstemplate()
*/
int ipfix_put_template_field(ipfix_exporter *exporter, uint16_t template_id,
uint16_t type, uint16_t length, uint32_t enterprise_id) {
int found_index;
/* set pointers to the buffer */
char *p_pos;
char *p_end;
int enterprise_specific = 0;
/* test if this is an enterprise-specific field */
if ((enterprise_id != 0) || ipfix_enterprise_flag_set(type)) {
enterprise_specific = 1;
/* make sure that enterprise bit is set */
type |= IPFIX_ENTERPRISE_BIT;
}
found_index = ipfix_find_template(exporter, template_id);
/* test for a valid slot */
if(found_index < 0) {
msg(MSG_VDEBUG, "template ID %u not found", template_id);
return -1;
}
if (exporter->template_arr[found_index].fields_added >=
exporter->template_arr[found_index].field_count +
exporter->template_arr[found_index].fixedfield_count) {
msg(MSG_ERROR, "Cannot add more template fields.");
return -1;
}
/* beginning of the buffer */
p_pos = exporter->template_arr[found_index].template_fields;
// end of the buffer
p_end = p_pos + exporter->template_arr[found_index].max_fields_length;
DPRINTFL(MSG_VDEBUG, "template found at %d", found_index);
DPRINTFL(MSG_VDEBUG, "A p_pos %p, p_end %p", p_pos, p_end);
DPRINTFL(MSG_VDEBUG, "max_fields_length %d", exporter->template_arr[found_index].max_fields_length);
DPRINTFL(MSG_VDEBUG, "fields_length %d", exporter->template_arr[found_index].fields_length);
// add offset to the buffer's beginning: this is, where we will write to.
p_pos += exporter->template_arr[found_index].fields_length;
DPRINTFL(MSG_VDEBUG, "B p_pos %p, p_end %p", p_pos, p_end);
if(enterprise_specific) {
DPRINTFL(MSG_VDEBUG, "Notice: using enterprise ID %d with data %d", template_id, enterprise_id);
}
// now write the field to the buffer:
write_extension_and_fieldID(&p_pos, p_end, type);
// write the field length
write_unsigned16(&p_pos, p_end, length);
// add the 4 bytes to the written length:
exporter->template_arr[found_index].fields_length += 4;
// write the vendor specific id
if (enterprise_specific) {
write_unsigned32(&p_pos, p_end, enterprise_id);
exporter->template_arr[found_index].fields_length += 4;
}
exporter->template_arr[found_index].fields_added ++;
return 0;
}
/*!
* \brief Start defining a new Template (Set).
*
* ipfixlolib supports only one Template Record per Template Set. So this
* function basically starts a Template Set and one Template Record.
*
* <em>Note:</em> It is not possible to start and define multiple Templates in parallel.
* ipfix_end_template() has to be called first before a new Template can be
* defined.
*
* Individual fields are added to the Template by calling
* ipfix_put_data_field() before calling ipfix_end_template() to end the
* Template.
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID for this Template. Must be > 255.
* \param field_count number of fields that will be added to this Template Record.
* It is considered an error if more or fewer fields are added.
* \return 0 success
* \return -1 failure. Reasons might be that <tt>template_id</tt> is not great than 255
* or that the maximum number of defined templates has been exceeded.
* \sa ipfix_end_template(), ipfix_put_template_field(), ipfix_send()
**/
int ipfix_start_template (ipfix_exporter *exporter, uint16_t template_id, uint16_t field_count) {
return ipfix_start_datatemplate(exporter, template_id, 0, field_count, 0);
}
/*!
* \brief Append fixed-value data type field to the exporter's current Data
* Template Set, see <tt>ipfix_put_template_field()</tt>.
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID of the template
* \param type Information Element ID of the field
* \param length length of the field (in host byte order)
* \param enterprise_id enterprise number (in host byte order) or 0 for Information Elements registered at IANA
* \return 0 success
* \return -1 failure
* \sa ipfix_start_datatemplate()
*/
int ipfix_put_template_fixedfield(ipfix_exporter *exporter, uint16_t template_id, uint16_t type, uint16_t length, uint32_t enterprise_id) {
return ipfix_put_template_field(exporter, template_id, type, length, enterprise_id);
}
/*!
* \brief Append fixed-value data to the exporter's current data template set
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID of the template
* \param data pointer to the data (data must be in network byte order)
* \param data_length length of data to be added, in bytes
* \return 0 success
* \return -1 failure
* \sa ipfix_start_datatemplate()
*/
int ipfix_put_template_data(ipfix_exporter *exporter, uint16_t template_id, void* data, uint16_t data_length) {
int found_index;
/* set pointers to the buffer */
int ret;
char *p_pos;
char *p_end;
int i;
found_index = ipfix_find_template(exporter, template_id);
/* test for a valid slot */
if (found_index < 0) {
msg(MSG_VDEBUG, "template ID %u not found", template_id);
return -1;
}
ipfix_lo_template *templ=(&(*exporter).template_arr[found_index]);
if (templ->fields_added != templ->field_count + templ->fixedfield_count) {
msg(MSG_ERROR, "All field specifiers must have been added before adding data values to Data Template Record.");
return -1;
}
templ->max_fields_length += data_length;
templ->template_fields=(char *)realloc(templ->template_fields, templ->max_fields_length);
/* beginning of the buffer */
p_pos = templ->template_fields;
// end of the buffer
p_end = p_pos + templ->max_fields_length;
#if 0
DPRINTFL(MSG_VDEBUG, "template found at %i", found_index);
DPRINTFL(MSG_VDEBUG, "A p_pos %p, p_end %p", p_pos, p_end);
DPRINTFL(MSG_VDEBUG, "max_fields_len %u ", (*exporter).template_arr[found_index].max_fields_length);
DPRINTFL(MSG_VDEBUG, "fieldss_len %u ", (*exporter).template_arr[found_index].fields_length);
#endif
// add offset to the buffer's beginning: this is, where we will write to.
p_pos += templ->fields_length;
#if 0
DPRINTFL(MSG_VDEBUG, "B p_pos %p, p_end %p", p_pos, p_end);
#endif
for(i = 0; i < data_length; i++) {
ret = write_octet(&p_pos, p_end, *(((uint8_t*)data)+i) );
}
// add to the written length:
templ->fields_length += data_length;
return 0;
}
/*!
* \brief End a previously started and defined template, options template, or data template.
*
* \param exporter pointer to previously initialized exporter struct
* \param template_id ID of the template
* \return 0 success
* \return -1 failure. Reasons include:<ul><li>Template ID unknown</li><li>number of Template fields added does not match number announced when calling <tt>ipfix_start_template()</tt></li></ul>
* \sa ipfix_start_template(), ipfix_put_template_field(), ipfix_send()
*/
int ipfix_end_template(ipfix_exporter *exporter, uint16_t template_id)
{
int found_index;
char *p_pos;
char *p_end;
found_index = ipfix_find_template(exporter, template_id);
// test for a valid slot:
if (found_index < 0) {
msg(MSG_ERROR, "template %u not found", template_id);
return -1;
}
ipfix_lo_template *templ=(&exporter->template_arr[found_index]);
if (templ->fields_added != templ->field_count + templ->fixedfield_count) {
msg(MSG_ERROR, "Number of added template fields does not match number passed to ipfix_start_template");
ipfix_deinit_template(exporter, templ);
return -1;
}
// reallocate the memory , i.e. free superfluous memory, as we allocated enough memory to hold
// all possible vendor specific IDs.
templ->template_fields=(char *)realloc(templ->template_fields, templ->fields_length);
templ->max_fields_length=templ->fields_length;
/*
write the real length field:
set pointers:
beginning of the buffer
*/
p_pos = exporter->template_arr[found_index].template_fields;
// end of the buffer
p_end = p_pos + exporter->template_arr[found_index].max_fields_length;
// add offset of 2 bytes to the buffer's beginning: this is, where we will write to.
p_pos += 2;
// write the length field
write_unsigned16 (&p_pos, p_end, templ->fields_length);
// call the template valid
templ->state = T_COMMITED;
// force resending templates to UDP collectors by resetting transmission time
exporter->last_template_transmission_time = 0;
return 0;
}
/*
* removes a template set from the exporter
* Checks, if the template is in use, before trying to free it.
* Parameters:
* exporter: exporting process to associate the template with
* template* : pointer to the template to be freed
* Returns: 0 on success, -1 on failure
* This is an internal function.
*/
static int ipfix_deinit_template(ipfix_exporter *exporter, ipfix_lo_template *templ) {
// note: ipfix_deinit_template_array tries to free all possible templates, many of them
// won't be initialized. So you'll get a lot of warning messages, which are just fine...
if(templ == NULL)
return -1;
// first test, if we can free this template
if (templ->state == T_UNUSED)
return -1;
DPRINTFL(MSG_VDEBUG, "deleting Template ID: %d validity: %d", templ->template_id, templ->state);
templ->state = T_UNUSED;
free(templ->template_fields);
templ->template_fields = 0;
return 0;
}
/*!
* \brief Set time after that Templates are going to be resent
*
* If UDP is used as the transport protocol, this value determines the interval
* in which active Templates are retransmitted.
* \param exporter pointer to previously initialized exporter struct
* \param timer timeout value in seconds
* \return 0 This value is always returned.
*/
int ipfix_set_template_transmission_timer(ipfix_exporter *exporter, uint32_t timer){
exporter->template_transmission_timer = timer;
return 0;
}
/*!
* \brief Set SCTP packet lifetime
*
* Set packet lifetime for SCTP data packets (lifetime > 0 : unreliable packets)
*
* \param exporter pointer to previously initialized exporter struct
* \param lifetime packet lifetime in milliseconds
* \return 0 This value is always returned.
*/
int ipfix_set_sctp_lifetime(ipfix_exporter *exporter, uint32_t lifetime) {
exporter->sctp_lifetime = lifetime;
return 0;
}
/*!
* \brief Set up SCTP reconnect timer
*
* Time after which a reconnection attempt is made in case the connection to
* the Collector is lost.
*
* \param exporter pointer to previously initialized exporter struct
* \param timer timeout value in seconds
* \return 0 This value is always returned.
*/
int ipfix_set_sctp_reconnect_timer(ipfix_exporter *exporter, uint32_t timer) {
exporter->sctp_reconnect_timer = 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