mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-02-10 08:46:58 +08:00
2849 lines
74 KiB
C
2849 lines
74 KiB
C
/*
|
||
* Copyright (C) 2013-2018 Nikos Mavrogiannopoulos
|
||
* Copyright (C) 2015, 2016 Red Hat, Inc.
|
||
*
|
||
* 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include <gnutls/gnutls.h>
|
||
#include <gnutls/dtls.h>
|
||
#include <gnutls/crypto.h>
|
||
#include <errno.h>
|
||
#include <stdlib.h>
|
||
#include <stdarg.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include <limits.h>
|
||
#include <netinet/in.h>
|
||
#include <sys/socket.h>
|
||
#include <netinet/tcp.h>
|
||
#include <arpa/inet.h>
|
||
#include <system.h>
|
||
#include <time.h>
|
||
#include <gettime.h>
|
||
#include <common.h>
|
||
#include <html.h>
|
||
#include <c-strcase.h>
|
||
#include <c-ctype.h>
|
||
#include <worker-bandwidth.h>
|
||
#include <signal.h>
|
||
#include <poll.h>
|
||
#include <math.h>
|
||
#include <ev.h>
|
||
|
||
#if defined(__linux__) && !defined(IPV6_PATHMTU)
|
||
# define IPV6_PATHMTU 61
|
||
#endif
|
||
|
||
#include <vpn.h>
|
||
#include "ipc.pb-c.h"
|
||
#include <worker.h>
|
||
#include <tlslib.h>
|
||
#include <http_parser.h>
|
||
|
||
#if defined(CAPTURE_LATENCY_SUPPORT)
|
||
#include <linux/net_tstamp.h>
|
||
#include <linux/errqueue.h>
|
||
#include <worker-latency.h>
|
||
#endif
|
||
|
||
#define MIN_MTU(ws) (((ws)->vinfo.ipv6!=NULL)?1280:800)
|
||
|
||
#define PERIODIC_CHECK_TIME 30
|
||
#define MIN_STATS_INTERVAL 10
|
||
|
||
/* The number of DPD packets a client skips before he's kicked */
|
||
#define DPD_TRIES 2
|
||
#define DPD_MAX_TRIES 3
|
||
|
||
/* HTTP requests prior to disconnection */
|
||
#define MAX_HTTP_REQUESTS 16
|
||
|
||
#define CSTP_DTLS_OVERHEAD 1
|
||
#define CSTP_OVERHEAD 8
|
||
|
||
#define IP_HEADER_SIZE 20
|
||
#define IPV6_HEADER_SIZE 40
|
||
#define TCP_HEADER_SIZE 20
|
||
#define UDP_HEADER_SIZE 8
|
||
|
||
#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;
|
||
|
||
static int terminate = 0;
|
||
static int terminate_reason = REASON_SERVER_DISCONNECT;
|
||
|
||
static struct ev_loop *worker_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,
|
||
time_t);
|
||
static int parse_dtls_data(struct worker_st *ws, uint8_t * buf, size_t buf_size,
|
||
time_t);
|
||
static int connect_handler(worker_st * ws);
|
||
static void session_info_send(worker_st * ws);
|
||
static void set_net_priority(worker_st * ws, int fd, int priority);
|
||
static void set_socket_timeout(worker_st * ws, int fd);
|
||
|
||
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 void dtls_watcher_cb (EV_P_ ev_io * w, int revents);
|
||
|
||
static void handle_alarm(int signo)
|
||
{
|
||
if (global_ws)
|
||
exit_worker_reason(global_ws, terminate_reason);
|
||
|
||
_exit(1);
|
||
}
|
||
|
||
static void handle_term(int signo)
|
||
{
|
||
terminate = 1;
|
||
terminate_reason = REASON_SERVER_DISCONNECT;
|
||
alarm(2); /* force exit by SIGALRM */
|
||
}
|
||
|
||
/* we override that function to force gnutls use poll()
|
||
*/
|
||
static
|
||
int tls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms)
|
||
{
|
||
int ret;
|
||
int fd = (long)ptr;
|
||
struct pollfd pfd;
|
||
|
||
pfd.fd = fd;
|
||
pfd.events = POLLIN;
|
||
pfd.revents = 0;
|
||
|
||
ret = poll(&pfd, 1, ms);
|
||
if (ret <= 0)
|
||
return ret;
|
||
|
||
return ret;
|
||
}
|
||
|
||
inline static ssize_t dtls_pull_buffer_non_empty(gnutls_transport_ptr_t ptr)
|
||
{
|
||
dtls_transport_ptr *p = ptr;
|
||
if (p->msg)
|
||
return 1;
|
||
return 0;
|
||
}
|
||
|
||
static
|
||
ssize_t dtls_pull(gnutls_transport_ptr_t ptr, void *data, size_t size)
|
||
{
|
||
dtls_transport_ptr *p = ptr;
|
||
|
||
if (p->msg) {
|
||
ssize_t need = p->msg->data.len;
|
||
if (need > size) {
|
||
need = size;
|
||
}
|
||
memcpy(data, p->msg->data.data, need);
|
||
|
||
udp_fd_msg__free_unpacked(p->msg, NULL);
|
||
p->msg = NULL;
|
||
return need;
|
||
}
|
||
return recv(p->fd, data, size, 0);
|
||
}
|
||
|
||
static
|
||
int dtls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms)
|
||
{
|
||
int ret;
|
||
dtls_transport_ptr *p = ptr;
|
||
int fd = p->fd;
|
||
struct pollfd pfd;
|
||
|
||
if (dtls_pull_buffer_non_empty(ptr)) {
|
||
return 1;
|
||
}
|
||
|
||
pfd.fd = fd;
|
||
pfd.events = POLLIN;
|
||
pfd.revents = 0;
|
||
|
||
ret = poll(&pfd, 1, ms);
|
||
if (ret <= 0)
|
||
return ret;
|
||
|
||
return ret;
|
||
}
|
||
|
||
static
|
||
ssize_t dtls_push(gnutls_transport_ptr_t ptr, const void *data, size_t size)
|
||
{
|
||
dtls_transport_ptr *p = ptr;
|
||
|
||
return send(p->fd, data, size, 0);
|
||
}
|
||
|
||
int get_psk_key(gnutls_session_t session,
|
||
const char *username, gnutls_datum_t *key)
|
||
{
|
||
struct worker_st *ws = gnutls_session_get_ptr(session);
|
||
|
||
key->data = gnutls_malloc(PSK_KEY_SIZE);
|
||
if (key->data == NULL)
|
||
return GNUTLS_E_MEMORY_ERROR;
|
||
|
||
memcpy(key->data, ws->master_secret, PSK_KEY_SIZE);
|
||
key->size = PSK_KEY_SIZE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if GNUTLS_VERSION_NUMBER < 0x030318
|
||
# define VERS_STRING "-VERS-TLS-ALL"
|
||
#else
|
||
# define VERS_STRING "-VERS-ALL"
|
||
#endif
|
||
|
||
#define PSK_LABEL "EXPORTER-openconnect-psk"
|
||
#define PSK_LABEL_SIZE sizeof(PSK_LABEL)-1
|
||
/* We initial a PSK connection with ciphers and MAC matching the TLS negotiated
|
||
* ciphers and MAC. The key is 32-bytes generated from gnutls_prf_rfc5705()
|
||
* with label being the PSK_LABEL.
|
||
*/
|
||
static int setup_dtls_psk_keys(gnutls_session_t session, struct worker_st *ws)
|
||
{
|
||
int ret;
|
||
char prio_string[256];
|
||
gnutls_mac_algorithm_t mac;
|
||
gnutls_cipher_algorithm_t cipher;
|
||
|
||
gnutls_psk_set_server_credentials_function(WSCREDS(ws)->pskcred, get_psk_key);
|
||
|
||
if (!ws->session) {
|
||
oclog(ws, LOG_ERR, "cannot setup PSK keys without an encrypted CSTP channel");
|
||
return -1;
|
||
}
|
||
|
||
if (WSCONFIG(ws)->match_dtls_and_tls) {
|
||
cipher = gnutls_cipher_get(ws->session);
|
||
mac = gnutls_mac_get(ws->session);
|
||
|
||
snprintf(prio_string, sizeof(prio_string), "%s:"VERS_STRING":-CIPHER-ALL:-MAC-ALL:-KX-ALL:+PSK:+VERS-DTLS-ALL:+%s:+%s",
|
||
WSCONFIG(ws)->priorities, gnutls_mac_get_name(mac), gnutls_cipher_get_name(cipher));
|
||
} else {
|
||
/* if we haven't an associated session, enable all ciphers we would have enabled
|
||
* otherwise for TLS. */
|
||
snprintf(prio_string, sizeof(prio_string), "%s:"VERS_STRING":-KX-ALL:+PSK:+VERS-DTLS-ALL",
|
||
WSCONFIG(ws)->priorities);
|
||
}
|
||
|
||
ret =
|
||
gnutls_priority_set_direct(session, prio_string, NULL);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not set TLS priority: '%s': %s",
|
||
prio_string, gnutls_strerror(ret));
|
||
return ret;
|
||
}
|
||
|
||
/* we should have used gnutls_prf_rfc5705() but since we don't use
|
||
* the RFC5705 context, the output is identical with gnutls_prf(). The
|
||
* latter is available in much earlier versions of gnutls. */
|
||
ret = gnutls_prf(ws->session, PSK_LABEL_SIZE, PSK_LABEL, 0, 0, 0, PSK_KEY_SIZE, (char*)ws->master_secret);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "error in PSK key generation: %s",
|
||
gnutls_strerror(ret));
|
||
return ret;
|
||
}
|
||
|
||
ret =
|
||
gnutls_credentials_set(session, GNUTLS_CRD_PSK,
|
||
WSCREDS(ws)->pskcred);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not set TLS PSK credentials: %s",
|
||
gnutls_strerror(ret));
|
||
return ret;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int setup_legacy_dtls_keys(gnutls_session_t session, struct worker_st *ws)
|
||
{
|
||
int ret;
|
||
gnutls_datum_t master =
|
||
{ ws->master_secret, sizeof(ws->master_secret) };
|
||
gnutls_datum_t sid = { ws->session_id, sizeof(ws->session_id) };
|
||
|
||
if (ws->req.selected_ciphersuite == NULL) {
|
||
oclog(ws, LOG_ERR, "no DTLS ciphersuite negotiated");
|
||
return -1;
|
||
}
|
||
|
||
ret =
|
||
gnutls_priority_set_direct(session,
|
||
ws->req.
|
||
selected_ciphersuite->gnutls_name, NULL);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not set TLS priority: %s",
|
||
gnutls_strerror(ret));
|
||
return ret;
|
||
}
|
||
|
||
ret = gnutls_session_set_premaster(session, GNUTLS_SERVER,
|
||
ws->req.
|
||
selected_ciphersuite->gnutls_version,
|
||
ws->req.
|
||
selected_ciphersuite->gnutls_kx,
|
||
ws->req.
|
||
selected_ciphersuite->gnutls_cipher,
|
||
ws->req.
|
||
selected_ciphersuite->gnutls_mac,
|
||
GNUTLS_COMP_NULL, &master, &sid);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not set TLS premaster: %s",
|
||
gnutls_strerror(ret));
|
||
return ret;
|
||
}
|
||
|
||
gnutls_certificate_server_set_request(session, GNUTLS_CERT_IGNORE);
|
||
|
||
ret =
|
||
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
|
||
WSCREDS(ws)->xcred);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not set TLS credentials: %s",
|
||
gnutls_strerror(ret));
|
||
return ret;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int setup_dtls_connection(struct worker_st *ws, struct dtls_st * dtls)
|
||
{
|
||
int ret;
|
||
gnutls_session_t session;
|
||
#if defined(CAPTURE_LATENCY_SUPPORT)
|
||
int ts_socket_opt = SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE;
|
||
#endif
|
||
|
||
/* DTLS cookie verified.
|
||
* Initialize session.
|
||
*/
|
||
ret = gnutls_init(&session, GNUTLS_SERVER|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not initialize TLS session: %s",
|
||
gnutls_strerror(ret));
|
||
return -1;
|
||
}
|
||
|
||
gnutls_session_set_ptr(session, ws);
|
||
|
||
if (ws->req.use_psk && ws->session) {
|
||
oclog(ws, LOG_INFO, "setting up DTLS-PSK connection");
|
||
ret = setup_dtls_psk_keys(session, ws);
|
||
} else {
|
||
if (!WSCONFIG(ws)->dtls_legacy) {
|
||
oclog(ws, LOG_INFO, "CISCO client compatibility (dtls-legacy) is disabled; will not setup a DTLS session");
|
||
goto fail;
|
||
}
|
||
oclog(ws, LOG_INFO, "setting up legacy DTLS (resumption) connection");
|
||
ret = setup_legacy_dtls_keys(session, ws);
|
||
}
|
||
|
||
if (ret < 0) {
|
||
goto fail;
|
||
}
|
||
|
||
gnutls_transport_set_push_function(session, dtls_push);
|
||
#if defined(CAPTURE_LATENCY_SUPPORT)
|
||
gnutls_transport_set_pull_function(session, dtls_pull_latency);
|
||
#else
|
||
gnutls_transport_set_pull_function(session, dtls_pull);
|
||
#endif
|
||
gnutls_transport_set_pull_timeout_function(session, dtls_pull_timeout);
|
||
gnutls_transport_set_ptr(session, &dtls->dtls_tptr);
|
||
|
||
/* we decrease the default retransmission timeout to bring
|
||
* our DTLS support in par with the DTLS1.3 recommendations.
|
||
*/
|
||
gnutls_dtls_set_timeouts(session, 400, 60*1000);
|
||
|
||
dtls->udp_state = UP_HANDSHAKE;
|
||
|
||
#if defined(CAPTURE_LATENCY_SUPPORT)
|
||
ret = setsockopt(dtls->dtls_tptr.fd, SOL_SOCKET, SO_TIMESTAMPING, &ts_socket_opt, sizeof(ts_socket_opt));
|
||
if (ret == -1)
|
||
oclog(ws, LOG_DEBUG, "setsockopt(UDP, SO_TIMESTAMPING), failed.");
|
||
#endif
|
||
|
||
/* Setup the fd settings */
|
||
if (WSCONFIG(ws)->output_buffer > 0) {
|
||
int t = MIN(2048, ws->link_mtu * WSCONFIG(ws)->output_buffer);
|
||
ret = setsockopt(dtls->dtls_tptr.fd, SOL_SOCKET, SO_SNDBUF, &t,
|
||
sizeof(t));
|
||
if (ret == -1)
|
||
oclog(ws, LOG_DEBUG,
|
||
"setsockopt(UDP, SO_SNDBUF) to %u, failed.",
|
||
t);
|
||
}
|
||
set_net_priority(ws, dtls->dtls_tptr.fd, ws->user_config->net_priority);
|
||
set_socket_timeout(ws, dtls->dtls_tptr.fd);
|
||
|
||
/* reset MTU */
|
||
link_mtu_set(ws, dtls, ws->adv_link_mtu);
|
||
|
||
if (dtls->dtls_session != NULL) {
|
||
gnutls_deinit(dtls->dtls_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(worker_loop, &dtls->io);
|
||
ev_invoke(worker_loop, &dtls->io, EV_READ);
|
||
|
||
return 0;
|
||
fail:
|
||
gnutls_deinit(session);
|
||
return -1;
|
||
}
|
||
|
||
void ws_add_score_to_ip(worker_st *ws, unsigned points, unsigned final, unsigned discon_reason)
|
||
{
|
||
int ret, e;
|
||
BanIpMsg msg = BAN_IP_MSG__INIT;
|
||
BanIpReplyMsg *reply = NULL;
|
||
PROTOBUF_ALLOCATOR(pa, ws);
|
||
|
||
/* no reporting if banning is disabled */
|
||
if (WSCONFIG(ws)->max_ban_score == 0)
|
||
return;
|
||
|
||
/* In final call, no score added, we simply send */
|
||
if (final == 0) {
|
||
ws->ban_points += points;
|
||
/* do not use IPC for small values */
|
||
if (points < WSCONFIG(ws)->ban_points_wrong_password)
|
||
return;
|
||
}
|
||
|
||
msg.ip = ws->remote_ip_str;
|
||
msg.score = points;
|
||
if (final) {
|
||
msg.has_discon_reason = 1;
|
||
msg.discon_reason = discon_reason;
|
||
}
|
||
|
||
ret = send_msg(ws, ws->cmd_fd, CMD_BAN_IP, &msg,
|
||
(pack_size_func) ban_ip_msg__get_packed_size,
|
||
(pack_func) ban_ip_msg__pack);
|
||
if (ret < 0) {
|
||
e = errno;
|
||
oclog(ws, LOG_WARNING, "error in sending BAN IP message: %s", strerror(e));
|
||
return;
|
||
}
|
||
|
||
ret = recv_msg(ws, ws->cmd_fd, CMD_BAN_IP_REPLY,
|
||
(void *)&reply, (unpack_func) ban_ip_reply_msg__unpack, DEFAULT_SOCKET_TIMEOUT);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "error receiving BAN IP reply message");
|
||
return;
|
||
}
|
||
|
||
if (final ==0 && reply->reply != AUTH__REP__OK) {
|
||
/* we have exceeded the maximum score */
|
||
exit(1);
|
||
}
|
||
|
||
ban_ip_reply_msg__free_unpacked(reply, &pa);
|
||
|
||
return;
|
||
}
|
||
|
||
void send_stats_to_secmod(worker_st * ws, time_t now, unsigned discon_reason)
|
||
{
|
||
CliStatsMsg msg = CLI_STATS_MSG__INIT;
|
||
int sd, ret, e;
|
||
|
||
/* this is only used by certain tests */
|
||
if (WSPCONFIG(ws)->debug_no_secmod_stats != 0)
|
||
return;
|
||
|
||
ws->last_stats_msg = now;
|
||
|
||
sd = connect_to_secmod(ws);
|
||
if (sd >= 0) {
|
||
char buf[64];
|
||
msg.bytes_in = ws->tun_bytes_in;
|
||
msg.bytes_out = ws->tun_bytes_out;
|
||
msg.uptime = now - ws->session_start_time;
|
||
msg.sid.len = sizeof(ws->sid);
|
||
msg.sid.data = ws->sid;
|
||
msg.has_sid = 1;
|
||
|
||
if (discon_reason) {
|
||
msg.has_discon_reason = 1;
|
||
msg.discon_reason = discon_reason;
|
||
}
|
||
|
||
msg.remote_ip = human_addr2((void *)&ws->remote_addr, ws->remote_addr_len,
|
||
buf, sizeof(buf), 0);
|
||
|
||
msg.ipv4 = ws->vinfo.ipv4;
|
||
msg.ipv6 = ws->vinfo.ipv6;
|
||
|
||
ret = send_msg_to_secmod(ws, sd, CMD_SEC_CLI_STATS, &msg,
|
||
(pack_size_func)cli_stats_msg__get_packed_size,
|
||
(pack_func) cli_stats_msg__pack);
|
||
if (discon_reason) { /* wait for sec-mod to close connection to verify data have been accounted */
|
||
e = read(sd, buf, sizeof(buf));
|
||
if (e == -1) {
|
||
e = errno;
|
||
oclog(ws, LOG_DEBUG, "could not wait for sec-mod: %s\n", strerror(e));
|
||
}
|
||
}
|
||
close(sd);
|
||
|
||
if (ret >= 0) {
|
||
oclog(ws, LOG_INFO,
|
||
"sent periodic stats (in: %lu, out: %lu) to sec-mod",
|
||
(unsigned long)msg.bytes_in,
|
||
(unsigned long)msg.bytes_out);
|
||
} else {
|
||
e = errno;
|
||
oclog(ws, LOG_WARNING, "could not send periodic stats to sec-mod: %s\n", strerror(e));
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Terminates the worker process, but communicates any required
|
||
* data to main process before (stats/ban points).
|
||
*/
|
||
void exit_worker(worker_st * ws)
|
||
{
|
||
exit_worker_reason(ws, REASON_ANY);
|
||
}
|
||
|
||
void exit_worker_reason(worker_st * ws, unsigned reason)
|
||
{
|
||
/* send statistics to parent */
|
||
if (ws->auth_state == S_AUTH_COMPLETE) {
|
||
send_stats_to_secmod(ws, time(0), reason);
|
||
}
|
||
|
||
if (ws->ban_points > 0)
|
||
ws_add_score_to_ip(ws, 0, 1, reason);
|
||
|
||
talloc_free(ws->main_pool);
|
||
closelog();
|
||
_exit(1);
|
||
}
|
||
|
||
#define HANDSHAKE_SESSION_ID_POS (34)
|
||
#define SKIP_V16(pos, total) \
|
||
{ uint16_t _s; \
|
||
if (pos+2 > total) goto finish; \
|
||
_s = (msg->data[pos] << 8) | msg->data[pos+1]; \
|
||
if (pos+2+_s > total) goto finish; \
|
||
pos += 2+_s; \
|
||
}
|
||
|
||
#define SKIP16(pos, total) \
|
||
if (pos+2 > total) goto finish; \
|
||
pos += 2
|
||
|
||
#define SKIP8(pos, total) \
|
||
if (pos+1 > total) goto finish; \
|
||
pos++
|
||
|
||
#define SKIP_V8(pos, total) \
|
||
{ uint8_t _s; \
|
||
if (pos+1 > total) goto finish; \
|
||
_s = msg->data[pos]; \
|
||
if (pos+1+_s > total) goto finish; \
|
||
pos += 1+_s; \
|
||
}
|
||
|
||
#define SET_VHOST_CREDS \
|
||
ret = \
|
||
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, \
|
||
WSCREDS(ws)->xcred); \
|
||
GNUTLS_FATAL_ERR(ret); \
|
||
gnutls_certificate_server_set_request(session, WSCONFIG(ws)->cert_req); \
|
||
ret = gnutls_priority_set(session, WSCREDS(ws)->cprio); \
|
||
GNUTLS_FATAL_ERR(ret); \
|
||
gnutls_db_set_cache_expiration(session, TLS_SESSION_EXPIRATION_TIME(WSCONFIG(ws)))
|
||
|
||
/* Parse the TLS client hello to figure vhost */
|
||
static int hello_hook_func(gnutls_session_t session, unsigned int htype,
|
||
unsigned when, unsigned int incoming,
|
||
const gnutls_datum_t *msg)
|
||
|
||
{
|
||
ssize_t ret;
|
||
size_t pos;
|
||
size_t hsize;
|
||
struct worker_st *ws = gnutls_session_get_ptr(session);
|
||
|
||
if (htype != GNUTLS_HANDSHAKE_CLIENT_HELLO || when != GNUTLS_HOOK_PRE)
|
||
goto finish;
|
||
|
||
/* find the server name extension */
|
||
|
||
pos = HANDSHAKE_SESSION_ID_POS;
|
||
if (msg->size <= pos)
|
||
goto finish;
|
||
|
||
if (msg->data[0] != 0x03) {
|
||
/* unknown packet version */
|
||
goto finish;
|
||
}
|
||
|
||
/* skip session id */
|
||
SKIP_V8(pos, msg->size);
|
||
|
||
/* CipherSuites */
|
||
SKIP_V16(pos, msg->size);
|
||
|
||
/* legacy_compression_methods */
|
||
SKIP_V8(pos, msg->size);
|
||
|
||
/* Skip extension total size */
|
||
SKIP16(pos, msg->size);
|
||
|
||
while (pos < msg->size) {
|
||
uint16_t type;
|
||
|
||
/* read ExtensionType */
|
||
SKIP16(pos, msg->size);
|
||
type = (msg->data[pos-2] << 8) | msg->data[pos-1];
|
||
|
||
if (type == 0) { /* server name ext */
|
||
SKIP16(pos, msg->size);
|
||
SKIP16(pos, msg->size); /* we don't support anything but a single name */
|
||
|
||
SKIP8(pos, msg->size);
|
||
if (msg->data[pos-1] != 0) { /* HostName */
|
||
oclog(ws, LOG_DEBUG,
|
||
"received server name extension with invalid name type field");
|
||
goto finish;
|
||
}
|
||
|
||
SKIP16(pos, msg->size);
|
||
hsize = (msg->data[pos-2] << 8) | msg->data[pos-1];
|
||
|
||
if (hsize == 0 || hsize + pos > msg->size || hsize > sizeof(ws->buffer)-1) {
|
||
oclog(ws, LOG_DEBUG,
|
||
"received server name extension with too large name");
|
||
goto finish;
|
||
}
|
||
|
||
memcpy(ws->buffer, &msg->data[pos], hsize);
|
||
ws->buffer[hsize] = 0;
|
||
|
||
oclog(ws, LOG_DEBUG,
|
||
"client requested hostname: %s", (char*)ws->buffer);
|
||
|
||
ws->vhost = find_vhost(ws->vconfig, (char*)ws->buffer);
|
||
if (ws->vhost->name && c_strcasecmp(ws->vhost->name, (char*)ws->buffer) != 0) {
|
||
oclog(ws, LOG_INFO,
|
||
"client requested hostname %s does not match known vhost", (char*)ws->buffer);
|
||
}
|
||
|
||
goto finish;
|
||
} else {
|
||
SKIP_V16(pos, msg->size);
|
||
}
|
||
}
|
||
|
||
finish:
|
||
/* We set credentials irrespective of whether a virtual host was found,
|
||
* as they have not been previously set. */
|
||
SET_VHOST_CREDS;
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if GNUTLS_VERSION_NUMBER < 0x030400
|
||
# define SIMULATE_CLIENT_HELLO_HOOK
|
||
#endif
|
||
|
||
#ifdef SIMULATE_CLIENT_HELLO_HOOK
|
||
#define TLS_RECORD_HEADER 5
|
||
#define TLS_HANDSHAKE_HEADER 4
|
||
|
||
/* In gnutls 3.3 we don't get the size in the handshake callback
|
||
* so we try to simulate.
|
||
*/
|
||
static void peek_client_hello(struct worker_st *ws, gnutls_session_t session, int fd)
|
||
{
|
||
unsigned read_tries = 0;
|
||
int ret;
|
||
size_t size, hsize;
|
||
gnutls_datum_t msg;
|
||
|
||
do {
|
||
if (read_tries > 0) {
|
||
if (read_tries > 5)
|
||
goto fallback;
|
||
ms_sleep(150);
|
||
}
|
||
read_tries++;
|
||
|
||
ret = recv(fd, ws->buffer, sizeof(ws->buffer), MSG_PEEK);
|
||
if (ret == -1)
|
||
goto fallback;
|
||
size = ret;
|
||
|
||
if (size < TLS_RECORD_HEADER)
|
||
goto fallback;
|
||
|
||
hsize = (ws->buffer[3] << 8) | ws->buffer[4];
|
||
} while(hsize+TLS_RECORD_HEADER > size);
|
||
|
||
if (size < TLS_RECORD_HEADER+TLS_HANDSHAKE_HEADER+HANDSHAKE_SESSION_ID_POS)
|
||
goto fallback;
|
||
|
||
msg.data = ws->buffer + TLS_RECORD_HEADER+TLS_HANDSHAKE_HEADER;
|
||
msg.size = size - (TLS_RECORD_HEADER+TLS_HANDSHAKE_HEADER);
|
||
hello_hook_func(session, GNUTLS_HANDSHAKE_CLIENT_HELLO,
|
||
GNUTLS_HOOK_PRE, 1, &msg);
|
||
|
||
return;
|
||
|
||
fallback:
|
||
SET_VHOST_CREDS;
|
||
}
|
||
#endif
|
||
|
||
/* vpn_server:
|
||
* @ws: an initialized worker structure
|
||
*
|
||
* This is the main worker process. It is executed
|
||
* by the main server after fork and drop of privileges.
|
||
*
|
||
* It handles the client connection including:
|
||
* - HTTPS authentication using XML forms that are parsed and
|
||
* forwarded to main.
|
||
* - TLS authentication (using certificate)
|
||
* - TCP VPN tunnel establishment (after HTTP CONNECT)
|
||
* - UDP VPN tunnel establishment (once an FD is forwarded by main)
|
||
*
|
||
*/
|
||
void vpn_server(struct worker_st *ws)
|
||
{
|
||
int ret;
|
||
ssize_t nparsed, nrecvd;
|
||
gnutls_session_t session = NULL;
|
||
http_parser parser;
|
||
http_parser_settings settings;
|
||
url_handler_fn fn;
|
||
int requests_left = MAX_HTTP_REQUESTS;
|
||
|
||
ocsigaltstack(ws);
|
||
|
||
ocsignal(SIGTERM, handle_term);
|
||
ocsignal(SIGINT, handle_term);
|
||
ocsignal(SIGHUP, SIG_IGN);
|
||
ocsignal(SIGALRM, handle_alarm);
|
||
|
||
global_ws = ws;
|
||
if (GETCONFIG(ws)->auth_timeout) {
|
||
terminate_reason = REASON_SERVER_DISCONNECT;
|
||
alarm(GETCONFIG(ws)->auth_timeout);
|
||
}
|
||
|
||
/* do not allow this process to be traced. That
|
||
* prevents worker processes tracing each other. */
|
||
if (GETPCONFIG(ws)->debug == 0)
|
||
pr_set_undumpable("worker");
|
||
if (GETCONFIG(ws)->isolate != 0) {
|
||
ret = disable_system_calls(ws);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_INFO,
|
||
"could not disable system calls, kernel might not support seccomp");
|
||
}
|
||
}
|
||
|
||
if (ws->remote_addr_len == sizeof(struct sockaddr_in))
|
||
ws->proto = AF_INET;
|
||
else
|
||
ws->proto = AF_INET6;
|
||
|
||
if (GETCONFIG(ws)->listen_proxy_proto) {
|
||
oclog(ws, LOG_DEBUG, "accepted proxy protocol connection");
|
||
ret = parse_proxy_proto_header(ws, ws->conn_fd);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR,
|
||
"could not parse proxy protocol header; discarding connection");
|
||
exit_worker(ws);
|
||
}
|
||
} else {
|
||
oclog(ws, LOG_DEBUG, "accepted connection");
|
||
}
|
||
|
||
if (ws->conn_type != SOCK_TYPE_UNIX) {
|
||
/* ws->vhost is being assigned in gnutls_handshake()
|
||
* after client hello is received. We set temporarily a value
|
||
* as we need to set some cipher priorities for handshake to start. */
|
||
ws->vhost = find_vhost(ws->vconfig, NULL);
|
||
|
||
if (test_for_tcp_health_probe(ws) != 0) {
|
||
oclog(ws, LOG_DEBUG, "Received TCP health probe from load-balancer");
|
||
exit_worker_reason(ws, REASON_HEALTH_PROBE);
|
||
}
|
||
|
||
/* initialize the session */
|
||
ret = gnutls_init(&session, GNUTLS_SERVER);
|
||
GNUTLS_FATAL_ERR(ret);
|
||
|
||
ret = gnutls_priority_set(session, WSCREDS(ws)->cprio);
|
||
GNUTLS_FATAL_ERR(ret);
|
||
gnutls_session_set_ptr(session, ws);
|
||
|
||
/* if we have a single vhost, avoid going through a callback to set credentials. */
|
||
if (!HAVE_VHOSTS(ws)) {
|
||
SET_VHOST_CREDS;
|
||
} else {
|
||
#ifdef SIMULATE_CLIENT_HELLO_HOOK
|
||
peek_client_hello(ws, session, ws->conn_fd);
|
||
#else
|
||
gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_CLIENT_HELLO,
|
||
GNUTLS_HOOK_PRE, hello_hook_func);
|
||
#endif
|
||
}
|
||
|
||
gnutls_transport_set_ptr(session,
|
||
(gnutls_transport_ptr_t) (long)ws->conn_fd);
|
||
|
||
set_resume_db_funcs(session);
|
||
gnutls_db_set_ptr(session, ws);
|
||
|
||
gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
|
||
gnutls_transport_set_pull_timeout_function(session, tls_pull_timeout);
|
||
do {
|
||
ret = gnutls_handshake(session);
|
||
} while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
|
||
GNUTLS_FATAL_ERR(ret);
|
||
|
||
oclog(ws, LOG_DEBUG, "TLS handshake completed");
|
||
} else {
|
||
ws->vhost = find_vhost(ws->vconfig, NULL);
|
||
|
||
oclog(ws, LOG_DEBUG, "Accepted unix connection");
|
||
}
|
||
|
||
ws->session = session;
|
||
|
||
session_info_send(ws);
|
||
|
||
memset(&settings, 0, sizeof(settings));
|
||
|
||
ws->selected_auth = &WSPCONFIG(ws)->auth[0];
|
||
if (ws->cert_auth_ok)
|
||
ws_switch_auth_to(ws, AUTH_TYPE_CERTIFICATE);
|
||
|
||
settings.on_url = http_url_cb;
|
||
settings.on_header_field = http_header_field_cb;
|
||
settings.on_header_value = http_header_value_cb;
|
||
settings.on_headers_complete = http_header_complete_cb;
|
||
settings.on_message_complete = http_message_complete_cb;
|
||
settings.on_body = http_body_cb;
|
||
http_req_init(ws);
|
||
|
||
if (WSCONFIG(ws)->listen_proxy_proto) {
|
||
oclog(ws, LOG_DEBUG, "proxy-hdr: peer is %s\n", ws->remote_ip_str);
|
||
}
|
||
|
||
ws->parser = &parser;
|
||
|
||
restart:
|
||
if (requests_left-- <= 0) {
|
||
oclog(ws, LOG_INFO, "maximum number of HTTP requests reached");
|
||
exit_worker(ws);
|
||
}
|
||
|
||
http_parser_init(&parser, HTTP_REQUEST);
|
||
parser.data = ws;
|
||
http_req_reset(ws);
|
||
/* parse as we go */
|
||
do {
|
||
nrecvd = cstp_recv(ws, ws->buffer, sizeof(ws->buffer));
|
||
if (nrecvd <= 0) {
|
||
if (nrecvd == 0)
|
||
goto finish;
|
||
if (nrecvd != GNUTLS_E_PREMATURE_TERMINATION)
|
||
oclog(ws, LOG_ERR,
|
||
"error receiving client data");
|
||
exit_worker(ws);
|
||
}
|
||
|
||
nparsed =
|
||
http_parser_execute(&parser, &settings, (void *)ws->buffer,
|
||
nrecvd);
|
||
if (nparsed == 0) {
|
||
oclog(ws, LOG_INFO, "error parsing HTTP request");
|
||
exit_worker(ws);
|
||
}
|
||
} while (ws->req.headers_complete == 0);
|
||
|
||
if (parser.method == HTTP_GET) {
|
||
oclog(ws, LOG_HTTP_DEBUG, "HTTP GET %s", ws->req.url);
|
||
fn = http_get_url_handler(ws->req.url);
|
||
if (fn == NULL) {
|
||
oclog(ws, LOG_HTTP_DEBUG, "unexpected URL %s", ws->req.url);
|
||
response_404(ws, parser.http_minor);
|
||
goto finish;
|
||
}
|
||
ret = fn(ws, parser.http_minor);
|
||
if (ret == 0
|
||
&& (parser.http_major != 1 || parser.http_minor != 0))
|
||
goto restart;
|
||
|
||
} else if (parser.method == HTTP_POST) {
|
||
/* continue reading */
|
||
oclog(ws, LOG_HTTP_DEBUG, "HTTP POST %s", ws->req.url);
|
||
while (ws->req.message_complete == 0) {
|
||
nrecvd = cstp_recv(ws, ws->buffer, sizeof(ws->buffer));
|
||
CSTP_FATAL_ERR(ws, nrecvd);
|
||
|
||
if (nrecvd == 0) {
|
||
oclog(ws, LOG_HTTP_DEBUG,
|
||
"EOF while receiving HTTP POST request");
|
||
exit_worker(ws);
|
||
}
|
||
|
||
nparsed =
|
||
http_parser_execute(&parser, &settings, (void *)ws->buffer,
|
||
nrecvd);
|
||
if (nparsed == 0) {
|
||
oclog(ws, LOG_HTTP_DEBUG,
|
||
"error parsing HTTP POST request");
|
||
exit_worker(ws);
|
||
}
|
||
}
|
||
|
||
fn = http_post_url_handler(ws, ws->req.url);
|
||
if (fn == NULL) {
|
||
oclog(ws, LOG_HTTP_DEBUG, "unexpected POST URL %s",
|
||
ws->req.url);
|
||
response_404(ws, parser.http_minor);
|
||
goto finish;
|
||
}
|
||
|
||
ret = fn(ws, parser.http_minor);
|
||
if (ret == 0
|
||
&& (parser.http_major != 1 || parser.http_minor != 0))
|
||
goto restart;
|
||
|
||
} else if (parser.method == HTTP_CONNECT) {
|
||
oclog(ws, LOG_HTTP_DEBUG, "HTTP CONNECT %s", ws->req.url);
|
||
ret = connect_handler(ws);
|
||
if (ret == 0
|
||
&& (parser.http_major != 1 || parser.http_minor != 0))
|
||
goto restart;
|
||
|
||
} else {
|
||
oclog(ws, LOG_HTTP_DEBUG, "unexpected HTTP method %s",
|
||
http_method_str(parser.method));
|
||
response_404(ws, parser.http_minor);
|
||
}
|
||
|
||
finish:
|
||
cstp_close(ws);
|
||
}
|
||
|
||
static
|
||
void data_mtu_send(worker_st * ws, unsigned mtu)
|
||
{
|
||
TunMtuMsg msg = TUN_MTU_MSG__INIT;
|
||
|
||
msg.mtu = mtu;
|
||
send_msg_to_main(ws, CMD_TUN_MTU, &msg,
|
||
(pack_size_func) tun_mtu_msg__get_packed_size,
|
||
(pack_func) tun_mtu_msg__pack);
|
||
|
||
oclog(ws, LOG_DEBUG, "setting data MTU to %u", msg.mtu);
|
||
}
|
||
|
||
static
|
||
void session_info_send(worker_st * ws)
|
||
{
|
||
SessionInfoMsg msg = SESSION_INFO_MSG__INIT;
|
||
|
||
if (ws->session) {
|
||
msg.tls_ciphersuite = gnutls_session_get_desc(ws->session);
|
||
if (ws->cstp_selected_comp)
|
||
msg.cstp_compr = (char*)ws->cstp_selected_comp->name;
|
||
}
|
||
|
||
if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED && DTLS_ACTIVE(ws)->dtls_session) {
|
||
msg.dtls_ciphersuite =
|
||
gnutls_session_get_desc(DTLS_ACTIVE(ws)->dtls_session);
|
||
if (ws->dtls_selected_comp)
|
||
msg.dtls_compr = (char*)ws->dtls_selected_comp->name;
|
||
}
|
||
|
||
if (WSCONFIG(ws)->listen_proxy_proto) {
|
||
msg.our_addr.data = (uint8_t*)&ws->our_addr;
|
||
msg.our_addr.len = ws->our_addr_len;
|
||
msg.has_our_addr = 1;
|
||
msg.remote_addr.data = (uint8_t*)&ws->remote_addr;
|
||
msg.remote_addr.len = ws->remote_addr_len;
|
||
msg.has_remote_addr = 1;
|
||
}
|
||
|
||
send_msg_to_main(ws, CMD_SESSION_INFO, &msg,
|
||
(pack_size_func) session_info_msg__get_packed_size,
|
||
(pack_func) session_info_msg__pack);
|
||
|
||
gnutls_free(msg.tls_ciphersuite);
|
||
gnutls_free(msg.dtls_ciphersuite);
|
||
}
|
||
|
||
/* link_mtu_set: Sets the link MTU for the session
|
||
*
|
||
* @ws: a worker structure
|
||
* @mtu: the link MTU
|
||
*/
|
||
static
|
||
void link_mtu_set(struct worker_st * ws, struct dtls_st * dtls, unsigned mtu)
|
||
{
|
||
if (ws->link_mtu == mtu || mtu > sizeof(ws->buffer))
|
||
return;
|
||
|
||
ws->link_mtu = mtu;
|
||
|
||
oclog(ws, LOG_DEBUG, "setting connection link MTU to %u", mtu);
|
||
if (dtls->dtls_session)
|
||
gnutls_dtls_set_mtu(dtls->dtls_session,
|
||
ws->link_mtu - ws->dtls_proto_overhead);
|
||
|
||
data_mtu_send(ws, DATA_MTU(ws, ws->link_mtu));
|
||
}
|
||
|
||
/* data_mtu_set: Sets the data MTU for the session
|
||
*
|
||
* @ws: a worker structure
|
||
* @mtu: the "plaintext" data MTU (not including the DTLS protocol byte)
|
||
*/
|
||
static
|
||
void data_mtu_set(worker_st * ws, struct dtls_st * dtls, unsigned mtu)
|
||
{
|
||
if (dtls->dtls_session) {
|
||
gnutls_dtls_set_data_mtu(dtls->dtls_session, mtu+1);
|
||
|
||
mtu = gnutls_dtls_get_mtu(dtls->dtls_session);
|
||
if (mtu <= 0 || mtu == ws->link_mtu)
|
||
return;
|
||
|
||
mtu += ws->dtls_proto_overhead;
|
||
link_mtu_set(ws, dtls, mtu);
|
||
}
|
||
}
|
||
|
||
static void disable_mtu_disc(worker_st *ws, struct dtls_st * dtls)
|
||
{
|
||
oclog(ws, LOG_DEBUG, "disabling MTU discovery on UDP socket");
|
||
set_mtu_disc(dtls->dtls_tptr.fd, ws->proto, 0);
|
||
link_mtu_set(ws, dtls, ws->adv_link_mtu);
|
||
WSCONFIG(ws)->try_mtu = 0;
|
||
}
|
||
|
||
/* sets the current value of mtu as bad,
|
||
* and returns an estimation of good.
|
||
*
|
||
* Returns -1 on failure.
|
||
*/
|
||
static
|
||
int mtu_not_ok(worker_st * ws, struct dtls_st * dtls)
|
||
{
|
||
if (WSCONFIG(ws)->try_mtu == 0 || dtls->dtls_session == NULL)
|
||
return 0;
|
||
|
||
if (ws->proto == AF_INET) {
|
||
const unsigned min = MIN_MTU(ws);
|
||
|
||
ws->last_bad_mtu = ws->link_mtu;
|
||
|
||
if (ws->last_good_mtu == min) {
|
||
oclog(ws, LOG_INFO,
|
||
"could not calculate a sufficient MTU; disabling MTU discovery");
|
||
disable_mtu_disc(ws, dtls);
|
||
link_mtu_set(ws, dtls, min);
|
||
return 0;
|
||
}
|
||
|
||
if (ws->last_good_mtu >= ws->link_mtu) {
|
||
ws->last_good_mtu = MAX(((2 * (ws->link_mtu)) / 3), min);
|
||
}
|
||
|
||
link_mtu_set(ws, dtls, ws->last_good_mtu);
|
||
oclog(ws, LOG_INFO, "MTU %u is too large, switching to %u",
|
||
ws->last_bad_mtu, ws->link_mtu);
|
||
} else if (ws->proto == AF_INET6) { /* IPv6 */
|
||
#ifdef IPV6_PATHMTU
|
||
struct ip6_mtuinfo mtuinfo;
|
||
socklen_t len = sizeof(mtuinfo);
|
||
|
||
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",
|
||
ws->link_mtu);
|
||
disable_mtu_disc(ws, dtls);
|
||
link_mtu_set(ws, dtls, MIN_MTU(ws));
|
||
return 0;
|
||
}
|
||
|
||
oclog(ws, LOG_DEBUG, "setting (via IPV6_PATHMTU) connection MTU to %u", mtuinfo.ip6m_mtu);
|
||
link_mtu_set(ws, dtls, mtuinfo.ip6m_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",
|
||
(unsigned)mtuinfo.ip6m_mtu, ws->adv_link_mtu);
|
||
return 0;
|
||
}
|
||
#else
|
||
link_mtu_set(ws, dtls, MIN_MTU(ws));
|
||
#endif
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* mtu_discovery_init: initiates MTU discovery
|
||
*
|
||
* @ws: a worker structure
|
||
* @mtu: the current "plaintext" data MTU
|
||
*/
|
||
static void mtu_discovery_init(worker_st * ws, struct dtls_st * dtls, unsigned mtu)
|
||
{
|
||
const unsigned min = MIN_MTU(ws);
|
||
if (mtu <= min) {
|
||
oclog(ws, LOG_INFO,
|
||
"our initial MTU is too low; disabling MTU discovery");
|
||
disable_mtu_disc(ws, dtls);
|
||
}
|
||
|
||
if (!WSCONFIG(ws)->try_mtu)
|
||
oclog(ws, LOG_DEBUG,
|
||
"Initializing MTU discovery; initial MTU: %u\n", mtu);
|
||
|
||
ws->last_good_mtu = mtu;
|
||
ws->last_bad_mtu = mtu;
|
||
}
|
||
|
||
static
|
||
void mtu_ok(worker_st * ws, struct dtls_st * dtls)
|
||
{
|
||
unsigned int c;
|
||
|
||
if (WSCONFIG(ws)->try_mtu == 0 || ws->proto == AF_INET6)
|
||
return;
|
||
|
||
if (ws->last_bad_mtu == (ws->link_mtu) + 1 ||
|
||
ws->last_bad_mtu == (ws->link_mtu))
|
||
return;
|
||
|
||
ws->last_good_mtu = ws->link_mtu;
|
||
c = (ws->link_mtu + ws->last_bad_mtu) / 2;
|
||
|
||
link_mtu_set(ws, dtls, c);
|
||
return;
|
||
}
|
||
|
||
#define FUZZ(x, diff, rnd) \
|
||
if (x > diff) { \
|
||
int16_t r = rnd; \
|
||
x += r % diff; \
|
||
}
|
||
|
||
int get_pmtu_approx(worker_st *ws)
|
||
{
|
||
socklen_t sl;
|
||
int ret, e;
|
||
|
||
#if defined(__linux__) && defined(TCP_INFO)
|
||
struct tcp_info ti;
|
||
sl = sizeof(ti);
|
||
|
||
ret = getsockopt(ws->conn_fd, IPPROTO_TCP, TCP_INFO, &ti, &sl);
|
||
if (ret == -1) {
|
||
e = errno;
|
||
oclog(ws, LOG_INFO, "error in getting TCP_INFO: %s",
|
||
strerror(e));
|
||
return -1;
|
||
} else {
|
||
return ti.tcpi_pmtu;
|
||
}
|
||
#else
|
||
int max = -1;
|
||
|
||
sl = sizeof(max);
|
||
ret = getsockopt(ws->conn_fd, IPPROTO_TCP, TCP_MAXSEG, &max, &sl);
|
||
if (ret == -1) {
|
||
e = errno;
|
||
oclog(ws, LOG_INFO, "error in getting TCP_MAXSEG: %s",
|
||
strerror(e));
|
||
return -1;
|
||
} else {
|
||
MSS_ADJUST(max);
|
||
return max;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
static
|
||
int periodic_check(worker_st * ws, struct timespec *tnow, unsigned dpd)
|
||
{
|
||
int max, ret;
|
||
time_t now = tnow->tv_sec;
|
||
time_t periodic_check_time = PERIODIC_CHECK_TIME;
|
||
|
||
/* modify timers with a fuzzying factor, to prevent all worker processes
|
||
* to act at exactly the same time (e.g., after a server restart on which
|
||
* all clients reconnect at the same time). */
|
||
FUZZ(periodic_check_time, 5, tnow->tv_nsec);
|
||
|
||
if (now - ws->last_periodic_check < periodic_check_time)
|
||
return 0;
|
||
|
||
/* we set an alarm at each periodic check to prevent any
|
||
* freezes in the worker due to an unexpected block (due to worker
|
||
* bug or kernel bug). In that case the worker will be killed due
|
||
* the the alarm instead of hanging. */
|
||
terminate_reason = REASON_SERVER_DISCONNECT;
|
||
alarm(1800);
|
||
|
||
if (WSCONFIG(ws)->idle_timeout > 0) {
|
||
if (now - ws->last_nc_msg > WSCONFIG(ws)->idle_timeout) {
|
||
oclog(ws, LOG_ERR,
|
||
"idle timeout reached for process (%d secs)",
|
||
(int)(now - ws->last_nc_msg));
|
||
terminate = 1;
|
||
terminate_reason = REASON_IDLE_TIMEOUT;
|
||
goto cleanup;
|
||
}
|
||
}
|
||
|
||
if (ws->user_config->session_timeout_secs > 0) {
|
||
if (now - ws->session_start_time > ws->user_config->session_timeout_secs) {
|
||
oclog(ws, LOG_ERR,
|
||
"session timeout reached for process (%d secs)",
|
||
(int)(now - ws->session_start_time));
|
||
terminate = 1;
|
||
terminate_reason = REASON_SESSION_TIMEOUT;
|
||
goto cleanup;
|
||
}
|
||
}
|
||
|
||
if (ws->user_config->interim_update_secs > 0 &&
|
||
now - ws->last_stats_msg >= ws->user_config->interim_update_secs &&
|
||
ws->sid_set) {
|
||
send_stats_to_secmod(ws, now, 0);
|
||
}
|
||
|
||
#if defined(CAPTURE_LATENCY_SUPPORT)
|
||
if (now - ws->latency.last_stats_msg >= LATENCY_WORKER_AGGREGATION_TIME) {
|
||
send_latency_stats_delta_to_main(ws, now);
|
||
}
|
||
#endif
|
||
|
||
/* check DPD. Otherwise exit */
|
||
if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE &&
|
||
now - ws->last_msg_udp > DPD_TRIES * dpd && dpd > 0) {
|
||
unsigned data_mtu = DATA_MTU(ws, ws->link_mtu);
|
||
oclog(ws, LOG_ERR,
|
||
"have not received any UDP message or DPD for long (%d secs, DPD is %d)",
|
||
(int)(now - ws->last_msg_udp), dpd);
|
||
|
||
memset(ws->buffer+1, 0, data_mtu);
|
||
ws->buffer[0] = AC_PKT_DPD_OUT;
|
||
|
||
ret = dtls_send(DTLS_ACTIVE(ws), ws->buffer, data_mtu+1);
|
||
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
|
||
|
||
if (now - ws->last_msg_udp > DPD_MAX_TRIES * dpd) {
|
||
oclog(ws, LOG_ERR,
|
||
"have not received UDP message or DPD for very long; disabling UDP port");
|
||
DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
|
||
}
|
||
}
|
||
if (dpd > 0 && now - ws->last_msg_tcp > DPD_TRIES * dpd) {
|
||
oclog(ws, LOG_DEBUG,
|
||
"have not received TCP DPD for long (%d secs)",
|
||
(int)(now - ws->last_msg_tcp));
|
||
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_DPD_OUT;
|
||
ws->buffer[7] = 0;
|
||
|
||
ret = cstp_send(ws, ws->buffer, 8);
|
||
CSTP_FATAL_ERR_CMD(ws, ret, exit_worker_reason(ws, REASON_ERROR));
|
||
|
||
if (now - ws->last_msg_tcp > DPD_MAX_TRIES * dpd) {
|
||
oclog(ws, LOG_ERR,
|
||
"connection timeout (DPD); tearing down connection");
|
||
exit_worker_reason(ws, REASON_DPD_TIMEOUT);
|
||
}
|
||
}
|
||
|
||
if (ws->conn_type != SOCK_TYPE_UNIX && DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) {
|
||
max = get_pmtu_approx(ws);
|
||
if (max > 0 && max < ws->link_mtu) {
|
||
oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u",
|
||
max);
|
||
link_mtu_set(ws, DTLS_ACTIVE(ws), max);
|
||
}
|
||
}
|
||
|
||
cleanup:
|
||
ws->last_periodic_check = now;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Disable any TCP queuing on the TLS port. This allows a connection that works over
|
||
* TCP instead of UDP to still be interactive.
|
||
*/
|
||
static void set_no_delay(worker_st * ws, int fd)
|
||
{
|
||
int flag = 1;
|
||
int ret;
|
||
|
||
ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
||
if (ret == -1) {
|
||
oclog(ws, LOG_DEBUG,
|
||
"setsockopt(TCP_NODELAY) to %x, failed.", (unsigned)flag);
|
||
return;
|
||
}
|
||
}
|
||
|
||
#define TOSCLASS(x) (IPTOS_CLASS_CS##x)
|
||
|
||
static void set_net_priority(worker_st * ws, int fd, int priority)
|
||
{
|
||
int t;
|
||
int ret;
|
||
#if defined(IP_TOS)
|
||
if (priority != 0 && IS_TOS(priority)) {
|
||
t = TOS_UNPACK(priority);
|
||
ret = setsockopt(fd, IPPROTO_IP, IP_TOS, &t, sizeof(t));
|
||
if (ret == -1)
|
||
oclog(ws, LOG_DEBUG,
|
||
"setsockopt(IP_TOS) to %x, failed.", (unsigned)t);
|
||
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
#ifdef SO_PRIORITY
|
||
if (priority != 0 && priority <= 7) {
|
||
t = ws->user_config->net_priority - 1;
|
||
ret = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &t, sizeof(t));
|
||
if (ret == -1)
|
||
oclog(ws, LOG_DEBUG,
|
||
"setsockopt(SO_PRIORITY) to %d, failed.", t);
|
||
|
||
return;
|
||
}
|
||
#endif
|
||
return;
|
||
}
|
||
|
||
#define SEND_ERR(x) if (x<0) goto send_error
|
||
|
||
static int dtls_mainloop(worker_st * ws, struct dtls_st * dtls, struct timespec *tnow)
|
||
{
|
||
int ret;
|
||
gnutls_datum_t data;
|
||
void *packet = NULL;
|
||
|
||
switch (dtls->udp_state) {
|
||
case UP_ACTIVE:
|
||
case UP_INACTIVE:
|
||
ret = dtls_recv_packet(dtls, &data, &packet);
|
||
oclog(ws, LOG_TRANSFER_DEBUG,
|
||
"received %d byte(s) (DTLS)", ret);
|
||
|
||
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
|
||
|
||
if (ret == GNUTLS_E_REHANDSHAKE) {
|
||
|
||
if (dtls->last_dtls_rehandshake > 0 &&
|
||
tnow->tv_sec - dtls->last_dtls_rehandshake <
|
||
WSCONFIG(ws)->rekey_time / 2) {
|
||
oclog(ws, LOG_INFO,
|
||
"client requested DTLS rehandshake too soon");
|
||
ret = -1;
|
||
goto cleanup;
|
||
}
|
||
|
||
/* there is not much we can rehandshake on the DTLS channel,
|
||
* at least not the way AnyConnect sets it up.
|
||
*/
|
||
oclog(ws, LOG_DEBUG,
|
||
"client requested rehandshake on DTLS channel");
|
||
|
||
do {
|
||
ret = gnutls_handshake(dtls->dtls_session);
|
||
} while (ret == GNUTLS_E_AGAIN
|
||
|| ret == GNUTLS_E_INTERRUPTED);
|
||
|
||
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
|
||
oclog(ws, LOG_DEBUG, "DTLS rehandshake completed");
|
||
|
||
dtls->last_dtls_rehandshake = tnow->tv_sec;
|
||
} else if (ret >= 1) {
|
||
/* where we receive any DTLS UDP packet we reset the state
|
||
* to active */
|
||
dtls->udp_state = UP_ACTIVE;
|
||
|
||
if (bandwidth_update
|
||
(&ws->b_rx, data.size - CSTP_DTLS_OVERHEAD, tnow) != 0) {
|
||
ret =
|
||
parse_dtls_data(ws, data.data, data.size,
|
||
tnow->tv_sec);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_INFO,
|
||
"error parsing CSTP data");
|
||
goto cleanup;
|
||
}
|
||
}
|
||
} else
|
||
oclog(ws, LOG_TRANSFER_DEBUG,
|
||
"no data received (%d)", ret);
|
||
|
||
ws->udp_recv_time = tnow->tv_sec;
|
||
break;
|
||
case UP_SETUP:
|
||
ret = setup_dtls_connection(ws, dtls);
|
||
if (ret < 0) {
|
||
ret = -1;
|
||
goto cleanup;
|
||
}
|
||
|
||
gnutls_dtls_set_mtu(dtls->dtls_session, ws->link_mtu - ws->dtls_proto_overhead);
|
||
mtu_discovery_init(ws, dtls, ws->link_mtu);
|
||
break;
|
||
|
||
case UP_HANDSHAKE:
|
||
hsk_restart:
|
||
ret = gnutls_handshake(dtls->dtls_session);
|
||
if (ret < 0 && gnutls_error_is_fatal(ret) != 0) {
|
||
if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED)
|
||
oclog(ws, LOG_ERR,
|
||
"error in DTLS handshake: %s: %s\n",
|
||
gnutls_strerror(ret),
|
||
gnutls_alert_get_name
|
||
(gnutls_alert_get(dtls->dtls_session)));
|
||
else
|
||
oclog(ws, LOG_ERR,
|
||
"error in DTLS handshake: %s\n",
|
||
gnutls_strerror(ret));
|
||
dtls->udp_state = UP_DISABLED;
|
||
ev_io_stop(worker_loop, &dtls->io);
|
||
break;
|
||
}
|
||
|
||
if (ret == GNUTLS_E_LARGE_PACKET) {
|
||
/* adjust mtu */
|
||
mtu_not_ok(ws, dtls);
|
||
goto hsk_restart;
|
||
} else if (ret == 0) {
|
||
unsigned data_mtu;
|
||
|
||
/* gnutls_dtls_get_data_mtu() already subtracts the crypto overhead */
|
||
data_mtu =
|
||
gnutls_dtls_get_data_mtu(dtls->dtls_session) -
|
||
CSTP_DTLS_OVERHEAD;
|
||
|
||
dtls->udp_state = UP_ACTIVE;
|
||
oclog(ws, LOG_DEBUG,
|
||
"DTLS handshake completed (link MTU: %u, data MTU: %u)\n",
|
||
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);
|
||
}
|
||
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
ret = 0;
|
||
cleanup:
|
||
packet_deinit(packet);
|
||
return ret;
|
||
}
|
||
|
||
static int tls_mainloop(struct worker_st *ws, struct timespec *tnow)
|
||
{
|
||
int ret;
|
||
gnutls_datum_t data;
|
||
void *packet = NULL;
|
||
|
||
ret = cstp_recv_packet(ws, &data, &packet);
|
||
if (ret == GNUTLS_E_PREMATURE_TERMINATION) {
|
||
oclog(ws, LOG_DEBUG, "client disconnected prematurely");
|
||
ret = -1;
|
||
goto cleanup;
|
||
}
|
||
|
||
CSTP_FATAL_ERR_CMD(ws, ret, exit_worker_reason(ws, REASON_ERROR));
|
||
|
||
if (ret == 0) { /* disconnect */
|
||
oclog(ws, LOG_DEBUG, "client disconnected");
|
||
ret = -1;
|
||
goto cleanup;
|
||
} else if (ret >= 8) {
|
||
oclog(ws, LOG_TRANSFER_DEBUG, "received %d byte(s) (TLS)", data.size);
|
||
|
||
if (bandwidth_update(&ws->b_rx, data.size - 8, tnow) != 0) {
|
||
ret = parse_cstp_data(ws, data.data, data.size, tnow->tv_sec);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "error parsing CSTP data");
|
||
goto cleanup;
|
||
}
|
||
|
||
if ((ret == AC_PKT_DATA || ret == AC_PKT_COMPRESSED) && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) {
|
||
/* client switched to TLS for some reason */
|
||
if (tnow->tv_sec - ws->udp_recv_time >
|
||
UDP_SWITCH_TIME)
|
||
DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
|
||
}
|
||
}
|
||
|
||
} else if (ret == GNUTLS_E_REHANDSHAKE) {
|
||
/* rekey? */
|
||
if (ws->last_tls_rehandshake > 0 &&
|
||
tnow->tv_sec - ws->last_tls_rehandshake <
|
||
WSCONFIG(ws)->rekey_time / 2) {
|
||
oclog(ws, LOG_INFO,
|
||
"client requested TLS rehandshake too soon");
|
||
ret = -1;
|
||
goto cleanup;
|
||
}
|
||
|
||
oclog(ws, LOG_INFO,
|
||
"client requested rehandshake on TLS channel");
|
||
do {
|
||
ret = gnutls_handshake(ws->session);
|
||
} while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
|
||
DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR));
|
||
|
||
ws->last_tls_rehandshake = tnow->tv_sec;
|
||
oclog(ws, LOG_INFO, "TLS rehandshake completed");
|
||
}
|
||
|
||
ret = 0;
|
||
cleanup:
|
||
packet_deinit(packet);
|
||
return ret;
|
||
}
|
||
|
||
static int tun_mainloop(struct worker_st *ws, struct timespec *tnow)
|
||
{
|
||
int ret, l, e;
|
||
unsigned tls_retry;
|
||
int dtls_type = AC_PKT_DATA;
|
||
int cstp_type = AC_PKT_DATA;
|
||
gnutls_datum_t dtls_to_send;
|
||
gnutls_datum_t cstp_to_send;
|
||
|
||
l = tun_read(ws->tun_fd, ws->buffer + 8, DATA_MTU(ws, ws->link_mtu));
|
||
if (l < 0) {
|
||
e = errno;
|
||
|
||
if (e != EAGAIN && e != EINTR) {
|
||
oclog(ws, LOG_ERR,
|
||
"received corrupt data from tun (%d): %s",
|
||
l, strerror(e));
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
if (l == 0) {
|
||
oclog(ws, LOG_INFO, "TUN device returned zero");
|
||
return 0;
|
||
}
|
||
|
||
|
||
dtls_to_send.data = ws->buffer;
|
||
dtls_to_send.size = l;
|
||
|
||
cstp_to_send.data = ws->buffer;
|
||
cstp_to_send.size = l;
|
||
|
||
if (WSCONFIG(ws)->switch_to_tcp_timeout &&
|
||
DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE &&
|
||
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",
|
||
tnow->tv_sec - ws->udp_recv_time);
|
||
DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
|
||
}
|
||
|
||
#ifdef ENABLE_COMPRESSION
|
||
if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && ws->dtls_selected_comp != NULL && l > WSCONFIG(ws)->no_compress_limit) {
|
||
/* otherwise don't compress */
|
||
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);
|
||
if (ret > 0 && ret < l) {
|
||
dtls_to_send.data = ws->decomp;
|
||
dtls_to_send.size = ret;
|
||
dtls_type = AC_PKT_COMPRESSED;
|
||
|
||
if (ws->cstp_selected_comp) {
|
||
if (ws->cstp_selected_comp->id == ws->dtls_selected_comp->id) {
|
||
cstp_to_send.data = ws->decomp;
|
||
cstp_to_send.size = ret;
|
||
cstp_type = AC_PKT_COMPRESSED;
|
||
}
|
||
}
|
||
}
|
||
} else if (ws->cstp_selected_comp != NULL && l > WSCONFIG(ws)->no_compress_limit) {
|
||
/* otherwise don't compress */
|
||
ret = ws->cstp_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);
|
||
if (ret > 0 && ret < l) {
|
||
cstp_to_send.data = ws->decomp;
|
||
cstp_to_send.size = ret;
|
||
cstp_type = AC_PKT_COMPRESSED;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/* only transmit if allowed */
|
||
if (bandwidth_update(&ws->b_tx, dtls_to_send.size, tnow)
|
||
!= 0) {
|
||
tls_retry = 0;
|
||
|
||
oclog(ws, LOG_TRANSFER_DEBUG, "sending %d byte(s)\n", l);
|
||
|
||
if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) {
|
||
|
||
ws->tun_bytes_out += dtls_to_send.size;
|
||
|
||
dtls_to_send.data[7] = dtls_type;
|
||
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));
|
||
|
||
if (ret == GNUTLS_E_LARGE_PACKET) {
|
||
mtu_not_ok(ws, DTLS_ACTIVE(ws));
|
||
|
||
oclog(ws, LOG_TRANSFER_DEBUG,
|
||
"retrying (TLS) %d\n", l);
|
||
tls_retry = 1;
|
||
} else if (ret >= 1+DATA_MTU(ws, ws->link_mtu) &&
|
||
WSCONFIG(ws)->try_mtu != 0) {
|
||
mtu_ok(ws, DTLS_ACTIVE(ws));
|
||
}
|
||
}
|
||
|
||
if (DTLS_ACTIVE(ws)->udp_state != UP_ACTIVE || tls_retry != 0) {
|
||
cstp_to_send.data[0] = 'S';
|
||
cstp_to_send.data[1] = 'T';
|
||
cstp_to_send.data[2] = 'F';
|
||
cstp_to_send.data[3] = 1;
|
||
cstp_to_send.data[4] = cstp_to_send.size >> 8;
|
||
cstp_to_send.data[5] = cstp_to_send.size & 0xff;
|
||
cstp_to_send.data[6] = cstp_type;
|
||
cstp_to_send.data[7] = 0;
|
||
|
||
ws->tun_bytes_out += cstp_to_send.size;
|
||
|
||
ret = cstp_send(ws, cstp_to_send.data, cstp_to_send.size + 8);
|
||
CSTP_FATAL_ERR_CMD(ws, ret, exit_worker_reason(ws, REASON_ERROR));
|
||
}
|
||
ws->last_nc_msg = tnow->tv_sec;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static
|
||
char *replace_vals(worker_st *ws, const char *txt)
|
||
{
|
||
str_st str;
|
||
int ret;
|
||
str_rep_tab tab[3];
|
||
|
||
STR_TAB_SET(0, "%{U}", ws->username);
|
||
STR_TAB_SET(1, "%{G}", ws->groupname);
|
||
STR_TAB_TERM(2);
|
||
|
||
str_init(&str, ws);
|
||
|
||
ret = str_append_str(&str, txt);
|
||
if (ret < 0)
|
||
return NULL;
|
||
|
||
ret = str_replace_str(&str, tab);
|
||
if (ret < 0) {
|
||
str_clear(&str);
|
||
return NULL;
|
||
}
|
||
|
||
return (char*)str.data;
|
||
}
|
||
|
||
static int send_routes(worker_st *ws, struct http_req_st *req,
|
||
char **routes, unsigned routes_size,
|
||
bool include)
|
||
{
|
||
unsigned i;
|
||
unsigned ip6;
|
||
const char *txt;
|
||
int ret;
|
||
|
||
if (include)
|
||
txt = "Include";
|
||
else
|
||
txt = "Exclude";
|
||
|
||
for (i = 0; i < routes_size; i++) {
|
||
if (strchr(routes[i], ':') != 0)
|
||
ip6 = 1;
|
||
else
|
||
ip6 = 0;
|
||
|
||
if (req->no_ipv6 != 0 && ip6 != 0)
|
||
continue;
|
||
if (req->no_ipv4 != 0 && ip6 == 0)
|
||
continue;
|
||
oclog(ws, LOG_DEBUG, "%s route %s", txt, routes[i]);
|
||
|
||
if (ip6 != 0 && ws->full_ipv6) {
|
||
ret = cstp_printf(ws,
|
||
"X-CSTP-Split-%s-IP6: %s\r\n",
|
||
txt, routes[i]);
|
||
} else {
|
||
ret = cstp_printf(ws,
|
||
"X-CSTP-Split-%s: %s\r\n",
|
||
txt, routes[i]);
|
||
}
|
||
if (ret < 0)
|
||
return ret;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Enforces a socket timeout. That is because, although we
|
||
* use poll() to see whether a call to recv() would block,
|
||
* there are certain cases in Linux where recv() blocks even
|
||
* though poll() notified of data */
|
||
static void set_socket_timeout(worker_st * ws, int fd)
|
||
{
|
||
struct timeval tval;
|
||
int ret;
|
||
|
||
tval.tv_sec = DEFAULT_SOCKET_TIMEOUT;
|
||
tval.tv_usec = 0;
|
||
ret =
|
||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tval,
|
||
sizeof(tval));
|
||
if (ret == -1) {
|
||
int e = errno;
|
||
oclog(ws, LOG_DEBUG,
|
||
"setsockopt(%s, SO_RCVTIMEO) failed: %s", (fd==ws->conn_fd)?"ΤCP":"UDP", strerror(e));
|
||
}
|
||
}
|
||
|
||
/* wild but conservative guess; this ciphersuite has the largest overhead */
|
||
#define MAX_CSTP_CRYPTO_OVERHEAD (CSTP_OVERHEAD+tls_get_overhead(GNUTLS_TLS1_0, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1))
|
||
#define MAX_DTLS_CRYPTO_OVERHEAD (CSTP_DTLS_OVERHEAD+tls_get_overhead(GNUTLS_DTLS1_0, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1))
|
||
#define MAX_DTLS_PROTO_OVERHEAD(ws) ((ws->proto == AF_INET)?(IP_HEADER_SIZE+UDP_HEADER_SIZE):(IPV6_HEADER_SIZE+UDP_HEADER_SIZE))
|
||
|
||
/* Calculate MTU for CSTP and DTLS channels.
|
||
*/
|
||
static void calc_mtu_values(worker_st * ws)
|
||
{
|
||
/* assume that if IPv6 is used over TCP then the same would be used over UDP */
|
||
if (ws->proto == AF_INET) {
|
||
ws->cstp_proto_overhead = IP_HEADER_SIZE;
|
||
ws->dtls_proto_overhead = IP_HEADER_SIZE;
|
||
} else {
|
||
ws->cstp_proto_overhead = IPV6_HEADER_SIZE;
|
||
ws->dtls_proto_overhead = IPV6_HEADER_SIZE;
|
||
}
|
||
ws->cstp_proto_overhead += TCP_HEADER_SIZE;
|
||
ws->dtls_proto_overhead += UDP_HEADER_SIZE;
|
||
|
||
if (ws->session == NULL) {
|
||
ws->cstp_crypto_overhead = MAX_CSTP_CRYPTO_OVERHEAD;
|
||
} else {
|
||
ws->cstp_crypto_overhead = CSTP_OVERHEAD +
|
||
tls_get_overhead(gnutls_protocol_get_version(ws->session),
|
||
gnutls_cipher_get(ws->session),
|
||
gnutls_mac_get(ws->session));
|
||
}
|
||
|
||
/* link MTU is the device MTU */
|
||
ws->link_mtu = ws->vinfo.mtu;
|
||
|
||
if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) {
|
||
/* crypto overhead for DTLS */
|
||
if (ws->req.use_psk) {
|
||
if (ws->session == NULL) {
|
||
ws->dtls_crypto_overhead = MAX_DTLS_CRYPTO_OVERHEAD;
|
||
} else {
|
||
ws->dtls_crypto_overhead = tls_get_overhead(
|
||
GNUTLS_DTLS1_0,
|
||
gnutls_cipher_get(ws->session),
|
||
gnutls_mac_get(ws->session));
|
||
}
|
||
} else if (ws->req.selected_ciphersuite) {
|
||
ws->dtls_crypto_overhead =
|
||
tls_get_overhead(ws->req.
|
||
selected_ciphersuite->gnutls_version,
|
||
ws->req.
|
||
selected_ciphersuite->gnutls_cipher,
|
||
ws->req.selected_ciphersuite->gnutls_mac);
|
||
}
|
||
ws->dtls_crypto_overhead += CSTP_DTLS_OVERHEAD;
|
||
|
||
oclog(ws, LOG_DEBUG,
|
||
"DTLS overhead is %u",
|
||
ws->dtls_proto_overhead + ws->dtls_crypto_overhead);
|
||
}
|
||
|
||
/* This is the data MTU we advertised to peer, we will never exceed this value */
|
||
ws->adv_link_mtu = ws->link_mtu;
|
||
}
|
||
|
||
/* connect_handler:
|
||
* @ws: an initialized worker structure
|
||
*
|
||
* This function handles the HTTPS session after a CONNECT
|
||
* command has been issued by the peer. The @ws->auth_state
|
||
* should be set to %S_AUTH_COMPLETE or the client will be
|
||
* disconnected.
|
||
*
|
||
* If the user is authenticate it handles the TCP and UDP VPN
|
||
* tunnels.
|
||
*
|
||
*/
|
||
static int connect_handler(worker_st * ws)
|
||
{
|
||
struct http_req_st *req = &ws->req;
|
||
int max, ret, t;
|
||
char *p;
|
||
unsigned rnd;
|
||
unsigned i;
|
||
unsigned ip6;
|
||
time_t now = time(0);
|
||
|
||
ret = gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(rnd));
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR,
|
||
"error in the random generator: %s", gnutls_strerror(ret));
|
||
cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n");
|
||
cstp_puts(ws,
|
||
"X-Reason: Server error\r\n\r\n");
|
||
return -1;
|
||
}
|
||
|
||
ws->buffer_size = sizeof(ws->buffer);
|
||
|
||
cookie_authenticate_or_exit(ws);
|
||
|
||
if (strcmp(req->url, "/CSCOSSLC/tunnel") != 0) {
|
||
oclog(ws, LOG_INFO, "bad connect request: '%s'\n", req->url);
|
||
response_404(ws, 1);
|
||
cstp_fatal_close(ws, GNUTLS_A_ACCESS_DENIED);
|
||
exit_worker(ws);
|
||
}
|
||
|
||
if (WSCONFIG(ws)->network.name[0] == 0) {
|
||
oclog(ws, LOG_ERR,
|
||
"no networks are configured; rejecting client");
|
||
cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n");
|
||
cstp_puts(ws,
|
||
"X-Reason: Server configuration error\r\n\r\n");
|
||
return -1;
|
||
}
|
||
|
||
ret = complete_vpn_info(ws, &ws->vinfo);
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR,
|
||
"no networks are configured; rejecting client");
|
||
cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n");
|
||
cstp_puts(ws,
|
||
"X-Reason: Server configuration error\r\n\r\n");
|
||
return -1;
|
||
}
|
||
|
||
/* override any hostname sent by the peer if we have one already configured */
|
||
if (ws->user_config->hostname) {
|
||
strlcpy(ws->req.hostname, ws->user_config->hostname, sizeof(ws->req.hostname));
|
||
}
|
||
|
||
FUZZ(ws->user_config->interim_update_secs, 5, rnd);
|
||
FUZZ(WSCONFIG(ws)->rekey_time, 30, rnd);
|
||
|
||
/* Connected. Turn of the alarm */
|
||
if (WSCONFIG(ws)->auth_timeout)
|
||
alarm(0);
|
||
http_req_deinit(ws);
|
||
|
||
cstp_cork(ws);
|
||
ret = cstp_puts(ws, "HTTP/1.1 200 CONNECTED\r\n");
|
||
SEND_ERR(ret);
|
||
|
||
ret = cstp_puts(ws, "X-CSTP-Version: 1\r\n");
|
||
SEND_ERR(ret);
|
||
|
||
ret = cstp_puts(ws, "X-CSTP-Server-Name: "PACKAGE_STRING"\r\n");
|
||
SEND_ERR(ret);
|
||
|
||
if (req->is_mobile) {
|
||
ws->user_config->dpd = ws->user_config->mobile_dpd;
|
||
WSCONFIG(ws)->idle_timeout = WSCONFIG(ws)->mobile_idle_timeout;
|
||
}
|
||
|
||
/* Notify back the client about the accepted hostname */
|
||
if (ws->req.hostname[0] != 0) {
|
||
ret = cstp_printf(ws, "X-CSTP-Hostname: %s\r\n", ws->req.hostname);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
oclog(ws, LOG_INFO, "suggesting DPD of %d secs", ws->user_config->dpd);
|
||
if (ws->user_config->dpd > 0) {
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-DPD: %u\r\n",
|
||
ws->user_config->dpd);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
if (WSCONFIG(ws)->default_domain) {
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Default-Domain: %s\r\n",
|
||
WSCONFIG(ws)->default_domain);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
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) {
|
||
memcpy(ws->master_secret, req->master_secret, TLS_MASTER_SIZE);
|
||
DTLS_ACTIVE(ws)->udp_state = UP_WAIT_FD;
|
||
DTLS_INACTIVE(ws)->udp_state = UP_WAIT_FD;
|
||
} else {
|
||
oclog(ws, LOG_DEBUG, "disabling UDP (DTLS) connection");
|
||
}
|
||
|
||
if (ws->user_config->mtu > 0)
|
||
ws->vinfo.mtu = ws->user_config->mtu;
|
||
oclog(ws, LOG_INFO, "configured link MTU is %u", ws->vinfo.mtu);
|
||
|
||
if (req->link_mtu > 0) {
|
||
oclog(ws, LOG_INFO, "peer's link MTU is %u", req->link_mtu);
|
||
ws->vinfo.mtu = MIN(ws->vinfo.mtu, req->link_mtu);
|
||
} else if (req->tunnel_mtu > 0) {
|
||
/* Old clients didn't send their link MTU, they send the plaintext MTU
|
||
* they can transfer. */
|
||
ws->vinfo.mtu = MIN(ws->vinfo.mtu, req->tunnel_mtu + MAX_DTLS_PROTO_OVERHEAD(ws) + MAX_DTLS_CRYPTO_OVERHEAD);
|
||
oclog(ws, LOG_INFO, "peer's data MTU is %u / link is %u", req->tunnel_mtu, ws->vinfo.mtu);
|
||
}
|
||
|
||
/* Attempt to use the TCP connection maximum segment size to set a more
|
||
* precise MTU. */
|
||
if (ws->conn_type != SOCK_TYPE_UNIX) {
|
||
max = get_pmtu_approx(ws);
|
||
if (max > 0 && max < ws->vinfo.mtu) {
|
||
oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u",
|
||
max);
|
||
link_mtu_set(ws, DTLS_ACTIVE(ws), max);
|
||
}
|
||
}
|
||
|
||
calc_mtu_values(ws);
|
||
|
||
if (DATA_MTU(ws, ws->link_mtu) < 1280 && ws->vinfo.ipv6 && req->no_ipv6 == 0) {
|
||
oclog(ws, LOG_INFO, "Connection MTU (link: %u, data: %u) is not sufficient for IPv6 (1280)", ws->link_mtu, DATA_MTU(ws, ws->link_mtu));
|
||
req->no_ipv6 = 1;
|
||
}
|
||
|
||
/* Send IP addresses */
|
||
if (ws->vinfo.ipv4 && req->no_ipv4 == 0) {
|
||
oclog(ws, LOG_INFO, "sending IPv4 %s", ws->vinfo.ipv4);
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Address: %s\r\n",
|
||
ws->vinfo.ipv4);
|
||
SEND_ERR(ret);
|
||
|
||
if (ws->user_config->ipv4_netmask) {
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Netmask: %s\r\n",
|
||
ws->user_config->ipv4_netmask);
|
||
SEND_ERR(ret);
|
||
}
|
||
}
|
||
|
||
if (ws->vinfo.ipv6 && req->no_ipv6 == 0 && ws->user_config->ipv6_prefix != 0) {
|
||
oclog(ws, LOG_INFO, "sending IPv6 %s/%u", ws->vinfo.ipv6, ws->user_config->ipv6_subnet_prefix);
|
||
if (ws->full_ipv6 && ws->user_config->ipv6_subnet_prefix) {
|
||
ret =
|
||
cstp_printf(ws,
|
||
"X-CSTP-Address-IP6: %s/%u\r\n",
|
||
ws->vinfo.ipv6, ws->user_config->ipv6_subnet_prefix);
|
||
SEND_ERR(ret);
|
||
} else {
|
||
const char *net;
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Address: %s\r\n",
|
||
ws->vinfo.ipv6);
|
||
SEND_ERR(ret);
|
||
|
||
net = ws->user_config->ipv6_net;
|
||
if (net == NULL)
|
||
net = ws->vinfo.ipv6;
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Netmask: %s/%u\r\n",
|
||
net, ws->user_config->ipv6_subnet_prefix);
|
||
SEND_ERR(ret);
|
||
}
|
||
}
|
||
|
||
/* While anyconnect clients can handle the assignment
|
||
* of an IPv6 address, they cannot handle routes or DNS
|
||
* in IPv6. So we disable IPv6 after an IP is assigned. */
|
||
if (ws->full_ipv6 == 0) {
|
||
req->no_ipv6 = 1;
|
||
oclog(ws, LOG_INFO, "IPv6 routes/DNS disabled because IPv6 support was not requested.");
|
||
} else if (req->user_agent_type != AGENT_OPENCONNECT && req->user_agent_type != AGENT_ANYCONNECT) {
|
||
req->no_ipv6 = 1;
|
||
oclog(ws, LOG_INFO, "IPv6 routes/DNS disabled because the agent is not known.");
|
||
}
|
||
|
||
for (i = 0; i < ws->user_config->n_dns; i++) {
|
||
if (strchr(ws->user_config->dns[i], ':') != 0)
|
||
ip6 = 1;
|
||
else
|
||
ip6 = 0;
|
||
|
||
if (req->no_ipv6 != 0 && ip6 != 0)
|
||
continue;
|
||
if (req->no_ipv4 != 0 && ip6 == 0)
|
||
continue;
|
||
|
||
oclog(ws, LOG_INFO, "adding DNS %s", ws->user_config->dns[i]);
|
||
if (req->user_agent_type == AGENT_ANYCONNECT) {
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-%s: %s\r\n",
|
||
ip6 ? "DNS-IP6" : "DNS",
|
||
ws->user_config->dns[i]);
|
||
} else { /* openconnect does not require the split
|
||
* of DNS and DNS-IP6 and only recent versions
|
||
* understand the IP6 variant. */
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-DNS: %s\r\n",
|
||
ws->user_config->dns[i]);
|
||
}
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
for (i = 0; i < ws->user_config->n_nbns; i++) {
|
||
if (strchr(ws->user_config->nbns[i], ':') != 0)
|
||
ip6 = 1;
|
||
else
|
||
ip6 = 0;
|
||
|
||
if (req->no_ipv6 != 0 && ip6 != 0)
|
||
continue;
|
||
if (req->no_ipv4 != 0 && ip6 == 0)
|
||
continue;
|
||
|
||
oclog(ws, LOG_INFO, "adding NBNS %s", ws->user_config->nbns[i]);
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-NBNS: %s\r\n",
|
||
ws->user_config->nbns[i]);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
for (i = 0; i < ws->user_config->n_split_dns; i++) {
|
||
if (strchr(ws->user_config->split_dns[i], ':') != 0)
|
||
ip6 = 1;
|
||
else
|
||
ip6 = 0;
|
||
|
||
if (req->no_ipv6 != 0 && ip6 != 0)
|
||
continue;
|
||
if (req->no_ipv4 != 0 && ip6 == 0)
|
||
continue;
|
||
|
||
oclog(ws, LOG_INFO, "adding split DNS %s",
|
||
ws->user_config->split_dns[i]);
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Split-DNS: %s\r\n",
|
||
ws->user_config->split_dns[i]);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
/* Anyconnect on IOS requires this route in order to use IPv6 */
|
||
if (ws->full_ipv6 && req->is_ios &&
|
||
(ws->user_config->n_routes == 0 || ws->default_route == 0)) {
|
||
oclog(ws, LOG_INFO, "adding special split DNS for Apple");
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Split-Include-IP6: 2000::/3\r\n");
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
if (ws->default_route == 0) {
|
||
ret = send_routes(ws, req, ws->user_config->routes, ws->user_config->n_routes, 1);
|
||
SEND_ERR(ret);
|
||
|
||
} else {
|
||
/* default route */
|
||
WSCONFIG(ws)->tunnel_all_dns = 1;
|
||
}
|
||
|
||
if (WSCONFIG(ws)->tunnel_all_dns) {
|
||
ret = cstp_puts(ws, "X-CSTP-Tunnel-All-DNS: true\r\n");
|
||
} else {
|
||
ret = cstp_puts(ws, "X-CSTP-Tunnel-All-DNS: false\r\n");
|
||
}
|
||
SEND_ERR(ret);
|
||
|
||
ret = send_routes(ws, req, ws->user_config->no_routes, ws->user_config->n_no_routes, 0);
|
||
SEND_ERR(ret);
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Keepalive: %u\r\n",
|
||
ws->user_config->keepalive);
|
||
SEND_ERR(ret);
|
||
|
||
if (WSCONFIG(ws)->idle_timeout > 0) {
|
||
ret =
|
||
cstp_printf(ws,
|
||
"X-CSTP-Idle-Timeout: %u\r\n",
|
||
(unsigned)WSCONFIG(ws)->idle_timeout);
|
||
} else {
|
||
ret = cstp_puts(ws, "X-CSTP-Idle-Timeout: none\r\n");
|
||
}
|
||
SEND_ERR(ret);
|
||
|
||
ret =
|
||
cstp_puts(ws,
|
||
"X-CSTP-Smartcard-Removal-Disconnect: true\r\n");
|
||
SEND_ERR(ret);
|
||
|
||
if (WSCONFIG(ws)->is_dyndns != 0) {
|
||
ret =
|
||
cstp_puts(ws,
|
||
"X-CSTP-DynDNS: true\r\n");
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
if (WSCONFIG(ws)->rekey_time > 0) {
|
||
unsigned method;
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Rekey-Time: %u\r\n",
|
||
(unsigned)(WSCONFIG(ws)->rekey_time));
|
||
SEND_ERR(ret);
|
||
|
||
/* if the peer isn't patched for safe renegotiation, always
|
||
* require him to open a new tunnel. */
|
||
if (ws->session != NULL && gnutls_safe_renegotiation_status(ws->session) != 0)
|
||
method = WSCONFIG(ws)->rekey_method;
|
||
else
|
||
method = REKEY_METHOD_NEW_TUNNEL;
|
||
|
||
ret = cstp_printf(ws, "X-CSTP-Rekey-Method: %s\r\n",
|
||
(method ==
|
||
REKEY_METHOD_SSL) ? "ssl" : "new-tunnel");
|
||
SEND_ERR(ret);
|
||
} else {
|
||
ret = cstp_puts(ws, "X-CSTP-Rekey-Method: none\r\n");
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
if (WSCONFIG(ws)->proxy_url != NULL) {
|
||
char *url = replace_vals(ws, WSCONFIG(ws)->proxy_url);
|
||
if (url != NULL) {
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-MSIE-Proxy-Pac-URL: %s\r\n",
|
||
url);
|
||
SEND_ERR(ret);
|
||
talloc_free(url);
|
||
}
|
||
}
|
||
|
||
if (!ws->user_config->has_session_timeout_secs) {
|
||
ret = cstp_puts(ws, "X-CSTP-Lease-Duration: none\r\n"
|
||
"X-CSTP-Session-Timeout: none\r\n");
|
||
SEND_ERR(ret);
|
||
} else {
|
||
time_t expiration = ws->session_start_time + ws->user_config->session_timeout_secs;
|
||
ret = cstp_printf(ws, "X-CSTP-Lease-Duration: %u\r\n"
|
||
"X-CSTP-Session-Timeout: %u\r\n"
|
||
"X-CSTP-Session-Timeout-Remaining: %ld\r\n",
|
||
ws->user_config->session_timeout_secs,
|
||
ws->user_config->session_timeout_secs,
|
||
MAX(expiration - now, 0));
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
ret = cstp_puts(ws, "X-CSTP-Disconnected-Timeout: none\r\n"
|
||
"X-CSTP-Keep: true\r\n"
|
||
"X-CSTP-TCP-Keepalive: true\r\n"
|
||
"X-CSTP-License: accept\r\n");
|
||
SEND_ERR(ret);
|
||
|
||
for (i = 0; i < WSCONFIG(ws)->custom_header_size; i++) {
|
||
char *h = replace_vals(ws, WSCONFIG(ws)->custom_header[i]);
|
||
|
||
if (h) {
|
||
oclog(ws, LOG_INFO, "adding custom header '%s'", h);
|
||
ret =
|
||
cstp_printf(ws, "%s\r\n", h);
|
||
SEND_ERR(ret);
|
||
talloc_free(h);
|
||
}
|
||
}
|
||
|
||
|
||
/* set TCP socket options */
|
||
if (WSCONFIG(ws)->output_buffer > 0) {
|
||
t = ws->link_mtu;
|
||
t *= WSCONFIG(ws)->output_buffer;
|
||
|
||
ret =
|
||
setsockopt(ws->conn_fd, SOL_SOCKET, SO_SNDBUF, &t,
|
||
sizeof(t));
|
||
if (ret == -1)
|
||
oclog(ws, LOG_DEBUG,
|
||
"setsockopt(TCP, SO_SNDBUF) to %u, failed.", t);
|
||
}
|
||
|
||
set_socket_timeout(ws, ws->conn_fd);
|
||
set_non_block(ws->conn_fd);
|
||
set_net_priority(ws, ws->conn_fd, ws->user_config->net_priority);
|
||
set_no_delay(ws, ws->conn_fd);
|
||
|
||
if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) {
|
||
|
||
if (ws->user_config->dpd > 0) {
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-DPD: %u\r\n",
|
||
ws->user_config->dpd);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-Port: %u\r\n",
|
||
WSPCONFIG(ws)->udp_port);
|
||
SEND_ERR(ret);
|
||
|
||
if (WSCONFIG(ws)->rekey_time > 0) {
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-Rekey-Time: %u\r\n",
|
||
(unsigned)(WSCONFIG(ws)->rekey_time + 10));
|
||
SEND_ERR(ret);
|
||
|
||
/* This is our private extension */
|
||
if (WSCONFIG(ws)->rekey_method == REKEY_METHOD_SSL) {
|
||
ret =
|
||
cstp_puts(ws,
|
||
"X-DTLS-Rekey-Method: ssl\r\n");
|
||
SEND_ERR(ret);
|
||
}
|
||
}
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-Keepalive: %u\r\n",
|
||
ws->user_config->keepalive);
|
||
SEND_ERR(ret);
|
||
|
||
p = (char *)ws->buffer;
|
||
for (i = 0; i < sizeof(ws->session_id); i++) {
|
||
sprintf(p, "%.2x", (unsigned int)ws->session_id[i]);
|
||
p += 2;
|
||
}
|
||
|
||
if (ws->req.use_psk || !WSCONFIG(ws)->dtls_legacy) {
|
||
oclog(ws, LOG_INFO, "X-DTLS-App-ID: %s", ws->buffer);
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-App-ID: %s\r\n",
|
||
ws->buffer);
|
||
SEND_ERR(ret);
|
||
|
||
oclog(ws, LOG_INFO, "DTLS ciphersuite: "DTLS_PROTO_INDICATOR);
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-CipherSuite: "DTLS_PROTO_INDICATOR"\r\n");
|
||
} else if (ws->req.selected_ciphersuite) {
|
||
oclog(ws, LOG_INFO, "X-DTLS-Session-ID: %s", ws->buffer);
|
||
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-Session-ID: %s\r\n",
|
||
ws->buffer);
|
||
SEND_ERR(ret);
|
||
|
||
oclog(ws, LOG_INFO, "DTLS ciphersuite: %s",
|
||
ws->req.selected_ciphersuite->oc_name);
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS%s-CipherSuite: %s\r\n",
|
||
(ws->req.selected_ciphersuite->dtls12_mode!=0)?"12":"",
|
||
ws->req.selected_ciphersuite->oc_name);
|
||
SEND_ERR(ret);
|
||
|
||
/* only send the X-DTLS-MTU in the legacy protocol, as there
|
||
* the DTLS ciphersuite/version is negotiated and we cannot predict
|
||
* the actual tunnel size */
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-MTU: %u\r\n", DATA_MTU(ws, ws->link_mtu));
|
||
SEND_ERR(ret);
|
||
oclog(ws, LOG_INFO, "DTLS data MTU %u", DATA_MTU(ws, ws->link_mtu));
|
||
}
|
||
SEND_ERR(ret);
|
||
|
||
}
|
||
|
||
/* hack for openconnect. It uses only a single MTU value */
|
||
ret = cstp_printf(ws, "X-CSTP-Base-MTU: %u\r\n", ws->link_mtu);
|
||
SEND_ERR(ret);
|
||
oclog(ws, LOG_INFO, "Link MTU is %u bytes", ws->link_mtu);
|
||
|
||
ret = cstp_printf(ws, "X-CSTP-MTU: %u\r\n", DATA_MTU(ws, ws->link_mtu));
|
||
SEND_ERR(ret);
|
||
|
||
if (ws->buffer_size < ws->link_mtu+16) {
|
||
oclog(ws, LOG_ERR,
|
||
"buffer size is smaller than MTU (%u < %u)",
|
||
ws->buffer_size, ws->link_mtu);
|
||
goto exit;
|
||
}
|
||
|
||
data_mtu_send(ws, DATA_MTU(ws, ws->link_mtu));
|
||
|
||
if (WSCONFIG(ws)->banner) {
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Banner: %s\r\n",
|
||
WSCONFIG(ws)->banner);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
/* send any compression methods */
|
||
if (ws->dtls_selected_comp) {
|
||
oclog(ws, LOG_INFO, "selected DTLS compression method %s\n", ws->dtls_selected_comp->name);
|
||
ret =
|
||
cstp_printf(ws, "X-DTLS-Content-Encoding: %s\r\n",
|
||
ws->dtls_selected_comp->name);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
if (ws->cstp_selected_comp) {
|
||
oclog(ws, LOG_INFO, "selected CSTP compression method %s\n", ws->cstp_selected_comp->name);
|
||
ret =
|
||
cstp_printf(ws, "X-CSTP-Content-Encoding: %s\r\n",
|
||
ws->cstp_selected_comp->name);
|
||
SEND_ERR(ret);
|
||
}
|
||
|
||
ret = cstp_puts(ws, "\r\n");
|
||
SEND_ERR(ret);
|
||
|
||
ret = cstp_uncork(ws);
|
||
SEND_ERR(ret);
|
||
|
||
ret = worker_event_loop(ws);
|
||
if (ret != 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);
|
||
|
||
send_error:
|
||
oclog(ws, LOG_DEBUG, "error sending data\n");
|
||
exit_worker(ws);
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int parse_data(struct worker_st *ws, uint8_t *buf, size_t buf_size,
|
||
time_t now, unsigned is_dtls)
|
||
{
|
||
int ret, e;
|
||
uint8_t *plain;
|
||
ssize_t plain_size;
|
||
unsigned head;
|
||
|
||
if (is_dtls == 0) { /* CSTP */
|
||
plain = buf + 8;
|
||
plain_size = buf_size - 8;
|
||
head = buf[6];
|
||
} else {
|
||
plain = buf + 1;
|
||
plain_size = buf_size - 1;
|
||
head = buf[0];
|
||
}
|
||
|
||
switch (head) {
|
||
case AC_PKT_DPD_RESP:
|
||
oclog(ws, LOG_TRANSFER_DEBUG, "received DPD response");
|
||
break;
|
||
case AC_PKT_KEEPALIVE:
|
||
oclog(ws, LOG_TRANSFER_DEBUG, "received keepalive");
|
||
break;
|
||
case AC_PKT_DPD_OUT:
|
||
if (is_dtls == 0) {
|
||
buf[6] = AC_PKT_DPD_RESP;
|
||
ret = cstp_send(ws, buf, buf_size);
|
||
|
||
oclog(ws, LOG_TRANSFER_DEBUG,
|
||
"received TLS DPD; sent response (%d bytes)",
|
||
ret);
|
||
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not send data: %d", ret);
|
||
return -1;
|
||
}
|
||
} else {
|
||
/* Use DPD for MTU discovery in DTLS */
|
||
buf[0] = AC_PKT_DPD_RESP;
|
||
|
||
if (buf_size-CSTP_DTLS_OVERHEAD > DATA_MTU(ws, ws->link_mtu)) {
|
||
/* peer is doing MTU discovery */
|
||
data_mtu_set(ws, DTLS_ACTIVE(ws), buf_size-CSTP_DTLS_OVERHEAD);
|
||
}
|
||
|
||
ret = dtls_send(DTLS_ACTIVE(ws), buf, buf_size);
|
||
if (ret == GNUTLS_E_LARGE_PACKET) {
|
||
oclog(ws, LOG_TRANSFER_DEBUG,
|
||
"could not send DPD of %d bytes", (int)buf_size);
|
||
mtu_not_ok(ws, DTLS_ACTIVE(ws));
|
||
ret = dtls_send(DTLS_ACTIVE(ws), buf, 1);
|
||
}
|
||
|
||
oclog(ws, LOG_TRANSFER_DEBUG,
|
||
"received DTLS DPD; sent response (%d bytes)",
|
||
ret);
|
||
|
||
if (ret < 0) {
|
||
oclog(ws, LOG_ERR, "could not send TLS data: %s",
|
||
gnutls_strerror(ret));
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
break;
|
||
case AC_PKT_DISCONN:
|
||
oclog(ws, LOG_INFO, "received BYE packet; exiting");
|
||
/* In openconnect the BYE packet indicates an explicit
|
||
* user disconnect. In anyconnect clients it may indicate
|
||
* an intention to reconnect (e.g., because network was
|
||
* changed). We separate the error codes to ensure we do
|
||
* do not interpret the intention incorrectly (see #281). */
|
||
if (plain_size > 0 && plain[0] == 0xb0) {
|
||
exit_worker_reason(ws, REASON_USER_DISCONNECT);
|
||
} else {
|
||
if (plain_size > 0) {
|
||
oclog_hex(ws, LOG_DEBUG, "bye packet with unknown payload", plain, plain_size, 0);
|
||
return -1;
|
||
}
|
||
|
||
exit_worker_reason(ws, REASON_TEMP_DISCONNECT);
|
||
}
|
||
break;
|
||
case AC_PKT_COMPRESSED:
|
||
/* decompress */
|
||
if (is_dtls == 0) { /* CSTP */
|
||
if (ws->cstp_selected_comp == NULL) {
|
||
oclog(ws, LOG_ERR, "received compression data but no compression was negotiated");
|
||
return -1;
|
||
}
|
||
|
||
plain_size = ws->cstp_selected_comp->decompress(ws->decomp, sizeof(ws->decomp), plain, plain_size);
|
||
oclog(ws, LOG_DEBUG, "decompressed %d to %d\n", (int)buf_size-8, (int)plain_size);
|
||
} else { /* DTLS */
|
||
if (ws->dtls_selected_comp == NULL) {
|
||
oclog(ws, LOG_ERR, "received compression data but no compression was negotiated");
|
||
return -1;
|
||
}
|
||
|
||
plain_size = ws->dtls_selected_comp->decompress(ws->decomp, sizeof(ws->decomp), plain, plain_size);
|
||
oclog(ws, LOG_DEBUG, "decompressed %d to %d\n", (int)buf_size-1, (int)plain_size);
|
||
}
|
||
|
||
if (plain_size <= 0) {
|
||
oclog(ws, LOG_ERR, "decompression error %d", (int)plain_size);
|
||
return -1;
|
||
}
|
||
plain = ws->decomp;
|
||
/* fall through */
|
||
case AC_PKT_DATA:
|
||
oclog(ws, LOG_TRANSFER_DEBUG, "writing %d byte(s) to TUN",
|
||
(int)plain_size);
|
||
ret = tun_write(ws->tun_fd, plain, plain_size);
|
||
if (ret == -1) {
|
||
e = errno;
|
||
oclog(ws, LOG_ERR, "could not write data to tun: %s",
|
||
strerror(e));
|
||
return -1;
|
||
}
|
||
ws->tun_bytes_in += plain_size;
|
||
ws->last_nc_msg = now;
|
||
|
||
break;
|
||
default:
|
||
oclog(ws, LOG_DEBUG, "received unknown packet %u/size: %u",
|
||
(unsigned)head, (unsigned)buf_size);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int parse_cstp_data(struct worker_st *ws,
|
||
uint8_t * buf, size_t buf_size, time_t now)
|
||
{
|
||
int pktlen, ret;
|
||
|
||
if (buf_size < 8) {
|
||
oclog(ws, LOG_INFO,
|
||
"can't read CSTP header (only %d bytes are available)",
|
||
(int)buf_size);
|
||
return -1;
|
||
}
|
||
|
||
if (buf[0] != 'S' || buf[1] != 'T' ||
|
||
buf[2] != 'F' || buf[3] != 1 || buf[7]) {
|
||
oclog(ws, LOG_INFO, "can't recognise CSTP header");
|
||
return -1;
|
||
}
|
||
|
||
pktlen = (buf[4] << 8) + buf[5];
|
||
if (buf_size != 8 + pktlen) {
|
||
oclog(ws, LOG_INFO, "unexpected CSTP length (have %u, should be %d)",
|
||
(unsigned)pktlen, (unsigned)buf_size-8);
|
||
return -1;
|
||
}
|
||
|
||
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
|
||
* our peer wants to switch to it as the communication channel */
|
||
DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE;
|
||
}
|
||
|
||
ret = parse_data(ws, buf, buf_size, now, 0);
|
||
/* whatever we received treat it as DPD response.
|
||
* it indicates that the channel is alive */
|
||
ws->last_msg_tcp = now;
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int parse_dtls_data(struct worker_st *ws,
|
||
uint8_t * buf, size_t buf_size, time_t now)
|
||
{
|
||
int ret;
|
||
|
||
if (buf_size < 1) {
|
||
oclog(ws, LOG_INFO,
|
||
"can't read DTLS header (only %d bytes are available)",
|
||
(int)buf_size);
|
||
return -1;
|
||
}
|
||
|
||
ret =
|
||
parse_data(ws, buf, buf_size, now, 1);
|
||
ws->last_msg_udp = now;
|
||
return ret;
|
||
}
|
||
|
||
static int test_for_tcp_health_probe(struct worker_st *ws)
|
||
{
|
||
int ret;
|
||
uint8_t buffer[1];
|
||
ret = recv(ws->conn_fd, buffer, sizeof(buffer), MSG_PEEK);
|
||
|
||
// If we get back an error, assume this was a tcp health probe
|
||
if (ret > 0)
|
||
return 0;
|
||
else
|
||
return 1;
|
||
}
|
||
|
||
static void syserr_cb (const char *msg)
|
||
{
|
||
struct worker_st * ws = ev_userdata(worker_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(worker_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(worker_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)
|
||
worker_loop = ev_default_loop(EVFLAG_NOENV|EVBACKEND_EPOLL);
|
||
#else
|
||
worker_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 (worker_loop, &alarm_sig_watcher);
|
||
|
||
ev_init (&int_sig_watcher, term_sig_watcher_cb);
|
||
ev_signal_set (&int_sig_watcher, SIGINT);
|
||
ev_signal_start (worker_loop, &int_sig_watcher);
|
||
|
||
ev_init (&term_sig_watcher, term_sig_watcher_cb);
|
||
ev_signal_set (&term_sig_watcher, SIGTERM);
|
||
ev_signal_start (worker_loop, &term_sig_watcher);
|
||
|
||
ev_set_userdata (worker_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(worker_loop, &command_watcher);
|
||
|
||
ev_init(&tls_watcher, tls_watcher_cb);
|
||
ev_io_set(&tls_watcher, ws->conn_fd, EV_READ);
|
||
ev_io_start(worker_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(worker_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(worker_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(worker_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;
|
||
} |