Allow setup of new DTLS session while processing on old session

Resolves: #359

Signed-off-by: Alan Jowett alan.jowett@microsoft.com
This commit is contained in:
Alan Jowett 2020-10-02 15:33:39 -06:00
parent c5d3e4f321
commit 3436705a9c
14 changed files with 506 additions and 284 deletions

1
NEWS
View File

@ -5,6 +5,7 @@
- Don't attempt TLS if the client closes the connection with zero data - Don't attempt TLS if the client closes the connection with zero data
sent (#357) sent (#357)
- Increased the maximum configuration line (#364) - Increased the maximum configuration line (#364)
- Allow setup of new DTLS session concurrent with old session.
* Version 1.1.1 (released 2020-09-21) * Version 1.1.1 (released 2020-09-21)

View File

@ -72,6 +72,7 @@ gnutls-bin / gnutls-utils
iproute2 / iproute iproute2 / iproute
yajl-tools / yajl yajl-tools / yajl
iproute2 / iproute iproute2 / iproute
tcpdump / tcpdump
``` ```
See [README-radius](doc/README-radius.md) for more information on Radius See [README-radius](doc/README-radius.md) for more information on Radius

View File

@ -1172,7 +1172,6 @@ static void listen_watcher_cb (EV_P_ ev_io *w, int revents)
ws->cmd_fd = cmd_fd[1]; ws->cmd_fd = cmd_fd[1];
ws->tun_fd = -1; ws->tun_fd = -1;
ws->dtls_tptr.fd = -1;
set_cloexec_flag(fd, false); set_cloexec_flag(fd, false);
ws->conn_fd = fd; ws->conn_fd = fd;
ws->conn_type = stype; ws->conn_type = stype;

View File

@ -339,13 +339,13 @@ void cstp_fatal_close(worker_st *ws,
} }
} }
ssize_t dtls_recv_packet(worker_st *ws, gnutls_datum_t *data, void **p) ssize_t dtls_recv_packet(struct dtls_st *dtls, gnutls_datum_t *data, void **p)
{ {
int ret; int ret;
#ifdef ZERO_COPY #ifdef ZERO_COPY
gnutls_packet_t packet = NULL; gnutls_packet_t packet = NULL;
ret = gnutls_record_recv_packet(ws->dtls_session, &packet); ret = gnutls_record_recv_packet(dtls->dtls_session, &packet);
if (ret > 0) { if (ret > 0) {
gnutls_packet_get(packet, data, NULL); gnutls_packet_get(packet, data, NULL);
*p = packet; *p = packet;
@ -354,7 +354,7 @@ ssize_t dtls_recv_packet(worker_st *ws, gnutls_datum_t *data, void **p)
} }
#else #else
ret = ret =
gnutls_record_recv(ws->dtls_session, ws->buffer, ws->buffer_size); gnutls_record_recv(dtls->dtls_session, ws->buffer, ws->buffer_size);
data->data = ws->buffer; data->data = ws->buffer;
data->size = ret; data->size = ret;
#endif #endif
@ -362,7 +362,7 @@ ssize_t dtls_recv_packet(worker_st *ws, gnutls_datum_t *data, void **p)
return ret; return ret;
} }
ssize_t dtls_send(worker_st *ws, const void *data, ssize_t dtls_send(struct dtls_st *dtls, const void *data,
size_t data_size) size_t data_size)
{ {
int ret; int ret;
@ -370,7 +370,7 @@ ssize_t dtls_send(worker_st *ws, const void *data,
const uint8_t* p = data; const uint8_t* p = data;
while(left > 0) { while(left > 0) {
ret = gnutls_record_send(ws->dtls_session, p, data_size); ret = gnutls_record_send(dtls->dtls_session, p, data_size);
if (ret < 0) { if (ret < 0) {
if (ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED) { if (ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED) {
return ret; return ret;
@ -389,10 +389,10 @@ ssize_t dtls_send(worker_st *ws, const void *data,
return data_size; return data_size;
} }
void dtls_close(worker_st *ws) void dtls_close(struct dtls_st *dtls)
{ {
gnutls_bye(ws->dtls_session, GNUTLS_SHUT_WR); gnutls_bye(dtls->dtls_session, GNUTLS_SHUT_WR);
gnutls_deinit(ws->dtls_session); gnutls_deinit(dtls->dtls_session);
} }
static size_t rehash(const void *_e, void *unused) static size_t rehash(const void *_e, void *unused)
@ -467,7 +467,8 @@ static int verify_certificate_cb(gnutls_session_t session)
return -1; return -1;
} }
if (session == ws->dtls_session) { if ((session == DTLS_ACTIVE(ws)->dtls_session) ||
(session == DTLS_INACTIVE(ws)->dtls_session)) {
oclog(ws, LOG_ERR, "unexpected issue; client shouldn't have offered a certificate in DTLS"); oclog(ws, LOG_ERR, "unexpected issue; client shouldn't have offered a certificate in DTLS");
return GNUTLS_E_CERTIFICATE_ERROR; return GNUTLS_E_CERTIFICATE_ERROR;
} }

View File

@ -144,8 +144,8 @@ void cstp_cork(struct worker_st *ws);
int cstp_uncork(struct worker_st *ws); int cstp_uncork(struct worker_st *ws);
/* DTLS API */ /* DTLS API */
void dtls_close(struct worker_st *ws); void dtls_close(struct dtls_st *dtls);
ssize_t dtls_send(struct worker_st *ws, const void *data, size_t data_size); ssize_t dtls_send(struct dtls_st *dtls, const void *data, size_t data_size);
/* packet API */ /* packet API */
inline static void packet_deinit(void *p) inline static void packet_deinit(void *p)
@ -158,7 +158,7 @@ inline static void packet_deinit(void *p)
} }
ssize_t cstp_recv_packet(struct worker_st *ws, gnutls_datum_t *data, void **p); ssize_t cstp_recv_packet(struct worker_st *ws, gnutls_datum_t *data, void **p);
ssize_t dtls_recv_packet(struct worker_st *ws, gnutls_datum_t *data, void **p); ssize_t dtls_recv_packet(struct dtls_st *dtls, gnutls_datum_t *data, void **p);
/* Helper functions */ /* Helper functions */
unsigned need_file_reload(const char *file, time_t last_access); unsigned need_file_reload(const char *file, time_t last_access);

View File

@ -416,6 +416,7 @@ typedef struct attic_entry_st {
/* generic thing to stop complaints */ /* generic thing to stop complaints */
struct worker_st; struct worker_st;
struct main_server_st; struct main_server_st;
struct dtls_st;
#define MAX_BANNER_SIZE 256 #define MAX_BANNER_SIZE 256
#define MAX_USERNAME_SIZE 64 #define MAX_USERNAME_SIZE 64

View File

@ -49,22 +49,22 @@
#endif #endif
/* recv from the new file descriptor and make sure we have a valid packet */ /* recv from the new file descriptor and make sure we have a valid packet */
static unsigned recv_from_new_fd(struct worker_st *ws, int fd, UdpFdMsg **tmsg) static unsigned recv_from_new_fd(struct worker_st * ws, struct dtls_st *dtls, int fd, UdpFdMsg **tmsg)
{ {
int saved_fd, ret; int saved_fd, ret;
UdpFdMsg *saved_tmsg; UdpFdMsg *saved_tmsg;
/* don't bother with anything if we are on uninitialized state */ /* don't bother with anything if we are on uninitialized state */
if (ws->dtls_session == NULL || ws->udp_state != UP_ACTIVE) if (dtls->dtls_session == NULL || dtls->udp_state != UP_ACTIVE)
return 1; return 1;
saved_fd = ws->dtls_tptr.fd; saved_fd = dtls->dtls_tptr.fd;
saved_tmsg = ws->dtls_tptr.msg; saved_tmsg = dtls->dtls_tptr.msg;
ws->dtls_tptr.msg = *tmsg; dtls->dtls_tptr.msg = *tmsg;
ws->dtls_tptr.fd = fd; dtls->dtls_tptr.fd = fd;
ret = gnutls_record_recv(ws->dtls_session, ws->buffer, ws->buffer_size); ret = gnutls_record_recv(dtls->dtls_session, ws->buffer, ws->buffer_size);
/* we receive GNUTLS_E_AGAIN in case the packet was discarded */ /* we receive GNUTLS_E_AGAIN in case the packet was discarded */
if (ret > 0) { if (ret > 0) {
ret = 1; ret = 1;
@ -73,9 +73,9 @@ static unsigned recv_from_new_fd(struct worker_st *ws, int fd, UdpFdMsg **tmsg)
ret = 0; ret = 0;
revert: revert:
*tmsg = ws->dtls_tptr.msg; *tmsg = dtls->dtls_tptr.msg;
ws->dtls_tptr.fd = saved_fd; dtls->dtls_tptr.fd = saved_fd;
ws->dtls_tptr.msg = saved_tmsg; dtls->dtls_tptr.msg = saved_tmsg;
return ret; return ret;
} }
@ -86,6 +86,7 @@ int handle_commands_from_main(struct worker_st *ws)
UdpFdMsg *tmsg = NULL; UdpFdMsg *tmsg = NULL;
int ret; int ret;
int fd = -1; int fd = -1;
struct dtls_st * dtls = NULL;
/*int cmd_data_len;*/ /*int cmd_data_len;*/
memset(&ws->buffer, 0, sizeof(ws->buffer)); memset(&ws->buffer, 0, sizeof(ws->buffer));
@ -113,7 +114,7 @@ int handle_commands_from_main(struct worker_st *ws)
case CMD_UDP_FD: { case CMD_UDP_FD: {
unsigned has_hello = 1; unsigned has_hello = 1;
if (ws->udp_state != UP_WAIT_FD) { if (DTLS_ACTIVE(ws)->udp_state != UP_WAIT_FD) {
oclog(ws, LOG_DEBUG, "received another a UDP fd!"); oclog(ws, LOG_DEBUG, "received another a UDP fd!");
} }
@ -131,24 +132,27 @@ int handle_commands_from_main(struct worker_st *ws)
if (has_hello == 0) { if (has_hello == 0) {
/* check if the first packet received is a valid one - /* check if the first packet received is a valid one -
* if not discard the new fd */ * if not discard the new fd */
if (!recv_from_new_fd(ws, fd, &tmsg)) { if (!recv_from_new_fd(ws, DTLS_ACTIVE(ws), fd, &tmsg)) {
oclog(ws, LOG_INFO, "received UDP fd message but its session has invalid data!"); oclog(ws, LOG_INFO, "received UDP fd message but its session has invalid data!");
if (tmsg) if (tmsg)
udp_fd_msg__free_unpacked(tmsg, NULL); udp_fd_msg__free_unpacked(tmsg, NULL);
close(fd); close(fd);
return 0; return 0;
} }
dtls = DTLS_ACTIVE(ws);
} else { /* received client hello */ } else { /* received client hello */
ws->udp_state = UP_SETUP; dtls = DTLS_INACTIVE(ws);
dtls->udp_state = UP_SETUP;
oclog(ws, LOG_DEBUG, "Starting DTLS session %d", ws->dtls_active_session ^ 1);
} }
if (ws->dtls_tptr.fd != -1) if (dtls->dtls_tptr.fd != -1)
close(ws->dtls_tptr.fd); close(dtls->dtls_tptr.fd);
if (ws->dtls_tptr.msg != NULL) if (dtls->dtls_tptr.msg != NULL)
udp_fd_msg__free_unpacked(ws->dtls_tptr.msg, NULL); udp_fd_msg__free_unpacked(dtls->dtls_tptr.msg, NULL);
ws->dtls_tptr.msg = tmsg; dtls->dtls_tptr.msg = tmsg;
ws->dtls_tptr.fd = fd; dtls->dtls_tptr.fd = fd;
if (WSCONFIG(ws)->try_mtu == 0) if (WSCONFIG(ws)->try_mtu == 0)
set_mtu_disc(fd, ws->proto, 0); set_mtu_disc(fd, ws->proto, 0);
@ -170,8 +174,8 @@ int handle_commands_from_main(struct worker_st *ws)
udp_fd_fail: udp_fd_fail:
if (tmsg) if (tmsg)
udp_fd_msg__free_unpacked(tmsg, NULL); udp_fd_msg__free_unpacked(tmsg, NULL);
if (ws->dtls_tptr.fd == -1) if (dtls && dtls->dtls_tptr.fd == -1)
ws->udp_state = UP_DISABLED; dtls->udp_state = UP_DISABLED;
return -1; return -1;
} }

View File

@ -169,6 +169,7 @@ int disable_system_calls(struct worker_st *ws)
ADD_SYSCALL(getsockopt, 0); ADD_SYSCALL(getsockopt, 0);
ADD_SYSCALL(setsockopt, 0); ADD_SYSCALL(setsockopt, 0);
#ifdef ANYCONNECT_CLIENT_COMPAT #ifdef ANYCONNECT_CLIENT_COMPAT
/* we need to open files when we have an xml_config_file setup on any vhost */ /* we need to open files when we have an xml_config_file setup on any vhost */
list_for_each(ws->vconfig, vhost, list) { list_for_each(ws->vconfig, vhost, list) {
@ -185,6 +186,13 @@ int disable_system_calls(struct worker_st *ws)
* the TUN device */ * the TUN device */
ADD_SYSCALL(ioctl, 1, SCMP_A1(SCMP_CMP_EQ, (int)SIOCGIFMTU)); ADD_SYSCALL(ioctl, 1, SCMP_A1(SCMP_CMP_EQ, (int)SIOCGIFMTU));
// Add calls to support libev
ADD_SYSCALL(epoll_wait, 0);
ADD_SYSCALL(epoll_create1, 0);
ADD_SYSCALL(epoll_ctl, 0);
ADD_SYSCALL(rt_sigaction, 0);
ADD_SYSCALL(eventfd2, 0);
ret = seccomp_load(ctx); ret = seccomp_load(ctx);
if (ret < 0) { if (ret < 0) {
oclog(ws, LOG_DEBUG, "could not load seccomp filter"); oclog(ws, LOG_DEBUG, "could not load seccomp filter");

View File

@ -48,6 +48,7 @@
#include <signal.h> #include <signal.h>
#include <poll.h> #include <poll.h>
#include <math.h> #include <math.h>
#include <ev.h>
#if defined(__linux__) && !defined(IPV6_PATHMTU) #if defined(__linux__) && !defined(IPV6_PATHMTU)
# define IPV6_PATHMTU 61 # define IPV6_PATHMTU 61
@ -87,11 +88,26 @@
#define MSS_ADJUST(x) x += TCP_HEADER_SIZE + ((ws->proto == AF_INET)?(IP_HEADER_SIZE):(IPV6_HEADER_SIZE)) #define MSS_ADJUST(x) x += TCP_HEADER_SIZE + ((ws->proto == AF_INET)?(IP_HEADER_SIZE):(IPV6_HEADER_SIZE))
#define WORKER_MAINTENANCE_TIME (10.)
struct worker_st *global_ws = NULL; struct worker_st *global_ws = NULL;
static int terminate = 0; static int terminate = 0;
static int terminate_reason = REASON_SERVER_DISCONNECT; static int terminate_reason = REASON_SERVER_DISCONNECT;
static struct ev_loop *loop = NULL;
ev_io command_watcher;
ev_io tls_watcher;
ev_io tun_watcher;
ev_timer period_check_watcher;
ev_signal term_sig_watcher;
ev_signal int_sig_watcher;
ev_signal alarm_sig_watcher;
static void term_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents);
static int worker_event_loop(struct worker_st * ws);
static int parse_cstp_data(struct worker_st *ws, uint8_t * buf, size_t buf_size, static int parse_cstp_data(struct worker_st *ws, uint8_t * buf, size_t buf_size,
time_t); time_t);
static int parse_dtls_data(struct worker_st *ws, uint8_t * buf, size_t buf_size, static int parse_dtls_data(struct worker_st *ws, uint8_t * buf, size_t buf_size,
@ -101,10 +117,12 @@ static void session_info_send(worker_st * ws);
static void set_net_priority(worker_st * ws, int fd, int priority); static void set_net_priority(worker_st * ws, int fd, int priority);
static void set_socket_timeout(worker_st * ws, int fd); static void set_socket_timeout(worker_st * ws, int fd);
static void link_mtu_set(worker_st * ws, unsigned mtu); static void link_mtu_set(worker_st * ws, struct dtls_st * dtls, unsigned mtu);
static int test_for_tcp_health_probe(struct worker_st *ws); static int test_for_tcp_health_probe(struct worker_st *ws);
static void dtls_watcher_cb (EV_P_ ev_io * w, int revents);
static void handle_alarm(int signo) static void handle_alarm(int signo)
{ {
if (global_ws) if (global_ws)
@ -115,9 +133,9 @@ static void handle_alarm(int signo)
static void handle_term(int signo) static void handle_term(int signo)
{ {
terminate = 1; terminate = 1;
terminate_reason = REASON_SERVER_DISCONNECT; terminate_reason = REASON_SERVER_DISCONNECT;
alarm(2); /* force exit by SIGALRM */ alarm(2); /* force exit by SIGALRM */
} }
/* we override that function to force gnutls use poll() /* we override that function to force gnutls use poll()
@ -334,7 +352,7 @@ static int setup_legacy_dtls_keys(gnutls_session_t session, struct worker_st *ws
return 0; return 0;
} }
static int setup_dtls_connection(struct worker_st *ws) static int setup_dtls_connection(struct worker_st *ws, struct dtls_st * dtls)
{ {
int ret; int ret;
gnutls_session_t session; gnutls_session_t session;
@ -377,17 +395,17 @@ static int setup_dtls_connection(struct worker_st *ws)
gnutls_transport_set_pull_function(session, dtls_pull); gnutls_transport_set_pull_function(session, dtls_pull);
#endif #endif
gnutls_transport_set_pull_timeout_function(session, dtls_pull_timeout); gnutls_transport_set_pull_timeout_function(session, dtls_pull_timeout);
gnutls_transport_set_ptr(session, &ws->dtls_tptr); gnutls_transport_set_ptr(session, &dtls->dtls_tptr);
/* we decrease the default retransmission timeout to bring /* we decrease the default retransmission timeout to bring
* our DTLS support in par with the DTLS1.3 recommendations. * our DTLS support in par with the DTLS1.3 recommendations.
*/ */
gnutls_dtls_set_timeouts(session, 400, 60*1000); gnutls_dtls_set_timeouts(session, 400, 60*1000);
ws->udp_state = UP_HANDSHAKE; dtls->udp_state = UP_HANDSHAKE;
#if defined(CAPTURE_LATENCY_SUPPORT) #if defined(CAPTURE_LATENCY_SUPPORT)
ret = setsockopt(ws->dtls_tptr.fd, SOL_SOCKET, SO_TIMESTAMPING, &ts_socket_opt, sizeof(ts_socket_opt)); ret = setsockopt(dtls->dtls_tptr.fd, SOL_SOCKET, SO_TIMESTAMPING, &ts_socket_opt, sizeof(ts_socket_opt));
if (ret == -1) if (ret == -1)
oclog(ws, LOG_DEBUG, "setsockopt(UDP, SO_TIMESTAMPING), failed."); oclog(ws, LOG_DEBUG, "setsockopt(UDP, SO_TIMESTAMPING), failed.");
#endif #endif
@ -395,24 +413,28 @@ static int setup_dtls_connection(struct worker_st *ws)
/* Setup the fd settings */ /* Setup the fd settings */
if (WSCONFIG(ws)->output_buffer > 0) { if (WSCONFIG(ws)->output_buffer > 0) {
int t = MIN(2048, ws->link_mtu * WSCONFIG(ws)->output_buffer); int t = MIN(2048, ws->link_mtu * WSCONFIG(ws)->output_buffer);
ret = setsockopt(ws->dtls_tptr.fd, SOL_SOCKET, SO_SNDBUF, &t, ret = setsockopt(dtls->dtls_tptr.fd, SOL_SOCKET, SO_SNDBUF, &t,
sizeof(t)); sizeof(t));
if (ret == -1) if (ret == -1)
oclog(ws, LOG_DEBUG, oclog(ws, LOG_DEBUG,
"setsockopt(UDP, SO_SNDBUF) to %u, failed.", "setsockopt(UDP, SO_SNDBUF) to %u, failed.",
t); t);
} }
set_net_priority(ws, ws->dtls_tptr.fd, ws->user_config->net_priority); set_net_priority(ws, dtls->dtls_tptr.fd, ws->user_config->net_priority);
set_socket_timeout(ws, ws->dtls_tptr.fd); set_socket_timeout(ws, dtls->dtls_tptr.fd);
/* reset MTU */ /* reset MTU */
link_mtu_set(ws, ws->adv_link_mtu); link_mtu_set(ws, dtls, ws->adv_link_mtu);
if (ws->dtls_session != NULL) { if (dtls->dtls_session != NULL) {
gnutls_deinit(ws->dtls_session); gnutls_deinit(dtls->dtls_session);
} }
ws->dtls_session = session; dtls->dtls_session = session;
ev_init(&dtls->io, dtls_watcher_cb);
ev_io_set(&dtls->io, dtls->dtls_tptr.fd, EV_READ);
ev_io_start(loop, &dtls->io);
ev_invoke(loop, &dtls->io, EV_READ);
return 0; return 0;
fail: fail:
@ -980,9 +1002,9 @@ void session_info_send(worker_st * ws)
msg.cstp_compr = (char*)ws->cstp_selected_comp->name; msg.cstp_compr = (char*)ws->cstp_selected_comp->name;
} }
if (ws->udp_state != UP_DISABLED && ws->dtls_session) { if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED && DTLS_ACTIVE(ws)->dtls_session) {
msg.dtls_ciphersuite = msg.dtls_ciphersuite =
gnutls_session_get_desc(ws->dtls_session); gnutls_session_get_desc(DTLS_ACTIVE(ws)->dtls_session);
if (ws->dtls_selected_comp) if (ws->dtls_selected_comp)
msg.dtls_compr = (char*)ws->dtls_selected_comp->name; msg.dtls_compr = (char*)ws->dtls_selected_comp->name;
} }
@ -1010,7 +1032,7 @@ void session_info_send(worker_st * ws)
* @mtu: the link MTU * @mtu: the link MTU
*/ */
static static
void link_mtu_set(worker_st * ws, unsigned mtu) void link_mtu_set(struct worker_st * ws, struct dtls_st * dtls, unsigned mtu)
{ {
if (ws->link_mtu == mtu || mtu > sizeof(ws->buffer)) if (ws->link_mtu == mtu || mtu > sizeof(ws->buffer))
return; return;
@ -1018,8 +1040,8 @@ void link_mtu_set(worker_st * ws, unsigned mtu)
ws->link_mtu = mtu; ws->link_mtu = mtu;
oclog(ws, LOG_DEBUG, "setting connection link MTU to %u", mtu); oclog(ws, LOG_DEBUG, "setting connection link MTU to %u", mtu);
if (ws->dtls_session) if (dtls->dtls_session)
gnutls_dtls_set_mtu(ws->dtls_session, gnutls_dtls_set_mtu(dtls->dtls_session,
ws->link_mtu - ws->dtls_proto_overhead); ws->link_mtu - ws->dtls_proto_overhead);
data_mtu_send(ws, DATA_MTU(ws, ws->link_mtu)); data_mtu_send(ws, DATA_MTU(ws, ws->link_mtu));
@ -1031,25 +1053,25 @@ void link_mtu_set(worker_st * ws, unsigned mtu)
* @mtu: the "plaintext" data MTU (not including the DTLS protocol byte) * @mtu: the "plaintext" data MTU (not including the DTLS protocol byte)
*/ */
static static
void data_mtu_set(worker_st * ws, unsigned mtu) void data_mtu_set(worker_st * ws, struct dtls_st * dtls, unsigned mtu)
{ {
if (ws->dtls_session) { if (dtls->dtls_session) {
gnutls_dtls_set_data_mtu(ws->dtls_session, mtu+1); gnutls_dtls_set_data_mtu(dtls->dtls_session, mtu+1);
mtu = gnutls_dtls_get_mtu(ws->dtls_session); mtu = gnutls_dtls_get_mtu(dtls->dtls_session);
if (mtu <= 0 || mtu == ws->link_mtu) if (mtu <= 0 || mtu == ws->link_mtu)
return; return;
mtu += ws->dtls_proto_overhead; mtu += ws->dtls_proto_overhead;
link_mtu_set(ws, mtu); link_mtu_set(ws, dtls, mtu);
} }
} }
static void disable_mtu_disc(worker_st *ws) static void disable_mtu_disc(worker_st *ws, struct dtls_st * dtls)
{ {
oclog(ws, LOG_DEBUG, "disabling MTU discovery on UDP socket"); oclog(ws, LOG_DEBUG, "disabling MTU discovery on UDP socket");
set_mtu_disc(ws->dtls_tptr.fd, ws->proto, 0); set_mtu_disc(dtls->dtls_tptr.fd, ws->proto, 0);
link_mtu_set(ws, ws->adv_link_mtu); link_mtu_set(ws, dtls, ws->adv_link_mtu);
WSCONFIG(ws)->try_mtu = 0; WSCONFIG(ws)->try_mtu = 0;
} }
@ -1059,9 +1081,9 @@ static void disable_mtu_disc(worker_st *ws)
* Returns -1 on failure. * Returns -1 on failure.
*/ */
static static
int mtu_not_ok(worker_st * ws) int mtu_not_ok(worker_st * ws, struct dtls_st * dtls)
{ {
if (WSCONFIG(ws)->try_mtu == 0 || ws->dtls_session == NULL) if (WSCONFIG(ws)->try_mtu == 0 || dtls->dtls_session == NULL)
return 0; return 0;
if (ws->proto == AF_INET) { if (ws->proto == AF_INET) {
@ -1072,8 +1094,8 @@ int mtu_not_ok(worker_st * ws)
if (ws->last_good_mtu == min) { if (ws->last_good_mtu == min) {
oclog(ws, LOG_INFO, oclog(ws, LOG_INFO,
"could not calculate a sufficient MTU; disabling MTU discovery"); "could not calculate a sufficient MTU; disabling MTU discovery");
disable_mtu_disc(ws); disable_mtu_disc(ws, dtls);
link_mtu_set(ws, min); link_mtu_set(ws, dtls, min);
return 0; return 0;
} }
@ -1081,7 +1103,7 @@ int mtu_not_ok(worker_st * ws)
ws->last_good_mtu = MAX(((2 * (ws->link_mtu)) / 3), min); ws->last_good_mtu = MAX(((2 * (ws->link_mtu)) / 3), min);
} }
link_mtu_set(ws, ws->last_good_mtu); link_mtu_set(ws, dtls, ws->last_good_mtu);
oclog(ws, LOG_INFO, "MTU %u is too large, switching to %u", oclog(ws, LOG_INFO, "MTU %u is too large, switching to %u",
ws->last_bad_mtu, ws->link_mtu); ws->last_bad_mtu, ws->link_mtu);
} else if (ws->proto == AF_INET6) { /* IPv6 */ } else if (ws->proto == AF_INET6) { /* IPv6 */
@ -1089,16 +1111,16 @@ int mtu_not_ok(worker_st * ws)
struct ip6_mtuinfo mtuinfo; struct ip6_mtuinfo mtuinfo;
socklen_t len = sizeof(mtuinfo); socklen_t len = sizeof(mtuinfo);
if (getsockopt(ws->dtls_tptr.fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) < 0 || mtuinfo.ip6m_mtu < 1280) { if (getsockopt(dtls->dtls_tptr.fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) < 0 || mtuinfo.ip6m_mtu < 1280) {
oclog(ws, LOG_INFO, "cannot obtain IPv6 MTU (was %u); disabling MTU discovery", oclog(ws, LOG_INFO, "cannot obtain IPv6 MTU (was %u); disabling MTU discovery",
ws->link_mtu); ws->link_mtu);
disable_mtu_disc(ws); disable_mtu_disc(ws, dtls);
link_mtu_set(ws, MIN_MTU(ws)); link_mtu_set(ws, dtls, MIN_MTU(ws));
return 0; return 0;
} }
oclog(ws, LOG_DEBUG, "setting (via IPV6_PATHMTU) connection MTU to %u", mtuinfo.ip6m_mtu); oclog(ws, LOG_DEBUG, "setting (via IPV6_PATHMTU) connection MTU to %u", mtuinfo.ip6m_mtu);
link_mtu_set(ws, mtuinfo.ip6m_mtu); link_mtu_set(ws, dtls, mtuinfo.ip6m_mtu);
if (mtuinfo.ip6m_mtu > ws->adv_link_mtu) { if (mtuinfo.ip6m_mtu > ws->adv_link_mtu) {
oclog(ws, LOG_INFO, "the discovered IPv6 MTU (%u) is larger than the advertised (%u); disabling MTU discovery", oclog(ws, LOG_INFO, "the discovered IPv6 MTU (%u) is larger than the advertised (%u); disabling MTU discovery",
@ -1106,7 +1128,7 @@ int mtu_not_ok(worker_st * ws)
return 0; return 0;
} }
#else #else
link_mtu_set(ws, MIN_MTU(ws)); link_mtu_set(ws, dtls, MIN_MTU(ws));
#endif #endif
} }
@ -1118,13 +1140,13 @@ int mtu_not_ok(worker_st * ws)
* @ws: a worker structure * @ws: a worker structure
* @mtu: the current "plaintext" data MTU * @mtu: the current "plaintext" data MTU
*/ */
static void mtu_discovery_init(worker_st * ws, unsigned mtu) static void mtu_discovery_init(worker_st * ws, struct dtls_st * dtls, unsigned mtu)
{ {
const unsigned min = MIN_MTU(ws); const unsigned min = MIN_MTU(ws);
if (mtu <= min) { if (mtu <= min) {
oclog(ws, LOG_INFO, oclog(ws, LOG_INFO,
"our initial MTU is too low; disabling MTU discovery"); "our initial MTU is too low; disabling MTU discovery");
disable_mtu_disc(ws); disable_mtu_disc(ws, dtls);
} }
if (!WSCONFIG(ws)->try_mtu) if (!WSCONFIG(ws)->try_mtu)
@ -1136,7 +1158,7 @@ static void mtu_discovery_init(worker_st * ws, unsigned mtu)
} }
static static
void mtu_ok(worker_st * ws) void mtu_ok(worker_st * ws, struct dtls_st * dtls)
{ {
unsigned int c; unsigned int c;
@ -1150,7 +1172,7 @@ void mtu_ok(worker_st * ws)
ws->last_good_mtu = ws->link_mtu; ws->last_good_mtu = ws->link_mtu;
c = (ws->link_mtu + ws->last_bad_mtu) / 2; c = (ws->link_mtu + ws->last_bad_mtu) / 2;
link_mtu_set(ws, c); link_mtu_set(ws, dtls, c);
return; return;
} }
@ -1252,7 +1274,7 @@ int periodic_check(worker_st * ws, struct timespec *tnow, unsigned dpd)
#endif #endif
/* check DPD. Otherwise exit */ /* check DPD. Otherwise exit */
if (ws->udp_state == UP_ACTIVE && if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE &&
now - ws->last_msg_udp > DPD_TRIES * dpd && dpd > 0) { now - ws->last_msg_udp > DPD_TRIES * dpd && dpd > 0) {
unsigned data_mtu = DATA_MTU(ws, ws->link_mtu); unsigned data_mtu = DATA_MTU(ws, ws->link_mtu);
oclog(ws, LOG_ERR, oclog(ws, LOG_ERR,
@ -1262,13 +1284,13 @@ int periodic_check(worker_st * ws, struct timespec *tnow, unsigned dpd)
memset(ws->buffer+1, 0, data_mtu); memset(ws->buffer+1, 0, data_mtu);
ws->buffer[0] = AC_PKT_DPD_OUT; ws->buffer[0] = AC_PKT_DPD_OUT;
ret = dtls_send(ws, ws->buffer, data_mtu+1); ret = dtls_send(DTLS_ACTIVE(ws), ws->buffer, data_mtu+1);
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
if (now - ws->last_msg_udp > DPD_MAX_TRIES * dpd) { if (now - ws->last_msg_udp > DPD_MAX_TRIES * dpd) {
oclog(ws, LOG_ERR, oclog(ws, LOG_ERR,
"have not received UDP message or DPD for very long; disabling UDP port"); "have not received UDP message or DPD for very long; disabling UDP port");
ws->udp_state = UP_INACTIVE; DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
} }
} }
if (dpd > 0 && now - ws->last_msg_tcp > DPD_TRIES * dpd) { if (dpd > 0 && now - ws->last_msg_tcp > DPD_TRIES * dpd) {
@ -1294,12 +1316,12 @@ int periodic_check(worker_st * ws, struct timespec *tnow, unsigned dpd)
} }
} }
if (ws->conn_type != SOCK_TYPE_UNIX && ws->udp_state != UP_DISABLED) { if (ws->conn_type != SOCK_TYPE_UNIX && DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) {
max = get_pmtu_approx(ws); max = get_pmtu_approx(ws);
if (max > 0 && max < ws->link_mtu) { if (max > 0 && max < ws->link_mtu) {
oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u", oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u",
max); max);
link_mtu_set(ws, max); link_mtu_set(ws, DTLS_ACTIVE(ws), max);
} }
} }
@ -1359,16 +1381,16 @@ static void set_net_priority(worker_st * ws, int fd, int priority)
#define SEND_ERR(x) if (x<0) goto send_error #define SEND_ERR(x) if (x<0) goto send_error
static int dtls_mainloop(worker_st * ws, struct timespec *tnow) static int dtls_mainloop(worker_st * ws, struct dtls_st * dtls, struct timespec *tnow)
{ {
int ret; int ret;
gnutls_datum_t data; gnutls_datum_t data;
void *packet = NULL; void *packet = NULL;
switch (ws->udp_state) { switch (dtls->udp_state) {
case UP_ACTIVE: case UP_ACTIVE:
case UP_INACTIVE: case UP_INACTIVE:
ret = dtls_recv_packet(ws, &data, &packet); ret = dtls_recv_packet(dtls, &data, &packet);
oclog(ws, LOG_TRANSFER_DEBUG, oclog(ws, LOG_TRANSFER_DEBUG,
"received %d byte(s) (DTLS)", ret); "received %d byte(s) (DTLS)", ret);
@ -1376,8 +1398,8 @@ static int dtls_mainloop(worker_st * ws, struct timespec *tnow)
if (ret == GNUTLS_E_REHANDSHAKE) { if (ret == GNUTLS_E_REHANDSHAKE) {
if (ws->last_dtls_rehandshake > 0 && if (dtls->last_dtls_rehandshake > 0 &&
tnow->tv_sec - ws->last_dtls_rehandshake < tnow->tv_sec - dtls->last_dtls_rehandshake <
WSCONFIG(ws)->rekey_time / 2) { WSCONFIG(ws)->rekey_time / 2) {
oclog(ws, LOG_INFO, oclog(ws, LOG_INFO,
"client requested DTLS rehandshake too soon"); "client requested DTLS rehandshake too soon");
@ -1392,18 +1414,18 @@ static int dtls_mainloop(worker_st * ws, struct timespec *tnow)
"client requested rehandshake on DTLS channel"); "client requested rehandshake on DTLS channel");
do { do {
ret = gnutls_handshake(ws->dtls_session); ret = gnutls_handshake(dtls->dtls_session);
} while (ret == GNUTLS_E_AGAIN } while (ret == GNUTLS_E_AGAIN
|| ret == GNUTLS_E_INTERRUPTED); || ret == GNUTLS_E_INTERRUPTED);
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
oclog(ws, LOG_DEBUG, "DTLS rehandshake completed"); oclog(ws, LOG_DEBUG, "DTLS rehandshake completed");
ws->last_dtls_rehandshake = tnow->tv_sec; dtls->last_dtls_rehandshake = tnow->tv_sec;
} else if (ret >= 1) { } else if (ret >= 1) {
/* where we receive any DTLS UDP packet we reset the state /* where we receive any DTLS UDP packet we reset the state
* to active */ * to active */
ws->udp_state = UP_ACTIVE; dtls->udp_state = UP_ACTIVE;
if (bandwidth_update if (bandwidth_update
(&ws->b_rx, data.size - CSTP_DTLS_OVERHEAD, tnow) != 0) { (&ws->b_rx, data.size - CSTP_DTLS_OVERHEAD, tnow) != 0) {
@ -1423,50 +1445,53 @@ static int dtls_mainloop(worker_st * ws, struct timespec *tnow)
ws->udp_recv_time = tnow->tv_sec; ws->udp_recv_time = tnow->tv_sec;
break; break;
case UP_SETUP: case UP_SETUP:
ret = setup_dtls_connection(ws); ret = setup_dtls_connection(ws, dtls);
if (ret < 0) { if (ret < 0) {
ret = -1; ret = -1;
goto cleanup; goto cleanup;
} }
gnutls_dtls_set_mtu(ws->dtls_session, ws->link_mtu - ws->dtls_proto_overhead); gnutls_dtls_set_mtu(dtls->dtls_session, ws->link_mtu - ws->dtls_proto_overhead);
mtu_discovery_init(ws, ws->link_mtu); mtu_discovery_init(ws, dtls, ws->link_mtu);
break; break;
case UP_HANDSHAKE: case UP_HANDSHAKE:
hsk_restart: hsk_restart:
ret = gnutls_handshake(ws->dtls_session); ret = gnutls_handshake(dtls->dtls_session);
if (ret < 0 && gnutls_error_is_fatal(ret) != 0) { if (ret < 0 && gnutls_error_is_fatal(ret) != 0) {
if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED) if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED)
oclog(ws, LOG_ERR, oclog(ws, LOG_ERR,
"error in DTLS handshake: %s: %s\n", "error in DTLS handshake: %s: %s\n",
gnutls_strerror(ret), gnutls_strerror(ret),
gnutls_alert_get_name gnutls_alert_get_name
(gnutls_alert_get(ws->dtls_session))); (gnutls_alert_get(dtls->dtls_session)));
else else
oclog(ws, LOG_ERR, oclog(ws, LOG_ERR,
"error in DTLS handshake: %s\n", "error in DTLS handshake: %s\n",
gnutls_strerror(ret)); gnutls_strerror(ret));
ws->udp_state = UP_DISABLED; dtls->udp_state = UP_DISABLED;
break; break;
} }
if (ret == GNUTLS_E_LARGE_PACKET) { if (ret == GNUTLS_E_LARGE_PACKET) {
/* adjust mtu */ /* adjust mtu */
mtu_not_ok(ws); mtu_not_ok(ws, dtls);
goto hsk_restart; goto hsk_restart;
} else if (ret == 0) { } else if (ret == 0) {
unsigned data_mtu; unsigned data_mtu;
/* gnutls_dtls_get_data_mtu() already subtracts the crypto overhead */ /* gnutls_dtls_get_data_mtu() already subtracts the crypto overhead */
data_mtu = data_mtu =
gnutls_dtls_get_data_mtu(ws->dtls_session) - gnutls_dtls_get_data_mtu(dtls->dtls_session) -
CSTP_DTLS_OVERHEAD; CSTP_DTLS_OVERHEAD;
ws->udp_state = UP_ACTIVE; dtls->udp_state = UP_ACTIVE;
oclog(ws, LOG_DEBUG, oclog(ws, LOG_DEBUG,
"DTLS handshake completed (link MTU: %u, data MTU: %u)\n", "DTLS handshake completed (link MTU: %u, data MTU: %u)\n",
ws->link_mtu, data_mtu); ws->link_mtu, data_mtu);
ws->dtls_active_session++;
oclog(ws, LOG_DEBUG,
"Maing DTLS session %d active", ws->dtls_active_session);
session_info_send(ws); session_info_send(ws);
} }
@ -1510,11 +1535,11 @@ static int tls_mainloop(struct worker_st *ws, struct timespec *tnow)
goto cleanup; goto cleanup;
} }
if ((ret == AC_PKT_DATA || ret == AC_PKT_COMPRESSED) && ws->udp_state == UP_ACTIVE) { if ((ret == AC_PKT_DATA || ret == AC_PKT_COMPRESSED) && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) {
/* client switched to TLS for some reason */ /* client switched to TLS for some reason */
if (tnow->tv_sec - ws->udp_recv_time > if (tnow->tv_sec - ws->udp_recv_time >
UDP_SWITCH_TIME) UDP_SWITCH_TIME)
ws->udp_state = UP_INACTIVE; DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
} }
} }
@ -1582,15 +1607,15 @@ static int tun_mainloop(struct worker_st *ws, struct timespec *tnow)
cstp_to_send.size = l; cstp_to_send.size = l;
if (WSCONFIG(ws)->switch_to_tcp_timeout && if (WSCONFIG(ws)->switch_to_tcp_timeout &&
ws->udp_state == UP_ACTIVE && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE &&
tnow->tv_sec > ws->udp_recv_time + WSCONFIG(ws)->switch_to_tcp_timeout) { tnow->tv_sec > ws->udp_recv_time + WSCONFIG(ws)->switch_to_tcp_timeout) {
oclog(ws, LOG_DEBUG, "No UDP data received for %li seconds, using TCP instead\n", oclog(ws, LOG_DEBUG, "No UDP data received for %li seconds, using TCP instead\n",
tnow->tv_sec - ws->udp_recv_time); tnow->tv_sec - ws->udp_recv_time);
ws->udp_state = UP_INACTIVE; DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
} }
#ifdef ENABLE_COMPRESSION #ifdef ENABLE_COMPRESSION
if (ws->udp_state == UP_ACTIVE && ws->dtls_selected_comp != NULL && l > WSCONFIG(ws)->no_compress_limit) { if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && ws->dtls_selected_comp != NULL && l > WSCONFIG(ws)->no_compress_limit) {
/* otherwise don't compress */ /* otherwise don't compress */
ret = ws->dtls_selected_comp->compress(ws->decomp+8, sizeof(ws->decomp)-8, ws->buffer+8, l); ret = ws->dtls_selected_comp->compress(ws->decomp+8, sizeof(ws->decomp)-8, ws->buffer+8, l);
oclog(ws, LOG_TRANSFER_DEBUG, "compressed %d to %d\n", (int)l, ret); oclog(ws, LOG_TRANSFER_DEBUG, "compressed %d to %d\n", (int)l, ret);
@ -1626,27 +1651,27 @@ static int tun_mainloop(struct worker_st *ws, struct timespec *tnow)
oclog(ws, LOG_TRANSFER_DEBUG, "sending %d byte(s)\n", l); oclog(ws, LOG_TRANSFER_DEBUG, "sending %d byte(s)\n", l);
if (ws->udp_state == UP_ACTIVE) { if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) {
ws->tun_bytes_out += dtls_to_send.size; ws->tun_bytes_out += dtls_to_send.size;
dtls_to_send.data[7] = dtls_type; dtls_to_send.data[7] = dtls_type;
ret = dtls_send(ws, dtls_to_send.data + 7, dtls_to_send.size + 1); ret = dtls_send(DTLS_ACTIVE(ws), dtls_to_send.data + 7, dtls_to_send.size + 1);
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
if (ret == GNUTLS_E_LARGE_PACKET) { if (ret == GNUTLS_E_LARGE_PACKET) {
mtu_not_ok(ws); mtu_not_ok(ws, DTLS_ACTIVE(ws));
oclog(ws, LOG_TRANSFER_DEBUG, oclog(ws, LOG_TRANSFER_DEBUG,
"retrying (TLS) %d\n", l); "retrying (TLS) %d\n", l);
tls_retry = 1; tls_retry = 1;
} else if (ret >= 1+DATA_MTU(ws, ws->link_mtu) && } else if (ret >= 1+DATA_MTU(ws, ws->link_mtu) &&
WSCONFIG(ws)->try_mtu != 0) { WSCONFIG(ws)->try_mtu != 0) {
mtu_ok(ws); mtu_ok(ws, DTLS_ACTIVE(ws));
} }
} }
if (ws->udp_state != UP_ACTIVE || tls_retry != 0) { if (DTLS_ACTIVE(ws)->udp_state != UP_ACTIVE || tls_retry != 0) {
cstp_to_send.data[0] = 'S'; cstp_to_send.data[0] = 'S';
cstp_to_send.data[1] = 'T'; cstp_to_send.data[1] = 'T';
cstp_to_send.data[2] = 'F'; cstp_to_send.data[2] = 'F';
@ -1787,7 +1812,7 @@ static void calc_mtu_values(worker_st * ws)
/* link MTU is the device MTU */ /* link MTU is the device MTU */
ws->link_mtu = ws->vinfo.mtu; ws->link_mtu = ws->vinfo.mtu;
if (ws->udp_state != UP_DISABLED) { if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) {
/* crypto overhead for DTLS */ /* crypto overhead for DTLS */
if (ws->req.use_psk) { if (ws->req.use_psk) {
if (ws->session == NULL) { if (ws->session == NULL) {
@ -1832,22 +1857,11 @@ static void calc_mtu_values(worker_st * ws)
static int connect_handler(worker_st * ws) static int connect_handler(worker_st * ws)
{ {
struct http_req_st *req = &ws->req; struct http_req_st *req = &ws->req;
struct pollfd pfd[4];
unsigned pfd_size;
int max, ret, t; int max, ret, t;
char *p; char *p;
unsigned rnd; unsigned rnd;
#ifdef HAVE_PPOLL unsigned i;
struct timespec tv;
#endif
unsigned tls_pending, dtls_pending = 0, i;
struct timespec tnow;
unsigned ip6; unsigned ip6;
sigset_t emptyset, blockset;
sigemptyset(&blockset);
sigemptyset(&emptyset);
sigaddset(&blockset, SIGTERM);
gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(rnd)); gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(rnd));
@ -1930,10 +1944,14 @@ static int connect_handler(worker_st * ws)
SEND_ERR(ret); SEND_ERR(ret);
} }
ws->udp_state = UP_DISABLED; ws->dtls_active_session = 0;
DTLS_ACTIVE(ws)->udp_state = UP_DISABLED;
DTLS_INACTIVE(ws)->udp_state = UP_DISABLED;
if (WSPCONFIG(ws)->udp_port != 0 && req->master_secret_set != 0) { if (WSPCONFIG(ws)->udp_port != 0 && req->master_secret_set != 0) {
memcpy(ws->master_secret, req->master_secret, TLS_MASTER_SIZE); memcpy(ws->master_secret, req->master_secret, TLS_MASTER_SIZE);
ws->udp_state = UP_WAIT_FD; DTLS_ACTIVE(ws)->udp_state = UP_WAIT_FD;
DTLS_INACTIVE(ws)->udp_state = UP_WAIT_FD;
} else { } else {
oclog(ws, LOG_DEBUG, "disabling UDP (DTLS) connection"); oclog(ws, LOG_DEBUG, "disabling UDP (DTLS) connection");
} }
@ -1959,7 +1977,7 @@ static int connect_handler(worker_st * ws)
if (max > 0 && max < ws->vinfo.mtu) { if (max > 0 && max < ws->vinfo.mtu) {
oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u", oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u",
max); max);
link_mtu_set(ws, max); link_mtu_set(ws, DTLS_ACTIVE(ws), max);
} }
} }
@ -2216,7 +2234,7 @@ static int connect_handler(worker_st * ws)
set_net_priority(ws, ws->conn_fd, ws->user_config->net_priority); set_net_priority(ws, ws->conn_fd, ws->user_config->net_priority);
set_no_delay(ws, ws->conn_fd); set_no_delay(ws, ws->conn_fd);
if (ws->udp_state != UP_DISABLED) { if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) {
if (ws->user_config->dpd > 0) { if (ws->user_config->dpd > 0) {
ret = ret =
@ -2342,150 +2360,10 @@ static int connect_handler(worker_st * ws)
ret = cstp_uncork(ws); ret = cstp_uncork(ws);
SEND_ERR(ret); SEND_ERR(ret);
/* start dead peer detection */ ret = worker_event_loop(ws);
gettime(&tnow); if (ret != 0)
ws->last_msg_tcp = ws->last_msg_udp = ws->last_nc_msg = tnow.tv_sec; {
goto exit;
bandwidth_init(&ws->b_rx, ws->user_config->rx_per_sec);
bandwidth_init(&ws->b_tx, ws->user_config->tx_per_sec);
sigprocmask(SIG_BLOCK, &blockset, NULL);
/* worker main loop */
for (;;) {
if (terminate != 0) {
terminate:
ws->buffer[0] = 'S';
ws->buffer[1] = 'T';
ws->buffer[2] = 'F';
ws->buffer[3] = 1;
ws->buffer[4] = 0;
ws->buffer[5] = 0;
ws->buffer[6] = AC_PKT_DISCONN;
ws->buffer[7] = 0;
oclog(ws, LOG_TRANSFER_DEBUG,
"sending disconnect message in TLS channel");
cstp_send(ws, ws->buffer, 8);
exit_worker_reason(ws, terminate_reason);
}
if (ws->session != NULL)
tls_pending = gnutls_record_check_pending(ws->session);
else
tls_pending = 0;
if (ws->udp_state > UP_WAIT_FD) {
dtls_pending = dtls_pull_buffer_non_empty(&ws->dtls_tptr);
if (ws->dtls_session != NULL)
dtls_pending +=
gnutls_record_check_pending(ws->dtls_session);
} else {
dtls_pending = 0;
}
pfd[0].revents = 0;
pfd[1].revents = 0;
pfd[2].revents = 0;
pfd[3].revents = 0;
if (tls_pending == 0 && dtls_pending == 0) {
pfd[0].fd = ws->conn_fd;
pfd[0].events = POLLIN;
pfd[1].fd = ws->cmd_fd;
pfd[1].events = POLLIN;
pfd[2].fd = ws->tun_fd;
pfd[2].events = POLLIN;
pfd_size = 3;
if (ws->udp_state > UP_WAIT_FD) {
pfd[3].fd = ws->dtls_tptr.fd;
pfd[3].events = POLLIN;
pfd_size++;
}
#ifdef HAVE_PPOLL
tv.tv_nsec = 0;
tv.tv_sec = 10;
ret = ppoll(pfd, pfd_size, &tv, &emptyset);
#else
sigprocmask(SIG_UNBLOCK, &blockset, NULL);
ret = poll(pfd, pfd_size, 10*1000);
sigprocmask(SIG_BLOCK, &blockset, NULL);
#endif
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
terminate_reason = REASON_ERROR;
goto exit;
}
if ((pfd[0].revents | pfd[1].revents |
pfd[2].revents | pfd[3].revents) & POLLERR) {
terminate_reason = REASON_ERROR;
goto exit;
}
}
gettime(&tnow);
if (periodic_check(ws, &tnow, ws->user_config->dpd) < 0) {
terminate_reason = REASON_ERROR;
goto exit;
}
/* send pending data from tun device */
if (pfd[2].revents & (POLLIN|POLLHUP)) {
ret = tun_mainloop(ws, &tnow);
if (ret < 0) {
terminate_reason = REASON_ERROR;
goto exit;
}
}
/* read pending data from TCP channel */
if ((pfd[0].revents & (POLLIN|POLLHUP)) || tls_pending != 0) {
ret = tls_mainloop(ws, &tnow);
if (ret < 0) {
terminate_reason = REASON_ERROR;
goto exit;
}
}
/* read data from UDP channel */
if (ws->udp_state > UP_WAIT_FD &&
((pfd[3].revents & (POLLIN|POLLHUP)) || dtls_pending != 0)) {
ret = dtls_mainloop(ws, &tnow);
if (ret < 0) {
terminate_reason = REASON_ERROR;
goto exit;
}
#if defined(CAPTURE_LATENCY_SUPPORT)
if (ws->dtls_tptr.rx_time.tv_sec != 0) {
capture_latency_sample(ws, &ws->dtls_tptr.rx_time);
ws->dtls_tptr.rx_time.tv_sec = 0;
ws->dtls_tptr.rx_time.tv_nsec = 0;
}
#endif
}
/* read commands from command fd */
if (pfd[1].revents & (POLLIN|POLLHUP)) {
ret = handle_commands_from_main(ws);
if (ret == ERR_NO_CMD_FD) {
terminate_reason = REASON_ERROR;
goto terminate;
}
if (ret < 0) {
terminate_reason = REASON_ERROR;
goto exit;
}
}
} }
return 0; return 0;
@ -2493,9 +2371,11 @@ static int connect_handler(worker_st * ws)
exit: exit:
cstp_close(ws); cstp_close(ws);
/*gnutls_deinit(ws->session); */ /*gnutls_deinit(ws->session); */
if (ws->udp_state == UP_ACTIVE && ws->dtls_session) { if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_ACTIVE(ws)->dtls_session) {
dtls_close(ws); dtls_close(DTLS_ACTIVE(ws));
/*gnutls_deinit(ws->dtls_session); */ }
if (DTLS_INACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_INACTIVE(ws)->dtls_session) {
dtls_close(DTLS_INACTIVE(ws));
} }
exit_worker_reason(ws, terminate_reason); exit_worker_reason(ws, terminate_reason);
@ -2551,15 +2431,15 @@ static int parse_data(struct worker_st *ws, uint8_t *buf, size_t buf_size,
if (buf_size-CSTP_DTLS_OVERHEAD > DATA_MTU(ws, ws->link_mtu)) { if (buf_size-CSTP_DTLS_OVERHEAD > DATA_MTU(ws, ws->link_mtu)) {
/* peer is doing MTU discovery */ /* peer is doing MTU discovery */
data_mtu_set(ws, buf_size-CSTP_DTLS_OVERHEAD); data_mtu_set(ws, DTLS_ACTIVE(ws), buf_size-CSTP_DTLS_OVERHEAD);
} }
ret = dtls_send(ws, buf, buf_size); ret = dtls_send(DTLS_ACTIVE(ws), buf, buf_size);
if (ret == GNUTLS_E_LARGE_PACKET) { if (ret == GNUTLS_E_LARGE_PACKET) {
oclog(ws, LOG_TRANSFER_DEBUG, oclog(ws, LOG_TRANSFER_DEBUG,
"could not send DPD of %d bytes", (int)buf_size); "could not send DPD of %d bytes", (int)buf_size);
mtu_not_ok(ws); mtu_not_ok(ws, DTLS_ACTIVE(ws));
ret = dtls_send(ws, buf, 1); ret = dtls_send(DTLS_ACTIVE(ws), buf, 1);
} }
oclog(ws, LOG_TRANSFER_DEBUG, oclog(ws, LOG_TRANSFER_DEBUG,
@ -2665,10 +2545,10 @@ static int parse_cstp_data(struct worker_st *ws,
return -1; return -1;
} }
if (buf[6] == AC_PKT_DATA && ws->udp_state == UP_ACTIVE) { if (buf[6] == AC_PKT_DATA && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) {
/* if we received a data packet in the CSTP channel we assume that /* if we received a data packet in the CSTP channel we assume that
* our peer wants to switch to it as the communication channel */ * our peer wants to switch to it as the communication channel */
ws->udp_state = UP_INACTIVE; DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
} }
ret = parse_data(ws, buf, buf_size, now, 0); ret = parse_data(ws, buf, buf_size, now, 0);
@ -2709,3 +2589,228 @@ static int test_for_tcp_health_probe(struct worker_st *ws)
else else
return 1; return 1;
} }
static void syserr_cb (const char *msg)
{
struct worker_st * ws = ev_userdata(loop);
int err = errno;
oclog(ws, LOG_ERR, "libev fatal error: %s / %s", msg, strerror(err));
terminate_reason = REASON_ERROR;
exit_worker_reason(ws, terminate_reason);
}
static void cstp_send_terminate(struct worker_st * ws)
{
ws->buffer[0] = 'S';
ws->buffer[1] = 'T';
ws->buffer[2] = 'F';
ws->buffer[3] = 1;
ws->buffer[4] = 0;
ws->buffer[5] = 0;
ws->buffer[6] = AC_PKT_DISCONN;
ws->buffer[7] = 0;
oclog(ws, LOG_TRANSFER_DEBUG,
"sending disconnect message in TLS channel");
cstp_send(ws, ws->buffer, 8);
exit_worker_reason(ws, terminate_reason);
}
static void command_watcher_cb (EV_P_ ev_io *w, int revents)
{
struct worker_st *ws = ev_userdata(loop);
int ret = handle_commands_from_main(ws);
if (ret == ERR_NO_CMD_FD) {
terminate_reason = REASON_ERROR;
cstp_send_terminate(ws);
}
if (ret < 0) {
terminate_reason = REASON_ERROR;
cstp_send_terminate(ws);
}
if (DTLS_ACTIVE(ws)->udp_state == UP_SETUP) {
ev_invoke(loop, &DTLS_ACTIVE(ws)->io, EV_READ);
}
if (DTLS_INACTIVE(ws)->udp_state == UP_SETUP) {
ev_invoke(loop, &DTLS_INACTIVE(ws)->io, EV_READ);
}
}
static void tls_watcher_cb (EV_P_ ev_io * w, int revents)
{
struct timespec tnow;
struct worker_st *ws = ev_userdata(loop);
int ret;
gettime(&tnow);
ret = tls_mainloop(ws, &tnow);
if (ret < 0) {
oclog(ws, LOG_ERR, "tls_mainloop failed %d", ret);
terminate_reason = REASON_ERROR;
cstp_send_terminate(ws);
}
}
static void tun_watcher_cb (EV_P_ ev_io * w, int revents)
{
struct timespec tnow;
struct worker_st *ws = ev_userdata(loop);
int ret;
gettime(&tnow);
ret = tun_mainloop(ws, &tnow);
if (ret < 0) {
oclog(ws, LOG_ERR, "tun_mainloop failed %d", ret);
terminate_reason = REASON_ERROR;
cstp_send_terminate(ws);
}
}
static void dtls_watcher_cb (EV_P_ ev_io * w, int revents)
{
struct timespec tnow;
struct worker_st *ws = ev_userdata(loop);
struct dtls_st * dtls = (struct dtls_st*)w;
int ret;
gettime(&tnow);
ret = dtls_mainloop(ws, dtls, &tnow);
if (ret < 0) {
oclog(ws, LOG_ERR, "dtls_mainloop failed %d", ret);
terminate_reason = REASON_ERROR;
cstp_send_terminate(ws);
}
#if defined(CAPTURE_LATENCY_SUPPORT)
if (dtls->dtls_tptr.rx_time.tv_sec != 0) {
capture_latency_sample(ws, &dtls->dtls_tptr.rx_time);
dtls->dtls_tptr.rx_time.tv_sec = 0;
dtls->dtls_tptr.rx_time.tv_nsec = 0;
}
#endif
}
static void term_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
struct worker_st *ws = ev_userdata(loop);
cstp_send_terminate(ws);
}
static void invoke_dtls_if_needed(struct dtls_st * dtls)
{
if ((dtls->udp_state > UP_WAIT_FD) &&
(dtls->dtls_session != NULL) &&
(gnutls_record_check_pending(dtls->dtls_session))) {
ev_invoke(loop, &dtls->io, EV_READ);
}
}
static void periodic_check_watcher_cb(EV_P_ ev_timer *w, int revents)
{
struct worker_st *ws = ev_userdata(loop);
struct timespec tnow;
gettime(&tnow);
if (periodic_check(ws, &tnow, ws->user_config->dpd) < 0) {
terminate_reason = REASON_ERROR;
cstp_send_terminate(ws);
return;
}
if (terminate)
cstp_send_terminate(ws);
if (gnutls_record_check_pending(ws->session))
{
ev_invoke(loop, &tls_watcher, EV_READ);
}
invoke_dtls_if_needed(DTLS_ACTIVE(ws));
invoke_dtls_if_needed(DTLS_INACTIVE(ws));
}
static int worker_event_loop(struct worker_st * ws)
{
struct timespec tnow;
#if defined(__linux__) && defined(HAVE_LIBSECCOMP)
loop = ev_default_loop(EVFLAG_NOENV|EVBACKEND_EPOLL);
#else
loop = EV_DEFAULT;
#endif
// Restore the signal handlers
ocsignal(SIGTERM, SIG_DFL);
ocsignal(SIGINT, SIG_DFL);
ocsignal(SIGALRM, SIG_DFL);
ev_init(&alarm_sig_watcher, term_sig_watcher_cb);
ev_signal_set (&alarm_sig_watcher, SIGALRM);
ev_signal_start (loop, &alarm_sig_watcher);
ev_init (&int_sig_watcher, term_sig_watcher_cb);
ev_signal_set (&int_sig_watcher, SIGINT);
ev_signal_start (loop, &int_sig_watcher);
ev_init (&term_sig_watcher, term_sig_watcher_cb);
ev_signal_set (&term_sig_watcher, SIGTERM);
ev_signal_start (loop, &term_sig_watcher);
ev_set_userdata (loop, ws);
ev_set_syserr_cb(syserr_cb);
ev_init(&command_watcher, command_watcher_cb);
ev_io_set(&command_watcher, ws->cmd_fd, EV_READ);
ev_io_start(loop, &command_watcher);
ev_init(&tls_watcher, tls_watcher_cb);
ev_io_set(&tls_watcher, ws->conn_fd, EV_READ);
ev_io_start(loop, &tls_watcher);
ev_init(&DTLS_ACTIVE(ws)->io, dtls_watcher_cb);
ev_init(&DTLS_INACTIVE(ws)->io, dtls_watcher_cb);
ev_init(&tun_watcher, tun_watcher_cb);
ev_io_set(&tun_watcher, ws->tun_fd, EV_READ);
ev_io_start(loop, &tun_watcher);
ev_init (&period_check_watcher, periodic_check_watcher_cb);
ev_timer_set(&period_check_watcher, WORKER_MAINTENANCE_TIME, WORKER_MAINTENANCE_TIME);
ev_timer_start(loop, &period_check_watcher);
/* start dead peer detection */
gettime(&tnow);
ws->last_msg_tcp = ws->last_msg_udp = ws->last_nc_msg = tnow.tv_sec;
bandwidth_init(&ws->b_rx, ws->user_config->rx_per_sec);
bandwidth_init(&ws->b_tx, ws->user_config->tx_per_sec);
ev_run(loop, 0);
if (terminate != 0)
{
goto exit;
}
return 0;
exit:
cstp_close(ws);
/*gnutls_deinit(ws->session); */
if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_ACTIVE(ws)->dtls_session) {
dtls_close(DTLS_ACTIVE(ws));
}
if (DTLS_INACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_INACTIVE(ws)->dtls_session) {
dtls_close(DTLS_INACTIVE(ws));
}
exit_worker_reason(ws, terminate_reason);
return 1;
}

View File

@ -189,7 +189,8 @@ int main(int argc, char **argv)
ws->vconfig = s->vconfig; ws->vconfig = s->vconfig;
ws->tun_fd = -1; ws->tun_fd = -1;
ws->dtls_tptr.fd = -1; DTLS_ACTIVE(ws)->dtls_tptr.fd = -1;
DTLS_INACTIVE(ws)->dtls_tptr.fd = -1;
/* Drop privileges after this point */ /* Drop privileges after this point */
drop_privileges(s); drop_privileges(s);

View File

@ -38,6 +38,7 @@
#include <sys/uio.h> #include <sys/uio.h>
#include <hmac.h> #include <hmac.h>
#include "vhost.h" #include "vhost.h"
#include "ev.h"
typedef enum { typedef enum {
UP_DISABLED, UP_DISABLED,
@ -166,13 +167,20 @@ typedef struct dtls_transport_ptr {
#endif #endif
} dtls_transport_ptr; } dtls_transport_ptr;
typedef struct dtls_st {
ev_io io;
dtls_transport_ptr dtls_tptr;
gnutls_session_t dtls_session;
udp_port_state_t udp_state;
time_t last_dtls_rehandshake;
} dtls_st;
/* Given a base MTU, this macro provides the DTLS plaintext data we can send; /* Given a base MTU, this macro provides the DTLS plaintext data we can send;
* the output value does not include the DTLS header */ * the output value does not include the DTLS header */
#define DATA_MTU(ws,mtu) (mtu-ws->dtls_crypto_overhead-ws->dtls_proto_overhead) #define DATA_MTU(ws,mtu) (mtu-ws->dtls_crypto_overhead-ws->dtls_proto_overhead)
typedef struct worker_st { typedef struct worker_st {
gnutls_session_t session; gnutls_session_t session;
gnutls_session_t dtls_session;
auth_struct_st *selected_auth; auth_struct_st *selected_auth;
const compression_method_st *dtls_selected_comp; const compression_method_st *dtls_selected_comp;
@ -224,13 +232,14 @@ typedef struct worker_st {
time_t last_periodic_check; time_t last_periodic_check;
/* set after authentication */ /* set after authentication */
dtls_transport_ptr dtls_tptr;
udp_port_state_t udp_state;
time_t udp_recv_time; /* time last udp packet was received */ time_t udp_recv_time; /* time last udp packet was received */
uint8_t dtls_active_session : 1;
dtls_st dtls[2];
#define DTLS_ACTIVE(ws) (&ws->dtls[ws->dtls_active_session])
#define DTLS_INACTIVE(ws) (&ws->dtls[ws->dtls_active_session ^ 1])
/* protection from multiple rehandshakes */ /* protection from multiple rehandshakes */
time_t last_tls_rehandshake; time_t last_tls_rehandshake;
time_t last_dtls_rehandshake;
/* the time the last stats message was sent */ /* the time the last stats message was sent */
time_t last_stats_msg; time_t last_stats_msg;

View File

@ -180,6 +180,9 @@ check_PROGRAMS += gen_oidc_test_data
dist_check_SCRIPTS += test-oidc dist_check_SCRIPTS += test-oidc
endif endif
dist_check_SCRIPTS += test-replay
TESTS = $(check_PROGRAMS) $(dist_check_SCRIPTS) $(xfail_scripts) TESTS = $(check_PROGRAMS) $(dist_check_SCRIPTS) $(xfail_scripts)
XFAIL_TESTS = $(xfail_scripts) XFAIL_TESTS = $(xfail_scripts)

View File

@ -173,7 +173,7 @@ fi
echo "Transferred ${OCTETS} bytes" echo "Transferred ${OCTETS} bytes"
echo "Waiting for disconnection report" echo "Waiting for disconnection report"
wait_file_contents ${RADIUSLOG} "Acct-Terminate-Cause" 70 wait_file_contents ${RADIUSLOG} "Acct-Terminate-Cause" 120
DISC=$(cat ${RADIUSLOG}|grep "Acct-Status-Type = Start"|tail -1) DISC=$(cat ${RADIUSLOG}|grep "Acct-Status-Type = Start"|tail -1)
if test -z "$DISC";then if test -z "$DISC";then

89
tests/test-replay Executable file
View File

@ -0,0 +1,89 @@
#!/bin/bash
#
# Copyright (C) 2020 Microsoft Corporation
#
# This file is part of ocserv.
#
# ocserv is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# ocserv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GnuTLS; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
SERV="${SERV:-../src/ocserv}"
srcdir=${srcdir:-.}
. `dirname $0`/common.sh
eval "${GETPORT}"
TCPDUMP=${TCPDUMP:-$(which tcpdump)}
if test -z "${TCPDUMP}" || ! test -x ${TCPDUMP};then
echo "You need tcpdump to run this test"
exit 77
fi
echo "Testing whether replay of DTLS hello breaks client session... "
PIDFILE1="${srcdir}/ci$$-1.pid.tmp"
PIDFILE2="${srcdir}/ci$$-2.pid.tmp"
TCPDUMP_FILE="${srcdir}/dtls_hello.pcap"
rm -f "${PIDFILE1}" "${PIDFILE2}"
function finish {
set +e
echo " * Cleaning up..."
test -n "${PID}" && kill ${PID} >/dev/null 2>&1
test -f "${PIDFILE1}" && kill $(cat ${PIDFILE1}) >/dev/null 2>&1
test -f "${PIDFILE2}" && kill $(cat ${PIDFILE2}) >/dev/null 2>&1
test -n "${CONFIG}" && rm -f ${CONFIG} >/dev/null 2>&1
test -n "${TCPDUMP_FILE}" && rm -f "${TCPDUMP_FILE}" >/dev/null 2>&1
rm -f "${PIDFILE1}" "${PIDFILE2}" 2>&1
}
trap finish EXIT
# Catch the DTLS client hello
$TCPDUMP -c 1 -n -i lo "udp and port $PORT" -w $TCPDUMP_FILE > tcpdump.log 2>&1 &
update_config test-max-same-1.config
launch_server -d 9999 -f -c ${CONFIG} & PID=$!
wait_server $PID
echo "Connecting to obtain cookie... "
eval `echo "test" | $OPENCONNECT -q localhost:$PORT -u test --authenticate --servercert=d66b507ae074d03b02eafca40d35f87dd81049d3`
if [ -z "$COOKIE" ];then
echo "Could not obtain cookie"
exit 1
fi
#echo "Cookie: $COOKIE"
echo "Connecting with cookie... "
echo "test" | $OPENCONNECT -q localhost:$PORT -u test -C "$COOKIE" --servercert=d66b507ae074d03b02eafca40d35f87dd81049d3 --verbose --pid-file "${PIDFILE1}" --background
sleep 4
# Extract the DTLS client hello
DTLS_HELLO=$($TCPDUMP -t -r $TCPDUMP_FILE -x | grep -v localhost | awk -F: '{print $2}' | tr -d '\n' | tr -d ' ' | cut -c 57- | sed 's/\(.\{2\}\)/\1 /g')
# Replay the DTLS client hello
echo Sending replayed DTLS Hello
echo $DTLS_HELLO | xxd -r -p | nc -w 1 -u localhost $PORT > /dev/null
sleep 5
ping -c 4 192.168.1.1 || fail $PID Tunnel was broken
exit 0