Skip to content

Commit af9a44a

Browse files
committedMay 9, 2024·
Introduces DTLS for UDP
1 parent e7d2fa8 commit af9a44a

12 files changed

+436
-10
lines changed
 

‎meson.build

+7
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ conf.set('GPERF_LEN_TYPE', gperf_len_type,
128128

129129
############################################################
130130

131+
libopenssl = dependency('openssl',
132+
version : '>= 1.1.0',
133+
required : get_option('openssl'))
134+
conf.set10('HAVE_OPENSSL', libopenssl.found())
135+
136+
############################################################
131137
config_h = configure_file(
132138
output : 'config.h',
133139
configuration : conf)
@@ -165,6 +171,7 @@ systemd_netlogd = executable(
165171
link_with : libshared,
166172
dependencies : [
167173
libcap,
174+
libopenssl,
168175
libsystemd],
169176
install : true,
170177
install_dir : get_option('prefix'))

‎meson_options.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
option('version-tag', type : 'string',
2+
description : 'override the git version string')
3+
4+
option('openssl', type : 'boolean', value : true,
5+
description : 'enable openssl support')

‎src/meson.build

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ libshared_sources = files('''
4141
share/ioprio.h
4242
share/io-util.c
4343
share/io-util.h
44+
share/iovec-util.c
45+
share/iovec-util.h
4446
share/escape.c
4547
share/escape.h
4648
share/user-util.c
@@ -103,6 +105,9 @@ systemd_netlogd_sources = files('''
103105
netlog/netlog-manager.c
104106
netlog/netlog-manager.h
105107
netlog/netlog-network.c
108+
netlog/netlog-network.h
109+
netlog/netlog-dtls.c
110+
netlog/netlog-dtls.h
106111
'''.split())
107112

108113
netlogd_gperf_c = custom_target(

‎src/netlog/netlog-dtls.c

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
3+
#include <openssl/bio.h>
4+
#include <openssl/err.h>
5+
#include <sys/epoll.h>
6+
#include <sys/socket.h>
7+
#include <netinet/in.h>
8+
#include <arpa/inet.h>
9+
10+
#include "alloc-util.h"
11+
#include "io-util.h"
12+
#include "iovec-util.h"
13+
#include "netlog-dtls.h"
14+
#include "fd-util.h"
15+
16+
static char *tls_error_string(int ssl_error, char *buf, size_t count) {
17+
assert(buf || count == 0);
18+
if (ssl_error == SSL_ERROR_SSL)
19+
ERR_error_string_n(ERR_get_error(), buf, count);
20+
else
21+
snprintf(buf, count, "SSL_get_error()=%d", ssl_error);
22+
return buf;
23+
}
24+
25+
#define TLS_ERROR_BUFSIZE 256
26+
#define TLS_ERROR_STRING(error) \
27+
tls_error_string((error), (char[TLS_ERROR_BUFSIZE]){}, TLS_ERROR_BUFSIZE)
28+
29+
static ssize_t dtls_write(DTLSManager *m, const char *buf, size_t count) {
30+
int error, r;
31+
ssize_t ss;
32+
33+
assert(m);
34+
35+
ERR_clear_error();
36+
ss = r = SSL_write(m->ssl, buf, count);
37+
if (r <= 0) {
38+
error = SSL_get_error(m->ssl, r);
39+
if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) {
40+
m->events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT;
41+
ss = -EAGAIN;
42+
} else if (error == SSL_ERROR_ZERO_RETURN) {
43+
m->events = 0;
44+
ss = 0;
45+
} else {
46+
log_debug("Failed to invoke SSL_write: %s", TLS_ERROR_STRING(error));
47+
m->events = 0;
48+
ss = -EPIPE;
49+
}
50+
} else
51+
m->events = 0;
52+
53+
return ss;
54+
}
55+
56+
ssize_t dtls_stream_writev(DTLSManager *m, const struct iovec *iov, size_t iovcnt) {
57+
_cleanup_free_ char *buf = NULL;
58+
size_t count;
59+
60+
assert(m);
61+
assert(m->ssl);
62+
assert(iov);
63+
assert(iovec_total_size(iov, iovcnt) > 0);
64+
65+
/* single buffer. Suboptimal, but better than multiple SSL_write calls. */
66+
count = iovec_total_size(iov, iovcnt);
67+
buf = new(char, count);
68+
for (size_t i = 0, pos = 0; i < iovcnt; pos += iov[i].iov_len, i++)
69+
memcpy(buf + pos, iov[i].iov_base, iov[i].iov_len);
70+
71+
return dtls_write(m, buf, count);
72+
}
73+
74+
int dtls_connect(DTLSManager *m, SocketAddress *address) {
75+
_cleanup_(BIO_freep) BIO *bio = NULL;
76+
_cleanup_(SSL_freep) SSL *ssl = NULL;
77+
const SSL_CIPHER *cipher;
78+
union sockaddr_union sa;
79+
socklen_t salen;
80+
SSL_CTX *ctx;
81+
struct timeval timeout = {
82+
.tv_sec = 3,
83+
.tv_usec = 0,
84+
};
85+
int fd, r;
86+
87+
assert(m);
88+
89+
switch (address->sockaddr.sa.sa_family) {
90+
case AF_INET:
91+
sa = (union sockaddr_union) {
92+
.in.sin_family = address->sockaddr.sa.sa_family,
93+
.in.sin_port = address->sockaddr.in.sin_port,
94+
.in.sin_addr = address->sockaddr.in.sin_addr,
95+
};
96+
salen = sizeof(sa.in);
97+
break;
98+
case AF_INET6:
99+
sa = (union sockaddr_union) {
100+
.in6.sin6_family = address->sockaddr.sa.sa_family,
101+
.in6.sin6_port = address->sockaddr.in6.sin6_port,
102+
.in6.sin6_addr = address->sockaddr.in6.sin6_addr,
103+
};
104+
salen = sizeof(sa.in6);
105+
break;
106+
default:
107+
return -EAFNOSUPPORT;
108+
}
109+
110+
fd = socket(AF_INET, SOCK_DGRAM, 0);
111+
if (fd < 0)
112+
return log_error_errno(errno, "Failed to allocate socket: %m");;
113+
114+
r = connect(fd, &address->sockaddr.sa, salen);
115+
if (r < 0 && errno != EINPROGRESS)
116+
return log_error_errno(errno, "Failed to connect dtls socket: %m");;
117+
118+
ctx = SSL_CTX_new(DTLS_method());
119+
if (!ctx)
120+
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM),
121+
"Failed to allocate memory for SSL CTX: %m");
122+
123+
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
124+
SSL_CTX_set_default_verify_paths(ctx);
125+
126+
ssl = SSL_new(ctx);
127+
if (!ssl)
128+
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM),
129+
"Failed to allocate memory for ssl: %s",
130+
ERR_error_string(ERR_get_error(), NULL));
131+
132+
/* Create BIO from socket array! */
133+
bio = BIO_new_dgram(fd, BIO_NOCLOSE);
134+
if (!bio)
135+
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM),
136+
"Failed to allocate memory for bio: %m");
137+
138+
BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &address);
139+
SSL_set_bio(ssl , bio, bio);
140+
141+
r = SSL_connect(ssl);
142+
if (r <= 0)
143+
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM),
144+
"Failed to SSL_connect: %s",
145+
ERR_error_string(ERR_get_error(), NULL));
146+
147+
cipher = SSL_get_current_cipher(ssl);
148+
log_debug("dtls_connect: Cipher Version: %s Name: %s", SSL_CIPHER_get_version(cipher), SSL_CIPHER_get_name(cipher));
149+
150+
/* Set reference in SSL obj */
151+
SSL_set_ex_data(ssl, 0, NULL);
152+
SSL_set_ex_data(ssl, 1, NULL);
153+
154+
/* Set and activate timeouts */
155+
BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);
156+
157+
m->bio = TAKE_PTR(bio);
158+
m->ssl = TAKE_PTR(ssl);
159+
m->ctx = ctx;
160+
m->fd = fd;
161+
162+
m->connected = true;
163+
return 0;
164+
}
165+
166+
void dtls_disconnect(DTLSManager *m) {
167+
if (!m)
168+
return;
169+
170+
ERR_clear_error();
171+
172+
if (m->ssl) {
173+
SSL_shutdown(m->ssl);
174+
SSL_free(m->ssl);
175+
m->ssl = NULL;
176+
}
177+
178+
if (m->bio) {
179+
BIO_free(m->bio);
180+
m->bio = NULL;
181+
}
182+
183+
m->fd = safe_close(m->fd);
184+
}
185+
186+
void dtls_manager_free(DTLSManager *m) {
187+
if (!m)
188+
return;
189+
190+
if (m->ctx)
191+
SSL_CTX_free(m->ctx);
192+
193+
free(m);
194+
}
195+
196+
int dtls_manager_init(DTLSManager **ret) {
197+
_cleanup_(dtls_manager_freep) DTLSManager *m = NULL;
198+
199+
m = new0(DTLSManager, 1);
200+
if (!m)
201+
return log_oom();
202+
203+
*ret = TAKE_PTR(m);
204+
return 0;
205+
}

‎src/netlog/netlog-dtls.h

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
#pragma once
3+
4+
#include <openssl/ssl.h>
5+
#include <openssl/bio.h>
6+
#include <stdbool.h>
7+
8+
#include "socket-util.h"
9+
10+
typedef struct DTLSManager DTLSManager;
11+
12+
struct DTLSManager {
13+
SSL_SESSION *session;
14+
SSL_CTX *ctx;
15+
BIO *bio;
16+
SSL *ssl;
17+
18+
uint32_t events;
19+
int fd;
20+
21+
bool shutdown;
22+
bool connected;
23+
24+
BUF_MEM *write_buffer;
25+
size_t buffer_offset;
26+
};
27+
28+
void dtls_manager_free(DTLSManager *m);
29+
int dtls_manager_init(DTLSManager **m);
30+
31+
int dtls_connect(DTLSManager *m, SocketAddress *addr);
32+
void dtls_disconnect(DTLSManager *m);
33+
34+
ssize_t dtls_stream_writev(DTLSManager *m, const struct iovec *iov, size_t iovcnt);
35+
36+
DEFINE_TRIVIAL_CLEANUP_FUNC(DTLSManager*, dtls_manager_free);
37+
38+
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
39+
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);

‎src/netlog/netlog-manager.c

+13-5
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828
for (sd_journal_restart_data(j); ((retval) = sd_journal_enumerate_data((j), &(data), &(l))) > 0; )
2929

3030
static const char *const protocol_table[_SYSLOG_TRANSMISSION_PROTOCOL_MAX] = {
31-
[SYSLOG_TRANSMISSION_PROTOCOL_UDP] = "udp",
32-
[SYSLOG_TRANSMISSION_PROTOCOL_TCP] = "tcp",
31+
[SYSLOG_TRANSMISSION_PROTOCOL_UDP] = "udp",
32+
[SYSLOG_TRANSMISSION_PROTOCOL_TCP] = "tcp",
33+
[SYSLOG_TRANSMISSION_PROTOCOL_DTLS] = "dtls",
3334
};
3435

3536
DEFINE_STRING_TABLE_LOOKUP(protocol, int);
@@ -387,9 +388,15 @@ int manager_connect(Manager *m) {
387388

388389
manager_disconnect(m);
389390

390-
r = manager_open_network_socket(m);
391-
if (r < 0)
392-
return log_error_errno(r, "Failed to create network socket: %m");
391+
if (m->protocol == SYSLOG_TRANSMISSION_PROTOCOL_DTLS) {
392+
r = dtls_connect(m->dtls, &m->address);
393+
if (r < 0)
394+
return r;
395+
} else {
396+
r = manager_open_network_socket(m);
397+
if (r < 0)
398+
return log_error_errno(r, "Failed to create network socket: %m");
399+
}
393400

394401
r = manager_journal_monitor_listen(m);
395402
if (r < 0)
@@ -404,6 +411,7 @@ void manager_disconnect(Manager *m) {
404411
close_journal_input(m);
405412

406413
manager_close_network_socket(m);
414+
dtls_disconnect(m->dtls);
407415

408416
m->event_journal_input = sd_event_source_unref(m->event_journal_input);
409417

‎src/netlog/netlog-manager.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#include "sd-network.h"
88
#include "socket-util.h"
9-
#include "netlog-tls.h"
9+
#include "netlog-dtls.h"
1010

1111
typedef enum SysLogTransmissionProtocol {
1212
SYSLOG_TRANSMISSION_PROTOCOL_UDP = 1 << 0,
@@ -61,7 +61,7 @@ struct Manager {
6161
bool syslog_msgid;
6262
bool encrypt;
6363

64-
TLSManager *tls;
64+
DTLSManager *dtls;
6565
};
6666

6767
int manager_new(const char *state_file, const char *cursor, Manager **ret);

‎src/netlog/netlog-network.c

+10-3
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ static int format_rfc5424(Manager *m,
168168
if (m->protocol == SYSLOG_TRANSMISSION_PROTOCOL_TCP)
169169
IOVEC_SET_STRING(iov[n++], "\n");
170170

171-
return network_send(m, iov, n);
171+
172+
if (m->protocol == SYSLOG_TRANSMISSION_PROTOCOL_DTLS)
173+
return dtls_stream_writev(m->dtls, iov, n);
174+
else
175+
return network_send(m, iov, n);
172176
}
173177

174178
static int format_rfc3339(Manager *m,
@@ -236,7 +240,10 @@ static int format_rfc3339(Manager *m,
236240
if (m->protocol == SYSLOG_TRANSMISSION_PROTOCOL_TCP)
237241
IOVEC_SET_STRING(iov[n++], "\n");
238242

239-
return network_send(m, iov, n);
243+
if (m->protocol == SYSLOG_TRANSMISSION_PROTOCOL_DTLS)
244+
return dtls_stream_writev(m->dtls, iov, n);
245+
else
246+
return network_send(m, iov, n);
240247
}
241248

242249
int manager_push_to_network(Manager *m,
@@ -305,7 +312,7 @@ int manager_network_connect_socket(Manager *m) {
305312
salen = sizeof(sa.in6);
306313
break;
307314
default:
308-
return EAFNOSUPPORT;
315+
return -EAFNOSUPPORT;
309316
}
310317

311318
r = connect(m->socket, &m->address.sockaddr.sa, salen);

‎src/netlog/systemd-netlogd.c

+6
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ int main(int argc, char **argv) {
171171
goto finish;
172172
}
173173

174+
if (m->protocol == SYSLOG_TRANSMISSION_PROTOCOL_DTLS) {
175+
r = dtls_manager_init(&m->dtls);
176+
if (r < 0)
177+
return r;
178+
}
179+
174180
r = setup_cursor_state_file(m, uid, gid);
175181
if (r < 0)
176182
goto cleanup;

‎src/share/iovec-util.c

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
3+
#include "iovec-util.h"
4+
#include "string-util.h"
5+
6+
size_t iovec_total_size(const struct iovec *iovec, size_t n) {
7+
size_t sum = 0;
8+
9+
assert(iovec || n == 0);
10+
11+
FOREACH_ARRAY(j, iovec, n)
12+
sum += j->iov_len;
13+
14+
return sum;
15+
}
16+
17+
bool iovec_increment(struct iovec *iovec, size_t n, size_t k) {
18+
assert(iovec || n == 0);
19+
20+
/* Returns true if there is nothing else to send (bytes written cover all of the iovec),
21+
* false if there's still work to do. */
22+
23+
FOREACH_ARRAY(j, iovec, n) {
24+
size_t sub;
25+
26+
if (j->iov_len == 0)
27+
continue;
28+
if (k == 0)
29+
return false;
30+
31+
sub = MIN(j->iov_len, k);
32+
j->iov_len -= sub;
33+
j->iov_base = (uint8_t*) j->iov_base + sub;
34+
k -= sub;
35+
}
36+
37+
assert(k == 0); /* Anything else would mean that we wrote more bytes than available,
38+
* or the kernel reported writing more bytes than sent. */
39+
return true;
40+
}
41+
42+
void iovec_array_free(struct iovec *iovec, size_t n_iovec) {
43+
assert(iovec || n_iovec == 0);
44+
45+
FOREACH_ARRAY(i, iovec, n_iovec)
46+
free(i->iov_base);
47+
48+
free(iovec);
49+
}

‎src/share/iovec-util.h

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2+
#pragma once
3+
4+
#include <stdbool.h>
5+
#include <sys/types.h>
6+
#include <sys/uio.h>
7+
8+
#include "alloc-util.h"
9+
#include "macro.h"
10+
11+
/* An iovec pointing to a single NUL byte */
12+
#define IOVEC_NUL_BYTE (const struct iovec) { \
13+
.iov_base = (void*) (const uint8_t[1]) { 0 }, \
14+
.iov_len = 1, \
15+
}
16+
17+
size_t iovec_total_size(const struct iovec *iovec, size_t n);
18+
19+
bool iovec_increment(struct iovec *iovec, size_t n, size_t k);
20+
21+
/* This accepts both const and non-const pointers */
22+
#define IOVEC_MAKE(base, len) \
23+
(struct iovec) { \
24+
.iov_base = (void*) (base), \
25+
.iov_len = (len), \
26+
}
27+
28+
static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) {
29+
assert(iovec);
30+
/* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */
31+
*iovec = IOVEC_MAKE(s, s ? strlen(s) : 0);
32+
return iovec;
33+
}
34+
35+
#define IOVEC_MAKE_STRING(s) \
36+
*iovec_make_string(&(struct iovec) {}, s)
37+
38+
#define CONST_IOVEC_MAKE_STRING(s) \
39+
(const struct iovec) { \
40+
.iov_base = (char*) s, \
41+
.iov_len = STRLEN(s), \
42+
}
43+
44+
static inline void iovec_done(struct iovec *iovec) {
45+
/* A _cleanup_() helper that frees the iov_base in the iovec */
46+
assert(iovec);
47+
48+
iovec->iov_base = mfree(iovec->iov_base);
49+
iovec->iov_len = 0;
50+
}
51+
52+
static inline bool iovec_is_set(const struct iovec *iovec) {
53+
/* Checks if the iovec points to a non-empty chunk of memory */
54+
return iovec && iovec->iov_len > 0 && iovec->iov_base;
55+
}
56+
57+
static inline bool iovec_is_valid(const struct iovec *iovec) {
58+
/* Checks if the iovec is either NULL, empty or points to a valid bit of memory */
59+
return !iovec || (iovec->iov_base || iovec->iov_len == 0);
60+
}
61+
62+
char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
63+
char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
64+
65+
void iovec_array_free(struct iovec *iovec, size_t n_iovec);

‎src/share/macro.h

+30
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
_Pragma("GCC diagnostic push"); \
5959
_Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
6060

61+
#define DISABLE_WARNING_ADDRESS \
62+
_Pragma("GCC diagnostic push"); \
63+
_Pragma("GCC diagnostic ignored \"-Waddress\"")
64+
6165
#define REENABLE_WARNING \
6266
_Pragma("GCC diagnostic pop")
6367

@@ -383,13 +387,39 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
383387
#endif
384388
#endif
385389

390+
#define _FOREACH_ARRAY(i, array, num, m, end) \
391+
for (typeof(array[0]) *i = (array), *end = ({ \
392+
typeof(num) m = (num); \
393+
(i && m > 0) ? i + m : NULL; \
394+
}); end && i < end; i++)
395+
396+
#define FOREACH_ARRAY(i, array, num) \
397+
_FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ))
398+
399+
#define FOREACH_ELEMENT(i, array) \
400+
FOREACH_ARRAY(i, array, ELEMENTSOF(array))
401+
386402
#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
387403
static inline void func##p(type *p) { \
388404
if (*p) \
389405
func(*p); \
390406
} \
391407
struct __useless_struct_to_allow_trailing_semicolon__
392408

409+
410+
/* When func() doesn't return the appropriate type, set variable to empty afterwards.
411+
* The func() may be provided by a dynamically loaded shared library, hence add an assertion. */
412+
#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty) \
413+
static inline void func##p(type *p) { \
414+
if (*p != (empty)) { \
415+
DISABLE_WARNING_ADDRESS; \
416+
assert(func); \
417+
REENABLE_WARNING; \
418+
func(*p); \
419+
*p = (empty); \
420+
} \
421+
}
422+
393423
/* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time
394424
* resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */
395425
#define TAKE_PTR(ptr) \

0 commit comments

Comments
 (0)
Please sign in to comment.