/* * OpenConnect (SSL + DTLS) VPN client * * Copyright © 2008-2015 Intel Corporation. * * Author: David Woodhouse * * 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 #include "openconnect-internal.h" #include #ifndef _WIN32 /* for setgroups() */ # include # include #endif #include #include #include #include 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; } }