From a5a936f4e4a46b2afe1e48759022b09ebf1157e8 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 7 Feb 2016 21:59:00 +0800 Subject: [PATCH] Implemented TCP forward function (can be used with SSH proxycommand) --- src/client.c | 247 ++++++++++++++++++++++++++++++++++++++----------- src/client.h | 5 +- src/common.c | 15 ++- src/common.h | 4 +- src/iodine.c | 54 ++++++----- src/iodined.c | 14 +-- src/server.c | 248 +++++++++++++++++++++++++++++++++++++------------- src/server.h | 2 +- src/tun.c | 7 -- src/user.c | 16 ++++ src/user.h | 5 +- 11 files changed, 453 insertions(+), 164 deletions(-) diff --git a/src/client.c b/src/client.c index ddcc7e4..0265587 100644 --- a/src/client.c +++ b/src/client.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef WINDOWS32 #include "windows.h" @@ -201,7 +202,7 @@ update_server_timeout(int handshake) if (handshake) { /* Send ping handshake to set server timeout */ - return send_ping(1, -1, 1); + return send_ping(1, -1, 1, 0); } return -1; } @@ -447,7 +448,7 @@ send_packet(char cmd, const uint8_t *data, const size_t datalen) } int -send_ping(int ping_response, int ack, int set_timeout) +send_ping(int ping_response, int ack, int set_timeout, int disconnect) { this.num_pings++; if (this.conn == CONN_DNS_NULL) { @@ -468,14 +469,16 @@ send_ping(int ping_response, int ack, int set_timeout) *(uint16_t *) (data + 7) = htons(this.downstream_timeout_ms); /* update server frag/lazy timeout, ack flag, respond with ping flag */ - data[9] = ((set_timeout & 1) << 4) | ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[9] = ((disconnect & 1) << 5) | ((set_timeout & 1) << 4) | + ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); data[10] = (this.rand_seed >> 8) & 0xff; data[11] = (this.rand_seed >> 0) & 0xff; this.rand_seed += 1; - DEBUG(3, " SEND PING: respond %d, ack %d, %s(server %ld ms, downfrag %ld ms), flags %02X", - ping_response, ack, set_timeout ? "SET " : "", this.server_timeout_ms, - this.downstream_timeout_ms, data[8]); + DEBUG(3, " SEND PING: %srespond %d, ack %d, %s(server %ld ms, downfrag %ld ms), flags %02X, wup %u, wdn %u", + disconnect ? "DISCONNECT! " : "", ping_response, ack, set_timeout ? "SET " : "", + this.server_timeout_ms, this.downstream_timeout_ms, + data[8], this.outbuf->windowsize, this.inbuf->windowsize); id = send_packet('p', data, sizeof(data)); @@ -505,7 +508,7 @@ send_next_frag() if (this.outbuf->numitems > 0) { /* There is stuff to send but we're out of sync, so send a ping * to get things back in order and keep the packets flowing */ - send_ping(1, this.next_downstream_ack, 1); + send_ping(1, this.next_downstream_ack, 1, 0); this.next_downstream_ack = -1; window_tick(this.outbuf); } @@ -889,11 +892,11 @@ handshake_waitdns(char *buf, size_t buflen, char cmd, int timeout) } int -parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) +parse_data(uint8_t *data, size_t len, fragment *f, int *immediate, int *ping) { size_t headerlen = DOWNSTREAM_HDR; - int ping; memset(f, 0, sizeof(fragment)); + int error; f->seqID = data[0]; @@ -902,12 +905,13 @@ parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) f->start = (data[2] >> 1) & 1; f->compressed = (data[2] >> 2) & 1; f->ack_other = (data[2] >> 3) & 1 ? data[1] : -1; - ping = (data[2] >> 4) & 1; + if (ping) *ping = (data[2] >> 4) & 1; + error = (data[2] >> 6) & 1; if (immediate) *immediate = (data[2] >> 5) & 1; - if (ping) { /* Handle ping stuff */ + if (ping && *ping) { /* Handle ping stuff */ static unsigned dn_start_seq, up_start_seq, dn_wsize, up_wsize; headerlen = DOWNSTREAM_PING_HDR; @@ -924,7 +928,52 @@ parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) f->len = len - headerlen; if (f->len > 0) memcpy(f->data, data + headerlen, MIN(f->len, sizeof(f->data))); - return ping; /* return ping flag (if corresponding query was a ping) */ + return error; /* return ping flag (if corresponding query was a ping) */ +} + +static ssize_t +tunnel_stdin() +{ + size_t datalen; + uint8_t out[64*1024]; + uint8_t in[64*1024]; + uint8_t *data; + ssize_t readlen; + + readlen = read(STDIN_FILENO, in, sizeof(in)); + DEBUG(4, " IN: %" L "d bytes on stdin, to be compressed: %d", readlen, this.compression_up); + if (readlen == 0) { + DEBUG(2, "EOF on stdin!"); + return -1; + } else if (readlen < 0) { + warnx("Error %d reading from stdin: %s", errno, strerror(errno)); + return -1; + } + + if (this.conn != CONN_DNS_NULL || this.compression_up) { + datalen = sizeof(out); + compress2(out, &datalen, in, readlen, 9); + data = out; + } else { + datalen = readlen; + data = in; + } + + if (this.conn == CONN_DNS_NULL) { + /* Check if outgoing buffer can hold data */ + if (window_buffer_available(this.outbuf) < (datalen / MAX_FRAGSIZE) + 1) { + DEBUG(1, " Outgoing buffer full (%" L "u/%" L "u), not adding data!", + this.outbuf->numitems, this.outbuf->length); + return -1; + } + + window_add_outgoing_data(this.outbuf, data, datalen, this.compression_up); + /* Don't send anything here to respect min. send interval */ + } else { + send_raw_data(data, datalen); + } + + return datalen; } static int @@ -974,7 +1023,7 @@ tunnel_dns() size_t datalen, buflen; uint8_t buf[64*1024], cbuf[64*1024], *data; fragment f; - int read, compressed, res, immediate; + int read, compressed, ping, immediate, error; memset(&q, 0, sizeof(q)); memset(buf, 0, sizeof(buf)); @@ -1060,27 +1109,35 @@ tunnel_dns() this.num_recv++; - /* Decode the downstream data header and fragment-ify ready for processing */ - res = parse_data(cbuf, read, &f, &immediate); - /* Mark query as received */ got_response(q.id, immediate, 0); - if ((this.debug >= 3 && res) || (this.debug >= 2 && !res)) + /* Decode the downstream data header and fragment-ify ready for processing */ + error = parse_data(cbuf, read, &f, &immediate, &ping); + + if ((this.debug >= 3 && ping) || (this.debug >= 2 && !ping)) fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %" L "u, s%d e%d\n", - res ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); + ping ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); window_ack(this.outbuf, f.ack_other); window_tick(this.outbuf); + /* respond to TCP forwarding errors by shutting down */ + if (error && this.use_remote_forward) { + f.data[f.len] = 0; + warnx("server: TCP forwarding error: %s", f.data); + this.running = 0; + return -1; + } + /* In lazy mode, we shouldn't get immediate replies to our most-recent query, only during heavy data transfer. Since this means the server doesn't have any packets to send, send one relatively fast (but not too fast, to avoid runaway ping-pong loops..) */ /* Don't send anything too soon; no data waiting from server */ if (f.len == 0) { - if (!res) + if (!ping) DEBUG(1, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); if (!this.lazymode) this.send_ping_soon = 100; @@ -1107,8 +1164,8 @@ tunnel_dns() if (datalen > 0) { if (compressed) { buflen = sizeof(buf); - if ((res = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { - DEBUG(1, "Uncompress failed (%d) for data len %" L "u: reassembled data corrupted or incomplete!", res, datalen); + if ((ping = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { + DEBUG(1, "Uncompress failed (%d) for data len %" L "u: reassembled data corrupted or incomplete!", ping, datalen); datalen = 0; } else { datalen = buflen; @@ -1118,8 +1175,12 @@ tunnel_dns() data = cbuf; } - if (datalen) - write_tun(this.tun_fd, data, datalen); + if (datalen) { + if (this.use_remote_forward) + write(STDOUT_FILENO, data, datalen); + else + write_tun(this.tun_fd, data, datalen); + } } /* Move window along after doing all data processing */ @@ -1135,7 +1196,7 @@ client_tunnel() fd_set fds; int rv; int i, use_min_send; - int sending, total; + int sending, total, maxfd; time_t last_stats; size_t sent_since_report, recv_since_report; @@ -1191,7 +1252,7 @@ client_tunnel() send_next_frag(); } else { /* Send ping if we didn't send anything yet */ - send_ping(0, this.next_downstream_ack, (this.num_pings > 20 && this.num_pings % 50 == 0)); + send_ping(0, this.next_downstream_ack, (this.num_pings > 20 && this.num_pings % 50 == 0), 0); this.next_downstream_ack = -1; } @@ -1260,12 +1321,20 @@ client_tunnel() } FD_ZERO(&fds); - if (this.conn != CONN_DNS_NULL || window_buffer_available(this.outbuf) > 16) { + maxfd = 0; + if (this.conn != CONN_DNS_NULL || window_buffer_available(this.outbuf) > 1) { /* Fill up outgoing buffer with available data if it has enough space * The windowing protocol manages data retransmits, timeouts etc. */ - FD_SET(this.tun_fd, &fds); + if (this.use_remote_forward) { + FD_SET(STDIN_FILENO, &fds); + maxfd = MAX(STDIN_FILENO, maxfd); + } else { + FD_SET(this.tun_fd, &fds); + maxfd = MAX(this.tun_fd, maxfd); + } } FD_SET(this.dns_fd, &fds); + maxfd = MAX(this.dns_fd, maxfd); DEBUG(4, "Waiting %ld ms before sending more... (min_send %d)", timeval_to_ms(&tv), use_min_send); @@ -1273,7 +1342,7 @@ client_tunnel() gettimeofday(&now, NULL); } - i = select(MAX(this.tun_fd, this.dns_fd) + 1, &fds, NULL, NULL, &tv); + i = select(maxfd + 1, &fds, NULL, NULL, &tv); if (use_min_send && i > 0) { /* enforce min_send_interval if we get interrupted by new tun data */ @@ -1299,7 +1368,7 @@ client_tunnel() if (i == 0) { /* timed out - no new packets recv'd */ } else { - if (FD_ISSET(this.tun_fd, &fds)) { + if (!this.use_remote_forward && FD_ISSET(this.tun_fd, &fds)) { if (tunnel_tun() <= 0) continue; /* Returns -1 on error OR when quickly @@ -1307,11 +1376,22 @@ client_tunnel() we need to _not_ do tunnel_dns() then. If chunk sent, sets this.send_ping_soon=0. */ } + if (this.use_remote_forward && FD_ISSET(STDIN_FILENO, &fds)) { + if (tunnel_stdin() <= 0) { + fprintf(stderr, "server: closing remote TCP forward connection\n"); + /* send ping to disconnect, don't care if it comes back */ + send_ping(0, 0, 0, 1); + this.running = 0; + break; + } + } if (FD_ISSET(this.dns_fd, &fds)) { tunnel_dns(); } } + if (this.running == 0) + break; } return rv; @@ -1369,6 +1449,7 @@ send_version(uint32_t version) static void send_login(char *login, int len) +/* Send DNS login packet. See doc/proto_xxxxxxxx.txt for details */ { uint8_t flags = 0, data[100]; int length = 17, addrlen = 0; @@ -1379,7 +1460,9 @@ send_login(char *login, int len) memcpy(data + 1, login, 16); - if (this.remote_forward_addr.ss_family != AF_UNSPEC) { + /* if remote forward address is specified and not currently connecting */ + if (this.remote_forward_connected != 2 && + this.remote_forward_addr.ss_family != AF_UNSPEC) { struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &this.remote_forward_addr; struct sockaddr_in *s = (struct sockaddr_in *) &this.remote_forward_addr; @@ -1405,6 +1488,10 @@ send_login(char *login, int len) } DEBUG(2, "Sending TCP forward login request: port %hu, length %d, addrlen %d", port, length, addrlen); + } else if (this.remote_forward_connected == 2) { + /* remote TCP forward connection in progress */ + DEBUG(2, "Sending TCP forward login/poll request to check connection status."); + flags |= (1 << 4); } data[0] = flags; @@ -1538,7 +1625,7 @@ static int handshake_login(int seed) { char in[4096], login[16], server[65], client[65], flag; - int mtu, read; + int mtu, netmask, read, numwaiting = 0; login_calculate(login, 16, this.password, seed); @@ -1550,31 +1637,84 @@ handshake_login(int seed) in[MIN(read, sizeof(in))] = 0; /* Null terminate */ if (read > 0) { - int netmask; if (strncmp("LNAK", in, 4) == 0) { fprintf(stderr, "Bad password\n"); return 1; - } else if (sscanf(in, "%c-%64[^-]-%64[^-]-%d-%d", - &flag, server, client, &mtu, &netmask) == 4) { - - server[64] = 0; - client[64] = 0; - if (tun_setip(client, server, netmask) == 0 && - tun_setmtu(mtu) == 0) { - - fprintf(stderr, "Server tunnel IP is %s\n", server); - return 0; - } else { - errx(4, "Failed to set IP and MTU"); - } - } else { - fprintf(stderr, "Received bad handshake: %.*s\n", read, in); + /* not reached */ } + flag = toupper(in[0]); + + switch (flag) { + case 'I': + if (sscanf(in, "%c-%64[^-]-%64[^-]-%d-%d", + &flag, server, client, &mtu, &netmask) == 5) { + + server[64] = 0; + client[64] = 0; + if (tun_setip(client, server, netmask) == 0 && + tun_setmtu(mtu) == 0) { + + fprintf(stderr, "Server tunnel IP is %s\n", server); + return 0; + } else { + errx(4, "Failed to set IP and MTU"); + } + } else { + goto bad_handshake; + } + break; + case 'C': + if (!this.use_remote_forward) { + goto bad_handshake; + } + + this.remote_forward_connected = 1; + fprintf(stderr, " done."); + return 0; + case 'W': + if (!this.use_remote_forward || this.remote_forward_connected == 1) { + goto bad_handshake; + } + + this.remote_forward_connected = 2; + + if (numwaiting == 0) + fprintf(stderr, "server: Opening Remote TCP forward.\n"); + else + fprintf(stderr, "%.*s", numwaiting, "..............."); + + numwaiting ++; + + /* wait a while before re-polling server, max 5 tries (14 seconds) */ + if (numwaiting > 1) + sleep(numwaiting); + + continue; + case 'E': + if (!this.use_remote_forward) + goto bad_handshake; + + char errormsg[100]; + strncpy(errormsg, in + 1, MIN(read, sizeof(errormsg))); + errormsg[99] = 0; + fprintf(stderr, "server: Remote TCP forward connection failed: %s\n", errormsg); + return 1; + default: + /* undefined flag */ + bad_handshake: + fprintf(stderr, "Received bad handshake: %.*s\n", read, in); + break; + } + } fprintf(stderr, "Retrying login...\n"); } - warnx("couldn't login to server"); + if (numwaiting != 0) + warnx("Remote TCP forward connection timed out after 5 tries."); + else + warnx("couldn't login to server"); + return 1; } @@ -2424,7 +2564,7 @@ handshake_set_timeout() for (int i = 0; this.running && i < 5; i++) { id = this.autodetect_server_timeout ? - update_server_timeout(1) : send_ping(1, -1, 1); + update_server_timeout(1) : send_ping(1, -1, 1, 0); read = handshake_waitdns(in, sizeof(in), 'P', i + 1); got_response(id, 1, 0); @@ -2464,13 +2604,11 @@ client_handshake() fprintf(stderr, "Using DNS type %s queries\n", client_get_qtype()); - r = handshake_version(&seed); - if (r) { + if ((r = handshake_version(&seed))) { return r; } - r = handshake_login(seed); - if (r) { + if ((r = handshake_login(seed))) { return r; } @@ -2479,6 +2617,9 @@ client_handshake() this.max_timeout_ms = 10000; this.compression_down = 1; this.compression_up = 1; + if (this.use_remote_forward) + fprintf(stderr, "Warning: Remote TCP forwards over Raw (UDP) mode may be unreliable.\n" + " If forwarded connections are unstable, try using '-r' to force DNS tunnelling mode.\n"); } else { if (this.raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); diff --git a/src/client.h b/src/client.h index eee25b0..eda47a0 100644 --- a/src/client.h +++ b/src/client.h @@ -47,6 +47,7 @@ struct client_instance { /* Remote TCP forwarding stuff (for -R) */ struct sockaddr_storage remote_forward_addr; int use_remote_forward; /* 0 if no forwarding used */ + int remote_forward_connected; int tun_fd; int dns_fd; @@ -160,9 +161,9 @@ void client_set_hostname_maxlen(size_t i); int client_handshake(); int client_tunnel(); -int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate); +int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate, int*); int handshake_waitdns(char *buf, size_t buflen, char cmd, int timeout); void handshake_switch_options(int lazy, int compression, char denc); -int send_ping(int ping_response, int ack, int timeout); +int send_ping(int ping_response, int ack, int timeout, int); #endif diff --git a/src/common.c b/src/common.c index 459c063..4d70ab1 100644 --- a/src/common.c +++ b/src/common.c @@ -228,9 +228,15 @@ open_dns_from_host(char *host, int port, int addr_family, int flags) } void -close_dns(int fd) +close_socket(int fd) { + if (fd <= 0) + return; +#ifdef WINDOWS32 + closesocket(fd); +#else close(fd); +#endif } void @@ -411,7 +417,7 @@ socket_set_blocking(int fd, int blocking) return flags; } - if (fcntl(fd, F_SETFL, blocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))) == -1) + if (fcntl(fd, F_SETFL, !blocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))) == -1) return errno; #endif @@ -442,11 +448,14 @@ open_tcp_nonblocking(struct sockaddr_storage *addr, char **errormsg) return -1; } + if (errormsg) + *errormsg = strerror(errno); + return fd; } int -check_tcp_status(int fd, char **error) +check_tcp_error(int fd, char **error) /* checks connected status of given socket. * returns error code. 0 if connected or EINPROGRESS if connecting */ { diff --git a/src/common.h b/src/common.h index 3064357..81716f2 100644 --- a/src/common.h +++ b/src/common.h @@ -159,10 +159,10 @@ int get_addr(char *, int, int, int, struct sockaddr_storage *); int open_dns(struct sockaddr_storage *, size_t); int open_dns_opt(struct sockaddr_storage *sockaddr, size_t sockaddr_len, int v6only); int open_dns_from_host(char *host, int port, int addr_family, int flags); -void close_dns(int); +void close_socket(int); int open_tcp_nonblocking(struct sockaddr_storage *addr, char **error); -int check_tcp_status(int fd, char **error); +int check_tcp_error(int fd, char **error); void do_chroot(char *); void do_setcon(char *); diff --git a/src/iodine.c b/src/iodine.c index 437e0e2..ac6f079 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -304,23 +304,23 @@ version() } static int -parse_tcp_forward_option() +parse_tcp_forward_option(char *optstr) { char *remote_port_str, *remote_host_str; int retval; - if (strrchr(optarg, ':')) { - remote_port_str = strrchr(optarg, ':') + 1; - if (optarg[0] == '[') { + if (strrchr(optstr, ':')) { + remote_port_str = strrchr(optstr, ':') + 1; + if (optstr[0] == '[') { /* IPv6 address enclosed in square brackets */ - remote_host_str = optarg + 1; + remote_host_str = optstr + 1; /* replace closing bracket with null terminator */ *strchr(remote_host_str, ']') = 0; this.remote_forward_addr.ss_family = AF_INET6; retval = inet_pton(AF_INET6, remote_host_str, &((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_addr); } else { - remote_host_str = optarg; + remote_host_str = optstr; /* replace separator with null terminator */ *strchr(remote_host_str, ':') = 0; this.remote_forward_addr.ss_family = AF_INET; @@ -328,14 +328,15 @@ parse_tcp_forward_option() &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr); } } else { - /* no address specified (use server localhost IPv4), optarg is port */ - remote_port_str = optarg; + /* no address specified (use server localhost IPv4), optstr is port */ + remote_port_str = optstr; this.remote_forward_addr.ss_family = AF_INET; - ((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr.s_addr = INADDR_LOOPBACK; + ((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + retval = 1; } - if (retval <= 0) { - errx(12, "Invalid remote forward address (-R)! Must be [host:]port,\n" + if (!retval) { + warnx("Invalid remote forward address (-R)! Must be [host:]port,\n" "where IPv6 addresses are enclosed in literal square brackets []."); usage(); /* not reached */ @@ -351,10 +352,10 @@ parse_tcp_forward_option() if (this.remote_forward_addr.ss_family == AF_INET) { /* set port as sockaddr_in (IPv4) */ - ((struct sockaddr_in *) &this.remote_forward_addr)->sin_port = port; + ((struct sockaddr_in *) &this.remote_forward_addr)->sin_port = htons(port); } else { /* set port in IPv6 sockaddr */ - ((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_port = port; + ((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_port = htons(port); } return port; } @@ -510,7 +511,7 @@ main(int argc, char **argv) /* Argument format: [host:]port */ if (!optarg) break; this.use_remote_forward = 1; - remote_forward_port = parse_tcp_forward_option(); + remote_forward_port = parse_tcp_forward_option(optarg); break; case OPT_NODROP: // TODO implement TCP-over-tun optimisations @@ -711,13 +712,16 @@ main(int argc, char **argv) read_password(this.password, sizeof(this.password)); } - if ((this.tun_fd = open_tun(device)) == -1) { - retval = 1; - goto cleanup1; + if (!this.use_remote_forward) { + if ((this.tun_fd = open_tun(device)) == -1) { + retval = 1; + goto cleanup; + } } + if ((this.dns_fd = open_dns_from_host(NULL, 0, nameservaddr.ss_family, AI_PASSIVE)) < 0) { retval = 1; - goto cleanup2; + goto cleanup; } #ifdef OPENBSD if (rtable > 0) @@ -739,7 +743,7 @@ main(int argc, char **argv) if (client_handshake()) { retval = 1; - goto cleanup2; + goto cleanup; } if (this.conn == CONN_RAW_UDP) { @@ -774,10 +778,14 @@ main(int argc, char **argv) client_tunnel(); -cleanup2: - close_dns(this.dns_fd); - close_tun(this.tun_fd); -cleanup1: +cleanup: + if (this.use_remote_forward) + close(STDOUT_FILENO); + close_socket(this.dns_fd); + close_socket(this.tun_fd); +#ifdef WINDOWS32 + WSACleanup(); +#endif return retval; } diff --git a/src/iodined.c b/src/iodined.c index 91942ef..53166b2 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -636,13 +636,15 @@ main(int argc, char **argv) server_tunnel(); syslog(LOG_INFO, "stopping"); - close_dns(server.bind_fd); + close_socket(server.bind_fd); cleanup: - if (server.dns_fds.v6fd >= 0) - close_dns(server.dns_fds.v6fd); - if (server.dns_fds.v4fd >= 0) - close_dns(server.dns_fds.v4fd); - close_tun(server.tun_fd); + close_socket(server.dns_fds.v6fd); + close_socket(server.dns_fds.v4fd); + close_socket(server.tun_fd); +#ifdef WINDOWS32 + WSACleanup(); +#endif + /* TODO close user TCP forward sockets */ return retval; } diff --git a/src/server.c b/src/server.c index 49e72d9..d5ec1bc 100644 --- a/src/server.c +++ b/src/server.c @@ -318,7 +318,7 @@ qmem_max_wait(int *touser, struct query **sendq) QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %" L "u of %" L "u + sending another %" L "u", q->id, u->next_upstream_ack, sent, total, sending); - send_data_or_ping(userid, q, 0, immediate); + send_data_or_ping(userid, q, 0, immediate, NULL); if (sending > 0) sending--; @@ -428,14 +428,15 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s } void -send_data_or_ping(int userid, struct query *q, int ping, int immediate) +send_data_or_ping(int userid, struct query *q, int ping, int immediate, char *tcperror) /* Sends current fragment to user, or a ping if no data available. ping: 1=force send ping (even if data available), 0=only send if no data. - immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem */ + immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem + disconnect: whether to tell user that TCP socket is closed (NULL if OK or pointer to error message) */ { uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; size_t datalen, headerlen; - fragment *f; + fragment *f = NULL; struct frag_buffer *out, *in; in = users[userid].incoming; @@ -443,7 +444,21 @@ send_data_or_ping(int userid, struct query *q, int ping, int immediate) window_tick(out); - f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); + if (!tcperror) { + f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); + } else { + /* construct fake fragment containing error message. */ + fragment fr; + f = &fr; + memset(f, 0, sizeof(fragment)); + f->ack_other = -1; + f->len = strlen(tcperror); + memcpy(f->data, tcperror, f->len); + f->data[f->len] = 0; + f->start = 1; + f->end = 1; + DEBUG(2, "Sending ping with TCP forward disconnect; error: %s", f->data); + } /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ if (!f) { @@ -464,6 +479,8 @@ send_data_or_ping(int userid, struct query *q, int ping, int immediate) /* If this is being responded to immediately (ie. not from qmem) */ pkt[2] |= (immediate & 1) << 5; + if (tcperror) + pkt[2] |= (1 << 6); if (ping) { /* set ping flag and build extra header */ @@ -556,6 +573,23 @@ user_send_data(int userid, uint8_t *indata, size_t len, int compressed) return ret; } +static int +user_send_tcp_disconnect(int userid, struct query *q, char *errormsg) +/* tell user that TCP socket has been disconnected */ +{ + users[userid].remote_forward_connected = -1; + close_socket(users[userid].remote_tcp_fd); + if (q == NULL) + q = qmem_get_next_response(userid); + if (q != NULL) { + send_data_or_ping(userid, q, 1, 0, errormsg); + users[userid].active = 0; + return 1; + } + users[userid].active = 0; + return 0; +} + static int tunnel_bind() { @@ -597,6 +631,37 @@ tunnel_bind() return 0; } +static ssize_t +tunnel_tcp(int userid) +{ + ssize_t len; + uint8_t buf[64*1024]; + char *errormsg = NULL; + + if (users[userid].remote_forward_connected != 1) { + DEBUG(2, "tunnel_tcp: user %d TCP socket not connected!", userid); + return 0; + } + + len = read(users[userid].remote_tcp_fd, buf, sizeof(buf)); + + DEBUG(5, "read %ld bytes on TCP", len); + if (len == 0) { + DEBUG(1, "EOF on TCP forward for user %d; closing connection.", userid); + errormsg = "Connection closed by remote host."; + user_send_tcp_disconnect(userid, NULL, errormsg); + return -1; + } else if (len < 0) { + errormsg = strerror(errno); + DEBUG(1, "Error %d on TCP forward for user %d: %s", errno, userid, errormsg); + user_send_tcp_disconnect(userid, NULL, errormsg); + return -1; + } + + user_send_data(userid, buf, (size_t) len, 0); + return len; +} + static int tunnel_tun() { @@ -696,7 +761,7 @@ int server_tunnel() { struct timeval tv; - fd_set fds; + fd_set read_fds, write_fds; int i; int userid; struct query *answer_now = NULL; @@ -710,31 +775,38 @@ server_tunnel() /* max wait time based on pending queries */ tv = qmem_max_wait(&userid, &answer_now); - FD_ZERO(&fds); + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); maxfd = 0; if (server.dns_fds.v4fd >= 0) { - FD_SET(server.dns_fds.v4fd, &fds); + FD_SET(server.dns_fds.v4fd, &read_fds); maxfd = MAX(server.dns_fds.v4fd, maxfd); } if (server.dns_fds.v6fd >= 0) { - FD_SET(server.dns_fds.v6fd, &fds); + FD_SET(server.dns_fds.v6fd, &read_fds); maxfd = MAX(server.dns_fds.v6fd, maxfd); } if (server.bind_fd) { /* wait for replies from real DNS */ - FD_SET(server.bind_fd, &fds); + FD_SET(server.bind_fd, &read_fds); maxfd = MAX(server.bind_fd, maxfd); } /* Don't read from tun if all users have filled outpacket queues */ if(!all_users_waiting_to_send()) { - FD_SET(server.tun_fd, &fds); + FD_SET(server.tun_fd, &read_fds); maxfd = MAX(server.tun_fd, maxfd); } - i = select(maxfd + 1, &fds, NULL, NULL, &tv); + /* add connected user TCP forward FDs to read set */ + maxfd = MAX(set_user_tcp_fds(&read_fds, 1), maxfd); + + /* add connectING user TCP FDs to write set */ + maxfd = MAX(set_user_tcp_fds(&write_fds, 2), maxfd); + + i = select(maxfd + 1, &read_fds, &write_fds, NULL, &tv); if(i < 0) { if (server.running) @@ -757,16 +829,29 @@ server_tunnel() } } } else { - if (FD_ISSET(server.tun_fd, &fds)) { + if (FD_ISSET(server.tun_fd, &read_fds)) { tunnel_tun(); } - if (FD_ISSET(server.dns_fds.v4fd, &fds)) { + + for (userid = 0; userid < created_users; userid++) { + if (FD_ISSET(users[userid].remote_tcp_fd, &read_fds) && users[userid].remoteforward_addr_len > 0) { + DEBUG(4, "tunnel_tcp called for user %d", userid); + tunnel_tcp(userid); + } else if (users[userid].remote_forward_connected == 2 && + FD_ISSET(users[userid].remote_tcp_fd, &write_fds)) { + DEBUG(2, "User %d TCP socket now writable (connection established)", userid); + users[userid].remote_forward_connected = 1; + } + } + + if (FD_ISSET(server.dns_fds.v4fd, &read_fds)) { tunnel_dns(server.dns_fds.v4fd); } - if (FD_ISSET(server.dns_fds.v6fd, &fds)) { + if (FD_ISSET(server.dns_fds.v6fd, &read_fds)) { tunnel_dns(server.dns_fds.v6fd); } - if (FD_ISSET(server.bind_fd, &fds)) { + + if (FD_ISSET(server.bind_fd, &read_fds)) { tunnel_bind(); } } @@ -781,7 +866,7 @@ handle_full_packet(int userid, uint8_t *data, size_t len, int compressed) size_t rawlen; uint8_t out[64*1024], *rawdata; struct ip *hdr; - int touser; + int touser = -1; int ret; /* Check if data needs to be uncompressed */ @@ -796,20 +881,28 @@ handle_full_packet(int userid, uint8_t *data, size_t len, int compressed) } if (ret == Z_OK) { - hdr = (struct ip*) (out + 4); - touser = find_user_by_ip(hdr->ip_dst.s_addr); - DEBUG(2, "FULL PKT: %" L "u bytes from user %d (touser %d)", len, userid, touser); - if (touser == -1) { - /* send the uncompressed packet to tun device */ - write_tun(server.tun_fd, rawdata, rawlen); - } else { - /* don't re-compress if possible */ - if (users[touser].down_compression && compressed) { - user_send_data(touser, data, len, 1); + if (users[userid].remoteforward_addr_len == 0) { + hdr = (struct ip*) (out + 4); + touser = find_user_by_ip(hdr->ip_dst.s_addr); + DEBUG(2, "FULL PKT: %" L "u bytes from user %d (touser %d)", len, userid, touser); + if (touser == -1) { + /* send the uncompressed packet to tun device */ + write_tun(server.tun_fd, rawdata, rawlen); } else { - user_send_data(touser, rawdata, rawlen, 0); + /* don't re-compress if possible */ + if (users[touser].down_compression && compressed) { + user_send_data(touser, data, len, 1); + } else { + user_send_data(touser, rawdata, rawlen, 0); + } + } + } else { + /* Write full pkt to user's remote forward TCP stream */ + if ((ret = write(users[userid].remote_tcp_fd, rawdata, rawlen)) != rawlen) { + DEBUG(2, "Write error %d on TCP socket for user %d: %s", errno, userid, strerror(errno)); } } + } else { DEBUG(2, "Discarded upstream data from user %d, uncompress() result: %d", userid, ret); } @@ -1170,6 +1263,7 @@ handle_dns_version(int dns_fd, struct query *q, uint8_t *domain, int domain_len) u->hostlen = q->fromlen; u->remote_forward_connected = 0; u->remoteforward_addr_len = 0; + u->remote_tcp_fd = 0; u->remoteforward_addr.ss_family = AF_UNSPEC; u->fragsize = 100; /* very safe */ u->conn = CONN_DNS_NULL; @@ -1250,7 +1344,7 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i { uint8_t unpacked[512], flags; char logindata[16], *tmp[2], out[512], *reason = NULL; - char *errormsg = NULL; + char *errormsg = NULL, fromaddr[100]; struct in_addr tempip; char remote_tcp, remote_isnt_localhost, use_ipv6, poll_status; //, drop_packets; int length = 17, read, addrlen, login_ok = 1; @@ -1279,8 +1373,10 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i CHECK_LEN(read, length); + strncpy(fromaddr, format_addr(&q->from, q->fromlen), 100); + DEBUG(2, "Received login request for user %d from %s", - userid, format_addr(&q->from, q->fromlen)); + userid, fromaddr); DEBUG(6, "Login: length=%d, flags=0x%02x, seed=0x%08x, hash=0x%016llx%016llx", length, flags, u->seed, *(unsigned long long *) (unpacked + 1), @@ -1289,7 +1385,7 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i if (check_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); syslog(LOG_WARNING, "rejected login request from user #%d from %s; expected source %s", - userid, format_addr(&q->from, q->fromlen), format_addr(&u->host, u->hostlen)); + userid, fromaddr, format_addr(&u->host, u->hostlen)); DEBUG(1, "Rejected login request from user %d: BADIP", userid); return; } @@ -1314,12 +1410,12 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i if (addrlen > 0) { if (use_ipv6) { addr6->sin6_family = AF_INET6; - addr6->sin6_port = port; + addr6->sin6_port = htons(port); u->remoteforward_addr_len = sizeof(*addr6); memcpy(&addr6->sin6_addr, unpacked + 19, MIN(sizeof(*addr6), addrlen)); } else { addr->sin_family = AF_INET; - addr->sin_port = port; + addr->sin_port = htons(port); u->remoteforward_addr_len = sizeof(*addr); memcpy(&addr->sin_addr, unpacked + 19, MIN(sizeof(*addr), addrlen)); } @@ -1329,8 +1425,8 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i port, login_ok ? "allowed" : "rejected"); } else { addr->sin_family = AF_INET; - addr->sin_port = port; - addr->sin_addr.s_addr = INADDR_LOOPBACK; + addr->sin_port = htons(port); + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); DEBUG(1, "User %d requested TCP connection to localhost:%hu, %s.", userid, port, login_ok ? "allowed" : "rejected"); } @@ -1347,12 +1443,11 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i write_dns(dns_fd, q, "LNAK", 4, 'T'); if (--u->authenticated >= 0) u->authenticated = -1; - char *src_ip = format_addr(&q->from, q->fromlen); int tries = abs(u->authenticated); DEBUG(1, "rejected login from user %d (%s), tries: %d, reason: %s", - userid, src_ip, tries, reason); + userid, fromaddr, tries, reason); syslog(LOG_WARNING, "rejected login request from user #%d from %s, %s; incorrect attempts: %d", - userid, src_ip, reason, tries); + userid, fromaddr, reason, tries); return; } @@ -1360,13 +1455,13 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i u->authenticated++; if (u->authenticated > 1 && !poll_status) syslog(LOG_WARNING, "duplicate login request from user #%d from %s", - userid, format_addr(&u->host, u->hostlen)); + userid, fromaddr); if (remote_tcp) { int tcp_fd; DEBUG(1, "User %d connected from %s, starting TCP connection to %s.", userid, - format_addr(&q->from, q->fromlen), format_addr(&u->remoteforward_addr, sizeof(struct sockaddr_storage))); + fromaddr, format_addr(&u->remoteforward_addr, sizeof(struct sockaddr_storage))); syslog(LOG_NOTICE, "accepted password from user #%d, connecting TCP forward", userid); /* Open socket and connect to TCP forward host:port */ @@ -1377,28 +1472,38 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i goto tcp_forward_error; } + /* connection in progress */ out[0] = 'W'; read = 1; write_dns(dns_fd, q, out, read + 1, u->downenc); - u->tcp_fd = tcp_fd; + u->remote_tcp_fd = tcp_fd; + u->remote_forward_connected = 2; /* connecting */ return; } else if (poll_status) { + /* Check TCP forward connection status and update user data */ int retval; - if ((retval = check_tcp_status(u->tcp_fd, &errormsg)) == -1) { - goto tcp_forward_error; + /* Check for connection errors */ + if ((retval = check_tcp_error(u->remote_tcp_fd, &errormsg)) != 0) { + /* if unacceptable error, tell user */ + if (retval != EINPROGRESS) + goto tcp_forward_error; } + if (retval == EINPROGRESS) + u->remote_forward_connected = 2; + read = 1; out[1] = 0; - if (retval == 0) { + /* check user TCP forward status flag, which is updated in server_tunnel + * when the file descriptor becomes writable (ie, connection established */ + if (u->remote_forward_connected == 1) { out[0] = 'C'; - u->remote_forward_connected = 1; - } else if (retval == EINPROGRESS) { + DEBUG(2, "User %d TCP forward connection established: %s", userid, errormsg); + } else if (u->remote_forward_connected == 2) { out[0] = 'W'; - } else { - goto tcp_forward_error; + DEBUG(3, "User %d TCP connection in progress: %s", userid, errormsg); } write_dns(dns_fd, q, out, read + 1, u->downenc); @@ -1412,11 +1517,11 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i tempip.s_addr = u->tun_ip; tmp[1] = strdup(inet_ntoa(tempip)); - read = snprintf(out, sizeof(out) - 1, "-%s-%s-%d-%d", + read = snprintf(out + 1, sizeof(out) - 1, "-%s-%s-%d-%d", tmp[0], tmp[1], server.mtu, server.netmask); DEBUG(1, "User %d connected from %s, tun_ip %s.", userid, - format_addr(&q->from, q->fromlen), tmp[1]); + fromaddr, tmp[1]); syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); free(tmp[1]); @@ -1618,7 +1723,7 @@ handle_dns_ping(int dns_fd, struct query *q, int userid, uint8_t *unpacked, size_t read) { int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; - int respond, set_qtimeout, set_wtimeout; + int respond, set_qtimeout, set_wtimeout, tcp_disconnect; unsigned qtimeout_ms, wtimeout_ms; CHECK_LEN(read, UPSTREAM_PING); @@ -1628,23 +1733,36 @@ handle_dns_ping(int dns_fd, struct query *q, int userid, return; /* Unpack flags/options from ping header */ - dn_ack = ((unpacked[10] >> 2) & 1) ? unpacked[1] : -1; - up_winsize = unpacked[2]; - dn_winsize = unpacked[3]; - up_seq = unpacked[4]; - dn_seq = unpacked[5]; + dn_ack = ((unpacked[9] >> 2) & 1) ? unpacked[0] : -1; + up_winsize = unpacked[1]; + dn_winsize = unpacked[2]; + up_seq = unpacked[3]; + dn_seq = unpacked[4]; /* Query timeout and window frag timeout */ - qtimeout_ms = ntohs(*(uint16_t *) (unpacked + 6)); - wtimeout_ms = ntohs(*(uint16_t *) (unpacked + 8)); - respond = unpacked[10] & 1; - set_qtimeout = (unpacked[10] >> 3) & 1; - set_wtimeout = (unpacked[10] >> 4) & 1; + qtimeout_ms = ntohs(*(uint16_t *) (unpacked + 5)); + wtimeout_ms = ntohs(*(uint16_t *) (unpacked + 7)); + respond = unpacked[9] & 1; + set_qtimeout = (unpacked[9] >> 3) & 1; + set_wtimeout = (unpacked[9] >> 4) & 1; + tcp_disconnect = (unpacked[9] >> 5) & 1; - DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, %swtime %u ms, respond %d (flags %02X)", + DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, " + "%swtime %u ms, respond %d, tcp_close %d (flags %02X)", userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, set_qtimeout ? "SET " : "", qtimeout_ms, set_wtimeout ? "SET " : "", - wtimeout_ms, respond, unpacked[10]); + wtimeout_ms, respond, tcp_disconnect, unpacked[9]); + + if (tcp_disconnect) { + /* close user's TCP forward connection and mark user as inactive */ + if (users[userid].remoteforward_addr_len == 0) { + DEBUG(1, "User %d attempted TCP disconnect but didn't request TCP forwarding!", userid); + } else { + DEBUG(1, "User %d closed remote TCP forward", userid); + close_socket(users[userid].remote_tcp_fd); + users[userid].active = 0; + } + } if (set_qtimeout) { /* update user's query timeout if timeout flag set */ @@ -1671,7 +1789,7 @@ handle_dns_ping(int dns_fd, struct query *q, int userid, users[userid].outgoing->windowsize, dn_winsize, users[userid].incoming->windowsize, up_winsize); users[userid].outgoing->windowsize = dn_winsize; users[userid].incoming->windowsize = up_winsize; - send_data_or_ping(userid, q, 1, 1); + send_data_or_ping(userid, q, 1, 1, NULL); return; } @@ -1682,7 +1800,7 @@ handle_dns_ping(int dns_fd, struct query *q, int userid, void handle_dns_data(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid) { - uint8_t unpacked[512]; + uint8_t unpacked[20]; static fragment f; size_t len; diff --git a/src/server.h b/src/server.h index 9ceade0..4e9e8f9 100644 --- a/src/server.h +++ b/src/server.h @@ -158,6 +158,6 @@ void handle_null_request(int dns_fd, struct query *q, int domain_len); void handle_ns_request(int dns_fd, struct query *q); void handle_a_request(int dns_fd, struct query *q, int fakeip); -void send_data_or_ping(int, struct query *, int, int); +void send_data_or_ping(int, struct query *, int, int, char*); #endif /* __SERVER_H__ */ diff --git a/src/tun.c b/src/tun.c index f563320..b8ed005 100644 --- a/src/tun.c +++ b/src/tun.c @@ -457,13 +457,6 @@ open_tun(const char *tun_device) #endif -void -close_tun(int tun_fd) -{ - if (tun_fd >= 0) - close(tun_fd); -} - #ifdef WINDOWS32 int write_tun(int tun_fd, uint8_t *data, size_t len) diff --git a/src/user.c b/src/user.c index e66ceb8..9606356 100644 --- a/src/user.c +++ b/src/user.c @@ -236,3 +236,19 @@ check_authenticated_user_and_ip(int userid, struct query *q, int check_ip) return 0; } + +int +set_user_tcp_fds(fd_set *fds, int conn_status) +/* Add TCP forward FDs to fd_set for users with given connection status; returns largest FD added */ +{ + int max_fd = 0; + for (int userid = 0; userid < created_users; userid ++) { + if (user_active(userid) && users[userid].remoteforward_addr_len > 0 + && users[userid].remote_forward_connected == conn_status) { + FD_SET(users[userid].remote_tcp_fd, fds); + max_fd = MAX(max_fd, users[userid].remote_tcp_fd); + } + } + return max_fd; +} + diff --git a/src/user.h b/src/user.h index 3fc471c..192cebd 100644 --- a/src/user.h +++ b/src/user.h @@ -37,8 +37,8 @@ struct tun_user { socklen_t hostlen; struct sockaddr_storage remoteforward_addr; socklen_t remoteforward_addr_len; /* 0 if no remote forwarding enabled */ - int tcp_fd; - int remote_forward_connected; + int remote_tcp_fd; + int remote_forward_connected; /* 0 if not connected, -1 if error or 1 if OK */ struct frag_buffer *incoming; struct frag_buffer *outgoing; int next_upstream_ack; @@ -67,5 +67,6 @@ int find_user_by_ip(uint32_t); int find_available_user(); void user_switch_codec(int userid, struct encoder *enc); void user_set_conn_type(int userid, enum connection c); +int set_user_tcp_fds(fd_set *fds, int); #endif