Implemented TCP forward function (can be used with SSH proxycommand)

This commit is contained in:
frekky 2016-02-07 21:59:00 +08:00
parent d46766bcc9
commit a5a936f4e4
11 changed files with 453 additions and 164 deletions

View File

@ -29,6 +29,7 @@
#include <zlib.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
#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,9 +1175,13 @@ tunnel_dns()
data = cbuf;
}
if (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 */
window_tick(this.inbuf);
@ -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. */
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,12 +1637,17 @@ 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) {
/* 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;
@ -1568,13 +1660,61 @@ handshake_login(int seed)
errx(4, "Failed to set IP and MTU");
}
} else {
fprintf(stderr, "Received bad handshake: %.*s\n", read, in);
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");
}
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");

View File

@ -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

View File

@ -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 */
{

View File

@ -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 *);

View File

@ -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.use_remote_forward) {
if ((this.tun_fd = open_tun(device)) == -1) {
retval = 1;
goto cleanup1;
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;
}

View File

@ -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;
}

View File

@ -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);
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,6 +881,7 @@ handle_full_packet(int userid, uint8_t *data, size_t len, int compressed)
}
if (ret == Z_OK) {
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);
@ -810,6 +896,13 @@ handle_full_packet(int userid, uint8_t *data, size_t len, int compressed)
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) {
/* 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;

View File

@ -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__ */

View File

@ -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)

View File

@ -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;
}

View File

@ -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