--- gvpe/src/vpn_tcp.C 2003/04/05 02:32:40 1.1 +++ gvpe/src/vpn_tcp.C 2007/12/02 00:39:06 1.21 @@ -1,7 +1,10 @@ /* vpn_tcp.C -- handle the tcp part of the protocol. + Copyright (C) 2003-2007 Marc Lehmann - This program is free software; you can redistribute it and/or modify + This file is part of GVPE. + + GVPE 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. @@ -12,39 +15,40 @@ 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, write to the Free Software - Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + along with gvpe; if not, write to the Free Software + Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #if ENABLE_TCP -// tcp processing is extremely ugly, since the vpe protocol is simply +// tcp processing is extremely ugly, since the gvpe protocol is simply // designed for unreliable datagram networks. tcp is implemented by // multiplexing packets over tcp. errors are completely ignored, as we -// rely on the higher level protocol to time out and reconnect. +// rely on the higher level layers to time out and reconnect. #include #include #include -#include #include -#include #include -#include #include #include #include +#include #include -#include -#include -#include + +#include "netcompat.h" #include "vpn.h" +#if ENABLE_HTTP_PROXY +# include "conf.h" +#endif + struct tcp_connection; struct lt_sockinfo @@ -56,59 +60,70 @@ }; struct tcp_si_map : public map { - void cleaner_cb (time_watcher &w); time_watcher cleaner; + void cleaner_cb (ev::timer &w, int revents); ev::timer cleaner; tcp_si_map () - : cleaner(this, &tcp_si_map::cleaner_cb) - { - cleaner.start (0); - } + : cleaner(this, &tcp_si_map::cleaner_cb) + { + cleaner.start (::conf.keepalive / 2, ::conf.keepalive / 2); + } + } tcp_si; -struct tcp_connection : io_watcher { +struct tcp_connection : ev::io { + int tos; tstamp last_activity; const sockinfo si; vpn &v; bool active; // this connection has been actively established - enum { ERROR, IDLE, CONNECTING, ESTABLISHED } state; + enum { ERROR, IDLE, CONNECTING, CONNECTING_PROXY, ESTABLISHED } state; vpn_packet *r_pkt; u32 r_len, r_ofs; - void tcpv4_ev (io_watcher &w, short revents); + vpn_packet *w_pkt; + u32 w_len, w_ofs; - void send_packet (vpn_packet *pkt, int tos); +#if ENABLE_HTTP_PROXY + char *proxy_req; + int proxy_req_len; +#endif + + void tcpv4_ev (ev::io &w, int revents); + + bool send_packet (vpn_packet *pkt, int tos); + bool write_packet (); void error (); // abort conenction && cleanup operator tcp_si_map::value_type() - { - return tcp_si_map::value_type (&si, this); - } + { + return tcp_si_map::value_type (&si, this); + } tcp_connection (int fd_, const sockinfo &si_, vpn &v_); ~tcp_connection (); }; -void tcp_si_map::cleaner_cb (time_watcher &w) +void tcp_si_map::cleaner_cb (ev::timer &w, int revents) { - w.at = NOW + 600; - tstamp to = NOW - ::conf.keepalive - 30 - 60; + tstamp to = ev_now () - ::conf.keepalive - 30 - 60; for (iterator i = begin (); i != end(); ) if (i->second->last_activity >= to) ++i; else { + delete i->second; erase (i); i = begin (); } } void -vpn::tcpv4_ev (io_watcher &w, short revents) +vpn::tcpv4_ev (ev::io &w, int revents) { - if (revents & (POLLIN | POLLERR)) + if (revents & EV_READ) { struct sockaddr_in sa; socklen_t sa_len = sizeof (sa); @@ -118,19 +133,20 @@ if (fd >= 0) { + fcntl (fd, F_SETFL, O_NONBLOCK); + fcntl (fd, F_SETFD, FD_CLOEXEC); + sockinfo si(sa, PROT_TCPv4); slog (L_DEBUG, _("%s: accepted tcp connection"), (const char *)si);//D - fcntl (fd, F_SETFL, O_NONBLOCK); - tcp_connection *i = new tcp_connection (fd, si, *this); tcp_si.insert (*i); } } } -void +bool vpn::send_tcpv4_packet (vpn_packet *pkt, const sockinfo &si, int tos) { tcp_si_map::iterator info = tcp_si.find (&si); @@ -145,89 +161,195 @@ else i = info->second; - i->send_packet (pkt, tos); + return i->send_packet (pkt, tos); } -void tcp_connection::error () +bool +tcp_connection::write_packet () { - if (fd >= 0) + ssize_t len; + + if (w_ofs < 2) { - close (fd); - fd = -1; + u16 plen = htons (w_pkt->len); + + iovec vec[2]; + //TODO: char* is the right type? hardly... + vec[0].iov_base = (char *)((u8 *)&plen) + w_ofs; + vec[0].iov_len = 2 - w_ofs; + vec[1].iov_base = (char *)&((*w_pkt)[0]); + vec[1].iov_len = w_len - 2; + + len = writev (fd, vec, 2); } + else + len = write (fd, &((*w_pkt)[w_ofs - 2]), w_len); - delete r_pkt; - r_pkt = 0; + if (len > 0) + { + w_ofs += len; + w_len -= len; - stop (); - state = active ? IDLE : ERROR; + return w_len == 0; + } + else if (len < 0 && (errno == EAGAIN || errno == EINTR)) + return false; + else + { + error (); + return false; + } } void -tcp_connection::tcpv4_ev (io_watcher &w, short revents) +tcp_connection::tcpv4_ev (ev::io &w, int revents) { - last_activity = NOW; + last_activity = ev_now (); - if (revents & (POLLERR | POLLHUP)) - error (); - else if (revents & POLLOUT && state == CONNECTING) + if (revents & EV_WRITE) { - state = ESTABLISHED; - set (POLLIN); - } + if (state == CONNECTING) + { + state = ESTABLISHED; + set (EV_READ); +#if ENABLE_HTTP_PROXY + if (::conf.proxy_host && ::conf.proxy_port) + { + state = CONNECTING_PROXY; - else if (revents & POLLIN) - { - for (;;) + if (write (fd, proxy_req, proxy_req_len) == 0) + { + error (); + return; + } + + free (proxy_req); proxy_req = 0; + } +#endif + } + else if (state == ESTABLISHED) { - if (!r_pkt) + if (w_pkt) { - r_pkt = new vpn_packet; - r_ofs = 0; - r_len = 2; // header + if (write_packet ()) + { + delete w_pkt; w_pkt = 0; + + set (EV_READ); + } } + else + set (EV_READ); + } + else + set (EV_READ); + } - ssize_t len = read (fd, &((*r_pkt)[r_ofs < 2 ? r_ofs : r_ofs - 2]), r_len); + if (revents & EV_READ) + { + if (state == ESTABLISHED) + for (;;) + { + if (!r_pkt) + { + r_pkt = new vpn_packet; + r_ofs = 0; + r_len = 2; // header + } + + ssize_t len = read (fd, &((*r_pkt)[r_ofs < 2 ? r_ofs : r_ofs - 2]), r_len); + + if (len > 0) + { + r_len -= len; + r_ofs += len; + + if (r_len == 0) + { + if (r_ofs == 2) + { + r_len = ntohs (*(u16 *)&((*r_pkt)[0])); + r_pkt->len = r_len; + + if (r_len > 0 && r_len < MAXSIZE) + continue; + } + else + { + v.recv_vpn_packet (r_pkt, si); + delete r_pkt; + r_pkt = 0; + + continue; + } + } + else + break; + } + else if (len < 0 && (errno == EINTR || errno == EAGAIN)) + break; + + // len == 0 <-> EOF + error (); + break; + } +#if ENABLE_HTTP_PROXY + else if (state == CONNECTING_PROXY) + { + fcntl (fd, F_SETFL, 0); + char r[1024]; + int i; + bool emptyline = false; - if (len > 0) + // we do a blocking read of the response, to hell with it + for (i = 0; i < 1023; i++) { - r_len -= len; - r_ofs += len; + int l = read (fd, &r[i], 1); - if (r_len == 0) + if (l <= 0) { - if (r_ofs == 2) - { - r_len = ntohs (*(u16 *)&((*r_pkt)[0])); - r_pkt->len = r_len; + error (); + return; + } - if (r_len > 0 && r_len < MAXSIZE) - continue; - } + if (r[i] == '\012') + { + if (emptyline) + break; else - { - v.recv_vpn_packet (r_pkt, si); - delete r_pkt; - r_pkt = 0; - - continue; - } + emptyline = true; } - + else if (r[i] != '\015') + emptyline = false; } - else if (len < 0 && (errno == EINTR || errno == EAGAIN)) - return; - error (); - return; + fcntl (fd, F_SETFL, O_NONBLOCK); + + if (i < 12) + { + slog (L_ERR, _("(%s): unable to do proxy-forwarding, short response"), + (const char *)si); + error (); + } + else if (r[0] != 'H' || r[1] != 'T' || r[2] != 'T' || r[3] != 'P' || r[4] != '/' + || r[5] != '1' // http-major + || r[9] != '2') // response + { + slog (L_ERR, _("(%s): malformed or unexpected proxy response (%.12s)"), + (const char *)si, r); + error (); + } + else + state = ESTABLISHED; } +#endif } } -void +bool tcp_connection::send_packet (vpn_packet *pkt, int tos) { - last_activity = NOW; + last_activity = ev_now (); if (state == IDLE) { @@ -236,13 +358,46 @@ if (fd >= 0) { - fcntl (fd, F_SETFL, O_NONBLOCK); + const sockinfo *csi = &si; + +#if ENABLE_HTTP_PROXY + sockinfo psi; + + if (::conf.proxy_host && ::conf.proxy_port) + { + psi.set (::conf.proxy_host, ::conf.proxy_port, PROT_TCPv4); + + if (psi.valid ()) + { + csi = ψ + + proxy_req_len = asprintf (&proxy_req, + "CONNECT %s:%d HTTP/1.0\015\012" + "%s%s%s" // optional proxy-auth + "\015\012", + si.ntoa (), + ntohs (si.port), + ::conf.proxy_auth ? "Proxy-Authorization: Basic " : "", + ::conf.proxy_auth ? ::conf.proxy_auth : "", + ::conf.proxy_auth ? "\015\012" : ""); + + } + else + slog (L_ERR, _("unable to resolve http proxy hostname '%s', trying direct"), + ::conf.proxy_host); + } +#endif - if (connect (fd, si.sav4 (), si.salenv4 ()) >= 0 + fcntl (fd, F_SETFL, O_NONBLOCK); + + if (connect (fd, csi->sav4 (), csi->salenv4 ()) >= 0 || errno == EINPROGRESS) { + fcntl (fd, F_SETFL, O_NONBLOCK); + fcntl (fd, F_SETFD, FD_CLOEXEC); + state = CONNECTING; - start (fd, POLLOUT); + start (fd, EV_WRITE); } else close (fd); @@ -250,33 +405,70 @@ } else if (state == ESTABLISHED) { - // how this maps to the underlying tcp packet we don't know - // and we don't care. at least we tried ;) - setsockopt (fd, SOL_IP, IP_TOS, &tos, sizeof tos); - - // we use none of the advantages of tcp; if an error occurs, just drop - // (this happens when a tcp connection gets stuck, too, which might not be - // the wisest thing to do.. either drop packet (too late) or make sure - // it gets delivered) - u16 len = htons (pkt->len); + // drop packet if the tcp write buffer is full. this *is* the + // right thing to do, not using tcp *is* the right thing to do. + if (!w_pkt) + { + // how this maps to the underlying tcp packets we don't know + // and we don't care. at least we tried ;) +#if defined(SOL_IP) && defined(IP_TOS) + if (tos != this->tos) + { + this->tos = tos; + setsockopt (fd, SOL_IP, IP_TOS, &tos, sizeof tos); + } +#endif - iovec vec[2]; - vec[0].iov_base = &len; - vec[0].iov_len = sizeof len; - vec[1].iov_base = &((*pkt)[0]); - vec[1].iov_len = pkt->len; - - if (sizeof (u16) + pkt->len != writev (fd, vec, 2)) - error (); + w_pkt = pkt; + w_ofs = 0; + w_len = pkt->len + 2; // length + size header + + if (write_packet ()) + w_pkt = 0; + else + { + w_pkt = new vpn_packet; + w_pkt->set (*pkt); + + set (EV_READ | EV_WRITE); + } + } + } + + return state != ERROR; +} + +void tcp_connection::error () +{ + stop (); + + if (fd >= 0) + { + close (fd); + tos = -1; + fd = -1; } + + delete r_pkt; r_pkt = 0; + delete w_pkt; w_pkt = 0; +#if ENABLE_HTTP_PROXY + free (proxy_req); proxy_req = 0; +#endif + + state = active ? IDLE : ERROR; } tcp_connection::tcp_connection (int fd_, const sockinfo &si_, vpn &v_) -: v(v_), si(si_), io_watcher(this, &tcp_connection::tcpv4_ev) +: v(v_), si(si_), ev::io(this, &tcp_connection::tcpv4_ev) { - last_activity = NOW; + last_activity = ev_now (); r_pkt = 0; + w_pkt = 0; + tos = -1; fd = fd_; +#if ENABLE_HTTP_PROXY + proxy_req = 0; +#endif if (fd < 0) { @@ -287,7 +479,7 @@ { active = false; state = ESTABLISHED; - start (fd, POLLIN); + start (fd, EV_READ); } }