Skip to content

Commit 7cd0577

Browse files
committed
Support TLS in ipfixsend
1 parent 4a41a98 commit 7cd0577

File tree

4 files changed

+207
-10
lines changed

4 files changed

+207
-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: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ 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");
8585
printf(" -c Precache input file (for performance tests)\n");
8686
printf(" -n num How many times the file should be sent (default: infinity)\n");
8787
printf(" -s speed Maximum data sending speed/s\n");
@@ -90,6 +90,8 @@ void usage()
9090
printf(" -R num Real-time sending\n");
9191
printf(" Allow speed-up sending 'num' times (realtime: 1.0)\n");
9292
printf(" -O num Rewrite Observation Domain ID (ODID)\n");
93+
printf(" -C path Set path to certificate/private key for TLS verification.\n");
94+
printf(" This is required only if the server requires clients to verify.\n");
9395
printf("\n");
9496
}
9597

@@ -125,14 +127,16 @@ int main(int argc, char** argv)
125127
bool odid_rewrite = false;
126128
long odid_new;
127129

130+
char *certificate = NULL;
131+
128132
if (argc == 1) {
129133
usage();
130134
return 0;
131135
}
132136

133137
// Parse parameters
134138
int c;
135-
while ((c = getopt(argc, argv, "hci:d:p:t:n:s:S:R:O:")) != -1) {
139+
while ((c = getopt(argc, argv, "hci:d:p:t:n:s:S:R:O:C:")) != -1) {
136140
switch (c) {
137141
case 'h':
138142
usage();
@@ -168,6 +172,9 @@ int main(int argc, char** argv)
168172
odid_rewrite = true;
169173
odid_new = atol(optarg);
170174
break;
175+
case 'C':
176+
certificate = optarg;
177+
break;
171178
default:
172179
fprintf(stderr, "Unknown option.\n");
173180
return 1;
@@ -202,6 +209,15 @@ int main(int argc, char** argv)
202209
return 1;
203210
}
204211

212+
if (certificate && strcmp(type, "TLS") != 0) {
213+
fprintf(
214+
stderr,
215+
"Certificate path is valid only with type TLS, but the type is %s.\n",
216+
type
217+
);
218+
return 1;
219+
}
220+
205221
// Check whether everything is set
206222
if (!input) {
207223
fprintf(stderr, "Input file must be set!\n");
@@ -217,6 +233,13 @@ int main(int argc, char** argv)
217233
return 1;
218234
}
219235

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

src/tools/ipfixsend/siso.c

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

447+
if (conf->type == SC_TLS && siso_tls_connect(conf, new_fd) != SISO_OK) {
448+
close(new_fd);
449+
return SISO_ERR;
450+
}
451+
312452
conf->sockfd = new_fd;
313453
return SISO_OK;
314454
}
@@ -345,7 +485,16 @@ int siso_create_connection(sisoconf* conf, const char* ip, const char *port, con
345485
*/
346486
void siso_close_connection(sisoconf *conf)
347487
{
348-
if (conf && conf->sockfd > 0) {
488+
if (!conf) {
489+
return;
490+
}
491+
492+
if (conf->ssl) {
493+
SSL_free(conf->ssl);
494+
conf->ssl = NULL;
495+
}
496+
497+
if (conf->sockfd > 0) {
349498
close(conf->sockfd);
350499
conf->sockfd = -1;
351500
}
@@ -375,6 +524,7 @@ int siso_send(sisoconf *conf, const char *data, ssize_t length)
375524

376525
// data sent per cycle
377526
ssize_t sent_now = 0;
527+
int ret_code = 1;
378528

379529
// Size of remaining data
380530
ssize_t todo = length;
@@ -390,12 +540,25 @@ int siso_send(sisoconf *conf, const char *data, ssize_t length)
390540
case SC_SCTP:
391541
sent_now = send(conf->sockfd, ptr, todo, MSG_NOSIGNAL);
392542
break;
543+
case SC_TLS:
544+
ret_code = SSL_write_ex(conf->ssl, ptr, todo, (size_t *)&sent_now);
545+
break;
393546
default:
394547
break;
395548
}
396549

397550
// Check for errors
398-
if (sent_now == -1) {
551+
if (ret_code <= 0) {
552+
int err_code = SSL_get_error(conf->ssl, ret_code);
553+
if (err_code != SSL_ERROR_WANT_READ && err_code != SSL_ERROR_WANT_WRITE) {
554+
// Connection is broken, close...
555+
conf->last_error = tls_code_error(err_code);
556+
siso_close_connection(conf);
557+
return SISO_ERR;
558+
}
559+
560+
// Don't use continue here, the write may be partial.
561+
} else if (sent_now == -1) {
399562
if (errno != EAGAIN && errno != EWOULDBLOCK) {
400563
// Connection broken, close...
401564
conf->last_error = PERROR_LAST;

src/tools/ipfixsend/siso.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ enum siso_units {
7878
*
7979
* \return new sisoconf object
8080
*/
81-
sisoconf *siso_create();
81+
sisoconf *siso_create(void);
8282

8383
/**
8484
* \brief Destructor
@@ -151,6 +151,16 @@ void siso_set_speed(sisoconf* conf, uint32_t limit, enum siso_units units);
151151
*/
152152
void siso_set_speed_str(sisoconf* conf, const char* limit);
153153

154+
/**
155+
* \brief Load certificate and private key for TLS conections. This must be called before
156+
* `siso_create_connection` or not at all.
157+
*
158+
* \param[in] conf susoconf configuration.
159+
* \param[in] cert_file path to file with certificate and private key in PEM format.
160+
* \return #SISO_OK or #SISO_ERR and sets error message.
161+
*/
162+
int siso_load_cert(sisoconf *conf, const char *cert_file);
163+
154164
/**
155165
* \brief Create new connection
156166
*
@@ -199,4 +209,3 @@ int siso_send(sisoconf *conf, const char *data, ssize_t length);
199209
#endif
200210

201211
#endif /* SISO_H */
202-

0 commit comments

Comments
 (0)