123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- /*
- * OpenConnect (SSL + DTLS) VPN client
- *
- * Copyright © 2008-2015 Intel Corporation.
- *
- * Author: David Woodhouse <dwmw2@infradead.org>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1, as published by the Free Software Foundation.
- *
- * This program 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
- * Lesser General Public License for more details.
- */
- #include <config.h>
- #include "openconnect-internal.h"
- #include <unistd.h>
- #ifndef _WIN32
- /* for setgroups() */
- # include <sys/types.h>
- # include <grp.h>
- #endif
- #include <errno.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- int queue_new_packet(struct openconnect_info *vpninfo,
- struct pkt_q *q, void *buf, int len)
- {
- struct pkt *new = alloc_pkt(vpninfo, len);
- if (!new)
- return -ENOMEM;
- new->len = len;
- new->next = NULL;
- memcpy(new->data, buf, len);
- queue_packet(q, new);
- return 0;
- }
- /* This is here because it's generic and hence can't live in either of the
- tun*.c files for specific platforms */
- int tun_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable, int did_work)
- {
- struct pkt *this;
- int work_done = 0;
- if (readable && read_fd_monitored(vpninfo, tun)) {
- struct pkt *out_pkt = vpninfo->tun_pkt;
- while (1) {
- int len = vpninfo->ip_info.mtu;
- if (!out_pkt) {
- out_pkt = alloc_pkt(vpninfo, len + vpninfo->pkt_trailer);
- if (!out_pkt) {
- vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
- break;
- }
- out_pkt->len = len;
- }
- if (os_read_tun(vpninfo, out_pkt))
- break;
- vpninfo->stats.tx_pkts++;
- vpninfo->stats.tx_bytes += out_pkt->len;
- work_done = 1;
- if (queue_packet(&vpninfo->outgoing_queue, out_pkt) +
- vpninfo->tcp_control_queue.count >= vpninfo->max_qlen) {
- out_pkt = NULL;
- unmonitor_read_fd(vpninfo, tun);
- break;
- }
- out_pkt = NULL;
- }
- vpninfo->tun_pkt = out_pkt;
- } else if (vpninfo->outgoing_queue.count + vpninfo->tcp_control_queue.count < vpninfo->max_qlen) {
- monitor_read_fd(vpninfo, tun);
- }
- while ((this = dequeue_packet(&vpninfo->incoming_queue))) {
- unmonitor_write_fd(vpninfo, tun);
- if (os_write_tun(vpninfo, this)) {
- requeue_packet(&vpninfo->incoming_queue, this);
- break;
- }
- vpninfo->stats.rx_pkts++;
- vpninfo->stats.rx_bytes += this->len;
- free_pkt(vpninfo, this);
- }
- /* Work is not done if we just got rid of packets off the queue */
- return work_done;
- }
- static int setup_tun_device(struct openconnect_info *vpninfo)
- {
- int ret;
- if (vpninfo->setup_tun) {
- vpninfo->setup_tun(vpninfo->cbdata);
- if (tun_is_up(vpninfo))
- return 0;
- }
- #ifndef _WIN32
- if (vpninfo->use_tun_script) {
- ret = openconnect_setup_tun_script(vpninfo, vpninfo->vpnc_script);
- if (ret) {
- vpn_progress(vpninfo, PRG_ERR, _("Set up tun script failed\n"));
- return ret;
- }
- } else
- #endif
- ret = openconnect_setup_tun_device(vpninfo, vpninfo->vpnc_script, vpninfo->ifname);
- if (ret) {
- vpn_progress(vpninfo, PRG_ERR, _("Set up tun device failed\n"));
- if (!vpninfo->quit_reason)
- vpninfo->quit_reason = "Set up tun device failed";
- return ret;
- }
- #if !defined(_WIN32) && !defined(__native_client__)
- if (vpninfo->uid != getuid()) {
- if (setgid(vpninfo->gid)) {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to set gid %ld: %s\n"),
- (long)vpninfo->gid, strerror(errno));
- return -EPERM;
- }
- if (setgroups(1, &vpninfo->gid)) {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to set groups to %ld: %s\n"),
- (long)vpninfo->gid, strerror(errno));
- return -EPERM;
- }
- if (setuid(vpninfo->uid)) {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to set uid %ld: %s\n"),
- (long)vpninfo->uid, strerror(errno));
- return -EPERM;
- }
- }
- #endif
- return 0;
- }
- /* Return value:
- * = 0, when successfully paused (may call again)
- * = -EINTR, if aborted locally via OC_CMD_CANCEL
- * = -ECONNABORTED, if aborted locally via OC_CMD_DETACH
- * = -EPIPE, if the remote end explicitly terminated the session
- * = -EPERM, if the gateway sent 401 Unauthorized (cookie expired)
- * < 0, for any other error
- */
- int openconnect_mainloop(struct openconnect_info *vpninfo,
- int reconnect_timeout,
- int reconnect_interval)
- {
- int ret = 0;
- int tun_r = 1, udp_r = 1, tcp_r = 1;
- #ifdef HAVE_VHOST
- int vhost_r = 0;
- #endif
- vpninfo->reconnect_timeout = reconnect_timeout;
- vpninfo->reconnect_interval = reconnect_interval;
- if (vpninfo->cmd_fd != -1) {
- monitor_fd_new(vpninfo, cmd);
- monitor_read_fd(vpninfo, cmd);
- }
- while (!vpninfo->quit_reason) {
- int did_work = 0;
- int timeout;
- #ifdef _WIN32
- HANDLE events[4];
- int nr_events = 0;
- #else
- struct timeval tv;
- fd_set rfds, wfds, efds;
- #endif
- /* If tun is not up, loop more often to detect
- * a DTLS timeout (due to a firewall block) as soon. */
- if (tun_is_up(vpninfo))
- timeout = INT_MAX;
- else
- timeout = 1000;
- if (!tun_is_up(vpninfo)) {
- if (vpninfo->delay_tunnel_reason) {
- vpn_progress(vpninfo, PRG_TRACE, _("Delaying tunnel with reason: %s\n"),
- vpninfo->delay_tunnel_reason);
- /* XX: don't let this spin forever */
- vpninfo->delay_tunnel_reason = NULL;
- } else {
- /* No DTLS, or DTLS failed; setup TUN device unconditionally */
- ret = setup_tun_device(vpninfo);
- if (ret)
- break;
- }
- }
- if (vpninfo->dtls_state > DTLS_DISABLED) {
- ret = vpninfo->proto->udp_mainloop(vpninfo, &timeout, udp_r);
- if (vpninfo->quit_reason)
- break;
- did_work += ret;
- }
- ret = vpninfo->proto->tcp_mainloop(vpninfo, &timeout, tcp_r);
- if (vpninfo->quit_reason)
- break;
- did_work += ret;
- /* Tun must be last because it will set/clear its bit
- in the select_rfds according to the queue length */
- if (!tun_is_up(vpninfo)) {
- struct pkt *this;
- /* no tun yet; clear any queued packets */
- while ((this = dequeue_packet(&vpninfo->incoming_queue)))
- free_pkt(vpninfo, this);
- #ifdef HAVE_VHOST
- } else if (vpninfo->vhost_fd != -1) {
- did_work += vhost_tun_mainloop(vpninfo, &timeout, vhost_r, did_work);
- /* If it returns zero *then* it will have read the eventfd
- * and there's no need to do so again until we poll again. */
- if (!did_work)
- vhost_r = 0;
- #endif
- } else {
- did_work += tun_mainloop(vpninfo, &timeout, tun_r, did_work);
- }
- if (vpninfo->quit_reason)
- break;
- if (vpninfo->need_poll_cmd_fd)
- poll_cmd_fd(vpninfo, 0);
- if (vpninfo->got_cancel_cmd) {
- if (vpninfo->delay_close != NO_DELAY_CLOSE) {
- if (vpninfo->delay_close == DELAY_CLOSE_IMMEDIATE_CALLBACK) {
- vpn_progress(vpninfo, PRG_TRACE, _("Delaying cancel (immediate callback).\n"));
- did_work++;
- } else
- vpn_progress(vpninfo, PRG_TRACE, _("Delaying cancel.\n"));
- /* XX: don't let this spin forever */
- vpninfo->delay_close = NO_DELAY_CLOSE;
- } else if (vpninfo->cancel_type == OC_CMD_CANCEL) {
- vpninfo->quit_reason = "Aborted by caller";
- vpninfo->got_cancel_cmd = 0;
- ret = -EINTR;
- break;
- } else {
- vpninfo->got_cancel_cmd = 0;
- ret = -ECONNABORTED;
- break;
- }
- }
- if (vpninfo->got_pause_cmd) {
- if (vpninfo->delay_close != NO_DELAY_CLOSE) {
- /* XX: don't let this spin forever */
- if (vpninfo->delay_close == DELAY_CLOSE_IMMEDIATE_CALLBACK) {
- vpn_progress(vpninfo, PRG_TRACE, _("Delaying pause (immediate callback).\n"));
- did_work++;
- } else
- vpn_progress(vpninfo, PRG_TRACE, _("Delaying pause.\n"));
- /* XX: don't let this spin forever */
- vpninfo->delay_close = NO_DELAY_CLOSE;
- } else {
- /* close all connections and wait for the user to call
- openconnect_mainloop() again */
- openconnect_close_https(vpninfo, 0);
- if (vpninfo->dtls_state > DTLS_DISABLED) {
- vpninfo->proto->udp_close(vpninfo);
- vpninfo->new_dtls_started = 0;
- }
- vpninfo->got_pause_cmd = 0;
- vpn_progress(vpninfo, PRG_INFO, _("Caller paused the connection\n"));
- if (vpninfo->cmd_fd >= 0)
- unmonitor_fd(vpninfo, cmd);
- return 0;
- }
- }
- if (did_work)
- continue;
- vpn_progress(vpninfo, PRG_TRACE,
- _("No work to do; sleeping for %d ms...\n"), timeout);
- #ifdef _WIN32
- if (vpninfo->dtls_monitored) {
- WSAEventSelect(vpninfo->dtls_fd, vpninfo->dtls_event, vpninfo->dtls_monitored);
- events[nr_events++] = vpninfo->dtls_event;
- }
- if (vpninfo->ssl_monitored) {
- WSAEventSelect(vpninfo->ssl_fd, vpninfo->ssl_event, vpninfo->ssl_monitored);
- events[nr_events++] = vpninfo->ssl_event;
- }
- if (vpninfo->cmd_monitored) {
- WSAEventSelect(vpninfo->cmd_fd, vpninfo->cmd_event, vpninfo->cmd_monitored);
- events[nr_events++] = vpninfo->cmd_event;
- }
- if (vpninfo->tun_monitored) {
- events[nr_events++] = vpninfo->tun_rd_overlap.hEvent;
- }
- if (WaitForMultipleObjects(nr_events, events, FALSE, timeout) == WAIT_FAILED) {
- char *errstr = openconnect__win32_strerror(GetLastError());
- vpn_progress(vpninfo, PRG_ERR,
- _("WaitForMultipleObjects failed: %s\n"),
- errstr);
- free(errstr);
- }
- #else
- #ifdef HAVE_EPOLL
- if (vpninfo->epoll_fd >= 0) {
- struct epoll_event evs[5];
- /* During busy periods, monitor_read_fd() and unmonitor_read_fd()
- * may get called multiple times as we go round and round the
- * loop and queues get full then have space again. In the past
- * with the select() loop, that was only a bitflip in the fd_set
- * and didn't cost much. With epoll() it's actually a system
- * call, so don't do it every time. Wait until we're about to
- * sleep, and *then* ensure that we call epoll_ctl() to sync the
- * set of events that we care about, if it's changed. */
- if (vpninfo->epoll_update) {
- update_epoll_fd(vpninfo, tun);
- update_epoll_fd(vpninfo, ssl);
- update_epoll_fd(vpninfo, cmd);
- update_epoll_fd(vpninfo, dtls);
- #ifdef HAVE_VHOST
- update_epoll_fd(vpninfo, vhost_call);
- #endif
- }
- tun_r = udp_r = tcp_r = 0;
- #ifdef HAVE_VHOST
- vhost_r = 0;
- #endif
- int nfds = epoll_wait(vpninfo->epoll_fd, evs, 5, timeout);
- if (nfds < 0) {
- if (errno != EINTR) {
- ret = -errno;
- vpn_perror(vpninfo, _("Failed epoll_wait() in mainloop"));
- break;
- }
- nfds = 0;
- }
- while (nfds--) {
- if (evs[nfds].events & (EPOLLIN|EPOLLERR)) {
- if (evs[nfds].data.fd == vpninfo->tun_fd)
- tun_r = 1;
- else if (evs[nfds].data.fd == vpninfo->ssl_fd)
- tcp_r = 1;
- else if (evs[nfds].data.fd == vpninfo->dtls_fd)
- udp_r = 1;
- #ifdef HAVE_VHOST
- else if (evs[nfds].data.fd == vpninfo->vhost_call_fd)
- vhost_r = 1;
- #endif
- }
- }
- continue;
- }
- #endif
- memcpy(&rfds, &vpninfo->_select_rfds, sizeof(rfds));
- memcpy(&wfds, &vpninfo->_select_wfds, sizeof(wfds));
- memcpy(&efds, &vpninfo->_select_efds, sizeof(efds));
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
- if (select(vpninfo->_select_nfds, &rfds, &wfds, &efds, &tv) < 0 &&
- errno != EINTR) {
- ret = -errno;
- vpn_perror(vpninfo, _("Failed select() in mainloop"));
- break;
- }
- #ifdef HAVE_VHOST
- if (vpninfo->vhost_call_fd >= 0)
- vhost_r = FD_ISSET(vpninfo->vhost_call_fd, &rfds);
- #endif
- if (vpninfo->tun_fd >= 0)
- tun_r = FD_ISSET(vpninfo->tun_fd, &rfds);
- if (vpninfo->dtls_fd >= 0)
- udp_r = FD_ISSET(vpninfo->dtls_fd, &rfds);
- if (vpninfo->ssl_fd >= 0)
- tcp_r = FD_ISSET(vpninfo->ssl_fd, &rfds);
- #endif
- }
- if (vpninfo->quit_reason && vpninfo->proto->vpn_close_session)
- vpninfo->proto->vpn_close_session(vpninfo, vpninfo->quit_reason);
- if (tun_is_up(vpninfo))
- os_shutdown_tun(vpninfo);
- if (vpninfo->cmd_fd >= 0)
- unmonitor_fd(vpninfo, cmd);
- return ret < 0 ? ret : -EIO;
- }
- int ka_check_deadline(int *timeout, time_t now, time_t due)
- {
- if (now >= due)
- return 1;
- if (*timeout > (due - now) * 1000)
- *timeout = (due - now) * 1000;
- return 0;
- }
- /* Called when the socket is unwritable, to get the deadline for DPD.
- Returns 1 if DPD deadline has already arrived. */
- int ka_stalled_action(struct keepalive_info *ka, int *timeout)
- {
- time_t now = time(NULL);
- /* We only support the new-tunnel rekey method for now. */
- if (ka->rekey_method != REKEY_NONE &&
- ka_check_deadline(timeout, now, ka->last_rekey + ka->rekey)) {
- ka->last_rekey = now;
- return KA_REKEY;
- }
- if (ka->dpd &&
- ka_check_deadline(timeout, now, ka->last_rx + (2 * ka->dpd)))
- return KA_DPD_DEAD;
- return KA_NONE;
- }
- int keepalive_action(struct keepalive_info *ka, int *timeout)
- {
- time_t now = time(NULL);
- if (ka->rekey_method != REKEY_NONE &&
- ka_check_deadline(timeout, now, ka->last_rekey + ka->rekey)) {
- ka->last_rekey = now;
- return KA_REKEY;
- }
- /* DPD is bidirectional -- PKT 3 out, PKT 4 back */
- if (ka->dpd) {
- time_t due = ka->last_rx + ka->dpd;
- time_t overdue = ka->last_rx + (2 * ka->dpd);
- /* Peer didn't respond */
- if (now > overdue)
- return KA_DPD_DEAD;
- /* If we already have DPD outstanding, don't flood. Repeat by
- all means, but only after half the DPD period. */
- if (ka->last_dpd > ka->last_rx)
- due = ka->last_dpd + ka->dpd / 2;
- /* We haven't seen a packet from this host for $DPD seconds.
- Prod it to see if it's still alive */
- if (ka_check_deadline(timeout, now, due)) {
- ka->last_dpd = now;
- return KA_DPD;
- }
- }
- /* Keepalive is just client -> server.
- If we haven't sent anything for $KEEPALIVE seconds, send a
- dummy packet (which the server will discard) */
- if (ka->keepalive &&
- ka_check_deadline(timeout, now, ka->last_tx + ka->keepalive))
- return KA_KEEPALIVE;
- return KA_NONE;
- }
- int trojan_check_deadline(struct openconnect_info *vpninfo, int *timeout)
- {
- time_t now = time(NULL);
- if (vpninfo->trojan_interval &&
- ka_check_deadline(timeout, now,
- vpninfo->last_trojan + vpninfo->trojan_interval)) {
- vpninfo->last_trojan = now;
- return 1;
- } else {
- return 0;
- }
- }
|