Skip to content

Commit af8ed1f

Browse files
committed
Support TLS in ipfixsend
1 parent 17fd3e9 commit af8ed1f

File tree

4 files changed

+223
-10
lines changed

4 files changed

+223
-10
lines changed

src/tools/ipfixsend/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ add_executable(ipfixsend2
88
siso.h
99
)
1010

11+
target_link_libraries(ipfixsend2 OpenSSL::Crypto OpenSSL::SSL)
12+
1113
# Installation targets
1214
install(
1315
TARGETS ipfixsend2
1416
DESTINATION bin
15-
)
17+
)

src/tools/ipfixsend/ipfixsend.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ void usage()
8181
printf(" -i path IPFIX input file\n");
8282
printf(" -d ip Destination IP address (default: %s)\n", DEFAULT_IP);
8383
printf(" -p port Destination port number (default: %s)\n", DEFAULT_PORT);
84-
printf(" -t type Connection type (UDP or TCP) (default: UDP)\n");
84+
printf(" -t type Connection type (UDP/TCP/SCTP/TLS) (default: UDP)\n");
85+
printf(" When using TLS you may want to specify trusted certificate with\n");
86+
printf(" environment variable SSL_CERT_FILE or SSL_CERT_DIR.\n");
8587
printf(" -c Precache input file (for performance tests)\n");
8688
printf(" -n num How many times the file should be sent (default: infinity)\n");
8789
printf(" -s speed Maximum data sending speed/s\n");
@@ -90,6 +92,8 @@ void usage()
9092
printf(" -R num Real-time sending\n");
9193
printf(" Allow speed-up sending 'num' times (realtime: 1.0)\n");
9294
printf(" -O num Rewrite Observation Domain ID (ODID)\n");
95+
printf(" -C path Set path to certificate/private key for TLS verification.\n");
96+
printf(" This is required only if the server requires clients to verify.\n");
9397
printf("\n");
9498
}
9599

@@ -125,14 +129,16 @@ int main(int argc, char** argv)
125129
bool odid_rewrite = false;
126130
long odid_new;
127131

132+
char *certificate = NULL;
133+
128134
if (argc == 1) {
129135
usage();
130136
return 0;
131137
}
132138

133139
// Parse parameters
134140
int c;
135-
while ((c = getopt(argc, argv, "hci:d:p:t:n:s:S:R:O:")) != -1) {
141+
while ((c = getopt(argc, argv, "hci:d:p:t:n:s:S:R:O:C:")) != -1) {
136142
switch (c) {
137143
case 'h':
138144
usage();
@@ -168,6 +174,9 @@ int main(int argc, char** argv)
168174
odid_rewrite = true;
169175
odid_new = atol(optarg);
170176
break;
177+
case 'C':
178+
certificate = optarg;
179+
break;
171180
default:
172181
fprintf(stderr, "Unknown option.\n");
173182
return 1;
@@ -202,6 +211,15 @@ int main(int argc, char** argv)
202211
return 1;
203212
}
204213

214+
if (certificate && strcmp(type, "TLS") != 0) {
215+
fprintf(
216+
stderr,
217+
"Certificate path is valid only with type TLS, but the type is %s.\n",
218+
type
219+
);
220+
return 1;
221+
}
222+
205223
// Check whether everything is set
206224
if (!input) {
207225
fprintf(stderr, "Input file must be set!\n");
@@ -217,6 +235,13 @@ int main(int argc, char** argv)
217235
return 1;
218236
}
219237

238+
// Load certificate and private key
239+
if (certificate && siso_load_cert(sender, certificate) != SISO_OK) {
240+
fprintf(stderr, "Failed to load certificate file: %s\n", siso_get_last_err(sender));
241+
siso_destroy(sender);
242+
return 1;
243+
}
244+
220245
// Prepare an input file
221246
reader_t *reader = reader_create(input, precache);
222247
if (!reader) {

src/tools/ipfixsend/siso.c

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141

4242
#include <stdbool.h>
4343
#include <stdlib.h>
44+
#include <assert.h>
45+
#include <signal.h>
4446

4547
#include <errno.h>
4648
#include <netdb.h>
@@ -57,6 +59,9 @@
5759
#include <stdio.h>
5860
#include <strings.h>
5961

62+
#include <openssl/ssl.h>
63+
#include <openssl/err.h>
64+
6065
/**
6166
* \brief Check that a pointer is not null. Otherwise returns #SISO_ERR
6267
* \param[in] ptr Pointer to check
@@ -71,6 +76,14 @@
7176
* \brief Get the lass error message
7277
*/
7378
#define PERROR_LAST strerror(errno)
79+
/**
80+
* @brief Get the last global OpenSSL error message
81+
*/
82+
#define TLS_LAST_ERROR tls_code_error(ERR_get_error())
83+
/**
84+
* @brief Get the last OpenSSL error message for SSL connection.
85+
*/
86+
#define TLS_LAST_SSL_ERROR(ssl, ret) tls_ssl_code_error(SSL_get_error((ssl), (ret)))
7487
/** Maximum message size that can be send over UDP */
7588
#define SISO_UDP_MAX 65000
7689
/**
@@ -86,6 +99,7 @@ enum siso_conn_type {
8699
SC_UDP,
87100
SC_TCP,
88101
SC_SCTP,
102+
SC_TLS,
89103
SC_UNKNOWN,
90104
};
91105

@@ -109,26 +123,32 @@ static const char *siso_messages[] = {
109123
static const char *siso_sc_types[] = {
110124
[SC_UDP] = "UDP",
111125
[SC_TCP] = "TCP",
112-
[SC_SCTP] = "SCTP"
126+
[SC_SCTP] = "SCTP",
127+
[SC_TLS] = "TLS",
113128
};
114129

130+
/** Buffer for error messages. */
131+
static char sisco_err_msg_buf[256] = { 0 };
132+
115133
/**
116134
* \brief Main sisolib structure
117135
*/
118136
struct sisoconf_s {
119137
const char *last_error; /**< last error message */
120-
enum siso_conn_type type; /**< UDP/TCP/SCTP */
138+
enum siso_conn_type type; /**< UDP/TCP/SCTP/TLS */
121139
struct addrinfo *servinfo; /**< server information */
122140
int sockfd; /**< socket descriptor */
123141
uint64_t max_speed; /**< max sending speed */
124142
uint64_t act_speed; /**< actual speed */
125143
struct timeval begin, end; /**< start/end time for limited transfers */
144+
SSL_CTX *ssl_ctx; /**< context for creating TLS connecitons */
145+
SSL *ssl; /**< TLS connection */
126146
};
127147

128148
/**
129149
* \brief Constructor
130150
*/
131-
sisoconf *siso_create()
151+
sisoconf *siso_create(void)
132152
{
133153
/* allocate memory */
134154
sisoconf *conf = (sisoconf *) calloc(1, sizeof(sisoconf));
@@ -154,6 +174,11 @@ void siso_destroy(sisoconf *conf)
154174
freeaddrinfo(conf->servinfo);
155175
}
156176

177+
if (conf->ssl_ctx) {
178+
SSL_CTX_free(conf->ssl_ctx);
179+
conf->ssl_ctx = NULL;
180+
}
181+
157182
free(conf);
158183
}
159184

@@ -256,6 +281,7 @@ int siso_getaddrinfo(sisoconf *conf, const char *ip, const char *port)
256281
hints.ai_socktype = SOCK_DGRAM;
257282
hints.ai_protocol = IPPROTO_UDP;
258283
break;
284+
case SC_TLS:
259285
case SC_TCP:
260286
hints.ai_socktype = SOCK_STREAM;
261287
hints.ai_protocol = IPPROTO_TCP;
@@ -281,6 +307,129 @@ int siso_getaddrinfo(sisoconf *conf, const char *ip, const char *port)
281307
return SISO_OK;
282308
}
283309

310+
/**
311+
* @brief Get OpenSSL error message for the given code
312+
*/
313+
static inline const char *tls_code_error(unsigned long code)
314+
{
315+
if (code == SSL_ERROR_SYSCALL) {
316+
return PERROR_LAST;
317+
}
318+
ERR_error_string_n(code, sisco_err_msg_buf, sizeof(sisco_err_msg_buf));
319+
return &sisco_err_msg_buf[0];
320+
}
321+
322+
/**
323+
* @brief Get OpenSSL error message for the given code from SSL connection.
324+
*/
325+
static inline const char *tls_ssl_code_error(unsigned long code)
326+
{
327+
if (code == SSL_ERROR_SYSCALL) {
328+
code = ERR_get_error();
329+
}
330+
return tls_code_error(code);
331+
}
332+
333+
/**
334+
* @brief Get SSL_CTX. Initialize it if it is not initialized.
335+
* @return NULL on failure.
336+
*/
337+
SSL_CTX *siso_get_ssl_ctx(sisoconf *conf)
338+
{
339+
if (conf->ssl_ctx) {
340+
return conf->ssl_ctx;
341+
}
342+
343+
SSL_load_error_strings();
344+
345+
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
346+
if (!ctx) {
347+
conf->last_error = TLS_LAST_ERROR;
348+
return NULL;
349+
}
350+
351+
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
352+
353+
if (!SSL_CTX_set_default_verify_paths(ctx)) {
354+
conf->last_error = TLS_LAST_ERROR;
355+
SSL_CTX_free(ctx);
356+
return NULL;
357+
}
358+
359+
if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) {
360+
conf->last_error = TLS_LAST_ERROR;
361+
SSL_CTX_free(ctx);
362+
return NULL;
363+
}
364+
365+
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
366+
367+
// There is no way to pass MSG_SIGIGN when writing using OpenSSL without
368+
// creating custom BIO. So we can just ignore SIGPIPE which can occur if
369+
// peer closes the file descriptor.
370+
signal(SIGPIPE, SIG_IGN);
371+
372+
return conf->ssl_ctx = ctx;
373+
}
374+
375+
int siso_load_cert(sisoconf *conf, const char *cert_file) {
376+
SSL_CTX *ctx = siso_get_ssl_ctx(conf);
377+
378+
if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
379+
conf->last_error = TLS_LAST_ERROR;
380+
return SISO_ERR;
381+
}
382+
383+
if (SSL_CTX_use_PrivateKey_file(ctx, cert_file, SSL_FILETYPE_PEM) <= 0) {
384+
conf->last_error = TLS_LAST_ERROR;
385+
return SISO_ERR;
386+
}
387+
388+
return SISO_OK;
389+
}
390+
391+
int siso_tls_connect(sisoconf *conf, int new_fd)
392+
{
393+
assert(!conf->ssl);
394+
395+
SSL_CTX *ctx = siso_get_ssl_ctx(conf);
396+
if (!ctx) {
397+
return SISO_ERR;
398+
}
399+
400+
SSL *ssl = SSL_new(ctx);
401+
if (!ssl) {
402+
conf->last_error = TLS_LAST_ERROR;
403+
return SISO_ERR;
404+
}
405+
406+
BIO *bio = BIO_new(BIO_s_socket());
407+
if (!bio) {
408+
conf->last_error = TLS_LAST_ERROR;
409+
SSL_free(ssl);
410+
return SISO_ERR;
411+
}
412+
413+
BIO_set_fd(bio, new_fd, BIO_NOCLOSE);
414+
SSL_set_bio(ssl, bio, bio);
415+
416+
int res = SSL_connect(ssl);
417+
if (res <= 0) {
418+
// Get the best error message.
419+
long vres = SSL_get_verify_result(ssl);
420+
if (vres == X509_V_OK) {
421+
conf->last_error = TLS_LAST_SSL_ERROR(ssl, res);
422+
} else {
423+
conf->last_error = X509_verify_cert_error_string(vres);
424+
}
425+
SSL_free(ssl);
426+
return SISO_ERR;
427+
}
428+
429+
conf->ssl = ssl;
430+
return SISO_OK;
431+
}
432+
284433
/**
285434
* \brief Create new socket
286435
*/
@@ -309,6 +458,11 @@ int siso_create_socket(sisoconf *conf)
309458
return SISO_ERR;
310459
}
311460

461+
if (conf->type == SC_TLS && siso_tls_connect(conf, new_fd) != SISO_OK) {
462+
close(new_fd);
463+
return SISO_ERR;
464+
}
465+
312466
conf->sockfd = new_fd;
313467
return SISO_OK;
314468
}
@@ -345,7 +499,16 @@ int siso_create_connection(sisoconf* conf, const char* ip, const char *port, con
345499
*/
346500
void siso_close_connection(sisoconf *conf)
347501
{
348-
if (conf && conf->sockfd > 0) {
502+
if (!conf) {
503+
return;
504+
}
505+
506+
if (conf->ssl) {
507+
SSL_free(conf->ssl);
508+
conf->ssl = NULL;
509+
}
510+
511+
if (conf->sockfd > 0) {
349512
close(conf->sockfd);
350513
conf->sockfd = -1;
351514
}
@@ -375,6 +538,7 @@ int siso_send(sisoconf *conf, const char *data, ssize_t length)
375538

376539
// data sent per cycle
377540
ssize_t sent_now = 0;
541+
int ret_code = 1;
378542

379543
// Size of remaining data
380544
ssize_t todo = length;
@@ -390,12 +554,25 @@ int siso_send(sisoconf *conf, const char *data, ssize_t length)
390554
case SC_SCTP:
391555
sent_now = send(conf->sockfd, ptr, todo, MSG_NOSIGNAL);
392556
break;
557+
case SC_TLS:
558+
ret_code = SSL_write_ex(conf->ssl, ptr, todo, (size_t *)&sent_now);
559+
break;
393560
default:
394561
break;
395562
}
396563

397564
// Check for errors
398-
if (sent_now == -1) {
565+
if (ret_code <= 0) {
566+
int err_code = SSL_get_error(conf->ssl, ret_code);
567+
if (err_code != SSL_ERROR_WANT_READ && err_code != SSL_ERROR_WANT_WRITE) {
568+
// Connection is broken, close...
569+
conf->last_error = tls_ssl_code_error(err_code);
570+
siso_close_connection(conf);
571+
return SISO_ERR;
572+
}
573+
574+
// Don't use continue here, the write may be partial.
575+
} else if (sent_now == -1) {
399576
if (errno != EAGAIN && errno != EWOULDBLOCK) {
400577
// Connection broken, close...
401578
conf->last_error = PERROR_LAST;

0 commit comments

Comments
 (0)