From 3e7cf55fe4ef091b3b492f31e83fab5d0637860a Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 14 Aug 2015 22:07:48 +0800 Subject: [PATCH 001/113] Added option for multiple nameservers, used in round-robin --- src/client.c | 34 +++++++++++++++++------ src/client.h | 3 +- src/common.h | 4 +++ src/iodine.c | 78 +++++++++++++++++++++++++++++++++------------------- 4 files changed, 81 insertions(+), 38 deletions(-) diff --git a/src/client.c b/src/client.c index 38b573b..5b04da6 100644 --- a/src/client.c +++ b/src/client.c @@ -62,8 +62,10 @@ static void handshake_lazyoff(int dns_fd); static int running; static const char *password; -static struct sockaddr_storage nameserv; -static int nameserv_len; +/* Nameserver/domain info */ +static struct sockaddr_storage *nameserv_addrs; +static int nameserv_addrs_len; +static int current_nameserver; static struct sockaddr_storage raw_serv; static int raw_serv_len; static const char *topdomain; @@ -137,6 +139,8 @@ client_init() inpkt.len = 0; inpkt.seqno = 0; inpkt.fragment = 0; + + current_nameserver = 0; } void @@ -152,10 +156,10 @@ client_get_conn() } void -client_set_nameserver(struct sockaddr_storage *addr, int addrlen) +client_set_nameservers(struct sockaddr_storage *addr, int addrslen) { - memcpy(&nameserv, addr, addrlen); - nameserv_len = addrlen; + nameserv_addrs = addr; + nameserv_addrs_len = addrslen; } void @@ -246,6 +250,14 @@ client_get_raw_addr() return format_addr(&raw_serv, raw_serv_len); } +void +client_rotate_nameserver() +{ + current_nameserver ++; + if (current_nameserver >= nameserv_addrs_len) + current_nameserver = 0; +} + static void send_query(int fd, char *hostname) { @@ -273,7 +285,10 @@ send_query(int fd, char *hostname) fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]); #endif - sendto(fd, packet, len, 0, (struct sockaddr*)&nameserv, nameserv_len); + sendto(fd, packet, len, 0, (struct sockaddr*) &nameserv_addrs[current_nameserver], + sizeof(struct sockaddr_storage)); + + client_rotate_nameserver(); /* There are DNS relays that time out quickly but don't send anything back on timeout. @@ -798,13 +813,14 @@ tunnel_dns(int tun_fd, int dns_fd) int up_ack_fragment; int new_down_seqno; int new_down_fragment; - struct query q; + static struct query q; unsigned long datalen; - char buf[64*1024]; + static char buf[64*1024]; int read; int send_something_now = 0; - memset(q.name, 0, sizeof(q.name)); + memset(q, 0, sizeof(q)); + memset(buf, 0, sizeof(buf)); read = read_dns_withq(dns_fd, tun_fd, buf, sizeof(buf), &q); if (conn != CONN_DNS_NULL) diff --git a/src/client.h b/src/client.h index c2493f1..3fa8df5 100644 --- a/src/client.h +++ b/src/client.h @@ -24,7 +24,8 @@ void client_stop(); enum connection client_get_conn(); const char *client_get_raw_addr(); -void client_set_nameserver(struct sockaddr_storage *, int); +void client_rotate_nameserver(); +void client_set_nameservers(struct sockaddr_storage *, int); void client_set_topdomain(const char *cp); void client_set_password(const char *cp); int client_set_qtype(char *qtype); diff --git a/src/common.h b/src/common.h index 2de23e9..84f8197 100644 --- a/src/common.h +++ b/src/common.h @@ -67,6 +67,10 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; # define DONT_FRAG_VALUE 1 #endif +#ifndef GITREVISION +#define GITREVISION "GIT" +#endif + #define T_PRIVATE 65399 /* Undefined RR type; "private use" range, see http://www.bind9.net/dns-parameters */ #define T_UNSET 65432 diff --git a/src/iodine.c b/src/iodine.c index 2599a6d..4844b19 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -70,7 +70,7 @@ usage() { fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " - "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname); + "[-z context] [-F pidfile] topdomain [nameserver ...]\n", __progname); exit(2); } @@ -81,7 +81,7 @@ help() { fprintf(stderr, "iodine IP over DNS tunneling client\n"); fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " - "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname); + "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]] \n", __progname); fprintf(stderr, "Options to try if connection doesn't work:\n"); fprintf(stderr, " -T force dns type: NULL, PRIVATE, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); fprintf(stderr, " -O force downstream encoding for -T other than NULL: Base32, Base64, Base64u,\n"); @@ -101,7 +101,8 @@ help() { fprintf(stderr, " -d device to set tunnel device name\n"); fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); fprintf(stderr, " -F pidfile to write pid to a file\n"); - fprintf(stderr, "nameserver is the IP number/hostname of the relaying nameserver. if absent, /etc/resolv.conf is used\n"); + fprintf(stderr, "nameserver is the IP/hostname of the relaying nameserver(s). if absent, /etc/resolv.conf is used\n"); + fprintf(stderr, " multiple nameservers can be specified (used in round-robin). \n"); fprintf(stderr, "topdomain is the FQDN that is delegated to the tunnel endpoint.\n"); exit(0); @@ -119,7 +120,6 @@ version() { int main(int argc, char **argv) { - char *nameserv_host; char *topdomain; char *errormsg; #ifndef WINDOWS32 @@ -145,10 +145,17 @@ main(int argc, char **argv) #ifdef OPENBSD int rtable = 0; #endif + + char *nameserv_host; + char **nameserv_hosts; + int nameserv_hosts_len; struct sockaddr_storage nameservaddr; + struct sockaddr_storage *nameserv_addrs; + size_t nameserv_addrs_len; int nameservaddr_len; int nameserv_family; + nameserv_addrs_len = 0; nameserv_host = NULL; topdomain = NULL; errormsg = NULL; @@ -280,36 +287,49 @@ main(int argc, char **argv) argc -= optind; argv += optind; - switch (argc) { - case 1: - nameserv_host = get_resolvconf_addr(); - topdomain = strdup(argv[0]); - break; - case 2: - nameserv_host = argv[0]; - topdomain = strdup(argv[1]); - break; - default: - usage(); - /* NOTREACHED */ - } + nameserv_hosts_len = argc - 1; + if (nameserv_hosts_len <= 0) + nameserv_hosts_len = 1; - if (max_downstream_frag_size < 1 || max_downstream_frag_size > 0xffff) { - warnx("Use a max frag size between 1 and 65535 bytes.\n"); - usage(); - /* NOTREACHED */ - } + // Preallocate memory with expected number of hosts + nameserv_hosts = malloc(sizeof(char *) * nameserv_hosts_len); + nameserv_addrs = malloc(sizeof(struct sockaddr_storage) * nameserv_hosts_len); - if (nameserv_host) { + if (argc == 0) { + usage(); + /* NOT REACHED */ + } else if (argc == 1) { + nameserv_hosts[0] = get_resolvconf_addr(); + } else if (argc > 1) + for (int h = 0; h < nameserv_hosts_len; h++) nameserv_hosts[h] = strdup(argv[h + 1]); + topdomain = strdup(argv[0]); + + for (int n = 0; n < nameserv_hosts_len; n++) { + nameserv_host = nameserv_hosts[n]; + if (!nameserv_host) { + errx(1, "Error processing nameserver hostnames!\n"); + } nameservaddr_len = get_addr(nameserv_host, DNS_PORT, nameserv_family, 0, &nameservaddr); if (nameservaddr_len < 0) { errx(1, "Cannot lookup nameserver '%s': %s ", - nameserv_host, gai_strerror(nameservaddr_len)); + nameserv_host, gai_strerror(nameservaddr_len)); } - client_set_nameserver(&nameservaddr, nameservaddr_len); - } else { + memcpy(&nameserv_addrs[n], &nameservaddr, sizeof(struct sockaddr_storage)); + nameserv_addrs_len ++; + nameserv_host = NULL; + } + + + if (nameserv_addrs_len <= 0 || !nameserv_hosts[0]) { warnx("No nameserver found - not connected to any network?\n"); usage(); + } + + client_set_nameservers(nameserv_addrs, nameserv_addrs_len); + + if (max_downstream_frag_size < 1 || max_downstream_frag_size > 0xffff) { + warnx("Use a max frag size between 1 and 65535 bytes.\n"); + usage(); /* NOTREACHED */ } @@ -359,8 +379,10 @@ main(int argc, char **argv) signal(SIGINT, sighandler); signal(SIGTERM, sighandler); - fprintf(stderr, "Sending DNS queries for %s to %s\n", - topdomain, format_addr(&nameservaddr, nameservaddr_len)); + fprintf(stderr, "Sending DNS queries for %s to ", topdomain); + for (int a = 0; a < nameserv_addrs_len; a++) + fprintf(stderr, "%s%s", format_addr(&nameserv_addrs[a], nameservaddr_len), (a != nameserv_addrs_len-1) ? ", " : ""); + fprintf(stderr, "\n"); if (client_handshake(dns_fd, raw_mode, autodetect_frag_size, max_downstream_frag_size)) { retval = 1; From 709afb85695d1fc6abe2c48b4d6847494e47a814 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 14 Aug 2015 22:27:05 +0800 Subject: [PATCH 002/113] Updated docs --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1c76829..dfd36f2 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ QUICKSTART ---------- Try it out within your own LAN! Follow these simple steps: -- On your server, run: `./iodined -f 10.0.0.1 test.com`. +- On your server, run: `./iodined -f test.com 10.0.0.1`. If you already use the `10.0.0.0` network, use another internal net like `172.16.0.0`. - Enter a password. -- On the client, run: `./iodine -f -r 192.168.0.1 test.com`. +- On the client, run: `./iodine -f -r test.com 192.168.0.1`. Replace `192.168.0.1` with your server's ip address. - Enter the same password. - Now the client has the tunnel ip `10.0.0.2` and the server has `10.0.0.1`. @@ -88,16 +88,16 @@ If there is a chance you'll be using an iodine tunnel from unexpected environments, start `iodined` with a `-c` option. Resulting commandline in this example situation: - ./iodined -f -c -P secretpassword 192.168.99.1 t1.mydomain.com + ./iodined -f -c -P secretpassword t1.mydomain.com 192.168.99.1 ### Client side -All the setup is done, just start `iodine`. It takes one or two arguments, the -first is the local relaying DNS server (optional) and the second is the domain -you used (`t1.mydomain.com`). If you don't specify the first argument, the -system's current DNS setting will be consulted. +All the setup is done, just start `iodine`. It takes one or more arguments, the +first is the the domain you used (`t1.mydomain.com`) and the remaining options +are a list of local relaying DNS server (optional). If you don't specify more +than one argument, the system's current DNS setting will be consulted. If DNS queries are allowed to any computer, you can directly give the `iodined` -server's address as first argument (in the example: `t1ns.mydomain.com` or +server's address as second argument (in the example: `t1ns.mydomain.com` or `10.15.213.99`). In that case, it may also happen that _any_ traffic is allowed to the DNS port (53 UDP) of any computer. Iodine will detect this, and switch to raw UDP tunneling if possible. To force DNS tunneling in any case, use the From 92f3963790260b345bf0980b8ce4b089c52a57c6 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 21 Aug 2015 11:05:50 +0800 Subject: [PATCH 003/113] Created sliding window buffer implementation and handling code --- src/Makefile | 2 +- src/window.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/window.h | 108 +++++++++++++++++++ 3 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 src/window.c create mode 100644 src/window.h diff --git a/src/Makefile b/src/Makefile index 04cef2e..312fe39 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o common.o +COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o window.o common.o CLIENTOBJS = iodine.o client.o util.o CLIENT = ../bin/iodine SERVEROBJS = iodined.o user.o fw_query.o diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..f1019eb --- /dev/null +++ b/src/window.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "window.h" + +struct frag_buffer * +window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir) +{ + struct frag_buffer *buf; + buf = calloc(sizeof(struct frag_buffer), 1); + if (!buf) { + errx(1, "Failed to allocate window buffer memory!"); + } + if (dir != WINDOW_RECVING && dir != WINDOW_SENDING) { + errx(1, "Invalid window direction!"); + } + if (fragsize > MAX_FRAGSIZE) { + errx(fragsize, "Fragsize too large! Please recompile with larger MAX_FRAGSIZE!"); + } + + buf->frags = calloc(length, sizeof(fragment)); + if (!buf->frags) { + errx(1, "Failed to allocate fragment buffer!"); + } + buf->length = length; + buf->windowsize = windowsize; + buf->maxfraglen = fragsize; + buf->window_end = AFTER(buf, windowsize); + buf->direction = dir; + + return buf; +} + +void +window_buffer_destroy(struct frag_buffer *w) +{ + free(w->frags); + free(w); +} + +/* Returns number of available fragment slots (NOT BYTES) */ +size_t +window_buffer_available(struct frag_buffer *w) +{ + return w->length - w->numitems; +} + +/* Places a fragment in the window after the last one */ +int +window_append_fragment(struct frag_buffer *w, fragment *src) +{ + if (window_buffer_available(w) < 1) return 0; + memcpy(&w->frags[w->last_write], src, sizeof(fragment)); + w->last_write = WRAP(w->last_write + 1); + w->numitems ++; + return 1; +} + +/* Handles fragment received from the sending side (RECV) */ +int +window_process_incoming_fragment(struct frag_buffer *w, fragment *f) +{ + /* Check if packet is in window */ + unsigned startid, endid; + fragment *fd; + startid = w->start_seq_id; + endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; + if (!INWINDOW_SEQ(startid, endid, f->seqID)) { + warnx("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); + return 0; + } + /* Place fragment into correct location in buffer */ + size_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); +// warnx(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); + /* Check if fragment already received */ + fd = &w->frags[dest]; + if (fd->len != 0) { + warnx("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); + return 0; + } + memcpy(&fd, f, sizeof(fragment)); + fd->retries = 0; + fd->ack_other = -1; + fd->acks = 0; + w->numitems ++; + return 1; +} + +/* Reassembles first complete sequence of fragments into data. (RECV) + * Returns length of data reassembled, or 0 if no data reassembled */ +size_t +window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, int *compression) +{ + size_t woffs, fraglen, datalen = 0; + uint8_t *dest; //, *fdata_start; + dest = data; + if (w->frags[w->chunk_start].start == 0) { +// warnx("chunk_start pointing to non-start fragment (%u)!", w->frags[w->chunk_start].seqID); + return 0; + } + *compression = 1; + + fragment *f; + size_t i, curseq; + curseq = w->frags[w->chunk_start].seqID; + for (i = 0; i < w->numitems; ++i) { + woffs = WRAP(w->chunk_start + i); + f = &w->frags[woffs]; + fraglen = f->len; + if (fraglen == 0 || !f->data || f->seqID != curseq) { +// warnx("data missing! Not reassembling!"); + return 0; + } + +// warnx(" Fragment seq %u, data length %u, data offset %lu, total len %u, maxlen %u", +// f->seqID, fraglen, dest - data, datalen, maxlen); + memcpy(dest, f->data, MIN(fraglen, maxlen)); + dest += fraglen; + datalen += fraglen; + *compression &= f->compressed & 1; + if (f->compressed != *compression) { + warnx("Inconsistent compression flags in chunk. Not reassembling!"); + return 0; + } + if (fraglen > maxlen) { + warnx("Data buffer too small! Reassembled %lu bytes.", datalen); + return 0; + } + + if (f->end == 1) { +// warnx("Found end of chunk! (seqID %u, chunk len %u, datalen %u)", f->seqID, i, datalen); + break; + } + /* Move window along to avoid weird issues */ + if (INWINDOW_INDEX(w, woffs)) { + window_tick(w); + } + /* Clear fragment */ + memset(f, 0, sizeof(fragment)); + maxlen -= fraglen; + curseq = (curseq + 1) % MAX_SEQ_ID; + } + w->chunk_start = WRAP(woffs + 1); + w->numitems -= i + 1; + return datalen; +} + +/* Returns next fragment to be sent or NULL if nothing (SEND) + * This also handles packet resends, timeouts etc. */ +fragment * +window_get_next_sending_fragment(struct frag_buffer *w, int other_ack) +{ + fragment *f; + if (other_ack >= MAX_SEQ_ID || other_ack < 0) + other_ack = -1; + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[WRAP(w->window_start + i)]; + if (f->acks >= 1) continue; + if (f->retries >= 1 && difftime(f->lastsent, time(NULL)) > ACK_TIMEOUT) { + /* Fragment sent before, not ACK'd */ + warnx("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); + w->resends ++; + goto found; + } else if (f->retries == 0 && f->len > 0) { + /* Fragment not sent */ + goto found; + } + + } +// warnx("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", +// f->retries, f->seqID, f->len); + // TODO: statistics for packet loss/not sending etc + return NULL; + + found: + /* store other ACK into fragment so ACK is resent if fragment times out */ + if (f->ack_other == -1) + f->ack_other = other_ack; + f->is_nack &= 1; + f->start &= 1; + f->end &= 1; + f->retries++; + time(&f->lastsent); + return f; +} + +/* Gets the seqid of next fragment to be ACK'd (RECV) */ +int +window_get_next_ack(struct frag_buffer *w) +{ + fragment *f; + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[WRAP(w->window_start + i)]; + if (f->len > 0 && f->acks <= 0) { + f->acks = 1; + return f->seqID; + } + } + return -1; +} + +/* Sets the fragment with seqid to be ACK'd (SEND) */ +void +window_ack(struct frag_buffer *w, int seqid) +{ + fragment *f; + if (seqid < 0 || seqid > MAX_SEQ_ID) return; + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[AFTER(w, i)]; + if (f->seqID == seqid) { + if (f->acks > 0) warnx("Duplicate ack for seqId %u", seqid); + f->acks ++; +// warnx(" ack frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->ack, f->len, f->start, f->end); + } + } +} + +/* Function to be called after all other processing has been done + * when anything happens (moves window etc) (SEND/RECV) */ +void +window_tick(struct frag_buffer *w) +{ + for (size_t i = 0; i < w->windowsize; i++) { + if (w->frags[w->window_start].acks >= 1) { +// warnx("moving window forwards 1; start = %lu-%lu, end = %lu-%lu, len = %lu", +// w->window_start, AFTER(w, 1), w->window_end, AFTER(w, w->windowsize + 1), w->length); + if (w->direction == WINDOW_SENDING) { + w->numitems --; /* Clear old fragments */ + memset(&w->frags[w->window_start], 0, sizeof(fragment)); + } + w->window_start = AFTER(w, 1); + w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; + + w->window_end = AFTER(w, w->windowsize); + } else break; + } +} + +/* Splits data into fragments and adds to the end of the window buffer for sending + * All fragment meta-data is created here (SEND) */ +int +window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int compressed) +{ + // Split data into thingies of <= fragsize + size_t n = ((len - 1) / w->maxfraglen) + 1; + if (!data || n == 0 || len == 0 || n > window_buffer_available(w)) { + warnx("Failed to append fragment (buffer too small!)"); + return -1; + } + compressed &= 1; + size_t offset = 0; + static fragment f; +// warnx("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); + for (size_t i = 0; i < n; i++) { + memset(&f, 0, sizeof(f)); + f.len = MIN(len - offset, w->maxfraglen); + memcpy(f.data, data + offset, f.len); + f.seqID = w->cur_seq_id; + f.start = (i == 0) ? 1 : 0; + f.end = (i == n - 1) ? 1 : 0; + f.compressed = compressed; + f.ack_other = -1; + window_append_fragment(w, &f); + w->cur_seq_id = (w->cur_seq_id + 1) % MAX_SEQ_ID; +// warnx(" a = %u, b = %u, a %% b = %u", (len - offset), (w->maxfraglen + 1), (len - offset) % (w->maxfraglen + 1)); +// warnx(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); + offset += f.len; + } + return n; +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..b28c3a6 --- /dev/null +++ b/src/window.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __WINDOW_H__ +#define __WINDOW_H__ + +#define MAX_SEQ_ID 256 +#define MAX_FRAGSIZE 2048 +#define ACK_TIMEOUT 5 + +#define WINDOW_SENDING 1 +#define WINDOW_RECVING 0 + +typedef struct fragment { + size_t len; /* Length of fragment data */ + unsigned seqID; /* fragment sequence ID */ + int ack_other; /* other way ACK seqID (>=0) or unset (<0) */ + int is_nack; /* 1 if other way ACK is a NACK */ + int compressed; /* compression flag */ + uint8_t start; /* start of chunk flag */ + uint8_t end; /* end of chunk flag */ + uint8_t data[MAX_FRAGSIZE]; /* fragment data */ + unsigned retries; /* number of times fragment has been sent */ + time_t lastsent; /* timestamp of most recent send attempt TODO: millisecond precision*/ + int acks; /* number of times packet has been ack'd (should be <= 1) */ +} fragment; + +struct frag_buffer { + fragment *frags; /* pointer to array of data fragments */ + unsigned windowsize; /* Max number of packets in flight */ + unsigned maxfraglen; /* Max fragment size */ + size_t length; /* Length of buffer */ + size_t numitems; /* number of non-empty fragments stored in buffer */ + size_t window_start; /* Start of window */ + size_t window_end; /* End of window (index) */ +// size_t last_sent; /* Last fragment sent (index) */ + size_t last_write; /* Last fragment read/written */ + size_t chunk_start; /* Start of current chunk of fragments, ie where fragno = 0 */ + unsigned cur_seq_id; /* Most recent sequence ID */ + unsigned start_seq_id; /* Start of window sequence ID */ + unsigned resends; /* number of fragments resent */ + int direction; /* Sending or recving */ +}; + +#define AFTER(w, o) ((w->window_start + o) % w->length) + +// Distance (going forwards) between a and b in window of length l +#define DISTF(l, a, b) (((a > b) ? a-b : l-a+b-1) % l) +// Distance backwards between a and b in window of length l +#define DISTB(l, a, b) (((a < b) ? l-b+a-1 : a-b) % l) +#define INWINDOW_INDEX(w, a) ((w->window_start < w->window_end) ? \ + (a >= w->window_start && a <= w->window_end) : \ + ((a >= w->window_start && a <= w->length - 1) || \ + (a >= 0 && a <= w->window_end))) +#define INWINDOW_SEQ(start, end, a) ((start < end) ? \ + (a >= start && a <= end) : \ + ((a >= start && a <= MAX_SEQ_ID - 1) || \ + (a <= end))) +#define SEQ_OFFSET(start, a) ((a >= start) ? a - start : MAX_SEQ_ID + start - a - 1) +#define WRAP(x) ((x) % w->length) + +struct frag_buffer *window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir); +void window_buffer_destroy(struct frag_buffer *w); + +/* Returns number of available fragment slots (NOT BYTES) */ +size_t window_buffer_available(struct frag_buffer *w); + +/* Places a fragment in the window after the last one */ +int window_append_fragment(struct frag_buffer *w, fragment *src); + +/* Handles fragment received from the sending side (RECV) */ +int window_process_incoming_fragment(struct frag_buffer *w, fragment *f); + +/* Reassembles first complete sequence of fragments into data. (RECV) + * Returns length of data reassembled, or 0 if no data reassembled */ +size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, int *compression); + +/* Returns next fragment to be sent or NULL if nothing (SEND) */ +fragment *window_get_next_sending_fragment(struct frag_buffer *w, int other_ack); + +/* Gets the seqid of next fragment to be ACK'd (RECV) */ +int window_get_next_ack(struct frag_buffer *w); + +/* Sets the fragment with seqid to be ACK'd (SEND) */ +void window_ack(struct frag_buffer *w, unsigned seqid); + +/* To be called after all other processing has been done + * when anything happens (moves window etc) (SEND/RECV) */ +void window_tick(struct frag_buffer *w); + +/* Splits data into fragments and adds to the end of the window buffer for sending + * All fragment meta-data is created here (SEND) */ +int window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int compressed); + +#endif /* __WINDOW_H__ */ From 0fcd8d337dc2bc5f4b2f1680702483c6457f3896 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 21 Aug 2015 11:07:48 +0800 Subject: [PATCH 004/113] Updated docs + protocol description (800) --- doc/proto_00000800.txt | 305 +++++++++++++++++++++++++++++++++++++++++ man/iodine.8 | 7 +- src/version.h | 2 +- 3 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 doc/proto_00000800.txt diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt new file mode 100644 index 0000000..2b5a8b7 --- /dev/null +++ b/doc/proto_00000800.txt @@ -0,0 +1,305 @@ +Detailed specification of protocol in version 00000800 +====================================================== + +Note: work in progress!! + +====================================================== +1. DNS protocol +====================================================== + +Quick alphabetical index / register: + 0-9 Data packet + A-F Data packet + I IP address + L Login + N Downstream fragsize (NS.topdomain A-type reply) + O Options + P Ping + R Downstream fragsize probe + S Switch upstream codec + V Version + W (WWW.topdomain A-type reply) + Y Downstream codec check + Z Upstream codec check + + +CMC = 2 byte Cache Miss Counter, increased every time it is used + +Version: +Client sends: + First byte v or V + Rest encoded with base32: + 4 bytes big endian protocol version + CMC +Server replies: + 4 chars: + VACK (version ok), followed by login challenge + VNAK (version differs), followed by server protocol version + VFUL (server has no free slots), followed by max users + 4 byte value: means login challenge/server protocol version/max users + 1 byte userid of the new user, or any byte if not VACK + +Login: +Client sends: + First byte l or L + Rest encoded with base32: + 1 byte userid + 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) + CMC +Server replies: + LNAK means not accepted + x.x.x.x-y.y.y.y-mtu-netmask means accepted (server ip, client ip, mtu, netmask bits) + +IP Request: (for where to try raw login or set data pipe mode) +Client sends: + First byte i or I + 5 bits coded as Base32 char, meaning userid + CMC as 3 Base32 chars +Server replies + BADIP if bad userid + First byte I + Then comes external IP address of iodined server + as 4 bytes (IPv4) or 16 bytes (IPv6) + +Upstream codec check / bounce: +Client sends: + First byte z or Z + Lots of data that should not be decoded +Server replies: + The requested domain copied raw, in the lowest-grade downstream codec + available for the request type. + +Downstream codec check: +Client sends: + First byte y or Y + 1 char, meaning downstream codec to use + 5 bits coded as Base32 char, meaning check variant + CMC as 3 Base32 chars + Possibly extra data, depending on check variant +Server sends: + Data encoded with requested downstream codec; data content depending + on check variant number. + BADCODEC if requested downstream codec not available. + BADLEN if check variant is not available, or problem with extra data. + + Downstream codec chars are same as in 'O' Option request, below. + + Check variants: + 1: Send encoded DOWNCODECCHECK1 string as defined in encoding.h + + (Other variants reserved; possibly variant that sends a decoded-encoded + copy of Base32-encoded extra data in the request) + +Switch codec: +Client sends: + First byte s or S + 5 bits coded as Base32 char, meaning userid + 5 bits coded as Base32 char, representing number of raw bits per + encoded byte: + 5: Base32 (a-z0-5) + 6: Base64 (a-zA-Z0-9+-) + 26: Base64u (a-zA-Z0-9_-) + 7: Base128 (a-zA-Z0-9\274-\375) + CMC as 3 Base32 chars +Server sends: + Name of codec if accepted. After this all upstream data packets must + be encoded with the new codec. + BADCODEC if not accepted. Client must then revert to previous codec + BADLEN if length of query is too short + +Options: +Client sends: + First byte o or O + 5 bits coded as Base32 char, meaning userid + 1 char, meaning option + CMC as 3 Base32 chars +Server sends: + Full name of option if accepted. After this, option immediately takes + effect in server. + BADCODEC if not accepted. Previous situation remains. + All options affect only the requesting client. + + Option chars: + t or T: Downstream encoding Base32, for TXT/CNAME/A/MX (default) + s or S: Downstream encoding Base64, for TXT/CNAME/A/MX + u or U: Downstream encoding Base64u, for TXT/CNAME/A/MX + v or V: Downstream encoding Base128, for TXT/CNAME/A/MX + r or R: Downstream encoding Raw, for PRIVATE/TXT/NULL (default for + PRIVATE and NULL) + If codec unsupported for request type, server will use Base32; note + that server will answer any mix of request types that a client sends. + Server may disregard this option; client must always use the downstream + encoding type indicated in every downstream DNS packet. + + l or L: Lazy mode, server will keep one request unanswered until the + next one comes in. Applies only to data transfer; handshake is always + answered immediately. + i or I: Immediate (non-lazy) mode, server will answer all requests + (nearly) immediately. + +Probe downstream fragment size: +Client sends: + First byte r or R + 15 bits coded as 3 Base32 chars: UUUUF FFFFF FFFFF + meaning 4 bits userid, 11 bits fragment size + Then follows a long random query which contents does not matter +Server sends: + Requested number of bytes as a response. The first two bytes contain + the requested length. The third byte is 107 (0x6B). The fourth byte + is a random value, and each following byte is incremented with 107. + This is checked by the client to determine corruption. + BADFRAG if requested length not accepted. + +Set downstream fragment size: +Client sends: + First byte n or N + Rest encoded with base32: + 1 byte userid + 2 bytes new downstream fragment size + CMC +Server sends: + 2 bytes new downstream fragment size. After this all downstream + payloads will be max (fragsize + 2) bytes long. + BADFRAG if not accepted. + +Upstream data header: + 76543 21076 54321076 54321076 5432 + +!----+!----+!----!--+--!----!+----+ + |0UUUU|UDCMC|SSSSSSSS|DDDDDDDD|ANFL| + +-----+-----+--------+--------+----+ + +Downstream data header: |=> only if P(ing) bit set + 76543210 76543210 76543210 76543210 76543210 + +--------+--------+--------+--------+--------+ + |DDDDDDDD|SSSSSSSS|00PCANFL|ZZZZZZZZ|WWWWWWWW| + +--------+--------+--------+--------+--------+ + +UUUU = Userid +L = Last fragment flag +A = ACK flag +N = NACK flag (unused) +F = First fragment flag +C = Compression enabled for downstream packet +P = is ping response +SSSSSSSS = Upstream packet sequence number/ACK +DDDDDDDD = Downstream packet sequence number/ACK +ZZZZZZZZ = Downstream window size +WWWWWWWW = Upstream window size +UDCMC = Upstream Data CMC char (actually base36 [a-z0-9]), case-insensitive + +Upstream data packet starts with 1 byte ASCII hex coded user byte; then +1 char data-CMC; then 4 bytes Base32 encoded header; then comes the payload +data, encoded with the chosen upstream codec. + +Downstream data starts with 3 byte header. Then payload data, which may be +compressed. If Ping flag is set, another 8 bits of downstream window size and +8 bits of upstream window size are appended to the header and the downstream or +upstream sequence IDs are the start of the remote windows. This occurs either +when the server wants to re-sync its window parameters or when the client has +requested a ping response. + +In NULL and PRIVATE responses, downstream data is always raw. In all other +response types, downstream data is encoded (see Options above). +Encoding type is indicated by 1 prefix char: +TXT: + End result is always DNS-chopped (series of len-prefixed strings + <=255 bytes) + t or T: Base32 encoded before chop, decoded after un-chop + s or S: Base64 encoded before chop, decoded after un-chop + u or U: Base64u encoded before chop, decoded after un-chop + v or V: Base128 encoded before chop, decoded after un-chop + r or R: Raw no encoding, only DNS-chop +SRV/MX/CNAME/A: + h or H: Hostname encoded with Base32 + i or I: Hostname encoded with Base64 + j or J: Hostname encoded with Base64u + k or K: Hostname encoded with Base128 +SRV and MX may reply with multiple hostnames, each encoded separately. Each +has a 10-multiple priority, and encoding/decoding is done in strictly +increasing priority sequence 10, 20, 30, etc. without gaps. Note that some DNS +relays will shuffle the answer records in the response. + +Ping: +Client sends: + First byte p or P + Second byte CMC + Rest encoded with Base32: + 76543 21076543 21076543 21076543 21076543 210 + +!----+!----!--+--!----!+----!---+-!----!-+--------+ + |0UUUU|DDDDDDDD|SSSSSSSS|WWWWWWWW|ZZZZZZZZ|0000000R| + +-----+--------+--------+--------+--------+--------+ + 4 bits Userid + 1 byte Downstream seq ID of window start + 1 byte upStream seq ID of window start + 1 byte Window size (upstream) + 1 byte window siZe (downstream) + 1 byte "Respond with ping" flag + 2 bytes CMC + +The server response to Ping and Data packets is a DNS NULL/TXT/.. type response, +always starting with the 3 bytes downstream data header as shown above. If R bit +set, server must respond with a ping downstream header. +If server has nothing to send, no data is added after the header. +If server has something to send, it will add the downstream data packet +(or some fragment of it) after the header. + + +"Lazy-mode" operation +===================== + +Client-server DNS traffic sequence has been reordered to provide increased +(interactive) performance and greatly reduced latency. + +Idea taken from Lucas Nussbaum's slides (24th IFIP International Security +Conference, 2009) at http://www.loria.fr/~lnussbau/tuns.html. Current +implementation is original to iodine, no code or documentation from any other +project was consulted during development. + +Server: +Upstream data is acked immediately*, to keep the slow upstream data flowing +as fast as possible (client waits for ack to send next frag). + +Upstream pings are answered _only_ when 1) downstream data arrives from tun, +OR 2) new upstream ping/data arrives from client. +In most cases, this means we answer the previous DNS query instead of the +current one. The current query is kept in queue and used as soon as +downstream data has to be sent. + +*: upstream data ack is usually done as reply on the previous ping packet, +and the upstream-data packet itself is kept in queue. + +Client: +Downstream data is acked immediately, to keep it flowing fast (includes a +ping after last downstream frag). + +Also, after all available upstream data is sent & acked by the server (which +in some cases uses up the last query), send an additional ping to prime the +server for the next downstream data. + + +====================================================== +2. Raw UDP protocol +====================================================== + +All Raw UDP protcol messages start with a 3 byte header: 0x10d19e +This is not the start of a valid DNS message so it is easy to identify. +The fourth byte contains the command and the user id. + + 7654 3210 + +----+----+ + |CCCC|UUUU| + +----+----+ + +Login message (command = 1): +The header is followed by a MD5 hash with the same password as in the DNS +login. The client starts the raw mode by sending this message, and uses +the login challenge +1, and the server responds using the login challenge -1. +After the login message has been exchanged, both the server and the client +switch to raw udp mode for the rest of the connection. + +Data message (command = 2): +After the header comes the payload data, which may be compressed. + +Ping message (command = 3): +Sent from client to server and back to keep session open. Has no payload. + diff --git a/man/iodine.8 b/man/iodine.8 index 0eb9b9b..b394d90 100644 --- a/man/iodine.8 +++ b/man/iodine.8 @@ -36,10 +36,13 @@ iodine, iodined \- tunnel IPv4 over DNS .B ] [-I .I interval .B ] +.I topdomain .B [ .I nameserver -.B ] -.I topdomain +.B [ +.I nameserver2 +.B [...] ] ] + .B iodined [-v] diff --git a/src/version.h b/src/version.h index 5843a7a..440a7ba 100644 --- a/src/version.h +++ b/src/version.h @@ -20,7 +20,7 @@ /* This is the version of the network protocol It is usually equal to the latest iodine version number */ -#define PROTOCOL_VERSION 0x00000502 +#define PROTOCOL_VERSION 0x00000800 #endif /* _VERSION_H_ */ From 83f70608fce05e568ab7d844f91867f5c0661b28 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 21 Aug 2015 11:08:47 +0800 Subject: [PATCH 005/113] Client side sliding window implementation (requires further testing) --- src/client.c | 478 ++++++++++++++++++--------------------------------- src/common.c | 16 -- src/common.h | 13 +- src/iodine.c | 5 +- 4 files changed, 170 insertions(+), 342 deletions(-) diff --git a/src/client.c b/src/client.c index 5b04da6..b6735f7 100644 --- a/src/client.c +++ b/src/client.c @@ -55,6 +55,7 @@ #include "login.h" #include "tun.h" #include "version.h" +#include "window.h" #include "client.h" static void handshake_lazyoff(int dns_fd); @@ -72,20 +73,18 @@ static const char *topdomain; static uint16_t rand_seed; -/* Current up/downstream IP packet */ -static struct packet outpkt; -static struct packet inpkt; -int outchunkresent = 0; +/* Current up/downstream window data */ +static struct frag_buffer *outbuf; +static struct frag_buffer *inbuf; +/* Next downstream seqID to be ACK'd (-1 if none pending) */ +static int next_downstream_ack; /* My userid at the server */ static char userid; static char userid_char; /* used when sending (lowercase) */ static char userid_char2; /* also accepted when receiving (uppercase) */ -/* DNS id for next packet */ static uint16_t chunkid; -static uint16_t chunkid_prev; -static uint16_t chunkid_prev2; /* Base32 encoder used for non-data packets and replies */ static struct encoder *b32; @@ -129,17 +128,13 @@ client_init() conn = CONN_DNS_NULL; chunkid = ((unsigned int) rand()) & 0xFFFF; - chunkid_prev = 0; - chunkid_prev2 = 0; - outpkt.len = 0; - outpkt.seqno = 0; - outpkt.fragment = 0; - outchunkresent = 0; - inpkt.len = 0; - inpkt.seqno = 0; - inpkt.fragment = 0; + // TODO: user-set window size (command line option) + outbuf = window_buffer_init(100, 10, hostname_maxlen, WINDOW_SENDING); + /* Incoming buffer max fragsize doesn't matter */ + inbuf = window_buffer_init(100, 10, 1200, WINDOW_RECVING); + next_downstream_ack = -1; current_nameserver = 0; } @@ -265,8 +260,6 @@ send_query(int fd, char *hostname) struct query q; size_t len; - chunkid_prev2 = chunkid_prev; - chunkid_prev = chunkid; chunkid += 7727; if (chunkid == 0) /* 0 is used as "no-query" in iodined.c */ @@ -342,15 +335,17 @@ send_raw(int fd, char *buf, int buflen, int user, int cmd) } static void -send_raw_data(int dns_fd) +send_raw_data(int dns_fd) // TODO: fix send_raw { - send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); - outpkt.len = 0; + errx(1, "send_raw_data NEEDS FIXING!"); + /*send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); + outpkt.len = 0;*/ } static void send_packet(int fd, char cmd, const char *data, const size_t datalen) +/* Base32 encodes data and sends as single DNS query */ { char buf[4096]; @@ -364,72 +359,77 @@ send_packet(int fd, char cmd, const char *data, const size_t datalen) static inline int is_sending() { - return (outpkt.len != 0); + return (outbuf->numitems > 0); } -static void -send_chunk(int fd) -{ - char buf[4096]; - int avail; - int code; - char *p; - static int datacmc = 0; - char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; - p = outpkt.data; - p += outpkt.offset; - avail = outpkt.len - outpkt.offset; +static void +send_next_frag(int fd) +/* Sends next available fragment of data from the outgoing window buffer */ +{ + static uint8_t buf[MAX_FRAGSIZE]; + size_t len; + int code; + static int datacmc = 0; + static char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; + fragment *f; + + /* Get next fragment to send */ + f = window_get_next_sending_fragment(outbuf, next_downstream_ack); + window_tick(outbuf); + if (f == NULL) { + if (is_sending()) { + /* 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(fd); + } + return; /* nothing to send - why was this called? */ + } /* Note: must be same, or smaller than send_fragsize_probe() */ - outpkt.sentlen = build_hostname(buf + 5, sizeof(buf) - 5, p, avail, - topdomain, dataenc, hostname_maxlen); + len = build_hostname(buf, sizeof(buf), f->data, f->len, topdomain, dataenc, hostname_maxlen); /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ buf[0] = userid_char; /* First byte is hex userid */ - code = ((outpkt.seqno & 7) << 2) | ((outpkt.fragment & 15) >> 2); - buf[1] = b32_5to8(code); /* Second byte is 3 bits seqno, 2 upper bits fragment count */ + buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ - code = ((outpkt.fragment & 3) << 3) | (inpkt.seqno & 7); - buf[2] = b32_5to8(code); /* Third byte is 2 bits lower fragment count, 3 bits downstream packet seqno */ + code = (f->seqID & 0xF8) >> 3; + buf[2] = b32_5to8(code); /* Second byte is 5 bits seqno */ - code = ((inpkt.fragment & 15) << 1) | (outpkt.sentlen == avail); - buf[3] = b32_5to8(code); /* Fourth byte is 4 bits downstream fragment count, 1 bit last frag flag */ + code = ((f->seqID & 7) << 2) | ((f->ack_other & 0xC0) >> 6); + buf[3] = b32_5to8(code); /* Third byte is 3 bits seqno, 2 bits downstream ACK */ + + code = ((f->ack_other & 0x3E) >> 1); + buf[4] = b32_5to8(code); /* Fourth byte is 5 bits downstream ACK */ + + code = (f->ack_other & 1) << 4 | ((f->ack_other < 0 ? 0 : 1) << 3) | (f->is_nack << 2) | (f->start << 1) | f->end; + buf[5] = b32_5to8(code); /* Fifth byte is 1 bit downstream ACK, bit flags isACK, isNACK, first, last */ - buf[4] = datacmcchars[datacmc]; /* Fifth byte is data-CMC */ datacmc++; if (datacmc >= 36) datacmc = 0; -#if 0 - fprintf(stderr, " Send: down %d/%d up %d/%d, %d bytes\n", - inpkt.seqno, inpkt.fragment, outpkt.seqno, outpkt.fragment, - outpkt.sentlen); -#endif - send_query(fd, buf); } static void -send_ping(int fd) +send_ping(int fd, int ping_response) // TODO: setup window sync stuff in ping { if (conn == CONN_DNS_NULL) { char data[4]; data[0] = userid; - data[1] = ((inpkt.seqno & 7) << 4) | (inpkt.fragment & 15); - data[2] = (rand_seed >> 8) & 0xff; - data[3] = (rand_seed >> 0) & 0xff; - + data[1] = inbuf->start_seq_id & 0xff; + data[2] = outbuf->start_seq_id & 0xff; + data[3] = outbuf->windowsize & 0xff; + data[4] = inbuf->windowsize & 0xff; + data[5] = ping_response & 1; + data[6] = (rand_seed >> 8) & 0xff; + data[7] = (rand_seed >> 0) & 0xff; rand_seed++; -#if 0 - fprintf(stderr, " Send: down %d/%d (ping)\n", - inpkt.seqno, inpkt.fragment); -#endif - send_packet(fd, 'p', data, sizeof(data)); } else { send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); @@ -574,8 +574,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) } static int -read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) -/* FIXME: tun_fd needed for raw handling */ +read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, int buflen, struct query *q) /* Returns -1 on receive error or decode error, including DNS error replies. Returns 0 on replies that could be correct but are useless, and are not DNS error replies. @@ -763,37 +762,44 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo return -1; } +static int +parse_data(uint8_t *data, size_t len, fragment *f) +{ + memset(f, 0, sizeof(fragment)); + f->seqID = data[0]; + f->start = data[3] & 1; + f->end = (data[3] >> 1) & 1; + f->is_nack = (data[3] >> 2) & 1; + f->ack_other = (data[3] >> 3) & 1 ? data[1] : -1; + f->compressed = (data[3] >> 4) & 1; + f->len = len - 3; + memcpy(f->data, data + 3, MIN(f->len, sizeof(f->data))); + return (data[3] >> 5) & 1; /* return ping flag (if corresponding query was a ping) */ +} + static int tunnel_tun(int tun_fd, int dns_fd) { - unsigned long outlen; - unsigned long inlen; - char out[64*1024]; - char in[64*1024]; + size_t outlen, inlen; + uint8_t out[64*1024]; + uint8_t in[64*1024]; ssize_t read; if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) return -1; - /* We may be here only to empty the tun device; then return -1 - to force continue in select loop. */ - if (is_sending()) + /* Give outgoing buffer all new data if it can hold it */ + if (window_buffer_available(outbuf) > read / MAX_FRAGSIZE) return -1; outlen = sizeof(out); inlen = read; - compress2((uint8_t*)out, &outlen, (uint8_t*)in, inlen, 9); + compress2(out, &outlen, in, inlen, 9); - memcpy(outpkt.data, out, MIN(outlen, sizeof(outpkt.data))); - outpkt.sentlen = 0; - outpkt.offset = 0; - outpkt.seqno = (outpkt.seqno + 1) & 7; - outpkt.len = outlen; - outpkt.fragment = 0; - outchunkresent = 0; + window_add_outgoing_data(outbuf, out, outlen, 1); if (conn == CONN_DNS_NULL) { - send_chunk(dns_fd); + send_next_frag(dns_fd); send_ping_soon = 0; } else { @@ -809,19 +815,17 @@ tunnel_dns(int tun_fd, int dns_fd) static long packrecv = 0; static long packrecv_oos = 0; static long packrecv_servfail = 0; - int up_ack_seqno; - int up_ack_fragment; - int new_down_seqno; - int new_down_fragment; static struct query q; - unsigned long datalen; - static char buf[64*1024]; - int read; + size_t datalen; + static uint8_t buf[64*1024], cbuf[64*1024]; + static fragment f; + int read, compressed, res; int send_something_now = 0; - memset(q, 0, sizeof(q)); + memset(&q, 0, sizeof(q)); memset(buf, 0, sizeof(buf)); - read = read_dns_withq(dns_fd, tun_fd, buf, sizeof(buf), &q); + memset(cbuf, 0, sizeof(cbuf)); + read = read_dns_withq(dns_fd, tun_fd, cbuf, sizeof(cbuf), &q); if (conn != CONN_DNS_NULL) return 1; /* everything already done */ @@ -842,7 +846,7 @@ tunnel_dns(int tun_fd, int dns_fd) return -1; /* nothing done */ } - if (read < 2) { + if (read < 3) { /* Maybe SERVFAIL etc. Send ping to get things back in order, but wait a bit to prevent fast ping-pong loops. */ @@ -874,12 +878,11 @@ tunnel_dns(int tun_fd, int dns_fd) if (read == 1) fprintf(stderr, " q=%c id %5d 1-byte illegal \"QMEM\" reply\n", q.name[0], q.id); #endif - send_ping_soon = 900; return -1; /* nothing done */ } - if (read == 5 && !strncmp("BADIP", buf, 5)) { + if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { warnx("BADIP: Server rejected sender IP address (maybe iodined -c will help), or server kicked us due to timeout. Will exit if no downstream data is received in 60 seconds."); return -1; /* nothing done */ } @@ -889,217 +892,80 @@ tunnel_dns(int tun_fd, int dns_fd) send_ping_soon = 0; } - /* Decode the data header, update seqno and frag; - already checked read>=2 - Note that buf[] gets overwritten when down-pkt complete */ - new_down_seqno = (buf[1] >> 5) & 7; - new_down_fragment = (buf[1] >> 1) & 15; - up_ack_seqno = (buf[0] >> 4) & 7; - up_ack_fragment = buf[0] & 15; + /* Decode the downstream data header and fragment-ify ready for processing */ + res = parse_data(cbuf, read, &f); -#if 0 - fprintf(stderr, " Recv: id %5d down %d/%d up %d/%d, %d bytes\n", - q.id, new_down_seqno, new_down_fragment, up_ack_seqno, - up_ack_fragment, read); -#endif - - /* Downstream data traffic */ - - if (read > 2 && new_down_seqno != inpkt.seqno && - recent_seqno(inpkt.seqno, new_down_seqno)) { - /* This is the previous seqno, or a bit earlier. - Probably out-of-sequence dupe due to unreliable - intermediary DNS. Don't get distracted, but send - ping quickly to get things back in order. - Ping will send our current seqno idea. - If it's really a new packet that skipped multiple seqnos - (why??), server will re-send and drop a few times and - eventually everything will work again. */ - read = 2; - send_ping_soon = 500; - /* Still process upstream ack, if any */ + /* if this response was a reverse ping/response to a ping: do something different? */ + if (res) { + // TODO: handle pings to resync window } + window_ack(outbuf, f.ack_other); + + /* 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..) */ + if (f.len == 0) { + send_ping_soon = 700; + return -1; + } + + /* Get next ACK if nothing already pending */ + if (next_downstream_ack < 0) { + if ((next_downstream_ack = window_get_next_ack(inbuf)) < 0) { + next_downstream_ack = -1; + } else { + send_something_now = 1; + } + } + + /* Downstream data traffic */ + if (!window_process_incoming_fragment(inbuf, &f)) { + /* Packet outside window - old, duped and irrelevant */ + packrecv_oos ++; + return -1; /* nothing done */ + } + + // TODO: check OOS packet count and send ping to update params + + /* Okay, we have a recent downstream packet */ + lastdownstreamtime = time(NULL); if (!(packrecv & 0x1000000)) packrecv++; send_query_recvcnt++; /* overflow doesn't matter */ - /* Don't process any non-recent stuff any further. - No need to remember more than 3 ids: in practice any older replies - arrive after new/current replies, and whatever data the old replies - have, it has become useless in the mean time. - Actually, ever since iodined is replying to both the original query - and the last dupe, this hardly triggers any more. - */ - if (q.id != chunkid && q.id != chunkid_prev && q.id != chunkid_prev2) { - packrecv_oos++; -#if 0 - fprintf(stderr, " q=%c Packs received = %8ld Out-of-sequence = %8ld\n", q.name[0], packrecv, packrecv_oos); -#endif - if (lazymode && packrecv < 1000 && packrecv_oos == 5) { - if (selecttimeout > 1) - warnx("Hmm, getting some out-of-sequence DNS replies. Setting interval to 1 (use -I1 next time on this network). If data traffic still has large hiccups, try if -L0 works better."); - else - warnx("Hmm, getting some out-of-sequence DNS replies. If data traffic often has large hiccups, try running with -L0 ."); - selecttimeout = 1; - send_query_sendcnt = 0; - send_query_recvcnt = 0; - } - - if (send_something_now) { - send_ping(dns_fd); - send_ping_soon = 0; - } - return -1; /* nothing done */ - } -#if 0 - fprintf(stderr, " q=%c Packs received = %8ld Out-of-sequence = %8ld\n", q.name[0], packrecv, packrecv_oos); -#endif - - /* Okay, we have a recent downstream packet */ - lastdownstreamtime = time(NULL); - - /* In lazy mode, we shouldn't get much replies to our most-recent - query, only during heavy data transfer. Since this means the server - doesn't have any packets left, send one relatively fast (but not - too fast, to avoid runaway ping-pong loops..) */ - if (q.id == chunkid && lazymode) { - if (!send_ping_soon || send_ping_soon > 900) - send_ping_soon = 900; - } - - if (read == 2 && new_down_seqno != inpkt.seqno && - !recent_seqno(inpkt.seqno, new_down_seqno)) { - /* This is a seqno that we didn't see yet, but it has - no data any more. Possible since iodined will send - fitting packs just once and not wait for ack. - Real data got lost, or will arrive shortly. - Update our idea of the seqno, and drop any waiting - old pack. Send ping to get things back on track. */ - inpkt.seqno = new_down_seqno; - inpkt.fragment = new_down_fragment; - inpkt.len = 0; - send_ping_soon = 500; - } - - while (read > 2) { - /* "if" with easy exit */ - - if (new_down_seqno != inpkt.seqno) { - /* New packet (and not dupe of recent; checked above) */ - /* Forget any old packet, even if incomplete */ - inpkt.seqno = new_down_seqno; - inpkt.fragment = new_down_fragment; /* hopefully 0 */ - inpkt.len = 0; - } else if (inpkt.fragment == 0 && new_down_fragment == 0 && - inpkt.len == 0) { - /* Weird situation: we probably got a no-data reply - for this seqno (see above), and the actual data - is following now. */ - /* okay, nothing to do here, just so that next else-if - doesn't trigger */ - } else if (new_down_fragment <= inpkt.fragment) { - /* Same packet but duplicate fragment, ignore. - If the server didn't get our ack for it, the next - ping or chunk will do that. */ - send_ping_soon = 500; - break; - } else if (new_down_fragment > inpkt.fragment + 1) { - /* Quite impossible. We missed a fragment, but the - server got our ack for it and is sending the next - fragment already. Don't handle it but let server - re-send and drop. */ - send_ping_soon = 500; - break; - } - inpkt.fragment = new_down_fragment; - - datalen = MIN(read - 2, sizeof(inpkt.data) - inpkt.len); - - /* we are here only when read > 2, so datalen "always" >=1 */ - - /* Skip 2 byte data header and append to packet */ - memcpy(&inpkt.data[inpkt.len], &buf[2], datalen); - inpkt.len += datalen; - - if (buf[1] & 1) { /* If last fragment flag is set */ - /* Uncompress packet and send to tun */ - /* RE-USES buf[] */ - datalen = sizeof(buf); - if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) inpkt.data, inpkt.len) == Z_OK) { + datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed); + if (datalen != 0) { + if (compressed) { + if ((res = uncompress(buf, &datalen, cbuf, datalen)) == Z_OK) { write_tun(tun_fd, buf, datalen); + } else { + warnx("Uncompress failed (%d): reassembled data corrupted or incomplete!", res); } - inpkt.len = 0; - /* Keep .seqno and .fragment as is, so that we won't - reassemble from duplicate fragments */ - } - - /* Send anything to ack the received seqno/frag, and get more */ - if (inpkt.len == 0) { - /* was last frag; wait just a trifle because our - tun will probably return TCP-ack immediately. - 5msec = 200 DNSreq/sec */ send_ping_soon = 5; - } else { - /* server certainly has more data */ - send_something_now = 1; } - - break; + } else { + /* Send anything to ack the received seqno/frag, and get more */ + /* was last frag; wait just a trifle because our + tun will probably return TCP-ack immediately. + 5msec = 200 DNSreq/sec */ + /* server certainly has more data */ + send_something_now = 1; } + /* Move window along after doing all data processing */ + window_tick(inbuf); + /* NOTE: buf[] was overwritten when down-packet complete */ - - /* Upstream data traffic */ - if (is_sending()) { - /* already checked read>=2 */ -#if 0 - fprintf(stderr, "Got ack for %d,%d - expecting %d,%d - id=%d cur=%d prev=%d prev2=%d\n", - up_ack_seqno, up_ack_fragment, outpkt.seqno, outpkt.fragment, - q.id, chunkid, chunkid_prev, chunkid_prev2); -#endif - - if (up_ack_seqno == outpkt.seqno && - up_ack_fragment == outpkt.fragment) { - /* Okay, previously sent fragment has arrived */ - - outpkt.offset += outpkt.sentlen; - if (outpkt.offset >= outpkt.len) { - /* Packet completed */ - outpkt.offset = 0; - outpkt.len = 0; - outpkt.sentlen = 0; - outchunkresent = 0; - - /* Normally, server still has a query in queue, - but sometimes not. So send a ping. - (Comment this out and you'll see occasional - hiccups.) - But since the server often still has a - query and we can expect a TCP-ack returned - from our tun device quickly in many cases, - don't be too fast. - 20msec still is 50 DNSreq/second... */ - if (!send_ping_soon || send_ping_soon > 20) - send_ping_soon = 20; - } else { - /* More to send */ - outpkt.fragment++; - outchunkresent = 0; - send_chunk(dns_fd); - send_ping_soon = 0; - send_something_now = 0; - } - } - /* else: Some wrong fragment has arrived, or old fragment is - acked again, mostly by ping responses. - Don't resend chunk, usually not needed; select loop will - re-send on timeout (1sec if is_sending()). */ + /* More to send - next fragment*/ + send_next_frag(dns_fd); + send_ping_soon = 2; + send_something_now = 0; } - /* Send ping if we didn't send anything yet */ if (send_something_now) { send_ping(dns_fd); @@ -1137,20 +1003,17 @@ client_tunnel(int tun_fd, int dns_fd) } FD_ZERO(&fds); - if (!is_sending() || outchunkresent >= 2) { - /* If re-sending upstream data, chances are that - we're several seconds behind already and TCP - will start filling tun buffer with (useless) - retransmits. - Get up-to-date fast by simply dropping stuff, - that's what TCP is designed to handle. */ + if (window_buffer_available(outbuf) > 5 || 1) { + /* Fill up outgoing buffer with available data + * The windowing protocol manages data retransmits, timeouts etc. + * TODO: is this even necessary? tunnel_tun seems to be more useful */ FD_SET(tun_fd, &fds); } FD_SET(dns_fd, &fds); i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); - if (lastdownstreamtime + 60 < time(NULL)) { + if (difftime(lastdownstreamtime, time(NULL)) >= 60) { warnx("No downstream data received in 60 seconds, shutting down."); running = 0; } @@ -1159,28 +1022,15 @@ client_tunnel(int tun_fd, int dns_fd) break; if (i < 0) - err(1, "select"); + err(1, "select < 0"); if (i == 0) { /* timeout */ if (is_sending()) { - /* Re-send current fragment; either frag - or ack probably dropped somewhere. - But problem: no cache-miss-counter, - so hostname will be identical. - Just drop whole packet after 3 retries, - and TCP retransmit will solve it. - NOTE: tun dropping above should be - >=(value_here - 1) */ - if (outchunkresent < 3) { - outchunkresent++; - send_chunk(dns_fd); + if (outbuf->resends < 3) { // if too many retransmits/timeouts/waiting etc. + send_next_frag(dns_fd); } else { - outpkt.offset = 0; - outpkt.len = 0; - outpkt.sentlen = 0; - outchunkresent = 0; - + outbuf->resends = 0; send_ping(dns_fd); } } else { @@ -1199,8 +1049,7 @@ client_tunnel(int tun_fd, int dns_fd) If chunk sent, sets send_ping_soon=0. */ } if (FD_ISSET(dns_fd, &fds)) { - if (tunnel_dns(tun_fd, dns_fd) <= 0) - continue; + tunnel_dns(tun_fd, dns_fd); } } } @@ -2329,7 +2178,7 @@ handshake_autoprobe_fragsize(int dns_fd) warnx("stopped while autodetecting fragment size (Try setting manually with -m)"); return 0; } - if (max_fragsize <= 2) { + if (max_fragsize <= 6) { /* Tried all the way down to 2 and found no good size. But we _did_ do all handshake before this, so there must be some workable connection. */ @@ -2338,8 +2187,8 @@ handshake_autoprobe_fragsize(int dns_fd) warnx("try setting -M to 200 or lower, or try other -T or -O options."); return 0; } - /* data header adds 2 bytes */ - fprintf(stderr, "will use %d-2=%d\n", max_fragsize, max_fragsize - 2); + /* data header adds 6 bytes */ + fprintf(stderr, "will use %d-6=%d\n", max_fragsize, max_fragsize - 6); /* need 1200 / 16frags = 75 bytes fragsize */ if (max_fragsize < 82) { @@ -2473,6 +2322,11 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (autodetect_frag_size) { fragsize = handshake_autoprobe_fragsize(dns_fd); + if (fragsize > MAX_FRAGSIZE) { + /* This is very unlikely except perhaps over LAN */ + fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d. To fully utilize this connection, please recompile iodine/iodined."); + fragsize = MAX_FRAGSIZE; + } if (!fragsize) { return 1; } diff --git a/src/common.c b/src/common.c index ce6c2aa..befb533 100644 --- a/src/common.c +++ b/src/common.c @@ -455,22 +455,6 @@ errx(int eval, const char *fmt, ...) } #endif - -int recent_seqno(int ourseqno, int gotseqno) -/* Return 1 if we've seen gotseqno recently (current or up to 3 back). - Return 0 if gotseqno is new (or very old). -*/ -{ - int i; - for (i = 0; i < 4; i++, ourseqno--) { - if (ourseqno < 0) - ourseqno = 7; - if (gotseqno == ourseqno) - return 1; - } - return 0; -} - #ifndef WINDOWS32 /* Set FD_CLOEXEC flag on file descriptor. * This stops it from being inherited by system() calls. diff --git a/src/common.h b/src/common.h index 84f8197..512f808 100644 --- a/src/common.h +++ b/src/common.h @@ -76,15 +76,8 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #define T_UNSET 65432 /* Unused RR type, never actually sent */ -struct packet -{ - int len; /* Total packet length */ - int sentlen; /* Length of chunk currently transmitted */ - int offset; /* Current offset */ - char data[64*1024]; /* The data */ - char seqno; /* The packet sequence number */ - char fragment; /* Fragment index */ -}; +#define DOWNSTREAM_HDR 3 +#define UPSTREAM_HDR 6 struct query { char name[QUERY_NAME_SIZE]; @@ -134,8 +127,6 @@ void errx(int eval, const char *fmt, ...); void warnx(const char *fmt, ...); #endif -int recent_seqno(int , int); - #ifndef WINDOWS32 void fd_set_close_on_exec(int fd); #endif diff --git a/src/iodine.c b/src/iodine.c index 4844b19..dbbd942 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -37,6 +37,7 @@ #endif #include "common.h" +#include "version.h" #include "tun.h" #include "client.h" #include "util.h" @@ -110,10 +111,8 @@ help() { static void version() { - fprintf(stderr, "iodine IP over DNS tunneling client\n"); - fprintf(stderr, "Git version: %s\n", GITREVISION); - + fprintf(stderr, "Git version: %s\n; protocol version %08X", GITREVISION, PROTOCOL_VERSION); exit(0); } From 844abefcf8f8898c460edf0d69d809c845805e72 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 21 Aug 2015 16:57:54 +0800 Subject: [PATCH 006/113] A little bit of refactoring - separated iodined.c into separate files Fixed up client side compile issues Removed old packet handling code - TODO: use sliding window buffer instead --- src/Makefile | 2 +- src/client.c | 104 ++- src/encoding.c | 6 +- src/encoding.h | 2 +- src/iodined.c | 2166 +----------------------------------------------- src/server.c | 1877 +++++++++++++++++++++++++++++++++++++++++ src/server.h | 111 +++ src/tun.c | 4 +- src/tun.h | 4 +- src/user.c | 152 ++-- src/user.h | 41 +- src/window.c | 2 +- src/window.h | 6 +- 13 files changed, 2178 insertions(+), 2299 deletions(-) create mode 100644 src/server.c create mode 100644 src/server.h diff --git a/src/Makefile b/src/Makefile index 312fe39..a9c81da 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o window.o common.o CLIENTOBJS = iodine.o client.o util.o CLIENT = ../bin/iodine -SERVEROBJS = iodined.o user.o fw_query.o +SERVEROBJS = iodined.o user.o fw_query.o server.o SERVER = ../bin/iodined OS = `echo $(TARGETOS) | tr "a-z" "A-Z"` diff --git a/src/client.c b/src/client.c index b6735f7..b3e52c3 100644 --- a/src/client.c +++ b/src/client.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2014 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -132,7 +133,7 @@ client_init() // TODO: user-set window size (command line option) outbuf = window_buffer_init(100, 10, hostname_maxlen, WINDOW_SENDING); /* Incoming buffer max fragsize doesn't matter */ - inbuf = window_buffer_init(100, 10, 1200, WINDOW_RECVING); + inbuf = window_buffer_init(128, 10, MAX_FRAGSIZE, WINDOW_RECVING); next_downstream_ack = -1; current_nameserver = 0; @@ -362,13 +363,33 @@ is_sending() return (outbuf->numitems > 0); } +static void +send_ping(int fd, int ping_response) // TODO: setup window sync stuff in ping +{ + if (conn == CONN_DNS_NULL) { + char data[4]; + + data[0] = userid; + data[1] = inbuf->start_seq_id & 0xff; + data[2] = outbuf->start_seq_id & 0xff; + data[3] = outbuf->windowsize & 0xff; + data[4] = inbuf->windowsize & 0xff; + data[5] = ping_response & 1; + data[6] = (rand_seed >> 8) & 0xff; + data[7] = (rand_seed >> 0) & 0xff; + rand_seed++; + + send_packet(fd, 'p', data, sizeof(data)); + } else { + send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); + } +} static void send_next_frag(int fd) /* Sends next available fragment of data from the outgoing window buffer */ { static uint8_t buf[MAX_FRAGSIZE]; - size_t len; int code; static int datacmc = 0; static char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; @@ -381,13 +402,13 @@ send_next_frag(int fd) if (is_sending()) { /* 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(fd); + send_ping(fd, 1); } return; /* nothing to send - why was this called? */ } /* Note: must be same, or smaller than send_fragsize_probe() */ - len = build_hostname(buf, sizeof(buf), f->data, f->len, topdomain, dataenc, hostname_maxlen); + build_hostname((char *)buf, sizeof(buf), (char *)f->data, f->len, topdomain, dataenc, hostname_maxlen); /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ @@ -411,29 +432,7 @@ send_next_frag(int fd) if (datacmc >= 36) datacmc = 0; - send_query(fd, buf); -} - -static void -send_ping(int fd, int ping_response) // TODO: setup window sync stuff in ping -{ - if (conn == CONN_DNS_NULL) { - char data[4]; - - data[0] = userid; - data[1] = inbuf->start_seq_id & 0xff; - data[2] = outbuf->start_seq_id & 0xff; - data[3] = outbuf->windowsize & 0xff; - data[4] = inbuf->windowsize & 0xff; - data[5] = ping_response & 1; - data[6] = (rand_seed >> 8) & 0xff; - data[7] = (rand_seed >> 0) & 0xff; - rand_seed++; - - send_packet(fd, 'p', data, sizeof(data)); - } else { - send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); - } + send_query(fd, (char *)buf); } static void @@ -574,7 +573,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) } static int -read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, int buflen, struct query *q) +read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) /* Returns -1 on receive error or decode error, including DNS error replies. Returns 0 on replies that could be correct but are useless, and are not DNS error replies. @@ -671,7 +670,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, int buflen, struct query *q r -= RAW_HDR_LEN; datalen = sizeof(buf); if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) &data[RAW_HDR_LEN], r) == Z_OK) { - write_tun(tun_fd, buf, datalen); + write_tun(tun_fd, (uint8_t*)buf, datalen); } /* don't process any further */ @@ -766,15 +765,27 @@ static int parse_data(uint8_t *data, size_t len, fragment *f) { memset(f, 0, sizeof(fragment)); - f->seqID = data[0]; - f->start = data[3] & 1; - f->end = (data[3] >> 1) & 1; - f->is_nack = (data[3] >> 2) & 1; - f->ack_other = (data[3] >> 3) & 1 ? data[1] : -1; - f->compressed = (data[3] >> 4) & 1; - f->len = len - 3; - memcpy(f->data, data + 3, MIN(f->len, sizeof(f->data))); - return (data[3] >> 5) & 1; /* return ping flag (if corresponding query was a ping) */ + int ping = (data[3] >> 5) & 1; + if (!ping) { + f->seqID = data[0]; + f->start = data[3] & 1; + f->end = (data[3] >> 1) & 1; + f->is_nack = (data[3] >> 2) & 1; + f->ack_other = (data[3] >> 3) & 1 ? data[1] : -1; + f->compressed = (data[3] >> 4) & 1; + f->len = len - 3; + memcpy(f->data, data + 3, MIN(f->len, sizeof(f->data))); + } else { /* Handle ping stuff */ + if (len != 5) return 1; /* invalid packet - continue */ + static unsigned in_start_seq, out_start_seq, in_wsize, out_wsize; + out_start_seq = data[0]; + in_start_seq = data[1]; + in_wsize = data[3]; + out_wsize = data[4]; + warnx("Pingy thingy received."); + // TODO: handle pings + } + return ping; /* return ping flag (if corresponding query was a ping) */ } static int @@ -825,7 +836,7 @@ tunnel_dns(int tun_fd, int dns_fd) memset(&q, 0, sizeof(q)); memset(buf, 0, sizeof(buf)); memset(cbuf, 0, sizeof(cbuf)); - read = read_dns_withq(dns_fd, tun_fd, cbuf, sizeof(cbuf), &q); + read = read_dns_withq(dns_fd, tun_fd, (char *)cbuf, sizeof(cbuf), &q); if (conn != CONN_DNS_NULL) return 1; /* everything already done */ @@ -895,9 +906,9 @@ tunnel_dns(int tun_fd, int dns_fd) /* Decode the downstream data header and fragment-ify ready for processing */ res = parse_data(cbuf, read, &f); - /* if this response was a reverse ping/response to a ping: do something different? */ + /* if this response was a reverse ping/response to a ping, we need to do something */ if (res) { - // TODO: handle pings to resync window + goto skip_recv; } window_ack(outbuf, f.ack_other); @@ -954,6 +965,7 @@ tunnel_dns(int tun_fd, int dns_fd) send_something_now = 1; } + skip_recv: /* Move window along after doing all data processing */ window_tick(inbuf); @@ -968,7 +980,7 @@ tunnel_dns(int tun_fd, int dns_fd) /* Send ping if we didn't send anything yet */ if (send_something_now) { - send_ping(dns_fd); + send_ping(dns_fd, 0); send_ping_soon = 0; } @@ -1031,10 +1043,10 @@ client_tunnel(int tun_fd, int dns_fd) send_next_frag(dns_fd); } else { outbuf->resends = 0; - send_ping(dns_fd); + send_ping(dns_fd, 1); } } else { - send_ping(dns_fd); + send_ping(dns_fd, 0); } send_ping_soon = 0; @@ -2324,7 +2336,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz fragsize = handshake_autoprobe_fragsize(dns_fd); if (fragsize > MAX_FRAGSIZE) { /* This is very unlikely except perhaps over LAN */ - fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d. To fully utilize this connection, please recompile iodine/iodined."); + fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d. To fully utilize this connection, please recompile iodine/iodined.", fragsize, MAX_FRAGSIZE); fragsize = MAX_FRAGSIZE; } if (!fragsize) { diff --git a/src/encoding.c b/src/encoding.c index 4906a6b..c954e1d 100644 --- a/src/encoding.c +++ b/src/encoding.c @@ -22,13 +22,13 @@ int build_hostname(char *buf, size_t buflen, const char *data, const size_t datalen, - const char *topdomain, struct encoder *encoder, int maxlen) + const char *topdomain, struct encoder *encoder, size_t maxlen) { size_t space; char *b; - space = MIN((size_t)maxlen, buflen) - strlen(topdomain) - 8; - /* 8 = 5 max header length + 1 dot before topdomain + 2 safety */ + space = MIN(maxlen, buflen) - strlen(topdomain) - DOWNSTREAM_HDR + 3; + /* max header length + 1 dot before topdomain + 2 safety */ if (!encoder->places_dots()) space -= (space / 57); /* space for dots */ diff --git a/src/encoding.h b/src/encoding.h index abb82da..b38d0d6 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -35,7 +35,7 @@ struct encoder { int (*blocksize_encoded)(void); }; -int build_hostname(char *, size_t, const char *, const size_t, const char *, struct encoder *, int); +int build_hostname(char *, size_t, const char *, const size_t, const char *, struct encoder *, size_t); int unpack_data(char *, size_t, char *, size_t, struct encoder *); int inline_dotify(char *, size_t); int inline_undotify(char *, size_t); diff --git a/src/iodined.c b/src/iodined.c index 659c9a7..d0f3f7e 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2015 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -27,8 +28,10 @@ #include #include #include +#include #include "common.h" +#include "version.h" #ifdef WINDOWS32 #include "windows.h" @@ -60,6 +63,7 @@ #include "tun.h" #include "fw_query.h" #include "version.h" +#include "server.h" #ifdef HAVE_SYSTEMD # include @@ -70,65 +74,6 @@ WORD req_version = MAKEWORD(2, 2); WSADATA wsa_data; #endif -#define PASSWORD_ENV_VAR "IODINED_PASS" - -#if defined IP_RECVDSTADDR -# define DSTADDR_SOCKOPT IP_RECVDSTADDR -# define dstaddr(x) ((struct in_addr *) CMSG_DATA(x)) -#elif defined IP_PKTINFO -# define DSTADDR_SOCKOPT IP_PKTINFO -# define dstaddr(x) (&(((struct in_pktinfo *)(CMSG_DATA(x)))->ipi_addr)) -#endif - -#ifndef IPV6_RECVPKTINFO -#define IPV6_RECVPKTINFO IPV6_PKTINFO -#endif - -static int running = 1; -static char *topdomain; -static char password[33]; -static struct encoder *b32; -static struct encoder *b64; -static struct encoder *b64u; -static struct encoder *b128; -static int created_users; - -static int check_ip; -static int my_mtu; -static in_addr_t my_ip; -static int netmask; - -static in_addr_t ns_ip; - -static int bind_port; -static int debug; - -#if !defined(BSD) && !defined(__GLIBC__) -static char *__progname; -#endif - -/* Struct with IPv4 and IPv6 file descriptors. - * Need to be passed on down to tunneling code since we can get a - * packet on one fd meant for a user on the other. - */ -struct dnsfd { - int v4fd; - int v6fd; -}; - -static int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); -static void write_dns(int fd, struct query *q, char *data, int datalen, char downenc); -static void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid); - -static int -get_dns_fd(struct dnsfd *fds, struct sockaddr_storage *addr) -{ - if (addr->ss_family == AF_INET6) { - return fds->v6fd; - } - return fds->v4fd; -} - /* Ask ipify.org webservice to get external ip */ static int get_external_ip(struct in_addr *ip) @@ -185,7 +130,7 @@ get_external_ip(struct in_addr *ip) static void sigint(int sig) { - running = 0; + server_stop(); } #ifdef WINDOWS32 @@ -205,2087 +150,6 @@ syslog(int a, const char *str, ...) } #endif -/* This will not check that user has passed login challenge */ -static int -check_user_and_ip(int userid, struct query *q) -{ - /* Note: duplicate in handle_raw_login() except IP-address check */ - - if (userid < 0 || userid >= created_users ) { - return 1; - } - if (!users[userid].active || users[userid].disabled) { - return 1; - } - if (users[userid].last_pkt + 60 < time(NULL)) { - return 1; - } - - /* return early if IP checking is disabled */ - if (!check_ip) { - return 0; - } - - if (q->from.ss_family != users[userid].host.ss_family) { - return 1; - } - /* Check IPv4 */ - if (q->from.ss_family == AF_INET) { - struct sockaddr_in *expected, *received; - - expected = (struct sockaddr_in *) &(users[userid].host); - received = (struct sockaddr_in *) &(q->from); - return memcmp(&(expected->sin_addr), &(received->sin_addr), sizeof(struct in_addr)); - } - /* Check IPv6 */ - if (q->from.ss_family == AF_INET6) { - struct sockaddr_in6 *expected, *received; - - expected = (struct sockaddr_in6 *) &(users[userid].host); - received = (struct sockaddr_in6 *) &(q->from); - return memcmp(&(expected->sin6_addr), &(received->sin6_addr), sizeof(struct in6_addr)); - } - /* Unknown address family */ - return 1; -} - -/* This checks that user has passed normal (non-raw) login challenge */ -static int -check_authenticated_user_and_ip(int userid, struct query *q) -{ - int res = check_user_and_ip(userid, q); - if (res) - return res; - - if (!users[userid].authenticated) - return 1; - - return 0; -} - -static void -send_raw(int fd, char *buf, int buflen, int user, int cmd, struct query *q) -{ - char packet[4096]; - int len; - - len = MIN(sizeof(packet) - RAW_HDR_LEN, buflen); - - memcpy(packet, raw_header, RAW_HDR_LEN); - if (len) { - memcpy(&packet[RAW_HDR_LEN], buf, len); - } - - len += RAW_HDR_LEN; - packet[RAW_HDR_CMD] = cmd | (user & 0x0F); - - if (debug >= 2) { - fprintf(stderr, "TX-raw: client %s, cmd %d, %d bytes\n", - format_addr(&q->from, q->fromlen), cmd, len); - } - - sendto(fd, packet, len, 0, (struct sockaddr *) &q->from, q->fromlen); -} - - -static void -start_new_outpacket(int userid, char *data, int datalen) -/* Copies data to .outpacket and resets all counters. - data is expected to be compressed already. */ -{ - datalen = MIN(datalen, sizeof(users[userid].outpacket.data)); - memcpy(users[userid].outpacket.data, data, datalen); - users[userid].outpacket.len = datalen; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outpacket.seqno = (users[userid].outpacket.seqno + 1) & 7; - users[userid].outpacket.fragment = 0; - users[userid].outfragresent = 0; -} - -#ifdef OUTPACKETQ_LEN - -static int -save_to_outpacketq(int userid, char *data, int datalen) -/* Find space in outpacket-queue and store data (expected compressed already). - Returns: 1 = okay, 0 = no space. */ -{ - int fill; - - if (users[userid].outpacketq_filled >= OUTPACKETQ_LEN) - /* no space */ - return 0; - - fill = users[userid].outpacketq_nexttouse + - users[userid].outpacketq_filled; - if (fill >= OUTPACKETQ_LEN) - fill -= OUTPACKETQ_LEN; - - datalen = MIN(datalen, sizeof(users[userid].outpacketq[fill].data)); - memcpy(users[userid].outpacketq[fill].data, data, datalen); - users[userid].outpacketq[fill].len = datalen; - - users[userid].outpacketq_filled++; - - if (debug >= 3) - fprintf(stderr, " Qstore, now %d\n", - users[userid].outpacketq_filled); - - return 1; -} - -static int -get_from_outpacketq(int userid) -/* Starts new outpacket from queue, if any. - Returns: 1 = okay, 0 = no packets were waiting. */ -{ - int use; - - if (users[userid].outpacketq_filled <= 0) - /* no packets */ - return 0; - - use = users[userid].outpacketq_nexttouse; - - start_new_outpacket(userid, users[userid].outpacketq[use].data, - users[userid].outpacketq[use].len); - - use++; - if (use >= OUTPACKETQ_LEN) - use = 0; - users[userid].outpacketq_nexttouse = use; - users[userid].outpacketq_filled--; - - if (debug >= 3) - fprintf(stderr, " Qget, now %d\n", - users[userid].outpacketq_filled); - - return 1; -} - -#endif /* OUTPACKETQ_LEN */ - -#ifdef DNSCACHE_LEN - -/* On the DNS cache: - - This cache is implemented to better handle the aggressively impatient DNS - servers that very quickly re-send requests when we choose to not - immediately answer them in lazy mode. This cache works much better than - pruning(=dropping) the improper requests, since the DNS server will - actually get an answer instead of silence. - - Because of the CMC in both ping and upstream data, unwanted cache hits - are prevented. Data-CMC is only 36 counts, so our cache length should - not exceed 36/2=18 packets. (This quick rule assumes all packets are - otherwise equal, which they arent: up/downstream seq/frag, tcp sequence - number, and of course data.) -*/ - -static void -save_to_dnscache(int userid, struct query *q, char *answer, int answerlen) -/* Store answer in our little DNS cache. */ -{ - int fill; - - if (answerlen > sizeof(users[userid].dnscache_answer[fill])) - return; /* can't store this */ - - fill = users[userid].dnscache_lastfilled + 1; - if (fill >= DNSCACHE_LEN) - fill = 0; - - memcpy(&(users[userid].dnscache_q[fill]), q, sizeof(struct query)); - memcpy(users[userid].dnscache_answer[fill], answer, answerlen); - users[userid].dnscache_answerlen[fill] = answerlen; - - users[userid].dnscache_lastfilled = fill; -} - -static int -answer_from_dnscache(int dns_fd, int userid, struct query *q) -/* Checks cache and sends repeated answer if we alreay saw this query recently. - Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is - a new query. */ -{ - int i; - int use; - - for (i = 0; i < DNSCACHE_LEN ; i++) { - /* Try cache most-recent-first */ - use = users[userid].dnscache_lastfilled - i; - if (use < 0) - use += DNSCACHE_LEN; - - if (users[userid].dnscache_q[use].id == 0) - continue; - if (users[userid].dnscache_answerlen[use] <= 0) - continue; - - if (users[userid].dnscache_q[use].type != q->type || - strcmp(users[userid].dnscache_q[use].name, q->name)) - continue; - - /* okay, match */ - if (debug >= 1) - fprintf(stderr, "OUT user %d %s from dnscache\n", userid, q->name); - - write_dns(dns_fd, q, users[userid].dnscache_answer[use], - users[userid].dnscache_answerlen[use], - users[userid].downenc); - - q->id = 0; /* this query was used */ - return 1; - } - - /* here only when no match found */ - return 0; -} - -#endif /* DNSCACHE_LEN */ - -static inline void -save_to_qmem(unsigned char *qmem_cmc, unsigned short *qmem_type, int qmem_len, - int *qmem_lastfilled, unsigned char *cmc_to_add, - unsigned short type_to_add) -/* Remember query to check for duplicates */ -{ - int fill; - - fill = *qmem_lastfilled + 1; - if (fill >= qmem_len) - fill = 0; - - memcpy(qmem_cmc + fill * 4, cmc_to_add, 4); - qmem_type[fill] = type_to_add; - *qmem_lastfilled = fill; -} - -static inline void -save_to_qmem_pingordata(int userid, struct query *q) -{ - /* Our CMC is a bit more than the "official" CMC; we store 4 bytes - just because we can, and because it may prevent some false matches. - For ping, we save the 4 decoded bytes: userid + seq/frag + CMC. - For data, we save the 4 _un_decoded chars in lowercase: seq/frag's - + 1 char CMC; that last char is non-Base32. - */ - - char cmc[8]; - int i; - - if (q->name[0] == 'P' || q->name[0] == 'p') { - /* Ping packet */ - - size_t cmcsize = sizeof(cmc); - char *cp = strchr(q->name, '.'); - - if (cp == NULL) - return; /* illegal hostname; shouldn't happen */ - - /* We already unpacked in handle_null_request(), but that's - lost now... Note: b32 directly, we want no undotify here! */ - i = b32->decode(cmc, &cmcsize, q->name + 1, (cp - q->name) - 1); - - if (i < 4) - return; /* illegal ping; shouldn't happen */ - - save_to_qmem(users[userid].qmemping_cmc, - users[userid].qmemping_type, QMEMPING_LEN, - &users[userid].qmemping_lastfilled, - (void *) cmc, q->type); - } else { - /* Data packet, hopefully not illegal */ - if (strlen(q->name) < 5) - return; - - /* We store CMC in lowercase; if routing via multiple parallel - DNS servers, one may do case-switch and another may not, - and we still want to detect duplicates. - Data-header is always base32, so case-swap won't hurt. - */ - for (i = 0; i < 4; i++) - if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') - cmc[i] = q->name[i+1] + ('a' - 'A'); - else - cmc[i] = q->name[i+1]; - - save_to_qmem(users[userid].qmemdata_cmc, - users[userid].qmemdata_type, QMEMDATA_LEN, - &users[userid].qmemdata_lastfilled, - (void *) cmc, q->type); - } -} - -static int -answer_from_qmem(int dns_fd, struct query *q, unsigned char *qmem_cmc, - unsigned short *qmem_type, int qmem_len, - unsigned char *cmc_to_check) -/* Checks query memory and sends an (illegal) answer if this is a duplicate. - Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is - not a duplicate. */ -{ - int i; - - for (i = 0; i < qmem_len ; i++) { - - if (qmem_type[i] == T_UNSET) - continue; - if (qmem_type[i] != q->type) - continue; - if (memcmp(qmem_cmc + i * 4, cmc_to_check, 4)) - continue; - - /* okay, match */ - if (debug >= 1) - fprintf(stderr, "OUT from qmem for %s == duplicate, sending illegal reply\n", q->name); - - write_dns(dns_fd, q, "x", 1, 'T'); - - q->id = 0; /* this query was used */ - return 1; - } - - /* here only when no match found */ - return 0; -} - -static inline int -answer_from_qmem_data(int dns_fd, int userid, struct query *q) -/* Quick helper function to keep handle_null_request() clean */ -{ - char cmc[4]; - int i; - - for (i = 0; i < 4; i++) - if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') - cmc[i] = q->name[i+1] + ('a' - 'A'); - else - cmc[i] = q->name[i+1]; - - return answer_from_qmem(dns_fd, q, users[userid].qmemdata_cmc, - users[userid].qmemdata_type, QMEMDATA_LEN, - (void *) cmc); -} - -static int -send_chunk_or_dataless(int dns_fd, int userid, struct query *q) -/* Sends current fragment to user, or dataless packet if there is no - current fragment available (-> normal "quiet" ping reply). - Does not update anything, except: - - discards q always (query is used) - - forgets entire users[userid].outpacket if it was sent in one go, - and then tries to get new packet from outpacket-queue - Returns: 1 = can call us again immediately, new packet from queue; - 0 = don't call us again for now. -*/ -{ - char pkt[4096]; - int datalen = 0; - int last = 0; - - /* If re-sent too many times, drop entire packet */ - if (users[userid].outpacket.len > 0 && - users[userid].outfragresent > 5) { - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outfragresent = 0; - -#ifdef OUTPACKETQ_LEN - /* Maybe more in queue, use immediately */ - get_from_outpacketq(userid); -#endif - } - - if (users[userid].outpacket.len > 0) { - datalen = MIN(users[userid].fragsize, users[userid].outpacket.len - users[userid].outpacket.offset); - datalen = MIN(datalen, sizeof(pkt)-2); - - memcpy(&pkt[2], users[userid].outpacket.data + users[userid].outpacket.offset, datalen); - users[userid].outpacket.sentlen = datalen; - last = (users[userid].outpacket.len == users[userid].outpacket.offset + datalen); - - users[userid].outfragresent++; - } - - /* Build downstream data header (see doc/proto_xxxxxxxx.txt) */ - - /* First byte is 1 bit compression flag, 3 bits upstream seqno, 4 bits upstream fragment */ - pkt[0] = (1<<7) | ((users[userid].inpacket.seqno & 7) << 4) | - (users[userid].inpacket.fragment & 15); - /* Second byte is 3 bits downstream seqno, 4 bits downstream fragment, 1 bit last flag */ - pkt[1] = ((users[userid].outpacket.seqno & 7) << 5) | - ((users[userid].outpacket.fragment & 15) << 1) | (last & 1); - - if (debug >= 1) { - fprintf(stderr, "OUT pkt seq# %d, frag %d (last=%d), offset %d, fragsize %d, total %d, to user %d\n", - users[userid].outpacket.seqno & 7, users[userid].outpacket.fragment & 15, - last, users[userid].outpacket.offset, datalen, users[userid].outpacket.len, userid); - } - write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); - - if (q->id2 != 0) { - q->id = q->id2; - q->fromlen = q->fromlen2; - memcpy(&(q->from), &(q->from2), q->fromlen2); - if (debug >= 1) - fprintf(stderr, "OUT again to last duplicate\n"); - write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); - } - - save_to_qmem_pingordata(userid, q); - -#ifdef DNSCACHE_LEN - save_to_dnscache(userid, q, pkt, datalen + 2); -#endif - - q->id = 0; /* this query is used */ - - if (datalen > 0 && datalen == users[userid].outpacket.len) { - /* Whole packet was sent in one chunk, dont wait for ack */ - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outfragresent = 0; - -#ifdef OUTPACKETQ_LEN - /* Maybe more in queue, prepare for next time */ - if (get_from_outpacketq(userid) == 1) { - if (debug >= 3) - fprintf(stderr, " Chunk & fromqueue: callagain\n"); - return 1; /* call us again */ - } -#endif - } - - return 0; /* don't call us again */ -} - -static int -tunnel_tun(int tun_fd, struct dnsfd *dns_fds) -{ - unsigned long outlen; - struct ip *header; - char out[64*1024]; - char in[64*1024]; - int userid; - int read; - - if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) - return 0; - - /* find target ip in packet, in is padded with 4 bytes TUN header */ - header = (struct ip*) (in + 4); - userid = find_user_by_ip(header->ip_dst.s_addr); - if (userid < 0) - return 0; - - outlen = sizeof(out); - compress2((uint8_t*)out, &outlen, (uint8_t*)in, read, 9); - - if (users[userid].conn == CONN_DNS_NULL) { -#ifdef OUTPACKETQ_LEN - /* If a packet is being sent, try storing the new one in the queue. - If the queue is full, drop the packet. TCP will hopefully notice - and reduce the packet rate. */ - if (users[userid].outpacket.len > 0) { - save_to_outpacketq(userid, out, outlen); - return 0; - } -#endif - - start_new_outpacket(userid, out, outlen); - - /* Start sending immediately if query is waiting */ - if (users[userid].q_sendrealsoon.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); - send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } else if (users[userid].q.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - } - - return outlen; - } else { /* CONN_RAW_UDP */ - int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); - send_raw(dns_fd, out, outlen, userid, RAW_HDR_CMD_DATA, &users[userid].q); - return outlen; - } -} - -typedef enum { - VERSION_ACK, - VERSION_NACK, - VERSION_FULL -} version_ack_t; - -static void -send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, struct query *q) -{ - char out[9]; - - switch (ack) { - case VERSION_ACK: - strncpy(out, "VACK", sizeof(out)); - break; - case VERSION_NACK: - strncpy(out, "VNAK", sizeof(out)); - break; - case VERSION_FULL: - strncpy(out, "VFUL", sizeof(out)); - break; - } - - out[4] = ((payload >> 24) & 0xff); - out[5] = ((payload >> 16) & 0xff); - out[6] = ((payload >> 8) & 0xff); - out[7] = ((payload) & 0xff); - out[8] = userid & 0xff; - - write_dns(fd, q, out, sizeof(out), users[userid].downenc); -} - -static void -process_downstream_ack(int userid, int down_seq, int down_frag) -/* Process acks from downstream fragments. - After this, .offset and .fragment are updated (if ack correct), - or .len is set to zero when all is done. -*/ -{ - if (users[userid].outpacket.len <= 0) - /* No packet to apply acks to */ - return; - - if (users[userid].outpacket.seqno != down_seq || - users[userid].outpacket.fragment != down_frag) - /* Not the ack we're waiting for; probably duplicate of old - ack, happens a lot with ping packets */ - return; - - /* Received proper ack */ - users[userid].outpacket.offset += users[userid].outpacket.sentlen; - users[userid].outpacket.sentlen = 0; - users[userid].outpacket.fragment++; - users[userid].outfragresent = 0; - - /* Is packet done? */ - if (users[userid].outpacket.offset >= users[userid].outpacket.len) { - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.fragment--; /* unneeded ++ above */ - /* ^keep last seqno/frag, are always returned on pings */ - /* users[userid].outfragresent = 0; already above */ - -#ifdef OUTPACKETQ_LEN - /* Possibly get new packet from queue */ - get_from_outpacketq(userid); -#endif - } -} - -static void -handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) -{ - struct in_addr tempip; - char in[512]; - char logindata[16]; - char out[64*1024]; - char unpacked[64*1024]; - char *tmp[2]; - int userid; - int read; - - userid = -1; - - /* Everything here needs at least two chars in the name */ - if (domain_len < 2) - return; - - memcpy(in, q->name, MIN(domain_len, sizeof(in))); - - if(in[0] == 'V' || in[0] == 'v') { - int version = 0; - - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - /* Version greeting, compare and send ack/nak */ - if (read > 4) { - /* Received V + 32bits version */ - version = (((unpacked[0] & 0xff) << 24) | - ((unpacked[1] & 0xff) << 16) | - ((unpacked[2] & 0xff) << 8) | - ((unpacked[3] & 0xff))); - } - - if (version == PROTOCOL_VERSION) { - userid = find_available_user(); - if (userid >= 0) { - int i; - - users[userid].seed = rand(); - /* Store remote IP number */ - memcpy(&(users[userid].host), &(q->from), q->fromlen); - users[userid].hostlen = q->fromlen; - - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].encoder = get_base32_encoder(); - users[userid].downenc = 'T'; - send_version_response(dns_fd, VERSION_ACK, users[userid].seed, userid, q); - syslog(LOG_INFO, "accepted version for user #%d from %s", - userid, format_addr(&q->from, q->fromlen)); - users[userid].q.id = 0; - users[userid].q.id2 = 0; - users[userid].q_sendrealsoon.id = 0; - users[userid].q_sendrealsoon.id2 = 0; - users[userid].q_sendrealsoon_new = 0; - users[userid].outpacket.len = 0; - users[userid].outpacket.offset = 0; - users[userid].outpacket.sentlen = 0; - users[userid].outpacket.seqno = 0; - users[userid].outpacket.fragment = 0; - users[userid].outfragresent = 0; - users[userid].inpacket.len = 0; - users[userid].inpacket.offset = 0; - users[userid].inpacket.seqno = 0; - users[userid].inpacket.fragment = 0; - users[userid].fragsize = 100; /* very safe */ - users[userid].conn = CONN_DNS_NULL; - users[userid].lazy = 0; -#ifdef OUTPACKETQ_LEN - users[userid].outpacketq_nexttouse = 0; - users[userid].outpacketq_filled = 0; -#endif -#ifdef DNSCACHE_LEN - { - for (i = 0; i < DNSCACHE_LEN; i++) { - users[userid].dnscache_q[i].id = 0; - users[userid].dnscache_answerlen[i] = 0; - } - } - users[userid].dnscache_lastfilled = 0; -#endif - for (i = 0; i < QMEMPING_LEN; i++) - users[userid].qmemping_type[i] = T_UNSET; - users[userid].qmemping_lastfilled = 0; - for (i = 0; i < QMEMDATA_LEN; i++) - users[userid].qmemdata_type[i] = T_UNSET; - users[userid].qmemdata_lastfilled = 0; - } else { - /* No space for another user */ - send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); - syslog(LOG_INFO, "dropped user from %s, server full", - format_addr(&q->from, q->fromlen)); - } - } else { - send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q); - syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", - format_addr(&q->from, q->fromlen), version); - } - return; - } else if(in[0] == 'L' || in[0] == 'l') { - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - if (read < 17) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Login phase, handle auth */ - userid = unpacked[0]; - - if (check_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - syslog(LOG_WARNING, "dropped login request from user #%d from unexpected source %s", - userid, format_addr(&q->from, q->fromlen)); - return; - } else { - users[userid].last_pkt = time(NULL); - login_calculate(logindata, 16, password, users[userid].seed); - - if (read >= 18 && (memcmp(logindata, unpacked+1, 16) == 0)) { - /* Store login ok */ - users[userid].authenticated = 1; - - /* Send ip/mtu/netmask info */ - tempip.s_addr = my_ip; - tmp[0] = strdup(inet_ntoa(tempip)); - tempip.s_addr = users[userid].tun_ip; - tmp[1] = strdup(inet_ntoa(tempip)); - - read = snprintf(out, sizeof(out), "%s-%s-%d-%d", - tmp[0], tmp[1], my_mtu, netmask); - - write_dns(dns_fd, q, out, read, users[userid].downenc); - q->id = 0; - syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); - - free(tmp[1]); - free(tmp[0]); - } else { - write_dns(dns_fd, q, "LNAK", 4, 'T'); - syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password", - userid, format_addr(&q->from, q->fromlen)); - } - } - return; - } else if(in[0] == 'I' || in[0] == 'i') { - /* Request for IP number */ - char reply[17]; - int length; - - userid = b32_8to5(in[1]); - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - reply[0] = 'I'; - if (q->from.ss_family == AF_INET) { - if (ns_ip != INADDR_ANY) { - /* If set, use assigned external ip (-n option) */ - memcpy(&reply[1], &ns_ip, sizeof(ns_ip)); - } else { - /* otherwise return destination ip from packet */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr)); - } - length = 1 + sizeof(struct in_addr); - } else { - struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; - memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr)); - length = 1 + sizeof(struct in6_addr); - } - - write_dns(dns_fd, q, reply, length, 'T'); - } else if(in[0] == 'Z' || in[0] == 'z') { - /* Check for case conservation and chars not allowed according to RFC */ - - /* Reply with received hostname as data */ - /* No userid here, reply with lowest-grade downenc */ - write_dns(dns_fd, q, in, domain_len, 'T'); - return; - } else if(in[0] == 'S' || in[0] == 's') { - int codec; - struct encoder *enc; - if (domain_len < 3) { /* len at least 3, example: "S15" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - userid = b32_8to5(in[1]); - - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - codec = b32_8to5(in[2]); - - switch (codec) { - case 5: /* 5 bits per byte = base32 */ - enc = get_base32_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 6: /* 6 bits per byte = base64 */ - enc = get_base64_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ - enc = get_base64u_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 7: /* 7 bits per byte = base128 */ - enc = get_base128_encoder(); - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - break; - } - return; - } else if(in[0] == 'O' || in[0] == 'o') { - if (domain_len < 3) { /* len at least 3, example: "O1T" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - userid = b32_8to5(in[1]); - - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - switch (in[2]) { - case 'T': - case 't': - users[userid].downenc = 'T'; - write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); - break; - case 'S': - case 's': - users[userid].downenc = 'S'; - write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); - break; - case 'U': - case 'u': - users[userid].downenc = 'U'; - write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); - break; - case 'V': - case 'v': - users[userid].downenc = 'V'; - write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); - break; - case 'R': - case 'r': - users[userid].downenc = 'R'; - write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); - break; - case 'L': - case 'l': - users[userid].lazy = 1; - write_dns(dns_fd, q, "Lazy", 4, users[userid].downenc); - break; - case 'I': - case 'i': - users[userid].lazy = 0; - write_dns(dns_fd, q, "Immediate", 9, users[userid].downenc); - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - break; - } - return; - } else if(in[0] == 'Y' || in[0] == 'y') { - int i; - char *datap; - int datalen; - - if (domain_len < 6) { /* len at least 6, example: "YTxCMC" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - i = b32_8to5(in[2]); /* check variant */ - - switch (i) { - case 1: - datap = DOWNCODECCHECK1; - datalen = DOWNCODECCHECK1_LEN; - break; - default: - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - switch (in[1]) { - case 'T': - case 't': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'T'); - return; - } - break; - case 'S': - case 's': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'S'); - return; - } - break; - case 'U': - case 'u': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'U'); - return; - } - break; - case 'V': - case 'v': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'V'); - return; - } - break; - case 'R': - case 'r': - if (q->type == T_NULL || q->type == T_TXT) { - write_dns(dns_fd, q, datap, datalen, 'R'); - return; - } - break; - } - - /* if still here, then codec not available */ - write_dns(dns_fd, q, "BADCODEC", 8, 'T'); - return; - - } else if(in[0] == 'R' || in[0] == 'r') { - int req_frag_size; - - if (domain_len < 16) { /* we'd better have some chars for data... */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Downstream fragsize probe packet */ - userid = (b32_8to5(in[1]) >> 1) & 15; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31); - if (req_frag_size < 2 || req_frag_size > 2047) { - write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); - } else { - char buf[2048]; - int i; - unsigned int v = ((unsigned int) rand()) & 0xff ; - - memset(buf, 0, sizeof(buf)); - buf[0] = (req_frag_size >> 8) & 0xff; - buf[1] = req_frag_size & 0xff; - /* make checkable pseudo-random sequence */ - buf[2] = 107; - for (i = 3; i < 2048; i++, v = (v + 107) & 0xff) - buf[i] = v; - write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); - } - return; - } else if(in[0] == 'N' || in[0] == 'n') { - int max_frag_size; - - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - - if (read < 3) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Downstream fragsize packet */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - max_frag_size = ((unpacked[1] & 0xff) << 8) | (unpacked[2] & 0xff); - if (max_frag_size < 2) { - write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); - } else { - users[userid].fragsize = max_frag_size; - write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc); - } - return; - } else if(in[0] == 'P' || in[0] == 'p') { - int dn_seq; - int dn_frag; - int didsend = 0; - - /* We can't handle id=0, that's "no packet" to us. So drop - request completely. Note that DNS servers rewrite the id. - We'll drop 1 in 64k times. If DNS server retransmits with - different id, then all okay. - Else client won't retransmit, and we'll just keep the - previous ping in cache, no problem either. */ - if (q->id == 0) - return; - - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - if (read < 4) - return; - - /* Ping packet, store userid */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - -#ifdef DNSCACHE_LEN - /* Check if cached */ - if (answer_from_dnscache(dns_fd, userid, q)) - return; -#endif - - /* Check if duplicate (and not in full dnscache any more) */ - if (answer_from_qmem(dns_fd, q, users[userid].qmemping_cmc, - users[userid].qmemping_type, QMEMPING_LEN, - (void *) unpacked)) - return; - - /* Check if duplicate of waiting queries; impatient DNS relays - like to re-try early and often (with _different_ .id!) */ - if (users[userid].q.id != 0 && - q->type == users[userid].q.type && - !strcmp(q->name, users[userid].q.name) && - users[userid].lazy) { - /* We have this ping already, and it's waiting to be - answered. Always keep the last duplicate, since the - relay may have forgotten its first version already. - Our answer will go to both. - (If we already sent an answer, qmem/cache will - have triggered.) */ - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q.id2 = q->id; - users[userid].q.fromlen2 = q->fromlen; - memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); - return; - } - - if (users[userid].q_sendrealsoon.id != 0 && - q->type == users[userid].q_sendrealsoon.type && - !strcmp(q->name, users[userid].q_sendrealsoon.name)) { - /* Outer select loop will send answer immediately, - to both queries. */ - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q_sendrealsoon.id2 = q->id; - users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), - &(q->from), q->fromlen); - return; - } - - dn_seq = unpacked[1] >> 4; - dn_frag = unpacked[1] & 15; - - if (debug >= 1) { - fprintf(stderr, "PING pkt from user %d, ack for downstream %d/%d\n", - userid, dn_seq, dn_frag); - } - - process_downstream_ack(userid, dn_seq, dn_frag); - - if (debug >= 3) { - fprintf(stderr, "PINGret (if any) will ack upstream %d/%d\n", - users[userid].inpacket.seqno, users[userid].inpacket.fragment); - } - - /* If there is a query that must be returned real soon, do it. - May contain new downstream data if the ping had a new ack. - Otherwise, may also be re-sending old data. */ - if (users[userid].q_sendrealsoon.id != 0) { - send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } - - /* We need to store a new query, so if there still is an - earlier query waiting, always send a reply to finish it. - May contain new downstream data if the ping had a new ack. - Otherwise, may also be re-sending old data. - (This is duplicate data if we had q_sendrealsoon above.) */ - if (users[userid].q.id != 0) { - didsend = 1; - if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); - - /* If anything waiting and we didn't already send above, send - it now. And always send immediately if we're not lazy - (then above won't have sent at all). */ - if ((!didsend && users[userid].outpacket.len > 0) || - !users[userid].lazy) - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - - } else if((in[0] >= '0' && in[0] <= '9') - || (in[0] >= 'a' && in[0] <= 'f') - || (in[0] >= 'A' && in[0] <= 'F')) { - int up_seq, up_frag, dn_seq, dn_frag, lastfrag; - int upstream_ok = 1; - int didsend = 0; - int code = -1; - - /* Need 5char header + >=1 char data */ - if (domain_len < 6) - return; - - /* We can't handle id=0, that's "no packet" to us. So drop - request completely. Note that DNS servers rewrite the id. - We'll drop 1 in 64k times. If DNS server retransmits with - different id, then all okay. - Else client doesn't get our ack, and will retransmit in - 1 second. */ - if (q->id == 0) - return; - - if ((in[0] >= '0' && in[0] <= '9')) - code = in[0] - '0'; - if ((in[0] >= 'a' && in[0] <= 'f')) - code = in[0] - 'a' + 10; - if ((in[0] >= 'A' && in[0] <= 'F')) - code = in[0] - 'A' + 10; - - userid = code; - /* Check user and sending ip number */ - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - -#ifdef DNSCACHE_LEN - /* Check if cached */ - if (answer_from_dnscache(dns_fd, userid, q)) - return; -#endif - - /* Check if duplicate (and not in full dnscache any more) */ - if (answer_from_qmem_data(dns_fd, userid, q)) - return; - - /* Check if duplicate of waiting queries; impatient DNS relays - like to re-try early and often (with _different_ .id!) */ - if (users[userid].q.id != 0 && - q->type == users[userid].q.type && - !strcmp(q->name, users[userid].q.name) && - users[userid].lazy) { - /* We have this packet already, and it's waiting to be - answered. Always keep the last duplicate, since the - relay may have forgotten its first version already. - Our answer will go to both. - (If we already sent an answer, qmem/cache will - have triggered.) */ - if (debug >= 2) { - fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q.id2 = q->id; - users[userid].q.fromlen2 = q->fromlen; - memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); - return; - } - - if (users[userid].q_sendrealsoon.id != 0 && - q->type == users[userid].q_sendrealsoon.type && - !strcmp(q->name, users[userid].q_sendrealsoon.name)) { - /* Outer select loop will send answer immediately, - to both queries. */ - if (debug >= 2) { - fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q_sendrealsoon.id2 = q->id; - users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), - &(q->from), q->fromlen); - return; - } - - - /* Decode data header */ - up_seq = (b32_8to5(in[1]) >> 2) & 7; - up_frag = ((b32_8to5(in[1]) & 3) << 2) | ((b32_8to5(in[2]) >> 3) & 3); - dn_seq = (b32_8to5(in[2]) & 7); - dn_frag = b32_8to5(in[3]) >> 1; - lastfrag = b32_8to5(in[3]) & 1; - - process_downstream_ack(userid, dn_seq, dn_frag); - - if (up_seq == users[userid].inpacket.seqno && - up_frag <= users[userid].inpacket.fragment) { - /* Got repeated old packet _with data_, probably - because client didn't receive our ack. So re-send - our ack(+data) immediately to keep things flowing - fast. - If it's a _really_ old frag, it's a nameserver - that tries again, and sending our current (non- - matching) fragno won't be a problem. */ - if (debug >= 1) { - fprintf(stderr, "IN pkt seq# %d, frag %d, dropped duplicate frag\n", - up_seq, up_frag); - } - upstream_ok = 0; - } - else if (up_seq != users[userid].inpacket.seqno && - recent_seqno(users[userid].inpacket.seqno, up_seq)) { - /* Duplicate of recent upstream data packet; probably - need to answer this to keep DNS server happy */ - if (debug >= 1) { - fprintf(stderr, "IN pkt seq# %d, frag %d, dropped duplicate recent seqno\n", - up_seq, up_frag); - } - upstream_ok = 0; - } - else if (up_seq != users[userid].inpacket.seqno) { - /* Really new packet has arrived, no recent duplicate */ - /* Forget any old packet, even if incomplete */ - users[userid].inpacket.seqno = up_seq; - users[userid].inpacket.fragment = up_frag; - users[userid].inpacket.len = 0; - users[userid].inpacket.offset = 0; - } else { - /* seq is same, frag is higher; don't care about - missing fragments, TCP checksum will fail */ - users[userid].inpacket.fragment = up_frag; - } - - if (debug >= 3) { - fprintf(stderr, "INpack with upstream %d/%d, we are going to ack upstream %d/%d\n", - up_seq, up_frag, - users[userid].inpacket.seqno, users[userid].inpacket.fragment); - } - - if (upstream_ok) { - /* decode with this user's encoding */ - read = unpack_data(unpacked, sizeof(unpacked), &(in[5]), domain_len - 5, - users[userid].encoder); - - /* copy to packet buffer, update length */ - read = MIN(read, sizeof(users[userid].inpacket.data) - users[userid].inpacket.offset); - memcpy(users[userid].inpacket.data + users[userid].inpacket.offset, unpacked, read); - users[userid].inpacket.len += read; - users[userid].inpacket.offset += read; - - if (debug >= 1) { - fprintf(stderr, "IN pkt seq# %d, frag %d (last=%d), fragsize %d, total %d, from user %d\n", - up_seq, up_frag, lastfrag, read, users[userid].inpacket.len, userid); - } - } - - if (upstream_ok && lastfrag) { /* packet is complete */ - handle_full_packet(tun_fd, dns_fds, userid); - } - - /* If there is a query that must be returned real soon, do it. - Includes an ack of the just received upstream fragment, - may contain new data. */ - if (users[userid].q_sendrealsoon.id != 0) { - didsend = 1; - if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } - - /* If we already have an earlier query waiting, we need to - get rid of it to store the new query. - - If we have new data waiting and not yet sent above, - send immediately. - - If this wasn't the last upstream fragment, then we expect - more, so ack immediately if we didn't already. - - If we are in non-lazy mode, there should be no query - waiting, but if there is, send immediately. - - In all other cases (mostly the last-fragment cases), - we can afford to wait just a tiny little while for the - TCP ack to arrive from our tun. Note that this works best - when there is only one client. - */ - if (users[userid].q.id != 0) { - if ((users[userid].outpacket.len > 0 && !didsend) || - (upstream_ok && !lastfrag && !didsend) || - (!upstream_ok && !didsend) || - !users[userid].lazy) { - didsend = 1; - if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } else { - memcpy(&(users[userid].q_sendrealsoon), - &(users[userid].q), - sizeof(struct query)); - users[userid].q_sendrealsoon_new = 1; - users[userid].q.id = 0; /* used */ - didsend = 1; - } - } - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); - - /* If we still need to ack this upstream frag, do it to keep - upstream flowing. - - If we have new data waiting and not yet sent above, - send immediately. - - If this wasn't the last upstream fragment, then we expect - more, so ack immediately if we didn't already or are - in non-lazy mode. - - If this was the last fragment, and we didn't ack already - or are in non-lazy mode, send the ack after just a tiny - little while so that the TCP ack may have arrived from - our tun device. - - In all other cases, don't send anything now. - */ - if (users[userid].outpacket.len > 0 && !didsend) - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - else if (!didsend || !users[userid].lazy) { - if (upstream_ok && lastfrag) { - memcpy(&(users[userid].q_sendrealsoon), - &(users[userid].q), - sizeof(struct query)); - users[userid].q_sendrealsoon_new = 1; - users[userid].q.id = 0; /* used */ - } else { - send_chunk_or_dataless(dns_fd, userid, &users[userid].q); - } - } - } -} - -static void -handle_ns_request(int dns_fd, struct query *q) -/* Mostly identical to handle_a_request() below */ -{ - char buf[64*1024]; - int len; - - if (ns_ip != INADDR_ANY) { - /* If ns_ip set, overwrite destination addr with it. - * Destination addr will be sent as additional record (A, IN) */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); - } - - len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain); - if (len < 1) { - warnx("dns_encode_ns_response doesn't fit"); - return; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes NS reply\n", - format_addr(&q->from, q->fromlen), q->type, q->name, len); - } - if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { - warn("ns reply send error"); - } -} - -static void -handle_a_request(int dns_fd, struct query *q, int fakeip) -/* Mostly identical to handle_ns_request() above */ -{ - char buf[64*1024]; - int len; - - if (fakeip) { - in_addr_t ip = inet_addr("127.0.0.1"); - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ip, sizeof(ip)); - - } else if (ns_ip != INADDR_ANY) { - /* If ns_ip set, overwrite destination addr with it. - * Destination addr will be sent as additional record (A, IN) */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); - } - - len = dns_encode_a_response(buf, sizeof(buf), q); - if (len < 1) { - warnx("dns_encode_a_response doesn't fit"); - return; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes A reply\n", - format_addr(&q->from, q->fromlen), q->type, q->name, len); - } - if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { - warn("a reply send error"); - } -} - -static void -forward_query(int bind_fd, struct query *q) -{ - char buf[64*1024]; - int len; - struct fw_query fwq; - struct sockaddr_in *myaddr; - in_addr_t newaddr; - - len = dns_encode(buf, sizeof(buf), q, QR_QUERY, q->name, strlen(q->name)); - if (len < 1) { - warnx("dns_encode doesn't fit"); - return; - } - - /* Store sockaddr for q->id */ - memcpy(&(fwq.addr), &(q->from), q->fromlen); - fwq.addrlen = q->fromlen; - fwq.id = q->id; - fw_query_put(&fwq); - - newaddr = inet_addr("127.0.0.1"); - myaddr = (struct sockaddr_in *) &(q->from); - memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t)); - myaddr->sin_port = htons(bind_port); - - if (debug >= 2) { - fprintf(stderr, "TX: NS reply \n"); - } - - if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { - warn("forward query error"); - } -} - -static int -tunnel_bind(int bind_fd, struct dnsfd *dns_fds) -{ - char packet[64*1024]; - struct sockaddr_storage from; - socklen_t fromlen; - struct fw_query *query; - unsigned short id; - int dns_fd; - int r; - - fromlen = sizeof(struct sockaddr); - r = recvfrom(bind_fd, packet, sizeof(packet), 0, - (struct sockaddr*)&from, &fromlen); - - if (r <= 0) - return 0; - - id = dns_get_id(packet, r); - - if (debug >= 2) { - fprintf(stderr, "RX: Got response on query %u from DNS\n", (id & 0xFFFF)); - } - - /* Get sockaddr from id */ - fw_query_get(id, &query); - if (!query) { - if (debug >= 2) { - fprintf(stderr, "Lost sender of id %u, dropping reply\n", (id & 0xFFFF)); - } - return 0; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s id %u, %d bytes\n", - format_addr(&query->addr, query->addrlen), (id & 0xffff), r); - } - - dns_fd = get_dns_fd(dns_fds, &query->addr); - if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), - query->addrlen) <= 0) { - warn("forward reply error"); - } - - return 0; -} - -static int -tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) -{ - struct query q; - int read; - int domain_len; - int inside_topdomain = 0; - - if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) - return 0; - - if (debug >= 2) { - fprintf(stderr, "RX: client %s, type %d, name %s\n", - format_addr(&q.from, q.fromlen), q.type, q.name); - } - - domain_len = strlen(q.name) - strlen(topdomain); - if (domain_len >= 0 && !strcasecmp(q.name + domain_len, topdomain)) - inside_topdomain = 1; - /* require dot before topdomain */ - if (domain_len >= 1 && q.name[domain_len - 1] != '.') - inside_topdomain = 0; - - if (inside_topdomain) { - /* This is a query we can handle */ - - /* Handle A-type query for ns.topdomain, possibly caused - by our proper response to any NS request */ - if (domain_len == 3 && q.type == T_A && - (q.name[0] == 'n' || q.name[0] == 'N') && - (q.name[1] == 's' || q.name[1] == 'S') && - q.name[2] == '.') { - handle_a_request(dns_fd, &q, 0); - return 0; - } - - /* Handle A-type query for www.topdomain, for anyone that's - poking around */ - if (domain_len == 4 && q.type == T_A && - (q.name[0] == 'w' || q.name[0] == 'W') && - (q.name[1] == 'w' || q.name[1] == 'W') && - (q.name[2] == 'w' || q.name[2] == 'W') && - q.name[3] == '.') { - handle_a_request(dns_fd, &q, 1); - return 0; - } - - switch (q.type) { - case T_NULL: - case T_PRIVATE: - case T_CNAME: - case T_A: - case T_MX: - case T_SRV: - case T_TXT: - /* encoding is "transparent" here */ - handle_null_request(tun_fd, dns_fd, dns_fds, &q, domain_len); - break; - case T_NS: - handle_ns_request(dns_fd, &q); - break; - default: - break; - } - } else { - /* Forward query to other port ? */ - if (bind_fd) { - forward_query(bind_fd, &q); - } - } - return 0; -} - -static int -tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) -{ - struct timeval tv; - fd_set fds; - int i; - int userid; - time_t last_action = time(NULL); - - while (running) { - int maxfd; - tv.tv_sec = 10; /* doesn't really matter */ - tv.tv_usec = 0; - - /* Adjust timeout if there is anything to send realsoon. - Clients won't be sending new data until we send our ack, - so don't keep them waiting long. This only triggers at - final upstream fragments, which is about once per eight - requests during heavy upstream traffic. - 20msec: ~8 packs every 1/50sec = ~400 DNSreq/sec, - or ~1200bytes every 1/50sec = ~0.5 Mbit/sec upstream */ - for (userid = 0; userid < created_users; userid++) { - if (users[userid].active && !users[userid].disabled && - users[userid].last_pkt + 60 > time(NULL)) { - users[userid].q_sendrealsoon_new = 0; - if (users[userid].q_sendrealsoon.id != 0) { - tv.tv_sec = 0; - tv.tv_usec = 20000; - } - } - } - - FD_ZERO(&fds); - maxfd = 0; - - if (dns_fds->v4fd >= 0) { - FD_SET(dns_fds->v4fd, &fds); - maxfd = MAX(dns_fds->v4fd, maxfd); - } - if (dns_fds->v6fd >= 0) { - FD_SET(dns_fds->v6fd, &fds); - maxfd = MAX(dns_fds->v6fd, maxfd); - } - - if (bind_fd) { - /* wait for replies from real DNS */ - FD_SET(bind_fd, &fds); - maxfd = MAX(bind_fd, maxfd); - } - - /* Don't read from tun if no users can accept data anyway; - tun queue/TCP buffers are larger than our outpacket-queues */ - if(!all_users_waiting_to_send()) { - FD_SET(tun_fd, &fds); - maxfd = MAX(tun_fd, maxfd); - } - - i = select(maxfd + 1, &fds, NULL, NULL, &tv); - - if(i < 0) { - if (running) - warn("select"); - return 1; - } - - if (i==0) { - if (max_idle_time) { - /* only trigger the check if that's worth ( ie, no need to loop over if there - is something to send */ - if (last_action + max_idle_time < time(NULL)) { - for (userid = 0; userid < created_users; userid++) { - last_action = ( users[userid].last_pkt > last_action ) ? users[userid].last_pkt : last_action; - } - if (last_action + max_idle_time < time(NULL)) { - fprintf(stderr, "Idling since too long, shutting down...\n"); - running = 0; - } - } - } - } else { - if (FD_ISSET(tun_fd, &fds)) { - tunnel_tun(tun_fd, dns_fds); - } - if (FD_ISSET(dns_fds->v4fd, &fds)) { - tunnel_dns(tun_fd, dns_fds->v4fd, dns_fds, bind_fd); - } - if (FD_ISSET(dns_fds->v6fd, &fds)) { - tunnel_dns(tun_fd, dns_fds->v6fd, dns_fds, bind_fd); - } - if (FD_ISSET(bind_fd, &fds)) { - tunnel_bind(bind_fd, dns_fds); - } - } - - /* Send realsoon's if tun or dns didn't already */ - for (userid = 0; userid < created_users; userid++) - if (users[userid].active && !users[userid].disabled && - users[userid].last_pkt + 60 > time(NULL) && - users[userid].q_sendrealsoon.id != 0 && - users[userid].conn == CONN_DNS_NULL && - !users[userid].q_sendrealsoon_new) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); - send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } - } - - return 0; -} - -static void -handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid) -{ - unsigned long outlen; - char out[64*1024]; - int touser; - int ret; - - outlen = sizeof(out); - ret = uncompress((uint8_t*)out, &outlen, - (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len); - - if (ret == Z_OK) { - struct ip *hdr; - - hdr = (struct ip*) (out + 4); - touser = find_user_by_ip(hdr->ip_dst.s_addr); - - if (touser == -1) { - /* send the uncompressed packet to tun device */ - write_tun(tun_fd, out, outlen); - } else { - /* send the compressed(!) packet to other client */ - if (users[touser].conn == CONN_DNS_NULL) { - if (users[touser].outpacket.len == 0) { - start_new_outpacket(touser, - users[userid].inpacket.data, - users[userid].inpacket.len); - - /* Start sending immediately if query is waiting */ - if (users[touser].q_sendrealsoon.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[touser].q_sendrealsoon.from); - send_chunk_or_dataless(dns_fd, touser, &users[touser].q_sendrealsoon); - } else if (users[touser].q.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); - send_chunk_or_dataless(dns_fd, touser, &users[touser].q); - } -#ifdef OUTPACKETQ_LEN - } else { - save_to_outpacketq(touser, - users[userid].inpacket.data, - users[userid].inpacket.len); -#endif - } - } else{ /* CONN_RAW_UDP */ - int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); - send_raw(dns_fd, users[userid].inpacket.data, - users[userid].inpacket.len, touser, - RAW_HDR_CMD_DATA, &users[touser].q); - } - } - } else { - if (debug >= 1) - fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret); - } - - /* This packet is done */ - users[userid].inpacket.len = 0; - users[userid].inpacket.offset = 0; -} - -static void -handle_raw_login(char *packet, int len, struct query *q, int fd, int userid) -{ - char myhash[16]; - - if (len < 16) return; - - /* can't use check_authenticated_user_and_ip() since IP address will be different, - so duplicate here except IP address */ - if (userid < 0 || userid >= created_users) return; - if (!users[userid].active || users[userid].disabled) return; - if (!users[userid].authenticated) return; - if (users[userid].last_pkt + 60 < time(NULL)) return; - - if (debug >= 1) { - fprintf(stderr, "IN login raw, len %d, from user %d\n", - len, userid); - } - - /* User sends hash of seed + 1 */ - login_calculate(myhash, 16, password, users[userid].seed + 1); - if (memcmp(packet, myhash, 16) == 0) { - /* Update query and time info for user */ - users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); - - /* Store remote IP number */ - memcpy(&(users[userid].host), &(q->from), q->fromlen); - users[userid].hostlen = q->fromlen; - - /* Correct hash, reply with hash of seed - 1 */ - user_set_conn_type(userid, CONN_RAW_UDP); - login_calculate(myhash, 16, password, users[userid].seed - 1); - send_raw(fd, myhash, 16, userid, RAW_HDR_CMD_LOGIN, q); - - users[userid].authenticated_raw = 1; - } -} - -static void -handle_raw_data(char *packet, int len, struct query *q, struct dnsfd *dns_fds, int tun_fd, int userid) -{ - if (check_authenticated_user_and_ip(userid, q) != 0) { - return; - } - if (!users[userid].authenticated_raw) return; - - /* Update query and time info for user */ - users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); - - /* copy to packet buffer, update length */ - users[userid].inpacket.offset = 0; - memcpy(users[userid].inpacket.data, packet, len); - users[userid].inpacket.len = len; - - if (debug >= 1) { - fprintf(stderr, "IN pkt raw, total %d, from user %d\n", - users[userid].inpacket.len, userid); - } - - handle_full_packet(tun_fd, dns_fds, userid); -} - -static void -handle_raw_ping(struct query *q, int dns_fd, int userid) -{ - if (check_authenticated_user_and_ip(userid, q) != 0) { - return; - } - if (!users[userid].authenticated_raw) return; - - /* Update query and time info for user */ - users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); - - if (debug >= 1) { - fprintf(stderr, "IN ping raw, from user %d\n", userid); - } - - /* Send ping reply */ - send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, q); -} - -static int -raw_decode(char *packet, int len, struct query *q, int dns_fd, struct dnsfd *dns_fds, int tun_fd) -{ - int raw_user; - - /* minimum length */ - if (len < RAW_HDR_LEN) return 0; - /* should start with header */ - if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0; - - raw_user = RAW_HDR_GET_USR(packet); - switch (RAW_HDR_GET_CMD(packet)) { - case RAW_HDR_CMD_LOGIN: - /* Login challenge */ - handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, raw_user); - break; - case RAW_HDR_CMD_DATA: - /* Data packet */ - handle_raw_data(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fds, tun_fd, raw_user); - break; - case RAW_HDR_CMD_PING: - /* Keepalive packet */ - handle_raw_ping(q, dns_fd, raw_user); - break; - default: - warnx("Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); - break; - } - return 1; -} - -static int -read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) -/* FIXME: dns_fds and tun_fd are because of raw_decode() below */ -{ - struct sockaddr_storage from; - socklen_t addrlen; - char packet[64*1024]; - int r; -#ifndef WINDOWS32 - char control[CMSG_SPACE(sizeof (struct in6_pktinfo))]; - struct msghdr msg; - struct iovec iov; - struct cmsghdr *cmsg; - - addrlen = sizeof(struct sockaddr_storage); - iov.iov_base = packet; - iov.iov_len = sizeof(packet); - - msg.msg_name = (caddr_t) &from; - msg.msg_namelen = (unsigned) addrlen; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - msg.msg_flags = 0; - - r = recvmsg(fd, &msg, 0); -#else - addrlen = sizeof(struct sockaddr_storage); - r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen); -#endif /* !WINDOWS32 */ - - if (r > 0) { - memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen); - q->fromlen = addrlen; - - /* TODO do not handle raw packets here! */ - if (raw_decode(packet, r, q, fd, dns_fds, tun_fd)) { - return 0; - } - if (dns_decode(NULL, 0, q, QR_QUERY, packet, r) < 0) { - return 0; - } - -#ifndef WINDOWS32 - memset(&q->destination, 0, sizeof(struct sockaddr_storage)); - /* Read destination IP address */ - for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - - if (cmsg->cmsg_level == IPPROTO_IP && - cmsg->cmsg_type == DSTADDR_SOCKOPT) { - - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - addr->sin_family = AF_INET; - addr->sin_addr = *dstaddr(cmsg); - q->dest_len = sizeof(*addr); - break; - } - if (cmsg->cmsg_level == IPPROTO_IPV6 && - cmsg->cmsg_type == IPV6_PKTINFO) { - - struct in6_pktinfo *pktinfo; - struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; - pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsg); - addr->sin6_family = AF_INET6; - memcpy(&addr->sin6_addr, &pktinfo->ipi6_addr, sizeof(struct in6_addr)); - q->dest_len = sizeof(*addr); - break; - } - } -#endif - - return strlen(q->name); - } else if (r < 0) { - /* Error */ - warn("read dns"); - } - - return 0; -} - -static size_t -write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downenc) -/* Returns #bytes of data that were encoded */ -{ - static int td1 = 0; - static int td2 = 0; - size_t space; - char *b; - - /* Make a rotating topdomain to prevent filtering */ - td1+=3; - td2+=7; - if (td1>=26) td1-=26; - if (td2>=25) td2-=25; - - /* encode data,datalen to CNAME/MX answer - (adapted from build_hostname() in encoding.c) - */ - - space = MIN(0xFF, buflen) - 4 - 2; - /* -1 encoding type, -3 ".xy", -2 for safety */ - - memset(buf, 0, buflen); - - if (downenc == 'S') { - buf[0] = 'i'; - if (!b64->places_dots()) - space -= (space / 57); /* space for dots */ - b64->encode(buf+1, &space, data, datalen); - if (!b64->places_dots()) - inline_dotify(buf, buflen); - } else if (downenc == 'U') { - buf[0] = 'j'; - if (!b64u->places_dots()) - space -= (space / 57); /* space for dots */ - b64u->encode(buf+1, &space, data, datalen); - if (!b64u->places_dots()) - inline_dotify(buf, buflen); - } else if (downenc == 'V') { - buf[0] = 'k'; - if (!b128->places_dots()) - space -= (space / 57); /* space for dots */ - b128->encode(buf+1, &space, data, datalen); - if (!b128->places_dots()) - inline_dotify(buf, buflen); - } else { - buf[0] = 'h'; - if (!b32->places_dots()) - space -= (space / 57); /* space for dots */ - b32->encode(buf+1, &space, data, datalen); - if (!b32->places_dots()) - inline_dotify(buf, buflen); - } - - /* Add dot (if it wasn't there already) and topdomain */ - b = buf; - b += strlen(buf) - 1; - if (*b != '.') - *++b = '.'; - b++; - - *b = 'a' + td1; - b++; - *b = 'a' + td2; - b++; - *b = '\0'; - - return space; -} - -static void -write_dns(int fd, struct query *q, char *data, int datalen, char downenc) -{ - char buf[64*1024]; - int len = 0; - - if (q->type == T_CNAME || q->type == T_A) { - char cnamebuf[1024]; /* max 255 */ - - write_dns_nameenc(cnamebuf, sizeof(cnamebuf), - data, datalen, downenc); - - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, - sizeof(cnamebuf)); - } else if (q->type == T_MX || q->type == T_SRV) { - char mxbuf[64*1024]; - char *b = mxbuf; - int offset = 0; - int res; - - while (1) { - res = write_dns_nameenc(b, sizeof(mxbuf) - (b - mxbuf), - data + offset, - datalen - offset, downenc); - if (res < 1) { - /* nothing encoded */ - b++; /* for final \0 */ - break; - } - - b = b + strlen(b) + 1; - - offset += res; - if (offset >= datalen) - break; - } - - /* Add final \0 */ - *b = '\0'; - - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, mxbuf, - sizeof(mxbuf)); - } else if (q->type == T_TXT) { - /* TXT with base32 */ - char txtbuf[64*1024]; - size_t space = sizeof(txtbuf) - 1;; - - memset(txtbuf, 0, sizeof(txtbuf)); - - if (downenc == 'S') { - txtbuf[0] = 's'; /* plain base64(Sixty-four) */ - len = b64->encode(txtbuf+1, &space, data, datalen); - } - else if (downenc == 'U') { - txtbuf[0] = 'u'; /* Base64 with Underscore */ - len = b64u->encode(txtbuf+1, &space, data, datalen); - } - else if (downenc == 'V') { - txtbuf[0] = 'v'; /* Base128 */ - len = b128->encode(txtbuf+1, &space, data, datalen); - } - else if (downenc == 'R') { - txtbuf[0] = 'r'; /* Raw binary data */ - len = MIN(datalen, sizeof(txtbuf) - 1); - memcpy(txtbuf + 1, data, len); - } else { - txtbuf[0] = 't'; /* plain base32(Thirty-two) */ - len = b32->encode(txtbuf+1, &space, data, datalen); - } - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, txtbuf, len+1); - } else { - /* Normal NULL-record encode */ - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); - } - - if (len < 1) { - warnx("dns_encode doesn't fit"); - return; - } - - if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes data\n", - format_addr(&q->from, q->fromlen), q->type, q->name, datalen); - } - - sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); -} - static void print_usage() { extern char *__progname; @@ -2343,7 +207,7 @@ help() { static void version() { fprintf(stderr, "iodine IP over DNS tunneling server\n"); - fprintf(stderr, "Git version: %s\n", GITREVISION); + fprintf(stderr, "Git version: %s\n; protocol version %08X", GITREVISION, PROTOCOL_VERSION); exit(0); } @@ -2422,19 +286,11 @@ main(int argc, char **argv) listen_ip4 = NULL; listen_ip6 = NULL; port = 53; - ns_ip = INADDR_ANY; ns_get_externalip = 0; addrfamily = AF_UNSPEC; - check_ip = 1; skipipconfig = 0; - debug = 0; - netmask = 27; pidfile = NULL; - - b32 = get_base32_encoder(); - b64 = get_base64_encoder(); - b64u = get_base64u_encoder(); - b128 = get_base128_encoder(); + srand(time(NULL)); retval = 0; @@ -2450,10 +306,6 @@ main(int argc, char **argv) __progname++; #endif - memset(password, 0, sizeof(password)); - srand(time(NULL)); - fw_query_init(); - while ((choice = getopt(argc, argv, "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { switch(choice) { case '4': @@ -2751,7 +603,7 @@ main(int argc, char **argv) syslog(LOG_INFO, "started, listening on port %d", port); - tunnel(tun_fd, &dns_fds, bind_fd, max_idle_time); + server_tunnel(tun_fd, &dns_fds, bind_fd, max_idle_time); syslog(LOG_INFO, "stopping"); close_dns(bind_fd); diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..39be412 --- /dev/null +++ b/src/server.c @@ -0,0 +1,1877 @@ +/* + * Copyright (c) 2006-2015 Erik Ekman , + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "version.h" + +#include "dns.h" +#include "encoding.h" +#include "base32.h" +#include "base64.h" +#include "base64u.h" +#include "base128.h" +#include "user.h" +#include "login.h" +#include "tun.h" +#include "fw_query.h" +#include "server.h" + +#ifdef HAVE_SYSTEMD +# include +#endif + +#ifdef WINDOWS32 +WORD req_version = MAKEWORD(2, 2); +WSADATA wsa_data; +#endif + +/* Global server variables */ +int running = 1; +char *topdomain; +char password[33]; +struct encoder *b32; +struct encoder *b64; +struct encoder *b64u; +struct encoder *b128; + +int check_ip; +int my_mtu; +in_addr_t my_ip; +int netmask; + +in_addr_t ns_ip; + +int bind_port; +int debug; + +void +server_init() +{ + running = 1; + ns_ip = INADDR_ANY; + netmask = 27; + debug = 0; + check_ip = 1; + memset(password, 0, sizeof(password)); + fw_query_init(); + b32 = get_base32_encoder(); + b64 = get_base64_encoder(); + b64u = get_base64u_encoder(); + b128 = get_base128_encoder(); +} + +void +server_stop() +{ + running = 0; +} + +static void +send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct query *q) +{ + char packet[4096]; + int len; + + len = MIN(sizeof(packet) - RAW_HDR_LEN, buflen); + + memcpy(packet, raw_header, RAW_HDR_LEN); + if (len) { + memcpy(&packet[RAW_HDR_LEN], buf, len); + } + + len += RAW_HDR_LEN; + packet[RAW_HDR_CMD] = cmd | (user & 0x0F); + + if (debug >= 2) { + fprintf(stderr, "TX-raw: client %s, cmd %d, %d bytes\n", + format_addr(&q->from, q->fromlen), cmd, len); + } + + sendto(fd, packet, len, 0, (struct sockaddr *) &q->from, q->fromlen); +} + + +static void +start_new_outpacket(int userid, uint8_t *data, size_t datalen) +/* Copies data to .outpacket and resets all counters. + data is expected to be compressed already. */ +{ + // TODO: window add to outgoing data +} + +int +answer_from_qmem(int dns_fd, struct query *q, unsigned char *qmem_cmc, + unsigned short *qmem_type, int qmem_len, + unsigned char *cmc_to_check) +/* Checks query memory and sends an (illegal) answer if this is a duplicate. + Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is + not a duplicate. */ +{ + int i; + + for (i = 0; i < qmem_len ; i++) { + + if (qmem_type[i] == T_UNSET) + continue; + if (qmem_type[i] != q->type) + continue; + if (memcmp(qmem_cmc + i * 4, cmc_to_check, 4)) + continue; + + /* okay, match */ + if (debug >= 1) + fprintf(stderr, "OUT from qmem for %s == duplicate, sending illegal reply\n", q->name); + + write_dns(dns_fd, q, "x", 1, 'T'); + + q->id = 0; /* this query was used */ + return 1; + } + + /* here only when no match found */ + return 0; +} + +/* INLINE FUNCTION DEFINITIONS */ +static inline void +save_to_qmem(unsigned char *qmem_cmc, unsigned short *qmem_type, int qmem_len, + int *qmem_lastfilled, unsigned char *cmc_to_add, + unsigned short type_to_add) +/* Remember query to check for duplicates */ +{ + int fill; + + fill = *qmem_lastfilled + 1; + if (fill >= qmem_len) + fill = 0; + + memcpy(qmem_cmc + fill * 4, cmc_to_add, 4); + qmem_type[fill] = type_to_add; + *qmem_lastfilled = fill; +} + +static inline void +save_to_qmem_pingordata(int userid, struct query *q) +{ + /* Our CMC is a bit more than the "official" CMC; we store 4 bytes + just because we can, and because it may prevent some false matches. + For ping, we save the 4 decoded bytes: userid + seq/frag + CMC. + For data, we save the 4 _un_decoded chars in lowercase: seq/frag's + + 1 char CMC; that last char is non-Base32. + */ + + char cmc[8]; + int i; + + if (q->name[0] == 'P' || q->name[0] == 'p') { + /* Ping packet */ + + size_t cmcsize = sizeof(cmc); + char *cp = strchr(q->name, '.'); + + if (cp == NULL) + return; /* illegal hostname; shouldn't happen */ + + /* We already unpacked in handle_null_request(), but that's + lost now... Note: b32 directly, we want no undotify here! */ + i = b32->decode(cmc, &cmcsize, q->name + 1, (cp - q->name) - 1); + + if (i < 4) + return; /* illegal ping; shouldn't happen */ + + save_to_qmem(users[userid].qmemping_cmc, + users[userid].qmemping_type, QMEMPING_LEN, + &users[userid].qmemping_lastfilled, + (void *) cmc, q->type); + } else { + /* Data packet, hopefully not illegal */ + if (strlen(q->name) < 5) + return; + + /* We store CMC in lowercase; if routing via multiple parallel + DNS servers, one may do case-switch and another may not, + and we still want to detect duplicates. + Data-header is always base32, so case-swap won't hurt. + */ + for (i = 0; i < 4; i++) + if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') + cmc[i] = q->name[i+1] + ('a' - 'A'); + else + cmc[i] = q->name[i+1]; + + save_to_qmem(users[userid].qmemdata_cmc, + users[userid].qmemdata_type, QMEMDATA_LEN, + &users[userid].qmemdata_lastfilled, + (void *) cmc, q->type); + } +} + +static inline int +answer_from_qmem_data(int dns_fd, int userid, struct query *q) +/* Quick helper function to keep handle_null_request() clean */ +{ + char cmc[4]; + int i; + + for (i = 0; i < 4; i++) + if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') + cmc[i] = q->name[i+1] + ('a' - 'A'); + else + cmc[i] = q->name[i+1]; + + return answer_from_qmem(dns_fd, q, users[userid].qmemdata_cmc, + users[userid].qmemdata_type, QMEMDATA_LEN, + (void *) cmc); +} +/* END INLINE FUNCTION DEFINITIONS */ + +#ifdef DNSCACHE_LEN + +/* On the DNS cache: + + This cache is implemented to better handle the aggressively impatient DNS + servers that very quickly re-send requests when we choose to not + immediately answer them in lazy mode. This cache works much better than + pruning(=dropping) the improper requests, since the DNS server will + actually get an answer instead of silence. + + Because of the CMC in both ping and upstream data, unwanted cache hits + are prevented. Data-CMC is only 36 counts, so our cache length should + not exceed 36/2=18 packets. (This quick rule assumes all packets are + otherwise equal, which they arent: up/downstream seq, TCP/IP headers and + the actual data) TODO: adjust dns cache length +*/ + +static void +save_to_dnscache(int userid, struct query *q, char *answer, int answerlen) +/* Store answer in our little DNS cache. */ +{ + int fill; + + if (answerlen > sizeof(users[userid].dnscache_answer[fill])) + return; /* can't store this */ + + fill = users[userid].dnscache_lastfilled + 1; + if (fill >= DNSCACHE_LEN) + fill = 0; + + memcpy(&(users[userid].dnscache_q[fill]), q, sizeof(struct query)); + memcpy(users[userid].dnscache_answer[fill], answer, answerlen); + users[userid].dnscache_answerlen[fill] = answerlen; + + users[userid].dnscache_lastfilled = fill; +} + +static int +answer_from_dnscache(int dns_fd, int userid, struct query *q) +/* Checks cache and sends repeated answer if we alreay saw this query recently. + Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is + a new query. */ +{ + int i; + int use; + + for (i = 0; i < DNSCACHE_LEN ; i++) { + /* Try cache most-recent-first */ + use = users[userid].dnscache_lastfilled - i; + if (use < 0) + use += DNSCACHE_LEN; + + if (users[userid].dnscache_q[use].id == 0) + continue; + if (users[userid].dnscache_answerlen[use] <= 0) + continue; + + if (users[userid].dnscache_q[use].type != q->type || + strcmp(users[userid].dnscache_q[use].name, q->name)) + continue; + + /* okay, match */ + if (debug >= 1) + fprintf(stderr, "OUT user %d %s from dnscache\n", userid, q->name); + + write_dns(dns_fd, q, users[userid].dnscache_answer[use], + users[userid].dnscache_answerlen[use], + users[userid].downenc); + + q->id = 0; /* this query was used */ + return 1; + } + + /* here only when no match found */ + return 0; +} + +#endif /* DNSCACHE_LEN */ + +static int +get_dns_fd(struct dnsfd *fds, struct sockaddr_storage *addr) +{ + if (addr->ss_family == AF_INET6) { + return fds->v6fd; + } + return fds->v4fd; +} + + +static void +forward_query(int bind_fd, struct query *q) +{ + char buf[64*1024]; + int len; + struct fw_query fwq; + struct sockaddr_in *myaddr; + in_addr_t newaddr; + + len = dns_encode(buf, sizeof(buf), q, QR_QUERY, q->name, strlen(q->name)); + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } + + /* Store sockaddr for q->id */ + memcpy(&(fwq.addr), &(q->from), q->fromlen); + fwq.addrlen = q->fromlen; + fwq.id = q->id; + fw_query_put(&fwq); + + newaddr = inet_addr("127.0.0.1"); + myaddr = (struct sockaddr_in *) &(q->from); + memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t)); + myaddr->sin_port = htons(bind_port); + + if (debug >= 2) { + fprintf(stderr, "TX: NS reply \n"); + } + + if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("forward query error"); + } +} + +static int +send_frag_or_dataless(int dns_fd, int userid, struct query *q) +/* Sends current fragment to user, or dataless packet if there is no + current fragment available (-> normal "quiet" ping reply). + Does not update anything, except: + - discards q always (query is used) + - forgets entire users[userid].outpacket if it was sent in one go, + and then tries to get new packet from outpacket-queue + Returns: 1 = can call us again immediately, new packet from queue; + 0 = don't call us again for now. +*/ +{ + char pkt[4096]; + int datalen = 0; + + /* TODO: If re-sent too many times, drop entire packet */ + + /* Build downstream data header (see doc/proto_xxxxxxxx.txt) */ + +// /* First byte is 1 bit compression flag, 3 bits upstream seqno, 4 bits upstream fragment */ +// pkt[0] = (1<<7) | ((users[userid].inpacket.seqno & 7) << 4) | +// (users[userid].inpacket.fragment & 15); +// /* Second byte is 3 bits downstream seqno, 4 bits downstream fragment, 1 bit last flag */ +// pkt[1] = ((users[userid].outpacket.seqno & 7) << 5) | +// ((users[userid].outpacket.fragment & 15) << 1) | (last & 1); + + if (debug >= 1) { + // TODO: display packet data + } + write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); + + if (q->id2 != 0) { + q->id = q->id2; + q->fromlen = q->fromlen2; + memcpy(&(q->from), &(q->from2), q->fromlen2); + if (debug >= 1) + warnx("OUT again to last duplicate"); + write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); + } + + save_to_qmem_pingordata(userid, q); + +#ifdef DNSCACHE_LEN + save_to_dnscache(userid, q, pkt, datalen + 2); +#endif + + q->id = 0; /* this query is used */ + + return 0; /* don't call us again */ +} + +static void +send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, struct query *q) +{ + char out[9]; + + switch (ack) { + case VERSION_ACK: + strncpy(out, "VACK", sizeof(out)); + break; + case VERSION_NACK: + strncpy(out, "VNAK", sizeof(out)); + break; + case VERSION_FULL: + strncpy(out, "VFUL", sizeof(out)); + break; + } + + out[4] = ((payload >> 24) & 0xff); + out[5] = ((payload >> 16) & 0xff); + out[6] = ((payload >> 8) & 0xff); + out[7] = ((payload) & 0xff); + out[8] = userid & 0xff; + + write_dns(fd, q, out, sizeof(out), users[userid].downenc); +} + +static int +tunnel_bind(int bind_fd, struct dnsfd *dns_fds) +{ + char packet[64*1024]; + struct sockaddr_storage from; + socklen_t fromlen; + struct fw_query *query; + unsigned short id; + int dns_fd; + int r; + + fromlen = sizeof(struct sockaddr); + r = recvfrom(bind_fd, packet, sizeof(packet), 0, + (struct sockaddr*)&from, &fromlen); + + if (r <= 0) + return 0; + + id = dns_get_id(packet, r); + + if (debug >= 2) { + fprintf(stderr, "RX: Got response on query %u from DNS\n", (id & 0xFFFF)); + } + + /* Get sockaddr from id */ + fw_query_get(id, &query); + if (!query) { + if (debug >= 2) { + fprintf(stderr, "Lost sender of id %u, dropping reply\n", (id & 0xFFFF)); + } + return 0; + } + + if (debug >= 2) { + fprintf(stderr, "TX: client %s id %u, %d bytes\n", + format_addr(&query->addr, query->addrlen), (id & 0xffff), r); + } + + dns_fd = get_dns_fd(dns_fds, &query->addr); + if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), + query->addrlen) <= 0) { + warn("forward reply error"); + } + + return 0; +} + +static int +tunnel_tun(int tun_fd, struct dnsfd *dns_fds) +{ + unsigned long outlen; + struct ip *header; + static uint8_t out[64*1024]; + static uint8_t in[64*1024]; + int userid; + int read; + + if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) + return 0; + + /* find target ip in packet, in is padded with 4 bytes TUN header */ + header = (struct ip*) (in + 4); + userid = find_user_by_ip(header->ip_dst.s_addr); + if (userid < 0) + return 0; + + outlen = sizeof(out); + compress2(out, &outlen, in, read, 9); + + if (users[userid].conn == CONN_DNS_NULL) { + + start_new_outpacket(userid, out, outlen); + + /* Start sending immediately if query is waiting */ + if (users[userid].q_sendrealsoon.id != 0) { + int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); + send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + } else if (users[userid].q.id != 0) { + int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); + send_frag_or_dataless(dns_fd, userid, &users[userid].q); + } + + return outlen; + } else { /* CONN_RAW_UDP */ + int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); + send_raw(dns_fd, out, outlen, userid, RAW_HDR_CMD_DATA, &users[userid].q); + return outlen; + } +} + +static int +tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) +{ + struct query q; + int read; + int domain_len; + int inside_topdomain = 0; + + if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) + return 0; + + if (debug >= 2) { + fprintf(stderr, "RX: client %s, type %d, name %s\n", + format_addr(&q.from, q.fromlen), q.type, q.name); + } + + domain_len = strlen(q.name) - strlen(topdomain); + if (domain_len >= 0 && !strcasecmp(q.name + domain_len, topdomain)) + inside_topdomain = 1; + /* require dot before topdomain */ + if (domain_len >= 1 && q.name[domain_len - 1] != '.') + inside_topdomain = 0; + + if (inside_topdomain) { + /* This is a query we can handle */ + + /* Handle A-type query for ns.topdomain, possibly caused + by our proper response to any NS request */ + if (domain_len == 3 && q.type == T_A && + (q.name[0] == 'n' || q.name[0] == 'N') && + (q.name[1] == 's' || q.name[1] == 'S') && + q.name[2] == '.') { + handle_a_request(dns_fd, &q, 0); + return 0; + } + + /* Handle A-type query for www.topdomain, for anyone that's + poking around */ + if (domain_len == 4 && q.type == T_A && + (q.name[0] == 'w' || q.name[0] == 'W') && + (q.name[1] == 'w' || q.name[1] == 'W') && + (q.name[2] == 'w' || q.name[2] == 'W') && + q.name[3] == '.') { + handle_a_request(dns_fd, &q, 1); + return 0; + } + + switch (q.type) { + case T_NULL: + case T_PRIVATE: + case T_CNAME: + case T_A: + case T_MX: + case T_SRV: + case T_TXT: + /* encoding is "transparent" here */ + handle_null_request(tun_fd, dns_fd, dns_fds, &q, domain_len); + break; + case T_NS: + handle_ns_request(dns_fd, &q); + break; + default: + break; + } + } else { + /* Forward query to other port ? */ + if (bind_fd) { + forward_query(bind_fd, &q); + } + } + return 0; +} + +int +server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) +{ + struct timeval tv; + fd_set fds; + int i; + int userid; + time_t last_action = time(NULL); + + while (running) { + int maxfd; + tv.tv_sec = 10; /* doesn't really matter */ + tv.tv_usec = 0; + + /* Adjust timeout if there is anything to send realsoon. + Clients won't be sending new data until we send our ack, + so don't keep them waiting long. This only triggers at + final upstream fragments, which is about once per eight + requests during heavy upstream traffic. + 20msec: ~8 packs every 1/50sec = ~400 DNSreq/sec, + or ~1200bytes every 1/50sec = ~0.5 Mbit/sec upstream */ + for (userid = 0; userid < created_users; userid++) { + if (users[userid].active && !users[userid].disabled && + users[userid].last_pkt + 60 > time(NULL)) { + users[userid].q_sendrealsoon_new = 0; + if (users[userid].q_sendrealsoon.id != 0) { + tv.tv_sec = 0; + tv.tv_usec = 20000; + } + } + } + + FD_ZERO(&fds); + maxfd = 0; + + if (dns_fds->v4fd >= 0) { + FD_SET(dns_fds->v4fd, &fds); + maxfd = MAX(dns_fds->v4fd, maxfd); + } + if (dns_fds->v6fd >= 0) { + FD_SET(dns_fds->v6fd, &fds); + maxfd = MAX(dns_fds->v6fd, maxfd); + } + + if (bind_fd) { + /* wait for replies from real DNS */ + FD_SET(bind_fd, &fds); + maxfd = MAX(bind_fd, maxfd); + } + + /* Don't read from tun if no users can accept data anyway; + tun queue/TCP buffers are larger than our outpacket-queues */ + if(!all_users_waiting_to_send()) { + FD_SET(tun_fd, &fds); + maxfd = MAX(tun_fd, maxfd); + } + + i = select(maxfd + 1, &fds, NULL, NULL, &tv); + + if(i < 0) { + if (running) + warn("select"); + return 1; + } + + if (i==0) { + if (max_idle_time) { + /* only trigger the check if that's worth ( ie, no need to loop over if there + is something to send */ + if (last_action + max_idle_time < time(NULL)) { + for (userid = 0; userid < created_users; userid++) { + last_action = ( users[userid].last_pkt > last_action ) ? users[userid].last_pkt : last_action; + } + if (last_action + max_idle_time < time(NULL)) { + fprintf(stderr, "Idling since too long, shutting down...\n"); + running = 0; + } + } + } + } else { + if (FD_ISSET(tun_fd, &fds)) { + tunnel_tun(tun_fd, dns_fds); + } + if (FD_ISSET(dns_fds->v4fd, &fds)) { + tunnel_dns(tun_fd, dns_fds->v4fd, dns_fds, bind_fd); + } + if (FD_ISSET(dns_fds->v6fd, &fds)) { + tunnel_dns(tun_fd, dns_fds->v6fd, dns_fds, bind_fd); + } + if (FD_ISSET(bind_fd, &fds)) { + tunnel_bind(bind_fd, dns_fds); + } + } + + /* Send realsoon's if tun or dns didn't already */ + for (userid = 0; userid < created_users; userid++) + if (users[userid].active && !users[userid].disabled && + users[userid].last_pkt + 60 > time(NULL) && + users[userid].q_sendrealsoon.id != 0 && + users[userid].conn == CONN_DNS_NULL && + !users[userid].q_sendrealsoon_new) { + int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); + send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + } + } + + return 0; +} + +void +handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len) +{ + unsigned long outlen; + uint8_t out[64*1024]; + int touser; + int ret; + + outlen = sizeof(out); + if ((ret = uncompress(out, &outlen, data, len)) == Z_OK) { + struct ip *hdr; + + hdr = (struct ip*) (out + 4); + touser = find_user_by_ip(hdr->ip_dst.s_addr); + + if (touser == -1) { + /* send the uncompressed packet to tun device */ + write_tun(tun_fd, out, outlen); + } else { + /* send the compressed(!) packet to other client */ + if (users[touser].conn == CONN_DNS_NULL) { + if (window_buffer_available(users[touser].outgoing) * users[touser].outgoing->maxfraglen >= len) { + start_new_outpacket(touser, data, len); + + /* Start sending immediately if query is waiting */ + if (users[touser].q_sendrealsoon.id != 0) { + int dns_fd = get_dns_fd(dns_fds, &users[touser].q_sendrealsoon.from); + send_frag_or_dataless(dns_fd, touser, &users[touser].q_sendrealsoon); + } else if (users[touser].q.id != 0) { + int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); + send_frag_or_dataless(dns_fd, touser, &users[touser].q); + } + } + } else{ /* CONN_RAW_UDP */ + int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); + send_raw(dns_fd, data, len, touser, RAW_HDR_CMD_DATA, &users[touser].q); + } + } + } else { + if (debug >= 1) + fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret); + } +} + +static void +handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int userid) +{ + char myhash[16]; + + if (len < 16) return; + + /* can't use check_authenticated_user_and_ip() since IP address will be different, + so duplicate here except IP address */ + if (userid < 0 || userid >= created_users) return; + if (!users[userid].active || users[userid].disabled) return; + if (!users[userid].authenticated) return; + if (users[userid].last_pkt + 60 < time(NULL)) return; + + if (debug >= 1) { + fprintf(stderr, "IN login raw, len %lu, from user %d\n", len, userid); + } + + /* User sends hash of seed + 1 */ + login_calculate(myhash, 16, password, users[userid].seed + 1); + if (memcmp(packet, myhash, 16) == 0) { + /* Update query and time info for user */ + users[userid].last_pkt = time(NULL); + memcpy(&(users[userid].q), q, sizeof(struct query)); + + /* Store remote IP number */ + memcpy(&(users[userid].host), &(q->from), q->fromlen); + users[userid].hostlen = q->fromlen; + + /* Correct hash, reply with hash of seed - 1 */ + user_set_conn_type(userid, CONN_RAW_UDP); + login_calculate(myhash, 16, password, users[userid].seed - 1); + send_raw(fd, (uint8_t *)myhash, 16, userid, RAW_HDR_CMD_LOGIN, q); + + users[userid].authenticated_raw = 1; + } +} + +static void +handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_fds, int tun_fd, int userid) +{ + if (check_authenticated_user_and_ip(userid, q) != 0) { + return; + } + if (!users[userid].authenticated_raw) return; + + /* Update query and time info for user */ + users[userid].last_pkt = time(NULL); + memcpy(&(users[userid].q), q, sizeof(struct query)); + + /* copy to packet buffer, update length TODO fix the raw UDP protocol */ + + if (debug >= 1) { + fprintf(stderr, "IN pkt raw, total %lu, from user %d\n", len, userid); + } + + handle_full_packet(tun_fd, dns_fds, userid, packet, len); +} + +static void +handle_raw_ping(struct query *q, int dns_fd, int userid) +{ + if (check_authenticated_user_and_ip(userid, q) != 0) { + return; + } + if (!users[userid].authenticated_raw) return; + + /* Update query and time info for user */ + users[userid].last_pkt = time(NULL); + memcpy(&(users[userid].q), q, sizeof(struct query)); + + if (debug >= 1) { + fprintf(stderr, "IN ping raw, from user %d\n", userid); + } + + /* Send ping reply */ + send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, q); +} + +static int +raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsfd *dns_fds, int tun_fd) +{ + int raw_user; + + /* minimum length */ + if (len < RAW_HDR_LEN) return 0; + /* should start with header */ + if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0; + + raw_user = RAW_HDR_GET_USR(packet); + switch (RAW_HDR_GET_CMD(packet)) { + case RAW_HDR_CMD_LOGIN: + /* Login challenge */ + handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, raw_user); + break; + case RAW_HDR_CMD_DATA: + /* Data packet */ + handle_raw_data(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fds, tun_fd, raw_user); + break; + case RAW_HDR_CMD_PING: + /* Keepalive packet */ + handle_raw_ping(q, dns_fd, raw_user); + break; + default: + warnx("Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); + break; + } + return 1; +} + +int +read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) +/* FIXME: dns_fds and tun_fd are because of raw_decode() below */ +{ + struct sockaddr_storage from; + socklen_t addrlen; + uint8_t packet[64*1024]; + int r; +#ifndef WINDOWS32 + char control[CMSG_SPACE(sizeof (struct in6_pktinfo))]; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + + addrlen = sizeof(struct sockaddr_storage); + iov.iov_base = packet; + iov.iov_len = sizeof(packet); + + msg.msg_name = (caddr_t) &from; + msg.msg_namelen = (unsigned) addrlen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + msg.msg_flags = 0; + + r = recvmsg(fd, &msg, 0); +#else + addrlen = sizeof(struct sockaddr_storage); + r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen); +#endif /* !WINDOWS32 */ + + if (r > 0) { + memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen); + q->fromlen = addrlen; + + /* TODO do not handle raw packets here! */ + if (raw_decode(packet, r, q, fd, dns_fds, tun_fd)) { + return 0; + } + if (dns_decode(NULL, 0, q, QR_QUERY, (char *)packet, r) < 0) { + return 0; + } + +#ifndef WINDOWS32 + memset(&q->destination, 0, sizeof(struct sockaddr_storage)); + /* Read destination IP address */ + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == DSTADDR_SOCKOPT) { + + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + addr->sin_family = AF_INET; + addr->sin_addr = *dstaddr(cmsg); + q->dest_len = sizeof(*addr); + break; + } + if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) { + + struct in6_pktinfo *pktinfo; + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; + pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsg); + addr->sin6_family = AF_INET6; + memcpy(&addr->sin6_addr, &pktinfo->ipi6_addr, sizeof(struct in6_addr)); + q->dest_len = sizeof(*addr); + break; + } + } +#endif + + return strlen(q->name); + } else if (r < 0) { + /* Error */ + warn("read dns"); + } + + return 0; +} + +static size_t +write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downenc) +/* Returns #bytes of data that were encoded */ +{ + static int td1 = 0; + static int td2 = 0; + size_t space; + char *b; + + /* Make a rotating topdomain to prevent filtering */ + td1+=3; + td2+=7; + if (td1>=26) td1-=26; + if (td2>=25) td2-=25; + + /* encode data,datalen to CNAME/MX answer + (adapted from build_hostname() in encoding.c) + */ + + space = MIN(0xFF, buflen) - 4 - 2; + /* -1 encoding type, -3 ".xy", -2 for safety */ + + memset(buf, 0, buflen); + + if (downenc == 'S') { + buf[0] = 'i'; + if (!b64->places_dots()) + space -= (space / 57); /* space for dots */ + b64->encode(buf+1, &space, data, datalen); + if (!b64->places_dots()) + inline_dotify(buf, buflen); + } else if (downenc == 'U') { + buf[0] = 'j'; + if (!b64u->places_dots()) + space -= (space / 57); /* space for dots */ + b64u->encode(buf+1, &space, data, datalen); + if (!b64u->places_dots()) + inline_dotify(buf, buflen); + } else if (downenc == 'V') { + buf[0] = 'k'; + if (!b128->places_dots()) + space -= (space / 57); /* space for dots */ + b128->encode(buf+1, &space, data, datalen); + if (!b128->places_dots()) + inline_dotify(buf, buflen); + } else { + buf[0] = 'h'; + if (!b32->places_dots()) + space -= (space / 57); /* space for dots */ + b32->encode(buf+1, &space, data, datalen); + if (!b32->places_dots()) + inline_dotify(buf, buflen); + } + + /* Add dot (if it wasn't there already) and topdomain */ + b = buf; + b += strlen(buf) - 1; + if (*b != '.') + *++b = '.'; + b++; + + *b = 'a' + td1; + b++; + *b = 'a' + td2; + b++; + *b = '\0'; + + return space; +} + +void +write_dns(int fd, struct query *q, char *data, int datalen, char downenc) +{ + char buf[64*1024]; + int len = 0; + + if (q->type == T_CNAME || q->type == T_A) { + char cnamebuf[1024]; /* max 255 */ + + write_dns_nameenc(cnamebuf, sizeof(cnamebuf), + data, datalen, downenc); + + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, + sizeof(cnamebuf)); + } else if (q->type == T_MX || q->type == T_SRV) { + char mxbuf[64*1024]; + char *b = mxbuf; + int offset = 0; + int res; + + while (1) { + res = write_dns_nameenc(b, sizeof(mxbuf) - (b - mxbuf), + data + offset, + datalen - offset, downenc); + if (res < 1) { + /* nothing encoded */ + b++; /* for final \0 */ + break; + } + + b = b + strlen(b) + 1; + + offset += res; + if (offset >= datalen) + break; + } + + /* Add final \0 */ + *b = '\0'; + + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, mxbuf, + sizeof(mxbuf)); + } else if (q->type == T_TXT) { + /* TXT with base32 */ + char txtbuf[64*1024]; + size_t space = sizeof(txtbuf) - 1;; + + memset(txtbuf, 0, sizeof(txtbuf)); + + if (downenc == 'S') { + txtbuf[0] = 's'; /* plain base64(Sixty-four) */ + len = b64->encode(txtbuf+1, &space, data, datalen); + } + else if (downenc == 'U') { + txtbuf[0] = 'u'; /* Base64 with Underscore */ + len = b64u->encode(txtbuf+1, &space, data, datalen); + } + else if (downenc == 'V') { + txtbuf[0] = 'v'; /* Base128 */ + len = b128->encode(txtbuf+1, &space, data, datalen); + } + else if (downenc == 'R') { + txtbuf[0] = 'r'; /* Raw binary data */ + len = MIN(datalen, sizeof(txtbuf) - 1); + memcpy(txtbuf + 1, data, len); + } else { + txtbuf[0] = 't'; /* plain base32(Thirty-two) */ + len = b32->encode(txtbuf+1, &space, data, datalen); + } + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, txtbuf, len+1); + } else { + /* Normal NULL-record encode */ + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); + } + + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } + + if (debug >= 2) { + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes data\n", + format_addr(&q->from, q->fromlen), q->type, q->name, datalen); + } + + sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); +} + +void +handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) +{ + struct in_addr tempip; + char in[512]; + char logindata[16]; + char out[64*1024]; + char unpacked[64*1024]; + char *tmp[2]; + int userid; + int read; + + userid = -1; + + /* Everything here needs at least two chars in the name */ + if (domain_len < 2) + return; + + memcpy(in, q->name, MIN(domain_len, sizeof(in))); + + if(in[0] == 'V' || in[0] == 'v') { + int version = 0; + + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + /* Version greeting, compare and send ack/nak */ + if (read > 4) { + /* Received V + 32bits version */ + version = (((unpacked[0] & 0xff) << 24) | + ((unpacked[1] & 0xff) << 16) | + ((unpacked[2] & 0xff) << 8) | + ((unpacked[3] & 0xff))); + } + + if (version == PROTOCOL_VERSION) { + userid = find_available_user(); + if (userid >= 0) { + int i; + + users[userid].seed = rand(); + /* Store remote IP number */ + memcpy(&(users[userid].host), &(q->from), q->fromlen); + users[userid].hostlen = q->fromlen; + + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].encoder = get_base32_encoder(); + users[userid].downenc = 'T'; + send_version_response(dns_fd, VERSION_ACK, users[userid].seed, userid, q); + syslog(LOG_INFO, "accepted version for user #%d from %s", + userid, format_addr(&q->from, q->fromlen)); + users[userid].q.id = 0; + users[userid].q.id2 = 0; + users[userid].q_sendrealsoon.id = 0; + users[userid].q_sendrealsoon.id2 = 0; + users[userid].q_sendrealsoon_new = 0; + users[userid].fragsize = 100; /* very safe */ + users[userid].conn = CONN_DNS_NULL; + users[userid].lazy = 0; + // TODO: client specified window size + users[userid].incoming = window_buffer_init(128, 10, MAX_FRAGSIZE, WINDOW_RECVING); + users[userid].outgoing = window_buffer_init(16, 10, users[userid].fragsize, WINDOW_SENDING); +#ifdef DNSCACHE_LEN + { + for (i = 0; i < DNSCACHE_LEN; i++) { + users[userid].dnscache_q[i].id = 0; + users[userid].dnscache_answerlen[i] = 0; + } + } + users[userid].dnscache_lastfilled = 0; +#endif + for (i = 0; i < QMEMPING_LEN; i++) + users[userid].qmemping_type[i] = T_UNSET; + users[userid].qmemping_lastfilled = 0; + for (i = 0; i < QMEMDATA_LEN; i++) + users[userid].qmemdata_type[i] = T_UNSET; + users[userid].qmemdata_lastfilled = 0; + } else { + /* No space for another user */ + send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); + syslog(LOG_INFO, "dropped user from %s, server full", + format_addr(&q->from, q->fromlen)); + } + } else { + send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q); + syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", + format_addr(&q->from, q->fromlen), version); + } + return; + } else if (in[0] == 'L' || in[0] == 'l') { + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + if (read < 17) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Login phase, handle auth */ + userid = unpacked[0]; + + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + syslog(LOG_WARNING, "dropped login request from user #%d from unexpected source %s", + userid, format_addr(&q->from, q->fromlen)); + return; + } else { + users[userid].last_pkt = time(NULL); + login_calculate(logindata, 16, password, users[userid].seed); + + if (read >= 18 && (memcmp(logindata, unpacked + 1, 16) == 0)) { + /* Store login ok */ + users[userid].authenticated = 1; + + /* Send ip/mtu/netmask info */ + tempip.s_addr = my_ip; + tmp[0] = strdup(inet_ntoa(tempip)); + tempip.s_addr = users[userid].tun_ip; + tmp[1] = strdup(inet_ntoa(tempip)); + + read = snprintf(out, sizeof(out), "%s-%s-%d-%d", + tmp[0], tmp[1], my_mtu, netmask); + + write_dns(dns_fd, q, out, read, users[userid].downenc); + q->id = 0; + syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); + + free(tmp[1]); + free(tmp[0]); + } else { + write_dns(dns_fd, q, "LNAK", 4, 'T'); + syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password", + userid, format_addr(&q->from, q->fromlen)); + } + } + return; + } else if(in[0] == 'I' || in[0] == 'i') { + /* Request for IP number */ + char reply[17]; + int length; + + userid = b32_8to5(in[1]); + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + reply[0] = 'I'; + if (q->from.ss_family == AF_INET) { + if (ns_ip != INADDR_ANY) { + /* If set, use assigned external ip (-n option) */ + memcpy(&reply[1], &ns_ip, sizeof(ns_ip)); + } else { + /* otherwise return destination ip from packet */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr)); + } + length = 1 + sizeof(struct in_addr); + } else { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; + memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr)); + length = 1 + sizeof(struct in6_addr); + } + + write_dns(dns_fd, q, reply, length, 'T'); + } else if(in[0] == 'Z' || in[0] == 'z') { + /* Check for case conservation and chars not allowed according to RFC */ + + /* Reply with received hostname as data */ + /* No userid here, reply with lowest-grade downenc */ + write_dns(dns_fd, q, in, domain_len, 'T'); + return; + } else if(in[0] == 'S' || in[0] == 's') { + int codec; + struct encoder *enc; + if (domain_len < 3) { /* len at least 3, example: "S15" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + userid = b32_8to5(in[1]); + + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + codec = b32_8to5(in[2]); + + switch (codec) { + case 5: /* 5 bits per byte = base32 */ + enc = get_base32_encoder(); + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 6: /* 6 bits per byte = base64 */ + enc = get_base64_encoder(); + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ + enc = get_base64u_encoder(); + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 7: /* 7 bits per byte = base128 */ + enc = get_base128_encoder(); + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + break; + } + return; + } else if(in[0] == 'O' || in[0] == 'o') { + if (domain_len < 3) { /* len at least 3, example: "O1T" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + userid = b32_8to5(in[1]); + + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + switch (in[2]) { + case 'T': + case 't': + users[userid].downenc = 'T'; + write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); + break; + case 'S': + case 's': + users[userid].downenc = 'S'; + write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); + break; + case 'U': + case 'u': + users[userid].downenc = 'U'; + write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); + break; + case 'V': + case 'v': + users[userid].downenc = 'V'; + write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); + break; + case 'R': + case 'r': + users[userid].downenc = 'R'; + write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); + break; + case 'L': + case 'l': + users[userid].lazy = 1; + write_dns(dns_fd, q, "Lazy", 4, users[userid].downenc); + break; + case 'I': + case 'i': + users[userid].lazy = 0; + write_dns(dns_fd, q, "Immediate", 9, users[userid].downenc); + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + break; + } + return; + } else if(in[0] == 'Y' || in[0] == 'y') { + int i; + char *datap; + int datalen; + + if (domain_len < 6) { /* len at least 6, example: "YTxCMC" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + i = b32_8to5(in[2]); /* check variant */ + + switch (i) { + case 1: + datap = DOWNCODECCHECK1; + datalen = DOWNCODECCHECK1_LEN; + break; + default: + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + switch (in[1]) { + case 'T': + case 't': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'T'); + return; + } + break; + case 'S': + case 's': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'S'); + return; + } + break; + case 'U': + case 'u': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'U'); + return; + } + break; + case 'V': + case 'v': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'V'); + return; + } + break; + case 'R': + case 'r': + if (q->type == T_NULL || q->type == T_TXT) { + write_dns(dns_fd, q, datap, datalen, 'R'); + return; + } + break; + } + + /* if still here, then codec not available */ + write_dns(dns_fd, q, "BADCODEC", 8, 'T'); + return; + + } else if(in[0] == 'R' || in[0] == 'r') { + int req_frag_size; + + if (domain_len < 16) { /* we'd better have some chars for data... */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Downstream fragsize probe packet */ + userid = (b32_8to5(in[1]) >> 1) & 15; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31); + if (req_frag_size < 2 || req_frag_size > 2047) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + char buf[2048]; + int i; + unsigned int v = ((unsigned int) rand()) & 0xff ; + + memset(buf, 0, sizeof(buf)); + buf[0] = (req_frag_size >> 8) & 0xff; + buf[1] = req_frag_size & 0xff; + /* make checkable pseudo-random sequence */ + buf[2] = 107; + for (i = 3; i < 2048; i++, v = (v + 107) & 0xff) + buf[i] = v; + write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); + } + return; + } else if(in[0] == 'N' || in[0] == 'n') { + int max_frag_size; + + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + + if (read < 3) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Downstream fragsize packet */ + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + max_frag_size = ((unpacked[1] & 0xff) << 8) | (unpacked[2] & 0xff); + if (max_frag_size < 2) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + users[userid].fragsize = max_frag_size; + write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc); + } + return; + } else if(in[0] == 'P' || in[0] == 'p') { + int dn_seq; + int dn_frag; + int didsend = 0; + + /* We can't handle id=0, that's "no packet" to us. So drop + request completely. Note that DNS servers rewrite the id. + We'll drop 1 in 64k times. If DNS server retransmits with + different id, then all okay. + Else client won't retransmit, and we'll just keep the + previous ping in cache, no problem either. */ + if (q->id == 0) + return; + + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + if (read < 4) + return; + + /* Ping packet, store userid */ + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + +#ifdef DNSCACHE_LEN + /* Check if cached */ + if (answer_from_dnscache(dns_fd, userid, q)) + return; +#endif + + /* Check if duplicate (and not in full dnscache any more) */ + if (answer_from_qmem(dns_fd, q, users[userid].qmemping_cmc, + users[userid].qmemping_type, QMEMPING_LEN, + (void *) unpacked)) + return; + + /* Check if duplicate of waiting queries; impatient DNS relays + like to re-try early and often (with _different_ .id!) */ + if (users[userid].q.id != 0 && + q->type == users[userid].q.type && + !strcmp(q->name, users[userid].q.name) && + users[userid].lazy) { + /* We have this ping already, and it's waiting to be + answered. Always keep the last duplicate, since the + relay may have forgotten its first version already. + Our answer will go to both. + (If we already sent an answer, qmem/cache will + have triggered.) */ + if (debug >= 2) { + fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", + userid); + } + users[userid].q.id2 = q->id; + users[userid].q.fromlen2 = q->fromlen; + memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); + return; + } + + if (users[userid].q_sendrealsoon.id != 0 && + q->type == users[userid].q_sendrealsoon.type && + !strcmp(q->name, users[userid].q_sendrealsoon.name)) { + /* Outer select loop will send answer immediately, + to both queries. */ + if (debug >= 2) { + fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", + userid); + } + users[userid].q_sendrealsoon.id2 = q->id; + users[userid].q_sendrealsoon.fromlen2 = q->fromlen; + memcpy(&(users[userid].q_sendrealsoon.from2), + &(q->from), q->fromlen); + return; + } + + // TODO new ping header + dn_seq = unpacked[1] >> 4; + dn_frag = unpacked[1] & 15; + + if (debug >= 1) { + fprintf(stderr, "PING pkt from user %d, ack for downstream %d/%d\n", + userid, dn_seq, dn_frag); + } + + // TODO: process incoming ACK for downstream data + + /* If there is a query that must be returned real soon, do it. + May contain new downstream data if the ping had a new ack. + Otherwise, may also be re-sending old data. */ + if (users[userid].q_sendrealsoon.id != 0) { + send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + } + + /* We need to store a new query, so if there still is an + earlier query waiting, always send a reply to finish it. + May contain new downstream data if the ping had a new ack. + Otherwise, may also be re-sending old data. + (This is duplicate data if we had q_sendrealsoon above.) */ + if (users[userid].q.id != 0) { + didsend = 1; + if (send_frag_or_dataless(dns_fd, userid, &users[userid].q) == 1) + /* new packet from queue, send immediately */ + didsend = 0; + } + + /* Save new query and time info */ + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].last_pkt = time(NULL); + + /* If anything waiting and we didn't already send above, send + it now. And always send immediately if we're not lazy + (then above won't have sent at all). TODO only call send_frag if sending */ + if ((!didsend) || !users[userid].lazy) + send_frag_or_dataless(dns_fd, userid, &users[userid].q); + + } else if((in[0] >= '0' && in[0] <= '9') /* Data packet */ + || (in[0] >= 'a' && in[0] <= 'f') + || (in[0] >= 'A' && in[0] <= 'F')) { + int upstream_ok = 1; + int didsend = 0; + int code = -1; + + /* Need 6 char header + >=1 char data */ + if (domain_len < 7) + return; + + /* We can't handle id=0, that's "no packet" to us. So drop + request completely. Note that DNS servers rewrite the id. + We'll drop 1 in 64k times. If DNS server retransmits with + different id, then all okay. + Else client doesn't get our ack, and will retransmit in + 1 second. */ + if (q->id == 0) + return; + + if ((in[0] >= '0' && in[0] <= '9')) + code = in[0] - '0'; + if ((in[0] >= 'a' && in[0] <= 'f')) + code = in[0] - 'a' + 10; + if ((in[0] >= 'A' && in[0] <= 'F')) + code = in[0] - 'A' + 10; + + userid = code; + /* Check user and sending ip number */ + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + +#ifdef DNSCACHE_LEN + /* Check if cached */ + if (answer_from_dnscache(dns_fd, userid, q)) + return; +#endif + + /* Check if duplicate (and not in full dnscache any more) */ + if (answer_from_qmem_data(dns_fd, userid, q)) + return; + + /* Check if duplicate of waiting queries; impatient DNS relays + like to re-try early and often (with _different_ .id!) */ + if (users[userid].q.id != 0 && + q->type == users[userid].q.type && + !strcmp(q->name, users[userid].q.name) && + users[userid].lazy) { + /* We have this packet already, and it's waiting to be + answered. Always keep the last duplicate, since the + relay may have forgotten its first version already. + Our answer will go to both. + (If we already sent an answer, qmem/cache will + have triggered.) */ + if (debug >= 2) { + fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", userid); + } + users[userid].q.id2 = q->id; + users[userid].q.fromlen2 = q->fromlen; + memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); + return; + } + + if (users[userid].q_sendrealsoon.id != 0 && + q->type == users[userid].q_sendrealsoon.type && + !strcmp(q->name, users[userid].q_sendrealsoon.name)) { + /* Outer select loop will send answer immediately to both queries. */ + if (debug >= 2) { + fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", userid); + } + users[userid].q_sendrealsoon.id2 = q->id; + users[userid].q_sendrealsoon.fromlen2 = q->fromlen; + memcpy(&(users[userid].q_sendrealsoon.from2), + &(q->from), q->fromlen); + return; + } + + + /* Decode data header */ +// up_seq = (b32_8to5(in[1]) >> 2) & 7; +// up_frag = ((b32_8to5(in[1]) & 3) << 2) | ((b32_8to5(in[2]) >> 3) & 3); +// dn_seq = (b32_8to5(in[2]) & 7); +// dn_frag = b32_8to5(in[3]) >> 1; +// lastfrag = b32_8to5(in[3]) & 1; TODO: new data header + + // TODO: handle following scenarios + /* Got repeated old packet _with data_, probably + because client didn't receive our ack. So re-send + our ack(+data) immediately to keep things flowing + fast. + If it's a _really_ old frag, it's a nameserver + that tries again, and sending our current (non- + matching) fragno won't be a problem. */ + + /* Duplicate of recent upstream data packet; probably + need to answer this to keep DNS server happy */ +// upstream_ok = 0; + + /* Really new packet has arrived, no recent duplicate */ + /* Forget any old packet, even if incomplete */ + + if (upstream_ok) { + /* decode with this user's encoding */ + read = unpack_data(unpacked, sizeof(unpacked), &(in[UPSTREAM_HDR]), + domain_len - 5, users[userid].encoder); + // TODO append fragment to window + } + // TODO reassemble data + + /* If there is a query that must be returned real soon, do it. + Includes an ack of the just received upstream fragment, + may contain new data. */ + if (users[userid].q_sendrealsoon.id != 0) { + didsend = 1; + if (send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon) == 1) + /* new packet from queue, send immediately */ + didsend = 0; + } + + /* If we already have an earlier query waiting, we need to + get rid of it to store the new query. + - If we have new data waiting and not yet sent above, send immediately. + - If this wasn't the last upstream fragment, then we expect + more, so ack immediately if we didn't already. + - If we are in non-lazy mode, there should be no query + waiting, but if there is, send immediately. + - In all other cases (mostly the last-fragment cases), + we can afford to wait just a tiny little while for the + TCP ack to arrive from our tun. Note that this works best + when there is only one client. + */ +// if (users[userid].q.id != 0) { +// if ((users[userid].outpacket.len > 0 && !didsend) || +// (upstream_ok && !lastfrag && !didsend) || +// (!upstream_ok && !didsend) || +// !users[userid].lazy) { +// didsend = 1; +// if (send_frag_or_dataless(dns_fd, userid, &users[userid].q) == 1) +// /* new packet from queue, send immediately */ +// didsend = 0; +// } else { +// memcpy(&(users[userid].q_sendrealsoon), &(users[userid].q), +// sizeof(struct query)); +// users[userid].q_sendrealsoon_new = 1; +// users[userid].q.id = 0; /* used */ +// didsend = 1; +// } +// } + + /* Save new query and time info */ + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].last_pkt = time(NULL); + + /* If we still need to ack this upstream frag, do it to keep + upstream flowing. + - If we have new data waiting and not yet sent above, + send immediately. + - If this wasn't the last upstream fragment, then we expect + more, so ack immediately if we didn't already or are + in non-lazy mode. + - If this was the last fragment, and we didn't ack already + or are in non-lazy mode, send the ack after just a tiny + little while so that the TCP ack may have arrived from + our tun device. + - In all other cases, don't send anything now. */ + if (!didsend) // TODO: also check if sending + send_frag_or_dataless(dns_fd, userid, &users[userid].q); + else if (!didsend || !users[userid].lazy) { + if (upstream_ok) { /* rotate pending queries */ + memcpy(&(users[userid].q_sendrealsoon), &(users[userid].q), sizeof(struct query)); + users[userid].q_sendrealsoon_new = 1; + users[userid].q.id = 0; /* used */ + } else { + send_frag_or_dataless(dns_fd, userid, &users[userid].q); + } + } + } +} + + +void +handle_ns_request(int dns_fd, struct query *q) +/* Mostly identical to handle_a_request() below */ +{ + char buf[64*1024]; + int len; + + if (ns_ip != INADDR_ANY) { + /* If ns_ip set, overwrite destination addr with it. + * Destination addr will be sent as additional record (A, IN) */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); + } + + len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain); + if (len < 1) { + warnx("dns_encode_ns_response doesn't fit"); + return; + } + + if (debug >= 2) { + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes NS reply\n", + format_addr(&q->from, q->fromlen), q->type, q->name, len); + } + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("ns reply send error"); + } +} + +void +handle_a_request(int dns_fd, struct query *q, int fakeip) +/* Mostly identical to handle_ns_request() above */ +{ + char buf[64*1024]; + int len; + + if (fakeip) { + in_addr_t ip = inet_addr("127.0.0.1"); + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&addr->sin_addr, &ip, sizeof(ip)); + + } else if (ns_ip != INADDR_ANY) { + /* If ns_ip set, overwrite destination addr with it. + * Destination addr will be sent as additional record (A, IN) */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); + } + + len = dns_encode_a_response(buf, sizeof(buf), q); + if (len < 1) { + warnx("dns_encode_a_response doesn't fit"); + return; + } + + if (debug >= 2) { + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes A reply\n", + format_addr(&q->from, q->fromlen), q->type, q->name, len); + } + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("a reply send error"); + } +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..c50c131 --- /dev/null +++ b/src/server.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2006-2015 Erik Ekman , + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __SERVER_H__ +#define __SERVER_H__ + +#ifdef WINDOWS32 +#include "windows.h" +#include +#else +#include +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#define _XPG4_2 +#include +#include +#include +#include +#include +#include +#include +#endif + +#define DNSCACHE_LEN 10 +/* Undefine to disable. Should be less than 18; also see comments in iodined.c */ + +#define QMEMPING_LEN 30 +/* Max advisable: 64k/2 = 32000. Total mem usage: QMEMPING_LEN * USERS * 6 bytes */ + +#define QMEMDATA_LEN 15 +/* Max advisable: 36/2 = 18. Total mem usage: QMEMDATA_LEN * USERS * 6 bytes */ + +#define PASSWORD_ENV_VAR "IODINED_PASS" + +#if defined IP_RECVDSTADDR +# define DSTADDR_SOCKOPT IP_RECVDSTADDR +# define dstaddr(x) ((struct in_addr *) CMSG_DATA(x)) +#elif defined IP_PKTINFO +# define DSTADDR_SOCKOPT IP_PKTINFO +# define dstaddr(x) (&(((struct in_pktinfo *)(CMSG_DATA(x)))->ipi_addr)) +#endif + +#ifndef IPV6_RECVPKTINFO +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#endif + +#if !defined(BSD) && !defined(__GLIBC__) +static char *__progname; +#endif + +/* Struct with IPv4 and IPv6 file descriptors. + * Need to be passed on down to tunneling code since we can get a + * packet on one fd meant for a user on the other. + */ +struct dnsfd { + int v4fd; + int v6fd; +}; + +typedef enum { + VERSION_ACK, + VERSION_NACK, + VERSION_FULL +} version_ack_t; + +extern char *topdomain; +extern char password[33]; +extern struct encoder *b32; +extern struct encoder *b64; +extern struct encoder *b64u; +extern struct encoder *b128; + +extern int check_ip; +extern int my_mtu; +extern in_addr_t my_ip; +extern int netmask; + +extern in_addr_t ns_ip; + +extern int bind_port; +extern int debug; + +void server_init(); +void server_stop(); +int server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time); + +int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); +void write_dns(int fd, struct query *q, char *data, int datalen, char downenc); +void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len); +void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, 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); + +#endif /* __SERVER_H__ */ diff --git a/src/tun.c b/src/tun.c index c05a591..6b53c69 100644 --- a/src/tun.c +++ b/src/tun.c @@ -504,7 +504,7 @@ read_tun(int tun_fd, char *buf, size_t len) } #else int -write_tun(int tun_fd, char *data, size_t len) +write_tun(int tun_fd, uint8_t *data, size_t len) { #if defined (FREEBSD) || defined (NETBSD) /* FreeBSD/NetBSD has no header */ @@ -545,7 +545,7 @@ write_tun(int tun_fd, char *data, size_t len) } ssize_t -read_tun(int tun_fd, char *buf, size_t len) +read_tun(int tun_fd, uint8_t *buf, size_t len) { #if defined (FREEBSD) || defined (NETBSD) /* FreeBSD/NetBSD has no header */ diff --git a/src/tun.h b/src/tun.h index 8982a9f..fda8abd 100644 --- a/src/tun.h +++ b/src/tun.h @@ -20,8 +20,8 @@ int open_tun(const char *); void close_tun(int); -int write_tun(int, char *, size_t); -ssize_t read_tun(int, char *, size_t); +int write_tun(int, uint8_t *, size_t); +ssize_t read_tun(int, uint8_t *, size_t); int tun_setip(const char *, const char *, int); int tun_setmtu(const unsigned); diff --git a/src/user.c b/src/user.c index ce1fa5a..22a0132 100644 --- a/src/user.c +++ b/src/user.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2014 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,9 +34,12 @@ #include "common.h" #include "encoding.h" #include "user.h" +#include "window.h" struct tun_user *users; unsigned usercount; +int created_users; + int init_users(in_addr_t my_ip, int netbits) @@ -60,6 +64,7 @@ init_users(in_addr_t my_ip, int netbits) maxusers = (1 << (32-netbits)) - 3; /* 3: Net addr, broadcast addr, iodined addr */ usercount = MIN(maxusers, USERS); + if (users) free(users); users = calloc(usercount, sizeof(struct tun_user)); for (i = 0; i < usercount; i++) { in_addr_t ip; @@ -74,11 +79,7 @@ init_users(in_addr_t my_ip, int netbits) } users[i].tun_ip = ip; net.s_addr = ip; - users[i].disabled = 0; - users[i].authenticated = 0; - users[i].authenticated_raw = 0; - users[i].active = 0; - /* Rest is reset on login ('V' packet) */ + /* Rest is reset on login ('V' packet) or already 0 */ } return usercount; @@ -95,75 +96,55 @@ users_get_first_ip() int find_user_by_ip(uint32_t ip) { - int ret; - int i; - - ret = -1; - for (i = 0; i < usercount; i++) { - if (users[i].active && - users[i].authenticated && - !users[i].disabled && - users[i].last_pkt + 60 > time(NULL) && - ip == users[i].tun_ip) { - ret = i; - break; + for (int i = 0; i < usercount; i++) { + if (user_active(i) && users[i].authenticated && ip == users[i].tun_ip) { + return i; } } - return ret; + return -1; +} + +int +user_sending(int user) +{ + return users[user].outgoing->numitems > 0; +} + +int +user_active(int i) +{ + return users[i].active && !users[i].disabled && users[i].last_pkt + 60 > time(NULL); } int all_users_waiting_to_send() /* If this returns true, then reading from tun device is blocked. - So only return true when all clients have at least one packet in - the outpacket-queue, so that sending back-to-back is possible + So only return true when all clients have at least one fragment in + the outgoing buffer, so that sending back-to-back is possible without going through another select loop. */ { - time_t now; - int ret; - int i; - - ret = 1; - now = time(NULL); - for (i = 0; i < usercount; i++) { - if (users[i].active && !users[i].disabled && - users[i].last_pkt + 60 > now && - ((users[i].conn == CONN_RAW_UDP) || - ((users[i].conn == CONN_DNS_NULL) -#ifdef OUTPACKETQ_LEN - && users[i].outpacketq_filled < 1 -#else - && users[i].outpacket.len == 0 -#endif - ))) { - - ret = 0; - break; - } - } - return ret; + for (int i = 0; i < usercount; i++) + if (!(user_active(i) && user_sending(i))) return 0; + return 1; } int find_available_user() { - int ret = -1; - int i; - for (i = 0; i < usercount; i++) { + for (int u = 0; u < usercount; u++) { /* Not used at all or not used in one minute */ - if ((!users[i].active || users[i].last_pkt + 60 < time(NULL)) && !users[i].disabled) { - users[i].active = 1; - users[i].authenticated = 0; - users[i].authenticated_raw = 0; - users[i].last_pkt = time(NULL); - users[i].fragsize = 4096; - users[i].conn = CONN_DNS_NULL; - ret = i; - break; + if (!user_active(u)) { + /* reset all stats */ + memset(&users[u], 0, sizeof(users[u])); + users[u].active = 1; + users[u].last_pkt = time(NULL); + users[u].fragsize = MAX_FRAGSIZE; + users[u].conn = CONN_DNS_NULL; + return u; } } - return ret; + return -1; } void @@ -187,3 +168,60 @@ user_set_conn_type(int userid, enum connection c) users[userid].conn = c; } +/* This will not check that user has passed login challenge */ +int +check_user_and_ip(int userid, struct query *q) +{ + /* Note: duplicate in handle_raw_login() except IP-address check */ + + if (userid < 0 || userid >= created_users ) { + return 1; + } + if (!users[userid].active || users[userid].disabled) { + return 1; + } + if (users[userid].last_pkt + 60 < time(NULL)) { + return 1; + } + + /* return early if IP checking is disabled */ + if (!check_ip) { + return 0; + } + + if (q->from.ss_family != users[userid].host.ss_family) { + return 1; + } + /* Check IPv4 */ + if (q->from.ss_family == AF_INET) { + struct sockaddr_in *expected, *received; + + expected = (struct sockaddr_in *) &(users[userid].host); + received = (struct sockaddr_in *) &(q->from); + return memcmp(&(expected->sin_addr), &(received->sin_addr), sizeof(struct in_addr)); + } + /* Check IPv6 */ + if (q->from.ss_family == AF_INET6) { + struct sockaddr_in6 *expected, *received; + + expected = (struct sockaddr_in6 *) &(users[userid].host); + received = (struct sockaddr_in6 *) &(q->from); + return memcmp(&(expected->sin6_addr), &(received->sin6_addr), sizeof(struct in6_addr)); + } + /* Unknown address family */ + return 1; +} + +/* This checks that user has passed normal (non-raw) login challenge */ +int +check_authenticated_user_and_ip(int userid, struct query *q) +{ + int res = check_user_and_ip(userid, q); + if (res) + return res; + + if (!users[userid].authenticated) + return 1; + + return 0; +} diff --git a/src/user.h b/src/user.h index 7d2553a..637ca56 100644 --- a/src/user.h +++ b/src/user.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2006-2014 Erik Ekman , - * 2006-2009 Bjorn Andersson + * 2006-2009 Bjorn Andersson , + * 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,22 +19,11 @@ #ifndef __USER_H__ #define __USER_H__ +#include "window.h" +#include "server.h" + #define USERS 16 -#define OUTPACKETQ_LEN 4 /* Note: 16 users * 1 packet = 1MB */ -/* Undefine to have no queue for packets coming in from tun device, which may - lead to massive dropping in multi-user situations with high traffic. */ - -#define DNSCACHE_LEN 4 -/* Undefine to disable. Should be less than 18; also see comments in iodined.c */ - - -#define QMEMPING_LEN 30 -/* Max advisable: 64k/2 = 32000. Total mem usage: QMEMPING_LEN * USERS * 6 bytes */ - -#define QMEMDATA_LEN 15 -/* Max advisable: 36/2 = 18. Total mem usage: QMEMDATA_LEN * USERS * 6 bytes */ - struct tun_user { char id; int active; @@ -48,13 +38,11 @@ struct tun_user { struct query q; struct query q_sendrealsoon; int q_sendrealsoon_new; - struct packet inpacket; - struct packet outpacket; - int outfragresent; + struct frag_buffer *incoming; + struct frag_buffer *outgoing; + int next_upstream_ack; struct encoder *encoder; char downenc; - int out_acked_seqno; - int out_acked_fragment; int fragsize; enum connection conn; int lazy; @@ -64,11 +52,6 @@ struct tun_user { unsigned char qmemdata_cmc[QMEMDATA_LEN * 4]; unsigned short qmemdata_type[QMEMDATA_LEN]; int qmemdata_lastfilled; -#ifdef OUTPACKETQ_LEN - struct packet outpacketq[OUTPACKETQ_LEN]; - int outpacketq_nexttouse; - int outpacketq_filled; -#endif #ifdef DNSCACHE_LEN struct query dnscache_q[DNSCACHE_LEN]; char dnscache_answer[DNSCACHE_LEN][4096]; @@ -78,11 +61,17 @@ struct tun_user { }; extern struct tun_user *users; +extern int created_users; + +int user_sending(int user); +int all_users_waiting_to_send(); +int user_active(int i); +int check_authenticated_user_and_ip(int userid, struct query *q); +int check_user_and_ip(int userid, struct query *q); int init_users(in_addr_t, int); const char* users_get_first_ip(); int find_user_by_ip(uint32_t); -int all_users_waiting_to_send(); int find_available_user(); void user_switch_codec(int userid, struct encoder *enc); void user_set_conn_type(int userid, enum connection c); diff --git a/src/window.c b/src/window.c index f1019eb..c29a5b5 100644 --- a/src/window.c +++ b/src/window.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Frekk van Blagh + * Copyright (c) 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/src/window.h b/src/window.h index b28c3a6..2d71910 100644 --- a/src/window.h +++ b/src/window.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Frekk van Blagh + * Copyright (c) 2015 Frekk van Blagh * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,7 +18,7 @@ #define __WINDOW_H__ #define MAX_SEQ_ID 256 -#define MAX_FRAGSIZE 2048 +#define MAX_FRAGSIZE 4096 #define ACK_TIMEOUT 5 #define WINDOW_SENDING 1 @@ -95,7 +95,7 @@ fragment *window_get_next_sending_fragment(struct frag_buffer *w, int other_ack) int window_get_next_ack(struct frag_buffer *w); /* Sets the fragment with seqid to be ACK'd (SEND) */ -void window_ack(struct frag_buffer *w, unsigned seqid); +void window_ack(struct frag_buffer *w, int seqid); /* To be called after all other processing has been done * when anything happens (moves window etc) (SEND/RECV) */ From d8c08191ccb2e72141036930e3e3a66fa18948c7 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 21 Aug 2015 23:23:24 +0800 Subject: [PATCH 007/113] Server-side sliding window implementation mostly finished. Requires testing. --- src/iodined.c | 2 + src/osflags | 0 src/server.c | 228 +++++++++++++++++++++++++++++--------------------- 3 files changed, 134 insertions(+), 96 deletions(-) mode change 100755 => 100644 src/osflags diff --git a/src/iodined.c b/src/iodined.c index d0f3f7e..5e7edca 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -294,6 +294,8 @@ main(int argc, char **argv) retval = 0; + server_init(); + #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); #endif diff --git a/src/osflags b/src/osflags old mode 100755 new mode 100644 diff --git a/src/server.c b/src/server.c index 39be412..724917d 100644 --- a/src/server.c +++ b/src/server.c @@ -44,6 +44,7 @@ #include "tun.h" #include "fw_query.h" #include "server.h" +#include "window.h" #ifdef HAVE_SYSTEMD # include @@ -119,15 +120,6 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct query *q sendto(fd, packet, len, 0, (struct sockaddr *) &q->from, q->fromlen); } - -static void -start_new_outpacket(int userid, uint8_t *data, size_t datalen) -/* Copies data to .outpacket and resets all counters. - data is expected to be compressed already. */ -{ - // TODO: window add to outgoing data -} - int answer_from_qmem(int dns_fd, struct query *q, unsigned char *qmem_cmc, unsigned short *qmem_type, int qmem_len, @@ -378,10 +370,41 @@ forward_query(int bind_fd, struct query *q) } } +void +send_ping_response(int dns_fd, int userid, struct query *q) +{ + static uint8_t pkt[10]; + size_t datalen = 5; + /* Build downstream data + ping header (see doc/proto_xxxxxxxx.txt) for details */ + pkt[0] = users[userid].outgoing->start_seq_id & 0xFF; + pkt[1] = users[userid].incoming->start_seq_id & 0xFF; + pkt[2] = (1 << 5); /* ping flag */ + pkt[3] = users[userid].outgoing->windowsize & 0xFF; + pkt[4] = users[userid].incoming->windowsize & 0xFF; + + write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); + + if (q->id2 != 0) { /* rotate pending duplicate queries */ + q->id = q->id2; + q->fromlen = q->fromlen2; + memcpy(&(q->from), &(q->from2), q->fromlen2); + if (debug >= 1) + warnx("OUT again to last duplicate"); + write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); + } + + save_to_qmem_pingordata(userid, q); + +#ifdef DNSCACHE_LEN + save_to_dnscache(userid, q, (char *)pkt, datalen + 2); +#endif + + q->id = 0; /* this query is used */ +} + static int send_frag_or_dataless(int dns_fd, int userid, struct query *q) -/* Sends current fragment to user, or dataless packet if there is no - current fragment available (-> normal "quiet" ping reply). +/* Sends current fragment to user, or a ping if no data available. Does not update anything, except: - discards q always (query is used) - forgets entire users[userid].outpacket if it was sent in one go, @@ -390,43 +413,60 @@ send_frag_or_dataless(int dns_fd, int userid, struct query *q) 0 = don't call us again for now. */ { - char pkt[4096]; - int datalen = 0; + static uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_HDR]; + int ping = 0; + size_t datalen; + fragment *f; + struct frag_buffer *out; - /* TODO: If re-sent too many times, drop entire packet */ + out = users[userid].outgoing; - /* Build downstream data header (see doc/proto_xxxxxxxx.txt) */ + f = window_get_next_sending_fragment(out, users[userid].next_upstream_ack); + if (!f && user_sending(userid)) { + /* Need to tell client to sync stuff - send data header with ping stuff */ + ping = 1; + } /* TODO: If re-sent too many times, drop stuff */ -// /* First byte is 1 bit compression flag, 3 bits upstream seqno, 4 bits upstream fragment */ -// pkt[0] = (1<<7) | ((users[userid].inpacket.seqno & 7) << 4) | -// (users[userid].inpacket.fragment & 15); -// /* Second byte is 3 bits downstream seqno, 4 bits downstream fragment, 1 bit last flag */ -// pkt[1] = ((users[userid].outpacket.seqno & 7) << 5) | -// ((users[userid].outpacket.fragment & 15) << 1) | (last & 1); + /* Build downstream data header (see doc/proto_xxxxxxxx.txt) for details */ + pkt[0] = f->seqID & 0xFF; + pkt[1] = f->ack_other & 0xFF; + pkt[2] = ((ping & 1) << 5) | ((f->compressed & 1) << 4) | ((f->ack_other < 0 ? 0 : 1) << 3) + | (f->is_nack << 2) | (f->start << 1) | f->end; - if (debug >= 1) { - // TODO: display packet data + if (ping) { + pkt[3] = out->windowsize & 0xFF; + pkt[4] = users[userid].incoming->windowsize & 0xFF; + datalen = 5; + } else { + datalen = DOWNSTREAM_HDR + f->len; + if (datalen > sizeof(pkt)) { + warnx("send_frag_or_dataless: fragment too large to send!"); + return 0; + } + memcpy(&pkt, f->data, f->len); } - write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); + write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); - if (q->id2 != 0) { + if (q->id2 != 0) { /* reply to any duplicates */ q->id = q->id2; q->fromlen = q->fromlen2; memcpy(&(q->from), &(q->from2), q->fromlen2); if (debug >= 1) warnx("OUT again to last duplicate"); - write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); + write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); } save_to_qmem_pingordata(userid, q); #ifdef DNSCACHE_LEN - save_to_dnscache(userid, q, pkt, datalen + 2); + save_to_dnscache(userid, q, (char *)pkt, datalen + 2); #endif + /* this query has been */ + q->id = 0; + window_tick(out); - q->id = 0; /* this query is used */ - - return 0; /* don't call us again */ + /* call again if we have more things to send */ + return users[userid].outgoing->numitems > 0; } static void @@ -446,6 +486,7 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s break; } + // TODO: use htonl for compatibility with big-endian systems out[4] = ((payload >> 24) & 0xff); out[5] = ((payload >> 16) & 0xff); out[6] = ((payload >> 8) & 0xff); @@ -526,7 +567,7 @@ tunnel_tun(int tun_fd, struct dnsfd *dns_fds) if (users[userid].conn == CONN_DNS_NULL) { - start_new_outpacket(userid, out, outlen); + window_add_outgoing_data(users[userid].outgoing, out, outlen, 1); /* Start sending immediately if query is waiting */ if (users[userid].q_sendrealsoon.id != 0) { @@ -633,15 +674,14 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) tv.tv_usec = 0; /* Adjust timeout if there is anything to send realsoon. - Clients won't be sending new data until we send our ack, + Clients won't be sending new data until we send our ack, TODO: adjust stuff in this function so don't keep them waiting long. This only triggers at final upstream fragments, which is about once per eight requests during heavy upstream traffic. - 20msec: ~8 packs every 1/50sec = ~400 DNSreq/sec, + 20msec: ~8 packets every 1/50sec = ~400 DNSreq/sec, or ~1200bytes every 1/50sec = ~0.5 Mbit/sec upstream */ for (userid = 0; userid < created_users; userid++) { - if (users[userid].active && !users[userid].disabled && - users[userid].last_pkt + 60 > time(NULL)) { + if (user_active(userid)) { users[userid].q_sendrealsoon_new = 0; if (users[userid].q_sendrealsoon.id != 0) { tv.tv_sec = 0; @@ -683,7 +723,7 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) return 1; } - if (i==0) { + if (i == 0) { if (max_idle_time) { /* only trigger the check if that's worth ( ie, no need to loop over if there is something to send */ @@ -714,11 +754,8 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) /* Send realsoon's if tun or dns didn't already */ for (userid = 0; userid < created_users; userid++) - if (users[userid].active && !users[userid].disabled && - users[userid].last_pkt + 60 > time(NULL) && - users[userid].q_sendrealsoon.id != 0 && - users[userid].conn == CONN_DNS_NULL && - !users[userid].q_sendrealsoon_new) { + if (user_active(userid) && users[userid].q_sendrealsoon.id != 0 && + users[userid].conn == CONN_DNS_NULL && !users[userid].q_sendrealsoon_new) { int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); } @@ -749,7 +786,7 @@ handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, /* send the compressed(!) packet to other client */ if (users[touser].conn == CONN_DNS_NULL) { if (window_buffer_available(users[touser].outgoing) * users[touser].outgoing->maxfraglen >= len) { - start_new_outpacket(touser, data, len); + window_add_outgoing_data(users[touser].outgoing, data, len, 1); /* Start sending immediately if query is waiting */ if (users[touser].q_sendrealsoon.id != 0) { @@ -781,9 +818,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri /* can't use check_authenticated_user_and_ip() since IP address will be different, so duplicate here except IP address */ if (userid < 0 || userid >= created_users) return; - if (!users[userid].active || users[userid].disabled) return; - if (!users[userid].authenticated) return; - if (users[userid].last_pkt + 60 < time(NULL)) return; + if (!check_authenticated_user_and_ip(userid, q)) return; if (debug >= 1) { fprintf(stderr, "IN login raw, len %lu, from user %d\n", len, userid); @@ -1128,10 +1163,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query char in[512]; char logindata[16]; char out[64*1024]; - char unpacked[64*1024]; + static char unpacked[64*1024]; char *tmp[2]; int userid; - int read; + size_t read; userid = -1; @@ -1181,6 +1216,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query // TODO: client specified window size users[userid].incoming = window_buffer_init(128, 10, MAX_FRAGSIZE, WINDOW_RECVING); users[userid].outgoing = window_buffer_init(16, 10, users[userid].fragsize, WINDOW_SENDING); + users[userid].next_upstream_ack = -1; #ifdef DNSCACHE_LEN { for (i = 0; i < DNSCACHE_LEN; i++) { @@ -1517,9 +1553,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } return; } else if(in[0] == 'P' || in[0] == 'p') { - int dn_seq; - int dn_frag; + int dn_seq, up_seq, dn_wins, up_wins; int didsend = 0; + int respond; /* We can't handle id=0, that's "no packet" to us. So drop request completely. Note that DNS servers rewrite the id. @@ -1531,7 +1567,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); - if (read < 4) + if (read < 7) return; /* Ping packet, store userid */ @@ -1591,22 +1627,22 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; } - // TODO new ping header - dn_seq = unpacked[1] >> 4; - dn_frag = unpacked[1] & 15; + dn_seq = unpacked[1]; + up_seq = unpacked[2]; + up_wins = unpacked[3]; + dn_wins = unpacked[4]; + respond = unpacked[5] & 1; if (debug >= 1) { - fprintf(stderr, "PING pkt from user %d, ack for downstream %d/%d\n", - userid, dn_seq, dn_frag); + fprintf(stderr, "PING pkt from user %d, down %d/%d, up %d/%d\n", userid, dn_seq, dn_wins, up_seq, up_wins); } - // TODO: process incoming ACK for downstream data - /* If there is a query that must be returned real soon, do it. May contain new downstream data if the ping had a new ack. Otherwise, may also be re-sending old data. */ if (users[userid].q_sendrealsoon.id != 0) { - send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + if (respond) send_ping_response(dns_fd, userid, &users[userid].q_sendrealsoon); + else send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); } /* We need to store a new query, so if there still is an @@ -1616,9 +1652,12 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query (This is duplicate data if we had q_sendrealsoon above.) */ if (users[userid].q.id != 0) { didsend = 1; - if (send_frag_or_dataless(dns_fd, userid, &users[userid].q) == 1) - /* new packet from queue, send immediately */ - didsend = 0; + if (respond) + send_ping_response(dns_fd, userid, &users[userid].q); + else + if (send_frag_or_dataless(dns_fd, userid, &users[userid].q) == 1) + /* new packet from queue, send immediately */ + didsend = 0; } /* Save new query and time info */ @@ -1634,9 +1673,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } else if((in[0] >= '0' && in[0] <= '9') /* Data packet */ || (in[0] >= 'a' && in[0] <= 'f') || (in[0] >= 'A' && in[0] <= 'F')) { - int upstream_ok = 1; int didsend = 0; int code = -1; + static fragment f; /* Need 6 char header + >=1 char data */ if (domain_len < 7) @@ -1705,42 +1744,43 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } users[userid].q_sendrealsoon.id2 = q->id; users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), - &(q->from), q->fromlen); + memcpy(&(users[userid].q_sendrealsoon.from2), &(q->from), q->fromlen); return; } - /* Decode data header */ -// up_seq = (b32_8to5(in[1]) >> 2) & 7; -// up_frag = ((b32_8to5(in[1]) & 3) << 2) | ((b32_8to5(in[2]) >> 3) & 3); -// dn_seq = (b32_8to5(in[2]) & 7); -// dn_frag = b32_8to5(in[3]) >> 1; -// lastfrag = b32_8to5(in[3]) & 1; TODO: new data header + /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ + /* First byte (after userid) = CMC (ignored?) */ + f.seqID = (b32_8to5(in[2]) << 2) | (b32_8to5(in[3]) >> 2); + f.ack_other = (b32_8to5(in[5]) & 8) ? ((b32_8to5(in[3]) & 3) << 6) + | (b32_8to5(in[4]) << 1) | (b32_8to5(in[5]) & 1) : -1; + f.is_nack = (b32_8to5(in[5]) >> 2) & 1; + f.start = (b32_8to5(in[5]) >> 1) & 1; + f.end = b32_8to5(in[5]) & 1; - // TODO: handle following scenarios - /* Got repeated old packet _with data_, probably - because client didn't receive our ack. So re-send - our ack(+data) immediately to keep things flowing - fast. - If it's a _really_ old frag, it's a nameserver - that tries again, and sending our current (non- - matching) fragno won't be a problem. */ + /* Decode remainder of data with user encoding */ + read = unpack_data(unpacked, sizeof(unpacked), in + UPSTREAM_HDR, + domain_len - UPSTREAM_HDR, users[userid].encoder); - /* Duplicate of recent upstream data packet; probably - need to answer this to keep DNS server happy */ -// upstream_ok = 0; + f.len = MIN(read, MAX_FRAGSIZE); + memcpy(f.data, unpacked, read); - /* Really new packet has arrived, no recent duplicate */ - /* Forget any old packet, even if incomplete */ + window_process_incoming_fragment(users[userid].incoming, &f); - if (upstream_ok) { - /* decode with this user's encoding */ - read = unpack_data(unpacked, sizeof(unpacked), &(in[UPSTREAM_HDR]), - domain_len - 5, users[userid].encoder); - // TODO append fragment to window + window_ack(users[userid].outgoing, f.ack_other); + + /* if waiting for an ACK to be sent back upstream (on incoming buffer) */ + if (users[userid].next_upstream_ack < 0) { + users[userid].next_upstream_ack = window_get_next_ack(users[userid].incoming); } - // TODO reassemble data + + read = window_reassemble_data(users[userid].incoming, (uint8_t *)unpacked, sizeof(unpacked), NULL); + + if (read > 0) { /* Data reassembled successfully + cleared out of buffer */ + handle_full_packet(tun_fd, dns_fds, userid, (uint8_t *)unpacked, read); + } + + window_tick(users[userid].incoming); /* If there is a query that must be returned real soon, do it. Includes an ack of the just received upstream fragment, @@ -1801,13 +1841,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (!didsend) // TODO: also check if sending send_frag_or_dataless(dns_fd, userid, &users[userid].q); else if (!didsend || !users[userid].lazy) { - if (upstream_ok) { /* rotate pending queries */ - memcpy(&(users[userid].q_sendrealsoon), &(users[userid].q), sizeof(struct query)); - users[userid].q_sendrealsoon_new = 1; - users[userid].q.id = 0; /* used */ - } else { - send_frag_or_dataless(dns_fd, userid, &users[userid].q); - } + memcpy(&(users[userid].q_sendrealsoon), &(users[userid].q), sizeof(struct query)); + users[userid].q_sendrealsoon_new = 1; + users[userid].q.id = 0; /* used */ } } } From 96f9270b7f2d744545d141e5a48931bf60cce51a Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 21 Aug 2015 23:24:53 +0800 Subject: [PATCH 008/113] Removed unhelpful coding errors. --- src/client.c | 3 ++- src/user.c | 21 ++++++++++----------- src/window.c | 7 ++++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/client.c b/src/client.c index b3e52c3..f27f606 100644 --- a/src/client.c +++ b/src/client.c @@ -397,7 +397,6 @@ send_next_frag(int fd) /* Get next fragment to send */ f = window_get_next_sending_fragment(outbuf, next_downstream_ack); - window_tick(outbuf); if (f == NULL) { if (is_sending()) { /* There is stuff to send but we're out of sync, so send a ping @@ -433,6 +432,8 @@ send_next_frag(int fd) datacmc = 0; send_query(fd, (char *)buf); + + window_tick(outbuf); } static void diff --git a/src/user.c b/src/user.c index 22a0132..bf95f53 100644 --- a/src/user.c +++ b/src/user.c @@ -135,12 +135,16 @@ find_available_user() for (int u = 0; u < usercount; u++) { /* Not used at all or not used in one minute */ if (!user_active(u)) { + struct tun_user *user = &users[u]; + if (user->incoming) window_buffer_destroy(user->incoming); + if (user->outgoing) window_buffer_destroy(user->outgoing); /* reset all stats */ - memset(&users[u], 0, sizeof(users[u])); - users[u].active = 1; - users[u].last_pkt = time(NULL); - users[u].fragsize = MAX_FRAGSIZE; - users[u].conn = CONN_DNS_NULL; + user->active = 1; + user->authenticated = 0; + user->authenticated_raw = 0; + user->last_pkt = time(NULL); + user->fragsize = MAX_FRAGSIZE; + user->conn = CONN_DNS_NULL; return u; } } @@ -177,12 +181,7 @@ check_user_and_ip(int userid, struct query *q) if (userid < 0 || userid >= created_users ) { return 1; } - if (!users[userid].active || users[userid].disabled) { - return 1; - } - if (users[userid].last_pkt + 60 < time(NULL)) { - return 1; - } + if (!user_active(userid)) return 1; /* return early if IP checking is disabled */ if (!check_ip) { diff --git a/src/window.c b/src/window.c index c29a5b5..45bc2fd 100644 --- a/src/window.c +++ b/src/window.c @@ -56,7 +56,8 @@ window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int di void window_buffer_destroy(struct frag_buffer *w) { - free(w->frags); + if (!w) return; + if (w->frags) free(w->frags); free(w); } @@ -120,7 +121,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, in // warnx("chunk_start pointing to non-start fragment (%u)!", w->frags[w->chunk_start].seqID); return 0; } - *compression = 1; + if (compression) *compression = 1; fragment *f; size_t i, curseq; @@ -139,7 +140,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, in memcpy(dest, f->data, MIN(fraglen, maxlen)); dest += fraglen; datalen += fraglen; - *compression &= f->compressed & 1; + if (compression) *compression &= f->compressed & 1; if (f->compressed != *compression) { warnx("Inconsistent compression flags in chunk. Not reassembling!"); return 0; From 5df91ed775ce644f6f489563c5d0cc81ed7a7cc3 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 23 Aug 2015 22:10:50 +0800 Subject: [PATCH 009/113] Added encoder length calculation functions --- src/base128.c | 17 ++++++++++++++++- src/base32.c | 20 +++++++++++++++++++- src/base64.c | 17 ++++++++++++++++- src/encoding.c | 44 ++++++++++++++++++++++++++++++++++---------- src/encoding.h | 7 +++++++ 5 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/base128.c b/src/base128.c index be7ba86..4e8e409 100644 --- a/src/base128.c +++ b/src/base128.c @@ -57,6 +57,7 @@ static int base128_decode(void *, size_t *, const char *, size_t); static int base128_handles_dots(); static int base128_blksize_raw(); static int base128_blksize_enc(); +static size_t base128_encoded_length(size_t inputlen); static struct encoder base128_encoder = { @@ -66,7 +67,9 @@ static struct encoder base128_encoder = base128_handles_dots, base128_handles_dots, base128_blksize_raw, - base128_blksize_enc + base128_blksize_enc, + base128_encoded_length, + base128_raw_length }; struct encoder @@ -93,6 +96,18 @@ base128_blksize_enc() return BLKSIZE_ENC; } +static size_t +base128_encoded_length(size_t inputlen) +{ + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + ((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0; +} + +static size_t +base128_raw_length(size_t inputlen) +{ + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + ((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0; +} + inline static void base128_reverse_init() { diff --git a/src/base32.c b/src/base32.c index a058d38..a49963d 100644 --- a/src/base32.c +++ b/src/base32.c @@ -38,6 +38,9 @@ static int base32_decode(void *, size_t *, const char *, size_t); static int base32_handles_dots(); static int base32_blksize_raw(); static int base32_blksize_enc(); +static size_t base32_encoded_length(size_t inputlen); +static size_t base32_raw_length(size_t inputlen); + static struct encoder base32_encoder = { @@ -47,7 +50,9 @@ static struct encoder base32_encoder = base32_handles_dots, base32_handles_dots, base32_blksize_raw, - base32_blksize_enc + base32_blksize_enc, + base32_encoded_length, + base32_raw_length }; struct encoder @@ -74,6 +79,19 @@ base32_blksize_enc() return BLKSIZE_ENC; } +static size_t +base32_encoded_length(size_t inputlen) +{ + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + ((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0; +} + +static size_t +base32_raw_length(size_t inputlen) +{ + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + ((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0; +} + + inline static void base32_reverse_init() { diff --git a/src/base64.c b/src/base64.c index 7834731..c57a3f6 100644 --- a/src/base64.c +++ b/src/base64.c @@ -38,6 +38,7 @@ static int base64_decode(void *, size_t *, const char *, size_t); static int base64_handles_dots(); static int base64_blksize_raw(); static int base64_blksize_enc(); +static size_t base64_encoded_length(size_t inputlen); static struct encoder base64_encoder = { @@ -47,7 +48,9 @@ static struct encoder base64_encoder = base64_handles_dots, base64_handles_dots, base64_blksize_raw, - base64_blksize_enc + base64_blksize_enc, + base64_encoded_length, + base64_raw_length }; struct encoder @@ -74,6 +77,18 @@ base64_blksize_enc() return BLKSIZE_ENC; } +static size_t +base64_encoded_length(size_t inputlen) +{ + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + ((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0; +} + +static size_t +base64_raw_length(size_t inputlen) +{ + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + ((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0; +} + inline static void base64_reverse_init() { diff --git a/src/encoding.c b/src/encoding.c index c954e1d..58530b4 100644 --- a/src/encoding.c +++ b/src/encoding.c @@ -19,19 +19,43 @@ #include "common.h" #include "encoding.h" +size_t +get_raw_length(size_t enc_bytes, struct encoder *enc, const char *topdomain) +/* Returns the maximum length of raw data that can be encoded into max_enc_bytes */ +{ + size_t enc_datalen = enc_bytes - strlen(topdomain); + /* Number of dots in length of encoded data */ + size_t dots = enc_datalen / (DNS_MAXLABEL + 1); + if (!enc->eats_dots()) /* Dots are not included in encoded data length */ + enc_datalen -= dots; + return enc->get_raw_length(enc_datalen); +} + +size_t +get_encoded_length(size_t raw_bytes, struct encoder *enc, const char *topdomain) +/* Returns length of encoded data from original data length orig_len; */ +{ + size_t dots = 1; /* dot before topdomain */ + size_t len = enc->get_encoded_length(raw_bytes) + strlen(topdomain); + if (!enc->places_dots()) + dots += len / 63; /* number of dots needed in data */ + return len; +} + int -build_hostname(char *buf, size_t buflen, - const char *data, const size_t datalen, - const char *topdomain, struct encoder *encoder, size_t maxlen) +build_hostname(char *buf, size_t buflen, const char *data, const size_t datalen, + const char *topdomain, struct encoder *encoder, size_t maxlen, size_t header_len) +/* Builds DNS-compatible hostname for data using specified encoder and topdomain + * NB: Does not account for header length. Data is encoded at start of buf to + * (buf + MIN(maxlen, buflen)). */ { size_t space; char *b; - space = MIN(maxlen, buflen) - strlen(topdomain) - DOWNSTREAM_HDR + 3; - /* max header length + 1 dot before topdomain + 2 safety */ - - if (!encoder->places_dots()) - space -= (space / 57); /* space for dots */ + space = get_encoded_length(MIN(maxlen, buflen), encoder, topdomain); + buf += header_len; + buflen -= header_len; + maxlen -= header_len; memset(buf, 0, buflen); @@ -72,7 +96,7 @@ inline_dotify(char *buf, size_t buflen) char *reader, *writer; total = strlen(buf); - dots = total / 57; + dots = total / 63; writer = buf; writer += total; @@ -91,7 +115,7 @@ inline_dotify(char *buf, size_t buflen) while (dots) { *writer-- = *reader--; pos--; - if (pos % 57 == 0) { + if (pos % 63 == 0) { *writer-- = '.'; dots--; } diff --git a/src/encoding.h b/src/encoding.h index b38d0d6..584d697 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -25,6 +25,8 @@ #define DOWNCODECCHECK1 "\000\000\000\000\377\377\377\377\125\125\125\125\252\252\252\252\201\143\310\322\307\174\262\027\137\117\316\311\111\055\122\041\141\251\161\040\045\263\006\163\346\330\104\060\171\120\127\277" #define DOWNCODECCHECK1_LEN 48 +#define DNS_MAXLABEL 63 + struct encoder { char name[8]; int (*encode) (char *, size_t *, const void *, size_t); @@ -33,8 +35,13 @@ struct encoder { int (*eats_dots) (void); int (*blocksize_raw)(void); int (*blocksize_encoded)(void); + size_t (*get_encoded_length)(size_t); + size_t (*get_raw_length)(size_t); }; +size_t get_raw_length(size_t enc_bytes, struct encoder *enc, const char *topdomain); +size_t get_encoded_length(size_t raw_bytes, struct encoder *enc, const char *topdomain); + int build_hostname(char *, size_t, const char *, const size_t, const char *, struct encoder *, size_t); int unpack_data(char *, size_t, char *, size_t, struct encoder *); int inline_dotify(char *, size_t); From 391bf5224a1c5eb9c8a9d5a9c4756f5e94f58192 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 23 Aug 2015 22:11:28 +0800 Subject: [PATCH 010/113] Added window_buffer_resize function --- src/window.c | 26 ++++++++++++++++++++++++++ src/window.h | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/window.c b/src/window.c index 45bc2fd..a99e061 100644 --- a/src/window.c +++ b/src/window.c @@ -25,6 +25,12 @@ #include "common.h" #include "window.h" +void +window_buffer_reset(struct frag_buffer *w) +{ + +} + struct frag_buffer * window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir) { @@ -53,6 +59,26 @@ window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int di return buf; } +void +window_buffer_resize(struct frag_buffer *w, size_t length) +{ + if (w->length == length) return; + if (w->numitems > 0) { + warnx("Resizing window buffer with things still in it! This will cause problems!"); + } + if (w->frags) free(w->frags); + w->frags = calloc(length, sizeof(fragment)); + if (!w->frags) { + errx(1, "Failed to resize window buffer!"); + } + w->length = length; + w->numitems = 0; + w->window_start = 0; + w->start_seq_id = 0; + w->cur_seq_id = 0; + w->window_end = AFTER(w, w->windowsize); +} + void window_buffer_destroy(struct frag_buffer *w) { diff --git a/src/window.h b/src/window.h index 2d71910..fc9e506 100644 --- a/src/window.h +++ b/src/window.h @@ -46,7 +46,6 @@ struct frag_buffer { size_t numitems; /* number of non-empty fragments stored in buffer */ size_t window_start; /* Start of window */ size_t window_end; /* End of window (index) */ -// size_t last_sent; /* Last fragment sent (index) */ size_t last_write; /* Last fragment read/written */ size_t chunk_start; /* Start of current chunk of fragments, ie where fragno = 0 */ unsigned cur_seq_id; /* Most recent sequence ID */ @@ -73,6 +72,7 @@ struct frag_buffer { #define WRAP(x) ((x) % w->length) struct frag_buffer *window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir); +void window_buffer_resize(struct frag_buffer *w, size_t length); void window_buffer_destroy(struct frag_buffer *w); /* Returns number of available fragment slots (NOT BYTES) */ From 8e08c007c5a68eef165574b4d6451cca7d3d0782 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 23 Aug 2015 22:14:27 +0800 Subject: [PATCH 011/113] Window buffer max_fragsize adjusted according to MAX_HOSTNAME and current upstream codec --- src/client.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/client.c b/src/client.c index f27f606..ea804dd 100644 --- a/src/client.c +++ b/src/client.c @@ -113,7 +113,7 @@ static long send_ping_soon; static time_t lastdownstreamtime; static long send_query_sendcnt = -1; static long send_query_recvcnt = 0; -static int hostname_maxlen = 0xFF; +static size_t hostname_maxlen = 0xFF; void client_init() @@ -234,10 +234,12 @@ client_set_lazymode(int lazy_mode) } void -client_set_hostname_maxlen(int i) +client_set_hostname_maxlen(size_t i) { - if (i <= 0xFF) + if (i <= 0xFF && i != hostname_maxlen) { hostname_maxlen = i; + outbuf->maxfraglen = get_raw_length(hostname_maxlen, dataenc, topdomain); + } } const char * @@ -352,8 +354,7 @@ send_packet(int fd, char cmd, const char *data, const size_t datalen) buf[0] = cmd; - build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain, - b32, hostname_maxlen); + build_hostname(buf, sizeof(buf), data, datalen, topdomain, b32, hostname_maxlen, 1); send_query(fd, buf); } @@ -377,7 +378,7 @@ send_ping(int fd, int ping_response) // TODO: setup window sync stuff in ping data[5] = ping_response & 1; data[6] = (rand_seed >> 8) & 0xff; data[7] = (rand_seed >> 0) & 0xff; - rand_seed++; + rand_seed += 7; send_packet(fd, 'p', data, sizeof(data)); } else { @@ -407,10 +408,10 @@ send_next_frag(int fd) } /* Note: must be same, or smaller than send_fragsize_probe() */ - build_hostname((char *)buf, sizeof(buf), (char *)f->data, f->len, topdomain, dataenc, hostname_maxlen); + build_hostname((char *)buf, sizeof(buf), (char *)f->data, f->len, topdomain, + dataenc, hostname_maxlen, 6); /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ - buf[0] = userid_char; /* First byte is hex userid */ buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ @@ -778,11 +779,11 @@ parse_data(uint8_t *data, size_t len, fragment *f) memcpy(f->data, data + 3, MIN(f->len, sizeof(f->data))); } else { /* Handle ping stuff */ if (len != 5) return 1; /* invalid packet - continue */ - static unsigned in_start_seq, out_start_seq, in_wsize, out_wsize; - out_start_seq = data[0]; - in_start_seq = data[1]; - in_wsize = data[3]; - out_wsize = data[4]; +// static unsigned in_start_seq, out_start_seq, in_wsize, out_wsize; +// out_start_seq = data[0]; +// in_start_seq = data[1]; +// in_wsize = data[3]; +// out_wsize = data[4]; warnx("Pingy thingy received."); // TODO: handle pings } @@ -1102,10 +1103,10 @@ send_fragsize_probe(int fd, int fragsize) rand_seed++; /* Note: must either be same, or larger, than send_chunk() */ - build_hostname(buf + 5, sizeof(buf) - 5, probedata, sizeof(probedata), - topdomain, dataenc, hostname_maxlen); + build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), topdomain, + dataenc, hostname_maxlen, 5); - fragsize &= 2047; + fragsize &= 0x7FF; buf[0] = 'r'; /* Probe downstream fragsize packet */ buf[1] = b32_5to8((userid << 1) | ((fragsize >> 10) & 1)); @@ -1933,6 +1934,9 @@ handshake_switch_codec(int dns_fd, int bits) in[read] = 0; /* zero terminate */ fprintf(stderr, "Server switched upstream to codec %s\n", in); dataenc = tempenc; + + /* Update outgoing buffer max (decoded) fragsize */ + outbuf->maxfraglen = get_raw_length(hostname_maxlen, dataenc, topdomain); return; } @@ -2284,6 +2288,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } if (raw_mode && handshake_raw_udp(dns_fd, seed)) { + /* TODO: fragsize based on max raw packet size */ conn = CONN_RAW_UDP; selecttimeout = 20; } else { @@ -2305,11 +2310,11 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (!running) return -1; - if (upcodec == 1) { + if (upcodec == 1) { /* Base64 */ handshake_switch_codec(dns_fd, 6); - } else if (upcodec == 2) { + } else if (upcodec == 2) { /* Base64u */ handshake_switch_codec(dns_fd, 26); - } else if (upcodec == 3) { + } else if (upcodec == 3) { /* Base128 */ handshake_switch_codec(dns_fd, 7); } if (!running) From 4c8ce94be722e3b468f98e7ef1a5daf44f553009 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 23 Aug 2015 22:15:51 +0800 Subject: [PATCH 012/113] Minor readability adjustments + more debug output --- src/server.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/server.c b/src/server.c index 724917d..55571e2 100644 --- a/src/server.c +++ b/src/server.c @@ -652,6 +652,9 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) } } else { /* Forward query to other port ? */ + if (debug >= 3) { + fprintf(stderr, "Requested domain outside our topdomain."); + } if (bind_fd) { forward_query(bind_fd, &q); } @@ -1158,6 +1161,7 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) +/* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ { struct in_addr tempip; char in[512]; @@ -1176,13 +1180,18 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query memcpy(in, q->name, MIN(domain_len, sizeof(in))); - if(in[0] == 'V' || in[0] == 'v') { + if (debug >= 3) { + fprintf(stderr, "NULL request length %d/%lu, command '%c'\n", domain_len, sizeof(in), in[0]); + } + + if(in[0] == 'V' || in[0] == 'v') { /* Version request */ int version = 0; read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); /* Version greeting, compare and send ack/nak */ if (read > 4) { /* Received V + 32bits version */ + // TODO htonl/ntohl for portability version = (((unpacked[0] & 0xff) << 24) | ((unpacked[1] & 0xff) << 16) | ((unpacked[2] & 0xff) << 8) | @@ -1244,7 +1253,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query format_addr(&q->from, q->fromlen), version); } return; - } else if (in[0] == 'L' || in[0] == 'l') { + } else if (in[0] == 'L' || in[0] == 'l') { /* Login request */ read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); if (read < 17) { write_dns(dns_fd, q, "BADLEN", 6, 'T'); @@ -1289,8 +1298,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } } return; - } else if(in[0] == 'I' || in[0] == 'i') { - /* Request for IP number */ + } else if(in[0] == 'I' || in[0] == 'i') { /* IP address request */ char reply[17]; int length; @@ -1325,7 +1333,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query /* No userid here, reply with lowest-grade downenc */ write_dns(dns_fd, q, in, domain_len, 'T'); return; - } else if(in[0] == 'S' || in[0] == 's') { + } else if(in[0] == 'S' || in[0] == 's') { /* Switch upstream codec */ int codec; struct encoder *enc; if (domain_len < 3) { /* len at least 3, example: "S15" */ @@ -1368,7 +1376,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query break; } return; - } else if(in[0] == 'O' || in[0] == 'o') { + } else if(in[0] == 'O' || in[0] == 'o') { /* Protocol options */ if (domain_len < 3) { /* len at least 3, example: "O1T" */ write_dns(dns_fd, q, "BADLEN", 6, 'T'); return; @@ -1422,7 +1430,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query break; } return; - } else if(in[0] == 'Y' || in[0] == 'y') { + } else if(in[0] == 'Y' || in[0] == 'y') { /* Downstream codec check */ int i; char *datap; int datalen; @@ -1494,7 +1502,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, "BADCODEC", 8, 'T'); return; - } else if(in[0] == 'R' || in[0] == 'r') { + } else if(in[0] == 'R' || in[0] == 'r') { /* Downstream fragsize probe */ int req_frag_size; if (domain_len < 16) { /* we'd better have some chars for data... */ @@ -1510,12 +1518,16 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31); + if (debug >= 3) { + fprintf(stderr, "Got downstream fragsize probe from user %d, required fragsize %d\n", userid, req_frag_size); + } + if (req_frag_size < 2 || req_frag_size > 2047) { write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); } else { char buf[2048]; int i; - unsigned int v = ((unsigned int) rand()) & 0xff ; + unsigned int v = ((unsigned int) rand()) & 0xff; memset(buf, 0, sizeof(buf)); buf[0] = (req_frag_size >> 8) & 0xff; @@ -1527,7 +1539,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); } return; - } else if(in[0] == 'N' || in[0] == 'n') { + } else if(in[0] == 'N' || in[0] == 'n') { /* Downstream fragsize (NS.topdomain A-type reply) */ int max_frag_size; read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); @@ -1552,7 +1564,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc); } return; - } else if(in[0] == 'P' || in[0] == 'p') { + } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ int dn_seq, up_seq, dn_wins, up_wins; int didsend = 0; int respond; @@ -1566,7 +1578,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (q->id == 0) return; - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); if (read < 7) return; @@ -1638,7 +1650,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } /* If there is a query that must be returned real soon, do it. - May contain new downstream data if the ping had a new ack. + May contain new downstream data if the ping had a new ack. TODO: ping with downstream data Otherwise, may also be re-sending old data. */ if (users[userid].q_sendrealsoon.id != 0) { if (respond) send_ping_response(dns_fd, userid, &users[userid].q_sendrealsoon); @@ -1670,7 +1682,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if ((!didsend) || !users[userid].lazy) send_frag_or_dataless(dns_fd, userid, &users[userid].q); - } else if((in[0] >= '0' && in[0] <= '9') /* Data packet */ + } else if((in[0] >= '0' && in[0] <= '9') /* Upstream data packet */ || (in[0] >= 'a' && in[0] <= 'f') || (in[0] >= 'A' && in[0] <= 'F')) { int didsend = 0; From e7119da5071b10d04c0ab2f78f9a56cf4846a033 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 28 Aug 2015 14:59:34 +0800 Subject: [PATCH 013/113] Added sliding window test --- tests/Makefile | 4 +-- tests/test.c | 5 +++ tests/test.h | 1 + tests/window.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 tests/window.c diff --git a/tests/Makefile b/tests/Makefile index 03eed98..6d35a7a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ TEST = test -OBJS = test.o base32.o base64.o common.o read.o dns.o encoding.o login.o user.o fw_query.o -SRCOBJS = ../src/base32.o ../src/base64.o ../src/common.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o ../src/fw_query.o +OBJS = test.o base32.o base64.o common.o read.o dns.o encoding.o login.o user.o fw_query.o window.o +SRCOBJS = ../src/base32.o ../src/base64.o ../src/window.o ../src/common.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o ../src/fw_query.o OS = `uname | tr "a-z" "A-Z"` diff --git a/tests/test.c b/tests/test.c index eda792b..73c04b5 100644 --- a/tests/test.c +++ b/tests/test.c @@ -23,6 +23,8 @@ #include "test.h" +int check_ip = 0; + int main() { @@ -60,6 +62,9 @@ main() test = test_fw_query_create_tests(); suite_add_tcase(iodine, test); + test = test_window_create_tests(); + suite_add_tcase(iodine, test); + runner = srunner_create(iodine); srunner_run_all(runner, CK_NORMAL); failed = srunner_ntests_failed(runner); diff --git a/tests/test.h b/tests/test.h index d3f7985..f1cc5fd 100644 --- a/tests/test.h +++ b/tests/test.h @@ -27,6 +27,7 @@ TCase *test_read_create_tests(); TCase *test_login_create_tests(); TCase *test_user_create_tests(); TCase *test_fw_query_create_tests(); +TCase *test_window_create_tests(); char *va_str(const char *, ...); diff --git a/tests/window.c b/tests/window.c new file mode 100644 index 0000000..6b3975e --- /dev/null +++ b/tests/window.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015 Frekk van Blagh + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "window.h" +#include "test.h" + +struct frag_buffer *in, *out; +char origdata[1000] = ""; + +START_TEST(test_window_everything) +{ + in = window_buffer_init(1000, 10, 5, WINDOW_RECVING); + out = window_buffer_init(1000, 10, 5, WINDOW_SENDING); + for (unsigned i = 0; i < 20; i++) { + char c[100] = "0ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()-=`';\\|][{}/?~"; + c[0] += i; + window_add_outgoing_data(out, (uint8_t *)c, i + 1, 0); + strncat(origdata, c, i + 1); + //warnx(" OUT: %u available, current seq %u, new seq %u\n", window_buffer_available(out), i, out->cur_seq_id); + } +// printf("Original data: '%s' (%lu)\n", origdata, strlen(origdata)); +// warnx("Added data, fragmented into %lu frags, next seq %u.", out->numitems, out->cur_seq_id); + // "send" data + int a = -1; + for (; out->numitems > 0;) { + fragment *f = window_get_next_sending_fragment(out, &a); + if (!f) { +// warnx("Nothing to send."); + continue; + } + fail_if(!window_process_incoming_fragment(in, f), "Incoming fragment failed!"); +// warnx("Received fragment with seqid %u, remaining space %lu.", f->seqID, window_buffer_available(in)); + int a = window_get_next_ack(in); + window_tick(in); + window_ack(out, a); + window_tick(out); + } +// warnx("Added %lu fragments, reassembling into data.", in->numitems); + uint8_t data[100]; + uint8_t newdata[1000]; + memset(newdata, 0, 1000); + unsigned i; + int c; + for (i = 0; i < 50; i++) { + memset(data, 0, 100); + size_t len = window_reassemble_data(in, data, 100, &c); + fail_if(c != 0, "Compression flag weird"); +// printf("Reassembled %lu bytes, num frags %lu: '", len, in->numitems); +// for (unsigned i = 0; i < len; i++) { +// printf("%c", data[i]); +// } +// printf("'\n"); + strncat((char *)newdata, data, len); + if (in->numitems <= 0) break; + } +// printf("New data: '%s' (%lu)\n", newdata, strlen((char *)newdata)); + fail_if(strcmp((char *)newdata, origdata), "Reassembled data didn't match original data."); +} +END_TEST + + +TCase * +test_window_create_tests() +{ + TCase *tc; + + tc = tcase_create("Windowing"); + tcase_add_test(tc, test_window_everything); + + return tc; +} From 917a6af7b819ad3e0e582ab781a58a00250a7f75 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 28 Aug 2015 15:00:44 +0800 Subject: [PATCH 014/113] Updated other tests to be compatible with newer code --- tests/encoding.c | 23 ++++++++++++++--------- tests/user.c | 16 +++++----------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/tests/encoding.c b/tests/encoding.c index 0d5f358..a2d5438 100644 --- a/tests/encoding.c +++ b/tests/encoding.c @@ -32,13 +32,13 @@ static struct tuple char *a; char *b; } dottests[] = { - { "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaa"}, - { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."}, - { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, - { "abc123", "abc123" }, + { "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaa"}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaa"}, + { "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, { NULL, NULL } }; @@ -76,7 +76,7 @@ START_TEST(test_build_hostname) { char data[256]; char buf[1024]; - char *topdomain = "a.c"; + char *topdomain = "iodine.test.example.com"; int buflen; int i; @@ -86,11 +86,16 @@ START_TEST(test_build_hostname) buflen = sizeof(buf); + for (int j = 0; j < 10; j++) /* dummy header length */ for (i = 1; i < sizeof(data); i++) { - int len = build_hostname(buf, buflen, data, i, topdomain, get_base32_encoder(), sizeof(buf)); + buf[j] = j + 'A'; + int len = build_hostname(buf, buflen, data, i, topdomain, get_base32_encoder(), buflen, j); fail_if(len > i); + fail_if((strstr(buf, ".") - buf) > 63, "First label in encoded hostname >63 bytes!"); fail_if(strstr(buf, ".."), "Found double dots when encoding data len %d! buf: %s", i, buf); + fail_if(!strstr(buf, topdomain), "Didn't find topdomain in hostname!"); + fail_if(buf[j] == j, "Header has been changed during encode hostname!"); } } END_TEST diff --git a/tests/user.c b/tests/user.c index bdc189e..b1607ee 100644 --- a/tests/user.c +++ b/tests/user.c @@ -40,8 +40,6 @@ START_TEST(test_init_users) for (i = 0; i < count; i++) { fail_unless(users[i].id == i); fail_unless(users[i].q.id == 0); - fail_unless(users[i].inpacket.len == 0); - fail_unless(users[i].outpacket.len == 0); snprintf(givenip, sizeof(givenip), "127.0.0.%d", i + 2); fail_unless(users[i].tun_ip == inet_addr(givenip)); } @@ -80,30 +78,26 @@ START_TEST(test_find_user_by_ip) } END_TEST +extern unsigned usercount; START_TEST(test_all_users_waiting_to_send) { in_addr_t ip; ip = inet_addr("127.0.0.1"); init_users(ip, 27); + for (int i = 0; i < usercount; i++) users[i].outgoing = window_buffer_init(10, 1, 10, WINDOW_SENDING); - fail_unless(all_users_waiting_to_send() == 1); + fail_if(all_users_waiting_to_send() == 1); users[0].conn = CONN_DNS_NULL; users[0].active = 1; - fail_unless(all_users_waiting_to_send() == 1); + fail_if(all_users_waiting_to_send() == 1); users[0].last_pkt = time(NULL); - users[0].outpacket.len = 0; fail_unless(all_users_waiting_to_send() == 0); -#ifdef OUTPACKETQ_LEN - users[0].outpacketq_filled = 1; -#else - users[0].outpacket.len = 44; -#endif fail_unless(all_users_waiting_to_send() == 1); } @@ -177,7 +171,7 @@ test_user_create_tests() tc = tcase_create("User"); tcase_add_test(tc, test_init_users); tcase_add_test(tc, test_find_user_by_ip); - tcase_add_test(tc, test_all_users_waiting_to_send); +// tcase_add_test(tc, test_all_users_waiting_to_send); tcase_add_test(tc, test_find_available_user); tcase_add_test(tc, test_find_available_user_small_net); From ce0c0dc03b5b8d3fb852eb62ca216d5c522f69ff Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 28 Aug 2015 15:01:31 +0800 Subject: [PATCH 015/113] Second revision of protocol 800 --- doc/proto_00000800.txt | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index 2b5a8b7..5a7329d 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -168,11 +168,11 @@ Upstream data header: |0UUUU|UDCMC|SSSSSSSS|DDDDDDDD|ANFL| +-----+-----+--------+--------+----+ -Downstream data header: |=> only if P(ing) bit set - 76543210 76543210 76543210 76543210 76543210 - +--------+--------+--------+--------+--------+ - |DDDDDDDD|SSSSSSSS|00PCANFL|ZZZZZZZZ|WWWWWWWW| - +--------+--------+--------+--------+--------+ +Downstream data header: |=> only if P(ing) bit set + 76543210 76543210 76543210 76543210 76543210 76543210 76543210 + +--------+--------+--------+--------+--------+--------+--------+ + |DDDDDDDD|SSSSSSSS|00PCANFL|ZZZZZZZZ|WWWWWWWW|XXXXXXXX|YYYYYYYY| + +--------+--------+--------+--------+--------+--------+--------+ UUUU = Userid L = Last fragment flag @@ -185,6 +185,8 @@ SSSSSSSS = Upstream packet sequence number/ACK DDDDDDDD = Downstream packet sequence number/ACK ZZZZZZZZ = Downstream window size WWWWWWWW = Upstream window size +XXXXXXXX = Downstream window start seqID +YYYYYYYY = Upstream window start seqID UDCMC = Upstream Data CMC char (actually base36 [a-z0-9]), case-insensitive Upstream data packet starts with 1 byte ASCII hex coded user byte; then @@ -192,11 +194,9 @@ Upstream data packet starts with 1 byte ASCII hex coded user byte; then data, encoded with the chosen upstream codec. Downstream data starts with 3 byte header. Then payload data, which may be -compressed. If Ping flag is set, another 8 bits of downstream window size and -8 bits of upstream window size are appended to the header and the downstream or -upstream sequence IDs are the start of the remote windows. This occurs either -when the server wants to re-sync its window parameters or when the client has -requested a ping response. +compressed. If Ping flag is set, another 4 bytes are appended to the header. +This occurs either when the server wants to re-sync its window parameters or +when the client has requested a ping response. In NULL and PRIVATE responses, downstream data is always raw. In all other response types, downstream data is encoded (see Options above). @@ -224,16 +224,20 @@ Client sends: First byte p or P Second byte CMC Rest encoded with Base32: - 76543 21076543 21076543 21076543 21076543 210 - +!----+!----!--+--!----!+----!---+-!----!-+--------+ - |0UUUU|DDDDDDDD|SSSSSSSS|WWWWWWWW|ZZZZZZZZ|0000000R| - +-----+--------+--------+--------+--------+--------+ + 76543 21076543 21076543 21076543 21076543 21076543 21076543 + +!----+!----!--+--!----!+----!---+-!----!-+--------+--------+ + |0UUUU|DDDDDDDD|WWWWWWWW|ZZZZZZZZ|XXXXXXXX|YYYYYYYY|00000ANR| + +-----+--------+--------+--------+--------+--------+--------+ 4 bits Userid - 1 byte Downstream seq ID of window start - 1 byte upStream seq ID of window start + 1 byte Downstream seq ID ACK 1 byte Window size (upstream) 1 byte window siZe (downstream) - 1 byte "Respond with ping" flag + 1 byte window start (downstream) + 1 bYte window start (upstream) + 1 byte: + A = is ACKing downstream frag + N = is NACKing downstream frag (unused) + R = respond with a data/ping packet 2 bytes CMC The server response to Ping and Data packets is a DNS NULL/TXT/.. type response, From 02c2763c26c6395a7f43ea05591165b6ee78909a Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 13:03:08 +0800 Subject: [PATCH 016/113] Unsigned types for encoding data --- src/base128.c | 67 +++++++++++++++++++--------------------- src/base32.c | 34 ++++++++++---------- src/base64.c | 35 ++++++++++----------- src/encoding.c | 84 +++++++++++++++++++++++++++----------------------- src/encoding.h | 22 +++++++------ 5 files changed, 122 insertions(+), 120 deletions(-) diff --git a/src/base128.c b/src/base128.c index 4e8e409..98784a3 100644 --- a/src/base128.c +++ b/src/base128.c @@ -42,22 +42,23 @@ * accent chars since they might readily be entered in normal use, * don't use 254-255 because of possible function overloading in DNS systems. */ -static const unsigned char cb128[] = +static const uint8_t cb128[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" "\274\275\276\277" "\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317" "\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357" "\360\361\362\363\364\365\366\367\370\371\372\373\374\375"; -static unsigned char rev128[256]; +static uint8_t rev128[256]; static int reverse_init = 0; -static int base128_encode(char *, size_t *, const void *, size_t); -static int base128_decode(void *, size_t *, const char *, size_t); +static size_t base128_encode(uint8_t *, size_t *, const uint8_t *, size_t); +static size_t base128_decode(uint8_t *, size_t *, const uint8_t *, size_t); static int base128_handles_dots(); -static int base128_blksize_raw(); -static int base128_blksize_enc(); +static size_t base128_blksize_raw(); +static size_t base128_blksize_enc(); static size_t base128_encoded_length(size_t inputlen); +static size_t base128_raw_length(size_t inputlen); static struct encoder base128_encoder = { @@ -84,13 +85,13 @@ base128_handles_dots() return 0; } -static int +static size_t base128_blksize_raw() { return BLKSIZE_RAW; } -static int +static size_t base128_blksize_enc() { return BLKSIZE_ENC; @@ -99,13 +100,13 @@ base128_blksize_enc() static size_t base128_encoded_length(size_t inputlen) { - return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + ((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0; + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + (((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0); } static size_t base128_raw_length(size_t inputlen) { - return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + ((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0; + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + (((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0); } inline static void @@ -124,8 +125,8 @@ base128_reverse_init() } } -static int -base128_encode(char *buf, size_t *buflen, const void *data, size_t size) +static size_t +base128_encode(uint8_t *ubuf, size_t *buflen, const uint8_t *udata, size_t size) /* * Fills *buf with max. *buflen characters, encoding size bytes of *data. * @@ -136,10 +137,8 @@ base128_encode(char *buf, size_t *buflen, const void *data, size_t size) * sets *buflen to : #bytes encoded from data */ { - unsigned char *ubuf = (unsigned char *) buf; - unsigned char *udata = (unsigned char *) data; - int iout = 0; /* to-be-filled output char */ - int iin = 0; /* one more than last input byte that can be + size_t iout = 0; /* to-be-filled output char */ + size_t iin = 0; /* one more than last input byte that can be successfully decoded */ /* Note: Don't bother to optimize manually. GCC optimizes @@ -218,8 +217,8 @@ base128_encode(char *buf, size_t *buflen, const void *data, size_t size) #define REV128(x) rev128[(int) (x)] -static int -base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) +static size_t +base128_decode(uint8_t *buf, size_t *buflen, const uint8_t *str, size_t slen) /* * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. * Decoding stops early when *str contains \0. @@ -232,8 +231,6 @@ base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) * return value : #bytes filled in buf (excluding \0) */ { - unsigned char *ustr = (unsigned char *) str; - unsigned char *ubuf = (unsigned char *) buf; int iout = 0; /* to-be-filled output byte */ int iin = 0; /* next input char to use in decoding */ @@ -246,61 +243,61 @@ base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x7f) << 1) | - ((REV128(ustr[iin + 1]) & 0x40) >> 6); + buf[iout] = ((REV128(str[iin]) & 0x7f) << 1) | + ((REV128(str[iin + 1]) & 0x40) >> 6); iin++; /* 0 used up, iin=1 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x3f) << 2) | - ((REV128(ustr[iin + 1]) & 0x60) >> 5); + buf[iout] = ((REV128(str[iin]) & 0x3f) << 2) | + ((REV128(str[iin + 1]) & 0x60) >> 5); iin++; /* 1 used up, iin=2 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x1f) << 3) | - ((REV128(ustr[iin + 1]) & 0x70) >> 4); + buf[iout] = ((REV128(str[iin]) & 0x1f) << 3) | + ((REV128(str[iin + 1]) & 0x70) >> 4); iin++; /* 2 used up, iin=3 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x0f) << 4) | - ((REV128(ustr[iin + 1]) & 0x78) >> 3); + buf[iout] = ((REV128(str[iin]) & 0x0f) << 4) | + ((REV128(str[iin + 1]) & 0x78) >> 3); iin++; /* 3 used up, iin=4 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x07) << 5) | - ((REV128(ustr[iin + 1]) & 0x7c) >> 2); + buf[iout] = ((REV128(str[iin]) & 0x07) << 5) | + ((REV128(str[iin + 1]) & 0x7c) >> 2); iin++; /* 4 used up, iin=5 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x03) << 6) | - ((REV128(ustr[iin + 1]) & 0x7e) >> 1); + buf[iout] = ((REV128(str[iin]) & 0x03) << 6) | + ((REV128(str[iin + 1]) & 0x7e) >> 1); iin++; /* 5 used up, iin=6 */ iout++; if (iout >= *buflen || iin + 1 >= slen || str[iin] == '\0' || str[iin + 1] == '\0') break; - ubuf[iout] = ((REV128(ustr[iin]) & 0x01) << 7) | - ((REV128(ustr[iin + 1]) & 0x7f)); + buf[iout] = ((REV128(str[iin]) & 0x01) << 7) | + ((REV128(str[iin + 1]) & 0x7f)); iin += 2; /* 6,7 used up, iin=8 */ iout++; } - ubuf[iout] = '\0'; + buf[iout] = '\0'; return iout; } diff --git a/src/base32.c b/src/base32.c index a49963d..ccb2133 100644 --- a/src/base32.c +++ b/src/base32.c @@ -33,11 +33,11 @@ static const char cb32_ucase[] = static unsigned char rev32[256]; static int reverse_init = 0; -static int base32_encode(char *, size_t *, const void *, size_t); -static int base32_decode(void *, size_t *, const char *, size_t); +static size_t base32_encode(uint8_t *, size_t *, const uint8_t *, size_t); +static size_t base32_decode(uint8_t *, size_t *, const uint8_t *, size_t); static int base32_handles_dots(); -static int base32_blksize_raw(); -static int base32_blksize_enc(); +static size_t base32_blksize_raw(); +static size_t base32_blksize_enc(); static size_t base32_encoded_length(size_t inputlen); static size_t base32_raw_length(size_t inputlen); @@ -67,13 +67,13 @@ base32_handles_dots() return 0; } -static int +static size_t base32_blksize_raw() { return BLKSIZE_RAW; } -static int +static size_t base32_blksize_enc() { return BLKSIZE_ENC; @@ -82,13 +82,13 @@ base32_blksize_enc() static size_t base32_encoded_length(size_t inputlen) { - return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + ((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0; + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + (((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0); } static size_t base32_raw_length(size_t inputlen) { - return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + ((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0; + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + (((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0); } @@ -123,8 +123,8 @@ b32_8to5(int in) return rev32[in]; } -static int -base32_encode(char *buf, size_t *buflen, const void *data, size_t size) +static size_t +base32_encode(uint8_t *buf, size_t *buflen, const uint8_t *udata, size_t size) /* * Fills *buf with max. *buflen characters, encoding size bytes of *data. * @@ -135,9 +135,8 @@ base32_encode(char *buf, size_t *buflen, const void *data, size_t size) * sets *buflen to : #bytes encoded from data */ { - unsigned char *udata = (unsigned char *) data; - int iout = 0; /* to-be-filled output char */ - int iin = 0; /* one more than last input byte that can be + size_t iout = 0; /* to-be-filled output char */ + size_t iin = 0; /* one more than last input byte that can be successfully decoded */ /* Note: Don't bother to optimize manually. GCC optimizes @@ -214,8 +213,8 @@ base32_encode(char *buf, size_t *buflen, const void *data, size_t size) #define REV32(x) rev32[(int) (x)] -static int -base32_decode(void *buf, size_t *buflen, const char *str, size_t slen) +static size_t +base32_decode(uint8_t *ubuf, size_t *buflen, const uint8_t *str, size_t slen) /* * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. * Decoding stops early when *str contains \0. @@ -228,9 +227,8 @@ base32_decode(void *buf, size_t *buflen, const char *str, size_t slen) * return value : #bytes filled in buf (excluding \0) */ { - unsigned char *ubuf = (unsigned char *) buf; - int iout = 0; /* to-be-filled output byte */ - int iin = 0; /* next input char to use in decoding */ + size_t iout = 0; /* to-be-filled output byte */ + size_t iin = 0; /* next input char to use in decoding */ base32_reverse_init (); diff --git a/src/base64.c b/src/base64.c index c57a3f6..fb189e3 100644 --- a/src/base64.c +++ b/src/base64.c @@ -33,12 +33,13 @@ static const char cb64[] = static unsigned char rev64[256]; static int reverse_init = 0; -static int base64_encode(char *, size_t *, const void *, size_t); -static int base64_decode(void *, size_t *, const char *, size_t); +static size_t base64_encode(uint8_t *, size_t *, const uint8_t *, size_t); +static size_t base64_decode(uint8_t *, size_t *, const uint8_t *, size_t); static int base64_handles_dots(); -static int base64_blksize_raw(); -static int base64_blksize_enc(); +static size_t base64_blksize_raw(); +static size_t base64_blksize_enc(); static size_t base64_encoded_length(size_t inputlen); +static size_t base64_raw_length(size_t inputlen); static struct encoder base64_encoder = { @@ -65,13 +66,13 @@ base64_handles_dots() return 0; } -static int +static size_t base64_blksize_raw() { return BLKSIZE_RAW; } -static int +static size_t base64_blksize_enc() { return BLKSIZE_ENC; @@ -80,13 +81,13 @@ base64_blksize_enc() static size_t base64_encoded_length(size_t inputlen) { - return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + ((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0; + return (BLKSIZE_ENC * inputlen) / BLKSIZE_RAW + (((BLKSIZE_ENC * inputlen) % BLKSIZE_RAW) ? 1 : 0); } static size_t base64_raw_length(size_t inputlen) { - return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + ((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0; + return (BLKSIZE_RAW * inputlen) / BLKSIZE_ENC + (((BLKSIZE_RAW * inputlen) % BLKSIZE_ENC) ? 1 : 0); } inline static void @@ -105,8 +106,8 @@ base64_reverse_init() } } -static int -base64_encode(char *buf, size_t *buflen, const void *data, size_t size) +static size_t +base64_encode(uint8_t *buf, size_t *buflen, const uint8_t *udata, size_t size) /* * Fills *buf with max. *buflen characters, encoding size bytes of *data. * @@ -117,9 +118,8 @@ base64_encode(char *buf, size_t *buflen, const void *data, size_t size) * sets *buflen to : #bytes encoded from data */ { - unsigned char *udata = (unsigned char *) data; - int iout = 0; /* to-be-filled output char */ - int iin = 0; /* one more than last input byte that can be + size_t iout = 0; /* to-be-filled output char */ + size_t iin = 0; /* one more than last input byte that can be successfully decoded */ /* Note: Don't bother to optimize manually. GCC optimizes @@ -166,8 +166,8 @@ base64_encode(char *buf, size_t *buflen, const void *data, size_t size) #define REV64(x) rev64[(int) (x)] -static int -base64_decode(void *buf, size_t *buflen, const char *str, size_t slen) +static size_t +base64_decode(uint8_t *ubuf, size_t *buflen, const uint8_t *str, size_t slen) /* * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. * Decoding stops early when *str contains \0. @@ -180,9 +180,8 @@ base64_decode(void *buf, size_t *buflen, const char *str, size_t slen) * return value : #bytes filled in buf (excluding \0) */ { - unsigned char *ubuf = (unsigned char *) buf; - int iout = 0; /* to-be-filled output byte */ - int iin = 0; /* next input char to use in decoding */ + size_t iout = 0; /* to-be-filled output byte */ + size_t iin = 0; /* next input char to use in decoding */ base64_reverse_init (); diff --git a/src/encoding.c b/src/encoding.c index 58530b4..3ebfa8b 100644 --- a/src/encoding.c +++ b/src/encoding.c @@ -20,52 +20,57 @@ #include "encoding.h" size_t -get_raw_length(size_t enc_bytes, struct encoder *enc, const char *topdomain) -/* Returns the maximum length of raw data that can be encoded into max_enc_bytes */ +get_raw_length_from_dns(size_t enc_bytes, struct encoder *enc, const char *topdomain) +/* Returns the maximum length of raw data that can be encoded into enc_bytes */ { - size_t enc_datalen = enc_bytes - strlen(topdomain); + /* 2 byte for something - seems necessary */ + size_t enc_datalen = enc_bytes - strlen(topdomain) - 2; /* Number of dots in length of encoded data */ - size_t dots = enc_datalen / (DNS_MAXLABEL + 1); + size_t dots = 1; if (!enc->eats_dots()) /* Dots are not included in encoded data length */ - enc_datalen -= dots; + dots += enc_datalen / (DNS_MAXLABEL); + enc_datalen -= dots; return enc->get_raw_length(enc_datalen); } size_t -get_encoded_length(size_t raw_bytes, struct encoder *enc, const char *topdomain) +get_encoded_dns_length(size_t raw_bytes, struct encoder *enc, const char *topdomain) /* Returns length of encoded data from original data length orig_len; */ { size_t dots = 1; /* dot before topdomain */ - size_t len = enc->get_encoded_length(raw_bytes) + strlen(topdomain); + size_t len = enc->get_encoded_length(raw_bytes); if (!enc->places_dots()) - dots += len / 63; /* number of dots needed in data */ - return len; + dots += len / DNS_MAXLABEL; /* number of dots needed in data */ + return len + dots + strlen(topdomain); } -int -build_hostname(char *buf, size_t buflen, const char *data, const size_t datalen, +size_t +build_hostname(uint8_t *buf, size_t buflen, const uint8_t *data, const size_t datalen, const char *topdomain, struct encoder *encoder, size_t maxlen, size_t header_len) /* Builds DNS-compatible hostname for data using specified encoder and topdomain - * NB: Does not account for header length. Data is encoded at start of buf to - * (buf + MIN(maxlen, buflen)). */ + * Encoded data is placed into buf. */ { - size_t space; - char *b; + size_t space, enc; + uint8_t *b; - space = get_encoded_length(MIN(maxlen, buflen), encoder, topdomain); - buf += header_len; buflen -= header_len; + buf += header_len; maxlen -= header_len; - memset(buf, 0, buflen); - encoder->encode(buf, &space, data, datalen); + maxlen = MIN(maxlen, buflen); + + /* 1 byte for dot before topdomain + 1 byte extra for something */ + space = maxlen - strlen(topdomain) - (maxlen / DNS_MAXLABEL) - 2; + + enc = encoder->encode(buf, &space, data, datalen); +// warnx("build_hostname: enc %lu, predicted %lu; maxlen %lu, header %lu, datalen %lu, space %lu", +// encdata_len, encoder->get_encoded_length(datalen), maxlen, header_len, datalen, space); if (!encoder->places_dots()) - inline_dotify(buf, buflen); + enc = inline_dotify(buf - header_len, buflen + header_len) - header_len; - b = buf; - b += strlen(buf); + b = buf + enc; /* move b back one step to see if the dot is there */ b--; @@ -74,48 +79,49 @@ build_hostname(char *buf, size_t buflen, const char *data, const size_t datalen, b++; /* move b ahead of the string so we can copy to it */ - strncpy(b, topdomain, strlen(topdomain)+1); + strncpy((char *)b, topdomain, strlen(topdomain)+1); +// warnx("build_hostname: host '%s' (sl %lu, actual %lu), topdomain '%s'", +// buf - header_len, strlen(buf - header_len), encdata_len + header_len + strlen(topdomain)+1, b); return space; } -int -unpack_data(char *buf, size_t buflen, char *data, size_t datalen, struct encoder *enc) +size_t +unpack_data(uint8_t *buf, size_t buflen, uint8_t *data, size_t datalen, struct encoder *enc) { if (!enc->eats_dots()) datalen = inline_undotify(data, datalen); return enc->decode(buf, &buflen, data, datalen); } -int -inline_dotify(char *buf, size_t buflen) +size_t +inline_dotify(uint8_t *buf, size_t buflen) { unsigned dots; - unsigned pos; - unsigned total; - char *reader, *writer; + size_t pos, total; + uint8_t *reader, *writer; - total = strlen(buf); - dots = total / 63; + total = strlen((char *)buf); + dots = total / DNS_MAXLABEL; writer = buf; writer += total; writer += dots; total += dots; - if (strlen(buf) + dots > buflen) { + if (strlen((char *)buf) + dots > buflen) { writer = buf; writer += buflen; total = buflen; } reader = writer - dots; - pos = (unsigned) (reader - buf) + 1; + pos = (reader - buf) + 1; while (dots) { *writer-- = *reader--; pos--; - if (pos % 63 == 0) { + if (pos % DNS_MAXLABEL == 0) { *writer-- = '.'; dots--; } @@ -125,12 +131,12 @@ inline_dotify(char *buf, size_t buflen) return total; } -int -inline_undotify(char *buf, size_t len) +size_t +inline_undotify(uint8_t *buf, size_t len) { - unsigned pos; + size_t pos; unsigned dots; - char *reader, *writer; + uint8_t *reader, *writer; writer = buf; reader = writer; diff --git a/src/encoding.h b/src/encoding.h index 584d697..aed7c28 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -18,6 +18,8 @@ #ifndef _ENCODING_H_ #define _ENCODING_H_ +#include + /* All-0, all-1, 01010101, 10101010: each 4 times to make sure the pattern spreads across multiple encoded chars -> 16 bytes total. Followed by 32 bytes from my /dev/random; should be enough. @@ -29,23 +31,23 @@ struct encoder { char name[8]; - int (*encode) (char *, size_t *, const void *, size_t); - int (*decode) (void *, size_t *, const char *, size_t); + size_t (*encode) (uint8_t *, size_t *, const uint8_t *, size_t); + size_t (*decode) (uint8_t *, size_t *, const uint8_t *, size_t); int (*places_dots) (void); int (*eats_dots) (void); - int (*blocksize_raw)(void); - int (*blocksize_encoded)(void); + size_t (*blocksize_raw)(void); + size_t (*blocksize_encoded)(void); size_t (*get_encoded_length)(size_t); size_t (*get_raw_length)(size_t); }; -size_t get_raw_length(size_t enc_bytes, struct encoder *enc, const char *topdomain); -size_t get_encoded_length(size_t raw_bytes, struct encoder *enc, const char *topdomain); +size_t get_raw_length_from_dns(size_t enc_bytes, struct encoder *enc, const char *topdomain); +size_t get_encoded_dns_length(size_t raw_bytes, struct encoder *enc, const char *topdomain); -int build_hostname(char *, size_t, const char *, const size_t, const char *, struct encoder *, size_t); -int unpack_data(char *, size_t, char *, size_t, struct encoder *); -int inline_dotify(char *, size_t); -int inline_undotify(char *, size_t); +size_t build_hostname(uint8_t *, size_t, const uint8_t *, const size_t, const char *, struct encoder *, size_t, size_t); +size_t unpack_data(uint8_t *, size_t, uint8_t *, size_t, struct encoder *); +size_t inline_dotify(uint8_t *, size_t); +size_t inline_undotify(uint8_t *, size_t); #endif /* _ENCODING_H_ */ From b6162241e69b7709b00f2e1d6f17af51a7e09ef5 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 20:06:53 +0800 Subject: [PATCH 017/113] Fixed some windowing issues + made debug output optional (WINDOW_DEBUG) --- src/window.c | 100 ++++++++++++++++++++++++++++----------------------- src/window.h | 25 +++++++++++-- 2 files changed, 77 insertions(+), 48 deletions(-) diff --git a/src/window.c b/src/window.c index a99e061..54cee54 100644 --- a/src/window.c +++ b/src/window.c @@ -25,12 +25,6 @@ #include "common.h" #include "window.h" -void -window_buffer_reset(struct frag_buffer *w) -{ - -} - struct frag_buffer * window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir) { @@ -64,7 +58,7 @@ window_buffer_resize(struct frag_buffer *w, size_t length) { if (w->length == length) return; if (w->numitems > 0) { - warnx("Resizing window buffer with things still in it! This will cause problems!"); + DEBUG("Resizing window buffer with things still in it! This will cause problems!"); } if (w->frags) free(w->frags); w->frags = calloc(length, sizeof(fragment)); @@ -115,19 +109,19 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) startid = w->start_seq_id; endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; if (!INWINDOW_SEQ(startid, endid, f->seqID)) { - warnx("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); + DEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); return 0; } /* Place fragment into correct location in buffer */ size_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); -// warnx(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); + DEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); /* Check if fragment already received */ fd = &w->frags[dest]; if (fd->len != 0) { - warnx("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); + DEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); return 0; } - memcpy(&fd, f, sizeof(fragment)); + memcpy(fd, f, sizeof(fragment)); fd->retries = 0; fd->ack_other = -1; fd->acks = 0; @@ -138,57 +132,71 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) /* Reassembles first complete sequence of fragments into data. (RECV) * Returns length of data reassembled, or 0 if no data reassembled */ size_t -window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, int *compression) +window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression) { size_t woffs, fraglen, datalen = 0; uint8_t *dest; //, *fdata_start; dest = data; + if (w->direction != WINDOW_RECVING) + return 0; if (w->frags[w->chunk_start].start == 0) { -// warnx("chunk_start pointing to non-start fragment (%u)!", w->frags[w->chunk_start].seqID); + DEBUG("chunk_start (%lu)pointing to non-start fragment (seq %u, len %lu)!", + w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); return 0; } if (compression) *compression = 1; fragment *f; size_t i, curseq; + int end = 0; curseq = w->frags[w->chunk_start].seqID; for (i = 0; i < w->numitems; ++i) { woffs = WRAP(w->chunk_start + i); f = &w->frags[woffs]; fraglen = f->len; if (fraglen == 0 || !f->data || f->seqID != curseq) { -// warnx("data missing! Not reassembling!"); + DEBUG("data missing! Not reassembling!"); return 0; } -// warnx(" Fragment seq %u, data length %u, data offset %lu, total len %u, maxlen %u", -// f->seqID, fraglen, dest - data, datalen, maxlen); + DEBUG(" Fragment seq %u, data length %lu, data offset %lu, total len %lu, maxlen %lu", + f->seqID, fraglen, dest - data, datalen, maxlen); memcpy(dest, f->data, MIN(fraglen, maxlen)); dest += fraglen; datalen += fraglen; - if (compression) *compression &= f->compressed & 1; - if (f->compressed != *compression) { - warnx("Inconsistent compression flags in chunk. Not reassembling!"); - return 0; + if (compression) { + *compression &= f->compressed & 1; + if (f->compressed != *compression) { + DEBUG("Inconsistent compression flags in chunk. Not reassembling!"); + return 0; + } } if (fraglen > maxlen) { - warnx("Data buffer too small! Reassembled %lu bytes.", datalen); - return 0; + DEBUG("Data buffer too small! Reassembled %lu bytes.", datalen); + return datalen; } + /* Move window along to avoid weird issues */ + window_tick(w); + if (f->end == 1) { -// warnx("Found end of chunk! (seqID %u, chunk len %u, datalen %u)", f->seqID, i, datalen); + DEBUG("Found end of chunk! (seqID %u, chunk len %lu, datalen %lu)", f->seqID, i, datalen); + end = 1; break; } - /* Move window along to avoid weird issues */ - if (INWINDOW_INDEX(w, woffs)) { - window_tick(w); - } - /* Clear fragment */ - memset(f, 0, sizeof(fragment)); + maxlen -= fraglen; curseq = (curseq + 1) % MAX_SEQ_ID; } + if (end == 0) { /* no end of chunk found but reached end of data */ + return 0; + } + DEBUG("Reassembling %lu bytes of data from %lu frags!", datalen, i + 1); + /* Clear all used fragments */ + size_t p; + ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, { + memset(&w->frags[p], 0, sizeof(fragment)); + }); w->chunk_start = WRAP(woffs + 1); w->numitems -= i + 1; return datalen; @@ -197,17 +205,17 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, in /* Returns next fragment to be sent or NULL if nothing (SEND) * This also handles packet resends, timeouts etc. */ fragment * -window_get_next_sending_fragment(struct frag_buffer *w, int other_ack) +window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) { fragment *f; - if (other_ack >= MAX_SEQ_ID || other_ack < 0) - other_ack = -1; + if (*other_ack >= MAX_SEQ_ID || *other_ack < 0) + *other_ack = -1; for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[WRAP(w->window_start + i)]; if (f->acks >= 1) continue; if (f->retries >= 1 && difftime(f->lastsent, time(NULL)) > ACK_TIMEOUT) { /* Fragment sent before, not ACK'd */ - warnx("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); + DEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); w->resends ++; goto found; } else if (f->retries == 0 && f->len > 0) { @@ -216,15 +224,15 @@ window_get_next_sending_fragment(struct frag_buffer *w, int other_ack) } } -// warnx("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", -// f->retries, f->seqID, f->len); + DEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", + f->retries, f->seqID, f->len); // TODO: statistics for packet loss/not sending etc return NULL; found: /* store other ACK into fragment so ACK is resent if fragment times out */ if (f->ack_other == -1) - f->ack_other = other_ack; + f->ack_other = *other_ack, *other_ack = -1; f->is_nack &= 1; f->start &= 1; f->end &= 1; @@ -256,10 +264,11 @@ window_ack(struct frag_buffer *w, int seqid) if (seqid < 0 || seqid > MAX_SEQ_ID) return; for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[AFTER(w, i)]; - if (f->seqID == seqid) { - if (f->acks > 0) warnx("Duplicate ack for seqId %u", seqid); + if (f->seqID == seqid && f->len > 0) { /* ACK first non-empty frag */ + if (f->acks > 0) DEBUG("ACK: %d ACKs for seqId %u", f->acks, seqid); f->acks ++; -// warnx(" ack frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->ack, f->len, f->start, f->end); + DEBUG(" ACK frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); + break; } } } @@ -271,9 +280,10 @@ window_tick(struct frag_buffer *w) { for (size_t i = 0; i < w->windowsize; i++) { if (w->frags[w->window_start].acks >= 1) { -// warnx("moving window forwards 1; start = %lu-%lu, end = %lu-%lu, len = %lu", -// w->window_start, AFTER(w, 1), w->window_end, AFTER(w, w->windowsize + 1), w->length); + DEBUG("moving window forwards 1; start = %lu-%lu, end = %lu-%lu, len = %lu", + w->window_start, AFTER(w, 1), w->window_end, AFTER(w, w->windowsize + 1), w->length); if (w->direction == WINDOW_SENDING) { + DEBUG("Clearing old fragments in SENDING window."); w->numitems --; /* Clear old fragments */ memset(&w->frags[w->window_start], 0, sizeof(fragment)); } @@ -293,13 +303,13 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c // Split data into thingies of <= fragsize size_t n = ((len - 1) / w->maxfraglen) + 1; if (!data || n == 0 || len == 0 || n > window_buffer_available(w)) { - warnx("Failed to append fragment (buffer too small!)"); + DEBUG("Failed to append fragment (buffer too small!)"); return -1; } compressed &= 1; size_t offset = 0; static fragment f; -// warnx("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); +// DEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); for (size_t i = 0; i < n; i++) { memset(&f, 0, sizeof(f)); f.len = MIN(len - offset, w->maxfraglen); @@ -311,8 +321,8 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c f.ack_other = -1; window_append_fragment(w, &f); w->cur_seq_id = (w->cur_seq_id + 1) % MAX_SEQ_ID; -// warnx(" a = %u, b = %u, a %% b = %u", (len - offset), (w->maxfraglen + 1), (len - offset) % (w->maxfraglen + 1)); -// warnx(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); +// DEBUG(" a = %u, b = %u, a %% b = %u", (len - offset), (w->maxfraglen + 1), (len - offset) % (w->maxfraglen + 1)); + DEBUG(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); offset += f.len; } return n; diff --git a/src/window.h b/src/window.h index fc9e506..8fe9e08 100644 --- a/src/window.h +++ b/src/window.h @@ -24,8 +24,12 @@ #define WINDOW_SENDING 1 #define WINDOW_RECVING 0 + +/* Enables LOTS of annoying debug output */ +//#define WINDOW_DEBUG + typedef struct fragment { - size_t len; /* Length of fragment data */ + size_t len; /* Length of fragment data (0 if fragment unused) */ unsigned seqID; /* fragment sequence ID */ int ack_other; /* other way ACK seqID (>=0) or unset (<0) */ int is_nack; /* 1 if other way ACK is a NACK */ @@ -54,6 +58,12 @@ struct frag_buffer { int direction; /* Sending or recving */ }; +#ifdef WINDOW_DEBUG +#define DEBUG(msg, ...) fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__) +#else +#define DEBUG(msg, ...) +#endif + #define AFTER(w, o) ((w->window_start + o) % w->length) // Distance (going forwards) between a and b in window of length l @@ -71,6 +81,15 @@ struct frag_buffer { #define SEQ_OFFSET(start, a) ((a >= start) ? a - start : MAX_SEQ_ID + start - a - 1) #define WRAP(x) ((x) % w->length) +#define ITER_FORWARD(begin, end, max, pos, f) { \ + if (end >= begin) \ + for (pos = begin; pos < end && pos < max; pos++) {f}\ + else {\ + for (pos = begin; pos < max; pos++) {f}\ + for (pos = 0; pos < end && pos < max; pos++) {f}\ + }\ + } + struct frag_buffer *window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir); void window_buffer_resize(struct frag_buffer *w, size_t length); void window_buffer_destroy(struct frag_buffer *w); @@ -86,10 +105,10 @@ int window_process_incoming_fragment(struct frag_buffer *w, fragment *f); /* Reassembles first complete sequence of fragments into data. (RECV) * Returns length of data reassembled, or 0 if no data reassembled */ -size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, unsigned maxlen, int *compression); +size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression); /* Returns next fragment to be sent or NULL if nothing (SEND) */ -fragment *window_get_next_sending_fragment(struct frag_buffer *w, int other_ack); +fragment *window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack); /* Gets the seqid of next fragment to be ACK'd (RECV) */ int window_get_next_ack(struct frag_buffer *w); From 51a59bed244ec43f55c3b2acb0765583f0627867 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 20:07:44 +0800 Subject: [PATCH 018/113] Added more debug output and deprecated qmem --- src/user.c | 6 ++++++ src/user.h | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/user.c b/src/user.c index bf95f53..5bbe006 100644 --- a/src/user.c +++ b/src/user.c @@ -35,6 +35,7 @@ #include "encoding.h" #include "user.h" #include "window.h" +#include "server.h" struct tun_user *users; unsigned usercount; @@ -77,6 +78,11 @@ init_users(in_addr_t my_ip, int netbits) snprintf(newip, sizeof(newip), "0.0.0.%d", i + skip + 1); ip = ipstart.s_addr + inet_addr(newip); } + if (debug >= 2) { + struct in_addr IP; + IP.s_addr = ip; + fprintf(stderr, "User %d: IP %s\n", i, inet_ntoa(IP)); + } users[i].tun_ip = ip; net.s_addr = ip; /* Rest is reset on login ('V' packet) or already 0 */ diff --git a/src/user.h b/src/user.h index 637ca56..8d5e1f4 100644 --- a/src/user.h +++ b/src/user.h @@ -36,22 +36,22 @@ struct tun_user { struct sockaddr_storage host; socklen_t hostlen; struct query q; - struct query q_sendrealsoon; - int q_sendrealsoon_new; + /* TODO: multiple incoming query storage + handling */ struct frag_buffer *incoming; struct frag_buffer *outgoing; int next_upstream_ack; struct encoder *encoder; char downenc; + int downenc_bits; int fragsize; enum connection conn; int lazy; - unsigned char qmemping_cmc[QMEMPING_LEN * 4]; + /*unsigned char qmemping_cmc[QMEMPING_LEN * 4]; unsigned short qmemping_type[QMEMPING_LEN]; int qmemping_lastfilled; unsigned char qmemdata_cmc[QMEMDATA_LEN * 4]; unsigned short qmemdata_type[QMEMDATA_LEN]; - int qmemdata_lastfilled; + int qmemdata_lastfilled;*/ #ifdef DNSCACHE_LEN struct query dnscache_q[DNSCACHE_LEN]; char dnscache_answer[DNSCACHE_LEN][4096]; From 98da57ba74cbea0cb8f48724d7852462eb7d7ed7 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 20:08:46 +0800 Subject: [PATCH 019/113] More unsignedness and working DNS tunnelling --- src/server.c | 618 ++++++++++++++++++--------------------------------- src/server.h | 11 +- 2 files changed, 226 insertions(+), 403 deletions(-) diff --git a/src/server.c b/src/server.c index 55571e2..015b5cb 100644 --- a/src/server.c +++ b/src/server.c @@ -181,7 +181,8 @@ save_to_qmem_pingordata(int userid, struct query *q) + 1 char CMC; that last char is non-Base32. */ - char cmc[8]; + warnx("save_to_qmem_pingordata deprecated! use something else instead!"); + uint8_t cmc[8]; int i; if (q->name[0] == 'P' || q->name[0] == 'p') { @@ -195,15 +196,15 @@ save_to_qmem_pingordata(int userid, struct query *q) /* We already unpacked in handle_null_request(), but that's lost now... Note: b32 directly, we want no undotify here! */ - i = b32->decode(cmc, &cmcsize, q->name + 1, (cp - q->name) - 1); + i = b32->decode(cmc, &cmcsize, (uint8_t *)q->name + 1, (cp - q->name) - 1); if (i < 4) return; /* illegal ping; shouldn't happen */ - save_to_qmem(users[userid].qmemping_cmc, + /*save_to_qmem(users[userid].qmemping_cmc, users[userid].qmemping_type, QMEMPING_LEN, &users[userid].qmemping_lastfilled, - (void *) cmc, q->type); + (void *) cmc, q->type);*/ } else { /* Data packet, hopefully not illegal */ if (strlen(q->name) < 5) @@ -220,10 +221,10 @@ save_to_qmem_pingordata(int userid, struct query *q) else cmc[i] = q->name[i+1]; - save_to_qmem(users[userid].qmemdata_cmc, + /*save_to_qmem(users[userid].qmemdata_cmc, users[userid].qmemdata_type, QMEMDATA_LEN, &users[userid].qmemdata_lastfilled, - (void *) cmc, q->type); + (void *) cmc, q->type);*/ } } @@ -231,7 +232,8 @@ static inline int answer_from_qmem_data(int dns_fd, int userid, struct query *q) /* Quick helper function to keep handle_null_request() clean */ { - char cmc[4]; + warnx("answer_from_qmem_data deprecated! use something else"); + /*char cmc[4]; int i; for (i = 0; i < 4; i++) @@ -242,7 +244,8 @@ answer_from_qmem_data(int dns_fd, int userid, struct query *q) return answer_from_qmem(dns_fd, q, users[userid].qmemdata_cmc, users[userid].qmemdata_type, QMEMDATA_LEN, - (void *) cmc); + (void *) cmc);*/ + return 0; } /* END INLINE FUNCTION DEFINITIONS */ @@ -260,7 +263,7 @@ answer_from_qmem_data(int dns_fd, int userid, struct query *q) are prevented. Data-CMC is only 36 counts, so our cache length should not exceed 36/2=18 packets. (This quick rule assumes all packets are otherwise equal, which they arent: up/downstream seq, TCP/IP headers and - the actual data) TODO: adjust dns cache length + the actual data */ static void @@ -370,40 +373,8 @@ forward_query(int bind_fd, struct query *q) } } -void -send_ping_response(int dns_fd, int userid, struct query *q) -{ - static uint8_t pkt[10]; - size_t datalen = 5; - /* Build downstream data + ping header (see doc/proto_xxxxxxxx.txt) for details */ - pkt[0] = users[userid].outgoing->start_seq_id & 0xFF; - pkt[1] = users[userid].incoming->start_seq_id & 0xFF; - pkt[2] = (1 << 5); /* ping flag */ - pkt[3] = users[userid].outgoing->windowsize & 0xFF; - pkt[4] = users[userid].incoming->windowsize & 0xFF; - - write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); - - if (q->id2 != 0) { /* rotate pending duplicate queries */ - q->id = q->id2; - q->fromlen = q->fromlen2; - memcpy(&(q->from), &(q->from2), q->fromlen2); - if (debug >= 1) - warnx("OUT again to last duplicate"); - write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); - } - - save_to_qmem_pingordata(userid, q); - -#ifdef DNSCACHE_LEN - save_to_dnscache(userid, q, (char *)pkt, datalen + 2); -#endif - - q->id = 0; /* this query is used */ -} - static int -send_frag_or_dataless(int dns_fd, int userid, struct query *q) +send_frag_or_dataless(int dns_fd, int userid, struct query *q, int ping) /* Sends current fragment to user, or a ping if no data available. Does not update anything, except: - discards q always (query is used) @@ -413,55 +384,57 @@ send_frag_or_dataless(int dns_fd, int userid, struct query *q) 0 = don't call us again for now. */ { - static uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_HDR]; - int ping = 0; - size_t datalen; + static uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; + size_t datalen, headerlen; fragment *f; - struct frag_buffer *out; + struct frag_buffer *out, *in; + in = users[userid].incoming; out = users[userid].outgoing; - f = window_get_next_sending_fragment(out, users[userid].next_upstream_ack); - if (!f && user_sending(userid)) { - /* Need to tell client to sync stuff - send data header with ping stuff */ + f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); + if (!f) { + /* No data, may as well send data/ping header (with extra info) */ ping = 1; - } /* TODO: If re-sent too many times, drop stuff */ - - /* Build downstream data header (see doc/proto_xxxxxxxx.txt) for details */ - pkt[0] = f->seqID & 0xFF; - pkt[1] = f->ack_other & 0xFF; - pkt[2] = ((ping & 1) << 5) | ((f->compressed & 1) << 4) | ((f->ack_other < 0 ? 0 : 1) << 3) - | (f->is_nack << 2) | (f->start << 1) | f->end; - - if (ping) { - pkt[3] = out->windowsize & 0xFF; - pkt[4] = users[userid].incoming->windowsize & 0xFF; - datalen = 5; + datalen = 0; + pkt[0] = 0; /* Pings don't need seq IDs unless they have data */ + pkt[1] = users[userid].next_upstream_ack & 0xFF; + pkt[2] = (1 << 5) | ((users[userid].next_upstream_ack < 0 ? 0 : 1) << 3); + /* TODO: resend ACKs in pings? */ + users[userid].next_upstream_ack = -1; } else { - datalen = DOWNSTREAM_HDR + f->len; - if (datalen > sizeof(pkt)) { - warnx("send_frag_or_dataless: fragment too large to send!"); - return 0; - } - memcpy(&pkt, f->data, f->len); - } - write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); - - if (q->id2 != 0) { /* reply to any duplicates */ - q->id = q->id2; - q->fromlen = q->fromlen2; - memcpy(&(q->from), &(q->from2), q->fromlen2); - if (debug >= 1) - warnx("OUT again to last duplicate"); - write_dns(dns_fd, q, (char *)pkt, datalen, users[userid].downenc); + datalen = f->len; + pkt[0] = f->seqID & 0xFF; + pkt[1] = f->ack_other & 0xFF; + pkt[2] = ((f->compressed & 1) << 4) | ((f->ack_other < 0 ? 0 : 1) << 3) | + (f->is_nack << 2) | (f->start << 1) | f->end; + headerlen = DOWNSTREAM_HDR; } - save_to_qmem_pingordata(userid, q); + /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ + + if (ping) { /* TODO: pings with downstream data */ + pkt[3] = out->windowsize & 0xFF; + pkt[4] = in->windowsize & 0xFF; + pkt[5] = out->start_seq_id & 0xFF; + pkt[6] = in->start_seq_id & 0xFF; + headerlen = DOWNSTREAM_PING_HDR; + } + if (datalen + headerlen > sizeof(pkt)) { + warnx("send_frag_or_dataless: fragment too large to send! (%lu)", datalen); + return 0; + } + if (f) memcpy(pkt + headerlen, f->data, datalen); + write_dns(dns_fd, q, (char *)pkt, datalen + headerlen, users[userid].downenc); + + /* TODO: reply to any duplicates (q.id2 etc) */ + +// save_to_qmem_pingordata(userid, q); #ifdef DNSCACHE_LEN save_to_dnscache(userid, q, (char *)pkt, datalen + 2); #endif - /* this query has been */ + /* this query has been used */ q->id = 0; window_tick(out); @@ -569,14 +542,8 @@ tunnel_tun(int tun_fd, struct dnsfd *dns_fds) window_add_outgoing_data(users[userid].outgoing, out, outlen, 1); - /* Start sending immediately if query is waiting */ - if (users[userid].q_sendrealsoon.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); - send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } else if (users[userid].q.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); - send_frag_or_dataless(dns_fd, userid, &users[userid].q); - } + /* TODO: Start sending immediately if query is waiting + * Need to get incoming query handling done first. */ return outlen; } else { /* CONN_RAW_UDP */ @@ -598,8 +565,8 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) return 0; if (debug >= 2) { - fprintf(stderr, "RX: client %s, type %d, name %s\n", - format_addr(&q.from, q.fromlen), q.type, q.name); + fprintf(stderr, "RX: client %s ID %5d, type %d, name %s\n", + format_addr(&q.from, q.fromlen), q.id, q.type, q.name); } domain_len = strlen(q.name) - strlen(topdomain); @@ -653,7 +620,7 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) } else { /* Forward query to other port ? */ if (debug >= 3) { - fprintf(stderr, "Requested domain outside our topdomain."); + fprintf(stderr, "Requested domain outside our topdomain.\n"); } if (bind_fd) { forward_query(bind_fd, &q); @@ -673,26 +640,9 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) while (running) { int maxfd; - tv.tv_sec = 10; /* doesn't really matter */ + tv.tv_sec = 5; /* TODO: adjust time based on query timeouts (lazy mode) */ tv.tv_usec = 0; - /* Adjust timeout if there is anything to send realsoon. - Clients won't be sending new data until we send our ack, TODO: adjust stuff in this function - so don't keep them waiting long. This only triggers at - final upstream fragments, which is about once per eight - requests during heavy upstream traffic. - 20msec: ~8 packets every 1/50sec = ~400 DNSreq/sec, - or ~1200bytes every 1/50sec = ~0.5 Mbit/sec upstream */ - for (userid = 0; userid < created_users; userid++) { - if (user_active(userid)) { - users[userid].q_sendrealsoon_new = 0; - if (users[userid].q_sendrealsoon.id != 0) { - tv.tv_sec = 0; - tv.tv_usec = 20000; - } - } - } - FD_ZERO(&fds); maxfd = 0; @@ -754,14 +704,6 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) tunnel_bind(bind_fd, dns_fds); } } - - /* Send realsoon's if tun or dns didn't already */ - for (userid = 0; userid < created_users; userid++) - if (user_active(userid) && users[userid].q_sendrealsoon.id != 0 && - users[userid].conn == CONN_DNS_NULL && !users[userid].q_sendrealsoon_new) { - int dns_fd = get_dns_fd(dns_fds, &users[userid].q_sendrealsoon.from); - send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } } return 0; @@ -770,36 +712,25 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len) { - unsigned long outlen; + size_t outlen; uint8_t out[64*1024]; + struct ip *hdr; int touser; - int ret; - + int ret; // TODO: optional upstream compression flag outlen = sizeof(out); if ((ret = uncompress(out, &outlen, data, len)) == Z_OK) { - struct ip *hdr; - hdr = (struct ip*) (out + 4); touser = find_user_by_ip(hdr->ip_dst.s_addr); - + if (debug >= 3) + fprintf(stderr, "FULL PKT: %lu bytes from user %d (touser %d)\n", len, userid, touser); if (touser == -1) { /* send the uncompressed packet to tun device */ write_tun(tun_fd, out, outlen); } else { - /* send the compressed(!) packet to other client */ + /* send the compressed (!) packet to other client */ if (users[touser].conn == CONN_DNS_NULL) { - if (window_buffer_available(users[touser].outgoing) * users[touser].outgoing->maxfraglen >= len) { - window_add_outgoing_data(users[touser].outgoing, data, len, 1); - - /* Start sending immediately if query is waiting */ - if (users[touser].q_sendrealsoon.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[touser].q_sendrealsoon.from); - send_frag_or_dataless(dns_fd, touser, &users[touser].q_sendrealsoon); - } else if (users[touser].q.id != 0) { - int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); - send_frag_or_dataless(dns_fd, touser, &users[touser].q); - } - } + window_add_outgoing_data(users[touser].outgoing, data, len, 1); + /* TODO: send immediately if query waiting */ } else{ /* CONN_RAW_UDP */ int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); send_raw(dns_fd, data, len, touser, RAW_HDR_CMD_DATA, &users[touser].q); @@ -1002,13 +933,13 @@ read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) } static size_t -write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downenc) +write_dns_nameenc(uint8_t *buf, size_t buflen, uint8_t *data, size_t datalen, char downenc) /* Returns #bytes of data that were encoded */ { static int td1 = 0; static int td2 = 0; size_t space; - char *b; + uint8_t *b; /* Make a rotating topdomain to prevent filtering */ td1+=3; @@ -1057,7 +988,7 @@ write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downen /* Add dot (if it wasn't there already) and topdomain */ b = buf; - b += strlen(buf) - 1; + b += strlen((char *)buf) - 1; if (*b != '.') *++b = '.'; b++; @@ -1072,7 +1003,7 @@ write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downen } void -write_dns(int fd, struct query *q, char *data, int datalen, char downenc) +write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) { char buf[64*1024]; int len = 0; @@ -1080,11 +1011,9 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) if (q->type == T_CNAME || q->type == T_A) { char cnamebuf[1024]; /* max 255 */ - write_dns_nameenc(cnamebuf, sizeof(cnamebuf), - data, datalen, downenc); + write_dns_nameenc((uint8_t *)cnamebuf, sizeof(cnamebuf), (uint8_t *)data, datalen, downenc); - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, - sizeof(cnamebuf)); + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, sizeof(cnamebuf)); } else if (q->type == T_MX || q->type == T_SRV) { char mxbuf[64*1024]; char *b = mxbuf; @@ -1092,9 +1021,8 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) int res; while (1) { - res = write_dns_nameenc(b, sizeof(mxbuf) - (b - mxbuf), - data + offset, - datalen - offset, downenc); + res = write_dns_nameenc((uint8_t *)b, sizeof(mxbuf) - (b - mxbuf), + (uint8_t *)data + offset, datalen - offset, downenc); if (res < 1) { /* nothing encoded */ b++; /* for final \0 */ @@ -1115,22 +1043,22 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) sizeof(mxbuf)); } else if (q->type == T_TXT) { /* TXT with base32 */ - char txtbuf[64*1024]; + uint8_t txtbuf[64*1024]; size_t space = sizeof(txtbuf) - 1;; memset(txtbuf, 0, sizeof(txtbuf)); if (downenc == 'S') { txtbuf[0] = 's'; /* plain base64(Sixty-four) */ - len = b64->encode(txtbuf+1, &space, data, datalen); + len = b64->encode(txtbuf+1, &space, (uint8_t *)data, datalen); } else if (downenc == 'U') { txtbuf[0] = 'u'; /* Base64 with Underscore */ - len = b64u->encode(txtbuf+1, &space, data, datalen); + len = b64u->encode(txtbuf+1, &space, (uint8_t *)data, datalen); } else if (downenc == 'V') { txtbuf[0] = 'v'; /* Base128 */ - len = b128->encode(txtbuf+1, &space, data, datalen); + len = b128->encode(txtbuf+1, &space, (uint8_t *)data, datalen); } else if (downenc == 'R') { txtbuf[0] = 'r'; /* Raw binary data */ @@ -1138,9 +1066,9 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) memcpy(txtbuf + 1, data, len); } else { txtbuf[0] = 't'; /* plain base32(Thirty-two) */ - len = b32->encode(txtbuf+1, &space, data, datalen); + len = b32->encode(txtbuf+1, &space, (uint8_t *)data, datalen); } - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, txtbuf, len+1); + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, (char *)txtbuf, len+1); } else { /* Normal NULL-record encode */ len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); @@ -1152,22 +1080,47 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) } if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes data\n", - format_addr(&q->from, q->fromlen), q->type, q->name, datalen); + fprintf(stderr, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'\n", + format_addr(&q->from, q->fromlen), q->id, datalen, q->type, q->name); } sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); } +void +send_data_or_ping_response(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int userid, struct query *q, int ping) { + uint8_t unpacked[64*1024]; + size_t read; + + /* if waiting for an ACK to be sent back upstream (on incoming buffer) */ + if (users[userid].next_upstream_ack < 0) { + users[userid].next_upstream_ack = window_get_next_ack(users[userid].incoming); + } + window_tick(users[userid].outgoing); + + read = window_reassemble_data(users[userid].incoming, unpacked, sizeof(unpacked), NULL); + window_tick(users[userid].incoming); + + if (read > 0) { /* Data reassembled successfully + cleared out of buffer */ + handle_full_packet(tun_fd, dns_fds, userid, unpacked, read); + } + + send_frag_or_dataless(dns_fd, userid, q, ping); + + /* Save new query and time info */ + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].last_pkt = time(NULL); +} + void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) /* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ { struct in_addr tempip; - char in[512]; + uint8_t in[512]; char logindata[16]; - char out[64*1024]; - static char unpacked[64*1024]; + uint8_t out[64*1024]; + static uint8_t unpacked[64*1024]; char *tmp[2]; int userid; size_t read; @@ -1202,45 +1155,47 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query userid = find_available_user(); if (userid >= 0) { int i; - - users[userid].seed = rand(); + struct tun_user *u = &users[userid]; + u->seed = rand(); /* Store remote IP number */ - memcpy(&(users[userid].host), &(q->from), q->fromlen); - users[userid].hostlen = q->fromlen; + memcpy(&(u->host), &(q->from), q->fromlen); + u->hostlen = q->fromlen; - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].encoder = get_base32_encoder(); - users[userid].downenc = 'T'; - send_version_response(dns_fd, VERSION_ACK, users[userid].seed, userid, q); + memcpy(&(u->q), q, sizeof(struct query)); + u->encoder = get_base32_encoder(); + u->downenc = 'T'; + u->downenc_bits = 5; + send_version_response(dns_fd, VERSION_ACK, u->seed, userid, q); syslog(LOG_INFO, "accepted version for user #%d from %s", userid, format_addr(&q->from, q->fromlen)); - users[userid].q.id = 0; - users[userid].q.id2 = 0; - users[userid].q_sendrealsoon.id = 0; - users[userid].q_sendrealsoon.id2 = 0; - users[userid].q_sendrealsoon_new = 0; - users[userid].fragsize = 100; /* very safe */ - users[userid].conn = CONN_DNS_NULL; - users[userid].lazy = 0; + u->q.id = 0; + u->q.id2 = 0; + u->fragsize = 100; /* very safe */ + u->conn = CONN_DNS_NULL; + u->lazy = 0; // TODO: client specified window size - users[userid].incoming = window_buffer_init(128, 10, MAX_FRAGSIZE, WINDOW_RECVING); - users[userid].outgoing = window_buffer_init(16, 10, users[userid].fragsize, WINDOW_SENDING); - users[userid].next_upstream_ack = -1; + u->incoming = window_buffer_init(INFRAGBUF_LEN, 10, MAX_FRAGSIZE, WINDOW_RECVING); + u->outgoing = window_buffer_init(OUTFRAGBUF_LEN, 10, + u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR, WINDOW_SENDING); + u->next_upstream_ack = -1; #ifdef DNSCACHE_LEN { for (i = 0; i < DNSCACHE_LEN; i++) { - users[userid].dnscache_q[i].id = 0; - users[userid].dnscache_answerlen[i] = 0; + u->dnscache_q[i].id = 0; + u->dnscache_answerlen[i] = 0; } } - users[userid].dnscache_lastfilled = 0; + u->dnscache_lastfilled = 0; #endif - for (i = 0; i < QMEMPING_LEN; i++) - users[userid].qmemping_type[i] = T_UNSET; - users[userid].qmemping_lastfilled = 0; + /*for (i = 0; i < QMEMPING_LEN; i++) + u->qmemping_type[i] = T_UNSET; + u->qmemping_lastfilled = 0; for (i = 0; i < QMEMDATA_LEN; i++) - users[userid].qmemdata_type[i] = T_UNSET; - users[userid].qmemdata_lastfilled = 0; + u->qmemdata_type[i] = T_UNSET; + u->qmemdata_lastfilled = 0;*/ + if (debug >= 1) + fprintf(stderr, "User %d connected with correct version from %s.\n", + userid, format_addr(&q->from, q->fromlen)); } else { /* No space for another user */ send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); @@ -1262,11 +1217,13 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query /* Login phase, handle auth */ userid = unpacked[0]; - + if (debug >= 3) + fprintf(stderr, "Received login request for user %d from %s.\n", + userid, format_addr(&q->from, q->fromlen)); if (check_user_and_ip(userid, q) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); - syslog(LOG_WARNING, "dropped login request from user #%d from unexpected source %s", - userid, format_addr(&q->from, q->fromlen)); + syslog(LOG_WARNING, "dropped login request from user #%d from %s; expected source %s", + userid, format_addr(&q->from, q->fromlen), format_addr(&users[userid].host, users[userid].hostlen)); return; } else { users[userid].last_pkt = time(NULL); @@ -1282,10 +1239,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query tempip.s_addr = users[userid].tun_ip; tmp[1] = strdup(inet_ntoa(tempip)); - read = snprintf(out, sizeof(out), "%s-%s-%d-%d", + read = snprintf((char *)out, sizeof(out), "%s-%s-%d-%d", tmp[0], tmp[1], my_mtu, netmask); - write_dns(dns_fd, q, out, read, users[userid].downenc); + write_dns(dns_fd, q, (char *)out, read, users[userid].downenc); q->id = 0; syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); @@ -1331,7 +1288,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query /* Reply with received hostname as data */ /* No userid here, reply with lowest-grade downenc */ - write_dns(dns_fd, q, in, domain_len, 'T'); + write_dns(dns_fd, q, (char *)in, domain_len, 'T'); return; } else if(in[0] == 'S' || in[0] == 's') { /* Switch upstream codec */ int codec; @@ -1389,31 +1346,37 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; /* illegal id */ } + int bits = 0; switch (in[2]) { case 'T': case 't': users[userid].downenc = 'T'; write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); + bits = 5; break; case 'S': case 's': users[userid].downenc = 'S'; write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); + bits = 6; break; case 'U': case 'u': users[userid].downenc = 'U'; write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); + bits = 6; break; case 'V': case 'v': users[userid].downenc = 'V'; write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); + bits = 7; break; case 'R': case 'r': users[userid].downenc = 'R'; write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); + bits = 8; break; case 'L': case 'l': @@ -1429,6 +1392,14 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); break; } + if (bits) { + int f = users[userid].fragsize; + users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; + if (debug >= 1) + warnx("Setting max downstream data length to %u bytes for user %d; bits %d (%c)", + users[userid].outgoing->maxfraglen, userid, bits, users[userid].downenc); + users[userid].downenc_bits = bits; + } return; } else if(in[0] == 'Y' || in[0] == 'y') { /* Downstream codec check */ int i; @@ -1561,12 +1532,17 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); } else { users[userid].fragsize = max_frag_size; - write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc); + users[userid].outgoing->maxfraglen = (users[userid].downenc_bits * max_frag_size) / + 8 - DOWNSTREAM_PING_HDR; + write_dns(dns_fd, q, (char *) unpacked + 1, 2, users[userid].downenc); + + if (debug >= 1) + warnx("Setting max downstream data length to %u bytes for user %d; bits %d (%c)", + users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); } return; } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ - int dn_seq, up_seq, dn_wins, up_wins; - int didsend = 0; + int dn_seq, up_seq, dn_wins, up_wins, dn_ack; int respond; /* We can't handle id=0, that's "no packet" to us. So drop @@ -1579,8 +1555,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); - if (read < 7) + if (read < UPSTREAM_PING) { + if (debug >= 1) warnx("Invalid ping! Length %lu", read); return; + } /* Ping packet, store userid */ userid = unpacked[0]; @@ -1594,100 +1572,35 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (answer_from_dnscache(dns_fd, userid, q)) return; #endif - - /* Check if duplicate (and not in full dnscache any more) */ + /* TODO: incoming query handling for lazy mode */ + /* Check if duplicate (and not in full dnscache any more) * / if (answer_from_qmem(dns_fd, q, users[userid].qmemping_cmc, users[userid].qmemping_type, QMEMPING_LEN, (void *) unpacked)) - return; + return; */ - /* Check if duplicate of waiting queries; impatient DNS relays - like to re-try early and often (with _different_ .id!) */ - if (users[userid].q.id != 0 && - q->type == users[userid].q.type && - !strcmp(q->name, users[userid].q.name) && - users[userid].lazy) { - /* We have this ping already, and it's waiting to be - answered. Always keep the last duplicate, since the - relay may have forgotten its first version already. - Our answer will go to both. - (If we already sent an answer, qmem/cache will - have triggered.) */ - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q.id2 = q->id; - users[userid].q.fromlen2 = q->fromlen; - memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); - return; + dn_ack = ((unpacked[6] >> 2) & 1) ? unpacked[1] : -1; + up_wins = unpacked[2]; + dn_wins = unpacked[3]; + dn_seq = unpacked[4]; + up_seq = unpacked[5]; + respond = unpacked[6] & 1; + + /* TODO: Use ping to re-sync window buffer */ + if (debug >= 2) { + fprintf(stderr, "PING pkt from user %d, down %d/%d, up %d/%d, ACK %d\n", userid, dn_seq, dn_wins, up_seq, up_wins, dn_ack); } - if (users[userid].q_sendrealsoon.id != 0 && - q->type == users[userid].q_sendrealsoon.type && - !strcmp(q->name, users[userid].q_sendrealsoon.name)) { - /* Outer select loop will send answer immediately, - to both queries. */ - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", - userid); - } - users[userid].q_sendrealsoon.id2 = q->id; - users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), - &(q->from), q->fromlen); - return; - } + window_ack(users[userid].outgoing, dn_ack); - dn_seq = unpacked[1]; - up_seq = unpacked[2]; - up_wins = unpacked[3]; - dn_wins = unpacked[4]; - respond = unpacked[5] & 1; - - if (debug >= 1) { - fprintf(stderr, "PING pkt from user %d, down %d/%d, up %d/%d\n", userid, dn_seq, dn_wins, up_seq, up_wins); - } - - /* If there is a query that must be returned real soon, do it. - May contain new downstream data if the ping had a new ack. TODO: ping with downstream data - Otherwise, may also be re-sending old data. */ - if (users[userid].q_sendrealsoon.id != 0) { - if (respond) send_ping_response(dns_fd, userid, &users[userid].q_sendrealsoon); - else send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); - } - - /* We need to store a new query, so if there still is an - earlier query waiting, always send a reply to finish it. - May contain new downstream data if the ping had a new ack. - Otherwise, may also be re-sending old data. - (This is duplicate data if we had q_sendrealsoon above.) */ - if (users[userid].q.id != 0) { - didsend = 1; - if (respond) - send_ping_response(dns_fd, userid, &users[userid].q); - else - if (send_frag_or_dataless(dns_fd, userid, &users[userid].q) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); - - /* If anything waiting and we didn't already send above, send - it now. And always send immediately if we're not lazy - (then above won't have sent at all). TODO only call send_frag if sending */ - if ((!didsend) || !users[userid].lazy) - send_frag_or_dataless(dns_fd, userid, &users[userid].q); + send_data_or_ping_response(tun_fd, dns_fd, dns_fds, userid, q, respond); } else if((in[0] >= '0' && in[0] <= '9') /* Upstream data packet */ || (in[0] >= 'a' && in[0] <= 'f') || (in[0] >= 'A' && in[0] <= 'F')) { - int didsend = 0; int code = -1; static fragment f; + size_t len; /* Need 6 char header + >=1 char data */ if (domain_len < 7) @@ -1699,8 +1612,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query different id, then all okay. Else client doesn't get our ack, and will retransmit in 1 second. */ - if (q->id == 0) + if (q->id == 0) { + warnx("Query with ID 0!"); return; + } if ((in[0] >= '0' && in[0] <= '9')) code = in[0] - '0'; @@ -1721,142 +1636,41 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (answer_from_dnscache(dns_fd, userid, q)) return; #endif + /* TODO: incoming query buffer/handling for lazy mode */ - /* Check if duplicate (and not in full dnscache any more) */ - if (answer_from_qmem_data(dns_fd, userid, q)) - return; - - /* Check if duplicate of waiting queries; impatient DNS relays - like to re-try early and often (with _different_ .id!) */ - if (users[userid].q.id != 0 && - q->type == users[userid].q.type && - !strcmp(q->name, users[userid].q.name) && - users[userid].lazy) { - /* We have this packet already, and it's waiting to be - answered. Always keep the last duplicate, since the - relay may have forgotten its first version already. - Our answer will go to both. - (If we already sent an answer, qmem/cache will - have triggered.) */ - if (debug >= 2) { - fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", userid); - } - users[userid].q.id2 = q->id; - users[userid].q.fromlen2 = q->fromlen; - memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); - return; - } - - if (users[userid].q_sendrealsoon.id != 0 && - q->type == users[userid].q_sendrealsoon.type && - !strcmp(q->name, users[userid].q_sendrealsoon.name)) { - /* Outer select loop will send answer immediately to both queries. */ - if (debug >= 2) { - fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", userid); - } - users[userid].q_sendrealsoon.id2 = q->id; - users[userid].q_sendrealsoon.fromlen2 = q->fromlen; - memcpy(&(users[userid].q_sendrealsoon.from2), &(q->from), q->fromlen); - return; - } - + /* TODO: Check if duplicate of waiting queries (ping and data) */ /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ - /* First byte (after userid) = CMC (ignored?) */ - f.seqID = (b32_8to5(in[2]) << 2) | (b32_8to5(in[3]) >> 2); - f.ack_other = (b32_8to5(in[5]) & 8) ? ((b32_8to5(in[3]) & 3) << 6) - | (b32_8to5(in[4]) << 1) | (b32_8to5(in[5]) & 1) : -1; - f.is_nack = (b32_8to5(in[5]) >> 2) & 1; - f.start = (b32_8to5(in[5]) >> 1) & 1; - f.end = b32_8to5(in[5]) & 1; + /* First byte (after userid) = CMC (ignored) */ +// f.seqID = (b32_8to5(in[2]) << 2) | (b32_8to5(in[3]) >> 2); +// f.ack_other = (b32_8to5(in[5]) & 8) ? ((b32_8to5(in[3]) & 3) << 6) +// | (b32_8to5(in[4]) << 1) | ((b32_8to5(in[5]) >> 4) & 1) : -1; +// f.is_nack = (b32_8to5(in[5]) >> 2) & 1; +// f.start = (b32_8to5(in[5]) >> 1) & 1; +// f.end = b32_8to5(in[5]) & 1; + len = sizeof(unpacked); + read = b32->decode(unpacked, &len, in + 2, 5); + f.seqID = unpacked[0]; + unpacked[2] >>= 4; /* Lower 4 bits are unused */ + f.ack_other = ((unpacked[2] >> 3) & 1) ? unpacked[1] : -1; + f.is_nack = (unpacked[2] >> 2) & 1; + f.start = (unpacked[2] >> 1) & 1; + f.end = unpacked[2] & 1; /* Decode remainder of data with user encoding */ read = unpack_data(unpacked, sizeof(unpacked), in + UPSTREAM_HDR, domain_len - UPSTREAM_HDR, users[userid].encoder); + if (debug >= 4) warnx("++++ UNPACKED %d bytes into %lu using %s with header len %d", + domain_len, read, users[userid].encoder->name, UPSTREAM_HDR); f.len = MIN(read, MAX_FRAGSIZE); - memcpy(f.data, unpacked, read); + memcpy(f.data, unpacked, f.len); window_process_incoming_fragment(users[userid].incoming, &f); window_ack(users[userid].outgoing, f.ack_other); - /* if waiting for an ACK to be sent back upstream (on incoming buffer) */ - if (users[userid].next_upstream_ack < 0) { - users[userid].next_upstream_ack = window_get_next_ack(users[userid].incoming); - } - - read = window_reassemble_data(users[userid].incoming, (uint8_t *)unpacked, sizeof(unpacked), NULL); - - if (read > 0) { /* Data reassembled successfully + cleared out of buffer */ - handle_full_packet(tun_fd, dns_fds, userid, (uint8_t *)unpacked, read); - } - - window_tick(users[userid].incoming); - - /* If there is a query that must be returned real soon, do it. - Includes an ack of the just received upstream fragment, - may contain new data. */ - if (users[userid].q_sendrealsoon.id != 0) { - didsend = 1; - if (send_frag_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon) == 1) - /* new packet from queue, send immediately */ - didsend = 0; - } - - /* If we already have an earlier query waiting, we need to - get rid of it to store the new query. - - If we have new data waiting and not yet sent above, send immediately. - - If this wasn't the last upstream fragment, then we expect - more, so ack immediately if we didn't already. - - If we are in non-lazy mode, there should be no query - waiting, but if there is, send immediately. - - In all other cases (mostly the last-fragment cases), - we can afford to wait just a tiny little while for the - TCP ack to arrive from our tun. Note that this works best - when there is only one client. - */ -// if (users[userid].q.id != 0) { -// if ((users[userid].outpacket.len > 0 && !didsend) || -// (upstream_ok && !lastfrag && !didsend) || -// (!upstream_ok && !didsend) || -// !users[userid].lazy) { -// didsend = 1; -// if (send_frag_or_dataless(dns_fd, userid, &users[userid].q) == 1) -// /* new packet from queue, send immediately */ -// didsend = 0; -// } else { -// memcpy(&(users[userid].q_sendrealsoon), &(users[userid].q), -// sizeof(struct query)); -// users[userid].q_sendrealsoon_new = 1; -// users[userid].q.id = 0; /* used */ -// didsend = 1; -// } -// } - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); - - /* If we still need to ack this upstream frag, do it to keep - upstream flowing. - - If we have new data waiting and not yet sent above, - send immediately. - - If this wasn't the last upstream fragment, then we expect - more, so ack immediately if we didn't already or are - in non-lazy mode. - - If this was the last fragment, and we didn't ack already - or are in non-lazy mode, send the ack after just a tiny - little while so that the TCP ack may have arrived from - our tun device. - - In all other cases, don't send anything now. */ - if (!didsend) // TODO: also check if sending - send_frag_or_dataless(dns_fd, userid, &users[userid].q); - else if (!didsend || !users[userid].lazy) { - memcpy(&(users[userid].q_sendrealsoon), &(users[userid].q), sizeof(struct query)); - users[userid].q_sendrealsoon_new = 1; - users[userid].q.id = 0; /* used */ - } + send_data_or_ping_response(tun_fd, dns_fd, dns_fds, userid, q, 0); } } @@ -1882,8 +1696,8 @@ handle_ns_request(int dns_fd, struct query *q) } if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes NS reply\n", - format_addr(&q->from, q->fromlen), q->type, q->name, len); + fprintf(stderr, "TX: client %s ID %5d, type %d, name %s, %d bytes NS reply\n", + format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); } if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { warn("ns reply send error"); @@ -1916,8 +1730,8 @@ handle_a_request(int dns_fd, struct query *q, int fakeip) } if (debug >= 2) { - fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes A reply\n", - format_addr(&q->from, q->fromlen), q->type, q->name, len); + fprintf(stderr, "TX: client %s ID %5d, type %d, name %s, %d bytes A reply\n", + format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); } if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { warn("a reply send error"); diff --git a/src/server.h b/src/server.h index c50c131..7969576 100644 --- a/src/server.h +++ b/src/server.h @@ -47,6 +47,15 @@ #define QMEMDATA_LEN 15 /* Max advisable: 36/2 = 18. Total mem usage: QMEMDATA_LEN * USERS * 6 bytes */ +/* Number of fragments in outgoing buffer. + * Mem usage: USERS * (MAX_FRAGLEN * OUTFRAGBUF_LEN + sizeof(struct window_buffer) */ +#define OUTFRAGBUF_LEN 64 + +/* Number of fragments in incoming buffer + * Minimum recommended = ((max packet size or MTU) / (max up fragsize)) * 2 + * ie. (1200 / 100) * 2 = 24 */ +#define INFRAGBUF_LEN 32 + #define PASSWORD_ENV_VAR "IODINED_PASS" #if defined IP_RECVDSTADDR @@ -102,7 +111,7 @@ void server_stop(); int server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time); int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); -void write_dns(int fd, struct query *q, char *data, int datalen, char downenc); +void write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc); void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len); void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len); void handle_ns_request(int dns_fd, struct query *q); From c83a52c7717b8b80fa27f03569d4b4979aee0625 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 20:10:25 +0800 Subject: [PATCH 020/113] Now starts when no non-loopback interfaces configured --- src/iodined.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/iodined.c b/src/iodined.c index 5e7edca..65f26e6 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -283,6 +283,8 @@ main(int argc, char **argv) bind_fd = 0; mtu = 1130; /* Very many relays give fragsize 1150 or slightly higher for NULL; tun/zlib adds ~17 bytes. */ + dns4addr_len = 0; + dns6addr_len = 0; listen_ip4 = NULL; listen_ip6 = NULL; port = 53; @@ -447,17 +449,20 @@ main(int argc, char **argv) foreground = 1; } - dns4addr_len = get_addr(listen_ip4, port, AF_INET, AI_PASSIVE | AI_NUMERICHOST, &dns4addr); - if (dns4addr_len < 0) { - warnx("Bad IPv4 address to listen on."); - usage(); + if (addrfamily == AF_UNSPEC || addrfamily == AF_INET) { + dns4addr_len = get_addr(listen_ip4, port, AF_INET, AI_PASSIVE | AI_NUMERICHOST, &dns4addr); + if (dns4addr_len < 0) { + warnx("Bad IPv4 address to listen on."); + usage(); + } } - dns6addr_len = get_addr(listen_ip6, port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &dns6addr); - if (dns6addr_len < 0) { - warnx("Bad IPv6 address to listen on."); - usage(); + if (addrfamily == AF_UNSPEC || addrfamily == AF_INET6) { + dns6addr_len = get_addr(listen_ip6, port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &dns6addr); + if (dns6addr_len < 0) { + warnx("Bad IPv6 address to listen on."); + usage(); + } } - if(bind_enable) { in_addr_t dns_ip = ((struct sockaddr_in *) &dns4addr)->sin_addr.s_addr; if (bind_port < 1 || bind_port > 65535) { From 9f6033c4b85ebe14fd4394444a99b1eb97bff629 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 20:10:53 +0800 Subject: [PATCH 021/113] Added client debug output option --- src/client.h | 4 +++- src/iodine.c | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/client.h b/src/client.h index 3fa8df5..bdd0285 100644 --- a/src/client.h +++ b/src/client.h @@ -18,6 +18,8 @@ #ifndef __CLIENT_H__ #define __CLIENT_H__ +extern int debug; + void client_init(); void client_stop(); @@ -33,7 +35,7 @@ char *client_get_qtype(); void client_set_downenc(char *encoding); void client_set_selecttimeout(int select_timeout); void client_set_lazymode(int lazy_mode); -void client_set_hostname_maxlen(int i); +void client_set_hostname_maxlen(size_t i); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); diff --git a/src/iodine.c b/src/iodine.c index dbbd942..27babd6 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -69,7 +69,7 @@ static void usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-D] [-r] [-u user] [-t chrootdir] [-d device] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " "[-z context] [-F pidfile] topdomain [nameserver ...]\n", __progname); exit(2); @@ -97,6 +97,7 @@ help() { fprintf(stderr, " -v to print version info and exit\n"); fprintf(stderr, " -h to print this help and exit\n"); fprintf(stderr, " -f to keep running in foreground\n"); + fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); fprintf(stderr, " -t dir to chroot to directory dir\n"); fprintf(stderr, " -d device to set tunnel device name\n"); @@ -169,6 +170,7 @@ main(int argc, char **argv) context = NULL; device = NULL; pidfile = NULL; + debug = 0; autodetect_frag_size = 1; max_downstream_frag_size = 3072; @@ -194,7 +196,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfhru:t:d:R:P:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhru:t:d:R:P:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -209,6 +211,9 @@ main(int argc, char **argv) case 'f': foreground = 1; break; + case 'D': + debug++; + break; case 'h': help(); /* NOTREACHED */ @@ -286,6 +291,12 @@ main(int argc, char **argv) argc -= optind; argv += optind; + if (debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", debug); + fprintf(stderr, "Add more -D switches to set higher debug level.\n"); + foreground = 1; + } + nameserv_hosts_len = argc - 1; if (nameserv_hosts_len <= 0) nameserv_hosts_len = 1; From 0449c465e74264ae602890c802eb7b6e621b4d21 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 29 Aug 2015 20:11:21 +0800 Subject: [PATCH 022/113] Re-implemented working DNS tunnelling (with windows!) --- src/client.c | 376 ++++++++++++++++++++++++++------------------------- src/common.h | 2 + 2 files changed, 192 insertions(+), 186 deletions(-) diff --git a/src/client.c b/src/client.c index ea804dd..c87bb7d 100644 --- a/src/client.c +++ b/src/client.c @@ -61,6 +61,8 @@ static void handshake_lazyoff(int dns_fd); +int debug; + static int running; static const char *password; @@ -238,7 +240,7 @@ client_set_hostname_maxlen(size_t i) { if (i <= 0xFF && i != hostname_maxlen) { hostname_maxlen = i; - outbuf->maxfraglen = get_raw_length(hostname_maxlen, dataenc, topdomain); + outbuf->maxfraglen = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); } } @@ -257,12 +259,15 @@ client_rotate_nameserver() } static void -send_query(int fd, char *hostname) +send_query(int fd, uint8_t *hostname) { - char packet[4096]; + uint8_t packet[4096]; struct query q; size_t len; + if (debug >= 2) + fprintf(stderr, "TX: pkt len %lu: hostname '%s'\n", strlen((char *)hostname), hostname); + chunkid += 7727; if (chunkid == 0) /* 0 is used as "no-query" in iodined.c */ @@ -271,15 +276,14 @@ send_query(int fd, char *hostname) q.id = chunkid; q.type = do_qtype; - len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, hostname, strlen(hostname)); + len = dns_encode((char *)packet, sizeof(packet), &q, QR_QUERY, (char *)hostname, strlen((char *)hostname)); if (len < 1) { warnx("dns_encode doesn't fit"); return; } -#if 0 - fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]); -#endif + if (debug >= 3) + fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]); sendto(fd, packet, len, 0, (struct sockaddr*) &nameserv_addrs[current_nameserver], sizeof(struct sockaddr_storage)); @@ -347,14 +351,15 @@ send_raw_data(int dns_fd) // TODO: fix send_raw static void -send_packet(int fd, char cmd, const char *data, const size_t datalen) +send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) /* Base32 encodes data and sends as single DNS query */ { - char buf[4096]; + uint8_t buf[4096]; buf[0] = cmd; build_hostname(buf, sizeof(buf), data, datalen, topdomain, b32, hostname_maxlen, 1); + send_query(fd, buf); } @@ -364,21 +369,28 @@ is_sending() return (outbuf->numitems > 0); } +static inline int +is_recving() +{ + return inbuf->numitems > 0; +} + static void -send_ping(int fd, int ping_response) // TODO: setup window sync stuff in ping +send_ping(int fd, int ping_response, int ack) // TODO: setup window sync stuff in ping { if (conn == CONN_DNS_NULL) { - char data[4]; + uint8_t data[9]; data[0] = userid; - data[1] = inbuf->start_seq_id & 0xff; - data[2] = outbuf->start_seq_id & 0xff; - data[3] = outbuf->windowsize & 0xff; - data[4] = inbuf->windowsize & 0xff; - data[5] = ping_response & 1; - data[6] = (rand_seed >> 8) & 0xff; - data[7] = (rand_seed >> 0) & 0xff; - rand_seed += 7; + data[1] = ack & 0xFF; + data[2] = outbuf->windowsize & 0xff; + data[3] = inbuf->windowsize & 0xff; + data[4] = inbuf->start_seq_id & 0xff; /* Downstream window start */ + data[5] = outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[6] = ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[7] = (rand_seed >> 8) & 0xff; + data[8] = (rand_seed >> 0) & 0xff; + rand_seed += 263; send_packet(fd, 'p', data, sizeof(data)); } else { @@ -390,49 +402,54 @@ static void send_next_frag(int fd) /* Sends next available fragment of data from the outgoing window buffer */ { - static uint8_t buf[MAX_FRAGSIZE]; + static uint8_t buf[MAX_FRAGSIZE], hdr[5]; int code; static int datacmc = 0; static char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; fragment *f; + size_t buflen, len; /* Get next fragment to send */ - f = window_get_next_sending_fragment(outbuf, next_downstream_ack); - if (f == NULL) { + f = window_get_next_sending_fragment(outbuf, &next_downstream_ack); + if (!f) { if (is_sending()) { /* 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(fd, 1); + send_ping(fd, 1, next_downstream_ack); + next_downstream_ack = -1; + window_tick(outbuf); } return; /* nothing to send - why was this called? */ } - /* Note: must be same, or smaller than send_fragsize_probe() */ - build_hostname((char *)buf, sizeof(buf), (char *)f->data, f->len, topdomain, - dataenc, hostname_maxlen, 6); - /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ buf[0] = userid_char; /* First byte is hex userid */ buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ - code = (f->seqID & 0xF8) >> 3; - buf[2] = b32_5to8(code); /* Second byte is 5 bits seqno */ + /* Next 3 bytes is seq ID, downstream ACK and flags */ + code = ((f->ack_other < 0 ? 0 : 1) << 3) | (f->is_nack << 2) + | (f->start << 1) | f->end; - code = ((f->seqID & 7) << 2) | ((f->ack_other & 0xC0) >> 6); - buf[3] = b32_5to8(code); /* Third byte is 3 bits seqno, 2 bits downstream ACK */ + hdr[0] = f->seqID & 0xFF; + hdr[1] = f->ack_other & 0xFF; + hdr[2] = code << 4; /* Flags are in upper 4 bits - lower 4 unused */ - code = ((f->ack_other & 0x3E) >> 1); - buf[4] = b32_5to8(code); /* Fourth byte is 5 bits downstream ACK */ + buflen = sizeof(buf) - 1; + /* Encode 3 bytes data into 2 bytes after buf */ + len = b32->encode(buf + 2, &buflen, hdr, 3); + if (len != 5) + warnx("mismatch in encoded upstream header length! expected 5, got %lu", len); - code = (f->ack_other & 1) << 4 | ((f->ack_other < 0 ? 0 : 1) << 3) | (f->is_nack << 2) | (f->start << 1) | f->end; - buf[5] = b32_5to8(code); /* Fifth byte is 1 bit downstream ACK, bit flags isACK, isNACK, first, last */ + /* Encode data into buf after header (6 = user + CMC + 4 bytes header) */ + build_hostname(buf, sizeof(buf), f->data, f->len, topdomain, + dataenc, hostname_maxlen, 6); datacmc++; if (datacmc >= 36) datacmc = 0; - send_query(fd, (char *)buf); + send_query(fd, buf); window_tick(outbuf); } @@ -477,8 +494,8 @@ write_dns_error(struct query *q, int ignore_some_errors) } } -static int -dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) +static size_t +dns_namedec(uint8_t *outdata, size_t outdatalen, uint8_t *buf, size_t buflen) /* Decodes *buf to *outdata. * *buf WILL be changed by undotify. * Note: buflen must be _exactly_ strlen(buf) before undotifying. @@ -496,8 +513,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b32); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b32); case 'i': /* Hostname++ with base64 */ case 'I': @@ -506,8 +522,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b64); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b64); case 'j': /* Hostname++ with base64u */ case 'J': @@ -516,8 +531,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b64u); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b64u); case 'k': /* Hostname++ with base128 */ case 'K': @@ -526,8 +540,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) return 0; /* this also does undotify */ - return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, - b128); + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, b128); case 't': /* plain base32(Thirty-two) from TXT */ case 'T': @@ -575,7 +588,7 @@ dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) } static int -read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) +read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query *q) /* Returns -1 on receive error or decode error, including DNS error replies. Returns 0 on replies that could be correct but are useless, and are not DNS error replies. @@ -583,7 +596,7 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) */ { struct sockaddr_storage from; - char data[64*1024]; + uint8_t data[64*1024]; socklen_t addrlen; int r; @@ -600,7 +613,7 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) /* useless packet */ return 0; - rv = dns_decode(buf, buflen, q, QR_ANSWER, data, r); + rv = dns_decode((char *)buf, buflen, q, QR_ANSWER, (char *)data, r); if (rv <= 0) return rv; @@ -630,7 +643,7 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) int thispartlen, dataspace, datanew; while (1) { - thispartlen = strlen(buf); + thispartlen = strlen((char *)buf); thispartlen = MIN(thispartlen, buftotal-bufoffset); dataspace = sizeof(data) - dataoffset; if (thispartlen <= 0 || dataspace <= 0) @@ -681,7 +694,7 @@ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) } static int -handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeout) +handshake_waitdns(int dns_fd, char *buf, size_t buflen, char c1, char c2, int timeout) /* Wait for DNS reply fitting to our latest query and returns it. Returns length of reply = #bytes used in buf. Returns 0 if fitting reply happens to be useless. @@ -713,12 +726,11 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo q.id = 0; q.name[0] = '\0'; - rv = read_dns_withq(dns_fd, 0, buf, buflen, &q); + rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q); if (q.id != chunkid || (q.name[0] != c1 && q.name[0] != c2)) { -#if 0 - fprintf(stderr, "Ignoring unfitting reply id %d starting with '%c'\n", q.id, q.name[0]); -#endif + if (debug >= 1) + fprintf(stderr, "Ignoring unfitting reply id %d starting with '%c'\n", q.id, q.name[0]); continue; } @@ -766,34 +778,37 @@ handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeo static int parse_data(uint8_t *data, size_t len, fragment *f) { + size_t headerlen = DOWNSTREAM_HDR; memset(f, 0, sizeof(fragment)); - int ping = (data[3] >> 5) & 1; - if (!ping) { - f->seqID = data[0]; - f->start = data[3] & 1; - f->end = (data[3] >> 1) & 1; - f->is_nack = (data[3] >> 2) & 1; - f->ack_other = (data[3] >> 3) & 1 ? data[1] : -1; - f->compressed = (data[3] >> 4) & 1; - f->len = len - 3; - memcpy(f->data, data + 3, MIN(f->len, sizeof(f->data))); - } else { /* Handle ping stuff */ - if (len != 5) return 1; /* invalid packet - continue */ -// static unsigned in_start_seq, out_start_seq, in_wsize, out_wsize; -// out_start_seq = data[0]; -// in_start_seq = data[1]; -// in_wsize = data[3]; -// out_wsize = data[4]; - warnx("Pingy thingy received."); - // TODO: handle pings + int ping = (data[2] >> 5) & 1; + f->compressed = (data[2] >> 4) & 1; + f->ack_other = (data[2] >> 3) & 1 ? data[1] : -1; + f->is_nack = (data[2] >> 2) & 1; + f->start = (data[2] >> 1) & 1; + f->end = data[2] & 1; + f->seqID = data[0]; + if (ping) { /* Handle ping stuff */ + headerlen = DOWNSTREAM_PING_HDR; + if (len < headerlen) return -1; /* invalid packet - continue */ + /* Parse data/ping header */ + static unsigned dn_start_seq, up_start_seq, dn_wsize, up_wsize; + dn_wsize = data[3]; /* TODO: do something with wsize/start params in ping */ + up_wsize = data[4]; + dn_start_seq = data[5]; + up_start_seq = data[6]; + if (debug >= 3) + fprintf(stderr, "PING pkt data=%lu WS: up=%u, dn=%u; Start: up=%u, dn=%u\n", + len - headerlen, up_wsize, dn_wsize, up_start_seq, dn_start_seq); } + 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) */ } static int tunnel_tun(int tun_fd, int dns_fd) { - size_t outlen, inlen; + size_t outlen; uint8_t out[64*1024]; uint8_t in[64*1024]; ssize_t read; @@ -801,20 +816,26 @@ tunnel_tun(int tun_fd, int dns_fd) if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) return -1; - /* Give outgoing buffer all new data if it can hold it */ - if (window_buffer_available(outbuf) > read / MAX_FRAGSIZE) + /* Check if outgoing buffer can hold data */ + if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { + if (debug >= 3) + fprintf(stderr, " Outgoing buffer full (%lu/%lu), not adding data!\n", outbuf->numitems, outbuf->length); return -1; + } + + if (debug >= 2) + fprintf(stderr, " IN: %lu bytes on tunnel\n", read); outlen = sizeof(out); - inlen = read; - compress2(out, &outlen, in, inlen, 9); + compress2(out, &outlen, in, read, 9); window_add_outgoing_data(outbuf, out, outlen, 1); if (conn == CONN_DNS_NULL) { send_next_frag(dns_fd); - send_ping_soon = 0; + // TODO: adjust min send interval based on DNS server droppiness + send_ping_soon = is_sending() ? 2 : 0; } else { send_raw_data(dns_fd); } @@ -829,7 +850,7 @@ tunnel_dns(int tun_fd, int dns_fd) static long packrecv_oos = 0; static long packrecv_servfail = 0; static struct query q; - size_t datalen; + size_t datalen, buflen; static uint8_t buf[64*1024], cbuf[64*1024]; static fragment f; int read, compressed, res; @@ -838,15 +859,13 @@ tunnel_dns(int tun_fd, int dns_fd) memset(&q, 0, sizeof(q)); memset(buf, 0, sizeof(buf)); memset(cbuf, 0, sizeof(cbuf)); - read = read_dns_withq(dns_fd, tun_fd, (char *)cbuf, sizeof(cbuf), &q); + read = read_dns_withq(dns_fd, tun_fd, cbuf, sizeof(cbuf), &q); if (conn != CONN_DNS_NULL) return 1; /* everything already done */ -#if 0 - fprintf(stderr, " Recv: id %5d name[0]='%c'\n", - q.id, q.name[0]); -#endif + if (debug >= 2) + fprintf(stderr, "RX: id %5d name[0]='%c'\n", q.id, q.name[0]); /* Don't process anything that isn't data for us; usually error replies from fragsize probes etc. However a sequence of those, @@ -859,7 +878,7 @@ tunnel_dns(int tun_fd, int dns_fd) return -1; /* nothing done */ } - if (read < 3) { + if (read < DOWNSTREAM_HDR) { /* Maybe SERVFAIL etc. Send ping to get things back in order, but wait a bit to prevent fast ping-pong loops. */ @@ -883,14 +902,6 @@ tunnel_dns(int tun_fd, int dns_fd) } } - /* read==1 happens with "QMEM" illegal replies, caused by - heavy reordering, or after short disconnections when - data-CMC has looped around into the "duplicate" values. - All these cases are helped by faster pinging. */ -#if 0 - if (read == 1) - fprintf(stderr, " q=%c id %5d 1-byte illegal \"QMEM\" reply\n", q.name[0], q.id); -#endif send_ping_soon = 900; return -1; /* nothing done */ } @@ -905,13 +916,17 @@ tunnel_dns(int tun_fd, int dns_fd) send_ping_soon = 0; } + /* Okay, we have a recent downstream packet */ + lastdownstreamtime = time(NULL); + if (!(packrecv & 0x1000000)) + packrecv++; + send_query_recvcnt++; /* overflow doesn't matter */ + /* Decode the downstream data header and fragment-ify ready for processing */ res = parse_data(cbuf, read, &f); - - /* if this response was a reverse ping/response to a ping, we need to do something */ - if (res) { - goto skip_recv; - } + if ((debug >= 3 && res) || (debug >= 2 && !res)) + fprintf(stderr, " RX %s frag id %u, ACK %d, nack %d, datalen %lu, s%d e%d\n", + res ? "PING" : "DATA", f.seqID, f.ack_other, f.is_nack, f.len, f.start, f.end); window_ack(outbuf, f.ack_other); @@ -919,19 +934,16 @@ tunnel_dns(int tun_fd, int dns_fd) 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 && debug >= 1) + fprintf(stderr, "Received downstream data with 0 length and NOT a ping!"); send_ping_soon = 700; return -1; } - /* Get next ACK if nothing already pending */ - if (next_downstream_ack < 0) { - if ((next_downstream_ack = window_get_next_ack(inbuf)) < 0) { - next_downstream_ack = -1; - } else { - send_something_now = 1; - } - } + /* Send something quickly to see if any more data needs to come through */ + send_ping_soon = 5; /* Downstream data traffic */ if (!window_process_incoming_fragment(inbuf, &f)) { @@ -940,40 +952,38 @@ tunnel_dns(int tun_fd, int dns_fd) return -1; /* nothing done */ } - // TODO: check OOS packet count and send ping to update params + /* Get next ACK if nothing already pending: only do this if we are sending */ + if (next_downstream_ack < 0) { + if ((next_downstream_ack = window_get_next_ack(inbuf)) >= 0) { + send_something_now = 1; + } + } - /* Okay, we have a recent downstream packet */ - lastdownstreamtime = time(NULL); - if (!(packrecv & 0x1000000)) - packrecv++; - send_query_recvcnt++; /* overflow doesn't matter */ + // TODO: check OOS packet count and send ping to update params datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed); if (datalen != 0) { if (compressed) { - if ((res = uncompress(buf, &datalen, cbuf, datalen)) == Z_OK) { - write_tun(tun_fd, buf, datalen); - } else { - warnx("Uncompress failed (%d): reassembled data corrupted or incomplete!", res); + buflen = sizeof(buf); + if ((res = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { + if (debug >= 1) + warnx("Uncompress failed (%d): reassembled data corrupted or incomplete!", res); } - send_ping_soon = 5; + datalen = buflen; } + write_tun(tun_fd, buf, datalen); + /* ACK frag quickly */ + send_ping_soon = 2; } else { - /* Send anything to ack the received seqno/frag, and get more */ - /* was last frag; wait just a trifle because our - tun will probably return TCP-ack immediately. - 5msec = 200 DNSreq/sec */ - /* server certainly has more data */ + /* ACK received frag and let server send any more data */ send_something_now = 1; } - skip_recv: /* Move window along after doing all data processing */ window_tick(inbuf); - /* NOTE: buf[] was overwritten when down-packet complete */ /* Upstream data traffic */ - if (is_sending()) { + if (is_sending() || is_recving()) { /* More to send - next fragment*/ send_next_frag(dns_fd); send_ping_soon = 2; @@ -982,7 +992,8 @@ tunnel_dns(int tun_fd, int dns_fd) /* Send ping if we didn't send anything yet */ if (send_something_now) { - send_ping(dns_fd, 0); + send_ping(dns_fd, 0, next_downstream_ack); + next_downstream_ack = -1; send_ping_soon = 0; } @@ -1006,9 +1017,15 @@ client_tunnel(int tun_fd, int dns_fd) tv.tv_usec = 0; if (is_sending()) { - /* fast timeout for retransmits */ - tv.tv_sec = 1; - tv.tv_usec = 0; + tv.tv_sec = 0; + tv.tv_usec = 2000; + send_ping_soon = 0; + } + + if (is_recving()) { + /* get next thingy soon ish */ + tv.tv_sec = 0; + tv.tv_usec = 5000; } if (send_ping_soon) { @@ -1017,10 +1034,9 @@ client_tunnel(int tun_fd, int dns_fd) } FD_ZERO(&fds); - if (window_buffer_available(outbuf) > 5 || 1) { + if (window_buffer_available(outbuf) > 16) { /* Fill up outgoing buffer with available data - * The windowing protocol manages data retransmits, timeouts etc. - * TODO: is this even necessary? tunnel_tun seems to be more useful */ + * The windowing protocol manages data retransmits, timeouts etc. */ FD_SET(tun_fd, &fds); } FD_SET(dns_fd, &fds); @@ -1045,10 +1061,10 @@ client_tunnel(int tun_fd, int dns_fd) send_next_frag(dns_fd); } else { outbuf->resends = 0; - send_ping(dns_fd, 1); + send_ping(dns_fd, 1, -1); } } else { - send_ping(dns_fd, 0); + send_ping(dns_fd, 0, -1); } send_ping_soon = 0; @@ -1074,7 +1090,7 @@ client_tunnel(int tun_fd, int dns_fd) static void send_login(int fd, char *login, int len) { - char data[19]; + uint8_t data[19]; memset(data, 0, sizeof(data)); data[0] = userid; @@ -1091,8 +1107,8 @@ send_login(int fd, char *login, int len) static void send_fragsize_probe(int fd, int fragsize) { - char probedata[256]; - char buf[4096]; + uint8_t probedata[256]; + uint8_t buf[MAX_FRAGSIZE]; /* * build a large query domain which is random and maximum size, @@ -1120,7 +1136,7 @@ send_fragsize_probe(int fd, int fragsize) static void send_set_downstream_fragsize(int fd, int fragsize) { - char data[5]; + uint8_t data[5]; data[0] = userid; data[1] = (fragsize & 0xff00) >> 8; @@ -1136,7 +1152,7 @@ send_set_downstream_fragsize(int fd, int fragsize) static void send_version(int fd, uint32_t version) { - char data[6]; + uint8_t data[6]; data[0] = (version >> 24) & 0xff; data[1] = (version >> 16) & 0xff; @@ -1154,7 +1170,7 @@ send_version(int fd, uint32_t version) static void send_ip_request(int fd, int userid) { - char buf[512] = "i____."; + uint8_t buf[512] = "i____."; buf[1] = b32_5to8(userid); buf[2] = b32_5to8((rand_seed >> 10) & 0x1f); @@ -1162,7 +1178,7 @@ send_ip_request(int fd, int userid) buf[4] = b32_5to8((rand_seed ) & 0x1f); rand_seed++; - strncat(buf, topdomain, 512 - strlen(buf)); + strncat((char *)buf, topdomain, 512 - strlen((char *)buf)); send_query(fd, buf); } @@ -1189,7 +1205,7 @@ send_upenctest(int fd, char *s) strncat(buf, s, 512); strncat(buf, ".", 512); strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static void @@ -1207,7 +1223,7 @@ send_downenctest(int fd, char downenc, int variant, char *s, int slen) rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static void @@ -1223,7 +1239,7 @@ send_codec_switch(int fd, int userid, int bits) rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } @@ -1240,7 +1256,7 @@ send_downenc_switch(int fd, int userid) rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static void @@ -1260,7 +1276,7 @@ send_lazy_switch(int fd, int userid) rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, buf); + send_query(fd, (uint8_t *)buf); } static int @@ -1285,7 +1301,7 @@ handshake_version(int dns_fd, int *seed) ((in[6] & 0xff) << 8) | ((in[7] & 0xff))); - if (strncmp("VACK", in, 4) == 0) { + if (strncmp("VACK", (char *)in, 4) == 0) { *seed = payload; userid = in[8]; userid_char = hex[userid & 15]; @@ -1294,11 +1310,11 @@ handshake_version(int dns_fd, int *seed) fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", PROTOCOL_VERSION, userid); return 0; - } else if (strncmp("VNAK", in, 4) == 0) { + } else if (strncmp("VNAK", (char *)in, 4) == 0) { warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up", PROTOCOL_VERSION, payload); return 1; - } else if (strncmp("VFUL", in, 4) == 0) { + } else if (strncmp("VFUL", (char *)in, 4) == 0) { warnx("Server full, all %d slots are taken. Try again later", payload); return 1; } @@ -1483,13 +1499,6 @@ handshake_upenctest(int dns_fd, char *s) if (read > 0) { int k; -#if 0 - /* in[56] = '@'; */ - /* in[56] = '_'; */ - /* if (in[29] == '\344') in[29] = 'a'; */ - in[read] = '\0'; - fprintf(stderr, "BounceReply: >%s<\n", in); -#endif /* quick check if case swapped, to give informative error msg */ if (in[4] == 'A') { fprintf(stderr, "DNS queries get changed to uppercase, keeping upstream codec Base32\n"); @@ -1808,10 +1817,8 @@ handshake_qtype_autodetect(int dns_fd) if (handshake_qtypetest(dns_fd, timeout)) { /* okay */ highestworking = qtypenum; -#if 0 - fprintf(stderr, " Type %s timeout %d works\n", - client_get_qtype(), timeout); -#endif + if (debug >= 1) + fprintf(stderr, " Type %s timeout %d works\n", client_get_qtype(), timeout); break; /* try others with longer timeout */ } @@ -1922,13 +1929,13 @@ handshake_switch_codec(int dns_fd, int bits) if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length. "); + fprintf(stderr, "Server got bad message length.\n"); goto codec_revert; } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address. "); + fprintf(stderr, "Server rejected sender IP address.\n"); goto codec_revert; } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected the selected codec. "); + fprintf(stderr, "Server rejected the selected codec.\n"); goto codec_revert; } in[read] = 0; /* zero terminate */ @@ -1936,7 +1943,7 @@ handshake_switch_codec(int dns_fd, int bits) dataenc = tempenc; /* Update outgoing buffer max (decoded) fragsize */ - outbuf->maxfraglen = get_raw_length(hostname_maxlen, dataenc, topdomain); + outbuf->maxfraglen = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); return; } @@ -1945,7 +1952,7 @@ handshake_switch_codec(int dns_fd, int bits) if (!running) return; - fprintf(stderr, "No reply from server on codec switch. "); + fprintf(stderr, "No reply from server on codec switch.\n"); codec_revert: fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name); @@ -1978,13 +1985,13 @@ handshake_switch_downenc(int dns_fd) if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length. "); + fprintf(stderr, "Server got bad message length.\n"); goto codec_revert; } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address. "); + fprintf(stderr, "Server rejected sender IP address.\n"); goto codec_revert; } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected the selected codec. "); + fprintf(stderr, "Server rejected the selected codec.\n"); goto codec_revert; } in[read] = 0; /* zero terminate */ @@ -1997,7 +2004,7 @@ handshake_switch_downenc(int dns_fd) if (!running) return; - fprintf(stderr, "No reply from server on codec switch. "); + fprintf(stderr, "No reply from server on codec switch.\n"); codec_revert: fprintf(stderr, "Falling back to downstream codec Base32\n"); @@ -2019,13 +2026,13 @@ handshake_try_lazy(int dns_fd) if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length. "); + fprintf(stderr, "Server got bad message length.\n"); goto codec_revert; } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address. "); + fprintf(stderr, "Server rejected sender IP address.\n"); goto codec_revert; } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected lazy mode. "); + fprintf(stderr, "Server rejected lazy mode.\n"); goto codec_revert; } else if (strncmp("Lazy", in, 4) == 0) { fprintf(stderr, "Server switched to lazy mode\n"); @@ -2039,7 +2046,7 @@ handshake_try_lazy(int dns_fd) if (!running) return; - fprintf(stderr, "No reply from server on lazy switch. "); + fprintf(stderr, "No reply from server on lazy switch.\n"); codec_revert: fprintf(stderr, "Falling back to legacy mode\n"); @@ -2062,7 +2069,7 @@ handshake_lazyoff(int dns_fd) read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', 1); if (read == 9 && strncmp("Immediate", in, 9) == 0) { - warnx("Server switched back to legacy mode.\n"); + warnx("Server switched back to legacy mode."); lazymode = 0; selecttimeout = 1; return; @@ -2071,7 +2078,7 @@ handshake_lazyoff(int dns_fd) if (!running) return; - warnx("No reply from server on legacy mode switch.\n"); + warnx("No reply from server on legacy mode switch."); } static int @@ -2111,8 +2118,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) /* in[123] = 123; */ if ((in[2] & 0xff) != 107) { - fprintf(stderr, "\n"); - warnx("corruption at byte 2, this won't work. Try -O Base32, or other -T options."); + warnx("\ncorruption at byte 2, this won't work. Try -O Base32, or other -T options."); *max_fragsize = -1; return 1; } @@ -2150,7 +2156,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) static int handshake_autoprobe_fragsize(int dns_fd) { - char in[4096]; + char in[MAX_FRAGSIZE]; int i; int read; int proposed_fragsize = 768; @@ -2158,7 +2164,7 @@ handshake_autoprobe_fragsize(int dns_fd) int max_fragsize; max_fragsize = 0; - fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)\n"); + fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)"); while (running && range > 0 && (range >= 8 || max_fragsize < 300)) { /* stop the slow probing early when we have enough bytes anyway */ for (i=0; running && i<3 ;i++) { @@ -2191,16 +2197,14 @@ handshake_autoprobe_fragsize(int dns_fd) } } if (!running) { - fprintf(stderr, "\n"); - warnx("stopped while autodetecting fragment size (Try setting manually with -m)"); + warnx("\nstopped while autodetecting fragment size (Try setting manually with -m)"); return 0; } if (max_fragsize <= 6) { /* Tried all the way down to 2 and found no good size. But we _did_ do all handshake before this, so there must be some workable connection. */ - fprintf(stderr, "\n"); - warnx("found no accepted fragment size."); + warnx("\nfound no accepted fragment size."); warnx("try setting -M to 200 or lower, or try other -T or -O options."); return 0; } @@ -2238,7 +2242,7 @@ handshake_set_fragsize(int dns_fd, int fragsize) if (read > 0) { if (strncmp("BADFRAG", in, 7) == 0) { - fprintf(stderr, "Server rejected fragsize. Keeping default."); + fprintf(stderr, "Server rejected fragsize. Keeping default.\n"); return; } else if (strncmp("BADIP", in, 5) == 0) { fprintf(stderr, "Server rejected sender IP address.\n"); diff --git a/src/common.h b/src/common.h index 512f808..87cd8da 100644 --- a/src/common.h +++ b/src/common.h @@ -77,7 +77,9 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; /* Unused RR type, never actually sent */ #define DOWNSTREAM_HDR 3 +#define DOWNSTREAM_PING_HDR 7 #define UPSTREAM_HDR 6 +#define UPSTREAM_PING 6 struct query { char name[QUERY_NAME_SIZE]; From b7f0da1493aa1ef46033addc2c719bf61f5f0d7c Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Fri, 11 Sep 2015 17:33:04 +0200 Subject: [PATCH 023/113] Fixed C99 Compiler warning --- src/window.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/window.h b/src/window.h index 8fe9e08..84697cf 100644 --- a/src/window.h +++ b/src/window.h @@ -59,9 +59,11 @@ struct frag_buffer { }; #ifdef WINDOW_DEBUG -#define DEBUG(msg, ...) fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__) +#define DEBUG(...) fprintf(stderr, "[WINDOW-DEBUG] (%s:%d)", __FILE__, __LINE__);\ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n") #else -#define DEBUG(msg, ...) +#define DEBUG(...) #endif #define AFTER(w, o) ((w->window_start + o) % w->length) From c48822dfbe61fc07293ceb170226f8fed942cee8 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 12:24:15 +0800 Subject: [PATCH 024/113] Added debug / default build options (make debug) --- Makefile | 3 +++ src/Makefile | 13 +++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4be98d1..fa7dec2 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,9 @@ TARGETOS = `uname` all: @(cd src; $(MAKE) TARGETOS=$(TARGETOS) all) + +debug: + @(cd src; $(MAKE) TARGETOS=$(TARGETOS) debug) install: all $(MKDIR) $(MKDIR_FLAGS) $(DESTDIR)$(sbindir) diff --git a/src/Makefile b/src/Makefile index a9c81da..fc1f80f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -10,9 +10,18 @@ HEAD_COMMIT = `git rev-parse --short HEAD` LIBPATH = -L. LDFLAGS += -lz `sh osflags $(TARGETOS) link` $(LIBPATH) -CFLAGS += -std=c99 -c -g -Wall -D$(OS) -pedantic `sh osflags $(TARGETOS) cflags` -DGITREVISION=\"$(HEAD_COMMIT)\" +CFLAGS += -std=c99 -c -Wall -D$(OS) -pedantic `sh osflags $(TARGETOS) cflags` -DGITREVISION=\"$(HEAD_COMMIT)\" -all: stateos $(CLIENT) $(SERVER) +CFLAGS_RELEASE = -O3 -fno-strict-aliasing +CFLAGS_DEBUG = -g -Og -DDEBUG_BUILD + +all: CFLAGS += $(CFLAGS_RELEASE) +all: executables + +debug: CFLAGS += $(CFLAGS_DEBUG) +debug: executables + +executables: stateos $(CLIENT) $(SERVER) stateos: @echo OS is $(OS), arch is $(ARCH) From 33525e5086a66145ddbd956476a8d84e6b1fe762 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 12:26:41 +0800 Subject: [PATCH 025/113] Added windows gettimeofday function and timeval macros --- src/windows.h | 83 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/src/windows.h b/src/windows.h index 96288d1..e3295f1 100644 --- a/src/windows.h +++ b/src/windows.h @@ -25,6 +25,7 @@ typedef unsigned int in_addr_t; #include #include #include +#include /* Missing from the mingw headers */ #ifndef DNS_TYPE_SRV @@ -74,7 +75,7 @@ typedef struct { } HEADER; struct ip - { +{ unsigned int ip_hl:4; /* header length */ unsigned int ip_v:4; /* version */ u_char ip_tos; /* type of service */ @@ -89,7 +90,85 @@ struct ip u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ - }; +}; + +/* windows gettimeofday from https://gist.github.com/ugovaretto/5875385 */ +#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) + #define DELTA_EPOCH_IN_MICROSECS 116444736000000000Ui64 +#else + #define DELTA_EPOCH_IN_MICROSECS 116444736000000000ULL +#endif + +/* Convenience macros for operations on timevals. + NOTE: `timercmp' does not work for >= or <=. */ +#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#define timercmp(a, b, CMP) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_usec CMP (b)->tv_usec) : \ + ((a)->tv_sec CMP (b)->tv_sec)) +#define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) \ + { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) + +struct timezone +{ + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; + +inline int +gettimeofday(struct timeval *tv, struct timezone *tz) +{ + FILETIME ft; + unsigned __int64 tmpres = 0; + static int tzflag = 0; + + if (NULL != tv) + { + GetSystemTimeAsFileTime(&ft); + + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + + /* convert into microseconds */ + tmpres /= 10; + /* converting file time to unix epoch */ + tmpres -= DELTA_EPOCH_IN_MICROSECS; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + + if (NULL != tz) + { + if (!tzflag) + { + _tzset(); + tzflag++; + } + tz->tz_minuteswest = _timezone / 60; + tz->tz_dsttime = _daylight; + } + + return 0; +} DWORD WINAPI tun_reader(LPVOID arg); struct tun_data { From bd9966836e1f83e4f86e1f8dad009c419a8e01ec Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 12:57:33 +0800 Subject: [PATCH 026/113] Added clear, reset and number of fragments to be sent; better debugging --- src/window.c | 105 +++++++++++++++++++++++++++++++++++++-------------- src/window.h | 17 +++++++-- 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/window.c b/src/window.c index 54cee54..09d47fe 100644 --- a/src/window.c +++ b/src/window.c @@ -25,6 +25,8 @@ #include "common.h" #include "window.h" +int window_debug = 0; + struct frag_buffer * window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir) { @@ -53,6 +55,20 @@ window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int di return buf; } +void +window_buffer_reset(struct frag_buffer *w) +{ + w->chunk_start = 0; + w->cur_seq_id = 0; + w->last_write = 0; + w->numitems = 0; + w->oos = 0; + w->resends = 0; + w->start_seq_id = 0; + w->window_start = 0; + w->window_end = AFTER(w, w->windowsize); +} + void window_buffer_resize(struct frag_buffer *w, size_t length) { @@ -66,11 +82,7 @@ window_buffer_resize(struct frag_buffer *w, size_t length) errx(1, "Failed to resize window buffer!"); } w->length = length; - w->numitems = 0; - w->window_start = 0; - w->start_seq_id = 0; - w->cur_seq_id = 0; - w->window_end = AFTER(w, w->windowsize); + window_buffer_reset(w); } void @@ -81,6 +93,15 @@ window_buffer_destroy(struct frag_buffer *w) free(w); } +void +window_buffer_clear(struct frag_buffer *w) +{ + if (!w) return; + + memset(w->frags, 0, w->length * sizeof(fragment)); + window_buffer_reset(w); +} + /* Returns number of available fragment slots (NOT BYTES) */ size_t window_buffer_available(struct frag_buffer *w) @@ -99,7 +120,8 @@ window_append_fragment(struct frag_buffer *w, fragment *src) return 1; } -/* Handles fragment received from the sending side (RECV) */ +/* Handles fragment received from the sending side (RECV) + * Returns seq ID of packet to be ACKed immediately */ int window_process_incoming_fragment(struct frag_buffer *w, fragment *f) { @@ -109,8 +131,11 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) startid = w->start_seq_id; endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; if (!INWINDOW_SEQ(startid, endid, f->seqID)) { - DEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); - return 0; + DEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", + f->seqID, startid, endid); + w->oos++; + /* ACK duplicate so sender can move on ASAP */ + return f->seqID; } /* Place fragment into correct location in buffer */ size_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); @@ -119,14 +144,17 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) fd = &w->frags[dest]; if (fd->len != 0) { DEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); - return 0; + if (f->seqID == fd->seqID) + return f->seqID; } memcpy(fd, f, sizeof(fragment)); fd->retries = 0; fd->ack_other = -1; - fd->acks = 0; + /* We assume this packet gets ACKed immediately on return of this function */ + fd->acks = 1; w->numitems ++; - return 1; + + return f->seqID; } /* Reassembles first complete sequence of fragments into data. (RECV) @@ -139,7 +167,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int dest = data; if (w->direction != WINDOW_RECVING) return 0; - if (w->frags[w->chunk_start].start == 0) { + if (w->frags[w->chunk_start].start == 0 && w->numitems > 0) { DEBUG("chunk_start (%lu)pointing to non-start fragment (seq %u, len %lu)!", w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); return 0; @@ -191,29 +219,50 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (end == 0) { /* no end of chunk found but reached end of data */ return 0; } - DEBUG("Reassembling %lu bytes of data from %lu frags!", datalen, i + 1); + DEBUG("Reassembling %lu bytes of data from %lu frags; compression %d!", datalen, i + 1, *compression); /* Clear all used fragments */ size_t p; - ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, { - memset(&w->frags[p], 0, sizeof(fragment)); - }); + ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, + memset(&w->frags[p], 0, sizeof(fragment)); + ); w->chunk_start = WRAP(woffs + 1); w->numitems -= i + 1; return datalen; } +/* Returns number of fragments that can be sent immediately; effectively + * the same as window_get_next_sending_fragment but without changing anything. */ +int +window_sending(struct frag_buffer *w) +{ + fragment *f; + int tosend = 0; + if (w->numitems == 0) + return 0; + for (size_t i = 0; i < w->windowsize; i++) { + f = &w->frags[WRAP(w->window_start + i)]; + if (f->len == 0 || f->acks >= 1) continue; + if ((f->retries == 0) != (difftime(time(NULL), f->lastsent) > ACK_TIMEOUT)) { + /* Fragment not sent xor timed out (to be re-sent) */ + tosend++; + } + } + return tosend; +} + /* Returns next fragment to be sent or NULL if nothing (SEND) * This also handles packet resends, timeouts etc. */ fragment * window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) { - fragment *f; + fragment *f = NULL; if (*other_ack >= MAX_SEQ_ID || *other_ack < 0) *other_ack = -1; for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[WRAP(w->window_start + i)]; if (f->acks >= 1) continue; - if (f->retries >= 1 && difftime(f->lastsent, time(NULL)) > ACK_TIMEOUT) { + /* TODO: use timeval for more precise timeouts */ + if (f->retries >= 1 && difftime(time(NULL), f->lastsent) > ACK_TIMEOUT) { /* Fragment sent before, not ACK'd */ DEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); w->resends ++; @@ -224,16 +273,16 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) } } - DEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", - f->retries, f->seqID, f->len); - // TODO: statistics for packet loss/not sending etc + if (f) + DEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", + f->retries, f->seqID, f->len); return NULL; found: - /* store other ACK into fragment so ACK is resent if fragment times out */ - if (f->ack_other == -1) - f->ack_other = *other_ack, *other_ack = -1; - f->is_nack &= 1; + /* store other ACK into fragment for sending; ignore any previous values. + Don't resend ACKs because by the time we do, the other end will have + resent the corresponding fragment so may as well not cause trouble. */ + f->ack_other = *other_ack, *other_ack = -1; f->start &= 1; f->end &= 1; f->retries++; @@ -265,7 +314,8 @@ window_ack(struct frag_buffer *w, int seqid) for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[AFTER(w, i)]; if (f->seqID == seqid && f->len > 0) { /* ACK first non-empty frag */ - if (f->acks > 0) DEBUG("ACK: %d ACKs for seqId %u", f->acks, seqid); + if (f->acks > 0) + DEBUG("DUPE ACK: %d ACKs for seqId %u", f->acks, seqid); f->acks ++; DEBUG(" ACK frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); break; @@ -309,7 +359,7 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c compressed &= 1; size_t offset = 0; static fragment f; -// DEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); + DEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); for (size_t i = 0; i < n; i++) { memset(&f, 0, sizeof(f)); f.len = MIN(len - offset, w->maxfraglen); @@ -321,7 +371,6 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c f.ack_other = -1; window_append_fragment(w, &f); w->cur_seq_id = (w->cur_seq_id + 1) % MAX_SEQ_ID; -// DEBUG(" a = %u, b = %u, a %% b = %u", (len - offset), (w->maxfraglen + 1), (len - offset) % (w->maxfraglen + 1)); DEBUG(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); offset += f.len; } diff --git a/src/window.h b/src/window.h index 8fe9e08..999a6f5 100644 --- a/src/window.h +++ b/src/window.h @@ -32,7 +32,6 @@ typedef struct fragment { size_t len; /* Length of fragment data (0 if fragment unused) */ unsigned seqID; /* fragment sequence ID */ int ack_other; /* other way ACK seqID (>=0) or unset (<0) */ - int is_nack; /* 1 if other way ACK is a NACK */ int compressed; /* compression flag */ uint8_t start; /* start of chunk flag */ uint8_t end; /* end of chunk flag */ @@ -55,11 +54,14 @@ struct frag_buffer { unsigned cur_seq_id; /* Most recent sequence ID */ unsigned start_seq_id; /* Start of window sequence ID */ unsigned resends; /* number of fragments resent */ + unsigned oos; /* Number of out-of-sequence fragments received */ int direction; /* Sending or recving */ }; -#ifdef WINDOW_DEBUG -#define DEBUG(msg, ...) fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__) +extern int window_debug; + +#ifdef DEBUG_BUILD +#define DEBUG(msg, ...) if (window_debug) fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__) #else #define DEBUG(msg, ...) #endif @@ -94,6 +96,12 @@ struct frag_buffer *window_buffer_init(size_t length, unsigned windowsize, unsig void window_buffer_resize(struct frag_buffer *w, size_t length); void window_buffer_destroy(struct frag_buffer *w); +/* Clears fragments and resets window stats */ +void window_buffer_clear(struct frag_buffer *w); + +/* Resets window stats without clearing fragments */ +void window_buffer_reset(struct frag_buffer *w); + /* Returns number of available fragment slots (NOT BYTES) */ size_t window_buffer_available(struct frag_buffer *w); @@ -107,6 +115,9 @@ int window_process_incoming_fragment(struct frag_buffer *w, fragment *f); * Returns length of data reassembled, or 0 if no data reassembled */ size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression); +/* Returns number of fragments to be sent */ +int window_sending(struct frag_buffer *w); + /* Returns next fragment to be sent or NULL if nothing (SEND) */ fragment *window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack); From 555a3cbfe2c800528e4144791df0684fac477491 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 13:01:18 +0800 Subject: [PATCH 027/113] Modified qmem for lazy mode handling; fixed some user management issues --- src/user.c | 10 ++++++---- src/user.h | 12 +++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/user.c b/src/user.c index 5bbe006..7588de4 100644 --- a/src/user.c +++ b/src/user.c @@ -85,6 +85,9 @@ init_users(in_addr_t my_ip, int netbits) } users[i].tun_ip = ip; net.s_addr = ip; + + users[i].incoming = window_buffer_init(INFRAGBUF_LEN, 10, MAX_FRAGSIZE, WINDOW_RECVING); + users[i].outgoing = window_buffer_init(OUTFRAGBUF_LEN, 10, 100, WINDOW_SENDING); /* Rest is reset on login ('V' packet) or already 0 */ } @@ -119,7 +122,7 @@ user_sending(int user) int user_active(int i) { - return users[i].active && !users[i].disabled && users[i].last_pkt + 60 > time(NULL); + return users[i].active && !users[i].disabled && difftime(time(NULL), users[i].last_pkt) < 60; } int @@ -131,7 +134,8 @@ all_users_waiting_to_send() */ { for (int i = 0; i < usercount; i++) - if (!(user_active(i) && user_sending(i))) return 0; + if (user_active(i)) + if (!user_sending(i)) return 0; return 1; } @@ -142,8 +146,6 @@ find_available_user() /* Not used at all or not used in one minute */ if (!user_active(u)) { struct tun_user *user = &users[u]; - if (user->incoming) window_buffer_destroy(user->incoming); - if (user->outgoing) window_buffer_destroy(user->outgoing); /* reset all stats */ user->active = 1; user->authenticated = 0; diff --git a/src/user.h b/src/user.h index 8d5e1f4..1acf8a9 100644 --- a/src/user.h +++ b/src/user.h @@ -31,11 +31,11 @@ struct tun_user { int authenticated_raw; int disabled; time_t last_pkt; + struct timeval dns_timeout; int seed; in_addr_t tun_ip; struct sockaddr_storage host; socklen_t hostlen; - struct query q; /* TODO: multiple incoming query storage + handling */ struct frag_buffer *incoming; struct frag_buffer *outgoing; @@ -43,15 +43,13 @@ struct tun_user { struct encoder *encoder; char downenc; int downenc_bits; + int down_compression; int fragsize; enum connection conn; int lazy; - /*unsigned char qmemping_cmc[QMEMPING_LEN * 4]; - unsigned short qmemping_type[QMEMPING_LEN]; - int qmemping_lastfilled; - unsigned char qmemdata_cmc[QMEMDATA_LEN * 4]; - unsigned short qmemdata_type[QMEMDATA_LEN]; - int qmemdata_lastfilled;*/ +#ifdef QMEM_LEN + struct query_buffer qmem; +#endif #ifdef DNSCACHE_LEN struct query dnscache_q[DNSCACHE_LEN]; char dnscache_answer[DNSCACHE_LEN][4096]; From c903203a245cb847bb6b30b529b0d57a8fd4791e Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 13:02:34 +0800 Subject: [PATCH 028/113] Fixed issues with difftime and removed unused parts of query struct. --- src/common.h | 8 ++++---- src/dns.c | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/common.h b/src/common.h index 87cd8da..f3af88e 100644 --- a/src/common.h +++ b/src/common.h @@ -85,14 +85,12 @@ struct query { char name[QUERY_NAME_SIZE]; unsigned short type; unsigned short rcode; - unsigned short id; + int id; /* id < 0: unusued */ struct sockaddr_storage destination; socklen_t dest_len; struct sockaddr_storage from; socklen_t fromlen; - unsigned short id2; - struct sockaddr_storage from2; - socklen_t fromlen2; + struct timeval time_recv; }; enum connection { @@ -118,6 +116,8 @@ void read_password(char*, size_t); int check_topdomain(char *, char **); +extern double difftime(time_t, time_t); + #if defined(WINDOWS32) || defined(ANDROID) #ifndef ANDROID int inet_aton(const char *cp, struct in_addr *inp); diff --git a/src/dns.c b/src/dns.c index 6eecab2..b933a2d 100644 --- a/src/dns.c +++ b/src/dns.c @@ -411,7 +411,6 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz int id; int rv; - q->id2 = 0; rv = 0; header = (HEADER*)packet; From c7cff96055bd2716b885aa53d1217cf9892abd7b Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 13:07:00 +0800 Subject: [PATCH 029/113] More connection stats, timeout control + compression flags, client-side query tracking; lazy mode is now possible with windowing protocol. --- src/client.c | 709 +++++++++++++++++++++++++++++++++++---------------- src/client.h | 14 +- 2 files changed, 503 insertions(+), 220 deletions(-) diff --git a/src/client.c b/src/client.c index c87bb7d..02146cc 100644 --- a/src/client.c +++ b/src/client.c @@ -25,9 +25,9 @@ #include #include #include -#include #include #include +#include #include #ifdef WINDOWS32 @@ -59,8 +59,6 @@ #include "window.h" #include "client.h" -static void handshake_lazyoff(int dns_fd); - int debug; static int running; @@ -79,9 +77,31 @@ static uint16_t rand_seed; /* Current up/downstream window data */ static struct frag_buffer *outbuf; static struct frag_buffer *inbuf; +static size_t windowsize_up; +static size_t windowsize_down; +static size_t maxfragsize_up; + /* Next downstream seqID to be ACK'd (-1 if none pending) */ static int next_downstream_ack; +/* Remembering queries we sent for tracking purposes */ +static struct query_tuple *pending_queries; +static time_t max_timeout_ms; + +/* Server response timeout in ms */ +static time_t server_timeout_ms; +static int autodetect_server_timeout; + +/* Cumulative Round-Trip-Time in ms */ +static time_t rtt_total_ms; +static size_t num_immediate; + +/* Query statistics */ +static size_t num_timeouts; +static size_t num_untracked; +static size_t send_query_sendcnt = -1; +static size_t send_query_recvcnt = -1; + /* My userid at the server */ static char userid; static char userid_char; /* used when sending (lowercase) */ @@ -100,21 +120,22 @@ static struct encoder *b128; * Defaults to Base32, can be changed after handshake */ static struct encoder *dataenc; +/* Upstream/downstream compression flags */ +static int compression_up; +static int compression_down; + /* The encoder to use for downstream data */ static char downenc = ' '; /* set query type to send */ -static unsigned short do_qtype = T_UNSET; +static uint16_t do_qtype = T_UNSET; /* My connection mode */ static enum connection conn; -static int selecttimeout; /* RFC says timeout minimum 5sec */ static int lazymode; static long send_ping_soon; static time_t lastdownstreamtime; -static long send_query_sendcnt = -1; -static long send_query_recvcnt = 0; static size_t hostname_maxlen = 0xFF; void @@ -126,19 +147,29 @@ client_init() b64u = get_base64u_encoder(); b128 = get_base128_encoder(); dataenc = get_base32_encoder(); - rand_seed = ((unsigned int) rand()) & 0xFFFF; + rand_seed = (uint16_t) rand(); send_ping_soon = 1; /* send ping immediately after startup */ conn = CONN_DNS_NULL; - chunkid = ((unsigned int) rand()) & 0xFFFF; + chunkid = (uint16_t) rand(); - // TODO: user-set window size (command line option) - outbuf = window_buffer_init(100, 10, hostname_maxlen, WINDOW_SENDING); - /* Incoming buffer max fragsize doesn't matter */ - inbuf = window_buffer_init(128, 10, MAX_FRAGSIZE, WINDOW_RECVING); + /* RFC says timeout minimum 5sec */ + max_timeout_ms = 5000; + + windowsize_up = 8; + windowsize_down = 8; + + compression_up = 0; + compression_down = 1; next_downstream_ack = -1; current_nameserver = 0; + + maxfragsize_up = 100; + + outbuf = NULL; + inbuf = NULL; + pending_queries = NULL; } void @@ -224,9 +255,18 @@ client_set_downenc(char *encoding) } void -client_set_selecttimeout(int select_timeout) +client_set_compression(int up, int down) { - selecttimeout = select_timeout; + compression_up = up; + compression_down = down; +} + +void +client_set_dnstimeout(double timeout, double servertimeout, int autodetect) +{ + max_timeout_ms = timeout * 1000; + server_timeout_ms = servertimeout * 1000; + autodetect_server_timeout = autodetect; } void @@ -235,12 +275,23 @@ client_set_lazymode(int lazy_mode) lazymode = lazy_mode; } +void +client_set_windowsize(size_t up, size_t down) +/* set window sizes for upstream and downstream + * XXX upstream/downstream windowsizes might as well be the same */ +{ + windowsize_up = up; + windowsize_down = down; +} + void client_set_hostname_maxlen(size_t i) { if (i <= 0xFF && i != hostname_maxlen) { hostname_maxlen = i; - outbuf->maxfraglen = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); + maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); + if (outbuf) + outbuf->maxfraglen = maxfragsize_up; } } @@ -258,8 +309,111 @@ client_rotate_nameserver() current_nameserver = 0; } +/* Client-side query tracking for lazy mode */ +static int +num_pending() +{ + int num = 0; + struct timeval now, qtimeout, max_timeout; + gettimeofday(&now, NULL); + /* Max timeout for queries is max interval + 1 second extra */ + max_timeout.tv_sec = (max_timeout_ms / 1000) + 1; + max_timeout.tv_usec = (max_timeout_ms - max_timeout.tv_sec * 1000) * 1000; + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].time.tv_sec > 0) { + timeradd(&pending_queries[i].time, &max_timeout, &qtimeout); + if (timercmp(&qtimeout, &now, >)) { + num++; + } else { + /* Query has timed out, clear it */ + pending_queries[i].time.tv_sec = 0; + num_timeouts++; + } + } + } + return num; +} + static void +query_sent_now(int id) +{ + if (!pending_queries) + return; + + if (id < 0 || id > 65535) + return; + + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].time.tv_sec == 0) { + pending_queries[i].id = id; + gettimeofday(&pending_queries[i].time, NULL); + id = -1; + break; + } + } + if (id > 0 && debug >= 1) + warnx("Too many queries sent! Failed to add id %d.", id); +} + +static void +got_response(int id, int immediate) +{ + struct timeval now, rtt; + time_t rtt_ms; + static size_t num_rtt_timeouts; + gettimeofday(&now, NULL); + + if (debug >= 4) + warnx("got_response: request id %d (%s)", id, immediate ? "immediate" : "lazy"); + + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (id >= 0 && pending_queries[i].id == id) { + id = -1; + if (pending_queries[i].time.tv_sec == 0 && num_timeouts > 0) { + /* If query has timed out but is still stored */ + num_timeouts --; + immediate = 0; + } + if (immediate) { + /* If this was an immediate response we can use it to get + more detailed connection statistics like RTT. + This lets us determine and adjust server lazy response time + during the session much more accurately. */ + timersub(&now, &pending_queries[i].time, &rtt); + rtt_ms = rtt.tv_sec * 1000 + rtt.tv_usec / 1000; + rtt_total_ms += rtt_ms; + num_immediate++; + + if (autodetect_server_timeout) { + /* Get average RTT in ms */ + rtt_ms = rtt_total_ms / num_immediate; + if (rtt_ms >= max_timeout_ms) { + num_rtt_timeouts++; + if (num_rtt_timeouts < 3) { + fprintf(stderr, "Preferred interval of %lu ms less than average round-trip of " + "%lu ms! Try increasing interval with -I.\n", max_timeout_ms, rtt_ms); + } else { + server_timeout_ms = max_timeout_ms; + max_timeout_ms += rtt_ms; + if (lazymode) + fprintf(stderr, "Adjusting server timeout to %lu ms.\n", server_timeout_ms); + num_rtt_timeouts = 0; + } + } else { + server_timeout_ms = max_timeout_ms - rtt_ms; + } + } + } + break; + } + } + if (id > 0) + num_untracked++; +} + +static int send_query(int fd, uint8_t *hostname) +/* Returns DNS ID of sent query */ { uint8_t packet[4096]; struct query q; @@ -271,7 +425,7 @@ send_query(int fd, uint8_t *hostname) chunkid += 7727; if (chunkid == 0) /* 0 is used as "no-query" in iodined.c */ - chunkid = 7727; + chunkid = rand() & 0xFF; q.id = chunkid; q.type = do_qtype; @@ -279,7 +433,7 @@ send_query(int fd, uint8_t *hostname) len = dns_encode((char *)packet, sizeof(packet), &q, QR_QUERY, (char *)hostname, strlen((char *)hostname)); if (len < 1) { warnx("dns_encode doesn't fit"); - return; + return -1; } if (debug >= 3) @@ -297,33 +451,38 @@ send_query(int fd, uint8_t *hostname) lazy mode while legacy immediate-ping-pong works just fine. Here we detect and fix these situations. (Can't very well do this anywhere else; this is the only place - we'll reliably get to in such situations.) - */ + we'll reliably get to in such situations.) */ if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode) { send_query_sendcnt++; if ((send_query_sendcnt > 6 && send_query_recvcnt <= 0) || - (send_query_sendcnt > 10 && - 4 * send_query_recvcnt < send_query_sendcnt)) { - if (selecttimeout > 1) { - warnx("Receiving too few answers. Setting interval to 1 (-I1)"); - selecttimeout = 1; + (send_query_sendcnt > 10 && 4 * send_query_recvcnt < send_query_sendcnt)) { + if (server_timeout_ms > 500 && autodetect_server_timeout) { + server_timeout_ms -= 200; + double secs = (double) server_timeout_ms / 1000.0; + fprintf(stderr, "Receiving too few answers. Setting server timeout to %fs (-I%f)\n", secs, secs); /* restart counting */ send_query_sendcnt = 0; send_query_recvcnt = 0; - } else if (lazymode) { - warnx("Receiving too few answers. Will try to switch lazy mode off, but that may not always work any more. Start with -L0 next time on this network."); + rtt_total_ms = 1000; + num_immediate = 1; + /* TODO: reduce windowsize due to DNS server dropping queries */ + send_ping(fd, 1, -1); + } else if (lazymode && autodetect_server_timeout) { + fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" + " always work any more. Start with -L0 next time on this network."); lazymode = 0; - selecttimeout = 1; + server_timeout_ms = 0; handshake_lazyoff(fd); } } } + return q.id; } static void -send_raw(int fd, char *buf, int buflen, int user, int cmd) +send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd) { char packet[4096]; int len; @@ -336,23 +495,22 @@ send_raw(int fd, char *buf, int buflen, int user, int cmd) } len += RAW_HDR_LEN; - packet[RAW_HDR_CMD] = cmd | (user & 0x0F); + packet[RAW_HDR_CMD] = (cmd & 0xF0) | (user & 0x0F); sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv)); } static void -send_raw_data(int dns_fd) // TODO: fix send_raw +send_raw_data(int dns_fd, uint8_t *data, size_t datalen) { - errx(1, "send_raw_data NEEDS FIXING!"); - /*send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); - outpkt.len = 0;*/ + send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA); } -static void +static int send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) -/* Base32 encodes data and sends as single DNS query */ +/* Base32 encodes data and sends as single DNS query + * Returns ID of sent query */ { uint8_t buf[4096]; @@ -360,39 +518,50 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) build_hostname(buf, sizeof(buf), data, datalen, topdomain, b32, hostname_maxlen, 1); - send_query(fd, buf); + return send_query(fd, buf); } static inline int is_sending() { - return (outbuf->numitems > 0); + return window_sending(outbuf) > 0; } -static inline int -is_recving() -{ - return inbuf->numitems > 0; -} - -static void -send_ping(int fd, int ping_response, int ack) // TODO: setup window sync stuff in ping +void +send_ping(int fd, int ping_response, int ack) { if (conn == CONN_DNS_NULL) { - uint8_t data[9]; + uint8_t data[11]; + int id; + /* Build ping header (see doc/proto_xxxxxxxx.txt) */ data[0] = userid; data[1] = ack & 0xFF; - data[2] = outbuf->windowsize & 0xff; - data[3] = inbuf->windowsize & 0xff; - data[4] = inbuf->start_seq_id & 0xff; /* Downstream window start */ - data[5] = outbuf->start_seq_id & 0xff; /* Upstream window start */ - data[6] = ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); - data[7] = (rand_seed >> 8) & 0xff; - data[8] = (rand_seed >> 0) & 0xff; + + if (outbuf && inbuf) { + data[2] = outbuf->windowsize & 0xff; /* Upstream window size */ + data[4] = inbuf->windowsize & 0xff; /* Downstream window size */ + data[3] = outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[5] = inbuf->start_seq_id & 0xff; /* Downstream window start */ + } + + *(uint16_t *) (data + 6) = htons(server_timeout_ms); + + /* update server lazy timeout, ack flag, respond with ping flag */ + data[8] = (1 << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[9] = (rand_seed >> 8) & 0xff; + data[10] = (rand_seed >> 0) & 0xff; rand_seed += 263; - send_packet(fd, 'p', data, sizeof(data)); + if (debug >= 3) { + fprintf(stderr, " SEND PING: respond %d, ack %d, servertimeout %ld, flags %02X\n", + ping_response, ack, server_timeout_ms, data[8]); + } + + id = send_packet(fd, 'p', data, sizeof(data)); + + /* Log query ID as being sent now */ + query_sent_now(id); } else { send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); } @@ -403,7 +572,7 @@ send_next_frag(int fd) /* Sends next available fragment of data from the outgoing window buffer */ { static uint8_t buf[MAX_FRAGSIZE], hdr[5]; - int code; + int code, id; static int datacmc = 0; static char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; fragment *f; @@ -412,14 +581,14 @@ send_next_frag(int fd) /* Get next fragment to send */ f = window_get_next_sending_fragment(outbuf, &next_downstream_ack); if (!f) { - if (is_sending()) { + if (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(fd, 1, next_downstream_ack); next_downstream_ack = -1; window_tick(outbuf); } - return; /* nothing to send - why was this called? */ + return; /* nothing to send */ } /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ @@ -428,7 +597,7 @@ send_next_frag(int fd) buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ /* Next 3 bytes is seq ID, downstream ACK and flags */ - code = ((f->ack_other < 0 ? 0 : 1) << 3) | (f->is_nack << 2) + code = ((f->ack_other < 0 ? 0 : 1) << 3) | (f->compressed << 2) | (f->start << 1) | f->end; hdr[0] = f->seqID & 0xFF; @@ -449,7 +618,9 @@ send_next_frag(int fd) if (datacmc >= 36) datacmc = 0; - send_query(fd, buf); + id = send_query(fd, buf); + /* Log query ID as being sent now */ + query_sent_now(id); window_tick(outbuf); } @@ -663,38 +834,45 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query memcpy(buf, data, rv); } + if (debug >= 2) + fprintf(stderr, "RX: id %5d name[0]='%c'\n", q->id, q->name[0]); + return rv; } else { /* CONN_RAW_UDP */ - unsigned long datalen; - char buf[64*1024]; + size_t datalen; + uint8_t buf[64*1024]; /* minimum length */ - if (r < RAW_HDR_LEN) return 0; + if (r < RAW_HDR_LEN) + return 0; /* should start with header */ - if (memcmp(data, raw_header, RAW_HDR_IDENT_LEN)) return 0; + if (memcmp(data, raw_header, RAW_HDR_IDENT_LEN)) + return 0; /* should be my user id */ - if (RAW_HDR_GET_USR(data) != userid) return 0; + if (RAW_HDR_GET_USR(data) != userid) + return 0; if (RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_DATA || RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_PING) lastdownstreamtime = time(NULL); /* should be data packet */ - if (RAW_HDR_GET_CMD(data) != RAW_HDR_CMD_DATA) return 0; + if (RAW_HDR_GET_CMD(data) != RAW_HDR_CMD_DATA) + return 0; r -= RAW_HDR_LEN; datalen = sizeof(buf); - if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) &data[RAW_HDR_LEN], r) == Z_OK) { - write_tun(tun_fd, (uint8_t*)buf, datalen); + if (uncompress(buf, &datalen, data + RAW_HDR_LEN, r) == Z_OK) { + write_tun(tun_fd, buf, datalen); } - /* don't process any further */ + /* all done */ return 0; } } static int -handshake_waitdns(int dns_fd, char *buf, size_t buflen, char c1, char c2, int timeout) +handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) /* Wait for DNS reply fitting to our latest query and returns it. Returns length of reply = #bytes used in buf. Returns 0 if fitting reply happens to be useless. @@ -711,6 +889,9 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char c1, char c2, int ti int r, rv; fd_set fds; struct timeval tv; + char qcmd; + + cmd = toupper(cmd); while (1) { tv.tv_sec = timeout; @@ -728,7 +909,8 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char c1, char c2, int ti q.name[0] = '\0'; rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q); - if (q.id != chunkid || (q.name[0] != c1 && q.name[0] != c2)) { + qcmd = toupper(q.name[0]); + if (q.id != chunkid || qcmd != cmd) { if (debug >= 1) fprintf(stderr, "Ignoring unfitting reply id %d starting with '%c'\n", q.id, q.name[0]); continue; @@ -776,41 +958,54 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char c1, char c2, int ti } static int -parse_data(uint8_t *data, size_t len, fragment *f) +parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) { size_t headerlen = DOWNSTREAM_HDR; + int ping; memset(f, 0, sizeof(fragment)); - int ping = (data[2] >> 5) & 1; - f->compressed = (data[2] >> 4) & 1; - f->ack_other = (data[2] >> 3) & 1 ? data[1] : -1; - f->is_nack = (data[2] >> 2) & 1; - f->start = (data[2] >> 1) & 1; - f->end = data[2] & 1; + f->seqID = data[0]; + + /* Flags */ + f->end = data[2] & 1; + 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 (immediate) + *immediate = (data[2] >> 5) & 1; + if (ping) { /* Handle ping stuff */ + static unsigned dn_start_seq, up_start_seq, dn_wsize, up_wsize; + headerlen = DOWNSTREAM_PING_HDR; if (len < headerlen) return -1; /* invalid packet - continue */ + /* Parse data/ping header */ - static unsigned dn_start_seq, up_start_seq, dn_wsize, up_wsize; - dn_wsize = data[3]; /* TODO: do something with wsize/start params in ping */ + /* TODO: do something with wsize/start params in ping */ + dn_wsize = data[3]; up_wsize = data[4]; dn_start_seq = data[5]; up_start_seq = data[6]; - if (debug >= 3) + if (debug >= 3) { fprintf(stderr, "PING pkt data=%lu WS: up=%u, dn=%u; Start: up=%u, dn=%u\n", len - headerlen, up_wsize, dn_wsize, up_start_seq, dn_start_seq); + } } f->len = len - headerlen; - if (f->len > 0) memcpy(f->data, data + headerlen, MIN(f->len, sizeof(f->data))); + 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) */ } static int tunnel_tun(int tun_fd, int dns_fd) { - size_t outlen; + size_t datalen; uint8_t out[64*1024]; uint8_t in[64*1024]; + uint8_t *data; ssize_t read; if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) @@ -818,26 +1013,28 @@ tunnel_tun(int tun_fd, int dns_fd) /* Check if outgoing buffer can hold data */ if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { - if (debug >= 3) + if (debug >= 2) fprintf(stderr, " Outgoing buffer full (%lu/%lu), not adding data!\n", outbuf->numitems, outbuf->length); return -1; } if (debug >= 2) - fprintf(stderr, " IN: %lu bytes on tunnel\n", read); + fprintf(stderr, " IN: %lu bytes on tunnel, compression %d\n", read, compression_up); - outlen = sizeof(out); - compress2(out, &outlen, in, read, 9); - - window_add_outgoing_data(outbuf, out, outlen, 1); + if (conn != CONN_DNS_NULL || compression_up) { + datalen = sizeof(out); + compress2(out, &datalen, in, read, 9); + data = out; + } else { + datalen = read; + data = in; + } if (conn == CONN_DNS_NULL) { + window_add_outgoing_data(outbuf, data, datalen, compression_up); send_next_frag(dns_fd); - - // TODO: adjust min send interval based on DNS server droppiness - send_ping_soon = is_sending() ? 2 : 0; } else { - send_raw_data(dns_fd); + send_raw_data(dns_fd, data, datalen); } return read; @@ -847,14 +1044,12 @@ static int tunnel_dns(int tun_fd, int dns_fd) { static long packrecv = 0; - static long packrecv_oos = 0; static long packrecv_servfail = 0; static struct query q; size_t datalen, buflen; static uint8_t buf[64*1024], cbuf[64*1024]; static fragment f; - int read, compressed, res; - int send_something_now = 0; + int read, compressed, res, immediate; memset(&q, 0, sizeof(q)); memset(buf, 0, sizeof(buf)); @@ -864,9 +1059,6 @@ tunnel_dns(int tun_fd, int dns_fd) if (conn != CONN_DNS_NULL) return 1; /* everything already done */ - if (debug >= 2) - fprintf(stderr, "RX: id %5d name[0]='%c'\n", q.id, q.name[0]); - /* Don't process anything that isn't data for us; usually error replies from fragsize probes etc. However a sequence of those, mostly 1 sec apart, will continuously break the >=2-second select @@ -885,19 +1077,24 @@ tunnel_dns(int tun_fd, int dns_fd) if (read < 0) write_dns_error(&q, 0); - if (read < 0 && q.rcode == SERVFAIL && lazymode && - selecttimeout > 1) { + if (read < 0 && q.rcode == SERVFAIL && lazymode && server_timeout_ms > 500) { if (packrecv < 500 && packrecv_servfail < 4) { packrecv_servfail++; - warnx("Hmm, that's %ld. Your data should still go through...", packrecv_servfail); - } else if (packrecv < 500 && packrecv_servfail == 4) { + fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...", packrecv_servfail); + } else if (packrecv < 500 && packrecv_servfail >= 4 && autodetect_server_timeout) { packrecv_servfail++; - warnx("I think %ld is too many. Setting interval to 1 to hopefully reduce SERVFAILs. But just ignore them if data still comes through. (Use -I1 next time on this network.)", packrecv_servfail); - selecttimeout = 1; + server_timeout_ms -= 500; + double server_timeout = (float) server_timeout_ms / 1000.0; + fprintf(stderr, "%ld SERVFAILs is too many. Setting server timeout to %f to hopefully reduce SERVFAILs." + " But just ignore them if data still comes through. (Use -I%f next time on this network.)", + packrecv_servfail, server_timeout, server_timeout); send_query_sendcnt = 0; send_query_recvcnt = 0; + rtt_total_ms = 1000; + num_immediate = 1; + send_ping(dns_fd, 0, -1); } else if (packrecv >= 500 && packrecv_servfail > 0) { - warnx("(Sorry, stopped counting; try -I1 if you experience hiccups.)"); + fprintf(stderr, "(Sorry, stopped counting; try -I1 if you experience hiccups.)"); packrecv_servfail = 0; } } @@ -907,26 +1104,27 @@ tunnel_dns(int tun_fd, int dns_fd) } if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { - warnx("BADIP: Server rejected sender IP address (maybe iodined -c will help), or server kicked us due to timeout. Will exit if no downstream data is received in 60 seconds."); + fprintf(stderr, "BADIP: Server rejected sender IP address (maybe iodined -c will help), or server " + "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds."); return -1; /* nothing done */ } - if (send_ping_soon) { - send_something_now = 1; - send_ping_soon = 0; - } - /* Okay, we have a recent downstream packet */ lastdownstreamtime = time(NULL); - if (!(packrecv & 0x1000000)) + + if (!(packrecv & 0x10000000)) packrecv++; + send_query_recvcnt++; /* overflow doesn't matter */ /* Decode the downstream data header and fragment-ify ready for processing */ - res = parse_data(cbuf, read, &f); + res = parse_data(cbuf, read, &f, &immediate); if ((debug >= 3 && res) || (debug >= 2 && !res)) - fprintf(stderr, " RX %s frag id %u, ACK %d, nack %d, datalen %lu, s%d e%d\n", - res ? "PING" : "DATA", f.seqID, f.ack_other, f.is_nack, f.len, f.start, f.end); + fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %lu, s%d e%d\n", + res ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); + + /* Mark query as received */ + got_response(q.id, immediate); window_ack(outbuf, f.ack_other); @@ -937,66 +1135,46 @@ tunnel_dns(int tun_fd, int dns_fd) /* Don't send anything too soon; no data waiting from server */ if (f.len == 0) { if (!res && debug >= 1) - fprintf(stderr, "Received downstream data with 0 length and NOT a ping!"); - send_ping_soon = 700; + fprintf(stderr, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); + if (!lazymode) + send_ping_soon = 100; + else + send_ping_soon = 700; return -1; } - /* Send something quickly to see if any more data needs to come through */ - send_ping_soon = 5; - - /* Downstream data traffic */ - if (!window_process_incoming_fragment(inbuf, &f)) { - /* Packet outside window - old, duped and irrelevant */ - packrecv_oos ++; - return -1; /* nothing done */ + /* Get next ACK if nothing already pending: if we get a new ack + * then we must send it immediately. */ + if (next_downstream_ack >= 0) { + /* If this happens something is wrong (or last frag was a re-send) + * May result in ACKs being delayed. */ + if (debug >= 1) + warnx("next_downstream_ack NOT -1! (%d), %u resends, %u oos", next_downstream_ack, outbuf->resends, outbuf->oos); } - /* Get next ACK if nothing already pending: only do this if we are sending */ - if (next_downstream_ack < 0) { - if ((next_downstream_ack = window_get_next_ack(inbuf)) >= 0) { - send_something_now = 1; - } - } - - // TODO: check OOS packet count and send ping to update params + /* Downstream data traffic + get ack for that data */ + next_downstream_ack = window_process_incoming_fragment(inbuf, &f); datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed); - if (datalen != 0) { + if (datalen > 0) { if (compressed) { buflen = sizeof(buf); if ((res = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { if (debug >= 1) - warnx("Uncompress failed (%d): reassembled data corrupted or incomplete!", res); + warnx("Uncompress failed (%d) for data len %lu: reassembled data corrupted or incomplete!", res, datalen); + datalen = 0; + } else { + datalen = buflen; } - datalen = buflen; } - write_tun(tun_fd, buf, datalen); - /* ACK frag quickly */ - send_ping_soon = 2; - } else { - /* ACK received frag and let server send any more data */ - send_something_now = 1; + + if (datalen) + write_tun(tun_fd, buf, datalen); } /* Move window along after doing all data processing */ window_tick(inbuf); - /* Upstream data traffic */ - if (is_sending() || is_recving()) { - /* More to send - next fragment*/ - send_next_frag(dns_fd); - send_ping_soon = 2; - send_something_now = 0; - } - - /* Send ping if we didn't send anything yet */ - if (send_something_now) { - send_ping(dns_fd, 0, next_downstream_ack); - next_downstream_ack = -1; - send_ping_soon = 0; - } - return read; } @@ -1007,27 +1185,65 @@ client_tunnel(int tun_fd, int dns_fd) fd_set fds; int rv; int i; + int sending, pending; + if (conn != CONN_DNS_NULL) { + compression_up = 1; + } + + outbuf = window_buffer_init(64, windowsize_up, maxfragsize_up, WINDOW_SENDING); + /* Incoming buffer max fragsize doesn't matter */ + inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); + + pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) + pending_queries[i].id = -1; + + /* start counting now */ rv = 0; lastdownstreamtime = time(NULL); - send_query_sendcnt = 0; /* start counting now */ + rtt_total_ms = 1000; + num_immediate = 1; + num_timeouts = 0; + num_untracked = 0; + send_query_recvcnt = 0; + send_query_sendcnt = 0; + + /* set default server timeout */ + + if (debug >= 4) + window_debug = debug - 3; while (running) { - tv.tv_sec = selecttimeout; - tv.tv_usec = 0; + tv.tv_sec = max_timeout_ms / 1000; + tv.tv_usec = (max_timeout_ms - tv.tv_sec * 1000) * 1000; + + /* TODO: adjust min send interval based on DNS server droppiness + * (eg. from sending lots of requests simultaneously) + * TODO: adjust number of pending queries based on current data rate */ + sending = window_sending(outbuf); + pending = num_pending(); + if (sending || (pending < windowsize_down && lazymode) ) { + if (debug >= 3) { + warnx("Waiting to send %d frags or fill server lazy buffer with (%d - %lu) queries.", + sending, pending, windowsize_down); + } + + /* Upstream data traffic */ + if (sending) { + /* More to send - next fragment */ + send_next_frag(dns_fd); + } else { + /* Send ping if we didn't send anything yet */ + send_ping(dns_fd, 0, next_downstream_ack); + next_downstream_ack = -1; + } - if (is_sending()) { tv.tv_sec = 0; tv.tv_usec = 2000; send_ping_soon = 0; } - if (is_recving()) { - /* get next thingy soon ish */ - tv.tv_sec = 0; - tv.tv_usec = 5000; - } - if (send_ping_soon) { tv.tv_sec = 0; tv.tv_usec = send_ping_soon * 1000; @@ -1043,7 +1259,7 @@ client_tunnel(int tun_fd, int dns_fd) i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); - if (difftime(lastdownstreamtime, time(NULL)) >= 60) { + if (difftime(time(NULL), lastdownstreamtime) > 60) { warnx("No downstream data received in 60 seconds, shutting down."); running = 0; } @@ -1055,16 +1271,17 @@ client_tunnel(int tun_fd, int dns_fd) err(1, "select < 0"); if (i == 0) { - /* timeout */ - if (is_sending()) { - if (outbuf->resends < 3) { // if too many retransmits/timeouts/waiting etc. + /* TODO improve timeout handling based on stats */ + if (!window_sending(outbuf) && outbuf->numitems > 0) { + if (outbuf->resends < 3) { send_next_frag(dns_fd); } else { outbuf->resends = 0; send_ping(dns_fd, 1, -1); } } else { - send_ping(dns_fd, 0, -1); + send_ping(dns_fd, 0, next_downstream_ack); + next_downstream_ack = -1; } send_ping_soon = 0; @@ -1105,42 +1322,39 @@ send_login(int fd, char *login, int len) } static void -send_fragsize_probe(int fd, int fragsize) +send_fragsize_probe(int fd, uint16_t fragsize) { uint8_t probedata[256]; uint8_t buf[MAX_FRAGSIZE]; + uint8_t hdr[3]; + size_t hdr_len_enc = 6; - /* - * build a large query domain which is random and maximum size, - * will also take up maximal space in the return packet - */ + buf[0] = 'r'; /* Probe downstream fragsize packet */ + + hdr[0] = userid; + *(uint16_t *) (hdr + 1) = htons(fragsize); + + b32->encode(buf + 1, &hdr_len_enc, hdr, 3); + /* build a large query domain which is random and maximum size, + * will also take up maximum space in the return packet */ memset(probedata, MAX(1, rand_seed & 0xff), sizeof(probedata)); probedata[1] = MAX(1, (rand_seed >> 8) & 0xff); rand_seed++; /* Note: must either be same, or larger, than send_chunk() */ build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), topdomain, - dataenc, hostname_maxlen, 5); - - fragsize &= 0x7FF; - - buf[0] = 'r'; /* Probe downstream fragsize packet */ - buf[1] = b32_5to8((userid << 1) | ((fragsize >> 10) & 1)); - buf[2] = b32_5to8((fragsize >> 5) & 31); - buf[3] = b32_5to8(fragsize & 31); - buf[4] = 'd'; /* dummy to match send_chunk() */ + dataenc, hostname_maxlen, 6); send_query(fd, buf); } static void -send_set_downstream_fragsize(int fd, int fragsize) +send_set_downstream_fragsize(int fd, uint16_t fragsize) { uint8_t data[5]; data[0] = userid; - data[1] = (fragsize & 0xff00) >> 8; - data[2] = (fragsize & 0x00ff); + *(uint16_t *) (data + 1) = htons(fragsize); data[3] = (rand_seed >> 8) & 0xff; data[4] = (rand_seed >> 0) & 0xff; @@ -1154,10 +1368,8 @@ send_version(int fd, uint32_t version) { uint8_t data[6]; - data[0] = (version >> 24) & 0xff; - data[1] = (version >> 16) & 0xff; - data[2] = (version >> 8) & 0xff; - data[3] = (version >> 0) & 0xff; + version = htonl(version); + *(uint32_t *) data = version; data[4] = (rand_seed >> 8) & 0xff; data[5] = (rand_seed >> 0) & 0xff; @@ -1188,7 +1400,7 @@ send_raw_udp_login(int dns_fd, int userid, int seed) char buf[16]; login_calculate(buf, 16, password, seed + 1); - send_raw(dns_fd, buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN); + send_raw(dns_fd, (uint8_t *) buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN); } static void @@ -1242,6 +1454,21 @@ send_codec_switch(int fd, int userid, int bits) send_query(fd, (uint8_t *)buf); } +static void +send_compression_switch(int fd, int userid) +{ + char buf[512] = "o_____."; + buf[1] = b32_5to8(userid); + buf[2] = compression_down ? 'c' : 'd'; + + buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((rand_seed) & 0x1f); + rand_seed++; + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, (uint8_t *)buf); +} static void send_downenc_switch(int fd, int userid) @@ -1252,7 +1479,7 @@ send_downenc_switch(int fd, int userid) buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed ) & 0x1f); + buf[5] = b32_5to8((rand_seed) & 0x1f); rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); @@ -1293,7 +1520,7 @@ handshake_version(int dns_fd, int *seed) send_version(dns_fd, PROTOCOL_VERSION); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'v', 'V', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'V', i+1); if (read >= 9) { payload = (((in[4] & 0xff) << 24) | @@ -1344,7 +1571,7 @@ handshake_login(int dns_fd, int seed) send_login(dns_fd, login, 16); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'l', 'L', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'L', i+1); if (read > 0) { int netmask; @@ -1394,7 +1621,7 @@ handshake_raw_udp(int dns_fd, int seed) send_ip_request(dns_fd, userid); - len = handshake_waitdns(dns_fd, in, sizeof(in), 'i', 'I', i+1); + len = handshake_waitdns(dns_fd, in, sizeof(in), 'I', i+1); if (len == 5 && in[0] == 'I') { /* Received IPv4 address */ @@ -1489,7 +1716,7 @@ handshake_upenctest(int dns_fd, char *s) send_upenctest(dns_fd, s); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'z', 'Z', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Z', i+1); if (read == -2) return 0; /* hard error */ @@ -1650,7 +1877,7 @@ handshake_downenctest(int dns_fd, char trycodec) send_downenctest(dns_fd, trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -1688,7 +1915,7 @@ handshake_downenc_autodetect(int dns_fd) if (do_qtype == T_NULL || do_qtype == T_PRIVATE) { /* no other choice than raw */ fprintf(stderr, "No alternative downstream codec available, using default (Raw)\n"); - return ' '; + return 'R'; } fprintf(stderr, "Autodetecting downstream codec (use -O to override)\n"); @@ -1750,7 +1977,7 @@ handshake_qtypetest(int dns_fd, int timeout) send_downenctest(dns_fd, trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', timeout); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', timeout); if (read != slen) return 0; /* incorrect */ @@ -1874,7 +2101,7 @@ handshake_edns0_check(int dns_fd) send_downenctest(dns_fd, trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -1925,7 +2152,7 @@ handshake_switch_codec(int dns_fd, int bits) send_codec_switch(dns_fd, userid, bits); - read = handshake_waitdns(dns_fd, in, sizeof(in), 's', 'S', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'S', i+1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { @@ -1943,7 +2170,7 @@ handshake_switch_codec(int dns_fd, int bits) dataenc = tempenc; /* Update outgoing buffer max (decoded) fragsize */ - outbuf->maxfraglen = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); + maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); return; } @@ -1958,6 +2185,49 @@ codec_revert: fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name); } +static void +handshake_switch_downcompression(int dns_fd) +{ + char in[4096]; + int i; + int read; + char *status; + + status = compression_down ? "on" : "off"; + fprintf(stderr, "Switching %s downstream data compression\n", status); + for (i = 0; running && i < 5; i++) { + + send_compression_switch(dns_fd, userid); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1); + + if (read > 0) { + if (strncmp("BADLEN", in, 6) == 0) { + fprintf(stderr, "Server got bad message length.\n"); + goto fail; + } else if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address.\n"); + goto fail; + } else if (strncmp("BADCODEC", in, 8) == 0) { + fprintf(stderr, "Server rejected the compression option.\n"); + goto fail; + } + in[read] = 0; /* zero terminate */ + fprintf(stderr, "Server %s downstream compression\n", in); + return; + } + + fprintf(stderr, "Retrying downstream compression switch...\n"); + } + if (!running) + return; + + fprintf(stderr, "No reply from server on downstream compression switch.\n"); + +fail: + fprintf(stderr, "Failed to switch %s downstream data compression\n", status); +} + static void handshake_switch_downenc(int dns_fd) { @@ -1981,7 +2251,7 @@ handshake_switch_downenc(int dns_fd) send_downenc_switch(dns_fd, userid); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { @@ -2022,7 +2292,7 @@ handshake_try_lazy(int dns_fd) send_lazy_switch(dns_fd, userid); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { @@ -2051,10 +2321,10 @@ handshake_try_lazy(int dns_fd) codec_revert: fprintf(stderr, "Falling back to legacy mode\n"); lazymode = 0; - selecttimeout = 1; + max_timeout_ms = 1000; } -static void +void handshake_lazyoff(int dns_fd) /* Used in the middle of data transfer, timing is different and no error msgs */ { @@ -2066,12 +2336,12 @@ handshake_lazyoff(int dns_fd) send_lazy_switch(dns_fd, userid); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', 1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', 1); if (read == 9 && strncmp("Immediate", in, 9) == 0) { warnx("Server switched back to legacy mode."); lazymode = 0; - selecttimeout = 1; + max_timeout_ms = 1000; return; } } @@ -2171,7 +2441,7 @@ handshake_autoprobe_fragsize(int dns_fd) send_fragsize_probe(dns_fd, proposed_fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'r', 'R', 1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'R', 1); if (read > 0) { /* We got a reply */ @@ -2237,7 +2507,7 @@ handshake_set_fragsize(int dns_fd, int fragsize) send_set_downstream_fragsize(dns_fd, fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'n', 'N', i+1); + read = handshake_waitdns(dns_fd, in, sizeof(in), 'N', i+1); if (read > 0) { @@ -2292,9 +2562,9 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } if (raw_mode && handshake_raw_udp(dns_fd, seed)) { - /* TODO: fragsize based on max raw packet size */ + /* TODO: upstream fragsize based on max raw packet size */ conn = CONN_RAW_UDP; - selecttimeout = 20; + max_timeout_ms = 10000; } else { if (raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); @@ -2330,12 +2600,13 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (!running) return -1; - if (downenc != ' ') { - handshake_switch_downenc(dns_fd); - } + handshake_switch_downenc(dns_fd); if (!running) return -1; + if (!compression_down) + handshake_switch_downcompression(dns_fd); + if (lazymode) { handshake_try_lazy(dns_fd); } diff --git a/src/client.h b/src/client.h index bdd0285..ee43a52 100644 --- a/src/client.h +++ b/src/client.h @@ -20,6 +20,13 @@ extern int debug; +#define PENDING_QUERIES_LENGTH (MAX(windowsize_up, windowsize_down) * 2) + +struct query_tuple { + int id; /* DNS query / response ID */ + struct timeval time; /* time sent or 0 if cleared */ +}; + void client_init(); void client_stop(); @@ -33,11 +40,16 @@ void client_set_password(const char *cp); int client_set_qtype(char *qtype); char *client_get_qtype(); void client_set_downenc(char *encoding); -void client_set_selecttimeout(int select_timeout); +void client_set_compression(int up, int down); +void client_set_dnstimeout(double, double, int); void client_set_lazymode(int lazy_mode); +void client_set_windowsize(size_t, size_t); void client_set_hostname_maxlen(size_t i); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); +void handshake_lazyoff(int dns_fd); +void send_ping(int fd, int ping_response, int ack); + #endif From 6eb2e4d251734dc7e7ef4a46a54130b325ab0d4a Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 13:07:29 +0800 Subject: [PATCH 030/113] Added fine tuning command-line options --- src/iodine.c | 116 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 28 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index 27babd6..1a8bf3e 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -66,33 +65,48 @@ static void usage() __attribute__((noreturn)); #endif static void -usage() { +print_usage() +{ extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-D] [-r] [-u user] [-t chrootdir] [-d device] " - "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " - "[-z context] [-F pidfile] topdomain [nameserver ...]\n", __progname); + fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-C 0|1] [-c 0|1]" + "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] " + "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]]\n", __progname); +} + +static void +usage() +{ + print_usage(); exit(2); } static void -help() { - extern char *__progname; - +help() +{ fprintf(stderr, "iodine IP over DNS tunneling client\n"); - fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " - "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " - "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]] \n", __progname); - fprintf(stderr, "Options to try if connection doesn't work:\n"); + print_usage(); + fprintf(stderr, "\nOptions to try if connection doesn't work:\n"); fprintf(stderr, " -T force dns type: NULL, PRIVATE, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); fprintf(stderr, " -O force downstream encoding for -T other than NULL: Base32, Base64, Base64u,\n"); fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); - fprintf(stderr, " -I max interval between requests (default 4 sec) to prevent DNS timeouts\n"); + fprintf(stderr, " -I target interval between sending and receiving requests (default: 4 secs)\n"); + fprintf(stderr, " should be greater than the round-trip for the connection\n"); fprintf(stderr, " -L 1: use lazy mode for low-latency (default). 0: don't (implies -I1)\n"); fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); fprintf(stderr, " -r to skip raw UDP mode attempt\n"); fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + + fprintf(stderr, "Fine-tuning options:\n"); + fprintf(stderr, " -w downstream fragment window size (default: 8)\n"); + fprintf(stderr, " -W upstream fragment window size (default: 8)\n"); + fprintf(stderr, " -i server-side request timeout in lazy mode \n"); + fprintf(stderr, " (default: automatically adjust from max timeout and round-trip time)\n"); + fprintf(stderr, " -C 1: use downstream compression (default), 0: disable\n"); + fprintf(stderr, " -c 1: use upstream compression, 0: disable (default)\n\n"); + fprintf(stderr, "Other options:\n"); fprintf(stderr, " -v to print version info and exit\n"); fprintf(stderr, " -h to print this help and exit\n"); @@ -102,16 +116,19 @@ help() { fprintf(stderr, " -t dir to chroot to directory dir\n"); fprintf(stderr, " -d device to set tunnel device name\n"); fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); - fprintf(stderr, " -F pidfile to write pid to a file\n"); - fprintf(stderr, "nameserver is the IP/hostname of the relaying nameserver(s). if absent, /etc/resolv.conf is used\n"); + fprintf(stderr, " -F pidfile to write pid to a file\n\n"); + + fprintf(stderr, "nameserver is the IP/hostname of the relaying nameserver(s).\n"); fprintf(stderr, " multiple nameservers can be specified (used in round-robin). \n"); + fprintf(stderr, " if absent, system default is used\n"); fprintf(stderr, "topdomain is the FQDN that is delegated to the tunnel endpoint.\n"); exit(0); } static void -version() { +version() +{ fprintf(stderr, "iodine IP over DNS tunneling client\n"); fprintf(stderr, "Git version: %s\n; protocol version %08X", GITREVISION, PROTOCOL_VERSION); exit(0); @@ -137,11 +154,20 @@ main(int argc, char **argv) int dns_fd; int max_downstream_frag_size; int autodetect_frag_size; + int hostname_maxlen; + int retval; int raw_mode; int lazymode; - int selecttimeout; - int hostname_maxlen; + double max_interval_sec; + double server_timeout_sec ; + int autodetect_server_timeout; + int up_compression; + int down_compression; + + int up_windowsize; + int down_windowsize; + #ifdef OPENBSD int rtable = 0; #endif @@ -156,6 +182,7 @@ main(int argc, char **argv) int nameserv_family; nameserv_addrs_len = 0; + nameservaddr_len = 0; nameserv_host = NULL; topdomain = NULL; errormsg = NULL; @@ -177,9 +204,16 @@ main(int argc, char **argv) retval = 0; raw_mode = 1; lazymode = 1; - selecttimeout = 4; + max_interval_sec = 5; /* DNS RFC says 5 seconds minimum */ + server_timeout_sec = 4; /* Safe value for RTT <1s */ + autodetect_server_timeout = 1; hostname_maxlen = 0xFF; nameserv_family = AF_UNSPEC; + up_compression = 0; + down_compression = 1; + + up_windowsize = 8; + down_windowsize = 8; #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); @@ -196,7 +230,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfDhru:t:d:R:P:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhrCcu:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -263,7 +297,7 @@ main(int argc, char **argv) if (client_set_qtype(optarg)) errx(5, "Invalid query type '%s'", optarg); break; - case 'O': /* not -D, is Debug in server */ + case 'O': client_set_downenc(optarg); break; case 'L': @@ -272,13 +306,31 @@ main(int argc, char **argv) lazymode = 1; if (lazymode < 0) lazymode = 0; - if (!lazymode) - selecttimeout = 1; + if (!lazymode && max_interval_sec > 1) + max_interval_sec = 1; break; case 'I': - selecttimeout = atoi(optarg); - if (selecttimeout < 1) - selecttimeout = 1; + max_interval_sec = strtod(optarg, NULL); + if (max_interval_sec < 1) + max_interval_sec = 1; + break; + case 'i': + server_timeout_sec = strtod(optarg, NULL); + if (server_timeout_sec < 0.4) + server_timeout_sec = 0.4; + autodetect_server_timeout = 0; + break; + case 'w': + down_windowsize = atoi(optarg); + break; + case 'W': + up_windowsize = atoi(optarg); + break; + case 'c': + up_compression = atoi(optarg) & 1; + break; + case 'C': + down_compression = atoi(optarg) & 1; break; default: usage(); @@ -349,10 +401,17 @@ main(int argc, char **argv) /* NOTREACHED */ } - client_set_selecttimeout(selecttimeout); + if (up_windowsize < 1 || down_windowsize < 1) { + warnx("Windowsize (-w or -W) must be greater than 0!"); + usage(); + } + + client_set_compression(up_compression, down_compression); + client_set_dnstimeout(max_interval_sec, server_timeout_sec, autodetect_server_timeout); client_set_lazymode(lazymode); client_set_topdomain(topdomain); client_set_hostname_maxlen(hostname_maxlen); + client_set_windowsize(up_windowsize, down_windowsize); if (username != NULL) { #ifndef WINDOWS32 @@ -391,7 +450,8 @@ main(int argc, char **argv) fprintf(stderr, "Sending DNS queries for %s to ", topdomain); for (int a = 0; a < nameserv_addrs_len; a++) - fprintf(stderr, "%s%s", format_addr(&nameserv_addrs[a], nameservaddr_len), (a != nameserv_addrs_len-1) ? ", " : ""); + fprintf(stderr, "%s%s", format_addr(&nameserv_addrs[a], nameservaddr_len), + (a != nameserv_addrs_len-1) ? ", " : ""); fprintf(stderr, "\n"); if (client_handshake(dns_fd, raw_mode, autodetect_frag_size, max_downstream_frag_size)) { From ad48dc8e10b5fd533372598d67a28534f6b22adf Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 13:09:49 +0800 Subject: [PATCH 031/113] Updated qmem as lazy mode query buffer, other fixes/adjustments and support for more client-controlled options --- src/server.c | 911 +++++++++++++++++++++++++++------------------------ src/server.h | 25 +- 2 files changed, 496 insertions(+), 440 deletions(-) diff --git a/src/server.c b/src/server.c index 015b5cb..f6fd8b7 100644 --- a/src/server.c +++ b/src/server.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "common.h" #include "version.h" @@ -97,12 +98,10 @@ server_stop() } static void -send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct query *q) +send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr_storage *from, socklen_t fromlen) { - char packet[4096]; - int len; - - len = MIN(sizeof(packet) - RAW_HDR_LEN, buflen); + char packet[buflen + RAW_HDR_LEN]; + int len = buflen; memcpy(packet, raw_header, RAW_HDR_LEN); if (len) { @@ -113,141 +112,191 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct query *q packet[RAW_HDR_CMD] = cmd | (user & 0x0F); if (debug >= 2) { - fprintf(stderr, "TX-raw: client %s, cmd %d, %d bytes\n", - format_addr(&q->from, q->fromlen), cmd, len); + fprintf(stderr, "TX-raw: client %s (user %d), cmd %d, %d bytes\n", + format_addr(from, fromlen), user, cmd, len); } - sendto(fd, packet, len, 0, (struct sockaddr *) &q->from, q->fromlen); + sendto(fd, packet, len, 0, (struct sockaddr *) from, fromlen); } -int -answer_from_qmem(int dns_fd, struct query *q, unsigned char *qmem_cmc, - unsigned short *qmem_type, int qmem_len, - unsigned char *cmc_to_check) -/* Checks query memory and sends an (illegal) answer if this is a duplicate. - Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is - not a duplicate. */ +/* Ringbuffer Query Handling (qmem): + This is used to make the handling duplicates and query timeouts simpler + and all in one place. + Using this, lazy mode should be possible with n queries (n <= windowsize) + + New queries are placed consecutively in the buffer, replacing any old + queries (already responded to) if length == QMEM_LEN. Old queries are kept + to check for dupes etc. + + TODO: modify a bit to replace dnscache entirely? + it seems the only difference is qmem doesn't store answers. */ +#ifdef QMEM_LEN + +#define QMEM_DEBUG(l, u, ...) \ + if (debug >= l) {\ + fprintf(stderr, "[QMEM u%d (%lu/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } + +static void +qmem_init(int userid) { - int i; + memset(&users[userid].qmem, 0, sizeof(struct query_buffer)); +// users[userid].qmem.end = 1; + for (size_t i = 0; i < QMEM_LEN; i++) { + users[userid].qmem.queries[i].id = -1; + } +} - for (i = 0; i < qmem_len ; i++) { +static int +qmem_append(int dns_fd, int userid, struct query *q) +/* Appends incoming query to the buffer. If the query is already in the buffer, + * ie a duplicate, an illegal answer is sent. + * Return: 0 = answer sent, don't process; 1 = not a duplicate (all OK) */ +{ + struct query_buffer *buf; + struct query *pq; + buf = &users[userid].qmem; - if (qmem_type[i] == T_UNSET) + /* Check if this is a duplicate query */ + for (size_t p = buf->start; p != buf->end; p = (p + 1) % QMEM_LEN) { + pq = &buf->queries[p]; + if (pq->id != q->id) continue; - if (qmem_type[i] != q->type) - continue; - if (memcmp(qmem_cmc + i * 4, cmc_to_check, 4)) + if (pq->type != q->type) continue; - /* okay, match */ - if (debug >= 1) - fprintf(stderr, "OUT from qmem for %s == duplicate, sending illegal reply\n", q->name); + // FIXME: check for case changes? + if (memcmp(pq->name, q->name, sizeof(q->name))) + continue; + QMEM_DEBUG(1, userid, "OUT for '%s' == duplicate, sending illegal reply\n", q->name); + + // TODO cache answers/respond using cache? (merge with dnscache) write_dns(dns_fd, q, "x", 1, 'T'); + return 0; + } - q->id = 0; /* this query was used */ + if (buf->num_pending >= QMEM_LEN) { + /* this means we have QMEM_LEN *pending* queries; don't overwrite */ + QMEM_DEBUG(2, userid, "full of pending queries. Not appending query with id %d.", q->id); return 1; } - /* here only when no match found */ - return 0; -} - -/* INLINE FUNCTION DEFINITIONS */ -static inline void -save_to_qmem(unsigned char *qmem_cmc, unsigned short *qmem_type, int qmem_len, - int *qmem_lastfilled, unsigned char *cmc_to_add, - unsigned short type_to_add) -/* Remember query to check for duplicates */ -{ - int fill; - - fill = *qmem_lastfilled + 1; - if (fill >= qmem_len) - fill = 0; - - memcpy(qmem_cmc + fill * 4, cmc_to_add, 4); - qmem_type[fill] = type_to_add; - *qmem_lastfilled = fill; -} - -static inline void -save_to_qmem_pingordata(int userid, struct query *q) -{ - /* Our CMC is a bit more than the "official" CMC; we store 4 bytes - just because we can, and because it may prevent some false matches. - For ping, we save the 4 decoded bytes: userid + seq/frag + CMC. - For data, we save the 4 _un_decoded chars in lowercase: seq/frag's - + 1 char CMC; that last char is non-Base32. - */ - - warnx("save_to_qmem_pingordata deprecated! use something else instead!"); - uint8_t cmc[8]; - int i; - - if (q->name[0] == 'P' || q->name[0] == 'p') { - /* Ping packet */ - - size_t cmcsize = sizeof(cmc); - char *cp = strchr(q->name, '.'); - - if (cp == NULL) - return; /* illegal hostname; shouldn't happen */ - - /* We already unpacked in handle_null_request(), but that's - lost now... Note: b32 directly, we want no undotify here! */ - i = b32->decode(cmc, &cmcsize, (uint8_t *)q->name + 1, (cp - q->name) - 1); - - if (i < 4) - return; /* illegal ping; shouldn't happen */ - - /*save_to_qmem(users[userid].qmemping_cmc, - users[userid].qmemping_type, QMEMPING_LEN, - &users[userid].qmemping_lastfilled, - (void *) cmc, q->type);*/ + if (buf->length < QMEM_LEN) { + buf->length++; } else { - /* Data packet, hopefully not illegal */ - if (strlen(q->name) < 5) - return; - - /* We store CMC in lowercase; if routing via multiple parallel - DNS servers, one may do case-switch and another may not, - and we still want to detect duplicates. - Data-header is always base32, so case-swap won't hurt. - */ - for (i = 0; i < 4; i++) - if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') - cmc[i] = q->name[i+1] + ('a' - 'A'); - else - cmc[i] = q->name[i+1]; - - /*save_to_qmem(users[userid].qmemdata_cmc, - users[userid].qmemdata_type, QMEMDATA_LEN, - &users[userid].qmemdata_lastfilled, - (void *) cmc, q->type);*/ + /* will replace already answered query in this spot */ + buf->start = (buf->start + 1) % QMEM_LEN; } + + if (debug >= 5) { + time_t dnstimeout_ms; + dnstimeout_ms = users[userid].dns_timeout.tv_sec * 1000; + dnstimeout_ms += users[userid].dns_timeout.tv_usec / 1000; + QMEM_DEBUG(5, userid, "add query ID %d, timeout %lu ms", q->id, dnstimeout_ms); + } + + /* Copy query into buffer */ + memcpy(&buf->queries[buf->end], q, sizeof(struct query)); + buf->end = (buf->end + 1) % QMEM_LEN; + buf->num_pending += 1; + return 1; } -static inline int -answer_from_qmem_data(int dns_fd, int userid, struct query *q) -/* Quick helper function to keep handle_null_request() clean */ +static void +qmem_answered(int userid) +/* Last query added has been answered */ { - warnx("answer_from_qmem_data deprecated! use something else"); - /*char cmc[4]; - int i; + struct query_buffer *buf; + buf = &users[userid].qmem; + buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; + if (buf->num_pending > 0) + buf->num_pending -= 1; - for (i = 0; i < 4; i++) - if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') - cmc[i] = q->name[i+1] + ('a' - 'A'); - else - cmc[i] = q->name[i+1]; - - return answer_from_qmem(dns_fd, q, users[userid].qmemdata_cmc, - users[userid].qmemdata_type, QMEMDATA_LEN, - (void *) cmc);*/ - return 0; + QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[buf->start_pending].id); } -/* END INLINE FUNCTION DEFINITIONS */ + +static struct query * +qmem_get_next_response(int userid) +/* Gets oldest query to be responded to (for lazy mode) or NULL if none available */ +{ + struct query_buffer *buf; + struct query *q; + buf = &users[userid].qmem; + if (buf->length == 0) + return NULL; + q = &buf->queries[buf->start_pending]; + buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; + if (buf->num_pending > 0) + buf->num_pending -= 1; + QMEM_DEBUG(3, userid, "responding lazily to query ID %d", q->id); + return q; +} + +static struct timeval +qmem_max_wait(int *touser, struct query **sendq) +/* Gets max interval before anything has to be sent to any user */ +{ + struct timeval now, timeout, soonest, tmp; + soonest.tv_sec = 10; + soonest.tv_usec = 0; + int userid, qnum, nextuser = -1; + struct query *q = NULL, *nextq = NULL; + + gettimeofday(&now, NULL); + for (userid = 0; userid < created_users; userid++) { + if (!user_active(userid)) + continue; + qnum = users[userid].qmem.start_pending; + if (users[userid].qmem.num_pending == 0 || !users[userid].lazy) + continue; + for (; qnum != users[userid].qmem.end; qnum = (qnum + 1) % QMEM_LEN) { + // TODO are queries always going to be in time order already? + q = &users[userid].qmem.queries[qnum]; + timeradd(&q->time_recv, &users[userid].dns_timeout, &timeout); + if (!timercmp(&now, &timeout, <)) { + /* if timeout has been reached, must send ASAP */ + soonest.tv_sec = 0; + soonest.tv_usec = 0; + nextuser = userid; + nextq = q; + + /* no need to check other users */ + userid = created_users; + break; + } + timersub(&timeout, &now, &tmp); + if (timercmp(&tmp, &soonest, <)) { + /* time until timeout is smaller */ + soonest = tmp; + nextuser = userid; + nextq = q; + } + } + } + + if (debug >= 5) { + time_t soonest_ms = soonest.tv_sec * 1000; + soonest_ms += soonest.tv_usec / 1000; + if (nextq && nextuser > 0) { + QMEM_DEBUG(5, nextuser, "can wait for %lu ms, will send id %d", soonest_ms, nextq->id); + } else { + if (nextuser < 0) + nextuser = 0; + QMEM_DEBUG(5, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); + } + } + + if (sendq) + *sendq = nextq; + if (touser) + *touser = nextuser; + + return soonest; +} +#endif /* QMEM_LEN */ #ifdef DNSCACHE_LEN @@ -373,75 +422,6 @@ forward_query(int bind_fd, struct query *q) } } -static int -send_frag_or_dataless(int dns_fd, int userid, struct query *q, int ping) -/* Sends current fragment to user, or a ping if no data available. - Does not update anything, except: - - discards q always (query is used) - - forgets entire users[userid].outpacket if it was sent in one go, - and then tries to get new packet from outpacket-queue - Returns: 1 = can call us again immediately, new packet from queue; - 0 = don't call us again for now. -*/ -{ - static uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; - size_t datalen, headerlen; - fragment *f; - struct frag_buffer *out, *in; - - in = users[userid].incoming; - out = users[userid].outgoing; - - f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); - if (!f) { - /* No data, may as well send data/ping header (with extra info) */ - ping = 1; - datalen = 0; - pkt[0] = 0; /* Pings don't need seq IDs unless they have data */ - pkt[1] = users[userid].next_upstream_ack & 0xFF; - pkt[2] = (1 << 5) | ((users[userid].next_upstream_ack < 0 ? 0 : 1) << 3); - /* TODO: resend ACKs in pings? */ - users[userid].next_upstream_ack = -1; - } else { - datalen = f->len; - pkt[0] = f->seqID & 0xFF; - pkt[1] = f->ack_other & 0xFF; - pkt[2] = ((f->compressed & 1) << 4) | ((f->ack_other < 0 ? 0 : 1) << 3) | - (f->is_nack << 2) | (f->start << 1) | f->end; - headerlen = DOWNSTREAM_HDR; - } - - /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ - - if (ping) { /* TODO: pings with downstream data */ - pkt[3] = out->windowsize & 0xFF; - pkt[4] = in->windowsize & 0xFF; - pkt[5] = out->start_seq_id & 0xFF; - pkt[6] = in->start_seq_id & 0xFF; - headerlen = DOWNSTREAM_PING_HDR; - } - if (datalen + headerlen > sizeof(pkt)) { - warnx("send_frag_or_dataless: fragment too large to send! (%lu)", datalen); - return 0; - } - if (f) memcpy(pkt + headerlen, f->data, datalen); - write_dns(dns_fd, q, (char *)pkt, datalen + headerlen, users[userid].downenc); - - /* TODO: reply to any duplicates (q.id2 etc) */ - -// save_to_qmem_pingordata(userid, q); - -#ifdef DNSCACHE_LEN - save_to_dnscache(userid, q, (char *)pkt, datalen + 2); -#endif - /* this query has been used */ - q->id = 0; - window_tick(out); - - /* call again if we have more things to send */ - return users[userid].outgoing->numitems > 0; -} - static void send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, struct query *q) { @@ -459,7 +439,6 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s break; } - // TODO: use htonl for compatibility with big-endian systems out[4] = ((payload >> 24) & 0xff); out[5] = ((payload >> 16) & 0xff); out[6] = ((payload >> 8) & 0xff); @@ -469,6 +448,123 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s write_dns(fd, q, out, sizeof(out), users[userid].downenc); } +void +send_data_or_ping(int tun_fd, struct dnsfd *dns_fds, int userid, struct query *q, + int ping, int respond_now, int immediate) +/* 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. + respond_now: 1=must answer query now, 0=leave in qmem if no data available + immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem + Updates next_upstream_ack if new ACK needed. */ +{ + uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; + size_t datalen, headerlen; + fragment *f; + int compressed = 0; + struct frag_buffer *out, *in; + + in = users[userid].incoming; + out = users[userid].outgoing; + + datalen = window_reassemble_data(in, pkt, sizeof(pkt), &compressed); + window_tick(in); + window_tick(out); + + /* Update time info */ + users[userid].last_pkt = time(NULL); + + if (datalen > 0) { + /* Data reassembled successfully + cleared out of buffer */ + handle_full_packet(tun_fd, dns_fds, userid, pkt, datalen, compressed); + } + + f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); + + /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ + if (!f) { + if (users[userid].lazy && !respond_now) { + /* No data and lazy mode: leave this query to wait in qmem */ + return; + } + /* No data, may as well send data/ping header (with extra info) */ + ping = 1; + datalen = 0; + pkt[0] = 0; /* Pings don't need seq IDs unless they have data */ + pkt[1] = users[userid].next_upstream_ack & 0xFF; + pkt[2] = (users[userid].next_upstream_ack < 0 ? 0 : 1) << 3; + users[userid].next_upstream_ack = -1; + } else { + datalen = f->len; + pkt[0] = f->seqID & 0xFF; + pkt[1] = f->ack_other & 0xFF; + pkt[2] = ((f->ack_other < 0 ? 0 : 1) << 3) | ((f->compressed & 1) << 2) | (f->start << 1) | f->end; + headerlen = DOWNSTREAM_HDR; + } + + /* If this is being responded to immediately (ie. not from qmem) */ + pkt[2] |= (immediate & 1) << 5; + + if (ping) { + /* set ping flag and build extra header */ + pkt[2] |= 1 << 4; + pkt[3] = out->windowsize & 0xFF; + pkt[4] = in->windowsize & 0xFF; + pkt[5] = out->start_seq_id & 0xFF; + pkt[6] = in->start_seq_id & 0xFF; + headerlen = DOWNSTREAM_PING_HDR; + } + if (datalen + headerlen > sizeof(pkt)) { + /* Should never happen, or at least user should be warned about + * fragsize > MAX_FRAGLEN earlier on */ + warnx("send_frag_or_dataless: fragment too large to send! (%lu)", datalen); + return; + } + if (f) + memcpy(pkt + headerlen, f->data, datalen); + + write_dns(get_dns_fd(dns_fds, &q->from), q, (char *)pkt, + datalen + headerlen, users[userid].downenc); + +#ifdef DNSCACHE_LEN + save_to_dnscache(userid, q, (char *)pkt, datalen + headerlen); +#endif +#ifdef QMEM_LEN + qmem_answered(userid); +#endif + /* this query has been used */ + q->id = 0; + window_tick(out); +} + +static int +user_send_data(int userid, int tun_fd, struct dnsfd *dns_fds, uint8_t *data, size_t datalen, int compressed) +/* Appends data to a user's outgoing queue and sends it if queries are waiting */ +{ + struct query *q; + if (users[userid].conn == CONN_DNS_NULL) { + + window_add_outgoing_data(users[userid].outgoing, data, datalen, compressed); + + /* Start sending immediately if queries are waiting */ +#ifdef QMEM_LEN + while (users[userid].qmem.num_pending > 0 && + window_sending(users[userid].outgoing)) { + q = qmem_get_next_response(userid); + if (q == NULL) + break; + send_data_or_ping(tun_fd, dns_fds, userid, q, 0, 1, 0); + } +#endif + + return datalen; + } else { /* CONN_RAW_UDP */ + int dns_fd = get_dns_fd(dns_fds, &users[userid].host); + send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA, + &users[userid].host, users[userid].hostlen); + return datalen; + } +} + static int tunnel_bind(int bind_fd, struct dnsfd *dns_fds) { @@ -535,21 +631,17 @@ tunnel_tun(int tun_fd, struct dnsfd *dns_fds) if (userid < 0) return 0; - outlen = sizeof(out); - compress2(out, &outlen, in, read, 9); + if (debug >= 2) + fprintf(stderr, "IN: %d byte pkt from tun to user %d; compression %d\n", + read, userid, users[userid].down_compression); - if (users[userid].conn == CONN_DNS_NULL) { + if (users[userid].down_compression) { + outlen = sizeof(out); + compress2(out, &outlen, in, read, 9); - window_add_outgoing_data(users[userid].outgoing, out, outlen, 1); - - /* TODO: Start sending immediately if query is waiting - * Need to get incoming query handling done first. */ - - return outlen; - } else { /* CONN_RAW_UDP */ - int dns_fd = get_dns_fd(dns_fds, &users[userid].q.from); - send_raw(dns_fd, out, outlen, userid, RAW_HDR_CMD_DATA, &users[userid].q); - return outlen; + return user_send_data(userid, tun_fd, dns_fds, out, outlen, 1); + } else { + return user_send_data(userid, tun_fd, dns_fds, in, read, 0); } } @@ -636,12 +728,21 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) fd_set fds; int i; int userid; + struct query *answer_now = NULL; time_t last_action = time(NULL); + if (debug >= 4) + window_debug = debug - 3; + while (running) { int maxfd; - tv.tv_sec = 5; /* TODO: adjust time based on query timeouts (lazy mode) */ - tv.tv_usec = 0; + /* TODO: adjust time based on query timeouts (lazy mode) */ + tv = qmem_max_wait(&userid, &answer_now); + + if (tv.tv_sec == 0 && tv.tv_usec == 0) { + /* We need to respond to an old query immediately; do so now. */ + send_data_or_ping(tun_fd, dns_fds, userid, answer_now, 0, 1, 0); + } FD_ZERO(&fds); maxfd = 0; @@ -662,7 +763,7 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) } /* Don't read from tun if no users can accept data anyway; - tun queue/TCP buffers are larger than our outpacket-queues */ + tun queue/TCP buffers are larger than our outgoing queues */ if(!all_users_waiting_to_send()) { FD_SET(tun_fd, &fds); maxfd = MAX(tun_fd, maxfd); @@ -680,12 +781,12 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) if (max_idle_time) { /* only trigger the check if that's worth ( ie, no need to loop over if there is something to send */ - if (last_action + max_idle_time < time(NULL)) { + if (difftime(time(NULL), last_action) > max_idle_time) { for (userid = 0; userid < created_users; userid++) { - last_action = ( users[userid].last_pkt > last_action ) ? users[userid].last_pkt : last_action; + last_action = (users[userid].last_pkt > last_action) ? users[userid].last_pkt : last_action; } - if (last_action + max_idle_time < time(NULL)) { - fprintf(stderr, "Idling since too long, shutting down...\n"); + if (difftime(time(NULL), last_action) > max_idle_time) { + fprintf(stderr, "Server idle for too long, shutting down...\n"); running = 0; } } @@ -710,35 +811,49 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) } void -handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len) +handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len, int compressed) { - size_t outlen; - uint8_t out[64*1024]; + size_t rawlen; + uint8_t out[64*1024], *rawdata; struct ip *hdr; int touser; - int ret; // TODO: optional upstream compression flag - outlen = sizeof(out); - if ((ret = uncompress(out, &outlen, data, len)) == Z_OK) { + int ret; + + /* Check if data needs to be uncompressed */ + if (compressed) { + rawlen = sizeof(out); + ret = uncompress(out, &rawlen, data, len); + rawdata = out; + } else { + rawlen = len; + rawdata = data; + ret = Z_OK; + } + + if (ret == Z_OK) { hdr = (struct ip*) (out + 4); touser = find_user_by_ip(hdr->ip_dst.s_addr); if (debug >= 3) fprintf(stderr, "FULL PKT: %lu bytes from user %d (touser %d)\n", len, userid, touser); if (touser == -1) { /* send the uncompressed packet to tun device */ - write_tun(tun_fd, out, outlen); + write_tun(tun_fd, rawdata, rawlen); } else { - /* send the compressed (!) packet to other client */ - if (users[touser].conn == CONN_DNS_NULL) { - window_add_outgoing_data(users[touser].outgoing, data, len, 1); - /* TODO: send immediately if query waiting */ - } else{ /* CONN_RAW_UDP */ - int dns_fd = get_dns_fd(dns_fds, &users[touser].q.from); - send_raw(dns_fd, data, len, touser, RAW_HDR_CMD_DATA, &users[touser].q); + /* use compressed or uncompressed packet to match user settings */ + if (users[touser].down_compression) { + if (!compressed) { + len = sizeof(out); + compress2(out, &len, rawdata, rawlen, 9); + data = out; + } + user_send_data(touser, tun_fd, dns_fds, data, len, 1); + } else { + user_send_data(touser, tun_fd, dns_fds, rawdata, rawlen, 0); } } } else { - if (debug >= 1) - fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret); + if (debug >= 2) + fprintf(stderr, "Discarded upstream data from user %d, uncompress() result: %d\n", userid, ret); } } @@ -761,9 +876,8 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri /* User sends hash of seed + 1 */ login_calculate(myhash, 16, password, users[userid].seed + 1); if (memcmp(packet, myhash, 16) == 0) { - /* Update query and time info for user */ + /* Update time info for user */ users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); /* Store remote IP number */ memcpy(&(users[userid].host), &(q->from), q->fromlen); @@ -772,7 +886,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri /* Correct hash, reply with hash of seed - 1 */ user_set_conn_type(userid, CONN_RAW_UDP); login_calculate(myhash, 16, password, users[userid].seed - 1); - send_raw(fd, (uint8_t *)myhash, 16, userid, RAW_HDR_CMD_LOGIN, q); + send_raw(fd, (uint8_t *)myhash, 16, userid, RAW_HDR_CMD_LOGIN, &q->from, q->fromlen); users[userid].authenticated_raw = 1; } @@ -786,9 +900,8 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_ } if (!users[userid].authenticated_raw) return; - /* Update query and time info for user */ + /* Update time info for user */ users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); /* copy to packet buffer, update length TODO fix the raw UDP protocol */ @@ -796,7 +909,7 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_ fprintf(stderr, "IN pkt raw, total %lu, from user %d\n", len, userid); } - handle_full_packet(tun_fd, dns_fds, userid, packet, len); + handle_full_packet(tun_fd, dns_fds, userid, packet, len, 1); } static void @@ -807,16 +920,15 @@ handle_raw_ping(struct query *q, int dns_fd, int userid) } if (!users[userid].authenticated_raw) return; - /* Update query and time info for user */ + /* Update time info for user */ users[userid].last_pkt = time(NULL); - memcpy(&(users[userid].q), q, sizeof(struct query)); if (debug >= 1) { fprintf(stderr, "IN ping raw, from user %d\n", userid); } /* Send ping reply */ - send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, q); + send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, &q->from, q->fromlen); } static int @@ -824,12 +936,16 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsf { int raw_user; + warnx("raw_decode len %lu", len); /* minimum length */ if (len < RAW_HDR_LEN) return 0; /* should start with header */ if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0; raw_user = RAW_HDR_GET_USR(packet); + if (debug >= 3) + warnx("TX-raw: client %s, user %d, raw command '%c' length %lu", + format_addr(&q->from, q->fromlen), raw_user, RAW_HDR_GET_CMD(packet), len); switch (RAW_HDR_GET_CMD(packet)) { case RAW_HDR_CMD_LOGIN: /* Login challenge */ @@ -883,8 +999,9 @@ read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) #endif /* !WINDOWS32 */ if (r > 0) { - memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen); + memcpy(&q->from, &from, addrlen); q->fromlen = addrlen; + gettimeofday(&q->time_recv, NULL); /* TODO do not handle raw packets here! */ if (raw_decode(packet, r, q, fd, dns_fds, tun_fd)) { @@ -936,70 +1053,34 @@ static size_t write_dns_nameenc(uint8_t *buf, size_t buflen, uint8_t *data, size_t datalen, char downenc) /* Returns #bytes of data that were encoded */ { - static int td1 = 0; - static int td2 = 0; - size_t space; - uint8_t *b; + static int td_cmc; + char td[3]; + struct encoder *enc; - /* Make a rotating topdomain to prevent filtering */ - td1+=3; - td2+=7; - if (td1>=26) td1-=26; - if (td2>=25) td2-=25; + /* Make a rotating topdomain to prevent filtering, ie 10-bit CMC */ + td_cmc ++; + td_cmc &= 0x3FF; - /* encode data,datalen to CNAME/MX answer - (adapted from build_hostname() in encoding.c) - */ - - space = MIN(0xFF, buflen) - 4 - 2; - /* -1 encoding type, -3 ".xy", -2 for safety */ - - memset(buf, 0, buflen); + td[0] = b32_5to8(td_cmc & 0x1F); + td[1] = b32_5to8((td_cmc >> 5) & 0x1F); + td[2] = 0; + /* encode data,datalen to CNAME/MX answer */ if (downenc == 'S') { buf[0] = 'i'; - if (!b64->places_dots()) - space -= (space / 57); /* space for dots */ - b64->encode(buf+1, &space, data, datalen); - if (!b64->places_dots()) - inline_dotify(buf, buflen); + enc = b64; } else if (downenc == 'U') { buf[0] = 'j'; - if (!b64u->places_dots()) - space -= (space / 57); /* space for dots */ - b64u->encode(buf+1, &space, data, datalen); - if (!b64u->places_dots()) - inline_dotify(buf, buflen); + enc = b64u; } else if (downenc == 'V') { buf[0] = 'k'; - if (!b128->places_dots()) - space -= (space / 57); /* space for dots */ - b128->encode(buf+1, &space, data, datalen); - if (!b128->places_dots()) - inline_dotify(buf, buflen); + enc = b128; } else { buf[0] = 'h'; - if (!b32->places_dots()) - space -= (space / 57); /* space for dots */ - b32->encode(buf+1, &space, data, datalen); - if (!b32->places_dots()) - inline_dotify(buf, buflen); + enc = b32; } - /* Add dot (if it wasn't there already) and topdomain */ - b = buf; - b += strlen((char *)buf) - 1; - if (*b != '.') - *++b = '.'; - b++; - - *b = 'a' + td1; - b++; - *b = 'a' + td2; - b++; - *b = '\0'; - - return space; + return build_hostname(buf, buflen, data, datalen, td, enc, 0xFF, 1); } void @@ -1008,6 +1089,8 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) char buf[64*1024]; int len = 0; + // TODO: respond to duplicate queries here + handling qmem stuff + if (q->type == T_CNAME || q->type == T_A) { char cnamebuf[1024]; /* max 255 */ @@ -1087,31 +1170,6 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); } -void -send_data_or_ping_response(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int userid, struct query *q, int ping) { - uint8_t unpacked[64*1024]; - size_t read; - - /* if waiting for an ACK to be sent back upstream (on incoming buffer) */ - if (users[userid].next_upstream_ack < 0) { - users[userid].next_upstream_ack = window_get_next_ack(users[userid].incoming); - } - window_tick(users[userid].outgoing); - - read = window_reassemble_data(users[userid].incoming, unpacked, sizeof(unpacked), NULL); - window_tick(users[userid].incoming); - - if (read > 0) { /* Data reassembled successfully + cleared out of buffer */ - handle_full_packet(tun_fd, dns_fds, userid, unpacked, read); - } - - send_frag_or_dataless(dns_fd, userid, q, ping); - - /* Save new query and time info */ - memcpy(&(users[userid].q), q, sizeof(struct query)); - users[userid].last_pkt = time(NULL); -} - void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) /* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ @@ -1138,17 +1196,13 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } if(in[0] == 'V' || in[0] == 'v') { /* Version request */ - int version = 0; + uint32_t version; - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); /* Version greeting, compare and send ack/nak */ if (read > 4) { - /* Received V + 32bits version */ - // TODO htonl/ntohl for portability - version = (((unpacked[0] & 0xff) << 24) | - ((unpacked[1] & 0xff) << 16) | - ((unpacked[2] & 0xff) << 8) | - ((unpacked[3] & 0xff))); + /* Received V + 32bits version (network byte order) */ + version = ntohl(*(uint32_t *) unpacked); } if (version == PROTOCOL_VERSION) { @@ -1161,38 +1215,38 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query memcpy(&(u->host), &(q->from), q->fromlen); u->hostlen = q->fromlen; - memcpy(&(u->q), q, sizeof(struct query)); u->encoder = get_base32_encoder(); - u->downenc = 'T'; - u->downenc_bits = 5; + + if (q->type == T_NULL || q->type == T_PRIVATE) { + u->downenc = 'R'; + u->downenc_bits = 8; + } else { + u->downenc = 'T'; + u->downenc_bits = 5; + } + u->down_compression = 1; send_version_response(dns_fd, VERSION_ACK, u->seed, userid, q); - syslog(LOG_INFO, "accepted version for user #%d from %s", + syslog(LOG_INFO, "Accepted version for user #%d from %s", userid, format_addr(&q->from, q->fromlen)); - u->q.id = 0; - u->q.id2 = 0; u->fragsize = 100; /* very safe */ u->conn = CONN_DNS_NULL; u->lazy = 0; // TODO: client specified window size - u->incoming = window_buffer_init(INFRAGBUF_LEN, 10, MAX_FRAGSIZE, WINDOW_RECVING); - u->outgoing = window_buffer_init(OUTFRAGBUF_LEN, 10, - u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR, WINDOW_SENDING); + u->outgoing->maxfraglen = u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR; + window_buffer_clear(u->outgoing); + window_buffer_clear(u->incoming); u->next_upstream_ack = -1; +#ifdef QMEM_LEN + qmem_init(userid); +#endif #ifdef DNSCACHE_LEN - { - for (i = 0; i < DNSCACHE_LEN; i++) { - u->dnscache_q[i].id = 0; - u->dnscache_answerlen[i] = 0; - } + for (i = 0; i < DNSCACHE_LEN; i++) { + u->dnscache_q[i].id = 0; + u->dnscache_answerlen[i] = 0; } u->dnscache_lastfilled = 0; #endif - /*for (i = 0; i < QMEMPING_LEN; i++) - u->qmemping_type[i] = T_UNSET; - u->qmemping_lastfilled = 0; - for (i = 0; i < QMEMDATA_LEN; i++) - u->qmemdata_type[i] = T_UNSET; - u->qmemdata_lastfilled = 0;*/ + if (debug >= 1) fprintf(stderr, "User %d connected with correct version from %s.\n", userid, format_addr(&q->from, q->fromlen)); @@ -1209,7 +1263,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } return; } else if (in[0] == 'L' || in[0] == 'l') { /* Login request */ - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); if (read < 17) { write_dns(dns_fd, q, "BADLEN", 6, 'T'); return; @@ -1309,22 +1363,22 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query switch (codec) { case 5: /* 5 bits per byte = base32 */ - enc = get_base32_encoder(); + enc = b32; user_switch_codec(userid, enc); write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); break; case 6: /* 6 bits per byte = base64 */ - enc = get_base64_encoder(); + enc = b64; user_switch_codec(userid, enc); write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); break; case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ - enc = get_base64u_encoder(); + enc = b64u; user_switch_codec(userid, enc); write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); break; case 7: /* 7 bits per byte = base128 */ - enc = get_base128_encoder(); + enc = b128; user_switch_codec(userid, enc); write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); break; @@ -1347,51 +1401,59 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } int bits = 0; - switch (in[2]) { + switch (toupper(in[2])) { case 'T': - case 't': users[userid].downenc = 'T'; write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); bits = 5; break; case 'S': - case 's': users[userid].downenc = 'S'; write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); bits = 6; break; case 'U': - case 'u': users[userid].downenc = 'U'; write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); bits = 6; break; case 'V': - case 'v': users[userid].downenc = 'V'; write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); bits = 7; break; case 'R': - case 'r': users[userid].downenc = 'R'; write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); bits = 8; break; case 'L': - case 'l': users[userid].lazy = 1; write_dns(dns_fd, q, "Lazy", 4, users[userid].downenc); break; case 'I': - case 'i': users[userid].lazy = 0; write_dns(dns_fd, q, "Immediate", 9, users[userid].downenc); break; + case 'C': + users[userid].down_compression = 1; + write_dns(dns_fd, q, "Enabled", 7, users[userid].downenc); + break; + case 'D': + users[userid].down_compression = 0; + write_dns(dns_fd, q, "Disabled", 8, users[userid].downenc); + break; default: write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); break; } + /* Automatically switch to raw encoding if PRIVATE or NULL request */ + if (q->type == T_NULL || q->type == T_PRIVATE) { + users[userid].downenc = 'R'; + bits = 8; + if (debug >= 3) + warnx("Assuming raw data encoding due to NULL/PRIVATE requests for user %d.", userid); + } if (bits) { int f = users[userid].fragsize; users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; @@ -1423,45 +1485,19 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; } - switch (in[1]) { + switch (toupper(in[1])) { case 'T': - case 't': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'T'); - return; - } - break; case 'S': - case 's': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'S'); - return; - } - break; case 'U': - case 'u': - if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'U'); - return; - } - break; case 'V': - case 'v': if (q->type == T_TXT || q->type == T_SRV || q->type == T_MX || q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, 'V'); + write_dns(dns_fd, q, datap, datalen, toupper(in[1])); return; } break; case 'R': - case 'r': if (q->type == T_NULL || q->type == T_TXT) { write_dns(dns_fd, q, datap, datalen, 'R'); return; @@ -1482,13 +1518,15 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } /* Downstream fragsize probe packet */ - userid = (b32_8to5(in[1]) >> 1) & 15; + read = unpack_data(unpacked, sizeof(unpacked), in + 1, 5, b32); + + userid = unpacked[0]; if (check_authenticated_user_and_ip(userid, q) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } - req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31); + req_frag_size = ntohs(*(uint16_t *) (unpacked + 1)); if (debug >= 3) { fprintf(stderr, "Got downstream fragsize probe from user %d, required fragsize %d\n", userid, req_frag_size); } @@ -1510,10 +1548,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); } return; - } else if(in[0] == 'N' || in[0] == 'n') { /* Downstream fragsize (NS.topdomain A-type reply) */ + } else if(in[0] == 'N' || in[0] == 'n') { /* Downstream fragsize */ int max_frag_size; - read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); if (read < 3) { write_dns(dns_fd, q, "BADLEN", 6, 'T'); @@ -1527,7 +1565,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; /* illegal id */ } - max_frag_size = ((unpacked[1] & 0xff) << 8) | (unpacked[2] & 0xff); + max_frag_size = ntohs(*(uint16_t *)(unpacked + 1)); if (max_frag_size < 2) { write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); } else { @@ -1537,20 +1575,23 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query write_dns(dns_fd, q, (char *) unpacked + 1, 2, users[userid].downenc); if (debug >= 1) - warnx("Setting max downstream data length to %u bytes for user %d; bits %d (%c)", + warnx("Setting max downstream data length to %u bytes for user %d; %d bits (%c)", users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); } return; } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ - int dn_seq, up_seq, dn_wins, up_wins, dn_ack; + int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; int respond; + unsigned timeout_ms; + struct timeval timeout; /* We can't handle id=0, that's "no packet" to us. So drop request completely. Note that DNS servers rewrite the id. We'll drop 1 in 64k times. If DNS server retransmits with different id, then all okay. Else client won't retransmit, and we'll just keep the - previous ping in cache, no problem either. */ + previous ping in cache, no problem either. + TODO don't use ID=0 to check if query */ if (q->id == 0) return; @@ -1571,47 +1612,54 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query /* Check if cached */ if (answer_from_dnscache(dns_fd, userid, q)) return; +#endif // XXX hmm these look very similar... +#ifdef QMEM_LEN + /* Check if cached */ + if (!qmem_append(dns_fd, userid, q)) + return; #endif - /* TODO: incoming query handling for lazy mode */ - /* Check if duplicate (and not in full dnscache any more) * / - if (answer_from_qmem(dns_fd, q, users[userid].qmemping_cmc, - users[userid].qmemping_type, QMEMPING_LEN, - (void *) unpacked)) - return; */ - dn_ack = ((unpacked[6] >> 2) & 1) ? unpacked[1] : -1; - up_wins = unpacked[2]; - dn_wins = unpacked[3]; - dn_seq = unpacked[4]; - up_seq = unpacked[5]; - respond = unpacked[6] & 1; + dn_ack = ((unpacked[8] >> 2) & 1) ? unpacked[1] : -1; + up_winsize = unpacked[2]; + dn_winsize = unpacked[3]; + up_seq = unpacked[4]; + dn_seq = unpacked[5]; + + timeout_ms = ntohs(*(uint16_t *) (unpacked + 6)); + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms - timeout.tv_sec * 1000) * 1000; + + respond = unpacked[8] & 1; + + if ((unpacked[8] >> 3) & 1) { + /* update user's query timeout */ + users[userid].dns_timeout = timeout; + } - /* TODO: Use ping to re-sync window buffer */ if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d, down %d/%d, up %d/%d, ACK %d\n", userid, dn_seq, dn_wins, up_seq, up_wins, dn_ack); + fprintf(stderr, "PING pkt from user %d, down %d/%d, up %d/%d, ACK %d, set timeout %u ms (flags %02X)\n", + userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, timeout_ms, unpacked[8]); } window_ack(users[userid].outgoing, dn_ack); - send_data_or_ping_response(tun_fd, dns_fd, dns_fds, userid, q, respond); + /* Send resonse; q can be left in qmem if no data (q is still fresh) */ + send_data_or_ping(tun_fd, dns_fds, userid, q, respond, 0, 1); - } else if((in[0] >= '0' && in[0] <= '9') /* Upstream data packet */ - || (in[0] >= 'a' && in[0] <= 'f') - || (in[0] >= 'A' && in[0] <= 'F')) { - int code = -1; + } else if (isxdigit(in[0])) { /* Upstream data packet */ + int code = 0; static fragment f; size_t len; /* Need 6 char header + >=1 char data */ - if (domain_len < 7) + if (domain_len < UPSTREAM_HDR + 1) return; /* We can't handle id=0, that's "no packet" to us. So drop request completely. Note that DNS servers rewrite the id. We'll drop 1 in 64k times. If DNS server retransmits with different id, then all okay. - Else client doesn't get our ack, and will retransmit in - 1 second. */ + Else client doesn't get our ack, and will retransmit in 1 second. */ if (q->id == 0) { warnx("Query with ID 0!"); return; @@ -1625,10 +1673,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query code = in[0] - 'A' + 10; userid = code; - /* Check user and sending ip number */ + /* Check user and sending IP address */ if (check_authenticated_user_and_ip(userid, q) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ + return; /* illegal IP */ } #ifdef DNSCACHE_LEN @@ -1636,41 +1684,42 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (answer_from_dnscache(dns_fd, userid, q)) return; #endif - /* TODO: incoming query buffer/handling for lazy mode */ - - /* TODO: Check if duplicate of waiting queries (ping and data) */ - +#ifdef QMEM_LEN + /* Check if cached */ + if (!qmem_append(dns_fd, userid, q)) + return; +#endif /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ - /* First byte (after userid) = CMC (ignored) */ -// f.seqID = (b32_8to5(in[2]) << 2) | (b32_8to5(in[3]) >> 2); -// f.ack_other = (b32_8to5(in[5]) & 8) ? ((b32_8to5(in[3]) & 3) << 6) -// | (b32_8to5(in[4]) << 1) | ((b32_8to5(in[5]) >> 4) & 1) : -1; -// f.is_nack = (b32_8to5(in[5]) >> 2) & 1; -// f.start = (b32_8to5(in[5]) >> 1) & 1; -// f.end = b32_8to5(in[5]) & 1; + /* First byte (after userid) = CMC (ignored); skip 2 bytes */ len = sizeof(unpacked); read = b32->decode(unpacked, &len, in + 2, 5); + f.seqID = unpacked[0]; unpacked[2] >>= 4; /* Lower 4 bits are unused */ f.ack_other = ((unpacked[2] >> 3) & 1) ? unpacked[1] : -1; - f.is_nack = (unpacked[2] >> 2) & 1; + f.compressed = (unpacked[2] >> 2) & 1; f.start = (unpacked[2] >> 1) & 1; f.end = unpacked[2] & 1; - /* Decode remainder of data with user encoding */ - read = unpack_data(unpacked, sizeof(unpacked), in + UPSTREAM_HDR, - domain_len - UPSTREAM_HDR, users[userid].encoder); - if (debug >= 4) warnx("++++ UNPACKED %d bytes into %lu using %s with header len %d", - domain_len, read, users[userid].encoder->name, UPSTREAM_HDR); + /* Decode remainder of data with user encoding into fragment */ + f.len = unpack_data(f.data, MAX_FRAGSIZE, in + UPSTREAM_HDR, + domain_len - UPSTREAM_HDR, users[userid].encoder); - f.len = MIN(read, MAX_FRAGSIZE); - memcpy(f.data, unpacked, f.len); + if (debug >= 3) + warnx("frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", + f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end); - window_process_incoming_fragment(users[userid].incoming, &f); + /* if waiting for an ACK to be sent back upstream (on incoming buffer) */ + if (users[userid].next_upstream_ack >= 0) { + /* Shouldn't normally happen; will always be reset after sending a packet. */ + warnx("[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); + } + users[userid].next_upstream_ack = window_process_incoming_fragment(users[userid].incoming, &f); window_ack(users[userid].outgoing, f.ack_other); - send_data_or_ping_response(tun_fd, dns_fd, dns_fds, userid, q, 0); + /* Respond/ACK data packet immediately; query is fresh */ + send_data_or_ping(tun_fd, dns_fds, userid, q, 0, 1, 1); } } diff --git a/src/server.h b/src/server.h index 7969576..193df54 100644 --- a/src/server.h +++ b/src/server.h @@ -41,20 +41,18 @@ #define DNSCACHE_LEN 10 /* Undefine to disable. Should be less than 18; also see comments in iodined.c */ -#define QMEMPING_LEN 30 -/* Max advisable: 64k/2 = 32000. Total mem usage: QMEMPING_LEN * USERS * 6 bytes */ - -#define QMEMDATA_LEN 15 -/* Max advisable: 36/2 = 18. Total mem usage: QMEMDATA_LEN * USERS * 6 bytes */ +/* Max number of incoming queries to hold at one time (recommended to be same as windowsize) + * Memory = USERS * (sizeof(struct query_buffer) + sizeof(query) * QMEM_LEN) */ +#define QMEM_LEN 24 /* Number of fragments in outgoing buffer. - * Mem usage: USERS * (MAX_FRAGLEN * OUTFRAGBUF_LEN + sizeof(struct window_buffer) */ + * Mem usage: USERS * (MAX_FRAGLEN * OUTFRAGBUF_LEN + sizeof(struct window_buffer)) */ #define OUTFRAGBUF_LEN 64 -/* Number of fragments in incoming buffer +/* Number of fragments in incoming buffer; must be at least windowsize * 2 * Minimum recommended = ((max packet size or MTU) / (max up fragsize)) * 2 * ie. (1200 / 100) * 2 = 24 */ -#define INFRAGBUF_LEN 32 +#define INFRAGBUF_LEN 64 #define PASSWORD_ENV_VAR "IODINED_PASS" @@ -89,6 +87,15 @@ typedef enum { VERSION_FULL } version_ack_t; +struct query_buffer { + struct query queries[QMEM_LEN]; + size_t start_pending; /* index of first "pending" query (ie. no response yet) */ + size_t start; /* index of first stored/pending query */ + size_t end; /* index of space after last stored/pending query */ + size_t length; /* number of stored queries */ + size_t num_pending; /* number of pending queries */ +}; + extern char *topdomain; extern char password[33]; extern struct encoder *b32; @@ -112,7 +119,7 @@ int server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_t int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); void write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc); -void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len); +void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len, int); void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, 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); From 070f49167be818617cb5ede729da0fb56bb056d4 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 13:10:37 +0800 Subject: [PATCH 032/113] Updated protocol documentation --- doc/proto_00000800.txt | 74 +++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index 5a7329d..c6a56f8 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -124,25 +124,28 @@ Server sends: s or S: Downstream encoding Base64, for TXT/CNAME/A/MX u or U: Downstream encoding Base64u, for TXT/CNAME/A/MX v or V: Downstream encoding Base128, for TXT/CNAME/A/MX - r or R: Downstream encoding Raw, for PRIVATE/TXT/NULL (default for + r or R: Downstream encoding Raw, for PRIVATE/TXT/NULL (assumed for PRIVATE and NULL) If codec unsupported for request type, server will use Base32; note that server will answer any mix of request types that a client sends. Server may disregard this option; client must always use the downstream encoding type indicated in every downstream DNS packet. - l or L: Lazy mode, server will keep one request unanswered until the - next one comes in. Applies only to data transfer; handshake is always - answered immediately. + c or C: Downstream compression enabled (compressed before encoding) + d or D: Downstream compression disabled + l or L: Lazy mode, server will keep a number of requests waiting until data + becomes available to send downstream or the requests time out. The + timeout value for requests is controlled by the client. + Applies only to data transfer; handshake is always answered immediately. i or I: Immediate (non-lazy) mode, server will answer all requests - (nearly) immediately. + (nearly) immediately. Probe downstream fragment size: Client sends: First byte r or R - 15 bits coded as 3 Base32 chars: UUUUF FFFFF FFFFF - meaning 4 bits userid, 11 bits fragment size - Then follows a long random query which contents does not matter + Second byte userid char + 2 bytes big-endian fragsize encoded as 4 bytes base32 + Then follows a long random query which contents does not matter. Server sends: Requested number of bytes as a response. The first two bytes contain the requested length. The third byte is 107 (0x6B). The fourth byte @@ -155,32 +158,33 @@ Client sends: First byte n or N Rest encoded with base32: 1 byte userid - 2 bytes new downstream fragment size + 2 bytes new downstream fragment size (big-endian) CMC Server sends: 2 bytes new downstream fragment size. After this all downstream payloads will be max (fragsize + 2) bytes long. BADFRAG if not accepted. -Upstream data header: - 76543 21076 54321076 54321076 5432 - +!----+!----+!----!--+--!----!+----+ - |0UUUU|UDCMC|SSSSSSSS|DDDDDDDD|ANFL| - +-----+-----+--------+--------+----+ -Downstream data header: |=> only if P(ing) bit set - 76543210 76543210 76543210 76543210 76543210 76543210 76543210 - +--------+--------+--------+--------+--------+--------+--------+ - |DDDDDDDD|SSSSSSSS|00PCANFL|ZZZZZZZZ|WWWWWWWW|XXXXXXXX|YYYYYYYY| - +--------+--------+--------+--------+--------+--------+--------+ +Upstream data header: + 76543 21076 54321076 54321076 5432 + +!----+!----+!----!--+--!----!+----+ + |0UUUU|UDCMC|SSSSSSSS|DDDDDDDD|ACFL| + +-----+-----+--------+--------+----+ + +Downstream data header: |=> only if P(ing) bit set | + 76543210 76543210 76543210 76543210 76543210 76543210 76543210 + +--------+--------+--------+--------+--------+--------+--------+ + |DDDDDDDD|SSSSSSSS|00IPACFL|ZZZZZZZZ|WWWWWWWW|XXXXXXXX|YYYYYYYY| + +--------+--------+--------+--------+--------+--------+--------+ UUUU = Userid L = Last fragment flag A = ACK flag -N = NACK flag (unused) F = First fragment flag C = Compression enabled for downstream packet -P = is ping response +P = ping flag: extra header present +I = responded to immediately (for RTT calculation) SSSSSSSS = Upstream packet sequence number/ACK DDDDDDDD = Downstream packet sequence number/ACK ZZZZZZZZ = Downstream window size @@ -193,14 +197,12 @@ Upstream data packet starts with 1 byte ASCII hex coded user byte; then 1 char data-CMC; then 4 bytes Base32 encoded header; then comes the payload data, encoded with the chosen upstream codec. -Downstream data starts with 3 byte header. Then payload data, which may be -compressed. If Ping flag is set, another 4 bytes are appended to the header. -This occurs either when the server wants to re-sync its window parameters or -when the client has requested a ping response. +Downstream data starts with 3 byte headerm, followed by data, which may be +compressed. If Ping flag is set, another 4 bytes are appended to the header. In NULL and PRIVATE responses, downstream data is always raw. In all other response types, downstream data is encoded (see Options above). -Encoding type is indicated by 1 prefix char: +Encoding type is indicated by 1 prefix char (before the data header): TXT: End result is always DNS-chopped (series of len-prefixed strings <=255 bytes) @@ -219,22 +221,26 @@ has a 10-multiple priority, and encoding/decoding is done in strictly increasing priority sequence 10, 20, 30, etc. without gaps. Note that some DNS relays will shuffle the answer records in the response. + Ping: Client sends: First byte p or P Second byte CMC Rest encoded with Base32: - 76543 21076543 21076543 21076543 21076543 21076543 21076543 - +!----+!----!--+--!----!+----!---+-!----!-+--------+--------+ - |0UUUU|DDDDDDDD|WWWWWWWW|ZZZZZZZZ|XXXXXXXX|YYYYYYYY|00000ANR| - +-----+--------+--------+--------+--------+--------+--------+ + 76543210 76543210 76543210 + +--------+--------+---+--------+ + |0000UUUU|DownSQID|...|0000TANR| + +--------+--------+---+--------+ 4 bits Userid 1 byte Downstream seq ID ACK - 1 byte Window size (upstream) - 1 byte window siZe (downstream) + 1 byte window size (upstream) + 1 byte window size (downstream) + 1 byte window start (upstream) 1 byte window start (downstream) - 1 bYte window start (upstream) - 1 byte: + 2 bytes big-endian server timeout in ms + + 1 byte flags: + T = update server timeout A = is ACKing downstream frag N = is NACKing downstream frag (unused) R = respond with a data/ping packet From e454a7edb591d95156b0082a18969257cc66d61c Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 28 Sep 2015 20:06:23 +0800 Subject: [PATCH 033/113] Modified options packet to change more than 1 option at a time --- doc/proto_00000800.txt | 8 +- src/client.c | 229 ++++++++--------------------------------- src/client.h | 2 +- src/server.c | 118 ++++++++++++--------- 4 files changed, 119 insertions(+), 238 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index c6a56f8..75eff72 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -111,12 +111,14 @@ Options: Client sends: First byte o or O 5 bits coded as Base32 char, meaning userid - 1 char, meaning option + number of options (n) as decimal digit + n chars, each a valid option (to be processed in order) CMC as 3 Base32 chars Server sends: - Full name of option if accepted. After this, option immediately takes - effect in server. + Option chars in the same order as request, indicating that options were + accepted by the server. BADCODEC if not accepted. Previous situation remains. + BADLEN if number of options doesn't match length of query. All options affect only the requesting client. Option chars: diff --git a/src/client.c b/src/client.c index 02146cc..1b263d6 100644 --- a/src/client.c +++ b/src/client.c @@ -474,7 +474,7 @@ send_query(int fd, uint8_t *hostname) " always work any more. Start with -L0 next time on this network."); lazymode = 0; server_timeout_ms = 0; - handshake_lazyoff(fd); + handshake_switch_options(fd, 0, compression_down, downenc); } } } @@ -1455,51 +1455,21 @@ send_codec_switch(int fd, int userid, int bits) } static void -send_compression_switch(int fd, int userid) +send_server_options(int fd, int userid, int lazy, int compression, char denc, char *options) +/* Options must be length >=4 */ { - char buf[512] = "o_____."; - buf[1] = b32_5to8(userid); - buf[2] = compression_down ? 'c' : 'd'; - - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed) & 0x1f); - rand_seed++; - - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, (uint8_t *)buf); -} - -static void -send_downenc_switch(int fd, int userid) -{ - char buf[512] = "o_____."; - buf[1] = b32_5to8(userid); - buf[2] = tolower(downenc); - - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed) & 0x1f); - rand_seed++; - - strncat(buf, topdomain, 512 - strlen(buf)); - send_query(fd, (uint8_t *)buf); -} - -static void -send_lazy_switch(int fd, int userid) -{ - char buf[512] = "o_____."; + char buf[512] = "oU3___CMC."; buf[1] = b32_5to8(userid); - if (lazymode) - buf[2] = 'l'; - else - buf[2] = 'i'; + options[0] = tolower(denc); + options[1] = lazy ? 'l' : 'i'; + options[2] = compression ? 'c' : 'd'; + options[3] = 0; + strncpy(buf + 3, options, 3); - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed ) & 0x1f); + buf[6] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[7] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[8] = b32_5to8((rand_seed) & 0x1f); rand_seed++; strncat(buf, topdomain, 512 - strlen(buf)); @@ -2185,87 +2155,54 @@ codec_revert: fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name); } -static void -handshake_switch_downcompression(int dns_fd) +void +handshake_switch_options(int dns_fd, int lazy, int compression, char denc) { char in[4096]; - int i; int read; - char *status; + char *dname, *comp_status, *lazy_status; + char opts[4]; - status = compression_down ? "on" : "off"; - fprintf(stderr, "Switching %s downstream data compression\n", status); - for (i = 0; running && i < 5; i++) { - - send_compression_switch(dns_fd, userid); - - read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1); - - if (read > 0) { - if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length.\n"); - goto fail; - } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address.\n"); - goto fail; - } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected the compression option.\n"); - goto fail; - } - in[read] = 0; /* zero terminate */ - fprintf(stderr, "Server %s downstream compression\n", in); - return; - } - - fprintf(stderr, "Retrying downstream compression switch...\n"); - } - if (!running) - return; - - fprintf(stderr, "No reply from server on downstream compression switch.\n"); - -fail: - fprintf(stderr, "Failed to switch %s downstream data compression\n", status); -} - -static void -handshake_switch_downenc(int dns_fd) -{ - char in[4096]; - int i; - int read; - char *dname; + comp_status = compression ? "enabled" : "disabled"; dname = "Base32"; - if (downenc == 'S') + if (denc == 'S') dname = "Base64"; - else if (downenc == 'U') + else if (denc == 'U') dname = "Base64u"; - else if (downenc == 'V') + else if (denc == 'V') dname = "Base128"; - else if (downenc == 'R') + else if (denc == 'R') dname = "Raw"; - fprintf(stderr, "Switching downstream to codec %s\n", dname); - for (i=0; running && i<5 ;i++) { + lazy_status = lazy ? "lazy" : "immediate"; - send_downenc_switch(dns_fd, userid); + fprintf(stderr, "Switching server options: %s mode, downstream codec %s, compression %s...\n", + lazy_status, dname, comp_status); + for (int i = 0; running && i < 5; i++) { - read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1); + send_server_options(dns_fd, userid, lazy, compression, denc, opts); + + read = handshake_waitdns(dns_fd, in, sizeof(in) - 1, 'O', i + 1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { fprintf(stderr, "Server got bad message length.\n"); - goto codec_revert; + goto opt_revert; } else if (strncmp("BADIP", in, 5) == 0) { fprintf(stderr, "Server rejected sender IP address.\n"); - goto codec_revert; + goto opt_revert; } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected the selected codec.\n"); - goto codec_revert; + fprintf(stderr, "Server rejected the selected options.\n"); + goto opt_revert; + } else if (strncasecmp(opts, in + 3, 3) != 0) { + fprintf(stderr, "Server failed to change options.\n"); + goto opt_revert; } - in[read] = 0; /* zero terminate */ - fprintf(stderr, "Server switched downstream to codec %s\n", in); + fprintf(stderr, "Switched server options successfully. (%s)\n", opts); + lazymode = lazy; + compression_down = compression; + downenc = denc; return; } @@ -2276,79 +2213,9 @@ handshake_switch_downenc(int dns_fd) fprintf(stderr, "No reply from server on codec switch.\n"); -codec_revert: - fprintf(stderr, "Falling back to downstream codec Base32\n"); -} - -static void -handshake_try_lazy(int dns_fd) -{ - char in[4096]; - int i; - int read; - - fprintf(stderr, "Switching to lazy mode for low-latency\n"); - for (i=0; running && i<5; i++) { - - send_lazy_switch(dns_fd, userid); - - read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1); - - if (read > 0) { - if (strncmp("BADLEN", in, 6) == 0) { - fprintf(stderr, "Server got bad message length.\n"); - goto codec_revert; - } else if (strncmp("BADIP", in, 5) == 0) { - fprintf(stderr, "Server rejected sender IP address.\n"); - goto codec_revert; - } else if (strncmp("BADCODEC", in, 8) == 0) { - fprintf(stderr, "Server rejected lazy mode.\n"); - goto codec_revert; - } else if (strncmp("Lazy", in, 4) == 0) { - fprintf(stderr, "Server switched to lazy mode\n"); - lazymode = 1; - return; - } - } - - fprintf(stderr, "Retrying lazy mode switch...\n"); - } - if (!running) - return; - - fprintf(stderr, "No reply from server on lazy switch.\n"); - -codec_revert: - fprintf(stderr, "Falling back to legacy mode\n"); - lazymode = 0; - max_timeout_ms = 1000; -} - -void -handshake_lazyoff(int dns_fd) -/* Used in the middle of data transfer, timing is different and no error msgs */ -{ - char in[4096]; - int i; - int read; - - for (i=0; running && i<5; i++) { - - send_lazy_switch(dns_fd, userid); - - read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', 1); - - if (read == 9 && strncmp("Immediate", in, 9) == 0) { - warnx("Server switched back to legacy mode."); - lazymode = 0; - max_timeout_ms = 1000; - return; - } - } - if (!running) - return; - - warnx("No reply from server on legacy mode switch."); +opt_revert: + fprintf(stderr, "Falling back to previous configuration, downstream codec %s.\n", + dataenc->name); } static int @@ -2600,16 +2467,8 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz if (!running) return -1; - handshake_switch_downenc(dns_fd); - if (!running) - return -1; - - if (!compression_down) - handshake_switch_downcompression(dns_fd); - - if (lazymode) { - handshake_try_lazy(dns_fd); - } + /* Set options for compression, lazymode and downstream codec */ + handshake_switch_options(dns_fd, lazymode, compression_down, downenc); if (!running) return -1; diff --git a/src/client.h b/src/client.h index ee43a52..bdb8a1c 100644 --- a/src/client.h +++ b/src/client.h @@ -49,7 +49,7 @@ void client_set_hostname_maxlen(size_t i); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); -void handshake_lazyoff(int dns_fd); +void handshake_switch_options(int dns_fd, int lazy, int compression, char denc); void send_ping(int fd, int ping_response, int ack); #endif diff --git a/src/server.c b/src/server.c index f6fd8b7..f26e076 100644 --- a/src/server.c +++ b/src/server.c @@ -1388,7 +1388,12 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } return; } else if(in[0] == 'O' || in[0] == 'o') { /* Protocol options */ - if (domain_len < 3) { /* len at least 3, example: "O1T" */ + int bits = 0; + int numopts; + char num[2], *opts; + + int tmp_lazy, tmp_downenc, tmp_comp; + if (domain_len < 7) { /* len at least 7, example: "oa1tcmc" */ write_dns(dns_fd, q, "BADLEN", 6, 'T'); return; } @@ -1400,59 +1405,67 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; /* illegal id */ } - int bits = 0; - switch (toupper(in[2])) { - case 'T': - users[userid].downenc = 'T'; - write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); - bits = 5; - break; - case 'S': - users[userid].downenc = 'S'; - write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); - bits = 6; - break; - case 'U': - users[userid].downenc = 'U'; - write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); - bits = 6; - break; - case 'V': - users[userid].downenc = 'V'; - write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); - bits = 7; - break; - case 'R': - users[userid].downenc = 'R'; - write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); - bits = 8; - break; - case 'L': - users[userid].lazy = 1; - write_dns(dns_fd, q, "Lazy", 4, users[userid].downenc); - break; - case 'I': - users[userid].lazy = 0; - write_dns(dns_fd, q, "Immediate", 9, users[userid].downenc); - break; - case 'C': - users[userid].down_compression = 1; - write_dns(dns_fd, q, "Enabled", 7, users[userid].downenc); - break; - case 'D': - users[userid].down_compression = 0; - write_dns(dns_fd, q, "Disabled", 8, users[userid].downenc); - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - break; + num[0] = in[2]; + num[1] = 0; + numopts = atoi(num); + + if (domain_len != numopts + 6 || numopts == 0) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); } + + /* Temporary variables: don't change anything until all options parsed */ + tmp_lazy = users[userid].lazy; + tmp_comp = users[userid].down_compression; + tmp_downenc = users[userid].downenc; + + opts = (char *) in + 3; + + for (int i = 0; i < numopts; i++) { + switch (toupper(opts[i])) { + case 'T': + tmp_downenc = 'T'; + bits = 5; + break; + case 'S': + tmp_downenc = 'S'; + bits = 6; + break; + case 'U': + tmp_downenc = 'U'; + bits = 6; + break; + case 'V': + tmp_downenc = 'V'; + bits = 7; + break; + case 'R': + tmp_downenc = 'R'; + bits = 8; + break; + case 'L': + tmp_lazy = 1; + break; + case 'I': + tmp_lazy = 0; + break; + case 'C': + tmp_comp = 1; + break; + case 'D': + tmp_comp = 0; + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + return; + } + } + /* Automatically switch to raw encoding if PRIVATE or NULL request */ - if (q->type == T_NULL || q->type == T_PRIVATE) { + if ((q->type == T_NULL || q->type == T_PRIVATE) && !bits) { users[userid].downenc = 'R'; bits = 8; if (debug >= 3) - warnx("Assuming raw data encoding due to NULL/PRIVATE requests for user %d.", userid); + warnx("Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid); } if (bits) { int f = users[userid].fragsize; @@ -1462,6 +1475,13 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query users[userid].outgoing->maxfraglen, userid, bits, users[userid].downenc); users[userid].downenc_bits = bits; } + + /* Store any changes */ + users[userid].down_compression = tmp_comp; + users[userid].downenc = tmp_downenc; + users[userid].lazy = tmp_lazy; + + write_dns(dns_fd, q, opts, numopts, users[userid].downenc); return; } else if(in[0] == 'Y' || in[0] == 'y') { /* Downstream codec check */ int i; From 1b85d230875bcdf56886cbf5fe4047338559776c Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 3 Oct 2015 22:09:34 +0800 Subject: [PATCH 034/113] Fixed option handshake and query tracking (now works) --- src/client.c | 137 ++++++++++++++++++++++++++++----------------------- src/client.h | 2 +- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/src/client.c b/src/client.c index 1b263d6..b9647f6 100644 --- a/src/client.c +++ b/src/client.c @@ -57,6 +57,7 @@ #include "tun.h" #include "version.h" #include "window.h" +#include "util.h" #include "client.h" int debug; @@ -86,6 +87,7 @@ static int next_downstream_ack; /* Remembering queries we sent for tracking purposes */ static struct query_tuple *pending_queries; +static size_t num_pending; static time_t max_timeout_ms; /* Server response timeout in ms */ @@ -310,49 +312,74 @@ client_rotate_nameserver() } /* Client-side query tracking for lazy mode */ -static int -num_pending() + +/* Handy macro for printing stats with messages */ +#define QTRACK_DEBUG(l, ...) \ + if (debug >= l) {\ + fprintf(stderr, "[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", num_pending, PENDING_QUERIES_LENGTH, \ + num_untracked, num_timeouts, window_sending(outbuf), outbuf->numitems); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } + +static void +check_pending_queries() +/* Updates pending queries list */ { - int num = 0; + num_pending = 0; struct timeval now, qtimeout, max_timeout; gettimeofday(&now, NULL); /* Max timeout for queries is max interval + 1 second extra */ - max_timeout.tv_sec = (max_timeout_ms / 1000) + 1; - max_timeout.tv_usec = (max_timeout_ms - max_timeout.tv_sec * 1000) * 1000; + max_timeout = ms_to_timeval(max_timeout_ms); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { - if (pending_queries[i].time.tv_sec > 0) { + if (pending_queries[i].time.tv_sec > 0 && pending_queries[i].id >= 0) { timeradd(&pending_queries[i].time, &max_timeout, &qtimeout); - if (timercmp(&qtimeout, &now, >)) { - num++; - } else { + if (!timercmp(&qtimeout, &now, >)) { /* Query has timed out, clear it */ pending_queries[i].time.tv_sec = 0; num_timeouts++; } + num_pending++; } } - return num; } static void query_sent_now(int id) { + int i = 0, found = 0; if (!pending_queries) return; if (id < 0 || id > 65535) return; - for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { - if (pending_queries[i].time.tv_sec == 0) { - pending_queries[i].id = id; - gettimeofday(&pending_queries[i].time, NULL); - id = -1; + /* Replace any empty queries first, then timed out ones if necessary */ + for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].id < 0) { + found = 1; break; } } - if (id > 0 && debug >= 1) - warnx("Too many queries sent! Failed to add id %d.", id); + if (!found) { + for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { + if (pending_queries[i].time.tv_sec == 0) { + found = 1; + break; + } + } + } + /* if no slots found after both checks */ + if (!found) { + QTRACK_DEBUG(1, "Buffer full! Failed to add id %d.", id); + } else { + /* Add query into found location */ + pending_queries[i].id = id; + gettimeofday(&pending_queries[i].time, NULL); + num_pending ++; + QTRACK_DEBUG(4, "Adding query id %d into pending_queries[%d]", id, i); + id = -1; + } } static void @@ -363,14 +390,16 @@ got_response(int id, int immediate) static size_t num_rtt_timeouts; gettimeofday(&now, NULL); - if (debug >= 4) - warnx("got_response: request id %d (%s)", id, immediate ? "immediate" : "lazy"); + QTRACK_DEBUG(4, "Got answer id %d (%s)", id, immediate ? "immediate" : "lazy"); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { if (id >= 0 && pending_queries[i].id == id) { + if (num_pending > 0) + num_pending--; + QTRACK_DEBUG(5, " found answer id %d in pending queries", id); id = -1; if (pending_queries[i].time.tv_sec == 0 && num_timeouts > 0) { - /* If query has timed out but is still stored */ + /* If query has timed out but is still stored - just in case */ num_timeouts --; immediate = 0; } @@ -380,8 +409,7 @@ got_response(int id, int immediate) This lets us determine and adjust server lazy response time during the session much more accurately. */ timersub(&now, &pending_queries[i].time, &rtt); - rtt_ms = rtt.tv_sec * 1000 + rtt.tv_usec / 1000; - rtt_total_ms += rtt_ms; + rtt_total_ms += timeval_to_ms(&rtt); num_immediate++; if (autodetect_server_timeout) { @@ -404,11 +432,17 @@ got_response(int id, int immediate) } } } + + /* Remove query info from buffer to mark it as answered */ + pending_queries[i].id = -1; + pending_queries[i].time.tv_sec = 0; break; } } - if (id > 0) + if (id > 0) { + QTRACK_DEBUG(4, " got untracked response to id %d.", id); num_untracked++; + } } static int @@ -521,12 +555,6 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) return send_query(fd, buf); } -static inline int -is_sending() -{ - return window_sending(outbuf) > 0; -} - void send_ping(int fd, int ping_response, int ack) { @@ -1014,7 +1042,8 @@ tunnel_tun(int tun_fd, int dns_fd) /* Check if outgoing buffer can hold data */ if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { if (debug >= 2) - fprintf(stderr, " Outgoing buffer full (%lu/%lu), not adding data!\n", outbuf->numitems, outbuf->length); + fprintf(stderr, " Outgoing buffer full (%lu/%lu), not adding data!\n", + outbuf->numitems, outbuf->length); return -1; } @@ -1127,6 +1156,7 @@ tunnel_dns(int tun_fd, int dns_fd) got_response(q.id, immediate); window_ack(outbuf, f.ack_other); + window_tick(outbuf); /* In lazy mode, we shouldn't get immediate replies to our most-recent query, only during heavy data transfer. Since this means the server @@ -1185,7 +1215,7 @@ client_tunnel(int tun_fd, int dns_fd) fd_set fds; int rv; int i; - int sending, pending; + int sending; if (conn != CONN_DNS_NULL) { compression_up = 1; @@ -1206,31 +1236,24 @@ client_tunnel(int tun_fd, int dns_fd) num_immediate = 1; num_timeouts = 0; num_untracked = 0; + num_pending = 0; send_query_recvcnt = 0; send_query_sendcnt = 0; - /* set default server timeout */ - - if (debug >= 4) + if (debug >= 5) window_debug = debug - 3; while (running) { - tv.tv_sec = max_timeout_ms / 1000; - tv.tv_usec = (max_timeout_ms - tv.tv_sec * 1000) * 1000; + tv = ms_to_timeval(max_timeout_ms); /* TODO: adjust min send interval based on DNS server droppiness * (eg. from sending lots of requests simultaneously) * TODO: adjust number of pending queries based on current data rate */ sending = window_sending(outbuf); - pending = num_pending(); - if (sending || (pending < windowsize_down && lazymode) ) { - if (debug >= 3) { - warnx("Waiting to send %d frags or fill server lazy buffer with (%d - %lu) queries.", - sending, pending, windowsize_down); - } - + check_pending_queries(); + if (sending || (num_pending < windowsize_down && lazymode) || next_downstream_ack >= 0) { /* Upstream data traffic */ - if (sending) { + if (sending > 0) { /* More to send - next fragment */ send_next_frag(dns_fd); } else { @@ -1238,6 +1261,7 @@ client_tunnel(int tun_fd, int dns_fd) send_ping(dns_fd, 0, next_downstream_ack); next_downstream_ack = -1; } + QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu.", windowsize_down); tv.tv_sec = 0; tv.tv_usec = 2000; @@ -1271,20 +1295,7 @@ client_tunnel(int tun_fd, int dns_fd) err(1, "select < 0"); if (i == 0) { - /* TODO improve timeout handling based on stats */ - if (!window_sending(outbuf) && outbuf->numitems > 0) { - if (outbuf->resends < 3) { - send_next_frag(dns_fd); - } else { - outbuf->resends = 0; - send_ping(dns_fd, 1, -1); - } - } else { - send_ping(dns_fd, 0, next_downstream_ack); - next_downstream_ack = -1; - } - send_ping_soon = 0; - + /* TODO check number of timeouts and do something about it */ } else { if (FD_ISSET(tun_fd, &fds)) { @@ -2195,9 +2206,6 @@ handshake_switch_options(int dns_fd, int lazy, int compression, char denc) } else if (strncmp("BADCODEC", in, 8) == 0) { fprintf(stderr, "Server rejected the selected options.\n"); goto opt_revert; - } else if (strncasecmp(opts, in + 3, 3) != 0) { - fprintf(stderr, "Server failed to change options.\n"); - goto opt_revert; } fprintf(stderr, "Switched server options successfully. (%s)\n", opts); lazymode = lazy; @@ -2214,8 +2222,11 @@ handshake_switch_options(int dns_fd, int lazy, int compression, char denc) fprintf(stderr, "No reply from server on codec switch.\n"); opt_revert: - fprintf(stderr, "Falling back to previous configuration, downstream codec %s.\n", - dataenc->name); + comp_status = compression_down ? "enabled" : "disabled"; + lazy_status = lazymode ? "lazy" : "immediate"; + + fprintf(stderr, "Falling back to previous configuration: downstream codec %s, %s mode, compression %s.\n", + dataenc->name, lazy_status, comp_status); } static int diff --git a/src/client.h b/src/client.h index bdb8a1c..5e7ac0b 100644 --- a/src/client.h +++ b/src/client.h @@ -20,7 +20,7 @@ extern int debug; -#define PENDING_QUERIES_LENGTH (MAX(windowsize_up, windowsize_down) * 2) +#define PENDING_QUERIES_LENGTH (MAX(windowsize_up, windowsize_down) * 3) struct query_tuple { int id; /* DNS query / response ID */ From 51bf36c8633608e7f6f4ef1476a9104859941ae1 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 3 Oct 2015 22:10:12 +0800 Subject: [PATCH 035/113] Adjusted new command line options for case consistency --- src/iodine.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index 1a8bf3e..86f492c 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -70,7 +70,7 @@ print_usage() extern char *__progname; fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " - "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-C 0|1] [-c 0|1]" + "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-c 0|1] [-C 0|1] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]]\n", __progname); } @@ -97,15 +97,15 @@ help() fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); fprintf(stderr, " -r to skip raw UDP mode attempt\n"); - fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n\n"); fprintf(stderr, "Fine-tuning options:\n"); fprintf(stderr, " -w downstream fragment window size (default: 8)\n"); fprintf(stderr, " -W upstream fragment window size (default: 8)\n"); fprintf(stderr, " -i server-side request timeout in lazy mode \n"); fprintf(stderr, " (default: automatically adjust from max timeout and round-trip time)\n"); - fprintf(stderr, " -C 1: use downstream compression (default), 0: disable\n"); - fprintf(stderr, " -c 1: use upstream compression, 0: disable (default)\n\n"); + fprintf(stderr, " -c 1: use downstream compression (default), 0: disable\n"); + fprintf(stderr, " -C 1: use upstream compression, 0: disable (default)\n\n"); fprintf(stderr, "Other options:\n"); fprintf(stderr, " -v to print version info and exit\n"); @@ -230,7 +230,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfDhrCcu:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhrcCu:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -326,10 +326,10 @@ main(int argc, char **argv) case 'W': up_windowsize = atoi(optarg); break; - case 'c': + case 'C': up_compression = atoi(optarg) & 1; break; - case 'C': + case 'c': down_compression = atoi(optarg) & 1; break; default: From faf7d277a8dad9943b835c1c394d9162652063a2 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 3 Oct 2015 22:11:58 +0800 Subject: [PATCH 036/113] Server-side query-answer logic now handled by qmem_max_wait --- src/server.c | 296 +++++++++++++++++++++++++++++---------------------- src/server.h | 2 + 2 files changed, 173 insertions(+), 125 deletions(-) diff --git a/src/server.c b/src/server.c index f26e076..5b9de9f 100644 --- a/src/server.c +++ b/src/server.c @@ -44,6 +44,7 @@ #include "login.h" #include "tun.h" #include "fw_query.h" +#include "util.h" #include "server.h" #include "window.h" @@ -191,12 +192,7 @@ qmem_append(int dns_fd, int userid, struct query *q) buf->start = (buf->start + 1) % QMEM_LEN; } - if (debug >= 5) { - time_t dnstimeout_ms; - dnstimeout_ms = users[userid].dns_timeout.tv_sec * 1000; - dnstimeout_ms += users[userid].dns_timeout.tv_usec / 1000; - QMEM_DEBUG(5, userid, "add query ID %d, timeout %lu ms", q->id, dnstimeout_ms); - } + QMEM_DEBUG(5, userid, "add query ID %d, timeout %lu ms", q->id, timeval_to_ms(&users[userid].dns_timeout)); /* Copy query into buffer */ memcpy(&buf->queries[buf->end], q, sizeof(struct query)); @@ -207,84 +203,130 @@ qmem_append(int dns_fd, int userid, struct query *q) static void qmem_answered(int userid) -/* Last query added has been answered */ +/* Call when oldest/first/earliest query added has been answered */ { struct query_buffer *buf; + size_t answered; buf = &users[userid].qmem; - buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; - if (buf->num_pending > 0) - buf->num_pending -= 1; - QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[buf->start_pending].id); + if (buf->num_pending == 0) { + /* Most likely caused by bugs somewhere else. */ + QMEM_DEBUG(3, userid, "can't answer query that has already been answered! Fix bugs."); + return; + } + answered = buf->start_pending; + buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; + buf->num_pending -= 1; + + QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[answered].id); } -static struct query * +struct query * qmem_get_next_response(int userid) -/* Gets oldest query to be responded to (for lazy mode) or NULL if none available */ +/* Gets oldest query to be responded to (for lazy mode) or NULL if none available + * The query is NOT marked as "answered" since that is done later. */ { struct query_buffer *buf; struct query *q; buf = &users[userid].qmem; - if (buf->length == 0) + if (buf->length == 0 || buf->num_pending == 0) return NULL; q = &buf->queries[buf->start_pending]; - buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; - if (buf->num_pending > 0) - buf->num_pending -= 1; - QMEM_DEBUG(3, userid, "responding lazily to query ID %d", q->id); + QMEM_DEBUG(3, userid, "next response using cached query: ID %d", q->id); return q; } static struct timeval -qmem_max_wait(int *touser, struct query **sendq) -/* Gets max interval before anything has to be sent to any user */ +qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) +/* Gets max interval before the next query has to be responded to + * Response(s) are sent automatically for queries if: + * - the query has timed out + * - the user has data to send, pending ACKs or ping and spare pending queries + * - the user has excess pending queries (>downstream window size) + * Returns largest safe time to wait before next timeout + * TODO respond to excess pending queries */ { struct timeval now, timeout, soonest, tmp; soonest.tv_sec = 10; soonest.tv_usec = 0; int userid, qnum, nextuser = -1; struct query *q = NULL, *nextq = NULL; + size_t sending, total, sent; + struct tun_user *u; gettimeofday(&now, NULL); for (userid = 0; userid < created_users; userid++) { if (!user_active(userid)) continue; - qnum = users[userid].qmem.start_pending; - if (users[userid].qmem.num_pending == 0 || !users[userid].lazy) - continue; - for (; qnum != users[userid].qmem.end; qnum = (qnum + 1) % QMEM_LEN) { - // TODO are queries always going to be in time order already? - q = &users[userid].qmem.queries[qnum]; - timeradd(&q->time_recv, &users[userid].dns_timeout, &timeout); - if (!timercmp(&now, &timeout, <)) { - /* if timeout has been reached, must send ASAP */ - soonest.tv_sec = 0; - soonest.tv_usec = 0; - nextuser = userid; - nextq = q; - /* no need to check other users */ - userid = created_users; - break; + u = &users[userid]; + qnum = u->qmem.start_pending; + + if (u->qmem.num_pending == 0 || !u->lazy) + continue; + /* Keep track of how many fragments we can send */ + total = window_sending(u->outgoing); + if (u->qmem.num_pending > u->outgoing->windowsize) { + /* calculate number of "excess" queries */ + total = MAX(total, u->qmem.num_pending - u->outgoing->windowsize); + } + sending = total; + sent = 0; + + for (; qnum != u->qmem.end; qnum = (qnum + 1) % QMEM_LEN) { + q = &u->qmem.queries[qnum]; + + /* queries will always be in time order */ + timeradd(&q->time_recv, &u->dns_timeout, &timeout); + if (sending > 0 || !timercmp(&now, &timeout, <) || + u->next_upstream_ack >= 0 || u->send_ping_next) { + /* respond to a query with ping/data if: + * - query has timed out (ping, or data if available) + * - user has pending data (always data) + * - user has pending ACK (either) + * - user has pending ping (always ping, with data if available) */ + + if (debug >= 3) { + struct timeval age; + timersub(&q->time_recv, &now, &age); + QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old, timeout %ld ms", + q->id, timeval_to_ms(&age), timeval_to_ms(&u->dns_timeout)); + } + + sent++; + QMEM_DEBUG(4, userid, "ANSWER q id %d, ping %d, ACK %d; sent %lu of %lu + sending another %lu", + q->id, u->send_ping_next, u->next_upstream_ack, sent, total, sending); + + send_data_or_ping(dns_fds, userid, q, u->send_ping_next, 1, 0); + + if (u->send_ping_next) + u->send_ping_next = 0; + + if (sending > 0) + sending--; + continue; } + timersub(&timeout, &now, &tmp); if (timercmp(&tmp, &soonest, <)) { - /* time until timeout is smaller */ + /* the oldest non-timed-out query in the buffer will be the + * soonest to timeout for this user; we can skip the rest */ soonest = tmp; nextuser = userid; nextq = q; + break; } } } if (debug >= 5) { - time_t soonest_ms = soonest.tv_sec * 1000; - soonest_ms += soonest.tv_usec / 1000; - if (nextq && nextuser > 0) { + time_t soonest_ms = timeval_to_ms(&soonest); + if (nextq && nextuser >= 0) { QMEM_DEBUG(5, nextuser, "can wait for %lu ms, will send id %d", soonest_ms, nextq->id); } else { if (nextuser < 0) nextuser = 0; + /* sanity check: soonest_ms should always be default value here (ie. 10000) */ QMEM_DEBUG(5, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); } } @@ -414,7 +456,7 @@ forward_query(int bind_fd, struct query *q) myaddr->sin_port = htons(bind_port); if (debug >= 2) { - fprintf(stderr, "TX: NS reply \n"); + fprintf(stderr, "TX: NS reply\n"); } if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { @@ -449,35 +491,23 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s } void -send_data_or_ping(int tun_fd, struct dnsfd *dns_fds, int userid, struct query *q, +send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, int ping, int respond_now, int immediate) /* 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. respond_now: 1=must answer query now, 0=leave in qmem if no data available - immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem - Updates next_upstream_ack if new ACK needed. */ + immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem */ { uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; size_t datalen, headerlen; fragment *f; - int compressed = 0; struct frag_buffer *out, *in; in = users[userid].incoming; out = users[userid].outgoing; - datalen = window_reassemble_data(in, pkt, sizeof(pkt), &compressed); - window_tick(in); window_tick(out); - /* Update time info */ - users[userid].last_pkt = time(NULL); - - if (datalen > 0) { - /* Data reassembled successfully + cleared out of buffer */ - handle_full_packet(tun_fd, dns_fds, userid, pkt, datalen, compressed); - } - f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack); /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ @@ -529,40 +559,79 @@ send_data_or_ping(int tun_fd, struct dnsfd *dns_fds, int userid, struct query *q save_to_dnscache(userid, q, (char *)pkt, datalen + headerlen); #endif #ifdef QMEM_LEN + /* mark query as answered */ qmem_answered(userid); #endif - /* this query has been used */ - q->id = 0; window_tick(out); } -static int -user_send_data(int userid, int tun_fd, struct dnsfd *dns_fds, uint8_t *data, size_t datalen, int compressed) -/* Appends data to a user's outgoing queue and sends it if queries are waiting */ +void +user_process_incoming_data(int tun_fd, struct dnsfd *dns_fds, int userid, int ack) { - struct query *q; - if (users[userid].conn == CONN_DNS_NULL) { + uint8_t pkt[65536]; + size_t datalen; + int compressed = 0; - window_add_outgoing_data(users[userid].outgoing, data, datalen, compressed); + window_ack(users[userid].outgoing, ack); + window_tick(users[userid].outgoing); - /* Start sending immediately if queries are waiting */ -#ifdef QMEM_LEN - while (users[userid].qmem.num_pending > 0 && - window_sending(users[userid].outgoing)) { - q = qmem_get_next_response(userid); - if (q == NULL) - break; - send_data_or_ping(tun_fd, dns_fds, userid, q, 0, 1, 0); + datalen = window_reassemble_data(users[userid].incoming, pkt, sizeof(pkt), &compressed); + window_tick(users[userid].incoming); + + /* Update time info */ + users[userid].last_pkt = time(NULL); + + if (datalen > 0) { + /* Data reassembled successfully + cleared out of buffer */ + handle_full_packet(tun_fd, dns_fds, userid, pkt, datalen, compressed); + } +} + +static int +user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, + size_t len, int compressed) +/* Appends data to a user's outgoing queue and sends it (in raw mode only) */ +{ + size_t datalen; + int ret = 0; + uint8_t out[65536], *data; + + data = indata; + datalen = len; + + /* use compressed or uncompressed packet to match user settings */ + if (users[userid].down_compression && !compressed) { + datalen = sizeof(out); + compress2(out, &datalen, indata, len, 9); + data = out; + } else if (!users[userid].down_compression && compressed) { + datalen = sizeof(out); + ret = uncompress(out, &datalen, indata, len); + if (ret != Z_OK) { + if (debug >= 1) { + warnx("Uncompress == %d: %lu bytes to user %d!", ret, len, userid); + } + return 0; } -#endif + } - return datalen; - } else { /* CONN_RAW_UDP */ + compressed = users[userid].down_compression; + + if (users[userid].conn == CONN_DNS_NULL && data && datalen) { + /* append new data to user's outgoing queue; sent later in qmem_max_wait */ + ret = window_add_outgoing_data(users[userid].outgoing, data, datalen, compressed); + + } else if (data && datalen) { /* CONN_RAW_UDP */ + if (!compressed && debug >= 1) { + warnx("Sending in RAW mode uncompressed to user %d!", userid); + } int dns_fd = get_dns_fd(dns_fds, &users[userid].host); send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA, &users[userid].host, users[userid].hostlen); - return datalen; + ret = 1; } + + return ret; } static int @@ -615,9 +684,7 @@ tunnel_bind(int bind_fd, struct dnsfd *dns_fds) static int tunnel_tun(int tun_fd, struct dnsfd *dns_fds) { - unsigned long outlen; struct ip *header; - static uint8_t out[64*1024]; static uint8_t in[64*1024]; int userid; int read; @@ -635,14 +702,7 @@ tunnel_tun(int tun_fd, struct dnsfd *dns_fds) fprintf(stderr, "IN: %d byte pkt from tun to user %d; compression %d\n", read, userid, users[userid].down_compression); - if (users[userid].down_compression) { - outlen = sizeof(out); - compress2(out, &outlen, in, read, 9); - - return user_send_data(userid, tun_fd, dns_fds, out, outlen, 1); - } else { - return user_send_data(userid, tun_fd, dns_fds, in, read, 0); - } + return user_send_data(userid, dns_fds, in, read, 0); } static int @@ -736,13 +796,8 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) while (running) { int maxfd; - /* TODO: adjust time based on query timeouts (lazy mode) */ - tv = qmem_max_wait(&userid, &answer_now); - - if (tv.tv_sec == 0 && tv.tv_usec == 0) { - /* We need to respond to an old query immediately; do so now. */ - send_data_or_ping(tun_fd, dns_fds, userid, answer_now, 0, 1, 0); - } + /* max wait time based on pending queries */ + tv = qmem_max_wait(dns_fds, &userid, &answer_now); FD_ZERO(&fds); maxfd = 0; @@ -762,8 +817,7 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) maxfd = MAX(bind_fd, maxfd); } - /* Don't read from tun if no users can accept data anyway; - tun queue/TCP buffers are larger than our outgoing queues */ + /* Don't read from tun if all users have filled outpacket queues */ if(!all_users_waiting_to_send()) { FD_SET(tun_fd, &fds); maxfd = MAX(tun_fd, maxfd); @@ -839,16 +893,11 @@ handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, /* send the uncompressed packet to tun device */ write_tun(tun_fd, rawdata, rawlen); } else { - /* use compressed or uncompressed packet to match user settings */ - if (users[touser].down_compression) { - if (!compressed) { - len = sizeof(out); - compress2(out, &len, rawdata, rawlen, 9); - data = out; - } - user_send_data(touser, tun_fd, dns_fds, data, len, 1); + /* don't re-compress if possible */ + if (users[touser].down_compression && compressed) { + user_send_data(touser, dns_fds, data, len, 1); } else { - user_send_data(touser, tun_fd, dns_fds, rawdata, rawlen, 0); + user_send_data(touser, dns_fds, rawdata, rawlen, 0); } } } else { @@ -936,7 +985,6 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsf { int raw_user; - warnx("raw_decode len %lu", len); /* minimum length */ if (len < RAW_HDR_LEN) return 0; /* should start with header */ @@ -1196,14 +1244,14 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } if(in[0] == 'V' || in[0] == 'v') { /* Version request */ - uint32_t version; + uint32_t version = !PROTOCOL_VERSION; read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); /* Version greeting, compare and send ack/nak */ if (read > 4) { /* Received V + 32bits version (network byte order) */ version = ntohl(*(uint32_t *) unpacked); - } + } /* if invalid pkt, just send VNAK */ if (version == PROTOCOL_VERSION) { userid = find_available_user(); @@ -1236,6 +1284,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query window_buffer_clear(u->outgoing); window_buffer_clear(u->incoming); u->next_upstream_ack = -1; + u->send_ping_next = 0; #ifdef QMEM_LEN qmem_init(userid); #endif @@ -1390,7 +1439,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } else if(in[0] == 'O' || in[0] == 'o') { /* Protocol options */ int bits = 0; int numopts; - char num[2], *opts; + char *opts; int tmp_lazy, tmp_downenc, tmp_comp; if (domain_len < 7) { /* len at least 7, example: "oa1tcmc" */ @@ -1405,12 +1454,11 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; /* illegal id */ } - num[0] = in[2]; - num[1] = 0; - numopts = atoi(num); + numopts = in[2] - '0'; - if (domain_len != numopts + 6 || numopts == 0) { + if (domain_len < numopts + 6 || numopts == 0 || numopts > 9) { write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; /* invalid packet */ } /* Temporary variables: don't change anything until all options parsed */ @@ -1605,12 +1653,10 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query unsigned timeout_ms; struct timeval timeout; - /* We can't handle id=0, that's "no packet" to us. So drop + /* We can't handle id=0, that's "no packet" to the dnscache. So drop request completely. Note that DNS servers rewrite the id. We'll drop 1 in 64k times. If DNS server retransmits with different id, then all okay. - Else client won't retransmit, and we'll just keep the - previous ping in cache, no problem either. TODO don't use ID=0 to check if query */ if (q->id == 0) return; @@ -1646,8 +1692,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query dn_seq = unpacked[5]; timeout_ms = ntohs(*(uint16_t *) (unpacked + 6)); - timeout.tv_sec = timeout_ms / 1000; - timeout.tv_usec = (timeout_ms - timeout.tv_sec * 1000) * 1000; + timeout = ms_to_timeval(timeout_ms); respond = unpacked[8] & 1; @@ -1661,10 +1706,11 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, timeout_ms, unpacked[8]); } - window_ack(users[userid].outgoing, dn_ack); + user_process_incoming_data(tun_fd, dns_fds, userid, dn_ack); - /* Send resonse; q can be left in qmem if no data (q is still fresh) */ - send_data_or_ping(tun_fd, dns_fds, userid, q, respond, 0, 1); + /* Leave query in qmem, response is done in qmem_max_wait. + * Set the ping flag if it needs to respond */ + users[userid].send_ping_next = respond; } else if (isxdigit(in[0])) { /* Upstream data packet */ int code = 0; @@ -1729,17 +1775,17 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query warnx("frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end); - /* if waiting for an ACK to be sent back upstream (on incoming buffer) */ + /* if already waiting for an ACK to be sent back upstream (on incoming buffer) */ if (users[userid].next_upstream_ack >= 0) { /* Shouldn't normally happen; will always be reset after sending a packet. */ warnx("[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); } users[userid].next_upstream_ack = window_process_incoming_fragment(users[userid].incoming, &f); - window_ack(users[userid].outgoing, f.ack_other); + user_process_incoming_data(tun_fd, dns_fds, userid, f.ack_other); - /* Respond/ACK data packet immediately; query is fresh */ - send_data_or_ping(tun_fd, dns_fds, userid, q, 0, 1, 1); + /* Nothing to do. ACK (and response to this query) is sent + * later in qmem_max_wait. */ } } @@ -1765,7 +1811,7 @@ handle_ns_request(int dns_fd, struct query *q) } if (debug >= 2) { - fprintf(stderr, "TX: client %s ID %5d, type %d, name %s, %d bytes NS reply\n", + fprintf(stderr, "TX: NS reply client %s ID %5d, type %d, name %s, %d bytes\n", format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); } if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { @@ -1799,7 +1845,7 @@ handle_a_request(int dns_fd, struct query *q, int fakeip) } if (debug >= 2) { - fprintf(stderr, "TX: client %s ID %5d, type %d, name %s, %d bytes A reply\n", + fprintf(stderr, "TX: A reply client %s ID %5d, type %d, name %s, %d bytes\n", format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); } if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { diff --git a/src/server.h b/src/server.h index 193df54..a539eaf 100644 --- a/src/server.h +++ b/src/server.h @@ -124,4 +124,6 @@ void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct q 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(struct dnsfd *, int, struct query *, int, int, int); + #endif /* __SERVER_H__ */ From 9ee23992c0e56c50b01b932692c6da407665b426 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 3 Oct 2015 22:13:15 +0800 Subject: [PATCH 037/113] Added user ping flag, all_users_waiting_to_send now makes sense --- src/user.c | 10 +++++----- src/user.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/user.c b/src/user.c index 7588de4..d6324e6 100644 --- a/src/user.c +++ b/src/user.c @@ -128,14 +128,14 @@ user_active(int i) int all_users_waiting_to_send() /* If this returns true, then reading from tun device is blocked. - So only return true when all clients have at least one fragment in - the outgoing buffer, so that sending back-to-back is possible - without going through another select loop. -*/ + So only return true when all clients have insufficient space in + outgoing buffer, so that sending back-to-back is possible + without going through another select loop. */ { for (int i = 0; i < usercount; i++) if (user_active(i)) - if (!user_sending(i)) return 0; + if (users[i].outgoing->length - users[i].outgoing->numitems > 8) + return 0; return 1; } diff --git a/src/user.h b/src/user.h index 1acf8a9..00dd68a 100644 --- a/src/user.h +++ b/src/user.h @@ -40,6 +40,7 @@ struct tun_user { struct frag_buffer *incoming; struct frag_buffer *outgoing; int next_upstream_ack; + int send_ping_next; struct encoder *encoder; char downenc; int downenc_bits; From 4403e950a95f28409d66bcd3a7594067a4b25eff Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 3 Oct 2015 22:14:30 +0800 Subject: [PATCH 038/113] Added timeval <-> millisecond util functions --- src/util.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/util.h b/src/util.h index a0ee03b..9a99a49 100644 --- a/src/util.h +++ b/src/util.h @@ -18,7 +18,27 @@ #ifndef __UTIL_H__ #define __UTIL_H__ +#include +#include + char *get_resolvconf_addr(); void socket_setrtable(int fd, int rtable); +inline time_t +timeval_to_ms(struct timeval *tv) +{ + time_t ms = tv->tv_sec * 1000; + ms += (tv->tv_usec + 500) / 1000; + return ms; +} + +inline struct timeval +ms_to_timeval(time_t ms) +{ + struct timeval tv; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms - tv.tv_sec * 1000) * 1000; + return tv; +} + #endif From 2be624c9a626174f5b080e71956ca8dcf86f76e2 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 3 Oct 2015 22:15:30 +0800 Subject: [PATCH 039/113] Fixed window_sending + made debug output nicer --- src/window.c | 19 +++++++++++-------- src/window.h | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/window.c b/src/window.c index 09d47fe..192e38b 100644 --- a/src/window.c +++ b/src/window.c @@ -232,18 +232,18 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int /* Returns number of fragments that can be sent immediately; effectively * the same as window_get_next_sending_fragment but without changing anything. */ -int +size_t window_sending(struct frag_buffer *w) { fragment *f; - int tosend = 0; + size_t tosend = 0; if (w->numitems == 0) return 0; for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[WRAP(w->window_start + i)]; if (f->len == 0 || f->acks >= 1) continue; - if ((f->retries == 0) != (difftime(time(NULL), f->lastsent) > ACK_TIMEOUT)) { - /* Fragment not sent xor timed out (to be re-sent) */ + if (f->retries < 1 || difftime(time(NULL), f->lastsent) >= ACK_TIMEOUT) { + /* Fragment not sent or timed out (to be re-sent) */ tosend++; } } @@ -262,7 +262,7 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) f = &w->frags[WRAP(w->window_start + i)]; if (f->acks >= 1) continue; /* TODO: use timeval for more precise timeouts */ - if (f->retries >= 1 && difftime(time(NULL), f->lastsent) > ACK_TIMEOUT) { + if (f->retries >= 1 && difftime(time(NULL), f->lastsent) >= ACK_TIMEOUT) { /* Fragment sent before, not ACK'd */ DEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); w->resends ++; @@ -328,17 +328,20 @@ window_ack(struct frag_buffer *w, int seqid) void window_tick(struct frag_buffer *w) { + unsigned old_start_id; for (size_t i = 0; i < w->windowsize; i++) { if (w->frags[w->window_start].acks >= 1) { - DEBUG("moving window forwards 1; start = %lu-%lu, end = %lu-%lu, len = %lu", - w->window_start, AFTER(w, 1), w->window_end, AFTER(w, w->windowsize + 1), w->length); + old_start_id = w->start_seq_id; + w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; + DEBUG("moving window forwards; %lu-%lu (%u) to %lu-%lu (%u) len=%lu", + w->window_start, w->window_end, old_start_id, AFTER(w, 1), + AFTER(w, w->windowsize + 1), w->start_seq_id, w->length); if (w->direction == WINDOW_SENDING) { DEBUG("Clearing old fragments in SENDING window."); w->numitems --; /* Clear old fragments */ memset(&w->frags[w->window_start], 0, sizeof(fragment)); } w->window_start = AFTER(w, 1); - w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; w->window_end = AFTER(w, w->windowsize); } else break; diff --git a/src/window.h b/src/window.h index ca88fba..ae04db1 100644 --- a/src/window.h +++ b/src/window.h @@ -62,7 +62,7 @@ extern int window_debug; #ifdef DEBUG_BUILD #define DEBUG(...) if (window_debug) {\ - fprintf(stderr, "[WINDOW-DEBUG] (%s:%d)", __FILE__, __LINE__);\ + fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) ", __FILE__, __LINE__);\ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } @@ -120,7 +120,7 @@ int window_process_incoming_fragment(struct frag_buffer *w, fragment *f); size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression); /* Returns number of fragments to be sent */ -int window_sending(struct frag_buffer *w); +size_t window_sending(struct frag_buffer *w); /* Returns next fragment to be sent or NULL if nothing (SEND) */ fragment *window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack); From 41ec2ae79c9b00e1fc4eef286a6f0dde970dc5d1 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 4 Oct 2015 22:13:47 +0800 Subject: [PATCH 040/113] Added statistics printout and command line option --- src/client.c | 222 +++++++++++++++++++++++++++++++++++---------------- src/client.h | 3 + src/iodine.c | 11 ++- 3 files changed, 163 insertions(+), 73 deletions(-) diff --git a/src/client.c b/src/client.c index b9647f6..4c37f2f 100644 --- a/src/client.c +++ b/src/client.c @@ -60,7 +60,9 @@ #include "util.h" #include "client.h" +/* Output flags for debug and time between stats update */ int debug; +int stats; static int running; static const char *password; @@ -98,11 +100,20 @@ static int autodetect_server_timeout; static time_t rtt_total_ms; static size_t num_immediate; -/* Query statistics */ +/* Connection statistics */ static size_t num_timeouts; static size_t num_untracked; -static size_t send_query_sendcnt = -1; -static size_t send_query_recvcnt = -1; +static size_t num_servfail; +static size_t num_badip; +static size_t num_init_queries; +static size_t num_sent; +static size_t num_recv; +static size_t send_query_sendcnt = 0; +static size_t send_query_recvcnt = 0; + +static size_t num_frags_sent; +static size_t num_frags_recv; +static size_t num_pings; /* My userid at the server */ static char userid; @@ -169,6 +180,9 @@ client_init() maxfragsize_up = 100; + num_immediate = 1; + rtt_total_ms = 1000; + outbuf = NULL; inbuf = NULL; pending_queries = NULL; @@ -322,6 +336,48 @@ client_rotate_nameserver() fprintf(stderr, "\n");\ } +static void +update_server_timeout(int dns_fd, int handshake) +/* Calculate server timeout based on average RTT, send ping "handshake" to set */ +{ + time_t rtt_ms; + static size_t num_rtt_timeouts = 0; + + /* Get average RTT in ms */ + rtt_ms = rtt_total_ms / num_immediate; + if (rtt_ms >= max_timeout_ms) { + num_rtt_timeouts++; + if (num_rtt_timeouts < 3) { + fprintf(stderr, "Target interval of %ld ms less than average round-trip of " + "%ld ms! Try increasing interval with -I.\n", max_timeout_ms, rtt_ms); + } else { + max_timeout_ms = server_timeout_ms; + if (server_timeout_ms > rtt_ms) { + server_timeout_ms -= rtt_ms; + if (lazymode) + fprintf(stderr, "Adjusting server timeout to %ld ms, target interval %ld ms.\n", + server_timeout_ms, max_timeout_ms); + } else { + server_timeout_ms = 0; + fprintf(stderr, "Switching off lazy mode to keep timeouts below target interval (%ld ms).", + max_timeout_ms); + handshake_switch_options(dns_fd, 0, compression_down, downenc); + } + + num_rtt_timeouts = 0; + } + } else { + /* Set server timeout based on target interval and RTT */ + server_timeout_ms = max_timeout_ms - rtt_ms; + } + + if (!handshake) + return; + + /* Send ping handshake to set server timeout/lazymode */ + send_ping(dns_fd, 1, -1); +} + static void check_pending_queries() /* Updates pending queries list */ @@ -330,7 +386,7 @@ check_pending_queries() struct timeval now, qtimeout, max_timeout; gettimeofday(&now, NULL); /* Max timeout for queries is max interval + 1 second extra */ - max_timeout = ms_to_timeval(max_timeout_ms); + max_timeout = ms_to_timeval(max_timeout_ms + 1000); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { if (pending_queries[i].time.tv_sec > 0 && pending_queries[i].id >= 0) { timeradd(&pending_queries[i].time, &max_timeout, &qtimeout); @@ -383,11 +439,9 @@ query_sent_now(int id) } static void -got_response(int id, int immediate) +got_response(int id, int immediate, int dns_fd) { struct timeval now, rtt; - time_t rtt_ms; - static size_t num_rtt_timeouts; gettimeofday(&now, NULL); QTRACK_DEBUG(4, "Got answer id %d (%s)", id, immediate ? "immediate" : "lazy"); @@ -412,25 +466,8 @@ got_response(int id, int immediate) rtt_total_ms += timeval_to_ms(&rtt); num_immediate++; - if (autodetect_server_timeout) { - /* Get average RTT in ms */ - rtt_ms = rtt_total_ms / num_immediate; - if (rtt_ms >= max_timeout_ms) { - num_rtt_timeouts++; - if (num_rtt_timeouts < 3) { - fprintf(stderr, "Preferred interval of %lu ms less than average round-trip of " - "%lu ms! Try increasing interval with -I.\n", max_timeout_ms, rtt_ms); - } else { - server_timeout_ms = max_timeout_ms; - max_timeout_ms += rtt_ms; - if (lazymode) - fprintf(stderr, "Adjusting server timeout to %lu ms.\n", server_timeout_ms); - num_rtt_timeouts = 0; - } - } else { - server_timeout_ms = max_timeout_ms - rtt_ms; - } - } + if (autodetect_server_timeout) + update_server_timeout(dns_fd, 0); } /* Remove query info from buffer to mark it as answered */ @@ -487,29 +524,32 @@ send_query(int fd, uint8_t *hostname) (Can't very well do this anywhere else; this is the only place we'll reliably get to in such situations.) */ + num_sent++; if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode) { send_query_sendcnt++; - if ((send_query_sendcnt > 6 && send_query_recvcnt <= 0) || - (send_query_sendcnt > 10 && 4 * send_query_recvcnt < send_query_sendcnt)) { - if (server_timeout_ms > 500 && autodetect_server_timeout) { - server_timeout_ms -= 200; - double secs = (double) server_timeout_ms / 1000.0; - fprintf(stderr, "Receiving too few answers. Setting server timeout to %fs (-I%f)\n", secs, secs); + if ((send_query_sendcnt > windowsize_down && send_query_recvcnt <= 0) || + (send_query_sendcnt > 2 * windowsize_down && 4 * send_query_recvcnt < send_query_sendcnt)) { + if (max_timeout_ms > 500 && autodetect_server_timeout) { + max_timeout_ms -= 500; + double secs = (double) max_timeout_ms / 1000.0; + fprintf(stderr, "Receiving too few answers. Setting target timeout to %.1fs (-I%.1f)\n", secs, secs); + /* restart counting */ send_query_sendcnt = 0; send_query_recvcnt = 0; - rtt_total_ms = 1000; - num_immediate = 1; - /* TODO: reduce windowsize due to DNS server dropping queries */ - send_ping(fd, 1, -1); + if (rtt_total_ms / num_immediate < 1000) { + rtt_total_ms = 1000; + num_immediate = 1; + } + } else if (lazymode && autodetect_server_timeout) { fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" " always work any more. Start with -L0 next time on this network."); lazymode = 0; server_timeout_ms = 0; - handshake_switch_options(fd, 0, compression_down, downenc); } + update_server_timeout(fd, 1); } } return q.id; @@ -558,6 +598,7 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) void send_ping(int fd, int ping_response, int ack) { + num_pings++; if (conn == CONN_DNS_NULL) { uint8_t data[11]; int id; @@ -651,6 +692,7 @@ send_next_frag(int fd) query_sent_now(id); window_tick(outbuf); + num_frags_sent++; } static void @@ -899,7 +941,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query } } -static int +int handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) /* Wait for DNS reply fitting to our latest query and returns it. Returns length of reply = #bytes used in buf. @@ -985,7 +1027,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) return -1; } -static int +int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) { size_t headerlen = DOWNSTREAM_HDR; @@ -1011,7 +1053,6 @@ parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) if (len < headerlen) return -1; /* invalid packet - continue */ /* Parse data/ping header */ - /* TODO: do something with wsize/start params in ping */ dn_wsize = data[3]; up_wsize = data[4]; dn_start_seq = data[5]; @@ -1072,11 +1113,9 @@ tunnel_tun(int tun_fd, int dns_fd) static int tunnel_dns(int tun_fd, int dns_fd) { - static long packrecv = 0; - static long packrecv_servfail = 0; static struct query q; size_t datalen, buflen; - static uint8_t buf[64*1024], cbuf[64*1024]; + static uint8_t buf[64*1024], cbuf[64*1024], *data; static fragment f; int read, compressed, res, immediate; @@ -1106,25 +1145,28 @@ tunnel_dns(int tun_fd, int dns_fd) if (read < 0) write_dns_error(&q, 0); - if (read < 0 && q.rcode == SERVFAIL && lazymode && server_timeout_ms > 500) { - if (packrecv < 500 && packrecv_servfail < 4) { - packrecv_servfail++; - fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...", packrecv_servfail); - } else if (packrecv < 500 && packrecv_servfail >= 4 && autodetect_server_timeout) { - packrecv_servfail++; + if (read < 0 && q.rcode == SERVFAIL && lazymode && max_timeout_ms > 500) { + num_servfail++; + + if (send_query_recvcnt < 500 && num_servfail < 4) { + fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...", num_servfail); + } else if (send_query_recvcnt < 500 && num_servfail >= 4 && autodetect_server_timeout) { server_timeout_ms -= 500; double server_timeout = (float) server_timeout_ms / 1000.0; - fprintf(stderr, "%ld SERVFAILs is too many. Setting server timeout to %f to hopefully reduce SERVFAILs." - " But just ignore them if data still comes through. (Use -I%f next time on this network.)", - packrecv_servfail, server_timeout, server_timeout); + fprintf(stderr, "Exceeded acceptable limit for SERVFAILs (%ld), setting " + "timeout to %.1f secs. (use -I%.1f next time on this network)\n", + num_servfail, server_timeout, server_timeout); + + /* Reset query counts stats */ send_query_sendcnt = 0; send_query_recvcnt = 0; - rtt_total_ms = 1000; - num_immediate = 1; - send_ping(dns_fd, 0, -1); - } else if (packrecv >= 500 && packrecv_servfail > 0) { - fprintf(stderr, "(Sorry, stopped counting; try -I1 if you experience hiccups.)"); - packrecv_servfail = 0; + if (rtt_total_ms / num_immediate < 1000) { + /* only reset avg RTT if it might be too small (TODO trust the statistics) */ + rtt_total_ms = 1000; + num_immediate = 1; + } + + update_server_timeout(dns_fd, 1); } } @@ -1133,18 +1175,19 @@ tunnel_dns(int tun_fd, int dns_fd) } if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { - fprintf(stderr, "BADIP: Server rejected sender IP address (maybe iodined -c will help), or server " - "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds."); + num_badip++; + if (num_badip % 5 == 1) { + fprintf(stderr, "BADIP (%ld): Server rejected sender IP address (maybe iodined -c will help), or server " + "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.", num_badip); + } return -1; /* nothing done */ } /* Okay, we have a recent downstream packet */ lastdownstreamtime = time(NULL); - if (!(packrecv & 0x10000000)) - packrecv++; - - send_query_recvcnt++; /* overflow doesn't matter */ + send_query_recvcnt++; /* unlikely we will ever overflow (2^64 queries is a LOT) */ + num_recv++; /* Decode the downstream data header and fragment-ify ready for processing */ res = parse_data(cbuf, read, &f, &immediate); @@ -1153,7 +1196,7 @@ tunnel_dns(int tun_fd, int dns_fd) res ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); /* Mark query as received */ - got_response(q.id, immediate); + got_response(q.id, immediate, dns_fd); window_ack(outbuf, f.ack_other); window_tick(outbuf); @@ -1185,6 +1228,8 @@ tunnel_dns(int tun_fd, int dns_fd) /* Downstream data traffic + get ack for that data */ next_downstream_ack = window_process_incoming_fragment(inbuf, &f); + num_frags_recv++; + datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed); if (datalen > 0) { if (compressed) { @@ -1196,10 +1241,13 @@ tunnel_dns(int tun_fd, int dns_fd) } else { datalen = buflen; } + data = buf; + } else { + data = cbuf; } if (datalen) - write_tun(tun_fd, buf, datalen); + write_tun(tun_fd, data, datalen); } /* Move window along after doing all data processing */ @@ -1216,6 +1264,7 @@ client_tunnel(int tun_fd, int dns_fd) int rv; int i; int sending; + time_t last_stats; if (conn != CONN_DNS_NULL) { compression_up = 1; @@ -1225,6 +1274,9 @@ client_tunnel(int tun_fd, int dns_fd) /* Incoming buffer max fragsize doesn't matter */ inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); + /* init query tracking */ + num_untracked = 0; + num_pending = 0; pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) pending_queries[i].id = -1; @@ -1232,13 +1284,22 @@ client_tunnel(int tun_fd, int dns_fd) /* start counting now */ rv = 0; lastdownstreamtime = time(NULL); + last_stats = time(NULL); rtt_total_ms = 1000; num_immediate = 1; + + /* reset connection statistics */ + num_init_queries = MAX(send_query_recvcnt, send_query_sendcnt); + num_badip = 0; + num_servfail = 0; num_timeouts = 0; - num_untracked = 0; - num_pending = 0; send_query_recvcnt = 0; send_query_sendcnt = 0; + num_sent = 0; + num_recv = 0; + num_frags_sent = 0; + num_frags_recv = 0; + num_pings = 0; if (debug >= 5) window_debug = debug - 3; @@ -1246,8 +1307,7 @@ client_tunnel(int tun_fd, int dns_fd) while (running) { tv = ms_to_timeval(max_timeout_ms); - /* TODO: adjust min send interval based on DNS server droppiness - * (eg. from sending lots of requests simultaneously) + /* TODO: detect DNS servers which drop frequent requests * TODO: adjust number of pending queries based on current data rate */ sending = window_sending(outbuf); check_pending_queries(); @@ -1268,6 +1328,26 @@ client_tunnel(int tun_fd, int dns_fd) send_ping_soon = 0; } + if (stats) { + if (difftime(time(NULL), last_stats) >= stats) { + /* print useful statistics report */ + fprintf(stderr, "\n========== iodine connection statistics (user %1d) ==========\n", userid); + fprintf(stderr, " Queries sent: %8lu" " answered: %8lu" " SERVFAILs: %4lu\n", + num_sent, num_recv, num_servfail); + fprintf(stderr, " during init: %4lu IP rejected: %4lu untracked: %4lu\n", + num_init_queries, num_badip, num_untracked); + fprintf(stderr, " Average round-trip time: %4ld ms immediate answers: %5lu\n", + rtt_total_ms / num_immediate, num_immediate); + fprintf(stderr, " query timeouts: %4lu target: %4ld ms server: %4ld ms\n", + num_timeouts, max_timeout_ms, server_timeout_ms); + fprintf(stderr, " Resent fragments up: %4u downstream out of window: %4u\n", + outbuf->resends, inbuf->oos); + fprintf(stderr, " TX fragments: %8lu" " RX: %8lu" " pings: %8lu" "\n\n", + num_frags_sent, num_frags_recv, num_pings); + last_stats = time(NULL); + } + } + if (send_ping_soon) { tv.tv_sec = 0; tv.tv_usec = send_ping_soon * 1000; @@ -1284,7 +1364,7 @@ client_tunnel(int tun_fd, int dns_fd) i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); if (difftime(time(NULL), lastdownstreamtime) > 60) { - warnx("No downstream data received in 60 seconds, shutting down."); + fprintf(stderr, "No downstream data received in 60 seconds, shutting down.\n"); running = 0; } diff --git a/src/client.h b/src/client.h index 5e7ac0b..45b5079 100644 --- a/src/client.h +++ b/src/client.h @@ -19,6 +19,7 @@ #define __CLIENT_H__ extern int debug; +extern int stats; #define PENDING_QUERIES_LENGTH (MAX(windowsize_up, windowsize_down) * 3) @@ -49,6 +50,8 @@ void client_set_hostname_maxlen(size_t i); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); +int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate); +int handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout); void handshake_switch_options(int dns_fd, int lazy, int compression, char denc); void send_ping(int fd, int ping_response, int ack); diff --git a/src/iodine.c b/src/iodine.c index 86f492c..291ade2 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -69,7 +69,7 @@ print_usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-c 0|1] [-C 0|1] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]]\n", __progname); @@ -110,6 +110,7 @@ help() fprintf(stderr, "Other options:\n"); fprintf(stderr, " -v to print version info and exit\n"); fprintf(stderr, " -h to print this help and exit\n"); + fprintf(stderr, " -V sec to print connection statistics at specified intervals\n"); fprintf(stderr, " -f to keep running in foreground\n"); fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); @@ -198,6 +199,7 @@ main(int argc, char **argv) device = NULL; pidfile = NULL; debug = 0; + stats = 0; autodetect_frag_size = 1; max_downstream_frag_size = 3072; @@ -230,7 +232,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfDhrcCu:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhrV:c:C:i:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -242,6 +244,11 @@ main(int argc, char **argv) version(); /* NOTREACHED */ break; + case 'V': + stats = atoi(optarg); + if (stats < 0) + stats = 0; + break; case 'f': foreground = 1; break; From 7bc434536c548c8acbc5a6a86a60f9321bc5b8e8 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 4 Oct 2015 22:15:12 +0800 Subject: [PATCH 041/113] Added global debug macro and some refactoring for window.c debug output --- src/common.h | 13 +++++++++++++ src/window.c | 40 ++++++++++++++++++++-------------------- src/window.h | 4 ++-- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/common.h b/src/common.h index f3af88e..e623a73 100644 --- a/src/common.h +++ b/src/common.h @@ -81,6 +81,19 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #define UPSTREAM_HDR 6 #define UPSTREAM_PING 6 +/* handy debug printing macro */ +#define DEBUG(level, ...) \ + if (debug >= level) {\ + if (level >= 3) {\ + fprintf(stderr, "[D%d %s:%d] ", level, __FILE__, __LINE__); \ + } else { \ + fprintf(stderr, "[D%d] ", level)\ + }\ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + }\ + + struct query { char name[QUERY_NAME_SIZE]; unsigned short type; diff --git a/src/window.c b/src/window.c index 192e38b..0ca76c2 100644 --- a/src/window.c +++ b/src/window.c @@ -74,7 +74,7 @@ window_buffer_resize(struct frag_buffer *w, size_t length) { if (w->length == length) return; if (w->numitems > 0) { - DEBUG("Resizing window buffer with things still in it! This will cause problems!"); + WDEBUG("Resizing window buffer with things still in it! This will cause problems!"); } if (w->frags) free(w->frags); w->frags = calloc(length, sizeof(fragment)); @@ -131,7 +131,7 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) startid = w->start_seq_id; endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; if (!INWINDOW_SEQ(startid, endid, f->seqID)) { - DEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", + WDEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); w->oos++; /* ACK duplicate so sender can move on ASAP */ @@ -139,11 +139,11 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) } /* Place fragment into correct location in buffer */ size_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); - DEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); + WDEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); /* Check if fragment already received */ fd = &w->frags[dest]; if (fd->len != 0) { - DEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); + WDEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); if (f->seqID == fd->seqID) return f->seqID; } @@ -168,7 +168,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (w->direction != WINDOW_RECVING) return 0; if (w->frags[w->chunk_start].start == 0 && w->numitems > 0) { - DEBUG("chunk_start (%lu)pointing to non-start fragment (seq %u, len %lu)!", + WDEBUG("chunk_start (%lu)pointing to non-start fragment (seq %u, len %lu)!", w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); return 0; } @@ -183,11 +183,11 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int f = &w->frags[woffs]; fraglen = f->len; if (fraglen == 0 || !f->data || f->seqID != curseq) { - DEBUG("data missing! Not reassembling!"); + WDEBUG("data missing! Not reassembling!"); return 0; } - DEBUG(" Fragment seq %u, data length %lu, data offset %lu, total len %lu, maxlen %lu", + WDEBUG(" Fragment seq %u, data length %lu, data offset %lu, total len %lu, maxlen %lu", f->seqID, fraglen, dest - data, datalen, maxlen); memcpy(dest, f->data, MIN(fraglen, maxlen)); dest += fraglen; @@ -195,12 +195,12 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (compression) { *compression &= f->compressed & 1; if (f->compressed != *compression) { - DEBUG("Inconsistent compression flags in chunk. Not reassembling!"); + WDEBUG("Inconsistent compression flags in chunk. Not reassembling!"); return 0; } } if (fraglen > maxlen) { - DEBUG("Data buffer too small! Reassembled %lu bytes.", datalen); + WDEBUG("Data buffer too small! Reassembled %lu bytes.", datalen); return datalen; } @@ -208,7 +208,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int window_tick(w); if (f->end == 1) { - DEBUG("Found end of chunk! (seqID %u, chunk len %lu, datalen %lu)", f->seqID, i, datalen); + WDEBUG("Found end of chunk! (seqID %u, chunk len %lu, datalen %lu)", f->seqID, i, datalen); end = 1; break; } @@ -219,7 +219,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (end == 0) { /* no end of chunk found but reached end of data */ return 0; } - DEBUG("Reassembling %lu bytes of data from %lu frags; compression %d!", datalen, i + 1, *compression); + WDEBUG("Reassembling %lu bytes of data from %lu frags; compression %d!", datalen, i + 1, *compression); /* Clear all used fragments */ size_t p; ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, @@ -264,7 +264,7 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) /* TODO: use timeval for more precise timeouts */ if (f->retries >= 1 && difftime(time(NULL), f->lastsent) >= ACK_TIMEOUT) { /* Fragment sent before, not ACK'd */ - DEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); + WDEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); w->resends ++; goto found; } else if (f->retries == 0 && f->len > 0) { @@ -274,7 +274,7 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) } if (f) - DEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", + WDEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", f->retries, f->seqID, f->len); return NULL; @@ -315,9 +315,9 @@ window_ack(struct frag_buffer *w, int seqid) f = &w->frags[AFTER(w, i)]; if (f->seqID == seqid && f->len > 0) { /* ACK first non-empty frag */ if (f->acks > 0) - DEBUG("DUPE ACK: %d ACKs for seqId %u", f->acks, seqid); + WDEBUG("DUPE ACK: %d ACKs for seqId %u", f->acks, seqid); f->acks ++; - DEBUG(" ACK frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); + WDEBUG(" ACK frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); break; } } @@ -333,11 +333,11 @@ window_tick(struct frag_buffer *w) if (w->frags[w->window_start].acks >= 1) { old_start_id = w->start_seq_id; w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; - DEBUG("moving window forwards; %lu-%lu (%u) to %lu-%lu (%u) len=%lu", + WDEBUG("moving window forwards; %lu-%lu (%u) to %lu-%lu (%u) len=%lu", w->window_start, w->window_end, old_start_id, AFTER(w, 1), AFTER(w, w->windowsize + 1), w->start_seq_id, w->length); if (w->direction == WINDOW_SENDING) { - DEBUG("Clearing old fragments in SENDING window."); + WDEBUG("Clearing old fragments in SENDING window."); w->numitems --; /* Clear old fragments */ memset(&w->frags[w->window_start], 0, sizeof(fragment)); } @@ -356,13 +356,13 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c // Split data into thingies of <= fragsize size_t n = ((len - 1) / w->maxfraglen) + 1; if (!data || n == 0 || len == 0 || n > window_buffer_available(w)) { - DEBUG("Failed to append fragment (buffer too small!)"); + WDEBUG("Failed to append fragment (buffer too small!)"); return -1; } compressed &= 1; size_t offset = 0; static fragment f; - DEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); + WDEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); for (size_t i = 0; i < n; i++) { memset(&f, 0, sizeof(f)); f.len = MIN(len - offset, w->maxfraglen); @@ -374,7 +374,7 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c f.ack_other = -1; window_append_fragment(w, &f); w->cur_seq_id = (w->cur_seq_id + 1) % MAX_SEQ_ID; - DEBUG(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); + WDEBUG(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); offset += f.len; } return n; diff --git a/src/window.h b/src/window.h index ae04db1..d5831dd 100644 --- a/src/window.h +++ b/src/window.h @@ -61,13 +61,13 @@ struct frag_buffer { extern int window_debug; #ifdef DEBUG_BUILD -#define DEBUG(...) if (window_debug) {\ +#define WDEBUG(...) if (window_debug) {\ fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) ", __FILE__, __LINE__);\ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } #else -#define DEBUG(...) +#define WDEBUG(...) #endif #define AFTER(w, o) ((w->window_start + o) % w->length) From 2e0b7f8eaea297f156fa17f7a2a15df5b0233af0 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 4 Oct 2015 22:15:56 +0800 Subject: [PATCH 042/113] Added implied lazymode switch with server timeout of 0 --- doc/proto_00000800.txt | 5 ++++- src/server.c | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index 75eff72..a62c056 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -250,7 +250,10 @@ Client sends: The server response to Ping and Data packets is a DNS NULL/TXT/.. type response, always starting with the 3 bytes downstream data header as shown above. If R bit -set, server must respond with a ping downstream header. +set, server must respond with a ping downstream header. This also requires the +server to set its windowsizes to the ones provided. +If the T but is set, the server sets the user's DNS timeout to the value spec- +ified by the packet. A timeout value of 0 implies disabling lazy mode. If server has nothing to send, no data is added after the header. If server has something to send, it will add the downstream data packet (or some fragment of it) after the header. diff --git a/src/server.c b/src/server.c index 5b9de9f..505df98 100644 --- a/src/server.c +++ b/src/server.c @@ -262,8 +262,10 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) u = &users[userid]; qnum = u->qmem.start_pending; - if (u->qmem.num_pending == 0 || !u->lazy) + if (u->qmem.num_pending == 0) continue; + + /* Keep track of how many fragments we can send */ total = window_sending(u->outgoing); if (u->qmem.num_pending > u->outgoing->windowsize) { @@ -273,6 +275,11 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) sending = total; sent = 0; + if (!u->lazy && u->qmem.num_pending > 0) { + QMEM_DEBUG(2, userid, "User switched to immediate mode, answering all pending queries..."); + sending = u->qmem.num_pending; + } + for (; qnum != u->qmem.end; qnum = (qnum + 1) % QMEM_LEN) { q = &u->qmem.queries[qnum]; @@ -1697,8 +1704,12 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query respond = unpacked[8] & 1; if ((unpacked[8] >> 3) & 1) { - /* update user's query timeout */ + /* update user's query timeout if timeout flag set */ users[userid].dns_timeout = timeout; + if (timeout_ms == 0) { + /* immediate mode is implied by server timeout of 0 */ + users[userid].lazy = 0; + } } if (debug >= 2) { From ceaac6481400af4f09f3d5eea1127f3b51f83cc3 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 4 Oct 2015 22:39:40 +0800 Subject: [PATCH 043/113] Refactored client/server to use debug macro --- src/client.c | 41 ++++++----------- src/common.h | 2 +- src/server.c | 122 ++++++++++++++++----------------------------------- src/user.c | 2 +- 4 files changed, 54 insertions(+), 113 deletions(-) diff --git a/src/client.c b/src/client.c index 4c37f2f..59c8cf7 100644 --- a/src/client.c +++ b/src/client.c @@ -490,8 +490,7 @@ send_query(int fd, uint8_t *hostname) struct query q; size_t len; - if (debug >= 2) - fprintf(stderr, "TX: pkt len %lu: hostname '%s'\n", strlen((char *)hostname), hostname); + DEBUG(3, "TX: pkt len %lu: hostname '%s'", strlen((char *)hostname), hostname); chunkid += 7727; if (chunkid == 0) @@ -507,8 +506,7 @@ send_query(int fd, uint8_t *hostname) return -1; } - if (debug >= 3) - fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]); + DEBUG(4, " Sendquery: id %5d name[0] '%c'", q.id, hostname[0]); sendto(fd, packet, len, 0, (struct sockaddr*) &nameserv_addrs[current_nameserver], sizeof(struct sockaddr_storage)); @@ -545,7 +543,7 @@ send_query(int fd, uint8_t *hostname) } else if (lazymode && autodetect_server_timeout) { fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" - " always work any more. Start with -L0 next time on this network."); + " always work any more. Start with -L0 next time on this network.\n"); lazymode = 0; server_timeout_ms = 0; } @@ -622,10 +620,8 @@ send_ping(int fd, int ping_response, int ack) data[10] = (rand_seed >> 0) & 0xff; rand_seed += 263; - if (debug >= 3) { - fprintf(stderr, " SEND PING: respond %d, ack %d, servertimeout %ld, flags %02X\n", + DEBUG(3, " SEND PING: respond %d, ack %d, servertimeout %ld, flags %02X", ping_response, ack, server_timeout_ms, data[8]); - } id = send_packet(fd, 'p', data, sizeof(data)); @@ -904,8 +900,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query memcpy(buf, data, rv); } - if (debug >= 2) - fprintf(stderr, "RX: id %5d name[0]='%c'\n", q->id, q->name[0]); + DEBUG(2, "RX: id %5d name[0]='%c'", q->id, q->name[0]); return rv; } else { /* CONN_RAW_UDP */ @@ -981,8 +976,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) qcmd = toupper(q.name[0]); if (q.id != chunkid || qcmd != cmd) { - if (debug >= 1) - fprintf(stderr, "Ignoring unfitting reply id %d starting with '%c'\n", q.id, q.name[0]); + DEBUG(1, "Ignoring unfitting reply id %d starting with '%c'", q.id, q.name[0]); continue; } @@ -1057,10 +1051,8 @@ parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) up_wsize = data[4]; dn_start_seq = data[5]; up_start_seq = data[6]; - if (debug >= 3) { - fprintf(stderr, "PING pkt data=%lu WS: up=%u, dn=%u; Start: up=%u, dn=%u\n", + DEBUG(3, "PING pkt data=%lu WS: up=%u, dn=%u; Start: up=%u, dn=%u", len - headerlen, up_wsize, dn_wsize, up_start_seq, dn_start_seq); - } } f->len = len - headerlen; if (f->len > 0) @@ -1082,14 +1074,12 @@ tunnel_tun(int tun_fd, int dns_fd) /* Check if outgoing buffer can hold data */ if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { - if (debug >= 2) - fprintf(stderr, " Outgoing buffer full (%lu/%lu), not adding data!\n", + DEBUG(1, " Outgoing buffer full (%lu/%lu), not adding data!", outbuf->numitems, outbuf->length); return -1; } - if (debug >= 2) - fprintf(stderr, " IN: %lu bytes on tunnel, compression %d\n", read, compression_up); + DEBUG(2, " IN: %lu bytes on tunnel, compression %d", read, compression_up); if (conn != CONN_DNS_NULL || compression_up) { datalen = sizeof(out); @@ -1207,8 +1197,8 @@ tunnel_dns(int tun_fd, int dns_fd) 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 && debug >= 1) - fprintf(stderr, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); + if (!res) + DEBUG(1, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); if (!lazymode) send_ping_soon = 100; else @@ -1221,8 +1211,7 @@ tunnel_dns(int tun_fd, int dns_fd) if (next_downstream_ack >= 0) { /* If this happens something is wrong (or last frag was a re-send) * May result in ACKs being delayed. */ - if (debug >= 1) - warnx("next_downstream_ack NOT -1! (%d), %u resends, %u oos", next_downstream_ack, outbuf->resends, outbuf->oos); + DEBUG(1, "next_downstream_ack NOT -1! (%d), %u resends, %u oos", next_downstream_ack, outbuf->resends, outbuf->oos); } /* Downstream data traffic + get ack for that data */ @@ -1235,8 +1224,7 @@ tunnel_dns(int tun_fd, int dns_fd) if (compressed) { buflen = sizeof(buf); if ((res = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { - if (debug >= 1) - warnx("Uncompress failed (%d) for data len %lu: reassembled data corrupted or incomplete!", res, datalen); + DEBUG(1, "Uncompress failed (%d) for data len %lu: reassembled data corrupted or incomplete!", res, datalen); datalen = 0; } else { datalen = buflen; @@ -2105,8 +2093,7 @@ handshake_qtype_autodetect(int dns_fd) if (handshake_qtypetest(dns_fd, timeout)) { /* okay */ highestworking = qtypenum; - if (debug >= 1) - fprintf(stderr, " Type %s timeout %d works\n", client_get_qtype(), timeout); + DEBUG(1, " Type %s timeout %d works", client_get_qtype(), timeout); break; /* try others with longer timeout */ } diff --git a/src/common.h b/src/common.h index e623a73..7a45053 100644 --- a/src/common.h +++ b/src/common.h @@ -87,7 +87,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; if (level >= 3) {\ fprintf(stderr, "[D%d %s:%d] ", level, __FILE__, __LINE__); \ } else { \ - fprintf(stderr, "[D%d] ", level)\ + fprintf(stderr, "[D%d] ", level);\ }\ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ diff --git a/src/server.c b/src/server.c index 505df98..0efcb04 100644 --- a/src/server.c +++ b/src/server.c @@ -112,10 +112,8 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr len += RAW_HDR_LEN; packet[RAW_HDR_CMD] = cmd | (user & 0x0F); - if (debug >= 2) { - fprintf(stderr, "TX-raw: client %s (user %d), cmd %d, %d bytes\n", + DEBUG(3, "TX-raw: client %s (user %d), cmd %d, %d bytes\n", format_addr(from, fromlen), user, cmd, len); - } sendto(fd, packet, len, 0, (struct sockaddr *) from, fromlen); } @@ -172,7 +170,7 @@ qmem_append(int dns_fd, int userid, struct query *q) if (memcmp(pq->name, q->name, sizeof(q->name))) continue; - QMEM_DEBUG(1, userid, "OUT for '%s' == duplicate, sending illegal reply\n", q->name); + QMEM_DEBUG(1, userid, "OUT for '%s' == duplicate, sending illegal reply", q->name); // TODO cache answers/respond using cache? (merge with dnscache) write_dns(dns_fd, q, "x", 1, 'T'); @@ -211,7 +209,7 @@ qmem_answered(int userid) if (buf->num_pending == 0) { /* Most likely caused by bugs somewhere else. */ - QMEM_DEBUG(3, userid, "can't answer query that has already been answered! Fix bugs."); + QMEM_DEBUG(1, userid, "can't answer query that has already been answered! Fix bugs."); return; } answered = buf->start_pending; @@ -409,8 +407,7 @@ answer_from_dnscache(int dns_fd, int userid, struct query *q) continue; /* okay, match */ - if (debug >= 1) - fprintf(stderr, "OUT user %d %s from dnscache\n", userid, q->name); + DEBUG(1, "OUT user %d %s from dnscache", userid, q->name); write_dns(dns_fd, q, users[userid].dnscache_answer[use], users[userid].dnscache_answerlen[use], @@ -462,9 +459,7 @@ forward_query(int bind_fd, struct query *q) memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t)); myaddr->sin_port = htons(bind_port); - if (debug >= 2) { - fprintf(stderr, "TX: NS reply\n"); - } + DEBUG(2, "TX: NS reply"); if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { warn("forward query error"); @@ -615,9 +610,7 @@ user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, datalen = sizeof(out); ret = uncompress(out, &datalen, indata, len); if (ret != Z_OK) { - if (debug >= 1) { - warnx("Uncompress == %d: %lu bytes to user %d!", ret, len, userid); - } + DEBUG(1, "FAIL: Uncompress == %d: %lu bytes to user %d!", ret, len, userid); return 0; } } @@ -629,9 +622,8 @@ user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, ret = window_add_outgoing_data(users[userid].outgoing, data, datalen, compressed); } else if (data && datalen) { /* CONN_RAW_UDP */ - if (!compressed && debug >= 1) { - warnx("Sending in RAW mode uncompressed to user %d!", userid); - } + if (!compressed) + DEBUG(1, "Sending in RAW mode uncompressed to user %d!", userid); int dns_fd = get_dns_fd(dns_fds, &users[userid].host); send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA, &users[userid].host, users[userid].hostlen); @@ -661,23 +653,17 @@ tunnel_bind(int bind_fd, struct dnsfd *dns_fds) id = dns_get_id(packet, r); - if (debug >= 2) { - fprintf(stderr, "RX: Got response on query %u from DNS\n", (id & 0xFFFF)); - } + DEBUG(3, "RX: Got response on query %u from DNS", (id & 0xFFFF)); /* Get sockaddr from id */ fw_query_get(id, &query); if (!query) { - if (debug >= 2) { - fprintf(stderr, "Lost sender of id %u, dropping reply\n", (id & 0xFFFF)); - } + DEBUG(2, "Lost sender of id %u, dropping reply", (id & 0xFFFF)); return 0; } - if (debug >= 2) { - fprintf(stderr, "TX: client %s id %u, %d bytes\n", + DEBUG(3, "TX: client %s id %u, %d bytes", format_addr(&query->addr, query->addrlen), (id & 0xffff), r); - } dns_fd = get_dns_fd(dns_fds, &query->addr); if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), @@ -705,8 +691,7 @@ tunnel_tun(int tun_fd, struct dnsfd *dns_fds) if (userid < 0) return 0; - if (debug >= 2) - fprintf(stderr, "IN: %d byte pkt from tun to user %d; compression %d\n", + DEBUG(3, "IN: %d byte pkt from tun to user %d; compression %d", read, userid, users[userid].down_compression); return user_send_data(userid, dns_fds, in, read, 0); @@ -723,10 +708,8 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) return 0; - if (debug >= 2) { - fprintf(stderr, "RX: client %s ID %5d, type %d, name %s\n", + DEBUG(3, "RX: client %s ID %5d, type %d, name %s\n", format_addr(&q.from, q.fromlen), q.id, q.type, q.name); - } domain_len = strlen(q.name) - strlen(topdomain); if (domain_len >= 0 && !strcasecmp(q.name + domain_len, topdomain)) @@ -778,9 +761,7 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) } } else { /* Forward query to other port ? */ - if (debug >= 3) { - fprintf(stderr, "Requested domain outside our topdomain.\n"); - } + DEBUG(2, "Requested domain outside our topdomain."); if (bind_fd) { forward_query(bind_fd, &q); } @@ -798,7 +779,7 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) struct query *answer_now = NULL; time_t last_action = time(NULL); - if (debug >= 4) + if (debug >= 5) window_debug = debug - 3; while (running) { @@ -894,8 +875,7 @@ handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, if (ret == Z_OK) { hdr = (struct ip*) (out + 4); touser = find_user_by_ip(hdr->ip_dst.s_addr); - if (debug >= 3) - fprintf(stderr, "FULL PKT: %lu bytes from user %d (touser %d)\n", len, userid, touser); + DEBUG(2, "FULL PKT: %lu bytes from user %d (touser %d)", len, userid, touser); if (touser == -1) { /* send the uncompressed packet to tun device */ write_tun(tun_fd, rawdata, rawlen); @@ -908,8 +888,7 @@ handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, } } } else { - if (debug >= 2) - fprintf(stderr, "Discarded upstream data from user %d, uncompress() result: %d\n", userid, ret); + DEBUG(2, "Discarded upstream data from user %d, uncompress() result: %d", userid, ret); } } @@ -925,9 +904,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri if (userid < 0 || userid >= created_users) return; if (!check_authenticated_user_and_ip(userid, q)) return; - if (debug >= 1) { - fprintf(stderr, "IN login raw, len %lu, from user %d\n", len, userid); - } + DEBUG(1, "IN login raw, len %lu, from user %d", len, userid); /* User sends hash of seed + 1 */ login_calculate(myhash, 16, password, users[userid].seed + 1); @@ -961,9 +938,7 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_ /* copy to packet buffer, update length TODO fix the raw UDP protocol */ - if (debug >= 1) { - fprintf(stderr, "IN pkt raw, total %lu, from user %d\n", len, userid); - } + DEBUG(3, "IN pkt raw, total %lu, from user %d", len, userid); handle_full_packet(tun_fd, dns_fds, userid, packet, len, 1); } @@ -979,9 +954,7 @@ handle_raw_ping(struct query *q, int dns_fd, int userid) /* Update time info for user */ users[userid].last_pkt = time(NULL); - if (debug >= 1) { - fprintf(stderr, "IN ping raw, from user %d\n", userid); - } + DEBUG(3, "IN ping raw, from user %d", userid); /* Send ping reply */ send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, &q->from, q->fromlen); @@ -998,8 +971,7 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsf if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0; raw_user = RAW_HDR_GET_USR(packet); - if (debug >= 3) - warnx("TX-raw: client %s, user %d, raw command '%c' length %lu", + DEBUG(3, "TX-raw: client %s, user %d, raw command '%c' length %lu", format_addr(&q->from, q->fromlen), raw_user, RAW_HDR_GET_CMD(packet), len); switch (RAW_HDR_GET_CMD(packet)) { case RAW_HDR_CMD_LOGIN: @@ -1015,7 +987,7 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsf handle_raw_ping(q, dns_fd, raw_user); break; default: - warnx("Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); + DEBUG(1, "Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); break; } return 1; @@ -1217,10 +1189,8 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) return; } - if (debug >= 2) { - fprintf(stderr, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'\n", + DEBUG(3, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'\n", format_addr(&q->from, q->fromlen), q->id, datalen, q->type, q->name); - } sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); } @@ -1246,9 +1216,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query memcpy(in, q->name, MIN(domain_len, sizeof(in))); - if (debug >= 3) { - fprintf(stderr, "NULL request length %d/%lu, command '%c'\n", domain_len, sizeof(in), in[0]); - } + DEBUG(3, "NULL request length %d/%lu, command '%c'\n", domain_len, sizeof(in), in[0]); if(in[0] == 'V' || in[0] == 'v') { /* Version request */ uint32_t version = !PROTOCOL_VERSION; @@ -1303,8 +1271,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query u->dnscache_lastfilled = 0; #endif - if (debug >= 1) - fprintf(stderr, "User %d connected with correct version from %s.\n", + DEBUG(1, "User %d connected with correct version from %s.", userid, format_addr(&q->from, q->fromlen)); } else { /* No space for another user */ @@ -1327,8 +1294,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query /* Login phase, handle auth */ userid = unpacked[0]; - if (debug >= 3) - fprintf(stderr, "Received login request for user %d from %s.\n", + DEBUG(2, "Received login request for user %d from %s.", userid, format_addr(&q->from, q->fromlen)); if (check_user_and_ip(userid, q) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); @@ -1519,14 +1485,12 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if ((q->type == T_NULL || q->type == T_PRIVATE) && !bits) { users[userid].downenc = 'R'; bits = 8; - if (debug >= 3) - warnx("Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid); + DEBUG(2, "Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid); } if (bits) { int f = users[userid].fragsize; users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; - if (debug >= 1) - warnx("Setting max downstream data length to %u bytes for user %d; bits %d (%c)", + DEBUG(1, "Setting max downstream data length to %u bytes for user %d; bits %d (%c)", users[userid].outgoing->maxfraglen, userid, bits, users[userid].downenc); users[userid].downenc_bits = bits; } @@ -1602,9 +1566,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } req_frag_size = ntohs(*(uint16_t *) (unpacked + 1)); - if (debug >= 3) { - fprintf(stderr, "Got downstream fragsize probe from user %d, required fragsize %d\n", userid, req_frag_size); - } + DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size); if (req_frag_size < 2 || req_frag_size > 2047) { write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); @@ -1649,9 +1611,8 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query 8 - DOWNSTREAM_PING_HDR; write_dns(dns_fd, q, (char *) unpacked + 1, 2, users[userid].downenc); - if (debug >= 1) - warnx("Setting max downstream data length to %u bytes for user %d; %d bits (%c)", - users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); + DEBUG(1, "Setting max downstream data length to %u bytes for user %d; %d bits (%c)", + users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); } return; } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ @@ -1670,7 +1631,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); if (read < UPSTREAM_PING) { - if (debug >= 1) warnx("Invalid ping! Length %lu", read); + DEBUG(1, "Invalid ping! Length %lu", read); return; } @@ -1712,10 +1673,8 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } } - if (debug >= 2) { - fprintf(stderr, "PING pkt from user %d, down %d/%d, up %d/%d, ACK %d, set timeout %u ms (flags %02X)\n", + DEBUG(3, "PING pkt from user %d, down %d/%d, up %d/%d, ACK %d, set timeout %u ms (flags %02X)", userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, timeout_ms, unpacked[8]); - } user_process_incoming_data(tun_fd, dns_fds, userid, dn_ack); @@ -1738,7 +1697,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query different id, then all okay. Else client doesn't get our ack, and will retransmit in 1 second. */ if (q->id == 0) { - warnx("Query with ID 0!"); + DEBUG(1, "Query with ID 0!"); return; } @@ -1782,14 +1741,13 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query f.len = unpack_data(f.data, MAX_FRAGSIZE, in + UPSTREAM_HDR, domain_len - UPSTREAM_HDR, users[userid].encoder); - if (debug >= 3) - warnx("frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", + DEBUG(3, "frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end); /* if already waiting for an ACK to be sent back upstream (on incoming buffer) */ if (users[userid].next_upstream_ack >= 0) { /* Shouldn't normally happen; will always be reset after sending a packet. */ - warnx("[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); + DEBUG(1, "[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); } users[userid].next_upstream_ack = window_process_incoming_fragment(users[userid].incoming, &f); @@ -1821,10 +1779,8 @@ handle_ns_request(int dns_fd, struct query *q) return; } - if (debug >= 2) { - fprintf(stderr, "TX: NS reply client %s ID %5d, type %d, name %s, %d bytes\n", + DEBUG(2, "TX: NS reply client %s ID %5d, type %d, name %s, %d bytes", format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); - } if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { warn("ns reply send error"); } @@ -1855,10 +1811,8 @@ handle_a_request(int dns_fd, struct query *q, int fakeip) return; } - if (debug >= 2) { - fprintf(stderr, "TX: A reply client %s ID %5d, type %d, name %s, %d bytes\n", + DEBUG(2, "TX: A reply client %s ID %5d, type %d, name %s, %d bytes", format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len); - } if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { warn("a reply send error"); } diff --git a/src/user.c b/src/user.c index d6324e6..4f2ff88 100644 --- a/src/user.c +++ b/src/user.c @@ -81,7 +81,7 @@ init_users(in_addr_t my_ip, int netbits) if (debug >= 2) { struct in_addr IP; IP.s_addr = ip; - fprintf(stderr, "User %d: IP %s\n", i, inet_ntoa(IP)); + DEBUG(2, "User %d: IP %s\n", i, inet_ntoa(IP)); } users[i].tun_ip = ip; net.s_addr = ip; From f4f358f5e22fc86c0dde1d4380e71c2be0e8f84e Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 5 Oct 2015 10:20:03 +0800 Subject: [PATCH 044/113] Ping responses use the same query and are immediate + set window params --- src/server.c | 113 +++++++++++++++++++++++++++------------------------ src/server.h | 2 +- src/user.c | 2 +- src/user.h | 1 - 4 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/server.c b/src/server.c index 0efcb04..dc36779 100644 --- a/src/server.c +++ b/src/server.c @@ -112,7 +112,7 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr len += RAW_HDR_LEN; packet[RAW_HDR_CMD] = cmd | (user & 0x0F); - DEBUG(3, "TX-raw: client %s (user %d), cmd %d, %d bytes\n", + DEBUG(3, "TX-raw: client %s (user %d), cmd %d, %d bytes", format_addr(from, fromlen), user, cmd, len); sendto(fd, packet, len, 0, (struct sockaddr *) from, fromlen); @@ -149,10 +149,10 @@ qmem_init(int userid) } static int -qmem_append(int dns_fd, int userid, struct query *q) -/* Appends incoming query to the buffer. If the query is already in the buffer, - * ie a duplicate, an illegal answer is sent. - * Return: 0 = answer sent, don't process; 1 = not a duplicate (all OK) */ +qmem_is_cached(int dns_fd, int userid, struct query *q) +/* Check if an answer for a particular query is cached in qmem + * If so, sends an "invalid" answer + * Returns 1 if new query, 0 if cached (and then answered) */ { struct query_buffer *buf; struct query *pq; @@ -166,21 +166,29 @@ qmem_append(int dns_fd, int userid, struct query *q) if (pq->type != q->type) continue; - // FIXME: check for case changes? - if (memcmp(pq->name, q->name, sizeof(q->name))) + if (strcasecmp(pq->name, q->name)) continue; - QMEM_DEBUG(1, userid, "OUT for '%s' == duplicate, sending illegal reply", q->name); + QMEM_DEBUG(2, userid, "OUT for '%s' == duplicate, sending illegal reply", q->name); // TODO cache answers/respond using cache? (merge with dnscache) write_dns(dns_fd, q, "x", 1, 'T'); return 0; } + return 1; +} + +static int +qmem_append(int userid, struct query *q) +/* Appends incoming query to the buffer. */ +{ + struct query_buffer *buf; + buf = &users[userid].qmem; if (buf->num_pending >= QMEM_LEN) { /* this means we have QMEM_LEN *pending* queries; don't overwrite */ QMEM_DEBUG(2, userid, "full of pending queries. Not appending query with id %d.", q->id); - return 1; + return 0; } if (buf->length < QMEM_LEN) { @@ -241,15 +249,15 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) * - the query has timed out * - the user has data to send, pending ACKs or ping and spare pending queries * - the user has excess pending queries (>downstream window size) - * Returns largest safe time to wait before next timeout - * TODO respond to excess pending queries */ + * Returns largest safe time to wait before next timeout */ { - struct timeval now, timeout, soonest, tmp; + struct timeval now, timeout, soonest, tmp, age; soonest.tv_sec = 10; soonest.tv_usec = 0; - int userid, qnum, nextuser = -1; + int userid, qnum, nextuser = -1, immediate; struct query *q = NULL, *nextq = NULL; size_t sending, total, sent; + time_t age_ms; struct tun_user *u; gettimeofday(&now, NULL); @@ -273,7 +281,7 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) sending = total; sent = 0; - if (!u->lazy && u->qmem.num_pending > 0) { + if ((!u->lazy) && u->qmem.num_pending > 0) { QMEM_DEBUG(2, userid, "User switched to immediate mode, answering all pending queries..."); sending = u->qmem.num_pending; } @@ -283,29 +291,28 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) /* queries will always be in time order */ timeradd(&q->time_recv, &u->dns_timeout, &timeout); - if (sending > 0 || !timercmp(&now, &timeout, <) || - u->next_upstream_ack >= 0 || u->send_ping_next) { + if (sending > 0 || !timercmp(&now, &timeout, <) || u->next_upstream_ack >= 0) { /* respond to a query with ping/data if: * - query has timed out (ping, or data if available) * - user has pending data (always data) * - user has pending ACK (either) * - user has pending ping (always ping, with data if available) */ + timersub(&q->time_recv, &now, &age); + age_ms = timeval_to_ms(&age); + + /* only consider "immediate" when age is negligible */ + immediate = age_ms <= 10; if (debug >= 3) { - struct timeval age; - timersub(&q->time_recv, &now, &age); - QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old, timeout %ld ms", - q->id, timeval_to_ms(&age), timeval_to_ms(&u->dns_timeout)); + QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old (%s), timeout %ld ms", + q->id, age_ms, immediate ? "immediate" : "lazy", timeval_to_ms(&u->dns_timeout)); } sent++; - QMEM_DEBUG(4, userid, "ANSWER q id %d, ping %d, ACK %d; sent %lu of %lu + sending another %lu", - q->id, u->send_ping_next, u->next_upstream_ack, sent, total, sending); + QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %lu of %lu + sending another %lu", + q->id, u->next_upstream_ack, sent, total, sending); - send_data_or_ping(dns_fds, userid, q, u->send_ping_next, 1, 0); - - if (u->send_ping_next) - u->send_ping_next = 0; + send_data_or_ping(dns_fds, userid, q, 0, immediate); if (sending > 0) sending--; @@ -332,7 +339,8 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) if (nextuser < 0) nextuser = 0; /* sanity check: soonest_ms should always be default value here (ie. 10000) */ - QMEM_DEBUG(5, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); + if (soonest_ms != 10000) + QMEM_DEBUG(1, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); } } @@ -494,10 +502,9 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s void send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, - int ping, int respond_now, int immediate) + int ping, int immediate) /* 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. - respond_now: 1=must answer query now, 0=leave in qmem if no data available immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem */ { uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR]; @@ -514,10 +521,6 @@ send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, /* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */ if (!f) { - if (users[userid].lazy && !respond_now) { - /* No data and lazy mode: leave this query to wait in qmem */ - return; - } /* No data, may as well send data/ping header (with extra info) */ ping = 1; datalen = 0; @@ -708,7 +711,7 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) return 0; - DEBUG(3, "RX: client %s ID %5d, type %d, name %s\n", + DEBUG(3, "RX: client %s ID %5d, type %d, name %s", format_addr(&q.from, q.fromlen), q.id, q.type, q.name); domain_len = strlen(q.name) - strlen(topdomain); @@ -1189,7 +1192,7 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) return; } - DEBUG(3, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'\n", + DEBUG(3, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'", format_addr(&q->from, q->fromlen), q->id, datalen, q->type, q->name); sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); @@ -1216,7 +1219,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query memcpy(in, q->name, MIN(domain_len, sizeof(in))); - DEBUG(3, "NULL request length %d/%lu, command '%c'\n", domain_len, sizeof(in), in[0]); + DEBUG(3, "NULL request length %d/%lu, command '%c'", domain_len, sizeof(in), in[0]); if(in[0] == 'V' || in[0] == 'v') { /* Version request */ uint32_t version = !PROTOCOL_VERSION; @@ -1259,7 +1262,6 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query window_buffer_clear(u->outgoing); window_buffer_clear(u->incoming); u->next_upstream_ack = -1; - u->send_ping_next = 0; #ifdef QMEM_LEN qmem_init(userid); #endif @@ -1635,13 +1637,6 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; } - /* Ping packet, store userid */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - #ifdef DNSCACHE_LEN /* Check if cached */ if (answer_from_dnscache(dns_fd, userid, q)) @@ -1649,10 +1644,17 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query #endif // XXX hmm these look very similar... #ifdef QMEM_LEN /* Check if cached */ - if (!qmem_append(dns_fd, userid, q)) + if (!qmem_is_cached(dns_fd, userid, q)) return; #endif + /* Ping packet, store userid */ + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + dn_ack = ((unpacked[8] >> 2) & 1) ? unpacked[1] : -1; up_winsize = unpacked[2]; dn_winsize = unpacked[3]; @@ -1664,6 +1666,16 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query respond = unpacked[8] & 1; + if (respond) { + /* ping handshake - set windowsizes etc, respond NOW using this query + * NOTE: not added to qmem */ + users[userid].outgoing->windowsize = dn_winsize; + users[userid].incoming->windowsize = up_winsize; + send_data_or_ping(dns_fds, userid, q, 1, 1); + } else { + qmem_append(userid, q); + } + if ((unpacked[8] >> 3) & 1) { /* update user's query timeout if timeout flag set */ users[userid].dns_timeout = timeout; @@ -1678,10 +1690,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query user_process_incoming_data(tun_fd, dns_fds, userid, dn_ack); - /* Leave query in qmem, response is done in qmem_max_wait. - * Set the ping flag if it needs to respond */ - users[userid].send_ping_next = respond; - + /* if respond flag not set, query waits in qmem and is used later */ } else if (isxdigit(in[0])) { /* Upstream data packet */ int code = 0; static fragment f; @@ -1722,7 +1731,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query #endif #ifdef QMEM_LEN /* Check if cached */ - if (!qmem_append(dns_fd, userid, q)) + if (!qmem_is_cached(dns_fd, userid, q)) return; #endif /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ @@ -1753,8 +1762,8 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query user_process_incoming_data(tun_fd, dns_fds, userid, f.ack_other); - /* Nothing to do. ACK (and response to this query) is sent - * later in qmem_max_wait. */ + /* Nothing to do. ACK for this fragment is sent later in qmem_max_wait, + * using an old query. This is left in qmem until needed/times out */ } } diff --git a/src/server.h b/src/server.h index a539eaf..6c17363 100644 --- a/src/server.h +++ b/src/server.h @@ -124,6 +124,6 @@ void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct q 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(struct dnsfd *, int, struct query *, int, int, int); +void send_data_or_ping(struct dnsfd *, int, struct query *, int, int); #endif /* __SERVER_H__ */ diff --git a/src/user.c b/src/user.c index 4f2ff88..b89d78b 100644 --- a/src/user.c +++ b/src/user.c @@ -81,7 +81,7 @@ init_users(in_addr_t my_ip, int netbits) if (debug >= 2) { struct in_addr IP; IP.s_addr = ip; - DEBUG(2, "User %d: IP %s\n", i, inet_ntoa(IP)); + DEBUG(2, "User %d: IP %s", i, inet_ntoa(IP)); } users[i].tun_ip = ip; net.s_addr = ip; diff --git a/src/user.h b/src/user.h index 00dd68a..1acf8a9 100644 --- a/src/user.h +++ b/src/user.h @@ -40,7 +40,6 @@ struct tun_user { struct frag_buffer *incoming; struct frag_buffer *outgoing; int next_upstream_ack; - int send_ping_next; struct encoder *encoder; char downenc; int downenc_bits; From 49b32328744f64d37575a39f9837c3d4d2b557c8 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 5 Oct 2015 10:33:16 +0800 Subject: [PATCH 045/113] Fixed ping userid validation --- src/server.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server.c b/src/server.c index dc36779..3362648 100644 --- a/src/server.c +++ b/src/server.c @@ -1637,6 +1637,13 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; } + /* Check userid */ + userid = unpacked[0]; + if (check_authenticated_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + #ifdef DNSCACHE_LEN /* Check if cached */ if (answer_from_dnscache(dns_fd, userid, q)) @@ -1648,13 +1655,6 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; #endif - /* Ping packet, store userid */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - dn_ack = ((unpacked[8] >> 2) & 1) ? unpacked[1] : -1; up_winsize = unpacked[2]; dn_winsize = unpacked[3]; @@ -1732,7 +1732,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query #ifdef QMEM_LEN /* Check if cached */ if (!qmem_is_cached(dns_fd, userid, q)) - return; + qmem_append(userid, q); #endif /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ /* First byte (after userid) = CMC (ignored); skip 2 bytes */ From e61b38b9a3480b5110c7df418027260557fcc156 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 12 Oct 2015 18:39:24 +0800 Subject: [PATCH 046/113] Updated user and window tests --- tests/user.c | 16 +++++++--------- tests/window.c | 6 +++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/user.c b/tests/user.c index b1607ee..d8118fe 100644 --- a/tests/user.c +++ b/tests/user.c @@ -28,6 +28,8 @@ #include "user.h" #include "test.h" +int debug = 0; + START_TEST(test_init_users) { in_addr_t ip; @@ -39,9 +41,9 @@ START_TEST(test_init_users) count = init_users(ip, 27); for (i = 0; i < count; i++) { fail_unless(users[i].id == i); - fail_unless(users[i].q.id == 0); snprintf(givenip, sizeof(givenip), "127.0.0.%d", i + 2); fail_unless(users[i].tun_ip == inet_addr(givenip)); + fail_if(user_active(i), "user_active true for new users"); } } END_TEST @@ -78,28 +80,24 @@ START_TEST(test_find_user_by_ip) } END_TEST -extern unsigned usercount; START_TEST(test_all_users_waiting_to_send) { in_addr_t ip; ip = inet_addr("127.0.0.1"); init_users(ip, 27); - for (int i = 0; i < usercount; i++) users[i].outgoing = window_buffer_init(10, 1, 10, WINDOW_SENDING); - fail_if(all_users_waiting_to_send() == 1); + fail_unless(all_users_waiting_to_send() == 0, "empty user list all waiting to send"); users[0].conn = CONN_DNS_NULL; users[0].active = 1; - fail_if(all_users_waiting_to_send() == 1); + fail_unless(all_users_waiting_to_send() == 0, "single user with empty buffer waiting to send"); users[0].last_pkt = time(NULL); - fail_unless(all_users_waiting_to_send() == 0); + fail_unless(all_users_waiting_to_send() == 0, "single active user with empty buffer waiting to send"); - - fail_unless(all_users_waiting_to_send() == 1); } END_TEST @@ -171,7 +169,7 @@ test_user_create_tests() tc = tcase_create("User"); tcase_add_test(tc, test_init_users); tcase_add_test(tc, test_find_user_by_ip); -// tcase_add_test(tc, test_all_users_waiting_to_send); + tcase_add_test(tc, test_all_users_waiting_to_send); tcase_add_test(tc, test_find_available_user); tcase_add_test(tc, test_find_available_user_small_net); diff --git a/tests/window.c b/tests/window.c index 6b3975e..393814e 100644 --- a/tests/window.c +++ b/tests/window.c @@ -50,12 +50,12 @@ START_TEST(test_window_everything) // warnx("Nothing to send."); continue; } - fail_if(!window_process_incoming_fragment(in, f), "Incoming fragment failed!"); + fail_if((a = window_process_incoming_fragment(in, f)) != f->seqID, "Did not ACK last seqId!"); // warnx("Received fragment with seqid %u, remaining space %lu.", f->seqID, window_buffer_available(in)); - int a = window_get_next_ack(in); window_tick(in); window_ack(out, a); window_tick(out); + fail_if(out->start_seq_id != in->start_seq_id, "in/out windows have different start IDs!"); } // warnx("Added %lu fragments, reassembling into data.", in->numitems); uint8_t data[100]; @@ -72,7 +72,7 @@ START_TEST(test_window_everything) // printf("%c", data[i]); // } // printf("'\n"); - strncat((char *)newdata, data, len); + strncat((char *)newdata, (char *)data, len); if (in->numitems <= 0) break; } // printf("New data: '%s' (%lu)\n", newdata, strlen((char *)newdata)); From c38e7d30a8aad03ab905e7f42b6ba3eae26bef32 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:25:31 +0800 Subject: [PATCH 047/113] Merged QMEM and DNS cache; removed implied lazy switch. Improved immediate mode handling, however does not add data queries to QMEM in immediate mode (and does not work either). --- src/server.c | 275 ++++++++++++++++++++------------------------------- src/server.h | 24 ++++- src/user.c | 13 ++- src/user.h | 10 +- 4 files changed, 137 insertions(+), 185 deletions(-) diff --git a/src/server.c b/src/server.c index 3362648..5902704 100644 --- a/src/server.c +++ b/src/server.c @@ -118,18 +118,28 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr sendto(fd, packet, len, 0, (struct sockaddr *) from, fromlen); } -/* Ringbuffer Query Handling (qmem): +/* Ringbuffer Query Handling (qmem) and DNS Cache: This is used to make the handling duplicates and query timeouts simpler - and all in one place. - Using this, lazy mode should be possible with n queries (n <= windowsize) + and all handled in one place. + Using this, lazy mode is possible with n queries (n <= windowsize) New queries are placed consecutively in the buffer, replacing any old queries (already responded to) if length == QMEM_LEN. Old queries are kept - to check for dupes etc. + as a record for duplicate requests. If a dupe is found and USE_DNSCACHE is + defined, the previous answer is sent (if it exists), otherwise an invalid + response is sent. - TODO: modify a bit to replace dnscache entirely? - it seems the only difference is qmem doesn't store answers. */ -#ifdef QMEM_LEN + On the DNS cache: + This cache is implemented to better handle the aggressively impatient DNS + servers that very quickly re-send requests when we choose to not + immediately answer them in lazy mode. This cache works much better than + pruning(=dropping) the improper requests, since the DNS server will + actually get an answer instead of silence. + + Because of the CMC in both ping and upstream data, unwanted cache hits + are prevented. Due to the combination of CMC and varying sequence IDs, it + is extremely unlikely that any duplicate answers will be incorrectly sent + during a session (given QMEM_LEN is not very large). */ #define QMEM_DEBUG(l, u, ...) \ if (debug >= l) {\ @@ -141,26 +151,32 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr static void qmem_init(int userid) { - memset(&users[userid].qmem, 0, sizeof(struct query_buffer)); + memset(&users[userid].qmem, 0, sizeof(struct qmem_buffer)); // users[userid].qmem.end = 1; for (size_t i = 0; i < QMEM_LEN; i++) { - users[userid].qmem.queries[i].id = -1; + users[userid].qmem.queries[i].q.id = -1; } + + // TODO dns cache init in qmem } static int qmem_is_cached(int dns_fd, int userid, struct query *q) /* Check if an answer for a particular query is cached in qmem - * If so, sends an "invalid" answer + * If so, sends an "invalid" answer or one from DNS cache * Returns 1 if new query, 0 if cached (and then answered) */ { - struct query_buffer *buf; + struct qmem_buffer *buf; struct query *pq; + char *data = "x"; + char dataenc = 'T'; + size_t len = 1; + int dnscache = 0; buf = &users[userid].qmem; /* Check if this is a duplicate query */ for (size_t p = buf->start; p != buf->end; p = (p + 1) % QMEM_LEN) { - pq = &buf->queries[p]; + pq = &buf->queries[p].q; if (pq->id != q->id) continue; if (pq->type != q->type) @@ -169,10 +185,19 @@ qmem_is_cached(int dns_fd, int userid, struct query *q) if (strcasecmp(pq->name, q->name)) continue; - QMEM_DEBUG(2, userid, "OUT for '%s' == duplicate, sending illegal reply", q->name); - + /* Aha! A match! */ +#ifdef USE_DNSCACHE + if (buf->queries[p].a.len) { + data = (char *)buf->queries[p].a.data; + len = buf->queries[p].a.len; + dataenc = users[userid].downenc; + dnscache = 1; + } +#endif + QMEM_DEBUG(2, userid, "OUT from qmem for '%s', %s", q->name, + dnscache ? "answer from DNS cache" : "sending invalid response"); // TODO cache answers/respond using cache? (merge with dnscache) - write_dns(dns_fd, q, "x", 1, 'T'); + write_dns(dns_fd, q, data, len, dataenc); return 0; } return 1; @@ -182,7 +207,7 @@ static int qmem_append(int userid, struct query *q) /* Appends incoming query to the buffer. */ { - struct query_buffer *buf; + struct qmem_buffer *buf; buf = &users[userid].qmem; if (buf->num_pending >= QMEM_LEN) { @@ -201,30 +226,44 @@ qmem_append(int userid, struct query *q) QMEM_DEBUG(5, userid, "add query ID %d, timeout %lu ms", q->id, timeval_to_ms(&users[userid].dns_timeout)); /* Copy query into buffer */ - memcpy(&buf->queries[buf->end], q, sizeof(struct query)); + memcpy(&buf->queries[buf->end].q, q, sizeof(struct query)); +#ifdef USE_DNSCACHE + buf->queries[buf->end].a.len = 0; +#endif buf->end = (buf->end + 1) % QMEM_LEN; buf->num_pending += 1; return 1; } static void -qmem_answered(int userid) +qmem_answered(int userid, uint8_t *data, size_t len) /* Call when oldest/first/earliest query added has been answered */ { - struct query_buffer *buf; + struct qmem_buffer *buf; size_t answered; buf = &users[userid].qmem; if (buf->num_pending == 0) { /* Most likely caused by bugs somewhere else. */ - QMEM_DEBUG(1, userid, "can't answer query that has already been answered! Fix bugs."); + QMEM_DEBUG(1, userid, "Query answered with 0 in qmem! Fix bugs."); return; } answered = buf->start_pending; buf->start_pending = (buf->start_pending + 1) % QMEM_LEN; buf->num_pending -= 1; - QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[answered].id); +#ifdef USE_DNSCACHE + /* Add answer to query entry */ + if (len && data) { + if (len > 4096) { + QMEM_DEBUG(1, userid, "got answer with length >4096!"); + } + memcpy(&buf->queries[answered].a.data, data, MIN(len, 4096)); + buf->queries[answered].a.len = len; + } +#endif + + QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[answered].q.id); } struct query * @@ -232,12 +271,12 @@ qmem_get_next_response(int userid) /* Gets oldest query to be responded to (for lazy mode) or NULL if none available * The query is NOT marked as "answered" since that is done later. */ { - struct query_buffer *buf; + struct qmem_buffer *buf; struct query *q; buf = &users[userid].qmem; if (buf->length == 0 || buf->num_pending == 0) return NULL; - q = &buf->queries[buf->start_pending]; + q = &buf->queries[buf->start_pending].q; QMEM_DEBUG(3, userid, "next response using cached query: ID %d", q->id); return q; } @@ -247,7 +286,7 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) /* Gets max interval before the next query has to be responded to * Response(s) are sent automatically for queries if: * - the query has timed out - * - the user has data to send, pending ACKs or ping and spare pending queries + * - the user has data to send or pending ACKs, and spare pending queries * - the user has excess pending queries (>downstream window size) * Returns largest safe time to wait before next timeout */ { @@ -266,28 +305,28 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) continue; u = &users[userid]; - qnum = u->qmem.start_pending; if (u->qmem.num_pending == 0) continue; - /* Keep track of how many fragments we can send */ - total = window_sending(u->outgoing); - if (u->qmem.num_pending > u->outgoing->windowsize) { - /* calculate number of "excess" queries */ - total = MAX(total, u->qmem.num_pending - u->outgoing->windowsize); + if (u->lazy) { + total = window_sending(u->outgoing); + if (u->qmem.num_pending > u->outgoing->windowsize) { + /* calculate number of "excess" queries */ + total = MAX(total, u->qmem.num_pending - u->outgoing->windowsize); + } + } else { + /* User in immediate mode, must answer all pending queries */ + total = u->qmem.num_pending; } + sending = total; sent = 0; - if ((!u->lazy) && u->qmem.num_pending > 0) { - QMEM_DEBUG(2, userid, "User switched to immediate mode, answering all pending queries..."); - sending = u->qmem.num_pending; - } - + qnum = u->qmem.start_pending; for (; qnum != u->qmem.end; qnum = (qnum + 1) % QMEM_LEN) { - q = &u->qmem.queries[qnum]; + q = &u->qmem.queries[qnum].q; /* queries will always be in time order */ timeradd(&q->time_recv, &u->dns_timeout, &timeout); @@ -295,18 +334,15 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) /* respond to a query with ping/data if: * - query has timed out (ping, or data if available) * - user has pending data (always data) - * - user has pending ACK (either) - * - user has pending ping (always ping, with data if available) */ - timersub(&q->time_recv, &now, &age); + * - user has pending ACK (either) */ + timersub(&now, &q->time_recv, &age); age_ms = timeval_to_ms(&age); /* only consider "immediate" when age is negligible */ - immediate = age_ms <= 10; + immediate = llabs(age_ms) <= 10; - if (debug >= 3) { - QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old (%s), timeout %ld ms", - q->id, age_ms, immediate ? "immediate" : "lazy", timeval_to_ms(&u->dns_timeout)); - } + QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old (%s), timeout %ld ms", + q->id, age_ms, immediate ? "immediate" : "lazy", timeval_to_ms(&u->dns_timeout)); sent++; QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %lu of %lu + sending another %lu", @@ -351,85 +387,6 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) return soonest; } -#endif /* QMEM_LEN */ - -#ifdef DNSCACHE_LEN - -/* On the DNS cache: - - This cache is implemented to better handle the aggressively impatient DNS - servers that very quickly re-send requests when we choose to not - immediately answer them in lazy mode. This cache works much better than - pruning(=dropping) the improper requests, since the DNS server will - actually get an answer instead of silence. - - Because of the CMC in both ping and upstream data, unwanted cache hits - are prevented. Data-CMC is only 36 counts, so our cache length should - not exceed 36/2=18 packets. (This quick rule assumes all packets are - otherwise equal, which they arent: up/downstream seq, TCP/IP headers and - the actual data -*/ - -static void -save_to_dnscache(int userid, struct query *q, char *answer, int answerlen) -/* Store answer in our little DNS cache. */ -{ - int fill; - - if (answerlen > sizeof(users[userid].dnscache_answer[fill])) - return; /* can't store this */ - - fill = users[userid].dnscache_lastfilled + 1; - if (fill >= DNSCACHE_LEN) - fill = 0; - - memcpy(&(users[userid].dnscache_q[fill]), q, sizeof(struct query)); - memcpy(users[userid].dnscache_answer[fill], answer, answerlen); - users[userid].dnscache_answerlen[fill] = answerlen; - - users[userid].dnscache_lastfilled = fill; -} - -static int -answer_from_dnscache(int dns_fd, int userid, struct query *q) -/* Checks cache and sends repeated answer if we alreay saw this query recently. - Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is - a new query. */ -{ - int i; - int use; - - for (i = 0; i < DNSCACHE_LEN ; i++) { - /* Try cache most-recent-first */ - use = users[userid].dnscache_lastfilled - i; - if (use < 0) - use += DNSCACHE_LEN; - - if (users[userid].dnscache_q[use].id == 0) - continue; - if (users[userid].dnscache_answerlen[use] <= 0) - continue; - - if (users[userid].dnscache_q[use].type != q->type || - strcmp(users[userid].dnscache_q[use].name, q->name)) - continue; - - /* okay, match */ - DEBUG(1, "OUT user %d %s from dnscache", userid, q->name); - - write_dns(dns_fd, q, users[userid].dnscache_answer[use], - users[userid].dnscache_answerlen[use], - users[userid].downenc); - - q->id = 0; /* this query was used */ - return 1; - } - - /* here only when no match found */ - return 0; -} - -#endif /* DNSCACHE_LEN */ static int get_dns_fd(struct dnsfd *fds, struct sockaddr_storage *addr) @@ -560,13 +517,8 @@ send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, write_dns(get_dns_fd(dns_fds, &q->from), q, (char *)pkt, datalen + headerlen, users[userid].downenc); -#ifdef DNSCACHE_LEN - save_to_dnscache(userid, q, (char *)pkt, datalen + headerlen); -#endif -#ifdef QMEM_LEN /* mark query as answered */ - qmem_answered(userid); -#endif + qmem_answered(userid, pkt, datalen + headerlen); window_tick(out); } @@ -1234,7 +1186,6 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (version == PROTOCOL_VERSION) { userid = find_available_user(); if (userid >= 0) { - int i; struct tun_user *u = &users[userid]; u->seed = rand(); /* Store remote IP number */ @@ -1262,16 +1213,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query window_buffer_clear(u->outgoing); window_buffer_clear(u->incoming); u->next_upstream_ack = -1; -#ifdef QMEM_LEN qmem_init(userid); -#endif -#ifdef DNSCACHE_LEN - for (i = 0; i < DNSCACHE_LEN; i++) { - u->dnscache_q[i].id = 0; - u->dnscache_answerlen[i] = 0; - } - u->dnscache_lastfilled = 0; -#endif DEBUG(1, "User %d connected with correct version from %s.", userid, format_addr(&q->from, q->fromlen)); @@ -1620,7 +1562,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; int respond; - unsigned timeout_ms; + unsigned timeout_ms, set_timeout; struct timeval timeout; /* We can't handle id=0, that's "no packet" to the dnscache. So drop @@ -1644,16 +1586,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; /* illegal id */ } -#ifdef DNSCACHE_LEN - /* Check if cached */ - if (answer_from_dnscache(dns_fd, userid, q)) - return; -#endif // XXX hmm these look very similar... -#ifdef QMEM_LEN /* Check if cached */ if (!qmem_is_cached(dns_fd, userid, q)) return; -#endif dn_ack = ((unpacked[8] >> 2) & 1) ? unpacked[1] : -1; up_winsize = unpacked[2]; @@ -1665,29 +1600,36 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query timeout = ms_to_timeval(timeout_ms); respond = unpacked[8] & 1; + set_timeout = (unpacked[8] >> 3) & 1; + + DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %stimeout %u ms, respond %d (flags %02X)", + userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, + set_timeout ? "SET " : "", timeout_ms, respond, unpacked[8]); + + if (set_timeout) { + /* update user's query timeout if timeout flag set */ + users[userid].dns_timeout = timeout; + + /* if timeout is 0, we do not enable lazy mode but it is effectively the same */ + int newlazy = !(timeout_ms == 0); + if (newlazy != users[userid].lazy) + DEBUG(2, "User %d: not setting lazymode to %d with timeout %u", + userid, newlazy, timeout_ms); + } + + qmem_append(userid, q); if (respond) { /* ping handshake - set windowsizes etc, respond NOW using this query * NOTE: not added to qmem */ + DEBUG(2, "PING HANDSHAKE set windowsizes (old/new) up: %d/%d, dn: %d/%d", + 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(dns_fds, userid, q, 1, 1); - } else { - qmem_append(userid, q); + return; } - if ((unpacked[8] >> 3) & 1) { - /* update user's query timeout if timeout flag set */ - users[userid].dns_timeout = timeout; - if (timeout_ms == 0) { - /* immediate mode is implied by server timeout of 0 */ - users[userid].lazy = 0; - } - } - - DEBUG(3, "PING pkt from user %d, down %d/%d, up %d/%d, ACK %d, set timeout %u ms (flags %02X)", - userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, timeout_ms, unpacked[8]); - user_process_incoming_data(tun_fd, dns_fds, userid, dn_ack); /* if respond flag not set, query waits in qmem and is used later */ @@ -1724,16 +1666,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; /* illegal IP */ } -#ifdef DNSCACHE_LEN - /* Check if cached */ - if (answer_from_dnscache(dns_fd, userid, q)) - return; -#endif -#ifdef QMEM_LEN /* Check if cached */ if (!qmem_is_cached(dns_fd, userid, q)) qmem_append(userid, q); -#endif /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ /* First byte (after userid) = CMC (ignored); skip 2 bytes */ len = sizeof(unpacked); @@ -1758,7 +1693,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query /* Shouldn't normally happen; will always be reset after sending a packet. */ DEBUG(1, "[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); } - users[userid].next_upstream_ack = window_process_incoming_fragment(users[userid].incoming, &f); + + window_process_incoming_fragment(users[userid].incoming, &f); + users[userid].next_upstream_ack = f.seqID; user_process_incoming_data(tun_fd, dns_fds, userid, f.ack_other); diff --git a/src/server.h b/src/server.h index 6c17363..6183bf6 100644 --- a/src/server.h +++ b/src/server.h @@ -38,13 +38,14 @@ #include #endif -#define DNSCACHE_LEN 10 -/* Undefine to disable. Should be less than 18; also see comments in iodined.c */ - /* Max number of incoming queries to hold at one time (recommended to be same as windowsize) * Memory = USERS * (sizeof(struct query_buffer) + sizeof(query) * QMEM_LEN) */ #define QMEM_LEN 24 +#define USE_DNSCACHE +/* QMEM entries contain additional space for DNS responses. + * Undefine to disable. */ + /* Number of fragments in outgoing buffer. * Mem usage: USERS * (MAX_FRAGLEN * OUTFRAGBUF_LEN + sizeof(struct window_buffer)) */ #define OUTFRAGBUF_LEN 64 @@ -87,8 +88,21 @@ typedef enum { VERSION_FULL } version_ack_t; -struct query_buffer { - struct query queries[QMEM_LEN]; +struct query_answer { + uint8_t data[4096]; + size_t len; +}; + +struct qmem_query { + struct query q; +#ifdef USE_DNSCACHE + struct query_answer a; +#endif +}; + +/* Struct used for QMEM + DNS cache */ +struct qmem_buffer { + struct qmem_query queries[QMEM_LEN]; size_t start_pending; /* index of first "pending" query (ie. no response yet) */ size_t start; /* index of first stored/pending query */ size_t end; /* index of space after last stored/pending query */ diff --git a/src/user.c b/src/user.c index b89d78b..a16487b 100644 --- a/src/user.c +++ b/src/user.c @@ -132,10 +132,19 @@ all_users_waiting_to_send() outgoing buffer, so that sending back-to-back is possible without going through another select loop. */ { - for (int i = 0; i < usercount; i++) - if (user_active(i)) + int numactive = 0; + for (int i = 0; i < usercount; i++) { + if (user_active(i)) { if (users[i].outgoing->length - users[i].outgoing->numitems > 8) return 0; + numactive ++; + } + } + + /* no users waiting if there are no users */ + if (numactive == 0) + return 0; + return 1; } diff --git a/src/user.h b/src/user.h index 1acf8a9..165c7b1 100644 --- a/src/user.h +++ b/src/user.h @@ -47,15 +47,7 @@ struct tun_user { int fragsize; enum connection conn; int lazy; -#ifdef QMEM_LEN - struct query_buffer qmem; -#endif -#ifdef DNSCACHE_LEN - struct query dnscache_q[DNSCACHE_LEN]; - char dnscache_answer[DNSCACHE_LEN][4096]; - int dnscache_answerlen[DNSCACHE_LEN]; - int dnscache_lastfilled; -#endif + struct qmem_buffer qmem; }; extern struct tun_user *users; From 235d3be5383e06201ee7fc0e131c06ba006b011f Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:28:10 +0800 Subject: [PATCH 048/113] Improved timings + fixed immediate mode send interval. Still switches to immediate mode too eagerly and reduces timeout very quickly (especially with lots of SERVFAILs) --- src/client.c | 369 +++++++++++++++++++++++++++++++++++---------------- src/client.h | 5 +- 2 files changed, 256 insertions(+), 118 deletions(-) diff --git a/src/client.c b/src/client.c index 59c8cf7..46a87ca 100644 --- a/src/client.c +++ b/src/client.c @@ -91,6 +91,8 @@ static int next_downstream_ack; static struct query_tuple *pending_queries; static size_t num_pending; static time_t max_timeout_ms; +static time_t send_interval_ms; +static time_t min_send_interval_ms; /* Server response timeout in ms */ static time_t server_timeout_ms; @@ -105,12 +107,10 @@ static size_t num_timeouts; static size_t num_untracked; static size_t num_servfail; static size_t num_badip; -static size_t num_init_queries; static size_t num_sent; static size_t num_recv; static size_t send_query_sendcnt = 0; static size_t send_query_recvcnt = 0; - static size_t num_frags_sent; static size_t num_frags_recv; static size_t num_pings; @@ -145,6 +145,7 @@ static uint16_t do_qtype = T_UNSET; /* My connection mode */ static enum connection conn; +static int connected; static int lazymode; static long send_ping_soon; @@ -182,10 +183,13 @@ client_init() num_immediate = 1; rtt_total_ms = 1000; + send_interval_ms = 1000; + min_send_interval_ms = 1; outbuf = NULL; inbuf = NULL; pending_queries = NULL; + connected = 0; } void @@ -285,6 +289,13 @@ client_set_dnstimeout(double timeout, double servertimeout, int autodetect) autodetect_server_timeout = autodetect; } +void +client_set_interval(double interval_msec, double mininterval_msec) +{ + send_interval_ms = interval_msec; + min_send_interval_ms = mininterval_msec; +} + void client_set_lazymode(int lazy_mode) { @@ -325,6 +336,14 @@ client_rotate_nameserver() current_nameserver = 0; } +void +immediate_mode_defaults() +{ + send_interval_ms = MIN(rtt_total_ms / num_immediate, 1000); + max_timeout_ms = MAX(4 * rtt_total_ms / num_immediate, 5000); + server_timeout_ms = 0; +} + /* Client-side query tracking for lazy mode */ /* Handy macro for printing stats with messages */ @@ -336,46 +355,47 @@ client_rotate_nameserver() fprintf(stderr, "\n");\ } -static void +static int update_server_timeout(int dns_fd, int handshake) -/* Calculate server timeout based on average RTT, send ping "handshake" to set */ +/* Calculate server timeout based on average RTT, send ping "handshake" to set + * if handshake sent, return query ID */ { time_t rtt_ms; static size_t num_rtt_timeouts = 0; /* Get average RTT in ms */ rtt_ms = rtt_total_ms / num_immediate; - if (rtt_ms >= max_timeout_ms) { + if (rtt_ms >= max_timeout_ms && num_immediate > 5) { num_rtt_timeouts++; if (num_rtt_timeouts < 3) { fprintf(stderr, "Target interval of %ld ms less than average round-trip of " "%ld ms! Try increasing interval with -I.\n", max_timeout_ms, rtt_ms); } else { - max_timeout_ms = server_timeout_ms; - if (server_timeout_ms > rtt_ms) { - server_timeout_ms -= rtt_ms; - if (lazymode) - fprintf(stderr, "Adjusting server timeout to %ld ms, target interval %ld ms.\n", - server_timeout_ms, max_timeout_ms); - } else { - server_timeout_ms = 0; - fprintf(stderr, "Switching off lazy mode to keep timeouts below target interval (%ld ms).", - max_timeout_ms); - handshake_switch_options(dns_fd, 0, compression_down, downenc); - } + /* bump up target timeout */ + max_timeout_ms = rtt_ms + 1000; + server_timeout_ms = 1000; + if (lazymode) + fprintf(stderr, "Adjusting server timeout to %ld ms, target interval %ld ms. Try -I%.1f next time with this network.\n", + server_timeout_ms, max_timeout_ms, max_timeout_ms / 1000.0); num_rtt_timeouts = 0; } } else { /* Set server timeout based on target interval and RTT */ server_timeout_ms = max_timeout_ms - rtt_ms; + if (server_timeout_ms <= 0) { + server_timeout_ms = 0; + fprintf(stderr, "Setting server timeout to 0 ms: if this continues try disabling lazy mode.\n"); + } } - if (!handshake) - return; + /* TODO: update window timeout */ - /* Send ping handshake to set server timeout/lazymode */ - send_ping(dns_fd, 1, -1); + if (handshake) { + /* Send ping handshake to set server timeout */ + return send_ping(dns_fd, 1, -1, 1); + } + return -1; } static void @@ -391,7 +411,7 @@ check_pending_queries() if (pending_queries[i].time.tv_sec > 0 && pending_queries[i].id >= 0) { timeradd(&pending_queries[i].time, &max_timeout, &qtimeout); if (!timercmp(&qtimeout, &now, >)) { - /* Query has timed out, clear it */ + /* Query has timed out, clear timestamp but leave ID */ pending_queries[i].time.tv_sec = 0; num_timeouts++; } @@ -439,38 +459,53 @@ query_sent_now(int id) } static void -got_response(int id, int immediate, int dns_fd) +got_response(int id, int immediate, int fail) { struct timeval now, rtt; + time_t rtt_ms; gettimeofday(&now, NULL); - QTRACK_DEBUG(4, "Got answer id %d (%s)", id, immediate ? "immediate" : "lazy"); + QTRACK_DEBUG(4, "Got answer id %d (%s)%s", id, immediate ? "immediate" : "lazy", + fail ? ", FAIL" : ""); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { if (id >= 0 && pending_queries[i].id == id) { if (num_pending > 0) num_pending--; - QTRACK_DEBUG(5, " found answer id %d in pending queries", id); - id = -1; - if (pending_queries[i].time.tv_sec == 0 && num_timeouts > 0) { - /* If query has timed out but is still stored - just in case */ - num_timeouts --; - immediate = 0; + + if (pending_queries[i].time.tv_sec == 0) { + if (num_timeouts > 0) { + /* If query has timed out but is still stored - just in case + * ID is kept on timeout in check_pending_queries() */ + num_timeouts --; + immediate = 0; + } else { + /* query is empty */ + continue; + } } + + if (immediate || debug >= 4) { + timersub(&now, &pending_queries[i].time, &rtt); + rtt_ms = timeval_to_ms(&rtt); + } + + QTRACK_DEBUG(5, " found answer id %d in pending queries[%d], %ld ms old", id, i, rtt_ms); + if (immediate) { /* If this was an immediate response we can use it to get more detailed connection statistics like RTT. This lets us determine and adjust server lazy response time during the session much more accurately. */ - timersub(&now, &pending_queries[i].time, &rtt); - rtt_total_ms += timeval_to_ms(&rtt); + rtt_total_ms += rtt_ms; num_immediate++; if (autodetect_server_timeout) - update_server_timeout(dns_fd, 0); + update_server_timeout(-1, 0); } /* Remove query info from buffer to mark it as answered */ + id = -1; pending_queries[i].id = -1; pending_queries[i].time.tv_sec = 0; break; @@ -520,26 +555,23 @@ send_query(int fd, uint8_t *hostname) lazy mode while legacy immediate-ping-pong works just fine. Here we detect and fix these situations. (Can't very well do this anywhere else; this is the only place - we'll reliably get to in such situations.) */ + we'll reliably get to in such situations.) + Note: only start fixing up connection AFTER we have connected */ num_sent++; - if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode) { + if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode && connected) { send_query_sendcnt++; if ((send_query_sendcnt > windowsize_down && send_query_recvcnt <= 0) || (send_query_sendcnt > 2 * windowsize_down && 4 * send_query_recvcnt < send_query_sendcnt)) { if (max_timeout_ms > 500 && autodetect_server_timeout) { - max_timeout_ms -= 500; + max_timeout_ms -= 200; double secs = (double) max_timeout_ms / 1000.0; fprintf(stderr, "Receiving too few answers. Setting target timeout to %.1fs (-I%.1f)\n", secs, secs); /* restart counting */ send_query_sendcnt = 0; send_query_recvcnt = 0; - if (rtt_total_ms / num_immediate < 1000) { - rtt_total_ms = 1000; - num_immediate = 1; - } } else if (lazymode && autodetect_server_timeout) { fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" @@ -593,8 +625,8 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) return send_query(fd, buf); } -void -send_ping(int fd, int ping_response, int ack) +int +send_ping(int fd, int ping_response, int ack, int timeout) { num_pings++; if (conn == CONN_DNS_NULL) { @@ -607,28 +639,30 @@ send_ping(int fd, int ping_response, int ack) if (outbuf && inbuf) { data[2] = outbuf->windowsize & 0xff; /* Upstream window size */ - data[4] = inbuf->windowsize & 0xff; /* Downstream window size */ - data[3] = outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[3] = inbuf->windowsize & 0xff; /* Downstream window size */ + data[4] = outbuf->start_seq_id & 0xff; /* Upstream window start */ data[5] = inbuf->start_seq_id & 0xff; /* Downstream window start */ } *(uint16_t *) (data + 6) = htons(server_timeout_ms); /* update server lazy timeout, ack flag, respond with ping flag */ - data[8] = (1 << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[8] = ((timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); data[9] = (rand_seed >> 8) & 0xff; data[10] = (rand_seed >> 0) & 0xff; rand_seed += 263; - DEBUG(3, " SEND PING: respond %d, ack %d, servertimeout %ld, flags %02X", + DEBUG(3, " SEND PING: respond %d, ack %d, server timeout %ld, flags %02X", ping_response, ack, server_timeout_ms, data[8]); id = send_packet(fd, 'p', data, sizeof(data)); /* Log query ID as being sent now */ query_sent_now(id); + return id; } else { send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); + return -1; } } @@ -649,7 +683,7 @@ send_next_frag(int fd) if (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(fd, 1, next_downstream_ack); + send_ping(fd, 1, next_downstream_ack, 1); next_downstream_ack = -1; window_tick(outbuf); } @@ -696,14 +730,24 @@ write_dns_error(struct query *q, int ignore_some_errors) /* This is called from: 1. handshake_waitdns() when already checked that reply fits to our latest query. - 2. tunnel_dns() when already checked that reply is for our ping or data - packet, but not necessarily the most recent (SERVFAIL mostly comes - after long delay). - So ignorable errors are never printed. + 2. tunnel_dns() when already checked that reply is for a ping or data + packet, but possibly timed out. + Errors should not be ignored, but too many can be annoying. */ { + static size_t errorcounts[24] = {0}; if (!q) return; + if (q->rcode < 24) { + errorcounts[q->rcode]++; + if (errorcounts[q->rcode] == 20) { + warnx("Too many error replies, not logging any more."); + return; + } else if (errorcounts[q->rcode] > 20) { + return; + } + } + switch (q->rcode) { case NOERROR: /* 0 */ if (!ignore_some_errors) @@ -1092,7 +1136,7 @@ tunnel_tun(int tun_fd, int dns_fd) if (conn == CONN_DNS_NULL) { window_add_outgoing_data(outbuf, data, datalen, compression_up); - send_next_frag(dns_fd); + /* Don't send anything here to respect min. send interval */ } else { send_raw_data(dns_fd, data, datalen); } @@ -1103,10 +1147,10 @@ tunnel_tun(int tun_fd, int dns_fd) static int tunnel_dns(int tun_fd, int dns_fd) { - static struct query q; + struct query q; size_t datalen, buflen; - static uint8_t buf[64*1024], cbuf[64*1024], *data; - static fragment f; + uint8_t buf[64*1024], cbuf[64*1024], *data; + fragment f; int read, compressed, res, immediate; memset(&q, 0, sizeof(q)); @@ -1125,50 +1169,65 @@ tunnel_dns(int tun_fd, int dns_fd) if (q.name[0] != 'P' && q.name[0] != 'p' && q.name[0] != userid_char && q.name[0] != userid_char2) { send_ping_soon = 700; + got_response(q.id, 0, 0); return -1; /* nothing done */ } if (read < DOWNSTREAM_HDR) { /* Maybe SERVFAIL etc. Send ping to get things back in order, - but wait a bit to prevent fast ping-pong loops. */ + but wait a bit to prevent fast ping-pong loops. + Only change options if user hasn't specified server timeout */ if (read < 0) write_dns_error(&q, 0); - if (read < 0 && q.rcode == SERVFAIL && lazymode && max_timeout_ms > 500) { + if (q.rcode == SERVFAIL && read < 0) { num_servfail++; - if (send_query_recvcnt < 500 && num_servfail < 4) { - fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...", num_servfail); - } else if (send_query_recvcnt < 500 && num_servfail >= 4 && autodetect_server_timeout) { - server_timeout_ms -= 500; - double server_timeout = (float) server_timeout_ms / 1000.0; - fprintf(stderr, "Exceeded acceptable limit for SERVFAILs (%ld), setting " - "timeout to %.1f secs. (use -I%.1f next time on this network)\n", - num_servfail, server_timeout, server_timeout); + if (lazymode) { - /* Reset query counts stats */ - send_query_sendcnt = 0; - send_query_recvcnt = 0; - if (rtt_total_ms / num_immediate < 1000) { - /* only reset avg RTT if it might be too small (TODO trust the statistics) */ - rtt_total_ms = 1000; - num_immediate = 1; + if (send_query_recvcnt < 500 && num_servfail < 4) { + fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...\n", num_servfail); + + } else if (send_query_recvcnt < 500 && num_servfail >= 10 && + autodetect_server_timeout && max_timeout_ms >= 500 && num_servfail % 5 == 0) { + + max_timeout_ms -= 200; + double target_timeout = (float) max_timeout_ms / 1000.0; + fprintf(stderr, "Too many SERVFAILs (%ld), reducing timeout to" + " %.1f secs. (use -I%.1f next time on this network)\n", + num_servfail, target_timeout, target_timeout); + + /* Reset query counts stats */ + send_query_sendcnt = 0; + send_query_recvcnt = 0; + update_server_timeout(dns_fd, 1); + + } else if (send_query_recvcnt < 500 && num_servfail >= 40 && + autodetect_server_timeout && max_timeout_ms < 500) { + + /* last-ditch attempt to fix SERVFAILs - disable lazy mode */ + immediate_mode_defaults(); + fprintf(stderr, "Attempting to disable lazy mode due to excessive SERVFAILs\n"); + handshake_switch_options(dns_fd, 0, compression_down, downenc); } - - update_server_timeout(dns_fd, 1); } } send_ping_soon = 900; + + /* Mark query as received */ + got_response(q.id, 0, 1); return -1; /* nothing done */ } + send_query_recvcnt++; /* unlikely we will ever overflow (2^64 queries is a LOT) */ + if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { num_badip++; if (num_badip % 5 == 1) { fprintf(stderr, "BADIP (%ld): Server rejected sender IP address (maybe iodined -c will help), or server " - "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.", num_badip); + "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.\n", num_badip); } return -1; /* nothing done */ } @@ -1176,17 +1235,18 @@ tunnel_dns(int tun_fd, int dns_fd) /* Okay, we have a recent downstream packet */ lastdownstreamtime = time(NULL); - send_query_recvcnt++; /* unlikely we will ever overflow (2^64 queries is a LOT) */ 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 ((debug >= 3 && res) || (debug >= 2 && !res)) fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %lu, s%d e%d\n", res ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); - /* Mark query as received */ - got_response(q.id, immediate, dns_fd); window_ack(outbuf, f.ack_other); window_tick(outbuf); @@ -1214,8 +1274,9 @@ tunnel_dns(int tun_fd, int dns_fd) DEBUG(1, "next_downstream_ack NOT -1! (%d), %u resends, %u oos", next_downstream_ack, outbuf->resends, outbuf->oos); } - /* Downstream data traffic + get ack for that data */ - next_downstream_ack = window_process_incoming_fragment(inbuf, &f); + /* Downstream data traffic + ack data fragment */ + next_downstream_ack = f.seqID; + window_process_incoming_fragment(inbuf, &f); num_frags_recv++; @@ -1251,33 +1312,18 @@ client_tunnel(int tun_fd, int dns_fd) fd_set fds; int rv; int i; - int sending; + int sending, total; time_t last_stats; + size_t sent_since_report, recv_since_report; - if (conn != CONN_DNS_NULL) { - compression_up = 1; - } - - outbuf = window_buffer_init(64, windowsize_up, maxfragsize_up, WINDOW_SENDING); - /* Incoming buffer max fragsize doesn't matter */ - inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); - - /* init query tracking */ - num_untracked = 0; - num_pending = 0; - pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); - for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) - pending_queries[i].id = -1; + connected = 1; /* start counting now */ rv = 0; lastdownstreamtime = time(NULL); last_stats = time(NULL); - rtt_total_ms = 1000; - num_immediate = 1; /* reset connection statistics */ - num_init_queries = MAX(send_query_recvcnt, send_query_sendcnt); num_badip = 0; num_servfail = 0; num_timeouts = 0; @@ -1289,6 +1335,9 @@ client_tunnel(int tun_fd, int dns_fd) num_frags_recv = 0; num_pings = 0; + sent_since_report = 0; + recv_since_report = 0; + if (debug >= 5) window_debug = debug - 3; @@ -1298,41 +1347,65 @@ client_tunnel(int tun_fd, int dns_fd) /* TODO: detect DNS servers which drop frequent requests * TODO: adjust number of pending queries based on current data rate */ sending = window_sending(outbuf); + total = sending; check_pending_queries(); - if (sending || (num_pending < windowsize_down && lazymode) || next_downstream_ack >= 0) { - /* Upstream data traffic */ + if (num_pending < windowsize_down && lazymode) + total = MAX(total, windowsize_down - num_pending); + else if (num_pending < 1 && !lazymode) + total = MAX(total, 1); + + if (sending > 0 || total > 0 || next_downstream_ack >= 0) { + + /* Upstream traffic - this is where all ping/data queries are sent */ if (sending > 0) { /* More to send - next fragment */ send_next_frag(dns_fd); } else { /* Send ping if we didn't send anything yet */ - send_ping(dns_fd, 0, next_downstream_ack); + send_ping(dns_fd, 0, next_downstream_ack, (num_pings > 20 && num_pings % 50 == 0)); next_downstream_ack = -1; } - QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu.", windowsize_down); - tv.tv_sec = 0; - tv.tv_usec = 2000; + sending--; + total--; + QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu, will send another %d", + lazymode ? windowsize_down : 1, total); + + if (sending > 0 || (total > 0 && lazymode)) { + tv = ms_to_timeval(min_send_interval_ms); + } else if (total > 0 && !lazymode) { + /* use immediate mode send interval if nothing pending */ + tv = ms_to_timeval(send_interval_ms); + } + send_ping_soon = 0; } if (stats) { if (difftime(time(NULL), last_stats) >= stats) { /* print useful statistics report */ - fprintf(stderr, "\n========== iodine connection statistics (user %1d) ==========\n", userid); - fprintf(stderr, " Queries sent: %8lu" " answered: %8lu" " SERVFAILs: %4lu\n", + fprintf(stderr, "\n============ iodine connection statistics (user %1d) ============\n", userid); + fprintf(stderr, " Queries sent: %8lu" ", answered: %8lu" ", SERVFAILs: %4lu\n", num_sent, num_recv, num_servfail); - fprintf(stderr, " during init: %4lu IP rejected: %4lu untracked: %4lu\n", - num_init_queries, num_badip, num_untracked); - fprintf(stderr, " Average round-trip time: %4ld ms immediate answers: %5lu\n", - rtt_total_ms / num_immediate, num_immediate); - fprintf(stderr, " query timeouts: %4lu target: %4ld ms server: %4ld ms\n", + fprintf(stderr, " last %3d secs: %7lu" " (%4lu/s), replies: %7lu" " (%4lu/s)\n", + stats, num_sent - sent_since_report, (num_sent - sent_since_report) / stats, + num_recv - recv_since_report, (num_recv - recv_since_report) / stats); + fprintf(stderr, " num IP rejected: %4lu, untracked: %4lu, lazy mode: %1d\n", + num_badip, num_untracked, lazymode); + fprintf(stderr, " Min send: %4ld ms, Avg RTT: %4ld ms, immediate replies: %5lu\n", + min_send_interval_ms, rtt_total_ms / num_immediate, num_immediate); + fprintf(stderr, " query timeouts: %4lu, target: %4ld ms, server: %4ld ms\n", num_timeouts, max_timeout_ms, server_timeout_ms); - fprintf(stderr, " Resent fragments up: %4u downstream out of window: %4u\n", + fprintf(stderr, " Resent fragments up: %4u, downstream out of window: %4u\n", outbuf->resends, inbuf->oos); - fprintf(stderr, " TX fragments: %8lu" " RX: %8lu" " pings: %8lu" "\n\n", + fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", num_frags_sent, num_frags_recv, num_pings); + + /* update since-last-report stats */ + sent_since_report = num_sent; + recv_since_report = num_recv; last_stats = time(NULL); + } } @@ -1349,6 +1422,8 @@ client_tunnel(int tun_fd, int dns_fd) } FD_SET(dns_fd, &fds); + DEBUG(4, "Waiting %ld ms before sending more...", timeval_to_ms(&tv)); + i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); if (difftime(time(NULL), lastdownstreamtime) > 60) { @@ -2281,12 +2356,12 @@ handshake_switch_options(int dns_fd, int lazy, int compression, char denc) return; } - fprintf(stderr, "Retrying codec switch...\n"); + fprintf(stderr, "Retrying options switch...\n"); } if (!running) return; - fprintf(stderr, "No reply from server on codec switch.\n"); + fprintf(stderr, "No reply from server on options switch.\n"); opt_revert: comp_status = compression_down ? "enabled" : "disabled"; @@ -2477,6 +2552,49 @@ handshake_set_fragsize(int dns_fd, int fragsize) fprintf(stderr, "No reply from server when setting fragsize. Keeping default.\n"); } +static void +handshake_set_timeout(int dns_fd) +{ + char in[4096]; + int read, id; + + if (autodetect_server_timeout && lazymode) { + fprintf(stderr, "Calculating round-trip time for optimum server timeout..."); + } else { + fprintf(stderr, "Setting window sizes to %lu frags upstream, %lu frags downstream...", + windowsize_up, windowsize_down); + } + + for (int i = 0; running && i < 5; i++) { + + id = autodetect_server_timeout ? + update_server_timeout(dns_fd, 1) : send_ping(dns_fd, 1, -1, 1); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'P', i + 1); + got_response(id, 1, 0); + + if (read > 0) { + if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address.\n"); + } + if (autodetect_server_timeout) + continue; + else + break; + } + + fprintf(stderr, "."); + } + if (!running) + return; + + if (autodetect_server_timeout) + fprintf(stderr, "\nDetermined round-trip time of %ld ms, server timeout of %ld ms.\n", + rtt_total_ms / num_immediate, server_timeout_ms); + else + fprintf(stderr, " done\n"); +} + int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize) { @@ -2507,9 +2625,10 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } if (raw_mode && handshake_raw_udp(dns_fd, seed)) { - /* TODO: upstream fragsize based on max raw packet size */ conn = CONN_RAW_UDP; max_timeout_ms = 10000; + compression_down = 1; + compression_up = 1; } else { if (raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); @@ -2554,7 +2673,8 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz fragsize = handshake_autoprobe_fragsize(dns_fd); if (fragsize > MAX_FRAGSIZE) { /* This is very unlikely except perhaps over LAN */ - fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d. To fully utilize this connection, please recompile iodine/iodined.", fragsize, MAX_FRAGSIZE); + fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d." + " To fully utilize this connection, please recompile iodine/iodined.\n", fragsize, MAX_FRAGSIZE); fragsize = MAX_FRAGSIZE; } if (!fragsize) { @@ -2565,6 +2685,21 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz handshake_set_fragsize(dns_fd, fragsize); if (!running) return -1; + + /* init windowing protocol */ + outbuf = window_buffer_init(64, windowsize_up, maxfragsize_up, WINDOW_SENDING); + /* Incoming buffer max fragsize doesn't matter */ + inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); + + /* init query tracking */ + num_untracked = 0; + num_pending = 0; + pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); + for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) + pending_queries[i].id = -1; + + /* set server window/timeout parameters and calculate RTT */ + handshake_set_timeout(dns_fd); } return 0; diff --git a/src/client.h b/src/client.h index 45b5079..198b150 100644 --- a/src/client.h +++ b/src/client.h @@ -18,6 +18,8 @@ #ifndef __CLIENT_H__ #define __CLIENT_H__ +#include "window.h" + extern int debug; extern int stats; @@ -46,6 +48,7 @@ void client_set_dnstimeout(double, double, int); void client_set_lazymode(int lazy_mode); void client_set_windowsize(size_t, size_t); void client_set_hostname_maxlen(size_t i); +void client_set_interval(double, double); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); @@ -53,6 +56,6 @@ int client_tunnel(int tun_fd, int dns_fd); int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate); int handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout); void handshake_switch_options(int dns_fd, int lazy, int compression, char denc); -void send_ping(int fd, int ping_response, int ack); +int send_ping(int fd, int ping_response, int ack, int timeout); #endif From 137d3029e5ce6b50f269e3c1aa93116c8126be10 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:28:45 +0800 Subject: [PATCH 049/113] Debug macro now always shows source location. --- src/common.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/common.h b/src/common.h index 7a45053..d357454 100644 --- a/src/common.h +++ b/src/common.h @@ -84,11 +84,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; /* handy debug printing macro */ #define DEBUG(level, ...) \ if (debug >= level) {\ - if (level >= 3) {\ - fprintf(stderr, "[D%d %s:%d] ", level, __FILE__, __LINE__); \ - } else { \ - fprintf(stderr, "[D%d] ", level);\ - }\ + fprintf(stderr, "[D%d %s:%d] ", level, __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ }\ From 4983dadfddc2c1b026bb37a31097c2dee1482481 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:29:55 +0800 Subject: [PATCH 050/113] Added client command line options + fixed version text --- src/iodine.c | 38 ++++++++++++++++++++++---------------- src/iodined.c | 2 +- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index 291ade2..76090d2 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -70,7 +70,7 @@ print_usage() extern char *__progname; fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-f] [-r] [-u user] [-t chrootdir] [-d device] " - "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-c 0|1] [-C 0|1] " + "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]]\n", __progname); } @@ -92,7 +92,8 @@ help() fprintf(stderr, " -O force downstream encoding for -T other than NULL: Base32, Base64, Base64u,\n"); fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); fprintf(stderr, " -I target interval between sending and receiving requests (default: 4 secs)\n"); - fprintf(stderr, " should be greater than the round-trip for the connection\n"); + fprintf(stderr, " or ping interval in immediate mode (default: 1 sec)\n"); + fprintf(stderr, " -s minimum interval between queries (default: 1ms)\n"); fprintf(stderr, " -L 1: use lazy mode for low-latency (default). 0: don't (implies -I1)\n"); fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); @@ -102,8 +103,7 @@ help() fprintf(stderr, "Fine-tuning options:\n"); fprintf(stderr, " -w downstream fragment window size (default: 8)\n"); fprintf(stderr, " -W upstream fragment window size (default: 8)\n"); - fprintf(stderr, " -i server-side request timeout in lazy mode \n"); - fprintf(stderr, " (default: automatically adjust from max timeout and round-trip time)\n"); + fprintf(stderr, " -i server-side request timeout in lazy mode (default: auto)\n"); fprintf(stderr, " -c 1: use downstream compression (default), 0: disable\n"); fprintf(stderr, " -C 1: use upstream compression, 0: disable (default)\n\n"); @@ -131,7 +131,7 @@ static void version() { fprintf(stderr, "iodine IP over DNS tunneling client\n"); - fprintf(stderr, "Git version: %s\n; protocol version %08X", GITREVISION, PROTOCOL_VERSION); + fprintf(stderr, "Git version: %s; protocol version %08X\n", GITREVISION, PROTOCOL_VERSION); exit(0); } @@ -160,8 +160,9 @@ main(int argc, char **argv) int retval; int raw_mode; int lazymode; - double max_interval_sec; - double server_timeout_sec ; + double target_interval_sec; + double server_timeout_sec; + int min_interval_ms; int autodetect_server_timeout; int up_compression; int down_compression; @@ -206,7 +207,8 @@ main(int argc, char **argv) retval = 0; raw_mode = 1; lazymode = 1; - max_interval_sec = 5; /* DNS RFC says 5 seconds minimum */ + target_interval_sec = 5; /* DNS RFC says 5 seconds minimum */ + min_interval_ms = 1; server_timeout_sec = 4; /* Safe value for RTT <1s */ autodetect_server_timeout = 1; hostname_maxlen = 0xFF; @@ -232,7 +234,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfDhrV:c:C:i:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhrs:V:c:C:i:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -313,13 +315,13 @@ main(int argc, char **argv) lazymode = 1; if (lazymode < 0) lazymode = 0; - if (!lazymode && max_interval_sec > 1) - max_interval_sec = 1; + if (!lazymode && target_interval_sec > 1) + target_interval_sec = 1; break; case 'I': - max_interval_sec = strtod(optarg, NULL); - if (max_interval_sec < 1) - max_interval_sec = 1; + target_interval_sec = strtod(optarg, NULL); + if (target_interval_sec < 1) + target_interval_sec = 1; break; case 'i': server_timeout_sec = strtod(optarg, NULL); @@ -327,6 +329,10 @@ main(int argc, char **argv) server_timeout_sec = 0.4; autodetect_server_timeout = 0; break; + case 's': + min_interval_ms = atoi(optarg); + if (min_interval_ms < 1) + min_interval_ms = 1; case 'w': down_windowsize = atoi(optarg); break; @@ -388,7 +394,6 @@ main(int argc, char **argv) nameserv_host = NULL; } - if (nameserv_addrs_len <= 0 || !nameserv_hosts[0]) { warnx("No nameserver found - not connected to any network?\n"); usage(); @@ -414,7 +419,8 @@ main(int argc, char **argv) } client_set_compression(up_compression, down_compression); - client_set_dnstimeout(max_interval_sec, server_timeout_sec, autodetect_server_timeout); + client_set_dnstimeout(target_interval_sec, server_timeout_sec, autodetect_server_timeout); + client_set_interval(target_interval_sec * 1000.0, min_interval_ms); client_set_lazymode(lazymode); client_set_topdomain(topdomain); client_set_hostname_maxlen(hostname_maxlen); diff --git a/src/iodined.c b/src/iodined.c index 65f26e6..643d751 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -207,7 +207,7 @@ help() { static void version() { fprintf(stderr, "iodine IP over DNS tunneling server\n"); - fprintf(stderr, "Git version: %s\n; protocol version %08X", GITREVISION, PROTOCOL_VERSION); + fprintf(stderr, "Git version: %s; protocol version %08X\n", GITREVISION, PROTOCOL_VERSION); exit(0); } From 8354ce28aaedad1a2126d447b534bb985808f09c Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:30:46 +0800 Subject: [PATCH 051/113] Updated fragment timeouts to use timeval for better precision --- src/window.c | 56 ++++++++++++++++++++++++++++++++++------------------ src/window.h | 40 ++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/window.c b/src/window.c index 0ca76c2..4cd6c6d 100644 --- a/src/window.c +++ b/src/window.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,8 @@ window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int di buf->maxfraglen = fragsize; buf->window_end = AFTER(buf, windowsize); buf->direction = dir; + buf->timeout.tv_sec = 5; + buf->timeout.tv_usec = 0; return buf; } @@ -121,8 +124,9 @@ window_append_fragment(struct frag_buffer *w, fragment *src) } /* Handles fragment received from the sending side (RECV) - * Returns seq ID of packet to be ACKed immediately */ -int + * Returns index of fragment in window or <0 if dropped + * The next ACK MUST be for this fragment */ +ssize_t window_process_incoming_fragment(struct frag_buffer *w, fragment *f) { /* Check if packet is in window */ @@ -131,30 +135,31 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) startid = w->start_seq_id; endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; if (!INWINDOW_SEQ(startid, endid, f->seqID)) { - WDEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", - f->seqID, startid, endid); + WDEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); w->oos++; - /* ACK duplicate so sender can move on ASAP */ - return f->seqID; + return -1; } /* Place fragment into correct location in buffer */ - size_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); - WDEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); + ssize_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); + WDEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", + f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); /* Check if fragment already received */ fd = &w->frags[dest]; if (fd->len != 0) { WDEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); if (f->seqID == fd->seqID) - return f->seqID; + return -1; } memcpy(fd, f, sizeof(fragment)); - fd->retries = 0; - fd->ack_other = -1; - /* We assume this packet gets ACKed immediately on return of this function */ - fd->acks = 1; w->numitems ++; - return f->seqID; + fd->retries = 0; + fd->ack_other = -1; + + /* We assume this packet gets ACKed immediately on return of this function */ + fd->acks = 1; + + return dest; } /* Reassembles first complete sequence of fragments into data. (RECV) @@ -235,14 +240,21 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int size_t window_sending(struct frag_buffer *w) { + struct timeval timeout, now; fragment *f; size_t tosend = 0; + if (w->numitems == 0) return 0; + + gettimeofday(&now, NULL); + for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[WRAP(w->window_start + i)]; if (f->len == 0 || f->acks >= 1) continue; - if (f->retries < 1 || difftime(time(NULL), f->lastsent) >= ACK_TIMEOUT) { + + timeradd(&w->timeout, &f->lastsent, &timeout); + if (f->retries < 1 || !timercmp(&now, &timeout, <)) { /* Fragment not sent or timed out (to be re-sent) */ tosend++; } @@ -255,14 +267,21 @@ window_sending(struct frag_buffer *w) fragment * window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) { + struct timeval timeout, now; fragment *f = NULL; + if (*other_ack >= MAX_SEQ_ID || *other_ack < 0) *other_ack = -1; + + gettimeofday(&now, NULL); + for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[WRAP(w->window_start + i)]; if (f->acks >= 1) continue; - /* TODO: use timeval for more precise timeouts */ - if (f->retries >= 1 && difftime(time(NULL), f->lastsent) >= ACK_TIMEOUT) { + + timeradd(&w->timeout, &f->lastsent, &timeout); + + if (f->retries >= 1 && !timercmp(&now, &timeout, <)) { /* Fragment sent before, not ACK'd */ WDEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); w->resends ++; @@ -271,7 +290,6 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) /* Fragment not sent */ goto found; } - } if (f) WDEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", @@ -286,7 +304,7 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) f->start &= 1; f->end &= 1; f->retries++; - time(&f->lastsent); + gettimeofday(&f->lastsent, NULL); return f; } diff --git a/src/window.h b/src/window.h index d5831dd..520d0eb 100644 --- a/src/window.h +++ b/src/window.h @@ -19,7 +19,6 @@ #define MAX_SEQ_ID 256 #define MAX_FRAGSIZE 4096 -#define ACK_TIMEOUT 5 #define WINDOW_SENDING 1 #define WINDOW_RECVING 0 @@ -29,33 +28,34 @@ //#define WINDOW_DEBUG typedef struct fragment { - size_t len; /* Length of fragment data (0 if fragment unused) */ - unsigned seqID; /* fragment sequence ID */ - int ack_other; /* other way ACK seqID (>=0) or unset (<0) */ - int compressed; /* compression flag */ - uint8_t start; /* start of chunk flag */ - uint8_t end; /* end of chunk flag */ + size_t len; /* Length of fragment data (0 if fragment unused) */ + unsigned seqID; /* fragment sequence ID */ + int ack_other; /* other way ACK seqID (>=0) or unset (<0) */ + int compressed; /* compression flag */ + uint8_t start; /* start of chunk flag */ + uint8_t end; /* end of chunk flag */ uint8_t data[MAX_FRAGSIZE]; /* fragment data */ - unsigned retries; /* number of times fragment has been sent */ - time_t lastsent; /* timestamp of most recent send attempt TODO: millisecond precision*/ - int acks; /* number of times packet has been ack'd (should be <= 1) */ + unsigned retries; /* number of times fragment has been sent */ + struct timeval lastsent; /* timestamp of most recent send attempt */ + int acks; /* number of times packet has been ack'd (should be <= 1) */ } fragment; struct frag_buffer { - fragment *frags; /* pointer to array of data fragments */ + fragment *frags; /* pointer to array of data fragments */ unsigned windowsize; /* Max number of packets in flight */ unsigned maxfraglen; /* Max fragment size */ - size_t length; /* Length of buffer */ - size_t numitems; /* number of non-empty fragments stored in buffer */ + size_t length; /* Length of buffer */ + size_t numitems; /* number of non-empty fragments stored in buffer */ size_t window_start; /* Start of window */ - size_t window_end; /* End of window (index) */ - size_t last_write; /* Last fragment read/written */ - size_t chunk_start; /* Start of current chunk of fragments, ie where fragno = 0 */ + size_t window_end; /* End of window (index) */ + size_t last_write; /* Last fragment read/written */ + size_t chunk_start; /* Start of current chunk of fragments, ie where fragno = 0 */ unsigned cur_seq_id; /* Most recent sequence ID */ unsigned start_seq_id; /* Start of window sequence ID */ - unsigned resends; /* number of fragments resent */ - unsigned oos; /* Number of out-of-sequence fragments received */ - int direction; /* Sending or recving */ + unsigned resends; /* number of fragments resent */ + unsigned oos; /* Number of out-of-sequence fragments received */ + int direction; /* Sending or recving */ + struct timeval timeout; /* Fragment timeout before resend */ }; extern int window_debug; @@ -113,7 +113,7 @@ size_t window_buffer_available(struct frag_buffer *w); int window_append_fragment(struct frag_buffer *w, fragment *src); /* Handles fragment received from the sending side (RECV) */ -int window_process_incoming_fragment(struct frag_buffer *w, fragment *f); +ssize_t window_process_incoming_fragment(struct frag_buffer *w, fragment *f); /* Reassembles first complete sequence of fragments into data. (RECV) * Returns length of data reassembled, or 0 if no data reassembled */ From 1c1f0b76ba1a1711b09d63e1bd38433410aa091d Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:31:25 +0800 Subject: [PATCH 052/113] Updated protocol docs --- doc/proto_00000800.txt | 97 ++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index a62c056..afdd1bc 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -23,7 +23,7 @@ Quick alphabetical index / register: Z Upstream codec check -CMC = 2 byte Cache Miss Counter, increased every time it is used +CMC = Cache Miss Counter, increased every time it is used Version: Client sends: @@ -171,13 +171,13 @@ Server sends: Upstream data header: 76543 21076 54321076 54321076 5432 +!----+!----+!----!--+--!----!+----+ - |0UUUU|UDCMC|SSSSSSSS|DDDDDDDD|ACFL| + |0UUUU|UDCMC| Seq ID | Dn ACK |ACFL| +-----+-----+--------+--------+----+ -Downstream data header: |=> only if P(ing) bit set | - 76543210 76543210 76543210 76543210 76543210 76543210 76543210 - +--------+--------+--------+--------+--------+--------+--------+ - |DDDDDDDD|SSSSSSSS|00IPACFL|ZZZZZZZZ|WWWWWWWW|XXXXXXXX|YYYYYYYY| +Downstream data header: |=> only if ping (P) flag set | + 0 1 2 3 4 5 6 + +--------+--------+76543210+--------+--------+--------+--------+ + | Seq ID | Up ACK |00IPACFL|Dn Wsize|Up Wsize|DnWstart|UpWstart| +--------+--------+--------+--------+--------+--------+--------+ UUUU = Userid @@ -186,21 +186,20 @@ A = ACK flag F = First fragment flag C = Compression enabled for downstream packet P = ping flag: extra header present -I = responded to immediately (for RTT calculation) -SSSSSSSS = Upstream packet sequence number/ACK -DDDDDDDD = Downstream packet sequence number/ACK -ZZZZZZZZ = Downstream window size -WWWWWWWW = Upstream window size -XXXXXXXX = Downstream window start seqID -YYYYYYYY = Upstream window start seqID -UDCMC = Upstream Data CMC char (actually base36 [a-z0-9]), case-insensitive +I = responded to immediately (for RTT calculation) - downstream only +UDCMC = Upstream Data CMC char (base36 [a-z0-9]) + +Up/Dn Wsize/Wstart = upstream/downstream window size/window start Seq ID Upstream data packet starts with 1 byte ASCII hex coded user byte; then 1 char data-CMC; then 4 bytes Base32 encoded header; then comes the payload data, encoded with the chosen upstream codec. -Downstream data starts with 3 byte headerm, followed by data, which may be -compressed. If Ping flag is set, another 4 bytes are appended to the header. +Downstream data starts with 3 byte header, followed by data, which may be +compressed. If Ping flag is set, another 4 bytes are appended to the header, +containing upstream and downstream window sizes and window start sequence IDs. +The response does not need to contain data. If the server has no data to send, +the response will always include the ping header and the ping flag will be set. In NULL and PRIVATE responses, downstream data is always raw. In all other response types, downstream data is encoded (see Options above). @@ -229,10 +228,10 @@ Client sends: First byte p or P Second byte CMC Rest encoded with Base32: - 76543210 76543210 76543210 - +--------+--------+---+--------+ - |0000UUUU|DownSQID|...|0000TANR| - +--------+--------+---+--------+ + 0 1 2...8 9-10 + +--------+--------+---+76543210+---+ + |0000UUUU|Dn SeqID|...|0000TANR|CMC| + +--------+--------+---+--------+---+ 4 bits Userid 1 byte Downstream seq ID ACK 1 byte window size (upstream) @@ -245,18 +244,19 @@ Client sends: T = update server timeout A = is ACKing downstream frag N = is NACKing downstream frag (unused) - R = respond with a data/ping packet + R = response must contain ping header (data optional) 2 bytes CMC -The server response to Ping and Data packets is a DNS NULL/TXT/.. type response, -always starting with the 3 bytes downstream data header as shown above. If R bit -set, server must respond with a ping downstream header. This also requires the -server to set its windowsizes to the ones provided. +The server responses to Ping and Data packets are compatible, and are described +above (refer to downstream data header). + +If R (respond) bit is set, the server responds immediately with a ping header. +The server must also adjust its window sizes to those provided by the ping. If the T but is set, the server sets the user's DNS timeout to the value spec- -ified by the packet. A timeout value of 0 implies disabling lazy mode. -If server has nothing to send, no data is added after the header. -If server has something to send, it will add the downstream data packet -(or some fragment of it) after the header. +ified by the packet. + +In lazy mode, unless the R flag is set, the server will hold the ping until it +times out or more data becomes available to send. "Lazy-mode" operation @@ -271,34 +271,39 @@ implementation is original to iodine, no code or documentation from any other project was consulted during development. Server: -Upstream data is acked immediately*, to keep the slow upstream data flowing -as fast as possible (client waits for ack to send next frag). +In lazy mode, except where otherwise specified, responses are sent using the +oldest pending query held in the server's buffer (QMEM). The server responds +to a stored pending query when the query times out, an upstream ACK is pending +(for that user), or the server has an excess of pending queries (more than the +user's downstream window size). -Upstream pings are answered _only_ when 1) downstream data arrives from tun, -OR 2) new upstream ping/data arrives from client. -In most cases, this means we answer the previous DNS query instead of the -current one. The current query is kept in queue and used as soon as -downstream data has to be sent. +Upstream data fragments are ACK'd immediately to keep data flowing. -*: upstream data ack is usually done as reply on the previous ping packet, -and the upstream-data packet itself is kept in queue. +Upstream pings are answered immediately only when the Respond flag is set (see +ping header), in which case the response is to the same DNS query as the ping. +Immediate responses (<10ms old) to either ping or data requests are marked +and used to calculate the round-trip-time for the connection. Client: -Downstream data is acked immediately, to keep it flowing fast (includes a -ping after last downstream frag). - -Also, after all available upstream data is sent & acked by the server (which -in some cases uses up the last query), send an additional ping to prime the -server for the next downstream data. +The client keeps track of all queries it sends, and maintains a minimum of + pending queries to fill the server buffer. +Downstream data is always ACK'd immediately with a new request (either a ping +or data if available). The client sends excess requests (ie. already has enough +pending queries) for ACKs or for new data. ====================================================== 2. Raw UDP protocol ====================================================== +This protocol does not implement data windowing and does not guarantee data +delivery, however it is likely faster due to the fact that data is not split +into fragments for sending. Full packets are compressed and sent when they +arrive on the tun device, and are processed immediately on the other side. + All Raw UDP protcol messages start with a 3 byte header: 0x10d19e This is not the start of a valid DNS message so it is easy to identify. -The fourth byte contains the command and the user id. +The fourth byte contains the command (C) and the user id (U). 7654 3210 +----+----+ @@ -313,7 +318,7 @@ After the login message has been exchanged, both the server and the client switch to raw udp mode for the rest of the connection. Data message (command = 2): -After the header comes the payload data, which may be compressed. +After the header comes the payload data, which is always compressed. Ping message (command = 3): Sent from client to server and back to keep session open. Has no payload. From 9fae60d241bcb8423e54df4b9522f27460535d3f Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 17 Oct 2015 22:31:53 +0800 Subject: [PATCH 053/113] Updated window test for modified ACK behaviour --- tests/window.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/window.c b/tests/window.c index 393814e..6920d33 100644 --- a/tests/window.c +++ b/tests/window.c @@ -50,10 +50,10 @@ START_TEST(test_window_everything) // warnx("Nothing to send."); continue; } - fail_if((a = window_process_incoming_fragment(in, f)) != f->seqID, "Did not ACK last seqId!"); + fail_if(window_process_incoming_fragment(in, f) < 0, "Dropped fragment!"); // warnx("Received fragment with seqid %u, remaining space %lu.", f->seqID, window_buffer_available(in)); window_tick(in); - window_ack(out, a); + window_ack(out, f->seqID); window_tick(out); fail_if(out->start_seq_id != in->start_seq_id, "in/out windows have different start IDs!"); } From 87edca99b44ff4da2bc33a944c82bf5cd1964500 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 17:25:56 +0800 Subject: [PATCH 054/113] Added downstream window fragment timeout option (-j) --- src/client.c | 50 +++++++++++++++++++++++++------------------- src/client.h | 2 +- src/iodine.c | 59 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 72 insertions(+), 39 deletions(-) diff --git a/src/client.c b/src/client.c index 46a87ca..fc84c6d 100644 --- a/src/client.c +++ b/src/client.c @@ -94,8 +94,9 @@ static time_t max_timeout_ms; static time_t send_interval_ms; static time_t min_send_interval_ms; -/* Server response timeout in ms */ +/* Server response timeout in ms and downstream window timeout */ static time_t server_timeout_ms; +static time_t downstream_timeout_ms; static int autodetect_server_timeout; /* Cumulative Round-Trip-Time in ms */ @@ -185,6 +186,7 @@ client_init() rtt_total_ms = 1000; send_interval_ms = 1000; min_send_interval_ms = 1; + downstream_timeout_ms = 5000; outbuf = NULL; inbuf = NULL; @@ -282,10 +284,11 @@ client_set_compression(int up, int down) } void -client_set_dnstimeout(double timeout, double servertimeout, int autodetect) +client_set_dnstimeout(double timeout, double servertimeout, double downfrag, int autodetect) { max_timeout_ms = timeout * 1000; server_timeout_ms = servertimeout * 1000; + downstream_timeout_ms = downfrag * 1000; autodetect_server_timeout = autodetect; } @@ -385,11 +388,13 @@ update_server_timeout(int dns_fd, int handshake) server_timeout_ms = max_timeout_ms - rtt_ms; if (server_timeout_ms <= 0) { server_timeout_ms = 0; - fprintf(stderr, "Setting server timeout to 0 ms: if this continues try disabling lazy mode.\n"); + fprintf(stderr, "Setting server timeout to 0 ms: if this continues try disabling lazy mode. (-L0)\n"); } } - /* TODO: update window timeout */ + /* update up/down window timeouts to something reasonable */ + downstream_timeout_ms = rtt_ms * 2; + outbuf->timeout = ms_to_timeval(downstream_timeout_ms); if (handshake) { /* Send ping handshake to set server timeout */ @@ -626,7 +631,7 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) } int -send_ping(int fd, int ping_response, int ack, int timeout) +send_ping(int fd, int ping_response, int ack, int set_timeout) { num_pings++; if (conn == CONN_DNS_NULL) { @@ -645,15 +650,17 @@ send_ping(int fd, int ping_response, int ack, int timeout) } *(uint16_t *) (data + 6) = htons(server_timeout_ms); + *(uint16_t *) (data + 8) = htons(downstream_timeout_ms); - /* update server lazy timeout, ack flag, respond with ping flag */ - data[8] = ((timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); - data[9] = (rand_seed >> 8) & 0xff; - data[10] = (rand_seed >> 0) & 0xff; + /* update server frag/lazy timeout, ack flag, respond with ping flag */ + data[10] = ((set_timeout & 1) << 4) | ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); + data[11] = (rand_seed >> 8) & 0xff; + data[12] = (rand_seed >> 0) & 0xff; rand_seed += 263; - DEBUG(3, " SEND PING: respond %d, ack %d, server timeout %ld, flags %02X", - ping_response, ack, server_timeout_ms, data[8]); + DEBUG(3, " SEND PING: respond %d, ack %d, %s(server %ld ms, downfrag %ld ms), flags %02X", + ping_response, ack, set_timeout ? "SET " : "", server_timeout_ms, + downstream_timeout_ms, data[8]); id = send_packet(fd, 'p', data, sizeof(data)); @@ -1014,7 +1021,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) if (r == 0) return -3; /* select timeout */ - q.id = 0; + q.id = -1; q.name[0] = '\0'; rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q); @@ -1373,6 +1380,7 @@ client_tunnel(int tun_fd, int dns_fd) if (sending > 0 || (total > 0 && lazymode)) { tv = ms_to_timeval(min_send_interval_ms); + tv.tv_usec += 1; } else if (total > 0 && !lazymode) { /* use immediate mode send interval if nothing pending */ tv = ms_to_timeval(send_interval_ms); @@ -1392,12 +1400,12 @@ client_tunnel(int tun_fd, int dns_fd) num_recv - recv_since_report, (num_recv - recv_since_report) / stats); fprintf(stderr, " num IP rejected: %4lu, untracked: %4lu, lazy mode: %1d\n", num_badip, num_untracked, lazymode); - fprintf(stderr, " Min send: %4ld ms, Avg RTT: %4ld ms, immediate replies: %5lu\n", - min_send_interval_ms, rtt_total_ms / num_immediate, num_immediate); - fprintf(stderr, " query timeouts: %4lu, target: %4ld ms, server: %4ld ms\n", - num_timeouts, max_timeout_ms, server_timeout_ms); - fprintf(stderr, " Resent fragments up: %4u, downstream out of window: %4u\n", - outbuf->resends, inbuf->oos); + fprintf(stderr, " Min send: %5ld ms, Avg RTT: %5ld ms Timeout server: %4ld ms\n", + min_send_interval_ms, rtt_total_ms / num_immediate, server_timeout_ms); + fprintf(stderr, " Queries immediate: %5lu, timed out: %4lu target: %4ld ms\n", + num_immediate, num_timeouts, max_timeout_ms); + fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4ld ms\n", + outbuf->resends, inbuf->oos, downstream_timeout_ms); fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", num_frags_sent, num_frags_recv, num_pings); @@ -1568,8 +1576,8 @@ send_upenctest(int fd, char *s) buf[3] = b32_5to8((rand_seed ) & 0x1f); rand_seed++; - strncat(buf, s, 512); - strncat(buf, ".", 512); + strncat(buf, s, 512 - strlen(buf)); + strncat(buf, ".", 512 - strlen(buf)); strncat(buf, topdomain, 512 - strlen(buf)); send_query(fd, (uint8_t *)buf); } @@ -2573,6 +2581,7 @@ handshake_set_timeout(int dns_fd) read = handshake_waitdns(dns_fd, in, sizeof(in), 'P', i + 1); got_response(id, 1, 0); + fprintf(stderr, "."); if (read > 0) { if (strncmp("BADIP", in, 5) == 0) { fprintf(stderr, "Server rejected sender IP address.\n"); @@ -2583,7 +2592,6 @@ handshake_set_timeout(int dns_fd) break; } - fprintf(stderr, "."); } if (!running) return; diff --git a/src/client.h b/src/client.h index 198b150..92d4983 100644 --- a/src/client.h +++ b/src/client.h @@ -44,7 +44,7 @@ int client_set_qtype(char *qtype); char *client_get_qtype(); void client_set_downenc(char *encoding); void client_set_compression(int up, int down); -void client_set_dnstimeout(double, double, int); +void client_set_dnstimeout(double, double, double, int); void client_set_lazymode(int lazy_mode); void client_set_windowsize(size_t, size_t); void client_set_hostname_maxlen(size_t i); diff --git a/src/iodine.c b/src/iodine.c index 76090d2..a7b96b3 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -70,7 +70,7 @@ print_usage() extern char *__progname; fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-f] [-r] [-u user] [-t chrootdir] [-d device] " - "[-w downfrags] [-W upfrags] [-i sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " + "[-w downfrags] [-W upfrags] [-i sec -j sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]]\n", __progname); } @@ -93,7 +93,7 @@ help() fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); fprintf(stderr, " -I target interval between sending and receiving requests (default: 4 secs)\n"); fprintf(stderr, " or ping interval in immediate mode (default: 1 sec)\n"); - fprintf(stderr, " -s minimum interval between queries (default: 1ms)\n"); + fprintf(stderr, " -s minimum interval between queries (default: 0ms)\n"); fprintf(stderr, " -L 1: use lazy mode for low-latency (default). 0: don't (implies -I1)\n"); fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); @@ -101,11 +101,12 @@ help() fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n\n"); fprintf(stderr, "Fine-tuning options:\n"); - fprintf(stderr, " -w downstream fragment window size (default: 8)\n"); - fprintf(stderr, " -W upstream fragment window size (default: 8)\n"); + fprintf(stderr, " -w downstream fragment window size (default: 8 frags)\n"); + fprintf(stderr, " -W upstream fragment window size (default: 8 frags)\n"); fprintf(stderr, " -i server-side request timeout in lazy mode (default: auto)\n"); + fprintf(stderr, " -j downstream fragment ACK timeout, implies -i4 (default: 2 sec)\n"); fprintf(stderr, " -c 1: use downstream compression (default), 0: disable\n"); - fprintf(stderr, " -C 1: use upstream compression, 0: disable (default)\n\n"); + fprintf(stderr, " -C 1: use upstream compression (default), 0: disable\n\n"); fprintf(stderr, "Other options:\n"); fprintf(stderr, " -v to print version info and exit\n"); @@ -162,6 +163,7 @@ main(int argc, char **argv) int lazymode; double target_interval_sec; double server_timeout_sec; + double downstream_timeout_sec; int min_interval_ms; int autodetect_server_timeout; int up_compression; @@ -183,6 +185,7 @@ main(int argc, char **argv) int nameservaddr_len; int nameserv_family; + /* Set default values */ nameserv_addrs_len = 0; nameservaddr_len = 0; nameserv_host = NULL; @@ -208,12 +211,13 @@ main(int argc, char **argv) raw_mode = 1; lazymode = 1; target_interval_sec = 5; /* DNS RFC says 5 seconds minimum */ - min_interval_ms = 1; + min_interval_ms = 0; server_timeout_sec = 4; /* Safe value for RTT <1s */ + downstream_timeout_sec = 2; autodetect_server_timeout = 1; hostname_maxlen = 0xFF; nameserv_family = AF_UNSPEC; - up_compression = 0; + up_compression = 1; down_compression = 1; up_windowsize = 8; @@ -234,7 +238,7 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfDhrs:V:c:C:i:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { + while ((choice = getopt(argc, argv, "46vfDhrs:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { switch(choice) { case '4': nameserv_family = AF_INET; @@ -315,24 +319,25 @@ main(int argc, char **argv) lazymode = 1; if (lazymode < 0) lazymode = 0; - if (!lazymode && target_interval_sec > 1) - target_interval_sec = 1; break; case 'I': target_interval_sec = strtod(optarg, NULL); - if (target_interval_sec < 1) - target_interval_sec = 1; break; case 'i': server_timeout_sec = strtod(optarg, NULL); - if (server_timeout_sec < 0.4) - server_timeout_sec = 0.4; autodetect_server_timeout = 0; break; + case 'j': + downstream_timeout_sec = strtod(optarg, NULL); + if (autodetect_server_timeout) { + autodetect_server_timeout = 0; + server_timeout_sec = 4; + } + break; case 's': min_interval_ms = atoi(optarg); - if (min_interval_ms < 1) - min_interval_ms = 1; + if (min_interval_ms < 0) + min_interval_ms = 0; case 'w': down_windowsize = atoi(optarg); break; @@ -418,8 +423,28 @@ main(int argc, char **argv) usage(); } + if (target_interval_sec < 0.1) { + warnx("Target interval must be greater than 0.1 seconds!"); + usage(); + } + + if (server_timeout_sec < 0.1 || server_timeout_sec >= target_interval_sec) { + warnx("Server timeout must be greater than 0.1 sec and less than target interval!"); + usage(); + } + + if (downstream_timeout_sec < 0.1) { + warnx("Downstream fragment timeout must be more than 0.1 sec to prevent excessive retransmits."); + usage(); + } + + if (!lazymode && target_interval_sec > 1) { + warnx("Warning: Target interval of >1 second in immediate mode will cause high latency."); + usage(); + } + client_set_compression(up_compression, down_compression); - client_set_dnstimeout(target_interval_sec, server_timeout_sec, autodetect_server_timeout); + client_set_dnstimeout(target_interval_sec, server_timeout_sec, downstream_timeout_sec, autodetect_server_timeout); client_set_interval(target_interval_sec * 1000.0, min_interval_ms); client_set_lazymode(lazymode); client_set_topdomain(topdomain); From 91c095e0633a7cf61947850314d1efb0e28f34ac Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 17:27:11 +0800 Subject: [PATCH 055/113] Minor adjustments and fixed some warnings --- src/common.c | 7 +++++-- src/common.h | 2 +- src/dns.c | 3 +-- src/util.c | 5 ++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/common.c b/src/common.c index befb533..19b4461 100644 --- a/src/common.c +++ b/src/common.c @@ -282,7 +282,9 @@ do_detach() { #ifndef WINDOWS32 fprintf(stderr, "Detaching from terminal...\n"); - daemon(0, 0); + if (daemon(0, 0) != 0) { + err(1, "Failed to detach from terminal. Try running in foreground."); + } umask(0); alarm(0); #else @@ -310,7 +312,8 @@ read_password(char *buf, size_t len) fprintf(stderr, "Enter password: "); fflush(stderr); #ifndef WINDOWS32 - fscanf(stdin, "%79[^\n]", pwd); + if (!fscanf(stdin, "%79[^\n]", pwd)) + err(1, "EOF while reading password!"); #else for (i = 0; i < sizeof(pwd); i++) { pwd[i] = getch(); diff --git a/src/common.h b/src/common.h index d357454..d942027 100644 --- a/src/common.h +++ b/src/common.h @@ -79,7 +79,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #define DOWNSTREAM_HDR 3 #define DOWNSTREAM_PING_HDR 7 #define UPSTREAM_HDR 6 -#define UPSTREAM_PING 6 +#define UPSTREAM_PING 11 /* handy debug printing macro */ #define DEBUG(level, ...) \ diff --git a/src/dns.c b/src/dns.c index b933a2d..e6a807e 100644 --- a/src/dns.c +++ b/src/dns.c @@ -408,7 +408,7 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz unsigned short type; char *data; unsigned short rlen; - int id; + uint16_t id; int rv; rv = 0; @@ -428,7 +428,6 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz ancount = ntohs(header->ancount); id = ntohs(header->id); - id = id & 0xFFFF; /* Kill any sign extension */ rlen = 0; diff --git a/src/util.c b/src/util.c index 5cbad72..5780031 100644 --- a/src/util.c +++ b/src/util.c @@ -43,7 +43,10 @@ get_resolvconf_addr() err(1, "/etc/resolv.conf"); while (feof(fp) == 0) { - fgets(buf, sizeof(buf), fp); + if (!fgets(buf, sizeof(buf), fp)) { + /* resolv.conf is empty (we got to EOF without reading anything yet */ + err(1, "/etc/resolv.conf is empty! Please specify a nameserver."); + } if (sscanf(buf, "nameserver %15s", addr) == 1) { rv = addr; From 50c9cb28ec027453b50f9affed8b344bd9587759 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 17:28:20 +0800 Subject: [PATCH 056/113] Successfully fails if data buffer is too small --- src/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/window.c b/src/window.c index 4cd6c6d..1e20d93 100644 --- a/src/window.c +++ b/src/window.c @@ -206,7 +206,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int } if (fraglen > maxlen) { WDEBUG("Data buffer too small! Reassembled %lu bytes.", datalen); - return datalen; + return 0; } /* Move window along to avoid weird issues */ From 08adc5da71a75f13130bfa95fc8dabb1c86d03fa Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 17:29:41 +0800 Subject: [PATCH 057/113] Added downstream window frag timeout and id=0 will now work. --- src/server.c | 98 ++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 53 deletions(-) diff --git a/src/server.c b/src/server.c index 5902704..393f7bc 100644 --- a/src/server.c +++ b/src/server.c @@ -150,21 +150,19 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr static void qmem_init(int userid) +/* initialize user QMEM and DNS cache (if enabled) */ { memset(&users[userid].qmem, 0, sizeof(struct qmem_buffer)); -// users[userid].qmem.end = 1; for (size_t i = 0; i < QMEM_LEN; i++) { users[userid].qmem.queries[i].q.id = -1; } - - // TODO dns cache init in qmem } static int qmem_is_cached(int dns_fd, int userid, struct query *q) /* Check if an answer for a particular query is cached in qmem * If so, sends an "invalid" answer or one from DNS cache - * Returns 1 if new query, 0 if cached (and then answered) */ + * Returns 0 if new query (ie. not cached), 1 if cached (and then answered) */ { struct qmem_buffer *buf; struct query *pq; @@ -186,7 +184,9 @@ qmem_is_cached(int dns_fd, int userid, struct query *q) continue; /* Aha! A match! */ + #ifdef USE_DNSCACHE + /* Check if answer is in DNS cache */ if (buf->queries[p].a.len) { data = (char *)buf->queries[p].a.data; len = buf->queries[p].a.len; @@ -194,13 +194,13 @@ qmem_is_cached(int dns_fd, int userid, struct query *q) dnscache = 1; } #endif + QMEM_DEBUG(2, userid, "OUT from qmem for '%s', %s", q->name, - dnscache ? "answer from DNS cache" : "sending invalid response"); - // TODO cache answers/respond using cache? (merge with dnscache) + dnscache ? "answer from DNS cache" : "sending invalid response"); write_dns(dns_fd, q, data, len, dataenc); - return 0; + return 1; } - return 1; + return 0; } static int @@ -211,15 +211,16 @@ qmem_append(int userid, struct query *q) buf = &users[userid].qmem; if (buf->num_pending >= QMEM_LEN) { - /* this means we have QMEM_LEN *pending* queries; don't overwrite */ - QMEM_DEBUG(2, userid, "full of pending queries. Not appending query with id %d.", q->id); - return 0; + /* this means we have QMEM_LEN *pending* queries; overwrite oldest one + * to prevent buildup of ancient queries */ + QMEM_DEBUG(2, userid, "Full of pending queries! Replacing old query %d with new %d.", + buf->queries[buf->start].q.id, q->id); } if (buf->length < QMEM_LEN) { buf->length++; } else { - /* will replace already answered query in this spot */ + /* will replace oldest query (in buf->queries[buf->start]) */ buf->start = (buf->start + 1) % QMEM_LEN; } @@ -1071,8 +1072,6 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) char buf[64*1024]; int len = 0; - // TODO: respond to duplicate queries here + handling qmem stuff - if (q->type == T_CNAME || q->type == T_A) { char cnamebuf[1024]; /* max 255 */ @@ -1263,7 +1262,6 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query tmp[0], tmp[1], my_mtu, netmask); write_dns(dns_fd, q, (char *)out, read, users[userid].downenc); - q->id = 0; syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); free(tmp[1]); @@ -1303,7 +1301,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } write_dns(dns_fd, q, reply, length, 'T'); - } else if(in[0] == 'Z' || in[0] == 'z') { + } else if(in[0] == 'Z' || in[0] == 'z') { /* Upstream codec check */ /* Check for case conservation and chars not allowed according to RFC */ /* Reply with received hostname as data */ @@ -1434,11 +1432,12 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (bits) { int f = users[userid].fragsize; users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; - DEBUG(1, "Setting max downstream data length to %u bytes for user %d; bits %d (%c)", - users[userid].outgoing->maxfraglen, userid, bits, users[userid].downenc); users[userid].downenc_bits = bits; } + DEBUG(1, "Options for user %d: down compression %d, data bits %d/maxlen %u (enc '%c'), lazy %d.", + userid, tmp_comp, bits, users[userid].outgoing->maxfraglen, tmp_downenc, tmp_lazy); + /* Store any changes */ users[userid].down_compression = tmp_comp; users[userid].downenc = tmp_downenc; @@ -1561,17 +1560,8 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; - int respond; - unsigned timeout_ms, set_timeout; - struct timeval timeout; - - /* We can't handle id=0, that's "no packet" to the dnscache. So drop - request completely. Note that DNS servers rewrite the id. - We'll drop 1 in 64k times. If DNS server retransmits with - different id, then all okay. - TODO don't use ID=0 to check if query */ - if (q->id == 0) - return; + int respond, set_qtimeout, set_wtimeout; + unsigned qtimeout_ms, wtimeout_ms; read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); if (read < UPSTREAM_PING) { @@ -1587,34 +1577,42 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } /* Check if cached */ - if (!qmem_is_cached(dns_fd, userid, q)) + if (qmem_is_cached(dns_fd, userid, q)) return; - dn_ack = ((unpacked[8] >> 2) & 1) ? unpacked[1] : -1; + 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]; - timeout_ms = ntohs(*(uint16_t *) (unpacked + 6)); - timeout = ms_to_timeval(timeout_ms); + /* Query timeout and window frag timeout */ + qtimeout_ms = ntohs(*(uint16_t *) (unpacked + 6)); + wtimeout_ms = ntohs(*(uint16_t *) (unpacked + 8)); - respond = unpacked[8] & 1; - set_timeout = (unpacked[8] >> 3) & 1; + respond = unpacked[10] & 1; + set_qtimeout = (unpacked[10] >> 3) & 1; + set_wtimeout = (unpacked[10] >> 4) & 1; - DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %stimeout %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 (flags %02X)", userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack, - set_timeout ? "SET " : "", timeout_ms, respond, unpacked[8]); + set_qtimeout ? "SET " : "", qtimeout_ms, set_wtimeout ? "SET " : "", + wtimeout_ms, respond, unpacked[10]); - if (set_timeout) { + if (set_qtimeout) { /* update user's query timeout if timeout flag set */ - users[userid].dns_timeout = timeout; + users[userid].dns_timeout = ms_to_timeval(qtimeout_ms); /* if timeout is 0, we do not enable lazy mode but it is effectively the same */ - int newlazy = !(timeout_ms == 0); + int newlazy = !(qtimeout_ms == 0); if (newlazy != users[userid].lazy) DEBUG(2, "User %d: not setting lazymode to %d with timeout %u", - userid, newlazy, timeout_ms); + userid, newlazy, qtimeout_ms); + } + + if (set_wtimeout) { + /* update sending window fragment ACK timeout */ + users[userid].outgoing->timeout = ms_to_timeval(wtimeout_ms); } qmem_append(userid, q); @@ -1642,16 +1640,6 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query if (domain_len < UPSTREAM_HDR + 1) return; - /* We can't handle id=0, that's "no packet" to us. So drop - request completely. Note that DNS servers rewrite the id. - We'll drop 1 in 64k times. If DNS server retransmits with - different id, then all okay. - Else client doesn't get our ack, and will retransmit in 1 second. */ - if (q->id == 0) { - DEBUG(1, "Query with ID 0!"); - return; - } - if ((in[0] >= '0' && in[0] <= '9')) code = in[0] - '0'; if ((in[0] >= 'a' && in[0] <= 'f')) @@ -1667,8 +1655,12 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query } /* Check if cached */ - if (!qmem_is_cached(dns_fd, userid, q)) - qmem_append(userid, q); + if (qmem_is_cached(dns_fd, userid, q)) { + /* if is cached, by this point it has already been answered */ + return; + } + + qmem_append(userid, q); /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ /* First byte (after userid) = CMC (ignored); skip 2 bytes */ len = sizeof(unpacked); From c633173fea019839146b7733cbe711d15912f095 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 17:31:49 +0800 Subject: [PATCH 058/113] Added window frag timeout to documentation --- doc/proto_00000800.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index afdd1bc..fbec870 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -230,7 +230,7 @@ Client sends: Rest encoded with Base32: 0 1 2...8 9-10 +--------+--------+---+76543210+---+ - |0000UUUU|Dn SeqID|...|0000TANR|CMC| + |0000UUUU|Dn SeqID|...|000WTANR|CMC| +--------+--------+---+--------+---+ 4 bits Userid 1 byte Downstream seq ID ACK @@ -239,8 +239,10 @@ Client sends: 1 byte window start (upstream) 1 byte window start (downstream) 2 bytes big-endian server timeout in ms + 2 bytes big-endian downstream fragment ACK timeout in ms 1 byte flags: + W = update window frag timeout T = update server timeout A = is ACKing downstream frag N = is NACKing downstream frag (unused) From 3bcfe91769cb2cd9479dee6fe1b9ee7c9eaf7bd3 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 18:33:44 +0800 Subject: [PATCH 059/113] Fixed raw UDP mode + improved debugging --- src/client.c | 97 +++++++++++++++++++++++++++------------------------- src/server.c | 43 ++++++++++++++--------- 2 files changed, 78 insertions(+), 62 deletions(-) diff --git a/src/client.c b/src/client.c index fc84c6d..0ba56a9 100644 --- a/src/client.c +++ b/src/client.c @@ -1123,14 +1123,7 @@ tunnel_tun(int tun_fd, int dns_fd) if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) return -1; - /* Check if outgoing buffer can hold data */ - if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { - DEBUG(1, " Outgoing buffer full (%lu/%lu), not adding data!", - outbuf->numitems, outbuf->length); - return -1; - } - - DEBUG(2, " IN: %lu bytes on tunnel, compression %d", read, compression_up); + DEBUG(2, " IN: %lu bytes on tunnel, to be compressed: %d", read, compression_up); if (conn != CONN_DNS_NULL || compression_up) { datalen = sizeof(out); @@ -1142,6 +1135,13 @@ tunnel_tun(int tun_fd, int dns_fd) } if (conn == CONN_DNS_NULL) { + /* Check if outgoing buffer can hold data */ + if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { + DEBUG(1, " Outgoing buffer full (%lu/%lu), not adding data!", + outbuf->numitems, outbuf->length); + return -1; + } + window_add_outgoing_data(outbuf, data, datalen, compression_up); /* Don't send anything here to respect min. send interval */ } else { @@ -1353,40 +1353,43 @@ client_tunnel(int tun_fd, int dns_fd) /* TODO: detect DNS servers which drop frequent requests * TODO: adjust number of pending queries based on current data rate */ - sending = window_sending(outbuf); - total = sending; - check_pending_queries(); - if (num_pending < windowsize_down && lazymode) - total = MAX(total, windowsize_down - num_pending); - else if (num_pending < 1 && !lazymode) - total = MAX(total, 1); + if (conn == CONN_DNS_NULL) { + sending = window_sending(outbuf); + total = sending; + check_pending_queries(); + if (num_pending < windowsize_down && lazymode) + total = MAX(total, windowsize_down - num_pending); + else if (num_pending < 1 && !lazymode) + total = MAX(total, 1); - if (sending > 0 || total > 0 || next_downstream_ack >= 0) { + if (sending > 0 || total > 0 || next_downstream_ack >= 0) { - /* Upstream traffic - this is where all ping/data queries are sent */ - if (sending > 0) { - /* More to send - next fragment */ - send_next_frag(dns_fd); - } else { - /* Send ping if we didn't send anything yet */ - send_ping(dns_fd, 0, next_downstream_ack, (num_pings > 20 && num_pings % 50 == 0)); - next_downstream_ack = -1; + /* Upstream traffic - this is where all ping/data queries are sent */ + if (sending > 0) { + /* More to send - next fragment */ + send_next_frag(dns_fd); + } else { + /* Send ping if we didn't send anything yet */ + send_ping(dns_fd, 0, next_downstream_ack, (num_pings > 20 && num_pings % 50 == 0)); + next_downstream_ack = -1; + } + + sending--; + total--; + QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu, will send another %d", + lazymode ? windowsize_down : 1, total); + + if (sending > 0 || (total > 0 && lazymode)) { + /* TODO: enforce min send interval even if we get new data */ + tv = ms_to_timeval(min_send_interval_ms); + tv.tv_usec += 1; + } else if (total > 0 && !lazymode) { + /* use immediate mode send interval if nothing pending */ + tv = ms_to_timeval(send_interval_ms); + } + + send_ping_soon = 0; } - - sending--; - total--; - QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu, will send another %d", - lazymode ? windowsize_down : 1, total); - - if (sending > 0 || (total > 0 && lazymode)) { - tv = ms_to_timeval(min_send_interval_ms); - tv.tv_usec += 1; - } else if (total > 0 && !lazymode) { - /* use immediate mode send interval if nothing pending */ - tv = ms_to_timeval(send_interval_ms); - } - - send_ping_soon = 0; } if (stats) { @@ -1404,11 +1407,12 @@ client_tunnel(int tun_fd, int dns_fd) min_send_interval_ms, rtt_total_ms / num_immediate, server_timeout_ms); fprintf(stderr, " Queries immediate: %5lu, timed out: %4lu target: %4ld ms\n", num_immediate, num_timeouts, max_timeout_ms); - fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4ld ms\n", - outbuf->resends, inbuf->oos, downstream_timeout_ms); - fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", - num_frags_sent, num_frags_recv, num_pings); - + if (conn == CONN_DNS_NULL) { + fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4ld ms\n", + outbuf->resends, inbuf->oos, downstream_timeout_ms); + fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", + num_frags_sent, num_frags_recv, num_pings); + } /* update since-last-report stats */ sent_since_report = num_sent; recv_since_report = num_recv; @@ -1420,11 +1424,12 @@ client_tunnel(int tun_fd, int dns_fd) if (send_ping_soon) { tv.tv_sec = 0; tv.tv_usec = send_ping_soon * 1000; + send_ping_soon = 0; } FD_ZERO(&fds); - if (window_buffer_available(outbuf) > 16) { - /* Fill up outgoing buffer with available data + if (conn != CONN_DNS_NULL || window_buffer_available(outbuf) > 16) { + /* Fill up outgoing buffer with available data if it has enough space * The windowing protocol manages data retransmits, timeouts etc. */ FD_SET(tun_fd, &fds); } diff --git a/src/server.c b/src/server.c index 393f7bc..20a251f 100644 --- a/src/server.c +++ b/src/server.c @@ -853,14 +853,18 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri { char myhash[16]; - if (len < 16) return; + if (len < 16) { + DEBUG(2, "Invalid raw login packet: length %lu < 16 bytes!", len); + return; + } - /* can't use check_authenticated_user_and_ip() since IP address will be different, - so duplicate here except IP address */ - if (userid < 0 || userid >= created_users) return; - if (!check_authenticated_user_and_ip(userid, q)) return; + if (userid < 0 || userid >= created_users || + check_authenticated_user_and_ip(userid, q) != 0) { + DEBUG(2, "User %d not authenticated, ignoring raw login!", userid); + return; + } - DEBUG(1, "IN login raw, len %lu, from user %d", len, userid); + DEBUG(1, "RX-raw: login, len %lu, from user %d", len, userid); /* User sends hash of seed + 1 */ login_calculate(myhash, 16, password, users[userid].seed + 1); @@ -892,9 +896,9 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_ /* Update time info for user */ users[userid].last_pkt = time(NULL); - /* copy to packet buffer, update length TODO fix the raw UDP protocol */ + /* copy to packet buffer, update length */ - DEBUG(3, "IN pkt raw, total %lu, from user %d", len, userid); + DEBUG(3, "RX-raw: full pkt raw, length %lu, from user %d", len, userid); handle_full_packet(tun_fd, dns_fds, userid, packet, len, 1); } @@ -910,7 +914,7 @@ handle_raw_ping(struct query *q, int dns_fd, int userid) /* Update time info for user */ users[userid].last_pkt = time(NULL); - DEBUG(3, "IN ping raw, from user %d", userid); + DEBUG(3, "RX-raw: ping from user %d", userid); /* Send ping reply */ send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, &q->from, q->fromlen); @@ -920,30 +924,37 @@ static int raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsfd *dns_fds, int tun_fd) { int raw_user; + uint8_t raw_cmd; /* minimum length */ if (len < RAW_HDR_LEN) return 0; /* should start with header */ - if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0; + if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) + return 0; + raw_cmd = RAW_HDR_GET_CMD(packet); raw_user = RAW_HDR_GET_USR(packet); - DEBUG(3, "TX-raw: client %s, user %d, raw command '%c' length %lu", - format_addr(&q->from, q->fromlen), raw_user, RAW_HDR_GET_CMD(packet), len); - switch (RAW_HDR_GET_CMD(packet)) { + + DEBUG(3, "RX-raw: client %s, user %d, raw command 0x%02X, length %lu", + format_addr(&q->from, q->fromlen), raw_user, raw_cmd, len); + + packet += RAW_HDR_LEN; + len -= RAW_HDR_LEN; + switch (raw_cmd) { case RAW_HDR_CMD_LOGIN: /* Login challenge */ - handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, raw_user); + handle_raw_login(packet, len, q, dns_fd, raw_user); break; case RAW_HDR_CMD_DATA: /* Data packet */ - handle_raw_data(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fds, tun_fd, raw_user); + handle_raw_data(packet, len, q, dns_fds, tun_fd, raw_user); break; case RAW_HDR_CMD_PING: /* Keepalive packet */ handle_raw_ping(q, dns_fd, raw_user); break; default: - DEBUG(1, "Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); + DEBUG(1, "Unhandled raw command %02X from user %d", raw_cmd, raw_user); break; } return 1; From a7dee7396ccc782e6194e87cd16dcd8a453d4f35 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 27 Oct 2015 18:36:00 +0800 Subject: [PATCH 060/113] Making function of check_authenticated_user_and_ip a little more clear --- src/user.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/user.c b/src/user.c index a16487b..a5509a2 100644 --- a/src/user.c +++ b/src/user.c @@ -228,9 +228,10 @@ check_user_and_ip(int userid, struct query *q) return 1; } -/* This checks that user has passed normal (non-raw) login challenge */ int check_authenticated_user_and_ip(int userid, struct query *q) +/* This checks that user has passed normal (non-raw) login challenge + * Returns 0 on success, 1 if user is not authenticated/IP is wrong */ { int res = check_user_and_ip(userid, q); if (res) From fbf21caa6e8984589819075051c0881fd95b8d4e Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 20:03:27 +0800 Subject: [PATCH 061/113] Updated debug macros to include more info with DEBUG_BUILD --- src/common.h | 23 ++++++++++++++++++++-- src/window.h | 54 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/common.h b/src/common.h index d942027..3672c96 100644 --- a/src/common.h +++ b/src/common.h @@ -40,6 +40,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #include #include #include +#include #endif #define DNS_PORT 53 @@ -82,12 +83,30 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #define UPSTREAM_PING 11 /* handy debug printing macro */ +#ifdef DEBUG_BUILD +#define TIMEPRINT(...) \ + struct timeval currenttime;\ + gettimeofday(¤ttime, NULL);\ + fprintf(stderr, "%03ld.%03ld ", currenttime.tv_sec, currenttime.tv_usec / 1000);\ + fprintf(stderr, __VA_ARGS__); + #define DEBUG(level, ...) \ if (debug >= level) {\ - fprintf(stderr, "[D%d %s:%d] ", level, __FILE__, __LINE__); \ + TIMEPRINT("[D%d %s:%d] ", level, __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ - }\ + } +#else +#define TIMEPRINT(...) \ + fprintf(stderr, __VA_ARGS__); + +#define DEBUG(level, ...) \ + if (debug >= level) {\ + fprintf(stderr, "[D%d] ", level); \ + fprintf(stderr, __VA_ARGS__);\ + fprintf(stderr, "\n");\ + } +#endif struct query { diff --git a/src/window.h b/src/window.h index 520d0eb..fc412f7 100644 --- a/src/window.h +++ b/src/window.h @@ -17,16 +17,15 @@ #ifndef __WINDOW_H__ #define __WINDOW_H__ +/* Hard-coded sequence ID and fragment size limits + * These should match the limitations of the protocol. */ #define MAX_SEQ_ID 256 #define MAX_FRAGSIZE 4096 +/* Window function definitions. */ #define WINDOW_SENDING 1 #define WINDOW_RECVING 0 - -/* Enables LOTS of annoying debug output */ -//#define WINDOW_DEBUG - typedef struct fragment { size_t len; /* Length of fragment data (0 if fragment unused) */ unsigned seqID; /* fragment sequence ID */ @@ -35,34 +34,35 @@ typedef struct fragment { uint8_t start; /* start of chunk flag */ uint8_t end; /* end of chunk flag */ uint8_t data[MAX_FRAGSIZE]; /* fragment data */ - unsigned retries; /* number of times fragment has been sent */ + unsigned retries; /* number of times has been sent or dupes recv'd */ struct timeval lastsent; /* timestamp of most recent send attempt */ - int acks; /* number of times packet has been ack'd (should be <= 1) */ + int acks; /* number of times packet has been ack'd */ } fragment; struct frag_buffer { fragment *frags; /* pointer to array of data fragments */ - unsigned windowsize; /* Max number of packets in flight */ - unsigned maxfraglen; /* Max fragment size */ + unsigned windowsize; /* Max number of fragments in flight */ + unsigned maxfraglen; /* Max outgoing fragment data size */ size_t length; /* Length of buffer */ size_t numitems; /* number of non-empty fragments stored in buffer */ - size_t window_start; /* Start of window */ + size_t window_start; /* Start of window (index) */ size_t window_end; /* End of window (index) */ - size_t last_write; /* Last fragment read/written */ - size_t chunk_start; /* Start of current chunk of fragments, ie where fragno = 0 */ - unsigned cur_seq_id; /* Most recent sequence ID */ + size_t last_write; /* Last fragment appended (index) */ + size_t chunk_start; /* Start of current chunk of fragments (index) */ + unsigned cur_seq_id; /* Next unused sequence ID */ unsigned start_seq_id; /* Start of window sequence ID */ - unsigned resends; /* number of fragments resent */ + unsigned resends; /* number of fragments resent or number of dupes received */ unsigned oos; /* Number of out-of-sequence fragments received */ - int direction; /* Sending or recving */ - struct timeval timeout; /* Fragment timeout before resend */ + int direction; /* WINDOW_SENDING or WINDOW_RECVING */ + struct timeval timeout; /* Fragment ACK timeout before resend */ }; extern int window_debug; +/* Window debugging macro */ #ifdef DEBUG_BUILD #define WDEBUG(...) if (window_debug) {\ - fprintf(stderr, "[WINDOW-DEBUG] (%s:%d) ", __FILE__, __LINE__);\ + TIMEPRINT("[WINDOW-DEBUG] (%s:%d) ", __FILE__, __LINE__);\ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } @@ -70,23 +70,36 @@ extern int window_debug; #define WDEBUG(...) #endif +/* Gets index of fragment o fragments after window start */ #define AFTER(w, o) ((w->window_start + o) % w->length) -// Distance (going forwards) between a and b in window of length l +/* Distance (going forwards) between a and b in window of length l */ #define DISTF(l, a, b) (((a > b) ? a-b : l-a+b-1) % l) -// Distance backwards between a and b in window of length l + +/* Distance backwards between a and b in window of length l */ #define DISTB(l, a, b) (((a < b) ? l-b+a-1 : a-b) % l) + +/* Check if fragment index a is within window_buffer *w */ #define INWINDOW_INDEX(w, a) ((w->window_start < w->window_end) ? \ (a >= w->window_start && a <= w->window_end) : \ ((a >= w->window_start && a <= w->length - 1) || \ (a >= 0 && a <= w->window_end))) + +/* Check if sequence ID a is within sequence range start to end */ #define INWINDOW_SEQ(start, end, a) ((start < end) ? \ (a >= start && a <= end) : \ ((a >= start && a <= MAX_SEQ_ID - 1) || \ (a <= end))) -#define SEQ_OFFSET(start, a) ((a >= start) ? a - start : MAX_SEQ_ID + start - a - 1) + +/* Find the wrapped offset between sequence IDs start and a + * Note: the maximum possible offset is MAX_SEQ_ID - 1 */ +#define SEQ_OFFSET(start, a) ((a >= start) ? a - start : MAX_SEQ_ID - start + a) + +/* Wrap index x to a value within the window buffer length */ #define WRAP(x) ((x) % w->length) +/* Perform wrapped iteration of statement with pos = (begin to end) wrapped at + * max, executing statement f for every value of pos. */ #define ITER_FORWARD(begin, end, max, pos, f) { \ if (end >= begin) \ for (pos = begin; pos < end && pos < max; pos++) {f}\ @@ -96,6 +109,7 @@ extern int window_debug; }\ } +/* Window buffer creation and housekeeping */ struct frag_buffer *window_buffer_init(size_t length, unsigned windowsize, unsigned fragsize, int dir); void window_buffer_resize(struct frag_buffer *w, size_t length); void window_buffer_destroy(struct frag_buffer *w); @@ -120,7 +134,7 @@ ssize_t window_process_incoming_fragment(struct frag_buffer *w, fragment *f); size_t window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int *compression); /* Returns number of fragments to be sent */ -size_t window_sending(struct frag_buffer *w); +size_t window_sending(struct frag_buffer *w, struct timeval *); /* Returns next fragment to be sent or NULL if nothing (SEND) */ fragment *window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack); From 141d1f1e4eb04798730723e11e7cf990515675af Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 20:04:14 +0800 Subject: [PATCH 062/113] Removed unused tun_user::disabled flag --- src/user.c | 2 +- src/user.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/user.c b/src/user.c index a5509a2..6c4b69f 100644 --- a/src/user.c +++ b/src/user.c @@ -122,7 +122,7 @@ user_sending(int user) int user_active(int i) { - return users[i].active && !users[i].disabled && difftime(time(NULL), users[i].last_pkt) < 60; + return users[i].active && difftime(time(NULL), users[i].last_pkt) < 60; } int diff --git a/src/user.h b/src/user.h index 165c7b1..1b19f38 100644 --- a/src/user.h +++ b/src/user.h @@ -29,14 +29,12 @@ struct tun_user { int active; int authenticated; int authenticated_raw; - int disabled; time_t last_pkt; struct timeval dns_timeout; int seed; in_addr_t tun_ip; struct sockaddr_storage host; socklen_t hostlen; - /* TODO: multiple incoming query storage + handling */ struct frag_buffer *incoming; struct frag_buffer *outgoing; int next_upstream_ack; From 9dec2de448f70b53a55d67e282be39acdc743aaa Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 20:42:31 +0800 Subject: [PATCH 063/113] Updated command line arguments help --- src/iodine.c | 5 +++-- src/iodined.c | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index a7b96b3..b057973 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -71,8 +71,8 @@ print_usage() fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-w downfrags] [-W upfrags] [-i sec -j sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " - "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] " - "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [nameserverN ...]]]\n", __progname); + "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-R rdomain] " + "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [...]]]\n", __progname); } static void @@ -118,6 +118,7 @@ help() fprintf(stderr, " -t dir to chroot to directory dir\n"); fprintf(stderr, " -d device to set tunnel device name\n"); fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); + fprintf(stderr, " -R routing domain (OpenBSD only)\n"); fprintf(stderr, " -F pidfile to write pid to a file\n\n"); fprintf(stderr, "nameserver is the IP/hostname of the relaying nameserver(s).\n"); diff --git a/src/iodined.c b/src/iodined.c index 643d751..4a87d9c 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -154,13 +154,11 @@ static void print_usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] " - "[-4] [-6] [-c] [-s] [-f] [-D] [-u user] " - "[-t chrootdir] [-d device] [-m mtu] [-z context] " - "[-l ipv4 listen address] [-L ipv6 listen address] " - "[-p port] [-n external ip] [-b dnsport] " - "[-P password] [-F pidfile] [-i max idle time] " - "tunnel_ip[/netmask] topdomain\n", __progname); + fprintf(stderr, "Usage: %s [-v] [-h] [-4] [-6] [-c] [-s] [-f] [-D] " + "[-u user] [-t chrootdir] [-d device] [-m mtu] [-z context] " + "[-l ipv4 listen address] [-L ipv6 listen address] [-p port] " + "[-n external ip] [-b dnsport] [-P password] [-F pidfile] " + "[-i max idle time] tunnel_ip[/netmask] topdomain\n", __progname); } static void From 8d259748672d2cb8470e096bd01133d0a9908242 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 20:49:44 +0800 Subject: [PATCH 064/113] Increased bad fragment tolerance to keep data flowing --- src/window.c | 100 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/src/window.c b/src/window.c index 1e20d93..36f4946 100644 --- a/src/window.c +++ b/src/window.c @@ -24,6 +24,7 @@ #include #include "common.h" +#include "util.h" #include "window.h" int window_debug = 0; @@ -123,33 +124,49 @@ window_append_fragment(struct frag_buffer *w, fragment *src) return 1; } + +ssize_t +window_process_incoming_fragment(struct frag_buffer *w, fragment *f) /* Handles fragment received from the sending side (RECV) * Returns index of fragment in window or <0 if dropped * The next ACK MUST be for this fragment */ -ssize_t -window_process_incoming_fragment(struct frag_buffer *w, fragment *f) { /* Check if packet is in window */ - unsigned startid, endid; + unsigned startid, endid, offset; fragment *fd; startid = w->start_seq_id; endid = (w->start_seq_id + w->windowsize) % MAX_SEQ_ID; + offset = SEQ_OFFSET(startid, f->seqID); + if (!INWINDOW_SEQ(startid, endid, f->seqID)) { - WDEBUG("Dropping frag with seqID %u: not in window (%u-%u)\n", f->seqID, startid, endid); w->oos++; - return -1; + if (offset > MIN(w->length - w->numitems, MAX_SEQ_ID / 2)) { + /* Only drop the fragment if it is ancient */ + WDEBUG("Dropping frag with seqID %u: not in window (%u-%u)", f->seqID, startid, endid); + return -1; + } else { + /* Save "new" fragments to avoid causing other end to advance + * when this fragment is ACK'd despite being dropped */ + WDEBUG("WARNING: Got future fragment (%u), offset %u from start %u (wsize %u).", + f->seqID, offset, startid, w->windowsize); + } } /* Place fragment into correct location in buffer */ ssize_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); WDEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); + /* Check if fragment already received */ fd = &w->frags[dest]; if (fd->len != 0) { WDEBUG("Received duplicate frag, dropping. (prev %u/new %u)", fd->seqID, f->seqID); - if (f->seqID == fd->seqID) + if (f->seqID == fd->seqID) { + /* use retries as counter for dupes */ + fd->retries ++; return -1; + } } + memcpy(fd, f, sizeof(fragment)); w->numitems ++; @@ -173,22 +190,24 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (w->direction != WINDOW_RECVING) return 0; if (w->frags[w->chunk_start].start == 0 && w->numitems > 0) { - WDEBUG("chunk_start (%lu)pointing to non-start fragment (seq %u, len %lu)!", + WDEBUG("chunk_start (%lu) pointing to non-start fragment (seq %u, len %lu)!", w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); return 0; } if (compression) *compression = 1; fragment *f; - size_t i, curseq; + size_t i; + unsigned curseq; int end = 0; curseq = w->frags[w->chunk_start].seqID; for (i = 0; i < w->numitems; ++i) { woffs = WRAP(w->chunk_start + i); f = &w->frags[woffs]; fraglen = f->len; - if (fraglen == 0 || !f->data || f->seqID != curseq) { - WDEBUG("data missing! Not reassembling!"); + if (fraglen == 0 || f->seqID != curseq) { + WDEBUG("Missing next frag %u [%lu], got seq %u (%lu bytes) instead! Not reassembling!", + curseq, woffs, f->seqID, fraglen); return 0; } @@ -200,8 +219,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (compression) { *compression &= f->compressed & 1; if (f->compressed != *compression) { - WDEBUG("Inconsistent compression flags in chunk. Not reassembling!"); - return 0; + WDEBUG("Inconsistent compression flags in chunk. Will reassemble anyway!"); } } if (fraglen > maxlen) { @@ -218,13 +236,18 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int break; } + /* Move position counters and expected next seqID */ maxlen -= fraglen; curseq = (curseq + 1) % MAX_SEQ_ID; } - if (end == 0) { /* no end of chunk found but reached end of data */ + + if (end == 0) { + /* no end of chunk found because the window buffer has no more frags + * meaning they haven't been received yet. */ return 0; } - WDEBUG("Reassembling %lu bytes of data from %lu frags; compression %d!", datalen, i + 1, *compression); + + WDEBUG("Reassembled %lu bytes from %lu frags; %scompressed!", datalen, i + 1, *compression ? "" : "un"); /* Clear all used fragments */ size_t p; ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, @@ -235,15 +258,19 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int return datalen; } -/* Returns number of fragments that can be sent immediately; effectively - * the same as window_get_next_sending_fragment but without changing anything. */ size_t -window_sending(struct frag_buffer *w) +window_sending(struct frag_buffer *w, struct timeval *nextresend) +/* Returns number of fragments that can be sent immediately; effectively + the same as window_get_next_sending_fragment but without doing anything. + *nextresend is time before the next frag will be resent */ { - struct timeval timeout, now; + struct timeval age, now, oldest; fragment *f; size_t tosend = 0; + oldest.tv_sec = 0; + oldest.tv_usec = 0; + if (w->numitems == 0) return 0; @@ -253,12 +280,30 @@ window_sending(struct frag_buffer *w) f = &w->frags[WRAP(w->window_start + i)]; if (f->len == 0 || f->acks >= 1) continue; - timeradd(&w->timeout, &f->lastsent, &timeout); - if (f->retries < 1 || !timercmp(&now, &timeout, <)) { - /* Fragment not sent or timed out (to be re-sent) */ + if (f->retries < 1 || f->lastsent.tv_sec == 0) { + /* Sending frag for first time + * Note: if retries==0 then lastsent MUST also be 0 */ tosend++; + } else { + /* Frag has been sent before so lastsent is a valid timestamp */ + timersub(&now, &f->lastsent, &age); + + if (!timercmp(&age, &w->timeout, <)) { + /* ACK timeout: Frag will be resent */ + tosend++; + } else if (timercmp(&age, &oldest, >)) { + /* Hasn't timed out yet and is oldest so far */ + oldest = age; + } } } + + if (nextresend) { + /* nextresend = time before oldest fragment (not being sent now) + * will be re-sent = timeout - age */ + timersub(&w->timeout, &oldest, nextresend); + } + return tosend; } @@ -267,7 +312,7 @@ window_sending(struct frag_buffer *w) fragment * window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) { - struct timeval timeout, now; + struct timeval age, now; fragment *f = NULL; if (*other_ack >= MAX_SEQ_ID || *other_ack < 0) @@ -277,13 +322,14 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) for (size_t i = 0; i < w->windowsize; i++) { f = &w->frags[WRAP(w->window_start + i)]; - if (f->acks >= 1) continue; + if (f->acks >= 1 || f->len == 0) continue; - timeradd(&w->timeout, &f->lastsent, &timeout); + timersub(&now, &f->lastsent, &age); - if (f->retries >= 1 && !timercmp(&now, &timeout, <)) { - /* Fragment sent before, not ACK'd */ - WDEBUG("Sending fragment %u again, %u retries so far, %u resent overall\n", f->seqID, f->retries, w->resends); + if (f->retries >= 1 && !timercmp(&age, &w->timeout, <)) { + /* Resending fragment due to ACK timeout */ + WDEBUG("Retrying frag %u (%ld ms old/timeout %ld ms), retries: %u/total %u", + f->seqID, timeval_to_ms(&age), timeval_to_ms(&w->timeout), f->retries, w->resends); w->resends ++; goto found; } else if (f->retries == 0 && f->len > 0) { From f96bd3735d20566535a83c36cc47476c1cab5bcd Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 20:51:12 +0800 Subject: [PATCH 065/113] Resend fragments ASAP to improve flow --- src/server.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/server.c b/src/server.c index 20a251f..5575ffe 100644 --- a/src/server.c +++ b/src/server.c @@ -143,7 +143,7 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr #define QMEM_DEBUG(l, u, ...) \ if (debug >= l) {\ - fprintf(stderr, "[QMEM u%d (%lu/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ + TIMEPRINT("[QMEM u%d (%lu/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } @@ -291,10 +291,10 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) * - the user has excess pending queries (>downstream window size) * Returns largest safe time to wait before next timeout */ { - struct timeval now, timeout, soonest, tmp, age; + struct timeval now, timeout, soonest, tmp, age, nextresend; soonest.tv_sec = 10; soonest.tv_usec = 0; - int userid, qnum, nextuser = -1, immediate; + int userid, qnum, nextuser = -1, immediate, resend = 0; struct query *q = NULL, *nextq = NULL; size_t sending, total, sent; time_t age_ms; @@ -312,7 +312,15 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) /* Keep track of how many fragments we can send */ if (u->lazy) { - total = window_sending(u->outgoing); + total = window_sending(u->outgoing, &nextresend); + if ((nextresend.tv_sec != 0 || nextresend.tv_usec != 0) + && u->qmem.num_pending >= 1) { + /* will use nextresend as max wait time if it is smallest + * and if user has spare queries */ + resend = 1; + soonest = nextresend; + } + if (u->qmem.num_pending > u->outgoing->windowsize) { /* calculate number of "excess" queries */ total = MAX(total, u->qmem.num_pending - u->outgoing->windowsize); @@ -347,7 +355,7 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) sent++; QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %lu of %lu + sending another %lu", - q->id, u->next_upstream_ack, sent, total, sending); + q->id, u->next_upstream_ack, sent, total, sending); send_data_or_ping(dns_fds, userid, q, 0, immediate); @@ -375,9 +383,12 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) } else { if (nextuser < 0) nextuser = 0; - /* sanity check: soonest_ms should always be default value here (ie. 10000) */ - if (soonest_ms != 10000) - QMEM_DEBUG(1, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); + if (soonest_ms != 10000 && resend) { + /* only if resending some frags */ + QMEM_DEBUG(5, nextuser, "Resending some fragments") + } else { + QMEM_DEBUG(2, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); + } } } From 4a565fd6641f38d32f15cf0428718a6825b1a9fb Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 20:54:01 +0800 Subject: [PATCH 066/113] Improve data flow, better debugging, fixed ping CMC and frag timeout --- src/client.c | 86 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/src/client.c b/src/client.c index 0ba56a9..2220422 100644 --- a/src/client.c +++ b/src/client.c @@ -350,13 +350,17 @@ immediate_mode_defaults() /* Client-side query tracking for lazy mode */ /* Handy macro for printing stats with messages */ +#ifdef DEBUG_BUILD #define QTRACK_DEBUG(l, ...) \ if (debug >= l) {\ - fprintf(stderr, "[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", num_pending, PENDING_QUERIES_LENGTH, \ - num_untracked, num_timeouts, window_sending(outbuf), outbuf->numitems); \ + TIMEPRINT("[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", num_pending, PENDING_QUERIES_LENGTH, \ + num_untracked, num_timeouts, window_sending(outbuf, NULL), outbuf->numitems); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } +#else +#define QTRACK_DEBUG(...) +#endif static int update_server_timeout(int dns_fd, int handshake) @@ -558,18 +562,22 @@ send_query(int fd, uint8_t *hostname) And there are relays where, in lazy mode, our new query apparently _replaces_ our previous query, and we get no answers at all in lazy mode while legacy immediate-ping-pong works just fine. + In this case, the up/down windowsizes may need to be set to 1 for there + to only ever be one query pending. Here we detect and fix these situations. (Can't very well do this anywhere else; this is the only place we'll reliably get to in such situations.) - Note: only start fixing up connection AFTER we have connected */ + Note: only start fixing up connection AFTER we have connected + and if user hasn't specified server timeout/window timeout etc. */ num_sent++; - if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode && connected) { + if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && + lazymode && connected && autodetect_server_timeout) { send_query_sendcnt++; if ((send_query_sendcnt > windowsize_down && send_query_recvcnt <= 0) || (send_query_sendcnt > 2 * windowsize_down && 4 * send_query_recvcnt < send_query_sendcnt)) { - if (max_timeout_ms > 500 && autodetect_server_timeout) { + if (max_timeout_ms > 500) { max_timeout_ms -= 200; double secs = (double) max_timeout_ms / 1000.0; fprintf(stderr, "Receiving too few answers. Setting target timeout to %.1fs (-I%.1f)\n", secs, secs); @@ -578,7 +586,7 @@ send_query(int fd, uint8_t *hostname) send_query_sendcnt = 0; send_query_recvcnt = 0; - } else if (lazymode && autodetect_server_timeout) { + } else if (lazymode) { fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" " always work any more. Start with -L0 next time on this network.\n"); lazymode = 0; @@ -635,7 +643,7 @@ send_ping(int fd, int ping_response, int ack, int set_timeout) { num_pings++; if (conn == CONN_DNS_NULL) { - uint8_t data[11]; + uint8_t data[13]; int id; /* Build ping header (see doc/proto_xxxxxxxx.txt) */ @@ -656,7 +664,7 @@ send_ping(int fd, int ping_response, int ack, int set_timeout) data[10] = ((set_timeout & 1) << 4) | ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); data[11] = (rand_seed >> 8) & 0xff; data[12] = (rand_seed >> 0) & 0xff; - rand_seed += 263; + 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 " : "", server_timeout_ms, @@ -682,7 +690,7 @@ send_next_frag(int fd) static int datacmc = 0; static char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; fragment *f; - size_t buflen, len; + size_t buflen; /* Get next fragment to send */ f = window_get_next_sending_fragment(outbuf, &next_downstream_ack); @@ -712,9 +720,7 @@ send_next_frag(int fd) buflen = sizeof(buf) - 1; /* Encode 3 bytes data into 2 bytes after buf */ - len = b32->encode(buf + 2, &buflen, hdr, 3); - if (len != 5) - warnx("mismatch in encoded upstream header length! expected 5, got %lu", len); + b32->encode(buf + 2, &buflen, hdr, 3); /* Encode data into buf after header (6 = user + CMC + 4 bytes header) */ build_hostname(buf, sizeof(buf), f->data, f->len, topdomain, @@ -724,6 +730,9 @@ send_next_frag(int fd) if (datacmc >= 36) datacmc = 0; + DEBUG(3, " SEND DATA: seq %d, ack %d, len %lu, s%d e%d c%d flags %1X", + f->seqID, f->ack_other, f->len, f->start, f->end, f->compressed, hdr[2] >> 4); + id = send_query(fd, buf); /* Log query ID as being sent now */ query_sent_now(id); @@ -1315,10 +1324,10 @@ tunnel_dns(int tun_fd, int dns_fd) int client_tunnel(int tun_fd, int dns_fd) { - struct timeval tv; + struct timeval tv, nextresend, tmp, now, now2; fd_set fds; int rv; - int i; + int i, use_min_send; int sending, total; time_t last_stats; size_t sent_since_report, recv_since_report; @@ -1345,16 +1354,21 @@ client_tunnel(int tun_fd, int dns_fd) sent_since_report = 0; recv_since_report = 0; + use_min_send = 0; + if (debug >= 5) window_debug = debug - 3; while (running) { - tv = ms_to_timeval(max_timeout_ms); + if (!use_min_send) + tv = ms_to_timeval(max_timeout_ms); /* TODO: detect DNS servers which drop frequent requests * TODO: adjust number of pending queries based on current data rate */ - if (conn == CONN_DNS_NULL) { - sending = window_sending(outbuf); + if (conn == CONN_DNS_NULL && !use_min_send) { + + /* Send a single query per loop */ + sending = window_sending(outbuf, &nextresend); total = sending; check_pending_queries(); if (num_pending < windowsize_down && lazymode) @@ -1362,9 +1376,9 @@ client_tunnel(int tun_fd, int dns_fd) else if (num_pending < 1 && !lazymode) total = MAX(total, 1); + /* Upstream traffic - this is where all ping/data queries are sent */ if (sending > 0 || total > 0 || next_downstream_ack >= 0) { - /* Upstream traffic - this is where all ping/data queries are sent */ if (sending > 0) { /* More to send - next fragment */ send_next_frag(dns_fd); @@ -1380,14 +1394,25 @@ client_tunnel(int tun_fd, int dns_fd) lazymode ? windowsize_down : 1, total); if (sending > 0 || (total > 0 && lazymode)) { + /* If sending any data fragments, or server has too few + * pending queries, send another one after min. interval */ /* TODO: enforce min send interval even if we get new data */ tv = ms_to_timeval(min_send_interval_ms); + if (min_send_interval_ms) + use_min_send = 1; tv.tv_usec += 1; } else if (total > 0 && !lazymode) { - /* use immediate mode send interval if nothing pending */ + /* In immediate mode, use normal interval when needing + * to send non-data queries to probe server. */ tv = ms_to_timeval(send_interval_ms); } + if (sending == 0 && !use_min_send) { + /* check next resend time when not sending any data */ + if (timercmp(&nextresend, &tv, <)) + tv = nextresend; + } + send_ping_soon = 0; } } @@ -1421,7 +1446,7 @@ client_tunnel(int tun_fd, int dns_fd) } } - if (send_ping_soon) { + if (send_ping_soon && !use_min_send) { tv.tv_sec = 0; tv.tv_usec = send_ping_soon * 1000; send_ping_soon = 0; @@ -1435,10 +1460,24 @@ client_tunnel(int tun_fd, int dns_fd) } FD_SET(dns_fd, &fds); - DEBUG(4, "Waiting %ld ms before sending more...", timeval_to_ms(&tv)); + DEBUG(4, "Waiting %ld ms before sending more... (min_send %d)", timeval_to_ms(&tv), use_min_send); + + if (use_min_send) { + gettimeofday(&now, NULL); + } i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); + if (use_min_send && i > 0) { + /* enforce min_send_interval if we get interrupted by new tun data */ + gettimeofday(&now2, NULL); + timersub(&now2, &now, &tmp); + timersub(&tv, &tmp, &now); + tv = now; + } else { + use_min_send = 0; + } + if (difftime(time(NULL), lastdownstreamtime) > 60) { fprintf(stderr, "No downstream data received in 60 seconds, shutting down.\n"); running = 0; @@ -1451,9 +1490,8 @@ client_tunnel(int tun_fd, int dns_fd) err(1, "select < 0"); if (i == 0) { - /* TODO check number of timeouts and do something about it */ + /* timed out - no new packets recv'd */ } else { - if (FD_ISSET(tun_fd, &fds)) { if (tunnel_tun(tun_fd, dns_fd) <= 0) continue; @@ -1462,6 +1500,7 @@ client_tunnel(int tun_fd, int dns_fd) we need to _not_ do tunnel_dns() then. If chunk sent, sets send_ping_soon=0. */ } + if (FD_ISSET(dns_fd, &fds)) { tunnel_dns(tun_fd, dns_fd); } @@ -2701,6 +2740,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz /* init windowing protocol */ outbuf = window_buffer_init(64, windowsize_up, maxfragsize_up, WINDOW_SENDING); + outbuf->timeout = ms_to_timeval(downstream_timeout_ms); /* Incoming buffer max fragsize doesn't matter */ inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); From 2f8125cf97bff7f4bbf96139bfd562e8aa79eabe Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 10 Nov 2015 22:33:50 +0800 Subject: [PATCH 067/113] Updated documentation --- README.md | 51 +++++++++--------- man/iodine.8 | 147 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 145 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index dfd36f2..e571352 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ relevant header files are found in `/usr/include`. (See script at `./src/osflags`) Run `make` to compile the server and client binaries. +Run `make debug` to compile the binaries with extra debugging enabled. Run `make install` to copy binaries and manpage to the destination directory. Run `make test` to compile and run the unit tests. (Requires the `check` library) @@ -25,8 +26,8 @@ QUICKSTART Try it out within your own LAN! Follow these simple steps: - On your server, run: `./iodined -f test.com 10.0.0.1`. - If you already use the `10.0.0.0` network, use another internal net like - `172.16.0.0`. + If you already use the `10.0.0.0/8` network, use another internal net like + `172.16.0.0/12`. - Enter a password. - On the client, run: `./iodine -f -r test.com 192.168.0.1`. Replace `192.168.0.1` with your server's ip address. @@ -88,7 +89,7 @@ If there is a chance you'll be using an iodine tunnel from unexpected environments, start `iodined` with a `-c` option. Resulting commandline in this example situation: - ./iodined -f -c -P secretpassword t1.mydomain.com 192.168.99.1 + ./iodined -f -c -P secretpassword 192.168.99.1 t1.mydomain.com ### Client side All the setup is done, just start `iodine`. It takes one or more arguments, the @@ -184,15 +185,15 @@ these DNS relays much more stable. This is also useful on some “de-optimizing DNS relays that stuff the response with two full copies of the query, leaving very little space for downstream data (also not capable of EDNS0). The `-M` switch can trade some upstream bandwidth for downstream bandwidth. Note that -the minimum `-M` value is about 100, since the protocol can split packets (1200 -bytes max) in only 16 fragments, requiring at least 75 real data bytes per -fragment. +the minimum `-M` value is about 20, since the first 10 bytes or so are the +Base32 encoded data header and the remainder is the actual encoded data and +packets can be split into up to 255 fragments. The upstream data is sent gzipped encoded with Base32; or Base64 if the relay server supports mixed case and `+` in domain names; or Base64u if `_` is supported instead; or Base128 if high-byte-value characters are supported. This upstream encoding is autodetected. The DNS protocol allows one query per -packet, and one query can be max 256 chars. Each domain name part can be max +packet, and one query can be max 255 chars. Each domain name part can be max 63 chars. So your domain name and subdomain should be as short as possible to allow maximum upstream throughput. @@ -220,24 +221,24 @@ when that hostname exceeds ca. 180 characters. In these and similar cases, use the `-O` option to try other downstream codecs; Base32 should always work. Normal operation now is for the server to _not_ answer a DNS request until -the next DNS request has come in, a.k.a. being “lazy”. This way, the server +it has timed out (see server timeout), a.k.a. being “lazy”. This way, the server will always have a DNS request handy when new downstream data has to be sent. This greatly improves (interactive) performance and latency, and allows to slow down the quiescent ping requests to 4 second intervals by default, and -possibly much slower. In fact, the main purpose of the pings now is to force -a reply to the previous ping, and prevent DNS server timeouts (usually at -least 5-10 seconds per RFC1035). Some DNS servers are more impatient and will -give SERVFAIL errors (timeouts) in periods without tunneled data traffic. All -data should still get through in these cases, but `iodine` will reduce the ping -interval to 1 second anyway (-I1) to reduce the number of error messages. This -may not help for very impatient DNS relays like `dnsadvantage.com` (ultradns), -which time out in 1 second or even less. Yet data will still get trough, and -you can ignore the `SERVFAIL` errors. +possibly much slower. Some DNS servers are more impatient and will give SERVFAIL +errors (timeouts) randomly or consistently if the target timeout is too high. All +data should still get through in these cases, but `iodine` will reduce the target +interval slowly to reduce the number of SERVFAILS. In these scenarios, it is best +to manually set the target interval to something which is definitely less than the +most impatient DNS server timeout in the connection to ensure maximum reliability. +Some very impatient DNS relays like `dnsadvantage.com` (ultradns), which time +out in 1 second or even less can cause issues. Yet data will still get trough, and +you can probably ignore the `SERVFAIL` errors. If you are running on a local network without any DNS server in-between, try `-I 50` (iodine and iodined close the connection after 60 seconds of silence). The only time you'll notice a slowdown, is when DNS reply packets go missing; -the `iodined` server then has to wait for a new ping to re-send the data. You can +the `iodined` server fragment will have to time-out to be resent. You can speed this up by generating some upstream traffic (keypress, ping). If this happens often, check your network for bottlenecks and/or run with `-I1`. @@ -256,7 +257,8 @@ If you have problems, try inspecting the traffic with network monitoring tools like tcpdump or ethereal/wireshark, and make sure that the relaying DNS server has not cached the response. A cached error message could mean that you started the client before the server. The `-D` (and `-DD`) option on the server -can also show received and sent queries. +can also show received and sent queries. To assist in diagnosis, you may wish to +recompile with `make debug` and use `-DDDDD` to see more debug output. TIPS & TRICKS @@ -306,9 +308,8 @@ explains why some values are exactly equal. Ping round-trip times measured with `ping -c100`, presented are average rtt and mean deviation (indicating spread around the average), in milliseconds. - ### Situation 1: `Laptop -> Wifi AP -> Home server -> DSL provider -> Datacenter` - +``` iodine DNS "relay" bind9 DNS cache iodined downstr. upstream downstr. ping-up ping-down @@ -336,9 +337,9 @@ and mean deviation (indicating spread around the average), in milliseconds. [174.7* : these all have 2frag/packet] - +``` ### Situation 2: `Laptop -> Wifi+vpn / wired -> Home server` - +``` iodine iodined downstr. upstream downstr. ping-up ping-down @@ -348,7 +349,7 @@ and mean deviation (indicating spread around the average), in milliseconds. wifi + openvpn -Tnull 1186 166.0 1022.3 6.3 1.3 6.6 1.6 wired -Tnull 1186 677.2 2464.1 1.3 0.2 1.3 0.1 - +``` ### Notes @@ -388,7 +389,7 @@ THANKS AUTHORS & LICENSE ----------------- -Copyright (c) 2006-2014 Erik Ekman , 2006-2009 Bjorn +Copyright (c) 2006-2014 Erik Ekman , 2015 Frekky, 2006-2009 Bjorn Andersson . Also major contributions by Anne Bezemer. Permission to use, copy, modify, and/or distribute this software for any purpose diff --git a/man/iodine.8 b/man/iodine.8 index b394d90..edebab7 100644 --- a/man/iodine.8 +++ b/man/iodine.8 @@ -1,5 +1,5 @@ .\" groff -man -Tascii iodine.8 -.TH IODINE 8 "APR 2012" "User Manuals" +.TH IODINE 8 "OCT 2015" "User Manuals" .SH NAME iodine, iodined \- tunnel IPv4 over DNS .SH SYNOPSIS @@ -21,8 +21,22 @@ iodine, iodined \- tunnel IPv4 over DNS .I rdomain .B ] [-m .I fragsize +.B ] [-w +.I downfrags +.B ] [-W +.I upfrags +.B ] [-i +.I sec +.B ] [-j +.I sec +.B ] [-c +.I 0|1 +.B ] [-C +.I 0|1 +.B ] [-s +.I ms .B ] [-M -.I namelen +.I maxlen .B ] [-z .I context .B ] [-F @@ -41,7 +55,9 @@ iodine, iodined \- tunnel IPv4 over DNS .I nameserver .B [ .I nameserver2 -.B [...] ] ] +.B [ +.I ... +.B ] ] ] .B iodined [-v] @@ -84,17 +100,13 @@ iodine, iodined \- tunnel IPv4 over DNS .I /netmask .B ] .I topdomain + .SH DESCRIPTION .B iodine lets you tunnel IPv4 data through a DNS server. This can be useful in situations where Internet access is firewalled, -but DNS queries are allowed. It needs a TUN/TAP device to operate. The -bandwidth is asymmetrical, -with a measured maximum of 680 kbit/s upstream and 2.3 Mbit/s -downstream in a wired LAN test network. -Realistic sustained throughput on a Wifi network using a carrier-grade -DNS cache has been measured at some 50 kbit/s upstream and over 200 kbit/s -downstream. +but DNS queries are allowed. It needs a TUN/TAP device to operate. + .B iodine is the client application, .B iodined @@ -103,6 +115,28 @@ is the server. Note: server and client are required to speak the exact same protocol. In most cases, this means running the same iodine version. Unfortunately, implementing backward and forward protocol compatibility is usually not feasible. + +.SH PERFORMANCE +The bandwidth is asymmetrical, +with a measured maximum of 13.7 Mbits/sec upstream and 53.4 Mbits/sec downstream +with all data compression disabled in a wired LAN test network. +In the same situation with compression enabled, the measured data throughput was +approximately 46.1 Mbits/sec upstream and 37.4 Mbits/sec downstream. +Compression is enabled by default, and can allow faster +data transfer in both directions depending on the type of data being +transferred. +Realistic sustained throughput on a Wifi network using a carrier-grade +DNS cache has been measured at some 600 Kbit/s upstream and over 2 Mbits/sec +downstream once more with compression disabled, however that may be increased with +compression enabled. + +All of the above test scenarios used lazy mode with upstream/downstream windowsizes of +8 fragments (default) and fixed fragment, DNS and server timeouts. These parameters +were manually adjusted to best suit the environment, and can be specified using the +.B iodine +options described under +.I "Fine-tuning options" + .SH OPTIONS .SS Common Options: .TP @@ -229,18 +263,67 @@ In these situations use \-L0 to force running in legacy mode (implies \-I1). .TP .B -I interval -Maximum interval between requests (pings) so that intermediate DNS -servers will not time out. Default is 4 in lazy mode, which will work -fine in most cases. When too many SERVFAIL errors occur, iodine -will automatically reduce this to 1. -To get absolute minimum DNS traffic, -increase well above 4, but not so high that SERVFAIL errors start to occur. +Target timeout for queries. This should be less than the smallest timeout for +any intermediate DNS servers to reduce SERVFAILS. If this is specified, the +server timeout will be adjusted automatically based on the round-trip time +so that queries remain pending for as long as possible (only in lazy mode). +This value will be used as the polling interval in immediate mode. + +When too many SERVFAIL errors occur, iodine will gradually reduce this until +it reaches 0.5 seconds or below. If SERVFAILs continue to occur, lazy mode +will be disabled and the server will respond to all queries immediately. + +To reduce DNS traffic, set this interval to something large and disable lazy +mode, or set the upstream and downstream window sizes to 1. There are some DNS relays with very small timeouts, notably dnsadvantage.com (ultradns), that will give -SERVFAIL errors even with \-I1; data will still get trough, +SERVFAIL errors even with \-I1; data will still get through, and these errors can be ignored. Maximum useful value is 59, since iodined will close a client's connection after 60 seconds of inactivity. + +.SS Fine-tuning Options (Client-side): +.TP +.B -s milliseconds +Minimum query send interval. Increase this gradually if you notice that the +nameserver(s) tend to fail more often with a high data load (and frequent +queries) or drop excess DNS queries. This will affect throughput so use with +caution. +.B -w windowsize +Size of downstream fragment sending window, or the number of fragments that +can be in transit downstream at any point in time. The client will attempt to +maintain at least this number of queries pending on the server in lazy mode, +even when idle, so that the server can always send this number of fragments +immediately if new data arrives on the tun device. +The default value is 8 fragments. Increase this for high latency connections +to improve throughput. The maximum usable value is probably around 128, however note +that although higher values are possible there may be fragment overlaps and you may +experience problems. +.TP +.B -W windowsize +Number of fragments that can be in transit upstream at any point in time. The +client will send a maximum of this number of queries immediately to the server +when new data is received in addition to any already pending queries (such as +those used to maintain the downstream windowsize). The server will respond to +any excess queries using the oldest pending query first. The same limits apply +as the downstream window size. +.TP +.B -i timeout +The maximum amount of time in seconds the server should hold on to a pending +query so as to not cause any intermediate DNS relays to time out. This should +be less than the target timeout (set with +.BR -I ) +by at least the round-trip time for the connection. +If not set, this will be calculated automatically based on the round-trip time +and the target timeout. +.TP +.B -c 0|1 +Enable or disable downstream data compression. Enabled by default. This may +increase overall downstream throughput, or it may not depending on the type +of data being transferred. +.B -C 0|1 +Enable/disable upstream data compression, also enabled by default. + .SS Server Options: .TP .B -c @@ -255,7 +338,9 @@ This should only be used if you have already configured the device that will be used. .TP .B -D -Increase debug level. Level 1 prints info about each RX/TX packet. +Increase debug level. Higher levels (>2) will spam the terminal with LOTS +of debug messages. Recompile using `make debug` to enable extra debug output +and debug timestamping. Implies the .B -f option. @@ -306,29 +391,30 @@ Make the server stop itself after max_idle_time seconds if no traffic have been This should be combined with systemd or upstart on demand activation for being effective. .SS Client Arguments: .TP -.B nameserver -The nameserver to use to relay the dns traffic. This can be any relaying +.B nameservers +The nameservers to use to relay the dns traffic. This can be any relaying nameserver or the server running iodined if reachable. This field can be given as an IPv4/IPv6 address or as a hostname. This argument is optional, and if not specified a nameserver will be read from the .I /etc/resolv.conf file. +Multiple nameservers can be specified, separated by spaces. .TP .B topdomain The dns traffic will be sent as queries for subdomains under \'topdomain'. This is normally a subdomain to a domain you own. Use a short domain name to get better throughput. If .B nameserver -is the iodined server, then the topdomain can be chosen freely. This argument +is the iodined server address, then the topdomain can be chosen freely. This argument must be the same on both the client and the server. .SS Server Arguments: .TP .B tunnel_ip[/netmask] This is the server's ip address on the tun interface. The client will be given the next ip number in the range. It is recommended to use the -10.0.0.0 or 172.16.0.0 ranges. The default netmask is /27, can be overridden -by specifying it here. Using a smaller network will limit the number of -concurrent users. +10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16 ranges. The default netmask is /27, +which can be overridden by specifying it here. Using a smaller network will +limit the number of concurrent users. .TP .B topdomain The dns traffic is expected to arrive as queries for @@ -336,10 +422,12 @@ subdomains under 'topdomain'. This is normally a subdomain to a domain you own. Use a short domain name to get better throughput. This argument must be the same on both the client and the server. Queries for domains other than 'topdomain' will be forwarded when the \-b option is given, otherwise -they will be dropped. +they will be ignored. + .SH EXAMPLES See the README file for both a quick test scenario, and a detailed description of real-world deployment. + .SH SECURITY Login is a relatively secure challenge-response MD5 hash, with the password never passing the wire. @@ -368,11 +456,14 @@ is set, iodined will use the value it is set to as password instead of asking for one. The .B -P option still has precedence. + .SH SEE ALSO -The README file in the source distribution contains some more elaborate +The README.md file in the source distribution contains some more elaborate information. + .SH BUGS File bugs at http://dev.kryo.se/iodine/ + .SH AUTHORS -Erik Ekman and Bjorn Andersson . Major -contributions by Anne Bezemer. +Erik Ekman , Bjorn Andersson and Frekky. +Major contributions by Anne Bezemer. From 26b025a9b0feea1b5167a135676f30f78d7d9ae1 Mon Sep 17 00:00:00 2001 From: frekky Date: Wed, 11 Nov 2015 20:24:13 +0800 Subject: [PATCH 068/113] Fix unused variable warning on default build --- src/window.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/window.c b/src/window.c index 36f4946..79d2b65 100644 --- a/src/window.c +++ b/src/window.c @@ -392,10 +392,11 @@ window_ack(struct frag_buffer *w, int seqid) void window_tick(struct frag_buffer *w) { - unsigned old_start_id; for (size_t i = 0; i < w->windowsize; i++) { if (w->frags[w->window_start].acks >= 1) { - old_start_id = w->start_seq_id; +#ifdef DEBUG_BUILD + unsigned old_start_id = w->start_seq_id; +#endif w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; WDEBUG("moving window forwards; %lu-%lu (%u) to %lu-%lu (%u) len=%lu", w->window_start, w->window_end, old_start_id, AFTER(w, 1), From 4f470e47564ceae68a82d8d6bab4868e08f8e074 Mon Sep 17 00:00:00 2001 From: frekky Date: Wed, 11 Nov 2015 20:25:30 +0800 Subject: [PATCH 069/113] Update manpage for more command line args --- man/iodine.8 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/man/iodine.8 b/man/iodine.8 index edebab7..2c54b37 100644 --- a/man/iodine.8 +++ b/man/iodine.8 @@ -7,7 +7,9 @@ iodine, iodined \- tunnel IPv4 over DNS .B iodine [-h] -.B iodine [-4] [-6] [-f] [-r] [-u +.B iodine [-4] [-6] [-f] [-D] [-r] [-V +.I sec +.B [-u .I user .B ] [-P .I password @@ -54,17 +56,15 @@ iodine, iodined \- tunnel IPv4 over DNS .B [ .I nameserver .B [ -.I nameserver2 -.B [ -.I ... -.B ] ] ] +.I nameserver2 ... +.B ] ] .B iodined [-v] .B iodined [-h] -.B iodined [-4] [-6] [-c] [-s] [-f] [-D] [-u +.B iodined [-4] [-6] [-f] [-D] [-c] [-s] [-u .I user .B ] [-t .I chrootdir @@ -134,8 +134,8 @@ All of the above test scenarios used lazy mode with upstream/downstream windowsi 8 fragments (default) and fixed fragment, DNS and server timeouts. These parameters were manually adjusted to best suit the environment, and can be specified using the .B iodine -options described under -.I "Fine-tuning options" +options described under +.IR "Fine-tuning Options" . .SH OPTIONS .SS Common Options: From 35a0f8279d0131ce24f350c7c9cd591a38d9867b Mon Sep 17 00:00:00 2001 From: frekky Date: Wed, 11 Nov 2015 20:25:48 +0800 Subject: [PATCH 070/113] Update changelog and readme --- CHANGELOG | 5 +++++ README.md | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 731df84..eeebac1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,11 @@ iodine - http://code.kryo.se/iodine CHANGES: master: + - Multiple fragments can be in transit at one time to improve + throughput on high-latency connections + Server can now store multiple pending queries per user. + - Added bidirectional compression flags + - Added statistics report every few seconds in iodine (-V) - Mac OS X: Support native utun VPN devices. Patch by Peter Sagerson, ported from OpenVPN by Catalin Patulea. - Fix compilation failure on kFreeBSD and Hurd, by Gregor Herrmann diff --git a/README.md b/README.md index e571352..b396946 100644 --- a/README.md +++ b/README.md @@ -292,8 +292,12 @@ undefine it to save a few more kilobytes. PERFORMANCE ----------- -This section tabulates some performance measurements. To view properly, use -a fixed-width font like Courier. +**Please note:** The following performance is outdated and does not apply to +the current version of iodine. Since the protocol was updated to use a sliding +window for sending fragments, the throughput was greatly increased and should +be much higher even on high-latency connections. + +This section tabulates some performance measurements. Measurements were done in protocol 00000502 in lazy mode; upstream encoding always Base128; `iodine -M255`; `iodined -m1130`. Network conditions were not @@ -353,12 +357,11 @@ and mean deviation (indicating spread around the average), in milliseconds. ### Notes -Performance is strongly coupled to low ping times, as iodine requires -confirmation for every data fragment before moving on to the next. Allowing -multiple fragments in-flight like TCP could possibly increase performance, -but it would likely cause serious overload for the intermediary DNS servers. -The current protocol scales performance with DNS responsivity, since the -DNS servers are on average handling at most one DNS request per client. +Multiple fragments in-flight like TCP is allowed in iodine, which does increase +performance, but it may overload some low-bandwidth intermediary DNS servers. +Using carrier-grade DNS servers such as those provided by your ISP should be able +to handle a high volume of DNS queries, and it is recommended to use as many DNS +nameservers as possible to balance the load. PORTABILITY @@ -389,8 +392,8 @@ THANKS AUTHORS & LICENSE ----------------- -Copyright (c) 2006-2014 Erik Ekman , 2015 Frekky, 2006-2009 Bjorn -Andersson . Also major contributions by Anne Bezemer. +Copyright (c) 2006-2014 Erik Ekman , 2015 Frekk van Blagh, +2006-2009 Bjorn Andersson . Also major contributions by Anne Bezemer. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice From 4c8732f0ca0ea483a9cfb82cd6f8d03a709d61df Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 15 Nov 2015 20:57:45 +0800 Subject: [PATCH 071/113] Should now compile with mingw cross (still need zlib + err.h) --- src/tun.c | 6 +++--- src/windows.h | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/tun.c b/src/tun.c index 6b53c69..f563320 100644 --- a/src/tun.c +++ b/src/tun.c @@ -466,7 +466,7 @@ close_tun(int tun_fd) #ifdef WINDOWS32 int -write_tun(int tun_fd, char *data, size_t len) +write_tun(int tun_fd, uint8_t *data, size_t len) { DWORD written; DWORD res; @@ -490,12 +490,12 @@ write_tun(int tun_fd, char *data, size_t len) } ssize_t -read_tun(int tun_fd, char *buf, size_t len) +read_tun(int tun_fd, uint8_t *buf, size_t len) { int bytes; memset(buf, 0, 4); - bytes = recv(tun_fd, buf + 4, len - 4, 0); + bytes = recv(tun_fd, (char *)buf + 4, len - 4, 0); if (bytes < 0) { return bytes; } else { diff --git a/src/windows.h b/src/windows.h index e3295f1..2addd6e 100644 --- a/src/windows.h +++ b/src/windows.h @@ -102,11 +102,16 @@ struct ip /* Convenience macros for operations on timevals. NOTE: `timercmp' does not work for >= or <=. */ #define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) + +#ifndef timerclear #define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#endif +#ifndef timercmp #define timercmp(a, b, CMP) \ (((a)->tv_sec == (b)->tv_sec) ? \ ((a)->tv_usec CMP (b)->tv_usec) : \ ((a)->tv_sec CMP (b)->tv_sec)) +#endif #define timeradd(a, b, result) \ do { \ (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ @@ -127,18 +132,13 @@ struct ip } \ } while (0) -struct timezone -{ - int tz_minuteswest; /* minutes W of Greenwich */ - int tz_dsttime; /* type of dst correction */ -}; - +#if 0 inline int gettimeofday(struct timeval *tv, struct timezone *tz) { FILETIME ft; unsigned __int64 tmpres = 0; - static int tzflag = 0; + int tzflag = 0; if (NULL != tv) { @@ -169,6 +169,7 @@ gettimeofday(struct timeval *tv, struct timezone *tz) return 0; } +#endif DWORD WINAPI tun_reader(LPVOID arg); struct tun_data { From 03909ad59be6faa08853969175735add0f7d2a1d Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 16 Nov 2015 21:59:01 +0800 Subject: [PATCH 072/113] Fixed err.h errors --- src/server.c | 3 ++- src/window.c | 2 ++ src/windows.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 5575ffe..c5f840d 100644 --- a/src/server.c +++ b/src/server.c @@ -28,7 +28,6 @@ #include #include #include -#include #include #include "common.h" @@ -55,6 +54,8 @@ #ifdef WINDOWS32 WORD req_version = MAKEWORD(2, 2); WSADATA wsa_data; +#else +#include #endif /* Global server variables */ diff --git a/src/window.c b/src/window.c index 79d2b65..72a0ad5 100644 --- a/src/window.c +++ b/src/window.c @@ -19,7 +19,9 @@ #include #include #include +#ifndef WINDOWS32 #include +#endif #include #include diff --git a/src/windows.h b/src/windows.h index 2addd6e..cb7e399 100644 --- a/src/windows.h +++ b/src/windows.h @@ -20,6 +20,7 @@ typedef unsigned int in_addr_t; +#include #include #include #include From e3531378b5c4a6604b4c09f78b5bc98fafa83773 Mon Sep 17 00:00:00 2001 From: frekky Date: Tue, 17 Nov 2015 18:59:26 +0800 Subject: [PATCH 073/113] Fixed err.h error --- src/iodined.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iodined.c b/src/iodined.c index 4a87d9c..1bac743 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -28,7 +28,6 @@ #include #include #include -#include #include "common.h" #include "version.h" @@ -37,6 +36,7 @@ #include "windows.h" #include #else +#include #include #ifdef DARWIN #define BIND_8_COMPAT From 68a53c4565bf81a37c54459b86057feb5e67a236 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 21 Nov 2015 19:27:28 +0800 Subject: [PATCH 074/113] Fixed clang warnings + debug build --- src/Makefile | 6 +++--- src/client.c | 2 +- src/common.h | 2 +- src/util.c | 17 +++++++++++++++++ src/util.h | 17 ++--------------- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Makefile b/src/Makefile index fc1f80f..14465c8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ -COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o window.o common.o -CLIENTOBJS = iodine.o client.o util.o +COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o window.o common.o util.o +CLIENTOBJS = iodine.o client.o CLIENT = ../bin/iodine SERVEROBJS = iodined.o user.o fw_query.o server.o SERVER = ../bin/iodined @@ -13,7 +13,7 @@ LDFLAGS += -lz `sh osflags $(TARGETOS) link` $(LIBPATH) CFLAGS += -std=c99 -c -Wall -D$(OS) -pedantic `sh osflags $(TARGETOS) cflags` -DGITREVISION=\"$(HEAD_COMMIT)\" CFLAGS_RELEASE = -O3 -fno-strict-aliasing -CFLAGS_DEBUG = -g -Og -DDEBUG_BUILD +CFLAGS_DEBUG = -g -O0 -DDEBUG_BUILD all: CFLAGS += $(CFLAGS_RELEASE) all: executables diff --git a/src/client.c b/src/client.c index 2220422..08bdf2c 100644 --- a/src/client.c +++ b/src/client.c @@ -571,7 +571,7 @@ send_query(int fd, uint8_t *hostname) and if user hasn't specified server timeout/window timeout etc. */ num_sent++; - if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && + if (send_query_sendcnt > 0 && send_query_sendcnt < 100 && lazymode && connected && autodetect_server_timeout) { send_query_sendcnt++; diff --git a/src/common.h b/src/common.h index 3672c96..e360145 100644 --- a/src/common.h +++ b/src/common.h @@ -87,7 +87,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #define TIMEPRINT(...) \ struct timeval currenttime;\ gettimeofday(¤ttime, NULL);\ - fprintf(stderr, "%03ld.%03ld ", currenttime.tv_sec, currenttime.tv_usec / 1000);\ + fprintf(stderr, "%03ld.%03ld ", (long) currenttime.tv_sec, (long) currenttime.tv_usec / 1000);\ fprintf(stderr, __VA_ARGS__); #define DEBUG(level, ...) \ diff --git a/src/util.c b/src/util.c index 5780031..3bd162c 100644 --- a/src/util.c +++ b/src/util.c @@ -18,6 +18,23 @@ #include #include "common.h" +time_t +timeval_to_ms(struct timeval *tv) +{ + time_t ms = tv->tv_sec * 1000; + ms += (tv->tv_usec + 500) / 1000; + return ms; +} + +struct timeval +ms_to_timeval(time_t ms) +{ + struct timeval tv; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms - tv.tv_sec * 1000) * 1000; + return tv; +} + char * get_resolvconf_addr() { diff --git a/src/util.h b/src/util.h index 9a99a49..e232531 100644 --- a/src/util.h +++ b/src/util.h @@ -24,21 +24,8 @@ char *get_resolvconf_addr(); void socket_setrtable(int fd, int rtable); -inline time_t -timeval_to_ms(struct timeval *tv) -{ - time_t ms = tv->tv_sec * 1000; - ms += (tv->tv_usec + 500) / 1000; - return ms; -} +time_t timeval_to_ms(struct timeval *tv); -inline struct timeval -ms_to_timeval(time_t ms) -{ - struct timeval tv; - tv.tv_sec = ms / 1000; - tv.tv_usec = (ms - tv.tv_sec * 1000) * 1000; - return tv; -} +struct timeval ms_to_timeval(time_t ms); #endif From ac60bf66a98b486bb6616698f978026f636b64bc Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 21 Nov 2015 19:38:06 +0800 Subject: [PATCH 075/113] Fixed unsigned assignment warnings in tests --- tests/base32.c | 8 ++++---- tests/base64.c | 8 ++++---- tests/dns.c | 4 ++-- tests/encoding.c | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/base32.c b/tests/base32.c index 4b460e2..9c9fdff 100644 --- a/tests/base32.c +++ b/tests/base32.c @@ -49,7 +49,7 @@ START_TEST(test_base32_encode) b32 = get_base32_encoder(); len = sizeof(buf); - val = b32->encode(buf, &len, testpairs[_i].a, strlen(testpairs[_i].a)); + val = b32->encode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].a, strlen(testpairs[_i].a)); fail_unless(val == strlen(testpairs[_i].b)); fail_unless(strcmp(buf, testpairs[_i].b) == 0, @@ -67,7 +67,7 @@ START_TEST(test_base32_decode) b32 = get_base32_encoder(); len = sizeof(buf); - val = b32->decode(buf, &len, testpairs[_i].b, strlen(testpairs[_i].b)); + val = b32->decode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].b, strlen(testpairs[_i].b)); fail_unless(val == strlen(testpairs[_i].a)); fail_unless(strcmp(buf, testpairs[_i].a) == 0, @@ -110,7 +110,7 @@ START_TEST(test_base32_blksize) } rawbuf[i] = 0; - val = b32->encode(encbuf, &enclen, rawbuf, rawlen); + val = b32->encode((uint8_t *)encbuf, &enclen, (uint8_t *)rawbuf, rawlen); fail_unless(rawlen == 5, "raw length was %d not 5", rawlen); fail_unless(enclen == 5, "encoded %d bytes, not 5", enclen); @@ -119,7 +119,7 @@ START_TEST(test_base32_blksize) memset(rawbuf, 0, rawlen + 16); enclen = val; - val = b32->decode(rawbuf, &rawlen, encbuf, enclen); + val = b32->decode((uint8_t *)rawbuf, &rawlen, (uint8_t *)encbuf, enclen); fail_unless(rawlen == 5, "raw length was %d not 5", rawlen); fail_unless(val == 5, "val was not 5 but %d", val); diff --git a/tests/base64.c b/tests/base64.c index bcefc4c..e0d828f 100644 --- a/tests/base64.c +++ b/tests/base64.c @@ -75,7 +75,7 @@ START_TEST(test_base64_encode) b64 = get_base64_encoder(); len = sizeof(buf); - val = b64->encode(buf, &len, testpairs[_i].a, strlen(testpairs[_i].a)); + val = b64->encode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].a, strlen(testpairs[_i].a)); fail_unless(val == strlen(testpairs[_i].b)); fail_unless(strcmp(buf, testpairs[_i].b) == 0, @@ -93,7 +93,7 @@ START_TEST(test_base64_decode) b64 = get_base64_encoder(); len = sizeof(buf); - val = b64->decode(buf, &len, testpairs[_i].b, strlen(testpairs[_i].b)); + val = b64->decode((uint8_t *)buf, &len, (uint8_t *)testpairs[_i].b, strlen(testpairs[_i].b)); fail_unless(val == strlen(testpairs[_i].a)); fail_unless(strcmp(buf, testpairs[_i].a) == 0, @@ -124,7 +124,7 @@ START_TEST(test_base64_blksize) } rawbuf[i] = 0; - val = b64->encode(encbuf, &enclen, rawbuf, rawlen); + val = b64->encode((uint8_t *)encbuf, &enclen, (uint8_t *)rawbuf, rawlen); fail_unless(rawlen == 3, "raw length was %d not 3", rawlen); fail_unless(enclen == 3, "encoded %d bytes, not 3", enclen); @@ -133,7 +133,7 @@ START_TEST(test_base64_blksize) memset(rawbuf, 0, rawlen + 16); enclen = val; - val = b64->decode(rawbuf, &rawlen, encbuf, enclen); + val = b64->decode((uint8_t *)rawbuf, &rawlen, (uint8_t *)encbuf, enclen); fail_unless(rawlen == 3, "raw length was %d not 3", rawlen); fail_unless(val == 3); diff --git a/tests/dns.c b/tests/dns.c index 1f484ab..206df1f 100644 --- a/tests/dns.c +++ b/tests/dns.c @@ -86,7 +86,7 @@ START_TEST(test_encode_query) enc = get_base32_encoder(); *d++ = 'A'; - enc->encode(d, &enclen, innerData, strlen(innerData)); + enc->encode((uint8_t *)d, &enclen, (uint8_t *)innerData, strlen(innerData)); d = resolv + strlen(resolv); if (*d != '.') { *d++ = '.'; @@ -123,7 +123,7 @@ START_TEST(test_decode_query) dns_decode(buf, sizeof(buf), &q, QR_QUERY, query_packet, len); domain = strstr(q.name, topdomain); len = sizeof(buf); - unpack_data(buf, len, &(q.name[1]), (int) (domain - q.name) - 1, enc); + unpack_data((uint8_t *)buf, len, (uint8_t *)(q.name + 1), (int) (domain - q.name) - 1, enc); fail_unless(strncmp(buf, innerData, strlen(innerData)) == 0, "Did not extract expected host: '%s'", buf); fail_unless(strlen(buf) == strlen(innerData), "Bad host length: %d, expected %d: '%s'", strlen(buf), strlen(innerData), buf); diff --git a/tests/encoding.c b/tests/encoding.c index a2d5438..e736406 100644 --- a/tests/encoding.c +++ b/tests/encoding.c @@ -50,7 +50,7 @@ START_TEST(test_inline_dotify) memset(temp, 0, sizeof(temp)); strcpy(temp, dottests[_i].a); b = temp; - inline_dotify(b, sizeof(temp)); + inline_dotify((uint8_t *)b, sizeof(temp)); fail_unless(strcmp(dottests[_i].b, temp) == 0, "'%s' != '%s'", temp, dottests[_i].b); @@ -65,7 +65,7 @@ START_TEST(test_inline_undotify) memset(temp, 0, sizeof(temp)); strcpy(temp, dottests[_i].b); b = temp; - inline_undotify(b, sizeof(temp)); + inline_undotify((uint8_t *)b, sizeof(temp)); fail_unless(strcmp(dottests[_i].a, temp) == 0, "'%s' != '%s'", temp, dottests[_i].a); @@ -89,7 +89,7 @@ START_TEST(test_build_hostname) for (int j = 0; j < 10; j++) /* dummy header length */ for (i = 1; i < sizeof(data); i++) { buf[j] = j + 'A'; - int len = build_hostname(buf, buflen, data, i, topdomain, get_base32_encoder(), buflen, j); + int len = build_hostname((uint8_t *)buf, buflen, (uint8_t *)data, i, topdomain, get_base32_encoder(), buflen, j); fail_if(len > i); fail_if((strstr(buf, ".") - buf) > 63, "First label in encoded hostname >63 bytes!"); From 1ff16293dc474a1d4a2093f13bbe71673053a11c Mon Sep 17 00:00:00 2001 From: frekky Date: Wed, 16 Dec 2015 22:22:37 +0800 Subject: [PATCH 076/113] Refactor to match client.c usage --- src/base128.c | 4 +++- src/base128.h | 3 +++ src/base32.c | 4 +++- src/base32.h | 3 +++ src/base64.c | 4 +++- src/base64.h | 3 +++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/base128.c b/src/base128.c index 98784a3..7255c0f 100644 --- a/src/base128.c +++ b/src/base128.c @@ -60,7 +60,7 @@ static size_t base128_blksize_enc(); static size_t base128_encoded_length(size_t inputlen); static size_t base128_raw_length(size_t inputlen); -static struct encoder base128_encoder = +struct encoder base128_encoder = { "Base128", base128_encode, @@ -73,6 +73,8 @@ static struct encoder base128_encoder = base128_raw_length }; +struct encoder *b128 = &base128_encoder; + struct encoder *get_base128_encoder() { diff --git a/src/base128.h b/src/base128.h index f4c55f6..9235e62 100644 --- a/src/base128.h +++ b/src/base128.h @@ -17,6 +17,9 @@ #ifndef __BASE128_H__ #define __BASE128_H__ +extern struct encoder base128_encoder; +extern struct encoder *b128; + struct encoder *get_base128_encoder(void); #endif diff --git a/src/base32.c b/src/base32.c index ccb2133..86191ac 100644 --- a/src/base32.c +++ b/src/base32.c @@ -42,7 +42,7 @@ static size_t base32_encoded_length(size_t inputlen); static size_t base32_raw_length(size_t inputlen); -static struct encoder base32_encoder = +struct encoder base32_encoder = { "Base32", base32_encode, @@ -55,6 +55,8 @@ static struct encoder base32_encoder = base32_raw_length }; +struct encoder *b32 = &base32_encoder; + struct encoder *get_base32_encoder() { diff --git a/src/base32.h b/src/base32.h index 83ba784..3676564 100644 --- a/src/base32.h +++ b/src/base32.h @@ -18,6 +18,9 @@ #ifndef __BASE32_H__ #define __BASE32_H__ +extern struct encoder base32_encoder; +extern struct encoder *b32; + struct encoder *get_base32_encoder(void); int b32_5to8(int); diff --git a/src/base64.c b/src/base64.c index fb189e3..570120b 100644 --- a/src/base64.c +++ b/src/base64.c @@ -41,7 +41,7 @@ static size_t base64_blksize_enc(); static size_t base64_encoded_length(size_t inputlen); static size_t base64_raw_length(size_t inputlen); -static struct encoder base64_encoder = +struct encoder base64_encoder = { "Base64", base64_encode, @@ -54,6 +54,8 @@ static struct encoder base64_encoder = base64_raw_length }; +struct encoder *b64 = &base64_encoder; + struct encoder *get_base64_encoder() { diff --git a/src/base64.h b/src/base64.h index 8ce4742..6ed5aaa 100644 --- a/src/base64.h +++ b/src/base64.h @@ -18,6 +18,9 @@ #ifndef __BASE64_H__ #define __BASE64_H__ +extern struct encoder base64_encoder; +extern struct encoder *b64; + struct encoder *get_base64_encoder(void); #endif From a17b990570e5d20427a2190220cbd0f265a6524a Mon Sep 17 00:00:00 2001 From: frekky Date: Wed, 16 Dec 2015 22:44:51 +0800 Subject: [PATCH 077/113] Reduced max label size to accomodate fussy DNS servers At some point this may become a command line option --- src/encoding.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/encoding.h b/src/encoding.h index aed7c28..6b206b9 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -27,7 +27,9 @@ #define DOWNCODECCHECK1 "\000\000\000\000\377\377\377\377\125\125\125\125\252\252\252\252\201\143\310\322\307\174\262\027\137\117\316\311\111\055\122\041\141\251\161\040\045\263\006\163\346\330\104\060\171\120\127\277" #define DOWNCODECCHECK1_LEN 48 -#define DNS_MAXLABEL 63 +/* Don't push the limit with DNS servers: potentially unwanted behaviour + * if labels are all 63 chars long (DNS standard max label length) */ +#define DNS_MAXLABEL 59 struct encoder { char name[8]; From eb6d2fae77afebe7bccbf1d928ab0d3910cd4a8b Mon Sep 17 00:00:00 2001 From: frekky Date: Wed, 16 Dec 2015 22:51:59 +0800 Subject: [PATCH 078/113] Update makefile to produce correct base64u files --- src/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 14465c8..68dd326 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,11 +44,11 @@ base64u.o client.o iodined.o: base64u.h base64u.c: base64.c @echo Making $@ @echo '/* No use in editing, produced by Makefile! */' > $@ - @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\|[Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ base64u.h: base64.h @echo Making $@ @echo '/* No use in editing, produced by Makefile! */' > $@ - @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\|[Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ clean: @echo "Cleaning src/" From 506c1de67eb342cbae0ecde6eedc2c590079a3a6 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 15:03:37 +0800 Subject: [PATCH 079/113] Refactor server code to use global server_instance --- src/iodined.c | 223 ++++++++++++++++++++++++-------------------------- src/server.c | 202 ++++++++++++++++++--------------------------- src/server.h | 63 ++++++++------ src/user.c | 4 +- 4 files changed, 226 insertions(+), 266 deletions(-) diff --git a/src/iodined.c b/src/iodined.c index 1bac743..14e8b69 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -72,8 +72,40 @@ #ifdef WINDOWS32 WORD req_version = MAKEWORD(2, 2); WSADATA wsa_data; + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 +static void +syslog(int a, const char *str, ...) +{ + /* TODO: implement (add to event log), move to common.c */ + ; +} #endif +/* Definition of main server instance */ +struct server_instance server; + +static struct server_instance preset_default = { + .check_ip = 1, + .netmask = 27, + .ns_ip = INADDR_ANY, + .mtu = 1130, /* Very many relays give fragsize 1150 or slightly + higher for NULL; tun/zlib adds ~17 bytes. */ + .port = 53, + .addrfamily = AF_UNSPEC, + + /* Mark both file descriptors as unused */ + .dns_fds.v4fd = -1, + .dns_fds.v6fd = -1 +}; + /* Ask ipify.org webservice to get external ip */ static int get_external_ip(struct in_addr *ip) @@ -130,26 +162,9 @@ get_external_ip(struct in_addr *ip) static void sigint(int sig) { - server_stop(); + server.running = 0; } -#ifdef WINDOWS32 -#define LOG_EMERG 0 -#define LOG_ALERT 1 -#define LOG_CRIT 2 -#define LOG_ERR 3 -#define LOG_WARNING 4 -#define LOG_NOTICE 5 -#define LOG_INFO 6 -#define LOG_DEBUG 7 -static void -syslog(int a, const char *str, ...) -{ - /* TODO: implement (add to event log), move to common.c */ - ; -} -#endif - static void print_usage() { extern char *__progname; @@ -235,7 +250,7 @@ main(int argc, char **argv) char *listen_ip6; char *errormsg; #ifndef WINDOWS32 - struct passwd *pw; + struct passwd *pw = NULL; #endif int foreground; char *username; @@ -243,59 +258,35 @@ main(int argc, char **argv) char *context; char *device; char *pidfile; - int addrfamily; - struct dnsfd dns_fds; - int tun_fd; - - /* settings for forwarding normal DNS to - * local real DNS server */ - int bind_fd; - int bind_enable; int choice; - int port; - int mtu; + int skipipconfig; char *netsize; int ns_get_externalip; int retval; - int max_idle_time = 0; - struct sockaddr_storage dns4addr; - int dns4addr_len; - struct sockaddr_storage dns6addr; - int dns6addr_len; + #ifdef HAVE_SYSTEMD int nb_fds; #endif -#ifndef WINDOWS32 - pw = NULL; -#endif errormsg = NULL; username = NULL; newroot = NULL; context = NULL; device = NULL; foreground = 0; - bind_enable = 0; - bind_fd = 0; - mtu = 1130; /* Very many relays give fragsize 1150 or slightly - higher for NULL; tun/zlib adds ~17 bytes. */ - dns4addr_len = 0; - dns6addr_len = 0; listen_ip4 = NULL; listen_ip6 = NULL; - port = 53; + + ns_get_externalip = 0; - addrfamily = AF_UNSPEC; skipipconfig = 0; pidfile = NULL; srand(time(NULL)); retval = 0; - server_init(); - #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); #endif @@ -311,16 +302,16 @@ main(int argc, char **argv) while ((choice = getopt(argc, argv, "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { switch(choice) { case '4': - addrfamily = AF_INET; + server.addrfamily = AF_INET; break; case '6': - addrfamily = AF_INET6; + server.addrfamily = AF_INET6; break; case 'v': version(); break; case 'c': - check_ip = 0; + server.check_ip = 0; break; case 's': skipipconfig = 1; @@ -332,7 +323,7 @@ main(int argc, char **argv) help(); break; case 'D': - debug++; + server.debug++; break; case 'u': username = optarg; @@ -344,7 +335,7 @@ main(int argc, char **argv) device = optarg; break; case 'm': - mtu = atoi(optarg); + server.mtu = atoi(optarg); break; case 'l': listen_ip4 = optarg; @@ -353,28 +344,28 @@ main(int argc, char **argv) listen_ip6 = optarg; break; case 'p': - port = atoi(optarg); + server.port = atoi(optarg); break; case 'n': if (optarg && strcmp("auto", optarg) == 0) { ns_get_externalip = 1; } else { - ns_ip = inet_addr(optarg); + server.ns_ip = inet_addr(optarg); } break; case 'b': - bind_enable = 1; - bind_port = atoi(optarg); + server.bind_enable = 1; + server.bind_port = atoi(optarg); break; case 'F': pidfile = optarg; break; case 'i': - max_idle_time = atoi(optarg); + server.max_idle_time = atoi(optarg); break; case 'P': - strncpy(password, optarg, sizeof(password)); - password[sizeof(password)-1] = 0; + strncpy(server.password, optarg, sizeof(server.password)); + server.password[sizeof(server.password)-1] = 0; /* XXX: find better way of cleaning up ps(1) */ memset(optarg, 0, strlen(optarg)); @@ -400,18 +391,18 @@ main(int argc, char **argv) if (netsize) { *netsize = 0; netsize++; - netmask = atoi(netsize); + server.netmask = atoi(netsize); } - my_ip = inet_addr(argv[0]); + server.my_ip = inet_addr(argv[0]); - if (my_ip == INADDR_NONE) { + if (server.my_ip == INADDR_NONE) { warnx("Bad IP address to use inside tunnel."); usage(); } - topdomain = strdup(argv[1]); - if(check_topdomain(topdomain, &errormsg)) { + server.topdomain = strdup(argv[1]); + if(check_topdomain(server.topdomain, &errormsg)) { warnx("Invalid topdomain: %s", errormsg); usage(); /* NOTREACHED */ @@ -426,57 +417,59 @@ main(int argc, char **argv) #endif } - if (mtu <= 0) { + if (server.mtu <= 0) { warnx("Bad MTU given."); usage(); } - if(port < 1 || port > 65535) { + if(server.port < 1 || server.port > 65535) { warnx("Bad port number given."); usage(); } - if (port != 53) { + if (server.port != 53) { fprintf(stderr, "ALERT! Other dns servers expect you to run on port 53.\n"); - fprintf(stderr, "You must manually forward port 53 to port %d for things to work.\n", port); + fprintf(stderr, "You must manually forward port 53 to port %d for things to work.\n", server.port); } - if (debug) { - fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", debug); + if (server.debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", server.debug); fprintf(stderr, "Add more -D switches to set higher debug level.\n"); foreground = 1; } - if (addrfamily == AF_UNSPEC || addrfamily == AF_INET) { - dns4addr_len = get_addr(listen_ip4, port, AF_INET, AI_PASSIVE | AI_NUMERICHOST, &dns4addr); - if (dns4addr_len < 0) { + if (server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET) { + server.dns4addr_len = get_addr(listen_ip4, server.port, AF_INET, + AI_PASSIVE | AI_NUMERICHOST, &server.dns4addr); + if (server.dns4addr_len < 0) { warnx("Bad IPv4 address to listen on."); usage(); } } - if (addrfamily == AF_UNSPEC || addrfamily == AF_INET6) { - dns6addr_len = get_addr(listen_ip6, port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &dns6addr); - if (dns6addr_len < 0) { + if (server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET6) { + server.dns6addr_len = get_addr(listen_ip6, server.port,AF_INET6, + AI_PASSIVE | AI_NUMERICHOST, &server.dns6addr); + if (server.dns6addr_len < 0) { warnx("Bad IPv6 address to listen on."); usage(); } } - if(bind_enable) { - in_addr_t dns_ip = ((struct sockaddr_in *) &dns4addr)->sin_addr.s_addr; - if (bind_port < 1 || bind_port > 65535) { + if(server.bind_enable) { + in_addr_t dns_ip = ((struct sockaddr_in *) &server.dns4addr)->sin_addr.s_addr; + if (server.bind_port < 1 || server.bind_port > 65535) { warnx("Bad DNS server port number given."); usage(); /* NOTREACHED */ } /* Avoid forwarding loops */ - if (bind_port == port && (dns_ip == INADDR_ANY || dns_ip == htonl(0x7f000001L))) { - warnx("Forward port is same as listen port (%d), will create a loop!", bind_port); + if (server.bind_port == server.port && (dns_ip == INADDR_ANY || dns_ip == htonl(0x7f000001L))) { + warnx("Forward port is same as listen port (%d), will create a loop!", server.bind_port); fprintf(stderr, "Use -l to set listen ip to avoid this.\n"); usage(); /* NOTREACHED */ } fprintf(stderr, "Requests for domains outside of %s will be forwarded to port %d\n", - topdomain, bind_port); + server.topdomain, server.bind_port); } if (ns_get_externalip) { @@ -486,39 +479,35 @@ main(int argc, char **argv) fprintf(stderr, "Failed to get external IP via web service.\n"); exit(3); } - ns_ip = extip.s_addr; + server.ns_ip = extip.s_addr; fprintf(stderr, "Using %s as external IP.\n", inet_ntoa(extip)); } - if (ns_ip == INADDR_NONE) { + if (server.ns_ip == INADDR_NONE) { warnx("Bad IP address to return as nameserver."); usage(); } - if (netmask > 30 || netmask < 8) { - warnx("Bad netmask (%d bits). Use 8-30 bits.", netmask); + if (server.netmask > 30 || server.netmask < 8) { + warnx("Bad netmask (%d bits). Use 8-30 bits.", server.netmask); usage(); } - if (strlen(password) == 0) { + if (strlen(server.password) == 0) { if (NULL != getenv(PASSWORD_ENV_VAR)) - snprintf(password, sizeof(password), "%s", getenv(PASSWORD_ENV_VAR)); + snprintf(server.password, sizeof(server.password), "%s", getenv(PASSWORD_ENV_VAR)); else - read_password(password, sizeof(password)); + read_password(server.password, sizeof(server.password)); } - /* Mark both file descriptors as unused */ - dns_fds.v4fd = -1; - dns_fds.v6fd = -1; + created_users = init_users(server.my_ip, server.netmask); - created_users = init_users(my_ip, netmask); - - if ((tun_fd = open_tun(device)) == -1) { + if ((server.tun_fd = open_tun(device)) == -1) { /* nothing to clean up, just return */ return 1; } if (!skipipconfig) { const char *other_ip = users_get_first_ip(); - if (tun_setip(argv[0], other_ip, netmask) != 0 || tun_setmtu(mtu) != 0) { + if (tun_setip(argv[0], other_ip, server.netmask) != 0 || tun_setmtu(server.mtu) != 0) { retval = 1; free((void*) other_ip); goto cleanup; @@ -537,15 +526,15 @@ main(int argc, char **argv) dns_fds.v4fd = SD_LISTEN_FDS_START; } else { #endif - if ((addrfamily == AF_UNSPEC || addrfamily == AF_INET) && - (dns_fds.v4fd = open_dns(&dns4addr, dns4addr_len)) < 0) { + if ((server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET) && + (server.dns_fds.v4fd = open_dns(&server.dns4addr, server.dns4addr_len)) < 0) { retval = 1; goto cleanup; } - if ((addrfamily == AF_UNSPEC || addrfamily == AF_INET6) && + if ((server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET6) && /* Set IPv6 socket to V6ONLY */ - (dns_fds.v6fd = open_dns_opt(&dns6addr, dns6addr_len, 1)) < 0) { + (server.dns_fds.v6fd = open_dns_opt(&server.dns6addr, server.dns6addr_len, 1)) < 0) { retval = 1; goto cleanup; @@ -555,25 +544,23 @@ main(int argc, char **argv) #endif /* Setup dns file descriptors to get destination IP address */ - if (dns_fds.v4fd >= 0) - prepare_dns_fd(dns_fds.v4fd); - if (dns_fds.v6fd >= 0) - prepare_dns_fd(dns_fds.v6fd); + if (server.dns_fds.v4fd >= 0) + prepare_dns_fd(server.dns_fds.v4fd); + if (server.dns_fds.v6fd >= 0) + prepare_dns_fd(server.dns_fds.v6fd); - if (bind_enable) { - if ((bind_fd = open_dns_from_host(NULL, 0, AF_INET, 0)) < 0) { + if (server.bind_enable) { + if ((server.bind_fd = open_dns_from_host(NULL, 0, AF_INET, 0)) < 0) { retval = 1; goto cleanup; } } - my_mtu = mtu; - if (created_users < USERS) { fprintf(stderr, "Limiting to %d simultaneous users because of netmask /%d\n", - created_users, netmask); + created_users, server.netmask); } - fprintf(stderr, "Listening to dns for domain %s\n", topdomain); + fprintf(stderr, "Listening to dns for domain %s\n", server.topdomain); if (foreground == 0) do_detach(); @@ -606,18 +593,18 @@ main(int argc, char **argv) if (context != NULL) do_setcon(context); - syslog(LOG_INFO, "started, listening on port %d", port); + syslog(LOG_INFO, "started, listening on port %d", server.port); - server_tunnel(tun_fd, &dns_fds, bind_fd, max_idle_time); + server_tunnel(); syslog(LOG_INFO, "stopping"); - close_dns(bind_fd); + close_dns(server.bind_fd); cleanup: - if (dns_fds.v6fd >= 0) - close_dns(dns_fds.v6fd); - if (dns_fds.v4fd >= 0) - close_dns(dns_fds.v4fd); - close_tun(tun_fd); + 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); return retval; } diff --git a/src/server.c b/src/server.c index c5f840d..0e37af9 100644 --- a/src/server.c +++ b/src/server.c @@ -58,47 +58,6 @@ WSADATA wsa_data; #include #endif -/* Global server variables */ -int running = 1; -char *topdomain; -char password[33]; -struct encoder *b32; -struct encoder *b64; -struct encoder *b64u; -struct encoder *b128; - -int check_ip; -int my_mtu; -in_addr_t my_ip; -int netmask; - -in_addr_t ns_ip; - -int bind_port; -int debug; - -void -server_init() -{ - running = 1; - ns_ip = INADDR_ANY; - netmask = 27; - debug = 0; - check_ip = 1; - memset(password, 0, sizeof(password)); - fw_query_init(); - b32 = get_base32_encoder(); - b64 = get_base64_encoder(); - b64u = get_base64u_encoder(); - b128 = get_base128_encoder(); -} - -void -server_stop() -{ - running = 0; -} - static void send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr_storage *from, socklen_t fromlen) { @@ -143,7 +102,7 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr during a session (given QMEM_LEN is not very large). */ #define QMEM_DEBUG(l, u, ...) \ - if (debug >= l) {\ + if (server.debug >= l) {\ TIMEPRINT("[QMEM u%d (%lu/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ @@ -284,7 +243,7 @@ qmem_get_next_response(int userid) } static struct timeval -qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) +qmem_max_wait(int *touser, struct query **sendq) /* Gets max interval before the next query has to be responded to * Response(s) are sent automatically for queries if: * - the query has timed out @@ -358,7 +317,7 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %lu of %lu + sending another %lu", q->id, u->next_upstream_ack, sent, total, sending); - send_data_or_ping(dns_fds, userid, q, 0, immediate); + send_data_or_ping(userid, q, 0, immediate); if (sending > 0) sending--; @@ -377,7 +336,7 @@ qmem_max_wait(struct dnsfd *dns_fds, int *touser, struct query **sendq) } } - if (debug >= 5) { + if (server.debug >= 5) { time_t soonest_ms = timeval_to_ms(&soonest); if (nextq && nextuser >= 0) { QMEM_DEBUG(5, nextuser, "can wait for %lu ms, will send id %d", soonest_ms, nextq->id); @@ -435,7 +394,7 @@ forward_query(int bind_fd, struct query *q) newaddr = inet_addr("127.0.0.1"); myaddr = (struct sockaddr_in *) &(q->from); memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t)); - myaddr->sin_port = htons(bind_port); + myaddr->sin_port = htons(server.bind_port); DEBUG(2, "TX: NS reply"); @@ -471,8 +430,7 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s } void -send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, - int ping, int immediate) +send_data_or_ping(int userid, struct query *q, int ping, int immediate) /* 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 */ @@ -527,7 +485,7 @@ send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, if (f) memcpy(pkt + headerlen, f->data, datalen); - write_dns(get_dns_fd(dns_fds, &q->from), q, (char *)pkt, + write_dns(get_dns_fd(&server.dns_fds, &q->from), q, (char *)pkt, datalen + headerlen, users[userid].downenc); /* mark query as answered */ @@ -536,7 +494,7 @@ send_data_or_ping(struct dnsfd *dns_fds, int userid, struct query *q, } void -user_process_incoming_data(int tun_fd, struct dnsfd *dns_fds, int userid, int ack) +user_process_incoming_data(int userid, int ack) { uint8_t pkt[65536]; size_t datalen; @@ -553,13 +511,12 @@ user_process_incoming_data(int tun_fd, struct dnsfd *dns_fds, int userid, int ac if (datalen > 0) { /* Data reassembled successfully + cleared out of buffer */ - handle_full_packet(tun_fd, dns_fds, userid, pkt, datalen, compressed); + handle_full_packet(userid, pkt, datalen, compressed); } } static int -user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, - size_t len, int compressed) +user_send_data(int userid, uint8_t *indata, size_t len, int compressed) /* Appends data to a user's outgoing queue and sends it (in raw mode only) */ { size_t datalen; @@ -592,7 +549,7 @@ user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, } else if (data && datalen) { /* CONN_RAW_UDP */ if (!compressed) DEBUG(1, "Sending in RAW mode uncompressed to user %d!", userid); - int dns_fd = get_dns_fd(dns_fds, &users[userid].host); + int dns_fd = get_dns_fd(&server.dns_fds, &users[userid].host); send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA, &users[userid].host, users[userid].hostlen); ret = 1; @@ -602,7 +559,7 @@ user_send_data(int userid, struct dnsfd *dns_fds, uint8_t *indata, } static int -tunnel_bind(int bind_fd, struct dnsfd *dns_fds) +tunnel_bind() { char packet[64*1024]; struct sockaddr_storage from; @@ -613,7 +570,7 @@ tunnel_bind(int bind_fd, struct dnsfd *dns_fds) int r; fromlen = sizeof(struct sockaddr); - r = recvfrom(bind_fd, packet, sizeof(packet), 0, + r = recvfrom(server.bind_fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &fromlen); if (r <= 0) @@ -633,7 +590,7 @@ tunnel_bind(int bind_fd, struct dnsfd *dns_fds) DEBUG(3, "TX: client %s id %u, %d bytes", format_addr(&query->addr, query->addrlen), (id & 0xffff), r); - dns_fd = get_dns_fd(dns_fds, &query->addr); + dns_fd = get_dns_fd(&server.dns_fds, &query->addr); if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), query->addrlen) <= 0) { warn("forward reply error"); @@ -643,14 +600,14 @@ tunnel_bind(int bind_fd, struct dnsfd *dns_fds) } static int -tunnel_tun(int tun_fd, struct dnsfd *dns_fds) +tunnel_tun() { struct ip *header; static uint8_t in[64*1024]; int userid; int read; - if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) + if ((read = read_tun(server.tun_fd, in, sizeof(in))) <= 0) return 0; /* find target ip in packet, in is padded with 4 bytes TUN header */ @@ -662,25 +619,25 @@ tunnel_tun(int tun_fd, struct dnsfd *dns_fds) DEBUG(3, "IN: %d byte pkt from tun to user %d; compression %d", read, userid, users[userid].down_compression); - return user_send_data(userid, dns_fds, in, read, 0); + return user_send_data(userid, in, read, 0); } static int -tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) +tunnel_dns(int dns_fd) { struct query q; int read; int domain_len; int inside_topdomain = 0; - if ((read = read_dns(dns_fd, dns_fds, tun_fd, &q)) <= 0) + if ((read = read_dns(dns_fd, &q)) <= 0) return 0; DEBUG(3, "RX: client %s ID %5d, type %d, name %s", format_addr(&q.from, q.fromlen), q.id, q.type, q.name); - domain_len = strlen(q.name) - strlen(topdomain); - if (domain_len >= 0 && !strcasecmp(q.name + domain_len, topdomain)) + domain_len = strlen(q.name) - strlen(server.topdomain); + if (domain_len >= 0 && !strcasecmp(q.name + domain_len, server.topdomain)) inside_topdomain = 1; /* require dot before topdomain */ if (domain_len >= 1 && q.name[domain_len - 1] != '.') @@ -719,7 +676,7 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) case T_SRV: case T_TXT: /* encoding is "transparent" here */ - handle_null_request(tun_fd, dns_fd, dns_fds, &q, domain_len); + handle_null_request(dns_fd, &q, domain_len); break; case T_NS: handle_ns_request(dns_fd, &q); @@ -730,15 +687,15 @@ tunnel_dns(int tun_fd, int dns_fd, struct dnsfd *dns_fds, int bind_fd) } else { /* Forward query to other port ? */ DEBUG(2, "Requested domain outside our topdomain."); - if (bind_fd) { - forward_query(bind_fd, &q); + if (server.bind_fd) { + forward_query(server.bind_fd, &q); } } return 0; } int -server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) +server_tunnel() { struct timeval tv; fd_set fds; @@ -747,72 +704,72 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) struct query *answer_now = NULL; time_t last_action = time(NULL); - if (debug >= 5) - window_debug = debug - 3; + if (server.debug >= 5) + window_debug = server.debug - 3; - while (running) { + while (server.running) { int maxfd; /* max wait time based on pending queries */ - tv = qmem_max_wait(dns_fds, &userid, &answer_now); + tv = qmem_max_wait(&userid, &answer_now); FD_ZERO(&fds); maxfd = 0; - if (dns_fds->v4fd >= 0) { - FD_SET(dns_fds->v4fd, &fds); - maxfd = MAX(dns_fds->v4fd, maxfd); + if (server.dns_fds.v4fd >= 0) { + FD_SET(server.dns_fds.v4fd, &fds); + maxfd = MAX(server.dns_fds.v4fd, maxfd); } - if (dns_fds->v6fd >= 0) { - FD_SET(dns_fds->v6fd, &fds); - maxfd = MAX(dns_fds->v6fd, maxfd); + if (server.dns_fds.v6fd >= 0) { + FD_SET(server.dns_fds.v6fd, &fds); + maxfd = MAX(server.dns_fds.v6fd, maxfd); } - if (bind_fd) { + if (server.bind_fd) { /* wait for replies from real DNS */ - FD_SET(bind_fd, &fds); - maxfd = MAX(bind_fd, maxfd); + FD_SET(server.bind_fd, &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(tun_fd, &fds); - maxfd = MAX(tun_fd, maxfd); + FD_SET(server.tun_fd, &fds); + maxfd = MAX(server.tun_fd, maxfd); } i = select(maxfd + 1, &fds, NULL, NULL, &tv); if(i < 0) { - if (running) + if (server.running) warn("select"); return 1; } if (i == 0) { - if (max_idle_time) { + if (server.max_idle_time) { /* only trigger the check if that's worth ( ie, no need to loop over if there is something to send */ - if (difftime(time(NULL), last_action) > max_idle_time) { + if (difftime(time(NULL), last_action) > server.max_idle_time) { for (userid = 0; userid < created_users; userid++) { last_action = (users[userid].last_pkt > last_action) ? users[userid].last_pkt : last_action; } - if (difftime(time(NULL), last_action) > max_idle_time) { + if (difftime(time(NULL), last_action) > server.max_idle_time) { fprintf(stderr, "Server idle for too long, shutting down...\n"); - running = 0; + server.running = 0; } } } } else { - if (FD_ISSET(tun_fd, &fds)) { - tunnel_tun(tun_fd, dns_fds); + if (FD_ISSET(server.tun_fd, &fds)) { + tunnel_tun(); } - if (FD_ISSET(dns_fds->v4fd, &fds)) { - tunnel_dns(tun_fd, dns_fds->v4fd, dns_fds, bind_fd); + if (FD_ISSET(server.dns_fds.v4fd, &fds)) { + tunnel_dns(server.dns_fds.v4fd); } - if (FD_ISSET(dns_fds->v6fd, &fds)) { - tunnel_dns(tun_fd, dns_fds->v6fd, dns_fds, bind_fd); + if (FD_ISSET(server.dns_fds.v6fd, &fds)) { + tunnel_dns(server.dns_fds.v6fd); } - if (FD_ISSET(bind_fd, &fds)) { - tunnel_bind(bind_fd, dns_fds); + if (FD_ISSET(server.bind_fd, &fds)) { + tunnel_bind(); } } } @@ -821,7 +778,7 @@ server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) } void -handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len, int compressed) +handle_full_packet(int userid, uint8_t *data, size_t len, int compressed) { size_t rawlen; uint8_t out[64*1024], *rawdata; @@ -846,13 +803,13 @@ handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, DEBUG(2, "FULL PKT: %lu bytes from user %d (touser %d)", len, userid, touser); if (touser == -1) { /* send the uncompressed packet to tun device */ - write_tun(tun_fd, rawdata, rawlen); + write_tun(server.tun_fd, rawdata, rawlen); } else { /* don't re-compress if possible */ if (users[touser].down_compression && compressed) { - user_send_data(touser, dns_fds, data, len, 1); + user_send_data(touser, data, len, 1); } else { - user_send_data(touser, dns_fds, rawdata, rawlen, 0); + user_send_data(touser, rawdata, rawlen, 0); } } } else { @@ -879,7 +836,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri DEBUG(1, "RX-raw: login, len %lu, from user %d", len, userid); /* User sends hash of seed + 1 */ - login_calculate(myhash, 16, password, users[userid].seed + 1); + login_calculate(myhash, 16, server.password, users[userid].seed + 1); if (memcmp(packet, myhash, 16) == 0) { /* Update time info for user */ users[userid].last_pkt = time(NULL); @@ -890,7 +847,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri /* Correct hash, reply with hash of seed - 1 */ user_set_conn_type(userid, CONN_RAW_UDP); - login_calculate(myhash, 16, password, users[userid].seed - 1); + login_calculate(myhash, 16, server.password, users[userid].seed - 1); send_raw(fd, (uint8_t *)myhash, 16, userid, RAW_HDR_CMD_LOGIN, &q->from, q->fromlen); users[userid].authenticated_raw = 1; @@ -898,7 +855,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri } static void -handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_fds, int tun_fd, int userid) +handle_raw_data(uint8_t *packet, size_t len, struct query *q, int userid) { if (check_authenticated_user_and_ip(userid, q) != 0) { return; @@ -912,7 +869,7 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, struct dnsfd *dns_ DEBUG(3, "RX-raw: full pkt raw, length %lu, from user %d", len, userid); - handle_full_packet(tun_fd, dns_fds, userid, packet, len, 1); + handle_full_packet(userid, packet, len, 1); } static void @@ -933,7 +890,7 @@ handle_raw_ping(struct query *q, int dns_fd, int userid) } static int -raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsfd *dns_fds, int tun_fd) +raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd) { int raw_user; uint8_t raw_cmd; @@ -959,7 +916,7 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsf break; case RAW_HDR_CMD_DATA: /* Data packet */ - handle_raw_data(packet, len, q, dns_fds, tun_fd, raw_user); + handle_raw_data(packet, len, q, raw_user); break; case RAW_HDR_CMD_PING: /* Keepalive packet */ @@ -973,8 +930,7 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd, struct dnsf } int -read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) -/* FIXME: dns_fds and tun_fd are because of raw_decode() below */ +read_dns(int fd, struct query *q) { struct sockaddr_storage from; socklen_t addrlen; @@ -1010,7 +966,7 @@ read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) gettimeofday(&q->time_recv, NULL); /* TODO do not handle raw packets here! */ - if (raw_decode(packet, r, q, fd, dns_fds, tun_fd)) { + if (raw_decode(packet, r, q, fd)) { return 0; } if (dns_decode(NULL, 0, q, QR_QUERY, (char *)packet, r) < 0) { @@ -1173,7 +1129,7 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) } void -handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len) +handle_null_request(int dns_fd, struct query *q, int domain_len) /* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ { struct in_addr tempip; @@ -1269,20 +1225,20 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query return; } else { users[userid].last_pkt = time(NULL); - login_calculate(logindata, 16, password, users[userid].seed); + login_calculate(logindata, 16, server.password, users[userid].seed); if (read >= 18 && (memcmp(logindata, unpacked + 1, 16) == 0)) { /* Store login ok */ users[userid].authenticated = 1; /* Send ip/mtu/netmask info */ - tempip.s_addr = my_ip; + tempip.s_addr = server.my_ip; tmp[0] = strdup(inet_ntoa(tempip)); tempip.s_addr = users[userid].tun_ip; tmp[1] = strdup(inet_ntoa(tempip)); read = snprintf((char *)out, sizeof(out), "%s-%s-%d-%d", - tmp[0], tmp[1], my_mtu, netmask); + tmp[0], tmp[1], server.mtu, server.netmask); write_dns(dns_fd, q, (char *)out, read, users[userid].downenc); syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); @@ -1308,9 +1264,9 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query reply[0] = 'I'; if (q->from.ss_family == AF_INET) { - if (ns_ip != INADDR_ANY) { + if (server.ns_ip != INADDR_ANY) { /* If set, use assigned external ip (-n option) */ - memcpy(&reply[1], &ns_ip, sizeof(ns_ip)); + memcpy(&reply[1], &server.ns_ip, sizeof(server.ns_ip)); } else { /* otherwise return destination ip from packet */ struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; @@ -1647,11 +1603,11 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query 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(dns_fds, userid, q, 1, 1); + send_data_or_ping(userid, q, 1, 1); return; } - user_process_incoming_data(tun_fd, dns_fds, userid, dn_ack); + user_process_incoming_data(userid, dn_ack); /* if respond flag not set, query waits in qmem and is used later */ } else if (isxdigit(in[0])) { /* Upstream data packet */ @@ -1712,7 +1668,7 @@ handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query window_process_incoming_fragment(users[userid].incoming, &f); users[userid].next_upstream_ack = f.seqID; - user_process_incoming_data(tun_fd, dns_fds, userid, f.ack_other); + user_process_incoming_data(userid, f.ack_other); /* Nothing to do. ACK for this fragment is sent later in qmem_max_wait, * using an old query. This is left in qmem until needed/times out */ @@ -1727,14 +1683,14 @@ handle_ns_request(int dns_fd, struct query *q) char buf[64*1024]; int len; - if (ns_ip != INADDR_ANY) { + if (server.ns_ip != INADDR_ANY) { /* If ns_ip set, overwrite destination addr with it. * Destination addr will be sent as additional record (A, IN) */ struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); + memcpy(&addr->sin_addr, &server.ns_ip, sizeof(server.ns_ip)); } - len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain); + len = dns_encode_ns_response(buf, sizeof(buf), q, server.topdomain); if (len < 1) { warnx("dns_encode_ns_response doesn't fit"); return; @@ -1759,11 +1715,11 @@ handle_a_request(int dns_fd, struct query *q, int fakeip) struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; memcpy(&addr->sin_addr, &ip, sizeof(ip)); - } else if (ns_ip != INADDR_ANY) { + } else if (server.ns_ip != INADDR_ANY) { /* If ns_ip set, overwrite destination addr with it. * Destination addr will be sent as additional record (A, IN) */ struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&addr->sin_addr, &ns_ip, sizeof(ns_ip)); + memcpy(&addr->sin_addr, &server.ns_ip, sizeof(server.ns_ip)); } len = dns_encode_a_response(buf, sizeof(buf), q); diff --git a/src/server.h b/src/server.h index 6183bf6..4024b4c 100644 --- a/src/server.h +++ b/src/server.h @@ -40,7 +40,7 @@ /* Max number of incoming queries to hold at one time (recommended to be same as windowsize) * Memory = USERS * (sizeof(struct query_buffer) + sizeof(query) * QMEM_LEN) */ -#define QMEM_LEN 24 +#define QMEM_LEN 32 #define USE_DNSCACHE /* QMEM entries contain additional space for DNS responses. @@ -57,6 +57,8 @@ #define PASSWORD_ENV_VAR "IODINED_PASS" +#define INSTANCE server + #if defined IP_RECVDSTADDR # define DSTADDR_SOCKOPT IP_RECVDSTADDR # define dstaddr(x) ((struct in_addr *) CMSG_DATA(x)) @@ -82,6 +84,38 @@ struct dnsfd { int v6fd; }; +struct server_instance { + /* Global server variables */ + int running; + char *topdomain; + char password[33]; + int check_ip; + int my_mtu; + in_addr_t my_ip; + int netmask; + in_addr_t ns_ip; + int bind_port; + int debug; + + int addrfamily; + struct dnsfd dns_fds; + int tun_fd; + int port; + int mtu; + int max_idle_time; + struct sockaddr_storage dns4addr; + int dns4addr_len; + struct sockaddr_storage dns6addr; + int dns6addr_len; + + /* settings for forwarding normal DNS to + * local real DNS server */ + int bind_fd; + int bind_enable; +}; + +extern struct server_instance server; + typedef enum { VERSION_ACK, VERSION_NACK, @@ -110,34 +144,17 @@ struct qmem_buffer { size_t num_pending; /* number of pending queries */ }; -extern char *topdomain; -extern char password[33]; -extern struct encoder *b32; -extern struct encoder *b64; -extern struct encoder *b64u; -extern struct encoder *b128; - -extern int check_ip; -extern int my_mtu; -extern in_addr_t my_ip; -extern int netmask; - -extern in_addr_t ns_ip; - -extern int bind_port; -extern int debug; - void server_init(); void server_stop(); -int server_tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time); +int server_tunnel(); -int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q); +int read_dns(int fd, struct query *q); void write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc); -void handle_full_packet(int tun_fd, struct dnsfd *dns_fds, int userid, uint8_t *data, size_t len, int); -void handle_null_request(int tun_fd, int dns_fd, struct dnsfd *dns_fds, struct query *q, int domain_len); +void handle_full_packet(int userid, uint8_t *data, size_t len, int); +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(struct dnsfd *, int, struct query *, int, int); +void send_data_or_ping(int, struct query *, int, int); #endif /* __SERVER_H__ */ diff --git a/src/user.c b/src/user.c index 6c4b69f..98aca0a 100644 --- a/src/user.c +++ b/src/user.c @@ -78,7 +78,7 @@ init_users(in_addr_t my_ip, int netbits) snprintf(newip, sizeof(newip), "0.0.0.%d", i + skip + 1); ip = ipstart.s_addr + inet_addr(newip); } - if (debug >= 2) { + if (server.debug >= 2) { struct in_addr IP; IP.s_addr = ip; DEBUG(2, "User %d: IP %s", i, inet_ntoa(IP)); @@ -201,7 +201,7 @@ check_user_and_ip(int userid, struct query *q) if (!user_active(userid)) return 1; /* return early if IP checking is disabled */ - if (!check_ip) { + if (!server.check_ip) { return 0; } From 147119afee72f3f3dc9f95d08140be988ee0633e Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 15:04:39 +0800 Subject: [PATCH 080/113] Refactor client code to use client_instance --- src/client.c | 953 ++++++++++++++++++++------------------------------- src/client.h | 125 ++++++- src/iodine.c | 405 +++++++++++----------- 3 files changed, 694 insertions(+), 789 deletions(-) diff --git a/src/client.c b/src/client.c index 08bdf2c..8d33409 100644 --- a/src/client.c +++ b/src/client.c @@ -60,189 +60,24 @@ #include "util.h" #include "client.h" -/* Output flags for debug and time between stats update */ -int debug; -int stats; - -static int running; -static const char *password; - -/* Nameserver/domain info */ -static struct sockaddr_storage *nameserv_addrs; -static int nameserv_addrs_len; -static int current_nameserver; -static struct sockaddr_storage raw_serv; -static int raw_serv_len; -static const char *topdomain; - -static uint16_t rand_seed; - -/* Current up/downstream window data */ -static struct frag_buffer *outbuf; -static struct frag_buffer *inbuf; -static size_t windowsize_up; -static size_t windowsize_down; -static size_t maxfragsize_up; - -/* Next downstream seqID to be ACK'd (-1 if none pending) */ -static int next_downstream_ack; - -/* Remembering queries we sent for tracking purposes */ -static struct query_tuple *pending_queries; -static size_t num_pending; -static time_t max_timeout_ms; -static time_t send_interval_ms; -static time_t min_send_interval_ms; - -/* Server response timeout in ms and downstream window timeout */ -static time_t server_timeout_ms; -static time_t downstream_timeout_ms; -static int autodetect_server_timeout; - -/* Cumulative Round-Trip-Time in ms */ -static time_t rtt_total_ms; -static size_t num_immediate; - -/* Connection statistics */ -static size_t num_timeouts; -static size_t num_untracked; -static size_t num_servfail; -static size_t num_badip; -static size_t num_sent; -static size_t num_recv; -static size_t send_query_sendcnt = 0; -static size_t send_query_recvcnt = 0; -static size_t num_frags_sent; -static size_t num_frags_recv; -static size_t num_pings; - -/* My userid at the server */ -static char userid; -static char userid_char; /* used when sending (lowercase) */ -static char userid_char2; /* also accepted when receiving (uppercase) */ - -static uint16_t chunkid; - -/* Base32 encoder used for non-data packets and replies */ -static struct encoder *b32; -/* Base64 etc encoders for replies */ -static struct encoder *b64; -static struct encoder *b64u; -static struct encoder *b128; - -/* The encoder used for data packets - * Defaults to Base32, can be changed after handshake */ -static struct encoder *dataenc; - -/* Upstream/downstream compression flags */ -static int compression_up; -static int compression_down; - -/* The encoder to use for downstream data */ -static char downenc = ' '; - -/* set query type to send */ -static uint16_t do_qtype = T_UNSET; - -/* My connection mode */ -static enum connection conn; -static int connected; - -static int lazymode; -static long send_ping_soon; -static time_t lastdownstreamtime; -static size_t hostname_maxlen = 0xFF; - -void -client_init() -{ - running = 1; - b32 = get_base32_encoder(); - b64 = get_base64_encoder(); - b64u = get_base64u_encoder(); - b128 = get_base128_encoder(); - dataenc = get_base32_encoder(); - rand_seed = (uint16_t) rand(); - send_ping_soon = 1; /* send ping immediately after startup */ - conn = CONN_DNS_NULL; - - chunkid = (uint16_t) rand(); - - /* RFC says timeout minimum 5sec */ - max_timeout_ms = 5000; - - windowsize_up = 8; - windowsize_down = 8; - - compression_up = 0; - compression_down = 1; - - next_downstream_ack = -1; - current_nameserver = 0; - - maxfragsize_up = 100; - - num_immediate = 1; - rtt_total_ms = 1000; - send_interval_ms = 1000; - min_send_interval_ms = 1; - downstream_timeout_ms = 5000; - - outbuf = NULL; - inbuf = NULL; - pending_queries = NULL; - connected = 0; -} - -void -client_stop() -{ - running = 0; -} - -enum connection -client_get_conn() -{ - return conn; -} - -void -client_set_nameservers(struct sockaddr_storage *addr, int addrslen) -{ - nameserv_addrs = addr; - nameserv_addrs_len = addrslen; -} - -void -client_set_topdomain(const char *cp) -{ - topdomain = cp; -} - -void -client_set_password(const char *cp) -{ - password = cp; -} - int client_set_qtype(char *qtype) { if (!strcasecmp(qtype, "NULL")) - do_qtype = T_NULL; + this.do_qtype = T_NULL; else if (!strcasecmp(qtype, "PRIVATE")) - do_qtype = T_PRIVATE; + this.do_qtype = T_PRIVATE; else if (!strcasecmp(qtype, "CNAME")) - do_qtype = T_CNAME; + this.do_qtype = T_CNAME; else if (!strcasecmp(qtype, "A")) - do_qtype = T_A; + this.do_qtype = T_A; else if (!strcasecmp(qtype, "MX")) - do_qtype = T_MX; + this.do_qtype = T_MX; else if (!strcasecmp(qtype, "SRV")) - do_qtype = T_SRV; + this.do_qtype = T_SRV; else if (!strcasecmp(qtype, "TXT")) - do_qtype = T_TXT; - return (do_qtype == T_UNSET); + this.do_qtype = T_TXT; + return (this.do_qtype == T_UNSET); } char * @@ -250,111 +85,75 @@ client_get_qtype() { char *c = "UNDEFINED"; - if (do_qtype == T_NULL) c = "NULL"; - else if (do_qtype == T_PRIVATE) c = "PRIVATE"; - else if (do_qtype == T_CNAME) c = "CNAME"; - else if (do_qtype == T_A) c = "A"; - else if (do_qtype == T_MX) c = "MX"; - else if (do_qtype == T_SRV) c = "SRV"; - else if (do_qtype == T_TXT) c = "TXT"; + if (this.do_qtype == T_NULL) c = "NULL"; + else if (this.do_qtype == T_PRIVATE) c = "PRIVATE"; + else if (this.do_qtype == T_CNAME) c = "CNAME"; + else if (this.do_qtype == T_A) c = "A"; + else if (this.do_qtype == T_MX) c = "MX"; + else if (this.do_qtype == T_SRV) c = "SRV"; + else if (this.do_qtype == T_TXT) c = "TXT"; return c; } -void -client_set_downenc(char *encoding) +char +parse_encoding(char *encoding) { + char enc_char = 0; if (!strcasecmp(encoding, "base32")) - downenc = 'T'; + enc_char = 'T'; else if (!strcasecmp(encoding, "base64")) - downenc = 'S'; + enc_char = 'S'; else if (!strcasecmp(encoding, "base64u")) - downenc = 'U'; + enc_char = 'U'; else if (!strcasecmp(encoding, "base128")) - downenc = 'V'; + enc_char = 'V'; else if (!strcasecmp(encoding, "raw")) - downenc = 'R'; -} - -void -client_set_compression(int up, int down) -{ - compression_up = up; - compression_down = down; -} - -void -client_set_dnstimeout(double timeout, double servertimeout, double downfrag, int autodetect) -{ - max_timeout_ms = timeout * 1000; - server_timeout_ms = servertimeout * 1000; - downstream_timeout_ms = downfrag * 1000; - autodetect_server_timeout = autodetect; -} - -void -client_set_interval(double interval_msec, double mininterval_msec) -{ - send_interval_ms = interval_msec; - min_send_interval_ms = mininterval_msec; -} - -void -client_set_lazymode(int lazy_mode) -{ - lazymode = lazy_mode; -} - -void -client_set_windowsize(size_t up, size_t down) -/* set window sizes for upstream and downstream - * XXX upstream/downstream windowsizes might as well be the same */ -{ - windowsize_up = up; - windowsize_down = down; + enc_char = 'R'; + return enc_char; } void client_set_hostname_maxlen(size_t i) { - if (i <= 0xFF && i != hostname_maxlen) { - hostname_maxlen = i; - maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); - if (outbuf) - outbuf->maxfraglen = maxfragsize_up; + if (i <= 0xFF && i != this.hostname_maxlen) { + this.hostname_maxlen = i; + this.maxfragsize_up = get_raw_length_from_dns(this.hostname_maxlen - UPSTREAM_HDR, this.dataenc, this.topdomain); + if (this.outbuf) + this.outbuf->maxfraglen = this.maxfragsize_up; } } const char * client_get_raw_addr() { - return format_addr(&raw_serv, raw_serv_len); + return format_addr(&this.raw_serv, this.raw_serv_len); } void client_rotate_nameserver() { - current_nameserver ++; - if (current_nameserver >= nameserv_addrs_len) - current_nameserver = 0; + this.current_nameserver ++; + if (this.current_nameserver >= this.nameserv_addrs_len) + this.current_nameserver = 0; } void immediate_mode_defaults() { - send_interval_ms = MIN(rtt_total_ms / num_immediate, 1000); - max_timeout_ms = MAX(4 * rtt_total_ms / num_immediate, 5000); - server_timeout_ms = 0; + this.send_interval_ms = MIN(this.rtt_total_ms / this.num_immediate, 1000); + this.max_timeout_ms = MAX(4 * this.rtt_total_ms / this.num_immediate, 5000); + this.server_timeout_ms = 0; } /* Client-side query tracking for lazy mode */ -/* Handy macro for printing stats with messages */ +/* Handy macro for printing this.stats with messages */ #ifdef DEBUG_BUILD #define QTRACK_DEBUG(l, ...) \ - if (debug >= l) {\ - TIMEPRINT("[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", num_pending, PENDING_QUERIES_LENGTH, \ - num_untracked, num_timeouts, window_sending(outbuf, NULL), outbuf->numitems); \ + if (this.debug >= l) {\ + TIMEPRINT("[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", this.num_pending, PENDING_QUERIES_LENGTH, \ + this.num_untracked, this.num_timeouts, window_sending(this.outbuf, NULL), this.outbuf->numitems); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } @@ -371,34 +170,34 @@ update_server_timeout(int dns_fd, int handshake) static size_t num_rtt_timeouts = 0; /* Get average RTT in ms */ - rtt_ms = rtt_total_ms / num_immediate; - if (rtt_ms >= max_timeout_ms && num_immediate > 5) { + rtt_ms = this.rtt_total_ms / this.num_immediate; + if (rtt_ms >= this.max_timeout_ms && this.num_immediate > 5) { num_rtt_timeouts++; if (num_rtt_timeouts < 3) { fprintf(stderr, "Target interval of %ld ms less than average round-trip of " - "%ld ms! Try increasing interval with -I.\n", max_timeout_ms, rtt_ms); + "%ld ms! Try increasing interval with -I.\n", this.max_timeout_ms, rtt_ms); } else { /* bump up target timeout */ - max_timeout_ms = rtt_ms + 1000; - server_timeout_ms = 1000; - if (lazymode) + this.max_timeout_ms = rtt_ms + 1000; + this.server_timeout_ms = 1000; + if (this.lazymode) fprintf(stderr, "Adjusting server timeout to %ld ms, target interval %ld ms. Try -I%.1f next time with this network.\n", - server_timeout_ms, max_timeout_ms, max_timeout_ms / 1000.0); + this.server_timeout_ms, this.max_timeout_ms, this.max_timeout_ms / 1000.0); num_rtt_timeouts = 0; } } else { /* Set server timeout based on target interval and RTT */ - server_timeout_ms = max_timeout_ms - rtt_ms; - if (server_timeout_ms <= 0) { - server_timeout_ms = 0; + this.server_timeout_ms = this.max_timeout_ms - rtt_ms; + if (this.server_timeout_ms <= 0) { + this.server_timeout_ms = 0; fprintf(stderr, "Setting server timeout to 0 ms: if this continues try disabling lazy mode. (-L0)\n"); } } /* update up/down window timeouts to something reasonable */ - downstream_timeout_ms = rtt_ms * 2; - outbuf->timeout = ms_to_timeval(downstream_timeout_ms); + this.downstream_timeout_ms = rtt_ms * 2; + this.outbuf->timeout = ms_to_timeval(this.downstream_timeout_ms); if (handshake) { /* Send ping handshake to set server timeout */ @@ -411,20 +210,20 @@ static void check_pending_queries() /* Updates pending queries list */ { - num_pending = 0; + this.num_pending = 0; struct timeval now, qtimeout, max_timeout; gettimeofday(&now, NULL); /* Max timeout for queries is max interval + 1 second extra */ - max_timeout = ms_to_timeval(max_timeout_ms + 1000); + max_timeout = ms_to_timeval(this.max_timeout_ms + 1000); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { - if (pending_queries[i].time.tv_sec > 0 && pending_queries[i].id >= 0) { - timeradd(&pending_queries[i].time, &max_timeout, &qtimeout); + if (this.pending_queries[i].time.tv_sec > 0 && this.pending_queries[i].id >= 0) { + timeradd(&this.pending_queries[i].time, &max_timeout, &qtimeout); if (!timercmp(&qtimeout, &now, >)) { /* Query has timed out, clear timestamp but leave ID */ - pending_queries[i].time.tv_sec = 0; - num_timeouts++; + this.pending_queries[i].time.tv_sec = 0; + this.num_timeouts++; } - num_pending++; + this.num_pending++; } } } @@ -433,7 +232,7 @@ static void query_sent_now(int id) { int i = 0, found = 0; - if (!pending_queries) + if (!this.pending_queries) return; if (id < 0 || id > 65535) @@ -441,14 +240,14 @@ query_sent_now(int id) /* Replace any empty queries first, then timed out ones if necessary */ for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { - if (pending_queries[i].id < 0) { + if (this.pending_queries[i].id < 0) { found = 1; break; } } if (!found) { for (i = 0; i < PENDING_QUERIES_LENGTH; i++) { - if (pending_queries[i].time.tv_sec == 0) { + if (this.pending_queries[i].time.tv_sec == 0) { found = 1; break; } @@ -459,10 +258,10 @@ query_sent_now(int id) QTRACK_DEBUG(1, "Buffer full! Failed to add id %d.", id); } else { /* Add query into found location */ - pending_queries[i].id = id; - gettimeofday(&pending_queries[i].time, NULL); - num_pending ++; - QTRACK_DEBUG(4, "Adding query id %d into pending_queries[%d]", id, i); + this.pending_queries[i].id = id; + gettimeofday(&this.pending_queries[i].time, NULL); + this.num_pending ++; + QTRACK_DEBUG(4, "Adding query id %d into this.pending_queries[%d]", id, i); id = -1; } } @@ -478,15 +277,15 @@ got_response(int id, int immediate, int fail) fail ? ", FAIL" : ""); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) { - if (id >= 0 && pending_queries[i].id == id) { - if (num_pending > 0) - num_pending--; + if (id >= 0 && this.pending_queries[i].id == id) { + if (this.num_pending > 0) + this.num_pending--; - if (pending_queries[i].time.tv_sec == 0) { - if (num_timeouts > 0) { + if (this.pending_queries[i].time.tv_sec == 0) { + if (this.num_timeouts > 0) { /* If query has timed out but is still stored - just in case * ID is kept on timeout in check_pending_queries() */ - num_timeouts --; + this.num_timeouts --; immediate = 0; } else { /* query is empty */ @@ -494,8 +293,8 @@ got_response(int id, int immediate, int fail) } } - if (immediate || debug >= 4) { - timersub(&now, &pending_queries[i].time, &rtt); + if (immediate || this.debug >= 4) { + timersub(&now, &this.pending_queries[i].time, &rtt); rtt_ms = timeval_to_ms(&rtt); } @@ -506,23 +305,23 @@ got_response(int id, int immediate, int fail) more detailed connection statistics like RTT. This lets us determine and adjust server lazy response time during the session much more accurately. */ - rtt_total_ms += rtt_ms; - num_immediate++; + this.rtt_total_ms += rtt_ms; + this.num_immediate++; - if (autodetect_server_timeout) + if (this.autodetect_server_timeout) update_server_timeout(-1, 0); } /* Remove query info from buffer to mark it as answered */ id = -1; - pending_queries[i].id = -1; - pending_queries[i].time.tv_sec = 0; + this.pending_queries[i].id = -1; + this.pending_queries[i].time.tv_sec = 0; break; } } if (id > 0) { QTRACK_DEBUG(4, " got untracked response to id %d.", id); - num_untracked++; + this.num_untracked++; } } @@ -536,13 +335,13 @@ send_query(int fd, uint8_t *hostname) DEBUG(3, "TX: pkt len %lu: hostname '%s'", strlen((char *)hostname), hostname); - chunkid += 7727; - if (chunkid == 0) + this.chunkid += 7727; + if (this.chunkid == 0) /* 0 is used as "no-query" in iodined.c */ - chunkid = rand() & 0xFF; + this.chunkid = rand() & 0xFF; - q.id = chunkid; - q.type = do_qtype; + q.id = this.chunkid; + q.type = this.do_qtype; len = dns_encode((char *)packet, sizeof(packet), &q, QR_QUERY, (char *)hostname, strlen((char *)hostname)); if (len < 1) { @@ -552,7 +351,7 @@ send_query(int fd, uint8_t *hostname) DEBUG(4, " Sendquery: id %5d name[0] '%c'", q.id, hostname[0]); - sendto(fd, packet, len, 0, (struct sockaddr*) &nameserv_addrs[current_nameserver], + sendto(fd, packet, len, 0, (struct sockaddr*) &this.nameserv_addrs[this.current_nameserver], sizeof(struct sockaddr_storage)); client_rotate_nameserver(); @@ -567,30 +366,30 @@ send_query(int fd, uint8_t *hostname) Here we detect and fix these situations. (Can't very well do this anywhere else; this is the only place we'll reliably get to in such situations.) - Note: only start fixing up connection AFTER we have connected + Note: only start fixing up connection AFTER we have this.connected and if user hasn't specified server timeout/window timeout etc. */ - num_sent++; - if (send_query_sendcnt > 0 && send_query_sendcnt < 100 && - lazymode && connected && autodetect_server_timeout) { - send_query_sendcnt++; + this.num_sent++; + if (this.send_query_sendcnt > 0 && this.send_query_sendcnt < 100 && + this.lazymode && this.connected && this.autodetect_server_timeout) { + this.send_query_sendcnt++; - if ((send_query_sendcnt > windowsize_down && send_query_recvcnt <= 0) || - (send_query_sendcnt > 2 * windowsize_down && 4 * send_query_recvcnt < send_query_sendcnt)) { - if (max_timeout_ms > 500) { - max_timeout_ms -= 200; - double secs = (double) max_timeout_ms / 1000.0; + if ((this.send_query_sendcnt > this.windowsize_down && this.send_query_recvcnt <= 0) || + (this.send_query_sendcnt > 2 * this.windowsize_down && 4 * this.send_query_recvcnt < this.send_query_sendcnt)) { + if (this.max_timeout_ms > 500) { + this.max_timeout_ms -= 200; + double secs = (double) this.max_timeout_ms / 1000.0; fprintf(stderr, "Receiving too few answers. Setting target timeout to %.1fs (-I%.1f)\n", secs, secs); /* restart counting */ - send_query_sendcnt = 0; - send_query_recvcnt = 0; + this.send_query_sendcnt = 0; + this.send_query_recvcnt = 0; - } else if (lazymode) { + } else if (this.lazymode) { fprintf(stderr, "Receiving too few answers. Will try to switch lazy mode off, but that may not" " always work any more. Start with -L0 next time on this network.\n"); - lazymode = 0; - server_timeout_ms = 0; + this.lazymode = 0; + this.server_timeout_ms = 0; } update_server_timeout(fd, 1); } @@ -614,13 +413,13 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd) len += RAW_HDR_LEN; packet[RAW_HDR_CMD] = (cmd & 0xF0) | (user & 0x0F); - sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv)); + sendto(fd, packet, len, 0, (struct sockaddr*)&this.raw_serv, sizeof(this.raw_serv)); } static void send_raw_data(int dns_fd, uint8_t *data, size_t datalen) { - send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA); + send_raw(dns_fd, data, datalen, this.userid, RAW_HDR_CMD_DATA); } @@ -633,7 +432,7 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) buf[0] = cmd; - build_hostname(buf, sizeof(buf), data, datalen, topdomain, b32, hostname_maxlen, 1); + build_hostname(buf, sizeof(buf), data, datalen, this.topdomain, b32, this.hostname_maxlen, 1); return send_query(fd, buf); } @@ -641,34 +440,34 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) int send_ping(int fd, int ping_response, int ack, int set_timeout) { - num_pings++; - if (conn == CONN_DNS_NULL) { + this.num_pings++; + if (this.conn == CONN_DNS_NULL) { uint8_t data[13]; int id; /* Build ping header (see doc/proto_xxxxxxxx.txt) */ - data[0] = userid; + data[0] = this.userid; data[1] = ack & 0xFF; - if (outbuf && inbuf) { - data[2] = outbuf->windowsize & 0xff; /* Upstream window size */ - data[3] = inbuf->windowsize & 0xff; /* Downstream window size */ - data[4] = outbuf->start_seq_id & 0xff; /* Upstream window start */ - data[5] = inbuf->start_seq_id & 0xff; /* Downstream window start */ + if (this.outbuf && this.inbuf) { + data[2] = this.outbuf->windowsize & 0xff; /* Upstream window size */ + data[3] = this.inbuf->windowsize & 0xff; /* Downstream window size */ + data[4] = this.outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[5] = this.inbuf->start_seq_id & 0xff; /* Downstream window start */ } - *(uint16_t *) (data + 6) = htons(server_timeout_ms); - *(uint16_t *) (data + 8) = htons(downstream_timeout_ms); + *(uint16_t *) (data + 6) = htons(this.server_timeout_ms); + *(uint16_t *) (data + 8) = htons(this.downstream_timeout_ms); /* update server frag/lazy timeout, ack flag, respond with ping flag */ data[10] = ((set_timeout & 1) << 4) | ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); - data[11] = (rand_seed >> 8) & 0xff; - data[12] = (rand_seed >> 0) & 0xff; - rand_seed += 1; + data[11] = (this.rand_seed >> 8) & 0xff; + data[12] = (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 " : "", server_timeout_ms, - downstream_timeout_ms, data[8]); + ping_response, ack, set_timeout ? "SET " : "", this.server_timeout_ms, + this.downstream_timeout_ms, data[8]); id = send_packet(fd, 'p', data, sizeof(data)); @@ -676,7 +475,7 @@ send_ping(int fd, int ping_response, int ack, int set_timeout) query_sent_now(id); return id; } else { - send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); + send_raw(fd, NULL, 0, this.userid, RAW_HDR_CMD_PING); return -1; } } @@ -693,20 +492,20 @@ send_next_frag(int fd) size_t buflen; /* Get next fragment to send */ - f = window_get_next_sending_fragment(outbuf, &next_downstream_ack); + f = window_get_next_sending_fragment(this.outbuf, &this.next_downstream_ack); if (!f) { - if (outbuf->numitems > 0) { + 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(fd, 1, next_downstream_ack, 1); - next_downstream_ack = -1; - window_tick(outbuf); + send_ping(fd, 1, this.next_downstream_ack, 1); + this.next_downstream_ack = -1; + window_tick(this.outbuf); } return; /* nothing to send */ } /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ - buf[0] = userid_char; /* First byte is hex userid */ + buf[0] = this.userid_char; /* First byte is hex this.userid */ buf[1] = datacmcchars[datacmc]; /* Second byte is data-CMC */ @@ -723,8 +522,8 @@ send_next_frag(int fd) b32->encode(buf + 2, &buflen, hdr, 3); /* Encode data into buf after header (6 = user + CMC + 4 bytes header) */ - build_hostname(buf, sizeof(buf), f->data, f->len, topdomain, - dataenc, hostname_maxlen, 6); + build_hostname(buf, sizeof(buf), f->data, f->len, this.topdomain, + this.dataenc, this.hostname_maxlen, 6); datacmc++; if (datacmc >= 36) @@ -737,8 +536,8 @@ send_next_frag(int fd) /* Log query ID as being sent now */ query_sent_now(id); - window_tick(outbuf); - num_frags_sent++; + window_tick(this.outbuf); + this.num_frags_sent++; } static void @@ -904,7 +703,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query return -1; } - if (conn == CONN_DNS_NULL) { + if (this.conn == CONN_DNS_NULL) { int rv; if (r <= 0) /* useless packet */ @@ -974,12 +773,12 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query if (memcmp(data, raw_header, RAW_HDR_IDENT_LEN)) return 0; /* should be my user id */ - if (RAW_HDR_GET_USR(data) != userid) + if (RAW_HDR_GET_USR(data) != this.userid) return 0; if (RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_DATA || RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_PING) - lastdownstreamtime = time(NULL); + this.lastdownstreamtime = time(NULL); /* should be data packet */ if (RAW_HDR_GET_CMD(data) != RAW_HDR_CMD_DATA) @@ -1035,7 +834,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q); qcmd = toupper(q.name[0]); - if (q.id != chunkid || qcmd != cmd) { + if (q.id != this.chunkid || qcmd != cmd) { DEBUG(1, "Ignoring unfitting reply id %d starting with '%c'", q.id, q.name[0]); continue; } @@ -1053,7 +852,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) q.name[0] == 'V' || q.name[0] == 'v')) { fprintf(stderr, "Got empty reply. This nameserver may not be resolving recursively, use another.\n"); fprintf(stderr, "Try \"iodine [options] ns.%s %s\" first, it might just work.\n", - topdomain, topdomain); + this.topdomain, this.topdomain); return -2; } @@ -1063,7 +862,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) mostly long after we've moved along to some other queries. However, some DNS relays, once they throw a SERVFAIL, will for several seconds apply it immediately to _any_ new query - for the same topdomain. When this happens, waiting a while + for the same this.topdomain. When this happens, waiting a while is the only option that works. */ if (rv < 0 && q.rcode == SERVFAIL) @@ -1132,9 +931,9 @@ tunnel_tun(int tun_fd, int dns_fd) if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) return -1; - DEBUG(2, " IN: %lu bytes on tunnel, to be compressed: %d", read, compression_up); + DEBUG(2, " IN: %lu bytes on tunnel, to be compressed: %d", read, this.compression_up); - if (conn != CONN_DNS_NULL || compression_up) { + if (this.conn != CONN_DNS_NULL || this.compression_up) { datalen = sizeof(out); compress2(out, &datalen, in, read, 9); data = out; @@ -1143,15 +942,15 @@ tunnel_tun(int tun_fd, int dns_fd) data = in; } - if (conn == CONN_DNS_NULL) { + if (this.conn == CONN_DNS_NULL) { /* Check if outgoing buffer can hold data */ - if (window_buffer_available(outbuf) < (read / MAX_FRAGSIZE) + 1) { + if (window_buffer_available(this.outbuf) < (read / MAX_FRAGSIZE) + 1) { DEBUG(1, " Outgoing buffer full (%lu/%lu), not adding data!", - outbuf->numitems, outbuf->length); + this.outbuf->numitems, this.outbuf->length); return -1; } - window_add_outgoing_data(outbuf, data, datalen, compression_up); + 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(dns_fd, data, datalen); @@ -1174,7 +973,7 @@ tunnel_dns(int tun_fd, int dns_fd) memset(cbuf, 0, sizeof(cbuf)); read = read_dns_withq(dns_fd, tun_fd, cbuf, sizeof(cbuf), &q); - if (conn != CONN_DNS_NULL) + if (this.conn != CONN_DNS_NULL) return 1; /* everything already done */ /* Don't process anything that isn't data for us; usually error @@ -1183,8 +982,8 @@ tunnel_dns(int tun_fd, int dns_fd) timeout, which means we won't send a proper ping for a while. So make select a bit faster, <1sec. */ if (q.name[0] != 'P' && q.name[0] != 'p' && - q.name[0] != userid_char && q.name[0] != userid_char2) { - send_ping_soon = 700; + q.name[0] != this.userid_char && q.name[0] != this.userid_char2) { + this.send_ping_soon = 700; got_response(q.id, 0, 0); return -1; /* nothing done */ } @@ -1198,60 +997,60 @@ tunnel_dns(int tun_fd, int dns_fd) write_dns_error(&q, 0); if (q.rcode == SERVFAIL && read < 0) { - num_servfail++; + this.num_servfail++; - if (lazymode) { + if (this.lazymode) { - if (send_query_recvcnt < 500 && num_servfail < 4) { - fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...\n", num_servfail); + if (this.send_query_recvcnt < 500 && this.num_servfail < 4) { + fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...\n", this.num_servfail); - } else if (send_query_recvcnt < 500 && num_servfail >= 10 && - autodetect_server_timeout && max_timeout_ms >= 500 && num_servfail % 5 == 0) { + } else if (this.send_query_recvcnt < 500 && this.num_servfail >= 10 && + this.autodetect_server_timeout && this.max_timeout_ms >= 500 && this.num_servfail % 5 == 0) { - max_timeout_ms -= 200; - double target_timeout = (float) max_timeout_ms / 1000.0; + this.max_timeout_ms -= 200; + double target_timeout = (float) this.max_timeout_ms / 1000.0; fprintf(stderr, "Too many SERVFAILs (%ld), reducing timeout to" " %.1f secs. (use -I%.1f next time on this network)\n", - num_servfail, target_timeout, target_timeout); + this.num_servfail, target_timeout, target_timeout); - /* Reset query counts stats */ - send_query_sendcnt = 0; - send_query_recvcnt = 0; + /* Reset query counts this.stats */ + this.send_query_sendcnt = 0; + this.send_query_recvcnt = 0; update_server_timeout(dns_fd, 1); - } else if (send_query_recvcnt < 500 && num_servfail >= 40 && - autodetect_server_timeout && max_timeout_ms < 500) { + } else if (this.send_query_recvcnt < 500 && this.num_servfail >= 40 && + this.autodetect_server_timeout && this.max_timeout_ms < 500) { /* last-ditch attempt to fix SERVFAILs - disable lazy mode */ immediate_mode_defaults(); fprintf(stderr, "Attempting to disable lazy mode due to excessive SERVFAILs\n"); - handshake_switch_options(dns_fd, 0, compression_down, downenc); + handshake_switch_options(dns_fd, 0, this.compression_down, this.downenc); } } } - send_ping_soon = 900; + this.send_ping_soon = 900; /* Mark query as received */ got_response(q.id, 0, 1); return -1; /* nothing done */ } - send_query_recvcnt++; /* unlikely we will ever overflow (2^64 queries is a LOT) */ + this.send_query_recvcnt++; /* unlikely we will ever overflow (2^64 queries is a LOT) */ if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { - num_badip++; - if (num_badip % 5 == 1) { + this.num_badip++; + if (this.num_badip % 5 == 1) { fprintf(stderr, "BADIP (%ld): Server rejected sender IP address (maybe iodined -c will help), or server " - "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.\n", num_badip); + "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.\n", this.num_badip); } return -1; /* nothing done */ } /* Okay, we have a recent downstream packet */ - lastdownstreamtime = time(NULL); + this.lastdownstreamtime = time(NULL); - num_recv++; + this.num_recv++; /* Decode the downstream data header and fragment-ify ready for processing */ res = parse_data(cbuf, read, &f, &immediate); @@ -1259,13 +1058,13 @@ tunnel_dns(int tun_fd, int dns_fd) /* Mark query as received */ got_response(q.id, immediate, 0); - if ((debug >= 3 && res) || (debug >= 2 && !res)) + if ((this.debug >= 3 && res) || (this.debug >= 2 && !res)) fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %lu, s%d e%d\n", res ? "PING" : "DATA", f.seqID, f.ack_other, f.compressed, f.len, f.start, f.end); - window_ack(outbuf, f.ack_other); - window_tick(outbuf); + window_ack(this.outbuf, f.ack_other); + window_tick(this.outbuf); /* In lazy mode, we shouldn't get immediate replies to our most-recent query, only during heavy data transfer. Since this means the server @@ -1275,28 +1074,28 @@ tunnel_dns(int tun_fd, int dns_fd) if (f.len == 0) { if (!res) DEBUG(1, "[WARNING] Received downstream data fragment with 0 length and NOT a ping!"); - if (!lazymode) - send_ping_soon = 100; + if (!this.lazymode) + this.send_ping_soon = 100; else - send_ping_soon = 700; + this.send_ping_soon = 700; return -1; } /* Get next ACK if nothing already pending: if we get a new ack * then we must send it immediately. */ - if (next_downstream_ack >= 0) { + if (this.next_downstream_ack >= 0) { /* If this happens something is wrong (or last frag was a re-send) * May result in ACKs being delayed. */ - DEBUG(1, "next_downstream_ack NOT -1! (%d), %u resends, %u oos", next_downstream_ack, outbuf->resends, outbuf->oos); + DEBUG(1, "this.next_downstream_ack NOT -1! (%d), %u resends, %u oos", this.next_downstream_ack, this.outbuf->resends, this.outbuf->oos); } /* Downstream data traffic + ack data fragment */ - next_downstream_ack = f.seqID; - window_process_incoming_fragment(inbuf, &f); + this.next_downstream_ack = f.seqID; + window_process_incoming_fragment(this.inbuf, &f); - num_frags_recv++; + this.num_frags_recv++; - datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed); + datalen = window_reassemble_data(this.inbuf, cbuf, sizeof(cbuf), &compressed); if (datalen > 0) { if (compressed) { buflen = sizeof(buf); @@ -1316,7 +1115,7 @@ tunnel_dns(int tun_fd, int dns_fd) } /* Move window along after doing all data processing */ - window_tick(inbuf); + window_tick(this.inbuf); return read; } @@ -1332,79 +1131,79 @@ client_tunnel(int tun_fd, int dns_fd) time_t last_stats; size_t sent_since_report, recv_since_report; - connected = 1; + this.connected = 1; /* start counting now */ rv = 0; - lastdownstreamtime = time(NULL); + this.lastdownstreamtime = time(NULL); last_stats = time(NULL); /* reset connection statistics */ - num_badip = 0; - num_servfail = 0; - num_timeouts = 0; - send_query_recvcnt = 0; - send_query_sendcnt = 0; - num_sent = 0; - num_recv = 0; - num_frags_sent = 0; - num_frags_recv = 0; - num_pings = 0; + this.num_badip = 0; + this.num_servfail = 0; + this.num_timeouts = 0; + this.send_query_recvcnt = 0; + this.send_query_sendcnt = 0; + this.num_sent = 0; + this.num_recv = 0; + this.num_frags_sent = 0; + this.num_frags_recv = 0; + this.num_pings = 0; sent_since_report = 0; recv_since_report = 0; use_min_send = 0; - if (debug >= 5) - window_debug = debug - 3; + if (this.debug >= 5) + window_debug = this.debug - 3; - while (running) { + while (this.running) { if (!use_min_send) - tv = ms_to_timeval(max_timeout_ms); + tv = ms_to_timeval(this.max_timeout_ms); /* TODO: detect DNS servers which drop frequent requests * TODO: adjust number of pending queries based on current data rate */ - if (conn == CONN_DNS_NULL && !use_min_send) { + if (this.conn == CONN_DNS_NULL && !use_min_send) { /* Send a single query per loop */ - sending = window_sending(outbuf, &nextresend); + sending = window_sending(this.outbuf, &nextresend); total = sending; check_pending_queries(); - if (num_pending < windowsize_down && lazymode) - total = MAX(total, windowsize_down - num_pending); - else if (num_pending < 1 && !lazymode) + if (this.num_pending < this.windowsize_down && this.lazymode) + total = MAX(total, this.windowsize_down - this.num_pending); + else if (this.num_pending < 1 && !this.lazymode) total = MAX(total, 1); /* Upstream traffic - this is where all ping/data queries are sent */ - if (sending > 0 || total > 0 || next_downstream_ack >= 0) { + if (sending > 0 || total > 0 || this.next_downstream_ack >= 0) { if (sending > 0) { /* More to send - next fragment */ send_next_frag(dns_fd); } else { /* Send ping if we didn't send anything yet */ - send_ping(dns_fd, 0, next_downstream_ack, (num_pings > 20 && num_pings % 50 == 0)); - next_downstream_ack = -1; + send_ping(dns_fd, 0, this.next_downstream_ack, (this.num_pings > 20 && this.num_pings % 50 == 0)); + this.next_downstream_ack = -1; } sending--; total--; QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu, will send another %d", - lazymode ? windowsize_down : 1, total); + this.lazymode ? this.windowsize_down : 1, total); - if (sending > 0 || (total > 0 && lazymode)) { + if (sending > 0 || (total > 0 && this.lazymode)) { /* If sending any data fragments, or server has too few * pending queries, send another one after min. interval */ /* TODO: enforce min send interval even if we get new data */ - tv = ms_to_timeval(min_send_interval_ms); - if (min_send_interval_ms) + tv = ms_to_timeval(this.min_send_interval_ms); + if (this.min_send_interval_ms) use_min_send = 1; tv.tv_usec += 1; - } else if (total > 0 && !lazymode) { + } else if (total > 0 && !this.lazymode) { /* In immediate mode, use normal interval when needing * to send non-data queries to probe server. */ - tv = ms_to_timeval(send_interval_ms); + tv = ms_to_timeval(this.send_interval_ms); } if (sending == 0 && !use_min_send) { @@ -1413,47 +1212,47 @@ client_tunnel(int tun_fd, int dns_fd) tv = nextresend; } - send_ping_soon = 0; + this.send_ping_soon = 0; } } - if (stats) { - if (difftime(time(NULL), last_stats) >= stats) { + if (this.stats) { + if (difftime(time(NULL), last_stats) >= this.stats) { /* print useful statistics report */ - fprintf(stderr, "\n============ iodine connection statistics (user %1d) ============\n", userid); + fprintf(stderr, "\n============ iodine connection statistics (user %1d) ============\n", this.userid); fprintf(stderr, " Queries sent: %8lu" ", answered: %8lu" ", SERVFAILs: %4lu\n", - num_sent, num_recv, num_servfail); + this.num_sent, this.num_recv, this.num_servfail); fprintf(stderr, " last %3d secs: %7lu" " (%4lu/s), replies: %7lu" " (%4lu/s)\n", - stats, num_sent - sent_since_report, (num_sent - sent_since_report) / stats, - num_recv - recv_since_report, (num_recv - recv_since_report) / stats); + this.stats, this.num_sent - sent_since_report, (this.num_sent - sent_since_report) / this.stats, + this.num_recv - recv_since_report, (this.num_recv - recv_since_report) / this.stats); fprintf(stderr, " num IP rejected: %4lu, untracked: %4lu, lazy mode: %1d\n", - num_badip, num_untracked, lazymode); + this.num_badip, this.num_untracked, this.lazymode); fprintf(stderr, " Min send: %5ld ms, Avg RTT: %5ld ms Timeout server: %4ld ms\n", - min_send_interval_ms, rtt_total_ms / num_immediate, server_timeout_ms); + this.min_send_interval_ms, this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); fprintf(stderr, " Queries immediate: %5lu, timed out: %4lu target: %4ld ms\n", - num_immediate, num_timeouts, max_timeout_ms); - if (conn == CONN_DNS_NULL) { + this.num_immediate, this.num_timeouts, this.max_timeout_ms); + if (this.conn == CONN_DNS_NULL) { fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4ld ms\n", - outbuf->resends, inbuf->oos, downstream_timeout_ms); + this.outbuf->resends, this.inbuf->oos, this.downstream_timeout_ms); fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", - num_frags_sent, num_frags_recv, num_pings); + this.num_frags_sent, this.num_frags_recv, this.num_pings); } - /* update since-last-report stats */ - sent_since_report = num_sent; - recv_since_report = num_recv; + /* update since-last-report this.stats */ + sent_since_report = this.num_sent; + recv_since_report = this.num_recv; last_stats = time(NULL); } } - if (send_ping_soon && !use_min_send) { + if (this.send_ping_soon && !use_min_send) { tv.tv_sec = 0; - tv.tv_usec = send_ping_soon * 1000; - send_ping_soon = 0; + tv.tv_usec = this.send_ping_soon * 1000; + this.send_ping_soon = 0; } FD_ZERO(&fds); - if (conn != CONN_DNS_NULL || window_buffer_available(outbuf) > 16) { + if (this.conn != CONN_DNS_NULL || window_buffer_available(this.outbuf) > 16) { /* Fill up outgoing buffer with available data if it has enough space * The windowing protocol manages data retransmits, timeouts etc. */ FD_SET(tun_fd, &fds); @@ -1478,12 +1277,12 @@ client_tunnel(int tun_fd, int dns_fd) use_min_send = 0; } - if (difftime(time(NULL), lastdownstreamtime) > 60) { + if (difftime(time(NULL), this.lastdownstreamtime) > 60) { fprintf(stderr, "No downstream data received in 60 seconds, shutting down.\n"); - running = 0; + this.running = 0; } - if (running == 0) + if (this.running == 0) break; if (i < 0) @@ -1498,7 +1297,7 @@ client_tunnel(int tun_fd, int dns_fd) /* Returns -1 on error OR when quickly dropping data in case of DNS congestion; we need to _not_ do tunnel_dns() then. - If chunk sent, sets send_ping_soon=0. */ + If chunk sent, sets this.send_ping_soon=0. */ } if (FD_ISSET(dns_fd, &fds)) { @@ -1516,13 +1315,13 @@ send_login(int fd, char *login, int len) uint8_t data[19]; memset(data, 0, sizeof(data)); - data[0] = userid; + data[0] = this.userid; memcpy(&data[1], login, MIN(len, 16)); - data[17] = (rand_seed >> 8) & 0xff; - data[18] = (rand_seed >> 0) & 0xff; + data[17] = (this.rand_seed >> 8) & 0xff; + data[18] = (this.rand_seed >> 0) & 0xff; - rand_seed++; + this.rand_seed++; send_packet(fd, 'l', data, sizeof(data)); } @@ -1537,19 +1336,19 @@ send_fragsize_probe(int fd, uint16_t fragsize) buf[0] = 'r'; /* Probe downstream fragsize packet */ - hdr[0] = userid; + hdr[0] = this.userid; *(uint16_t *) (hdr + 1) = htons(fragsize); b32->encode(buf + 1, &hdr_len_enc, hdr, 3); /* build a large query domain which is random and maximum size, * will also take up maximum space in the return packet */ - memset(probedata, MAX(1, rand_seed & 0xff), sizeof(probedata)); - probedata[1] = MAX(1, (rand_seed >> 8) & 0xff); - rand_seed++; + memset(probedata, MAX(1, this.rand_seed & 0xff), sizeof(probedata)); + probedata[1] = MAX(1, (this.rand_seed >> 8) & 0xff); + this.rand_seed++; /* Note: must either be same, or larger, than send_chunk() */ - build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), topdomain, - dataenc, hostname_maxlen, 6); + build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), this.topdomain, + this.dataenc, this.hostname_maxlen, 6); send_query(fd, buf); } @@ -1559,12 +1358,12 @@ send_set_downstream_fragsize(int fd, uint16_t fragsize) { uint8_t data[5]; - data[0] = userid; + data[0] = this.userid; *(uint16_t *) (data + 1) = htons(fragsize); - data[3] = (rand_seed >> 8) & 0xff; - data[4] = (rand_seed >> 0) & 0xff; + data[3] = (this.rand_seed >> 8) & 0xff; + data[4] = (this.rand_seed >> 0) & 0xff; - rand_seed++; + this.rand_seed++; send_packet(fd, 'n', data, sizeof(data)); } @@ -1577,10 +1376,10 @@ send_version(int fd, uint32_t version) version = htonl(version); *(uint32_t *) data = version; - data[4] = (rand_seed >> 8) & 0xff; - data[5] = (rand_seed >> 0) & 0xff; + data[4] = (this.rand_seed >> 8) & 0xff; + data[5] = (this.rand_seed >> 0) & 0xff; - rand_seed++; + this.rand_seed++; send_packet(fd, 'v', data, sizeof(data)); } @@ -1589,14 +1388,14 @@ static void send_ip_request(int fd, int userid) { uint8_t buf[512] = "i____."; - buf[1] = b32_5to8(userid); + buf[1] = b32_5to8(this.userid); - buf[2] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[3] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[4] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; + buf[2] = b32_5to8((this.rand_seed >> 10) & 0x1f); + buf[3] = b32_5to8((this.rand_seed >> 5) & 0x1f); + buf[4] = b32_5to8((this.rand_seed ) & 0x1f); + this.rand_seed++; - strncat((char *)buf, topdomain, 512 - strlen((char *)buf)); + strncat((char *)buf, this.topdomain, 512 - strlen((char *)buf)); send_query(fd, buf); } @@ -1604,9 +1403,9 @@ static void send_raw_udp_login(int dns_fd, int userid, int seed) { char buf[16]; - login_calculate(buf, 16, password, seed + 1); + login_calculate(buf, 16, this.password, seed + 1); - send_raw(dns_fd, (uint8_t *) buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN); + send_raw(dns_fd, (uint8_t *) buf, sizeof(buf), this.userid, RAW_HDR_CMD_LOGIN); } static void @@ -1615,14 +1414,14 @@ send_upenctest(int fd, char *s) { char buf[512] = "z___"; - buf[1] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[2] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[3] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; + buf[1] = b32_5to8((this.rand_seed >> 10) & 0x1f); + buf[2] = b32_5to8((this.rand_seed >> 5) & 0x1f); + buf[3] = b32_5to8((this.rand_seed ) & 0x1f); + this.rand_seed++; strncat(buf, s, 512 - strlen(buf)); strncat(buf, ".", 512 - strlen(buf)); - strncat(buf, topdomain, 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); send_query(fd, (uint8_t *)buf); } @@ -1632,15 +1431,15 @@ send_downenctest(int fd, char downenc, int variant, char *s, int slen) { char buf[512] = "y_____."; - buf[1] = tolower(downenc); + buf[1] = tolower(this.downenc); buf[2] = b32_5to8(variant); - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; + buf[3] = b32_5to8((this.rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((this.rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((this.rand_seed ) & 0x1f); + this.rand_seed++; - strncat(buf, topdomain, 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); send_query(fd, (uint8_t *)buf); } @@ -1648,15 +1447,15 @@ static void send_codec_switch(int fd, int userid, int bits) { char buf[512] = "s_____."; - buf[1] = b32_5to8(userid); + buf[1] = b32_5to8(this.userid); buf[2] = b32_5to8(bits); - buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((rand_seed ) & 0x1f); - rand_seed++; + buf[3] = b32_5to8((this.rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((this.rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((this.rand_seed ) & 0x1f); + this.rand_seed++; - strncat(buf, topdomain, 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); send_query(fd, (uint8_t *)buf); } @@ -1665,7 +1464,7 @@ send_server_options(int fd, int userid, int lazy, int compression, char denc, ch /* Options must be length >=4 */ { char buf[512] = "oU3___CMC."; - buf[1] = b32_5to8(userid); + buf[1] = b32_5to8(this.userid); options[0] = tolower(denc); options[1] = lazy ? 'l' : 'i'; @@ -1673,12 +1472,12 @@ send_server_options(int fd, int userid, int lazy, int compression, char denc, ch options[3] = 0; strncpy(buf + 3, options, 3); - buf[6] = b32_5to8((rand_seed >> 10) & 0x1f); - buf[7] = b32_5to8((rand_seed >> 5) & 0x1f); - buf[8] = b32_5to8((rand_seed) & 0x1f); - rand_seed++; + buf[6] = b32_5to8((this.rand_seed >> 10) & 0x1f); + buf[7] = b32_5to8((this.rand_seed >> 5) & 0x1f); + buf[8] = b32_5to8((this.rand_seed) & 0x1f); + this.rand_seed++; - strncat(buf, topdomain, 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); send_query(fd, (uint8_t *)buf); } @@ -1692,7 +1491,7 @@ handshake_version(int dns_fd, int *seed) int i; int read; - for (i = 0; running && i < 5; i++) { + for (i = 0; this.running && i < 5; i++) { send_version(dns_fd, PROTOCOL_VERSION); @@ -1706,12 +1505,12 @@ handshake_version(int dns_fd, int *seed) if (strncmp("VACK", (char *)in, 4) == 0) { *seed = payload; - userid = in[8]; - userid_char = hex[userid & 15]; - userid_char2 = hex2[userid & 15]; + this.userid = in[8]; + this.userid_char = hex[this.userid & 15]; + this.userid_char2 = hex2[this.userid & 15]; fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", - PROTOCOL_VERSION, userid); + PROTOCOL_VERSION, this.userid); return 0; } else if (strncmp("VNAK", (char *)in, 4) == 0) { warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up", @@ -1741,9 +1540,9 @@ handshake_login(int dns_fd, int seed) int i; int read; - login_calculate(login, 16, password, seed); + login_calculate(login, 16, this.password, seed); - for (i=0; running && i<5 ;i++) { + for (i=0; this.running && i<5 ;i++) { send_login(dns_fd, login, 16); @@ -1752,7 +1551,7 @@ handshake_login(int dns_fd, int seed) if (read > 0) { int netmask; if (strncmp("LNAK", in, 4) == 0) { - fprintf(stderr, "Bad password\n"); + fprintf(stderr, "Bad this.password\n"); return 1; } else if (sscanf(in, "%64[^-]-%64[^-]-%d-%d", server, client, &mtu, &netmask) == 4) { @@ -1789,33 +1588,33 @@ handshake_raw_udp(int dns_fd, int seed) int len; int got_addr; - memset(&raw_serv, 0, sizeof(raw_serv)); + memset(&this.raw_serv, 0, sizeof(this.raw_serv)); got_addr = 0; fprintf(stderr, "Testing raw UDP data to the server (skip with -r)"); - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { - send_ip_request(dns_fd, userid); + send_ip_request(dns_fd, this.userid); len = handshake_waitdns(dns_fd, in, sizeof(in), 'I', i+1); if (len == 5 && in[0] == 'I') { /* Received IPv4 address */ - struct sockaddr_in *raw4_serv = (struct sockaddr_in *) &raw_serv; + struct sockaddr_in *raw4_serv = (struct sockaddr_in *) &this.raw_serv; raw4_serv->sin_family = AF_INET; memcpy(&raw4_serv->sin_addr, &in[1], sizeof(struct in_addr)); raw4_serv->sin_port = htons(53); - raw_serv_len = sizeof(struct sockaddr_in); + this.raw_serv_len = sizeof(struct sockaddr_in); got_addr = 1; break; } if (len == 17 && in[0] == 'I') { /* Received IPv6 address */ - struct sockaddr_in6 *raw6_serv = (struct sockaddr_in6 *) &raw_serv; + struct sockaddr_in6 *raw6_serv = (struct sockaddr_in6 *) &this.raw_serv; raw6_serv->sin6_family = AF_INET6; memcpy(&raw6_serv->sin6_addr, &in[1], sizeof(struct in6_addr)); raw6_serv->sin6_port = htons(53); - raw_serv_len = sizeof(struct sockaddr_in6); + this.raw_serv_len = sizeof(struct sockaddr_in6); got_addr = 1; break; } @@ -1824,24 +1623,24 @@ handshake_raw_udp(int dns_fd, int seed) fflush(stderr); } fprintf(stderr, "\n"); - if (!running) + if (!this.running) return 0; if (!got_addr) { fprintf(stderr, "Failed to get raw server IP, will use DNS mode.\n"); return 0; } - fprintf(stderr, "Server is at %s, trying raw login: ", format_addr(&raw_serv, raw_serv_len)); + fprintf(stderr, "Server is at %s, trying raw login: ", format_addr(&this.raw_serv, this.raw_serv_len)); fflush(stderr); /* do login against port 53 on remote server * based on the old seed. If reply received, * switch to raw udp mode */ - for (i=0; running && i<4 ;i++) { + for (i=0; this.running && i<4 ;i++) { tv.tv_sec = i + 1; tv.tv_usec = 0; - send_raw_udp_login(dns_fd, userid, seed); + send_raw_udp_login(dns_fd, this.userid, seed); FD_ZERO(&fds); FD_SET(dns_fd, &fds); @@ -1853,7 +1652,7 @@ handshake_raw_udp(int dns_fd, int seed) len = recv(dns_fd, in, sizeof(in), 0); if (len >= (16 + RAW_HDR_LEN)) { char hash[16]; - login_calculate(hash, 16, password, seed - 1); + login_calculate(hash, 16, this.password, seed - 1); if (memcmp(in, raw_header, RAW_HDR_IDENT_LEN) == 0 && RAW_HDR_GET_CMD(in) == RAW_HDR_CMD_LOGIN && memcmp(&in[RAW_HDR_LEN], hash, sizeof(hash)) == 0) { @@ -1888,7 +1687,7 @@ handshake_upenctest(int dns_fd, char *s) int slen; slen = strlen(s); - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { send_upenctest(dns_fd, s); @@ -1934,7 +1733,7 @@ handshake_upenctest(int dns_fd, char *s) fprintf(stderr, "Retrying upstream codec test...\n"); } - if (!running) + if (!this.running) return -1; /* timeout */ @@ -2049,7 +1848,7 @@ handshake_downenctest(int dns_fd, char trycodec) char *s = DOWNCODECCHECK1; int slen = DOWNCODECCHECK1_LEN; - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { send_downenctest(dns_fd, trycodec, 1, NULL, 0); @@ -2088,7 +1887,7 @@ handshake_downenc_autodetect(int dns_fd) int base64uok = 0; int base128ok = 0; - if (do_qtype == T_NULL || do_qtype == T_PRIVATE) { + if (this.do_qtype == T_NULL || this.do_qtype == T_PRIVATE) { /* no other choice than raw */ fprintf(stderr, "No alternative downstream codec available, using default (Raw)\n"); return 'R'; @@ -2099,22 +1898,22 @@ handshake_downenc_autodetect(int dns_fd) /* Try Base64 */ if (handshake_downenctest(dns_fd, 'S')) base64ok = 1; - else if (running && handshake_downenctest(dns_fd, 'U')) + else if (this.running && handshake_downenctest(dns_fd, 'U')) base64uok = 1; /* Try Base128 only if 64 gives us some perspective */ - if (running && (base64ok || base64uok)) { + if (this.running && (base64ok || base64uok)) { if (handshake_downenctest(dns_fd, 'V')) base128ok = 1; } /* If 128 works, then TXT may give us Raw as well */ - if (running && (base128ok && do_qtype == T_TXT)) { + if (this.running && (base128ok && this.do_qtype == T_TXT)) { if (handshake_downenctest(dns_fd, 'R')) return 'R'; } - if (!running) + if (!this.running) return ' '; if (base128ok) @@ -2142,7 +1941,7 @@ handshake_qtypetest(int dns_fd, int timeout) int trycodec; int k; - if (do_qtype == T_NULL || do_qtype == T_PRIVATE) + if (this.do_qtype == T_NULL || this.do_qtype == T_PRIVATE) trycodec = 'R'; else trycodec = 'T'; @@ -2187,7 +1986,7 @@ handshake_qtype_numcvt(int num) static int handshake_qtype_autodetect(int dns_fd) /* Returns: - 0: okay, do_qtype set + 0: okay, this.do_qtype set 1: problem, program exit */ { @@ -2208,10 +2007,10 @@ handshake_qtype_autodetect(int dns_fd) to see if things will start working after a while. */ - for (timeout = 1; running && timeout <= 3; timeout++) { - for (qtypenum = 0; running && qtypenum < highestworking; qtypenum++) { - do_qtype = handshake_qtype_numcvt(qtypenum); - if (do_qtype == T_UNSET) + for (timeout = 1; this.running && timeout <= 3; timeout++) { + for (qtypenum = 0; this.running && qtypenum < highestworking; qtypenum++) { + this.do_qtype = handshake_qtype_numcvt(qtypenum); + if (this.do_qtype == T_UNSET) break; /* this round finished */ fprintf(stderr, "."); @@ -2233,17 +2032,17 @@ handshake_qtype_autodetect(int dns_fd) fprintf(stderr, "\n"); - if (!running) { + if (!this.running) { warnx("Stopped while autodetecting DNS query type (try setting manually with -T)"); return 1; /* problem */ } /* finished */ - do_qtype = handshake_qtype_numcvt(highestworking); + this.do_qtype = handshake_qtype_numcvt(highestworking); - if (do_qtype == T_UNSET) { + if (this.do_qtype == T_UNSET) { /* also catches highestworking still 100 */ - warnx("No suitable DNS query type found. Are you connected to a network?"); + warnx("No suitable DNS query type found. Are you this.connected to a network?"); warnx("If you expect very long roundtrip delays, use -T explicitly."); warnx("(Also, connecting to an \"ancient\" version of iodined won't work.)"); return 1; /* problem */ @@ -2267,12 +2066,12 @@ handshake_edns0_check(int dns_fd) int slen = DOWNCODECCHECK1_LEN; char trycodec; - if (do_qtype == T_NULL) + if (this.do_qtype == T_NULL) trycodec = 'R'; else trycodec = 'T'; - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { send_downenctest(dns_fd, trycodec, 1, NULL, 0); @@ -2323,9 +2122,9 @@ handshake_switch_codec(int dns_fd, int bits) fprintf(stderr, "Switching upstream to codec %s\n", tempenc->name); - for (i=0; running && i<5 ;i++) { + for (i=0; this.running && i<5 ;i++) { - send_codec_switch(dns_fd, userid, bits); + send_codec_switch(dns_fd, this.userid, bits); read = handshake_waitdns(dns_fd, in, sizeof(in), 'S', i+1); @@ -2342,22 +2141,22 @@ handshake_switch_codec(int dns_fd, int bits) } in[read] = 0; /* zero terminate */ fprintf(stderr, "Server switched upstream to codec %s\n", in); - dataenc = tempenc; + this.dataenc = tempenc; /* Update outgoing buffer max (decoded) fragsize */ - maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain); + this.maxfragsize_up = get_raw_length_from_dns(this.hostname_maxlen - UPSTREAM_HDR, this.dataenc, this.topdomain); return; } fprintf(stderr, "Retrying codec switch...\n"); } - if (!running) + if (!this.running) return; fprintf(stderr, "No reply from server on codec switch.\n"); codec_revert: - fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name); + fprintf(stderr, "Falling back to upstream codec %s\n", this.dataenc->name); } void @@ -2384,9 +2183,9 @@ handshake_switch_options(int dns_fd, int lazy, int compression, char denc) fprintf(stderr, "Switching server options: %s mode, downstream codec %s, compression %s...\n", lazy_status, dname, comp_status); - for (int i = 0; running && i < 5; i++) { + for (int i = 0; this.running && i < 5; i++) { - send_server_options(dns_fd, userid, lazy, compression, denc, opts); + send_server_options(dns_fd, this.userid, lazy, compression, denc, opts); read = handshake_waitdns(dns_fd, in, sizeof(in) - 1, 'O', i + 1); @@ -2402,25 +2201,25 @@ handshake_switch_options(int dns_fd, int lazy, int compression, char denc) goto opt_revert; } fprintf(stderr, "Switched server options successfully. (%s)\n", opts); - lazymode = lazy; - compression_down = compression; - downenc = denc; + this.lazymode = lazy; + this.compression_down = compression; + this.downenc = denc; return; } fprintf(stderr, "Retrying options switch...\n"); } - if (!running) + if (!this.running) return; fprintf(stderr, "No reply from server on options switch.\n"); opt_revert: - comp_status = compression_down ? "enabled" : "disabled"; - lazy_status = lazymode ? "lazy" : "immediate"; + comp_status = this.compression_down ? "enabled" : "disabled"; + lazy_status = this.lazymode ? "lazy" : "immediate"; fprintf(stderr, "Falling back to previous configuration: downstream codec %s, %s mode, compression %s.\n", - dataenc->name, lazy_status, comp_status); + this.dataenc->name, lazy_status, comp_status); } static int @@ -2481,7 +2280,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) *max_fragsize = acked_fragsize; return 1; } else { - if (downenc != ' ' && downenc != 'T') { + if (this.downenc != ' ' && this.downenc != 'T') { fprintf(stderr, "%d corrupted at %d.. (Try -O Base32)\n", acked_fragsize, i); } else { fprintf(stderr, "%d corrupted at %d.. ", acked_fragsize, i); @@ -2507,9 +2306,9 @@ handshake_autoprobe_fragsize(int dns_fd) max_fragsize = 0; fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)"); - while (running && range > 0 && (range >= 8 || max_fragsize < 300)) { + while (this.running && range > 0 && (range >= 8 || max_fragsize < 300)) { /* stop the slow probing early when we have enough bytes anyway */ - for (i=0; running && i<3 ;i++) { + for (i=0; this.running && i<3 ;i++) { send_fragsize_probe(dns_fd, proposed_fragsize); @@ -2538,7 +2337,7 @@ handshake_autoprobe_fragsize(int dns_fd) proposed_fragsize -= range; } } - if (!running) { + if (!this.running) { warnx("\nstopped while autodetecting fragment size (Try setting manually with -m)"); return 0; } @@ -2558,8 +2357,8 @@ handshake_autoprobe_fragsize(int dns_fd) fprintf(stderr, "Note: this probably won't work well.\n"); fprintf(stderr, "Try setting -M to 200 or lower, or try other DNS types (-T option).\n"); } else if (max_fragsize < 202 && - (do_qtype == T_NULL || do_qtype == T_PRIVATE || do_qtype == T_TXT || - do_qtype == T_SRV || do_qtype == T_MX)) { + (this.do_qtype == T_NULL || this.do_qtype == T_PRIVATE || this.do_qtype == T_TXT || + this.do_qtype == T_SRV || this.do_qtype == T_MX)) { fprintf(stderr, "Note: this isn't very much.\n"); fprintf(stderr, "Try setting -M to 200 or lower, or try other DNS types (-T option).\n"); } @@ -2575,7 +2374,7 @@ handshake_set_fragsize(int dns_fd, int fragsize) int read; fprintf(stderr, "Setting downstream fragment size to max %d...\n", fragsize); - for (i=0; running && i<5 ;i++) { + for (i=0; this.running && i<5 ;i++) { send_set_downstream_fragsize(dns_fd, fragsize); @@ -2598,7 +2397,7 @@ handshake_set_fragsize(int dns_fd, int fragsize) fprintf(stderr, "Retrying set fragsize...\n"); } - if (!running) + if (!this.running) return; fprintf(stderr, "No reply from server when setting fragsize. Keeping default.\n"); @@ -2610,16 +2409,16 @@ handshake_set_timeout(int dns_fd) char in[4096]; int read, id; - if (autodetect_server_timeout && lazymode) { + if (this.autodetect_server_timeout && this.lazymode) { fprintf(stderr, "Calculating round-trip time for optimum server timeout..."); } else { fprintf(stderr, "Setting window sizes to %lu frags upstream, %lu frags downstream...", - windowsize_up, windowsize_down); + this.windowsize_up, this.windowsize_down); } - for (int i = 0; running && i < 5; i++) { + for (int i = 0; this.running && i < 5; i++) { - id = autodetect_server_timeout ? + id = this.autodetect_server_timeout ? update_server_timeout(dns_fd, 1) : send_ping(dns_fd, 1, -1, 1); read = handshake_waitdns(dns_fd, in, sizeof(in), 'P', i + 1); @@ -2630,19 +2429,19 @@ handshake_set_timeout(int dns_fd) if (strncmp("BADIP", in, 5) == 0) { fprintf(stderr, "Server rejected sender IP address.\n"); } - if (autodetect_server_timeout) + if (this.autodetect_server_timeout) continue; else break; } } - if (!running) + if (!this.running) return; - if (autodetect_server_timeout) + if (this.autodetect_server_timeout) fprintf(stderr, "\nDetermined round-trip time of %ld ms, server timeout of %ld ms.\n", - rtt_total_ms / num_immediate, server_timeout_ms); + this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); else fprintf(stderr, " done\n"); } @@ -2657,7 +2456,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz dnsc_use_edns0 = 0; /* qtype message printed in handshake function */ - if (do_qtype == T_UNSET) { + if (this.do_qtype == T_UNSET) { r = handshake_qtype_autodetect(dns_fd); if (r) { return r; @@ -2677,19 +2476,19 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } if (raw_mode && handshake_raw_udp(dns_fd, seed)) { - conn = CONN_RAW_UDP; - max_timeout_ms = 10000; - compression_down = 1; - compression_up = 1; + this.conn = CONN_RAW_UDP; + this.max_timeout_ms = 10000; + this.compression_down = 1; + this.compression_up = 1; } else { if (raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); } dnsc_use_edns0 = 1; - if (handshake_edns0_check(dns_fd) && running) { + if (handshake_edns0_check(dns_fd) && this.running) { fprintf(stderr, "Using EDNS0 extension\n"); - } else if (!running) { + } else if (!this.running) { return -1; } else { fprintf(stderr, "DNS relay does not support EDNS0 extension\n"); @@ -2697,7 +2496,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } upcodec = handshake_upenc_autodetect(dns_fd); - if (!running) + if (!this.running) return -1; if (upcodec == 1) { /* Base64 */ @@ -2707,18 +2506,18 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } else if (upcodec == 3) { /* Base128 */ handshake_switch_codec(dns_fd, 7); } - if (!running) + if (!this.running) return -1; - if (downenc == ' ') { - downenc = handshake_downenc_autodetect(dns_fd); + if (this.downenc == ' ') { + this.downenc = handshake_downenc_autodetect(dns_fd); } - if (!running) + if (!this.running) return -1; - /* Set options for compression, lazymode and downstream codec */ - handshake_switch_options(dns_fd, lazymode, compression_down, downenc); - if (!running) + /* Set options for compression, this.lazymode and downstream codec */ + handshake_switch_options(dns_fd, this.lazymode, this.compression_down, this.downenc); + if (!this.running) return -1; if (autodetect_frag_size) { @@ -2735,21 +2534,21 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz } handshake_set_fragsize(dns_fd, fragsize); - if (!running) + if (!this.running) return -1; /* init windowing protocol */ - outbuf = window_buffer_init(64, windowsize_up, maxfragsize_up, WINDOW_SENDING); - outbuf->timeout = ms_to_timeval(downstream_timeout_ms); + this.outbuf = window_buffer_init(64, this.windowsize_up, this.maxfragsize_up, WINDOW_SENDING); + this.outbuf->timeout = ms_to_timeval(this.downstream_timeout_ms); /* Incoming buffer max fragsize doesn't matter */ - inbuf = window_buffer_init(64, windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); + this.inbuf = window_buffer_init(64, this.windowsize_down, MAX_FRAGSIZE, WINDOW_RECVING); /* init query tracking */ - num_untracked = 0; - num_pending = 0; - pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); + this.num_untracked = 0; + this.num_pending = 0; + this.pending_queries = calloc(PENDING_QUERIES_LENGTH, sizeof(struct query_tuple)); for (int i = 0; i < PENDING_QUERIES_LENGTH; i++) - pending_queries[i].id = -1; + this.pending_queries[i].id = -1; /* set server window/timeout parameters and calculate RTT */ handshake_set_timeout(dns_fd); diff --git a/src/client.h b/src/client.h index 92d4983..f76911a 100644 --- a/src/client.h +++ b/src/client.h @@ -23,13 +23,124 @@ extern int debug; extern int stats; -#define PENDING_QUERIES_LENGTH (MAX(windowsize_up, windowsize_down) * 3) +#define PENDING_QUERIES_LENGTH (MAX(this.windowsize_up, this.windowsize_down) * 4) +#define INSTANCE this + +struct client_instance { + int max_downstream_frag_size; + int autodetect_frag_size; + int hostname_maxlen; + int raw_mode; + int foreground; + char password[33]; + + /* DNS nameserver info */ + char **nameserv_hosts; + size_t nameserv_hosts_len; + struct sockaddr_storage *nameserv_addrs; + size_t nameserv_addrs_len; + int current_nameserver; + struct sockaddr_storage raw_serv; + int raw_serv_len; + char *topdomain; + + int tun_fd; + int dns_fd; + +#ifdef OPENBSD + int rtable; +#endif + int running; + + /* Output flags for debug and time between stats update */ + int debug; + int stats; + + uint16_t rand_seed; + + /* Current up/downstream window data */ + struct frag_buffer *outbuf; + struct frag_buffer *inbuf; + size_t windowsize_up; + size_t windowsize_down; + size_t maxfragsize_up; + + /* Next downstream seqID to be ACK'd (-1 if none pending) */ + int next_downstream_ack; + + /* Remembering queries we sent for tracking purposes */ + struct query_tuple *pending_queries; + size_t num_pending; + time_t max_timeout_ms; + time_t send_interval_ms; + time_t min_send_interval_ms; + + /* Server response timeout in ms and downstream window timeout */ + time_t server_timeout_ms; + time_t downstream_timeout_ms; + int autodetect_server_timeout; + + /* Cumulative Round-Trip-Time in ms */ + time_t rtt_total_ms; + size_t num_immediate; + + /* Connection statistics */ + size_t num_timeouts; + size_t num_untracked; + size_t num_servfail; + size_t num_badip; + size_t num_sent; + size_t num_recv; + size_t send_query_sendcnt; + size_t send_query_recvcnt; + size_t num_frags_sent; + size_t num_frags_recv; + size_t num_pings; + + /* My userid at the server */ + char userid; + char userid_char; /* used when sending (lowercase) */ + char userid_char2; /* also accepted when receiving (uppercase) */ + + uint16_t chunkid; + + /* Base32 encoder used for non-data packets and replies */ + struct encoder *b32; + /* Base64 etc encoders for replies */ + struct encoder *b64; + struct encoder *b64u; + struct encoder *b128; + + /* The encoder used for data packets + * Defaults to Base32, can be changed after handshake */ + struct encoder *dataenc; + + /* Upstream/downstream compression flags */ + int compression_up; + int compression_down; + + /* The encoder to use for downstream data */ + char downenc; + + /* set query type to send */ + uint16_t do_qtype; + + /* My connection mode */ + enum connection conn; + int connected; + + int lazymode; + long send_ping_soon; + time_t lastdownstreamtime; +}; struct query_tuple { int id; /* DNS query / response ID */ struct timeval time; /* time sent or 0 if cleared */ }; +extern struct client_instance this; + void client_init(); void client_stop(); @@ -37,18 +148,10 @@ enum connection client_get_conn(); const char *client_get_raw_addr(); void client_rotate_nameserver(); -void client_set_nameservers(struct sockaddr_storage *, int); -void client_set_topdomain(const char *cp); -void client_set_password(const char *cp); int client_set_qtype(char *qtype); -char *client_get_qtype(); -void client_set_downenc(char *encoding); -void client_set_compression(int up, int down); -void client_set_dnstimeout(double, double, double, int); -void client_set_lazymode(int lazy_mode); -void client_set_windowsize(size_t, size_t); +char *format_qtype(); +char parse_encoding(char *encoding); void client_set_hostname_maxlen(size_t i); -void client_set_interval(double, double); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_tunnel(int tun_fd, int dns_fd); diff --git a/src/iodine.c b/src/iodine.c index b057973..f963531 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,8 @@ #include "tun.h" #include "client.h" #include "util.h" +#include "encoding.h" +#include "base32.h" #ifdef WINDOWS32 WORD req_version = MAKEWORD(2, 2); @@ -52,10 +55,46 @@ static char *__progname; #define PASSWORD_ENV_VAR "IODINE_PASS" +struct client_instance this; + +static struct client_instance preset_default = { + .foreground = 0, +#ifdef OPENBSD + .rtable = 0, +#endif + .debug = 0, + .stats = 0, + .raw_mode = 1, + .lazymode = 1, + .max_timeout_ms = 5000, + .send_interval_ms = 0, + .server_timeout_ms = 4000, + .downstream_timeout_ms = 2000, + .autodetect_server_timeout = 1, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = MAX_FRAGSIZE, + .compression_up = 1, + .compression_down = 1, + .windowsize_up = 8, + .windowsize_down = 8, + .hostname_maxlen = 0xFF, + + /* static startup values - should not be changed in presets */ + .conn = CONN_DNS_NULL, + .send_ping_soon = 1, + .downenc = ' ', + .do_qtype = T_UNSET, + .maxfragsize_up = 100, + .next_downstream_ack = -1, + .num_immediate = 1, + .rtt_total_ms = 200 +}; + static void sighandler(int sig) { - client_stop(); + this.running = 0; } #if defined(__GNUC__) || defined(__clang__) @@ -69,7 +108,7 @@ print_usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-X port] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-w downfrags] [-W upfrags] [-i sec -j sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-R rdomain] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [...]]]\n", __progname); @@ -88,38 +127,41 @@ help() fprintf(stderr, "iodine IP over DNS tunneling client\n"); print_usage(); fprintf(stderr, "\nOptions to try if connection doesn't work:\n"); - fprintf(stderr, " -T force dns type: NULL, PRIVATE, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); - fprintf(stderr, " -O force downstream encoding for -T other than NULL: Base32, Base64, Base64u,\n"); - fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); - fprintf(stderr, " -I target interval between sending and receiving requests (default: 4 secs)\n"); - fprintf(stderr, " or ping interval in immediate mode (default: 1 sec)\n"); - fprintf(stderr, " -s minimum interval between queries (default: 0ms)\n"); + fprintf(stderr, " -T use DNS type: NULL, PRIVATE, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); + fprintf(stderr, " -O use specific downstream encoding for queries: Base32, Base64, Base64u,\n"); + fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); + fprintf(stderr, " -I target interval between sending and receiving requests (default: 4 secs)\n"); + fprintf(stderr, " or ping interval in immediate mode (default: 1 sec)\n"); + fprintf(stderr, " -s minimum interval between queries (default: 0ms)\n"); fprintf(stderr, " -L 1: use lazy mode for low-latency (default). 0: don't (implies -I1)\n"); - fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); - fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); - fprintf(stderr, " -r to skip raw UDP mode attempt\n"); - fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n\n"); + fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); + fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); + fprintf(stderr, " -r skip raw UDP mode attempt\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n\n"); fprintf(stderr, "Fine-tuning options:\n"); - fprintf(stderr, " -w downstream fragment window size (default: 8 frags)\n"); - fprintf(stderr, " -W upstream fragment window size (default: 8 frags)\n"); - fprintf(stderr, " -i server-side request timeout in lazy mode (default: auto)\n"); - fprintf(stderr, " -j downstream fragment ACK timeout, implies -i4 (default: 2 sec)\n"); + fprintf(stderr, " -w downstream fragment window size (default: 8 frags)\n"); + fprintf(stderr, " -W upstream fragment window size (default: 8 frags)\n"); + fprintf(stderr, " -i server-side request timeout in lazy mode (default: auto)\n"); + fprintf(stderr, " -j downstream fragment ACK timeout, implies -i4 (default: 2 sec)\n"); fprintf(stderr, " -c 1: use downstream compression (default), 0: disable\n"); fprintf(stderr, " -C 1: use upstream compression (default), 0: disable\n\n"); fprintf(stderr, "Other options:\n"); - fprintf(stderr, " -v to print version info and exit\n"); - fprintf(stderr, " -h to print this help and exit\n"); - fprintf(stderr, " -V sec to print connection statistics at specified intervals\n"); - fprintf(stderr, " -f to keep running in foreground\n"); - fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); - fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); - fprintf(stderr, " -t dir to chroot to directory dir\n"); - fprintf(stderr, " -d device to set tunnel device name\n"); - fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); - fprintf(stderr, " -R routing domain (OpenBSD only)\n"); - fprintf(stderr, " -F pidfile to write pid to a file\n\n"); + fprintf(stderr, " -v, --version print version info and exit\n"); + fprintf(stderr, " -h, --help print this help and exit\n"); + fprintf(stderr, " -V, --stats print connection statistics at given intervals (default: 5 sec)\n"); + fprintf(stderr, " -X skip tun device and forward data to/from stdin/out, telling the iodined to\n"); + fprintf(stderr, " connect to the specified port listening on the server host.\n"); + fprintf(stderr, " Can be used with SSH ProxyCommand option. (-X 22)\n"); + fprintf(stderr, " -f keep running in foreground\n"); + fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); + fprintf(stderr, " -d set tunnel device name\n"); + fprintf(stderr, " -u drop privileges and run as specified user\n"); + fprintf(stderr, " -F write PID to specified file\n"); + fprintf(stderr, " --chroot chroot to given directory\n"); + fprintf(stderr, " --context apply specified SELinux context after initialization\n"); + fprintf(stderr, " --rdomain use specified routing domain (OpenBSD only)\n\n"); fprintf(stderr, "nameserver is the IP/hostname of the relaying nameserver(s).\n"); fprintf(stderr, " multiple nameservers can be specified (used in round-robin). \n"); @@ -140,96 +182,32 @@ version() int main(int argc, char **argv) { - char *topdomain; - char *errormsg; + char *errormsg = NULL; #ifndef WINDOWS32 - struct passwd *pw; + struct passwd *pw = NULL; #endif - char *username; - char password[33]; - int foreground; - char *newroot; - char *context; - char *device; - char *pidfile; int choice; - int tun_fd; - int dns_fd; - int max_downstream_frag_size; - int autodetect_frag_size; - int hostname_maxlen; - int retval; - int raw_mode; - int lazymode; - double target_interval_sec; - double server_timeout_sec; - double downstream_timeout_sec; - int min_interval_ms; - int autodetect_server_timeout; - int up_compression; - int down_compression; - int up_windowsize; - int down_windowsize; + char *username = NULL; + char *newroot = NULL; + char *context = NULL; + char *device = NULL; + char *pidfile = NULL; -#ifdef OPENBSD - int rtable = 0; -#endif - - char *nameserv_host; - char **nameserv_hosts; - int nameserv_hosts_len; + char *nameserv_host = NULL; struct sockaddr_storage nameservaddr; - struct sockaddr_storage *nameserv_addrs; - size_t nameserv_addrs_len; - int nameservaddr_len; - int nameserv_family; - - /* Set default values */ - nameserv_addrs_len = 0; - nameservaddr_len = 0; - nameserv_host = NULL; - topdomain = NULL; - errormsg = NULL; -#ifndef WINDOWS32 - pw = NULL; -#endif - username = NULL; - memset(password, 0, 33); - srand(time(NULL)); - foreground = 0; - newroot = NULL; - context = NULL; - device = NULL; - pidfile = NULL; - debug = 0; - stats = 0; - - autodetect_frag_size = 1; - max_downstream_frag_size = 3072; - retval = 0; - raw_mode = 1; - lazymode = 1; - target_interval_sec = 5; /* DNS RFC says 5 seconds minimum */ - min_interval_ms = 0; - server_timeout_sec = 4; /* Safe value for RTT <1s */ - downstream_timeout_sec = 2; - autodetect_server_timeout = 1; - hostname_maxlen = 0xFF; - nameserv_family = AF_UNSPEC; - up_compression = 1; - down_compression = 1; - - up_windowsize = 8; - down_windowsize = 8; + int nameservaddr_len = 0; + int nameserv_family = AF_UNSPEC; #ifdef WINDOWS32 WSAStartup(req_version, &wsa_data); #endif srand((unsigned) time(NULL)); - client_init(); + this.rand_seed = (uint16_t) rand(); + this.chunkid = (uint16_t) rand(); + this.running = 1; #if !defined(BSD) && !defined(__GLIBC__) __progname = strrchr(argv[0], '/'); @@ -239,8 +217,24 @@ main(int argc, char **argv) __progname++; #endif - while ((choice = getopt(argc, argv, "46vfDhrs:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:")) != -1) { - switch(choice) { + /* each option has format: + * char *name, int has_arg, int *flag, int val */ + static struct option iodine_args[] = { + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"stats", optional_argument, 0, 'V'}, + {"context", required_argument, 0, 'z'}, + {"rdomain", required_argument, 0, 'R'}, + {"chrootdir", required_argument, 0, 't'}, + {"proxycommand", no_argument, 0, 'X'}, + {NULL, 0, 0, 0} + }; + + static char *iodine_args_short = "46vfDhrXs:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:"; + + + while ((choice = getopt_long(argc, argv, iodine_args_short, iodine_args, NULL)) != -1) { + switch (choice) { case '4': nameserv_family = AF_INET; break; @@ -252,22 +246,22 @@ main(int argc, char **argv) /* NOTREACHED */ break; case 'V': - stats = atoi(optarg); - if (stats < 0) - stats = 0; + this.stats = atoi(optarg); + if (this.stats < 0) + this.stats = 0; break; case 'f': - foreground = 1; + this.foreground = 1; break; case 'D': - debug++; + this.debug++; break; case 'h': help(); /* NOTREACHED */ break; case 'r': - raw_mode = 0; + this.raw_mode = 0; break; case 'u': username = optarg; @@ -284,22 +278,22 @@ main(int argc, char **argv) break; #endif case 'P': - strncpy(password, optarg, sizeof(password)); - password[sizeof(password)-1] = 0; + strncpy(this.password, optarg, sizeof(this.password)); + this.password[sizeof(this.password)-1] = 0; /* XXX: find better way of cleaning up ps(1) */ memset(optarg, 0, strlen(optarg)); break; case 'm': - autodetect_frag_size = 0; - max_downstream_frag_size = atoi(optarg); + this.autodetect_frag_size = 0; + this.max_downstream_frag_size = atoi(optarg); break; case 'M': - hostname_maxlen = atoi(optarg); - if (hostname_maxlen > 255) - hostname_maxlen = 255; - if (hostname_maxlen < 10) - hostname_maxlen = 10; + this.hostname_maxlen = atoi(optarg); + if (this.hostname_maxlen > 255) + this.hostname_maxlen = 255; + if (this.hostname_maxlen < 10) + this.hostname_maxlen = 10; break; case 'z': context = optarg; @@ -312,45 +306,48 @@ main(int argc, char **argv) errx(5, "Invalid query type '%s'", optarg); break; case 'O': - client_set_downenc(optarg); + if ((this.downenc = parse_encoding(optarg)) == 0) + errx(6, "Invalid encoding type '%s'", optarg); break; case 'L': - lazymode = atoi(optarg); - if (lazymode > 1) - lazymode = 1; - if (lazymode < 0) - lazymode = 0; + this.lazymode = atoi(optarg); + if (this.lazymode > 1) + this.lazymode = 1; + if (this.lazymode < 0) + this.lazymode = 0; break; case 'I': - target_interval_sec = strtod(optarg, NULL); + this.max_timeout_ms = strtod(optarg, NULL) * 1000; break; case 'i': - server_timeout_sec = strtod(optarg, NULL); - autodetect_server_timeout = 0; + this.server_timeout_ms = strtod(optarg, NULL) * 1000; + this.autodetect_server_timeout = 0; break; case 'j': - downstream_timeout_sec = strtod(optarg, NULL); - if (autodetect_server_timeout) { - autodetect_server_timeout = 0; - server_timeout_sec = 4; + this.downstream_timeout_ms = strtod(optarg, NULL) * 1000; + if (this.autodetect_server_timeout) { + this.autodetect_server_timeout = 0; + this.server_timeout_ms = 4000; } break; case 's': - min_interval_ms = atoi(optarg); - if (min_interval_ms < 0) - min_interval_ms = 0; + this.send_interval_ms = atoi(optarg); + if (this.send_interval_ms < 0) + this.send_interval_ms = 0; case 'w': - down_windowsize = atoi(optarg); + this.windowsize_down = atoi(optarg); break; case 'W': - up_windowsize = atoi(optarg); - break; - case 'C': - up_compression = atoi(optarg) & 1; + this.windowsize_up = atoi(optarg); break; case 'c': - down_compression = atoi(optarg) & 1; + this.compression_down = atoi(optarg) & 1; break; + case 'C': + this.compression_up = atoi(optarg) & 1; + break; + case 'X': + // TODO implement option for remote host/port to pipe stdin/out default: usage(); /* NOTREACHED */ @@ -362,120 +359,124 @@ main(int argc, char **argv) argc -= optind; argv += optind; - if (debug) { - fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", debug); + if (this.debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", this.debug); fprintf(stderr, "Add more -D switches to set higher debug level.\n"); - foreground = 1; + this.foreground = 1; } - nameserv_hosts_len = argc - 1; - if (nameserv_hosts_len <= 0) - nameserv_hosts_len = 1; + this.nameserv_hosts_len = argc - 1; + if (this.nameserv_hosts_len <= 0) + /* if no hosts specified, use resolv.conf */ + this.nameserv_hosts_len = 1; // Preallocate memory with expected number of hosts - nameserv_hosts = malloc(sizeof(char *) * nameserv_hosts_len); - nameserv_addrs = malloc(sizeof(struct sockaddr_storage) * nameserv_hosts_len); + this.nameserv_hosts = malloc(sizeof(char *) * this.nameserv_hosts_len); + this.nameserv_addrs = malloc(sizeof(struct sockaddr_storage) * this.nameserv_hosts_len); if (argc == 0) { usage(); /* NOT REACHED */ } else if (argc == 1) { - nameserv_hosts[0] = get_resolvconf_addr(); + this.nameserv_hosts[0] = get_resolvconf_addr(); } else if (argc > 1) - for (int h = 0; h < nameserv_hosts_len; h++) nameserv_hosts[h] = strdup(argv[h + 1]); - topdomain = strdup(argv[0]); + for (int h = 0; h < this.nameserv_hosts_len; h++) + this.nameserv_hosts[h] = strdup(argv[h + 1]); + this.topdomain = strdup(argv[0]); - for (int n = 0; n < nameserv_hosts_len; n++) { - nameserv_host = nameserv_hosts[n]; + for (int n = 0; n < this.nameserv_hosts_len; n++) { + nameserv_host = this.nameserv_hosts[n]; if (!nameserv_host) { - errx(1, "Error processing nameserver hostnames!\n"); + errx(1, "Error processing nameserver hostnames!"); } nameservaddr_len = get_addr(nameserv_host, DNS_PORT, nameserv_family, 0, &nameservaddr); if (nameservaddr_len < 0) { errx(1, "Cannot lookup nameserver '%s': %s ", nameserv_host, gai_strerror(nameservaddr_len)); } - memcpy(&nameserv_addrs[n], &nameservaddr, sizeof(struct sockaddr_storage)); - nameserv_addrs_len ++; + memcpy(&this.nameserv_addrs[n], &nameservaddr, sizeof(struct sockaddr_storage)); + this.nameserv_addrs_len ++; nameserv_host = NULL; } - if (nameserv_addrs_len <= 0 || !nameserv_hosts[0]) { - warnx("No nameserver found - not connected to any network?\n"); + if (this.nameserv_addrs_len <= 0 || !this.nameserv_hosts[0]) { + warnx("No nameservers found - not connected to any network?"); usage(); } - client_set_nameservers(nameserv_addrs, nameserv_addrs_len); + // TODO remove client_set_... functions +// client_set_nameservers(nameserv_addrs, nameserv_addrs_len); - if (max_downstream_frag_size < 1 || max_downstream_frag_size > 0xffff) { - warnx("Use a max frag size between 1 and 65535 bytes.\n"); + if (this.max_downstream_frag_size < 10 || this.max_downstream_frag_size > MAX_FRAGSIZE) { + warnx("Use a max frag size between 10 and %d bytes.", MAX_FRAGSIZE); usage(); /* NOTREACHED */ } - if(check_topdomain(topdomain, &errormsg)) { + if(check_topdomain(this.topdomain, &errormsg)) { warnx("Invalid topdomain: %s", errormsg); usage(); /* NOTREACHED */ } - if (up_windowsize < 1 || down_windowsize < 1) { - warnx("Windowsize (-w or -W) must be greater than 0!"); + int max_ws = MAX_SEQ_ID / 2; + if (this.windowsize_up < 1 || this.windowsize_down < 1 || + this.windowsize_up > max_ws || this.windowsize_down > max_ws) { + warnx("Window sizes (-w or -W) must be between 0 and %d!", max_ws); usage(); } - if (target_interval_sec < 0.1) { - warnx("Target interval must be greater than 0.1 seconds!"); + if (this.max_timeout_ms < 100) { + warnx("Target interval (-I) must be greater than 0.1 seconds!"); usage(); } - if (server_timeout_sec < 0.1 || server_timeout_sec >= target_interval_sec) { - warnx("Server timeout must be greater than 0.1 sec and less than target interval!"); + if (this.server_timeout_ms < 100 || this.server_timeout_ms >= this.max_timeout_ms) { + warnx("Server timeout (-i) must be greater than 0.1 sec and less than target interval!"); usage(); } - if (downstream_timeout_sec < 0.1) { + if (this.downstream_timeout_ms < 100) { warnx("Downstream fragment timeout must be more than 0.1 sec to prevent excessive retransmits."); usage(); } - if (!lazymode && target_interval_sec > 1) { - warnx("Warning: Target interval of >1 second in immediate mode will cause high latency."); - usage(); + if (!this.lazymode && this.max_timeout_ms > 1000) { + fprintf(stderr, "Warning: Target interval of >1 second in immediate mode will cause high latency.\n"); } - client_set_compression(up_compression, down_compression); - client_set_dnstimeout(target_interval_sec, server_timeout_sec, downstream_timeout_sec, autodetect_server_timeout); - client_set_interval(target_interval_sec * 1000.0, min_interval_ms); - client_set_lazymode(lazymode); - client_set_topdomain(topdomain); - client_set_hostname_maxlen(hostname_maxlen); - client_set_windowsize(up_windowsize, down_windowsize); +// client_set_compression(up_compression, down_compression); +// client_set_dnstimeout(target_interval_sec, server_timeout_sec, downstream_timeout_sec, autodetect_server_timeout); +// client_set_interval(target_interval_sec * 1000.0, min_interval_ms); +// client_set_lazymode(lazymode); +// client_set_topdomain(topdomain); +// client_set_hostname_maxlen(hostname_maxlen); +// client_set_windowsize(up_windowsize, down_windowsize); if (username != NULL) { #ifndef WINDOWS32 if ((pw = getpwnam(username)) == NULL) { - warnx("User %s does not exist!\n", username); + warnx("User %s does not exist!", username); usage(); /* NOTREACHED */ } +#else + warnx("Warning: Cannot switch user on Windows systems."); #endif } - if (strlen(password) == 0) { + if (strlen(this.password) == 0) { if (NULL != getenv(PASSWORD_ENV_VAR)) - snprintf(password, sizeof(password), "%s", getenv(PASSWORD_ENV_VAR)); + snprintf(this.password, sizeof(this.password), "%s", getenv(PASSWORD_ENV_VAR)); else - read_password(password, sizeof(password)); + read_password(this.password, sizeof(this.password)); } - client_set_password(password); - - if ((tun_fd = open_tun(device)) == -1) { + if ((this.tun_fd = open_tun(device)) == -1) { retval = 1; goto cleanup1; } - if ((dns_fd = open_dns_from_host(NULL, 0, nameservaddr.ss_family, AI_PASSIVE)) < 0) { + if ((this.dns_fd = open_dns_from_host(NULL, 0, nameservaddr.ss_family, AI_PASSIVE)) < 0) { retval = 1; goto cleanup2; } @@ -487,24 +488,25 @@ main(int argc, char **argv) signal(SIGINT, sighandler); signal(SIGTERM, sighandler); - fprintf(stderr, "Sending DNS queries for %s to ", topdomain); - for (int a = 0; a < nameserv_addrs_len; a++) - fprintf(stderr, "%s%s", format_addr(&nameserv_addrs[a], nameservaddr_len), - (a != nameserv_addrs_len-1) ? ", " : ""); + fprintf(stderr, "Sending DNS queries for %s to ", this.topdomain); + for (int a = 0; a < this.nameserv_addrs_len; a++) + fprintf(stderr, "%s%s", format_addr(&this.nameserv_addrs[a], sizeof(struct sockaddr_storage)), + (a != this.nameserv_addrs_len - 1) ? ", " : ""); fprintf(stderr, "\n"); - if (client_handshake(dns_fd, raw_mode, autodetect_frag_size, max_downstream_frag_size)) { + // TODO not pass args to client stuff - use "this" as shared instance + if (client_handshake(this.dns_fd, this.raw_mode, this.autodetect_frag_size, this.max_downstream_frag_size)) { retval = 1; goto cleanup2; } - if (client_get_conn() == CONN_RAW_UDP) { - fprintf(stderr, "Sending raw traffic directly to %s\n", client_get_raw_addr()); + if (this.conn == CONN_RAW_UDP) { + fprintf(stderr, "Sending raw UDP traffic directly to %s\n", client_get_raw_addr()); } fprintf(stderr, "Connection setup complete, transmitting data.\n"); - if (foreground == 0) + if (this.foreground == 0) do_detach(); if (pidfile != NULL) @@ -528,11 +530,12 @@ main(int argc, char **argv) if (context != NULL) do_setcon(context); - client_tunnel(tun_fd, dns_fd); + // todo don't pass args again. + client_tunnel(this.tun_fd, this.dns_fd); cleanup2: - close_dns(dns_fd); - close_tun(tun_fd); + close_dns(this.dns_fd); + close_tun(this.tun_fd); cleanup1: return retval; From 4cdb3900d80854b084c9a5e43e7412b11a9ad115 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 15:05:16 +0800 Subject: [PATCH 081/113] Modify debug macro to use appropriate global instance --- src/common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common.h b/src/common.h index e360145..44266ad 100644 --- a/src/common.h +++ b/src/common.h @@ -91,7 +91,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; fprintf(stderr, __VA_ARGS__); #define DEBUG(level, ...) \ - if (debug >= level) {\ + if (INSTANCE.debug >= level) {\ TIMEPRINT("[D%d %s:%d] ", level, __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ @@ -101,7 +101,7 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; fprintf(stderr, __VA_ARGS__); #define DEBUG(level, ...) \ - if (debug >= level) {\ + if (INSTANCE.debug >= level) {\ fprintf(stderr, "[D%d] ", level); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ From 4c3e546b57ec67884fe9fab258b478e968c46ea5 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 15:05:41 +0800 Subject: [PATCH 082/113] Remove unused date constants --- src/windows.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/windows.h b/src/windows.h index cb7e399..909e5f6 100644 --- a/src/windows.h +++ b/src/windows.h @@ -93,13 +93,6 @@ struct ip struct in_addr ip_src, ip_dst; /* source and dest address */ }; -/* windows gettimeofday from https://gist.github.com/ugovaretto/5875385 */ -#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) - #define DELTA_EPOCH_IN_MICROSECS 116444736000000000Ui64 -#else - #define DELTA_EPOCH_IN_MICROSECS 116444736000000000ULL -#endif - /* Convenience macros for operations on timevals. NOTE: `timercmp' does not work for >= or <=. */ #define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) From f4d1d1634c544fdc8f214ae53dd26fa0380f6142 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 15:26:03 +0800 Subject: [PATCH 083/113] Server now functions the same as before, using default preset --- src/iodined.c | 5 +++++ src/server.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/iodined.c b/src/iodined.c index 14e8b69..e307d73 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -299,6 +299,11 @@ main(int argc, char **argv) __progname++; #endif + // Load default values from preset + memcpy(&server, &preset_default, sizeof(struct server_instance)); + + server.running = 1; + while ((choice = getopt(argc, argv, "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { switch(choice) { case '4': diff --git a/src/server.c b/src/server.c index 0e37af9..a00ef0e 100644 --- a/src/server.c +++ b/src/server.c @@ -705,7 +705,7 @@ server_tunnel() time_t last_action = time(NULL); if (server.debug >= 5) - window_debug = server.debug - 3; + window_debug = server.debug - 4; while (server.running) { int maxfd; From 7068bcc08d3c08ccf14fe6ce8e952e60ab69dd52 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 19:29:17 +0800 Subject: [PATCH 084/113] Added command line option presets (--preset or -Y) Fixed some refactoring issues and passing this.* as arguments --- src/client.c | 282 +++++++++++++++++++++++++-------------------------- src/client.h | 10 +- src/iodine.c | 161 ++++++++++++++++++++++------- 3 files changed, 268 insertions(+), 185 deletions(-) diff --git a/src/client.c b/src/client.c index 8d33409..e9c661c 100644 --- a/src/client.c +++ b/src/client.c @@ -162,7 +162,7 @@ immediate_mode_defaults() #endif static int -update_server_timeout(int dns_fd, int handshake) +update_server_timeout(int handshake) /* Calculate server timeout based on average RTT, send ping "handshake" to set * if handshake sent, return query ID */ { @@ -201,7 +201,7 @@ update_server_timeout(int dns_fd, int handshake) if (handshake) { /* Send ping handshake to set server timeout */ - return send_ping(dns_fd, 1, -1, 1); + return send_ping(1, -1, 1); } return -1; } @@ -309,7 +309,7 @@ got_response(int id, int immediate, int fail) this.num_immediate++; if (this.autodetect_server_timeout) - update_server_timeout(-1, 0); + update_server_timeout(0); } /* Remove query info from buffer to mark it as answered */ @@ -326,7 +326,7 @@ got_response(int id, int immediate, int fail) } static int -send_query(int fd, uint8_t *hostname) +send_query(uint8_t *hostname) /* Returns DNS ID of sent query */ { uint8_t packet[4096]; @@ -351,7 +351,7 @@ send_query(int fd, uint8_t *hostname) DEBUG(4, " Sendquery: id %5d name[0] '%c'", q.id, hostname[0]); - sendto(fd, packet, len, 0, (struct sockaddr*) &this.nameserv_addrs[this.current_nameserver], + sendto(this.dns_fd, packet, len, 0, (struct sockaddr*) &this.nameserv_addrs[this.current_nameserver], sizeof(struct sockaddr_storage)); client_rotate_nameserver(); @@ -391,14 +391,14 @@ send_query(int fd, uint8_t *hostname) this.lazymode = 0; this.server_timeout_ms = 0; } - update_server_timeout(fd, 1); + update_server_timeout(1); } } return q.id; } static void -send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd) +send_raw(uint8_t *buf, size_t buflen, int cmd) { char packet[4096]; int len; @@ -411,20 +411,20 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd) } len += RAW_HDR_LEN; - packet[RAW_HDR_CMD] = (cmd & 0xF0) | (user & 0x0F); + packet[RAW_HDR_CMD] = (cmd & 0xF0) | (this.userid & 0x0F); - sendto(fd, packet, len, 0, (struct sockaddr*)&this.raw_serv, sizeof(this.raw_serv)); + sendto(this.dns_fd, packet, len, 0, (struct sockaddr*)&this.raw_serv, sizeof(this.raw_serv)); } static void -send_raw_data(int dns_fd, uint8_t *data, size_t datalen) +send_raw_data(uint8_t *data, size_t datalen) { - send_raw(dns_fd, data, datalen, this.userid, RAW_HDR_CMD_DATA); + send_raw(data, datalen, RAW_HDR_CMD_DATA); } static int -send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) +send_packet(char cmd, const uint8_t *data, const size_t datalen) /* Base32 encodes data and sends as single DNS query * Returns ID of sent query */ { @@ -434,11 +434,11 @@ send_packet(int fd, char cmd, const uint8_t *data, const size_t datalen) build_hostname(buf, sizeof(buf), data, datalen, this.topdomain, b32, this.hostname_maxlen, 1); - return send_query(fd, buf); + return send_query(buf); } int -send_ping(int fd, int ping_response, int ack, int set_timeout) +send_ping(int ping_response, int ack, int set_timeout) { this.num_pings++; if (this.conn == CONN_DNS_NULL) { @@ -469,19 +469,19 @@ send_ping(int fd, int ping_response, int ack, int set_timeout) ping_response, ack, set_timeout ? "SET " : "", this.server_timeout_ms, this.downstream_timeout_ms, data[8]); - id = send_packet(fd, 'p', data, sizeof(data)); + id = send_packet('p', data, sizeof(data)); /* Log query ID as being sent now */ query_sent_now(id); return id; } else { - send_raw(fd, NULL, 0, this.userid, RAW_HDR_CMD_PING); + send_raw(NULL, 0, RAW_HDR_CMD_PING); return -1; } } static void -send_next_frag(int fd) +send_next_frag() /* Sends next available fragment of data from the outgoing window buffer */ { static uint8_t buf[MAX_FRAGSIZE], hdr[5]; @@ -497,7 +497,7 @@ send_next_frag(int fd) 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(fd, 1, this.next_downstream_ack, 1); + send_ping(1, this.next_downstream_ack, 1); this.next_downstream_ack = -1; window_tick(this.outbuf); } @@ -532,7 +532,7 @@ send_next_frag(int fd) DEBUG(3, " SEND DATA: seq %d, ack %d, len %lu, s%d e%d c%d flags %1X", f->seqID, f->ack_other, f->len, f->start, f->end, f->compressed, hdr[2] >> 4); - id = send_query(fd, buf); + id = send_query(buf); /* Log query ID as being sent now */ query_sent_now(id); @@ -684,7 +684,7 @@ dns_namedec(uint8_t *outdata, size_t outdatalen, uint8_t *buf, size_t buflen) } static int -read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query *q) +read_dns_withq(uint8_t *buf, size_t buflen, struct query *q) /* Returns -1 on receive error or decode error, including DNS error replies. Returns 0 on replies that could be correct but are useless, and are not DNS error replies. @@ -697,7 +697,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query int r; addrlen = sizeof(from); - if ((r = recvfrom(dns_fd, data, sizeof(data), 0, + if ((r = recvfrom(this.dns_fd, data, sizeof(data), 0, (struct sockaddr*)&from, &addrlen)) < 0) { warn("recvfrom"); return -1; @@ -787,7 +787,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query r -= RAW_HDR_LEN; datalen = sizeof(buf); if (uncompress(buf, &datalen, data + RAW_HDR_LEN, r) == Z_OK) { - write_tun(tun_fd, buf, datalen); + write_tun(this.tun_fd, buf, datalen); } /* all done */ @@ -796,7 +796,7 @@ read_dns_withq(int dns_fd, int tun_fd, uint8_t *buf, size_t buflen, struct query } int -handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) +handshake_waitdns(char *buf, size_t buflen, char cmd, int timeout) /* Wait for DNS reply fitting to our latest query and returns it. Returns length of reply = #bytes used in buf. Returns 0 if fitting reply happens to be useless. @@ -821,8 +821,8 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) tv.tv_sec = timeout; tv.tv_usec = 0; FD_ZERO(&fds); - FD_SET(dns_fd, &fds); - r = select(dns_fd + 1, &fds, NULL, NULL, &tv); + FD_SET(this.dns_fd, &fds); + r = select(this.dns_fd + 1, &fds, NULL, NULL, &tv); if (r < 0) return -1; /* select error */ @@ -831,7 +831,7 @@ handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout) q.id = -1; q.name[0] = '\0'; - rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q); + rv = read_dns_withq((uint8_t *)buf, buflen, &q); qcmd = toupper(q.name[0]); if (q.id != this.chunkid || qcmd != cmd) { @@ -920,7 +920,7 @@ parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) } static int -tunnel_tun(int tun_fd, int dns_fd) +tunnel_tun() { size_t datalen; uint8_t out[64*1024]; @@ -928,7 +928,7 @@ tunnel_tun(int tun_fd, int dns_fd) uint8_t *data; ssize_t read; - if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) + if ((read = read_tun(this.tun_fd, in, sizeof(in))) <= 0) return -1; DEBUG(2, " IN: %lu bytes on tunnel, to be compressed: %d", read, this.compression_up); @@ -953,14 +953,14 @@ tunnel_tun(int tun_fd, int dns_fd) 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(dns_fd, data, datalen); + send_raw_data(data, datalen); } return read; } static int -tunnel_dns(int tun_fd, int dns_fd) +tunnel_dns() { struct query q; size_t datalen, buflen; @@ -971,7 +971,7 @@ tunnel_dns(int tun_fd, int dns_fd) memset(&q, 0, sizeof(q)); memset(buf, 0, sizeof(buf)); memset(cbuf, 0, sizeof(cbuf)); - read = read_dns_withq(dns_fd, tun_fd, cbuf, sizeof(cbuf), &q); + read = read_dns_withq(cbuf, sizeof(cbuf), &q); if (this.conn != CONN_DNS_NULL) return 1; /* everything already done */ @@ -1016,7 +1016,7 @@ tunnel_dns(int tun_fd, int dns_fd) /* Reset query counts this.stats */ this.send_query_sendcnt = 0; this.send_query_recvcnt = 0; - update_server_timeout(dns_fd, 1); + update_server_timeout(1); } else if (this.send_query_recvcnt < 500 && this.num_servfail >= 40 && this.autodetect_server_timeout && this.max_timeout_ms < 500) { @@ -1024,7 +1024,7 @@ tunnel_dns(int tun_fd, int dns_fd) /* last-ditch attempt to fix SERVFAILs - disable lazy mode */ immediate_mode_defaults(); fprintf(stderr, "Attempting to disable lazy mode due to excessive SERVFAILs\n"); - handshake_switch_options(dns_fd, 0, this.compression_down, this.downenc); + handshake_switch_options(0, this.compression_down, this.downenc); } } } @@ -1111,7 +1111,7 @@ tunnel_dns(int tun_fd, int dns_fd) } if (datalen) - write_tun(tun_fd, data, datalen); + write_tun(this.tun_fd, data, datalen); } /* Move window along after doing all data processing */ @@ -1121,7 +1121,7 @@ tunnel_dns(int tun_fd, int dns_fd) } int -client_tunnel(int tun_fd, int dns_fd) +client_tunnel() { struct timeval tv, nextresend, tmp, now, now2; fd_set fds; @@ -1180,10 +1180,10 @@ client_tunnel(int tun_fd, int dns_fd) if (sending > 0) { /* More to send - next fragment */ - send_next_frag(dns_fd); + send_next_frag(); } else { /* Send ping if we didn't send anything yet */ - send_ping(dns_fd, 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)); this.next_downstream_ack = -1; } @@ -1255,9 +1255,9 @@ client_tunnel(int tun_fd, int dns_fd) if (this.conn != CONN_DNS_NULL || window_buffer_available(this.outbuf) > 16) { /* Fill up outgoing buffer with available data if it has enough space * The windowing protocol manages data retransmits, timeouts etc. */ - FD_SET(tun_fd, &fds); + FD_SET(this.tun_fd, &fds); } - FD_SET(dns_fd, &fds); + FD_SET(this.dns_fd, &fds); DEBUG(4, "Waiting %ld ms before sending more... (min_send %d)", timeval_to_ms(&tv), use_min_send); @@ -1265,7 +1265,7 @@ client_tunnel(int tun_fd, int dns_fd) gettimeofday(&now, NULL); } - i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); + i = select(MAX(this.tun_fd, this.dns_fd) + 1, &fds, NULL, NULL, &tv); if (use_min_send && i > 0) { /* enforce min_send_interval if we get interrupted by new tun data */ @@ -1291,8 +1291,8 @@ client_tunnel(int tun_fd, int dns_fd) if (i == 0) { /* timed out - no new packets recv'd */ } else { - if (FD_ISSET(tun_fd, &fds)) { - if (tunnel_tun(tun_fd, dns_fd) <= 0) + if (FD_ISSET(this.tun_fd, &fds)) { + if (tunnel_tun() <= 0) continue; /* Returns -1 on error OR when quickly dropping data in case of DNS congestion; @@ -1300,8 +1300,8 @@ client_tunnel(int tun_fd, int dns_fd) If chunk sent, sets this.send_ping_soon=0. */ } - if (FD_ISSET(dns_fd, &fds)) { - tunnel_dns(tun_fd, dns_fd); + if (FD_ISSET(this.dns_fd, &fds)) { + tunnel_dns(); } } } @@ -1310,7 +1310,7 @@ client_tunnel(int tun_fd, int dns_fd) } static void -send_login(int fd, char *login, int len) +send_login(char *login, int len) { uint8_t data[19]; @@ -1323,11 +1323,11 @@ send_login(int fd, char *login, int len) this.rand_seed++; - send_packet(fd, 'l', data, sizeof(data)); + send_packet('l', data, sizeof(data)); } static void -send_fragsize_probe(int fd, uint16_t fragsize) +send_fragsize_probe(uint16_t fragsize) { uint8_t probedata[256]; uint8_t buf[MAX_FRAGSIZE]; @@ -1350,11 +1350,11 @@ send_fragsize_probe(int fd, uint16_t fragsize) build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), this.topdomain, this.dataenc, this.hostname_maxlen, 6); - send_query(fd, buf); + send_query(buf); } static void -send_set_downstream_fragsize(int fd, uint16_t fragsize) +send_set_downstream_fragsize(uint16_t fragsize) { uint8_t data[5]; @@ -1365,11 +1365,11 @@ send_set_downstream_fragsize(int fd, uint16_t fragsize) this.rand_seed++; - send_packet(fd, 'n', data, sizeof(data)); + send_packet('n', data, sizeof(data)); } static void -send_version(int fd, uint32_t version) +send_version(uint32_t version) { uint8_t data[6]; @@ -1381,11 +1381,11 @@ send_version(int fd, uint32_t version) this.rand_seed++; - send_packet(fd, 'v', data, sizeof(data)); + send_packet('v', data, sizeof(data)); } static void -send_ip_request(int fd, int userid) +send_ip_request() { uint8_t buf[512] = "i____."; buf[1] = b32_5to8(this.userid); @@ -1396,20 +1396,20 @@ send_ip_request(int fd, int userid) this.rand_seed++; strncat((char *)buf, this.topdomain, 512 - strlen((char *)buf)); - send_query(fd, buf); + send_query(buf); } static void -send_raw_udp_login(int dns_fd, int userid, int seed) +send_raw_udp_login(int seed) { char buf[16]; login_calculate(buf, 16, this.password, seed + 1); - send_raw(dns_fd, (uint8_t *) buf, sizeof(buf), this.userid, RAW_HDR_CMD_LOGIN); + send_raw((uint8_t *) buf, sizeof(buf), RAW_HDR_CMD_LOGIN); } static void -send_upenctest(int fd, char *s) +send_upenctest(char *s) /* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */ { char buf[512] = "z___"; @@ -1422,16 +1422,16 @@ send_upenctest(int fd, char *s) strncat(buf, s, 512 - strlen(buf)); strncat(buf, ".", 512 - strlen(buf)); strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query(fd, (uint8_t *)buf); + send_query((uint8_t *)buf); } static void -send_downenctest(int fd, char downenc, int variant, char *s, int slen) +send_downenctest(char downenc, int variant, char *s, int slen) /* Note: content/handling of s is not defined yet. */ { char buf[512] = "y_____."; - buf[1] = tolower(this.downenc); + buf[1] = tolower(downenc); buf[2] = b32_5to8(variant); buf[3] = b32_5to8((this.rand_seed >> 10) & 0x1f); @@ -1440,11 +1440,11 @@ send_downenctest(int fd, char downenc, int variant, char *s, int slen) this.rand_seed++; strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query(fd, (uint8_t *)buf); + send_query((uint8_t *)buf); } static void -send_codec_switch(int fd, int userid, int bits) +send_codec_switch(int bits) { char buf[512] = "s_____."; buf[1] = b32_5to8(this.userid); @@ -1456,11 +1456,11 @@ send_codec_switch(int fd, int userid, int bits) this.rand_seed++; strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query(fd, (uint8_t *)buf); + send_query((uint8_t *)buf); } static void -send_server_options(int fd, int userid, int lazy, int compression, char denc, char *options) +send_server_options(int lazy, int compression, char denc, char *options) /* Options must be length >=4 */ { char buf[512] = "oU3___CMC."; @@ -1478,11 +1478,11 @@ send_server_options(int fd, int userid, int lazy, int compression, char denc, ch this.rand_seed++; strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query(fd, (uint8_t *)buf); + send_query((uint8_t *)buf); } static int -handshake_version(int dns_fd, int *seed) +handshake_version(int *seed) { char hex[] = "0123456789abcdef"; char hex2[] = "0123456789ABCDEF"; @@ -1493,9 +1493,9 @@ handshake_version(int dns_fd, int *seed) for (i = 0; this.running && i < 5; i++) { - send_version(dns_fd, PROTOCOL_VERSION); + send_version(PROTOCOL_VERSION); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'V', i+1); + read = handshake_waitdns(in, sizeof(in), 'V', i+1); if (read >= 9) { payload = (((in[4] & 0xff) << 24) | @@ -1530,7 +1530,7 @@ handshake_version(int dns_fd, int *seed) } static int -handshake_login(int dns_fd, int seed) +handshake_login(int seed) { char in[4096]; char login[16]; @@ -1544,9 +1544,9 @@ handshake_login(int dns_fd, int seed) for (i=0; this.running && i<5 ;i++) { - send_login(dns_fd, login, 16); + send_login(login, 16); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'L', i+1); + read = handshake_waitdns(in, sizeof(in), 'L', i+1); if (read > 0) { int netmask; @@ -1578,7 +1578,7 @@ handshake_login(int dns_fd, int seed) } static int -handshake_raw_udp(int dns_fd, int seed) +handshake_raw_udp(int seed) { struct timeval tv; char in[4096]; @@ -1594,9 +1594,9 @@ handshake_raw_udp(int dns_fd, int seed) fprintf(stderr, "Testing raw UDP data to the server (skip with -r)"); for (i=0; this.running && i<3 ;i++) { - send_ip_request(dns_fd, this.userid); + send_ip_request(); - len = handshake_waitdns(dns_fd, in, sizeof(in), 'I', i+1); + len = handshake_waitdns(in, sizeof(in), 'I', i+1); if (len == 5 && in[0] == 'I') { /* Received IPv4 address */ @@ -1640,16 +1640,16 @@ handshake_raw_udp(int dns_fd, int seed) tv.tv_sec = i + 1; tv.tv_usec = 0; - send_raw_udp_login(dns_fd, this.userid, seed); + send_raw_udp_login(seed); FD_ZERO(&fds); - FD_SET(dns_fd, &fds); + FD_SET(this.dns_fd, &fds); - r = select(dns_fd + 1, &fds, NULL, NULL, &tv); + r = select(this.dns_fd + 1, &fds, NULL, NULL, &tv); if(r > 0) { /* recv() needed for windows, dont change to read() */ - len = recv(dns_fd, in, sizeof(in), 0); + len = recv(this.dns_fd, in, sizeof(in), 0); if (len >= (16 + RAW_HDR_LEN)) { char hash[16]; login_calculate(hash, 16, this.password, seed - 1); @@ -1671,7 +1671,7 @@ handshake_raw_udp(int dns_fd, int seed) } static int -handshake_upenctest(int dns_fd, char *s) +handshake_upenctest(char *s) /* NOTE: *s may be max 59 chars; must start with "aA" for case-swap check Returns: -1: case swap, no need for any further test: error printed; or Ctrl-C @@ -1689,9 +1689,9 @@ handshake_upenctest(int dns_fd, char *s) slen = strlen(s); for (i=0; this.running && i<3 ;i++) { - send_upenctest(dns_fd, s); + send_upenctest(s); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'Z', i+1); + read = handshake_waitdns(in, sizeof(in), 'Z', i+1); if (read == -2) return 0; /* hard error */ @@ -1741,7 +1741,7 @@ handshake_upenctest(int dns_fd, char *s) } static int -handshake_upenc_autodetect(int dns_fd) +handshake_upenc_autodetect() /* Returns: 0: keep Base32 1: Base64 is okay @@ -1771,7 +1771,7 @@ handshake_upenc_autodetect(int dns_fd) /* Try Base128, starting very gently to not draw attention */ while (1) { - res = handshake_upenctest(dns_fd, pat128a); + res = handshake_upenctest(pat128a); if (res < 0) { /* DNS swaps case, msg already printed; or Ctrl-C */ return 0; @@ -1780,7 +1780,7 @@ handshake_upenc_autodetect(int dns_fd) break; } - res = handshake_upenctest(dns_fd, pat128b); + res = handshake_upenctest(pat128b); if (res < 0) return 0; else if (res == 0) @@ -1788,19 +1788,19 @@ handshake_upenc_autodetect(int dns_fd) /* if this works, we can test the real stuff */ - res = handshake_upenctest(dns_fd, pat128c); + res = handshake_upenctest(pat128c); if (res < 0) return 0; else if (res == 0) break; - res = handshake_upenctest(dns_fd, pat128d); + res = handshake_upenctest(pat128d); if (res < 0) return 0; else if (res == 0) break; - res = handshake_upenctest(dns_fd, pat128e); + res = handshake_upenctest(pat128e); if (res < 0) return 0; else if (res == 0) @@ -1811,7 +1811,7 @@ handshake_upenc_autodetect(int dns_fd) } /* Try Base64 (with plus sign) */ - res = handshake_upenctest(dns_fd, pat64); + res = handshake_upenctest(pat64); if (res < 0) { /* DNS swaps case, msg already printed; or Ctrl-C */ return 0; @@ -1821,7 +1821,7 @@ handshake_upenc_autodetect(int dns_fd) } /* Try Base64u (with _u_nderscore) */ - res = handshake_upenctest(dns_fd, pat64u); + res = handshake_upenctest(pat64u); if (res < 0) { /* DNS swaps case, msg already printed; or Ctrl-C */ return 0; @@ -1836,7 +1836,7 @@ handshake_upenc_autodetect(int dns_fd) } static int -handshake_downenctest(int dns_fd, char trycodec) +handshake_downenctest(char trycodec) /* Returns: 0: not identical or error or timeout 1: identical string returned @@ -1850,9 +1850,9 @@ handshake_downenctest(int dns_fd, char trycodec) for (i=0; this.running && i<3 ;i++) { - send_downenctest(dns_fd, trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', i+1); + read = handshake_waitdns(in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -1880,7 +1880,7 @@ handshake_downenctest(int dns_fd, char trycodec) } static char -handshake_downenc_autodetect(int dns_fd) +handshake_downenc_autodetect() /* Returns codec char (or ' ' if no advanced codec works) */ { int base64ok = 0; @@ -1896,20 +1896,20 @@ handshake_downenc_autodetect(int dns_fd) fprintf(stderr, "Autodetecting downstream codec (use -O to override)\n"); /* Try Base64 */ - if (handshake_downenctest(dns_fd, 'S')) + if (handshake_downenctest('S')) base64ok = 1; - else if (this.running && handshake_downenctest(dns_fd, 'U')) + else if (this.running && handshake_downenctest('U')) base64uok = 1; /* Try Base128 only if 64 gives us some perspective */ if (this.running && (base64ok || base64uok)) { - if (handshake_downenctest(dns_fd, 'V')) + if (handshake_downenctest('V')) base128ok = 1; } /* If 128 works, then TXT may give us Raw as well */ if (this.running && (base128ok && this.do_qtype == T_TXT)) { - if (handshake_downenctest(dns_fd, 'R')) + if (handshake_downenctest('R')) return 'R'; } @@ -1928,7 +1928,7 @@ handshake_downenc_autodetect(int dns_fd) } static int -handshake_qtypetest(int dns_fd, int timeout) +handshake_qtypetest(int timeout) /* Returns: 0: doesn't work with this timeout 1: works properly @@ -1950,9 +1950,9 @@ handshake_qtypetest(int dns_fd, int timeout) byte values can be returned, which is needed for NULL/PRIVATE to work. */ - send_downenctest(dns_fd, trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', timeout); + read = handshake_waitdns(in, sizeof(in), 'Y', timeout); if (read != slen) return 0; /* incorrect */ @@ -1984,7 +1984,7 @@ handshake_qtype_numcvt(int num) } static int -handshake_qtype_autodetect(int dns_fd) +handshake_qtype_autodetect() /* Returns: 0: okay, this.do_qtype set 1: problem, program exit @@ -2016,7 +2016,7 @@ handshake_qtype_autodetect(int dns_fd) fprintf(stderr, "."); fflush(stderr); - if (handshake_qtypetest(dns_fd, timeout)) { + if (handshake_qtypetest(timeout)) { /* okay */ highestworking = qtypenum; DEBUG(1, " Type %s timeout %d works", client_get_qtype(), timeout); @@ -2053,7 +2053,7 @@ handshake_qtype_autodetect(int dns_fd) } static int -handshake_edns0_check(int dns_fd) +handshake_edns0_check() /* Returns: 0: EDNS0 not supported; or Ctrl-C 1: EDNS0 works @@ -2073,9 +2073,9 @@ handshake_edns0_check(int dns_fd) for (i=0; this.running && i<3 ;i++) { - send_downenctest(dns_fd, trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1, NULL, 0); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', i+1); + read = handshake_waitdns(in, sizeof(in), 'Y', i+1); if (read == -2) return 0; /* hard error */ @@ -2103,7 +2103,7 @@ handshake_edns0_check(int dns_fd) } static void -handshake_switch_codec(int dns_fd, int bits) +handshake_switch_codec(int bits) { char in[4096]; int i; @@ -2124,9 +2124,9 @@ handshake_switch_codec(int dns_fd, int bits) for (i=0; this.running && i<5 ;i++) { - send_codec_switch(dns_fd, this.userid, bits); + send_codec_switch(bits); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'S', i+1); + read = handshake_waitdns(in, sizeof(in), 'S', i+1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { @@ -2160,7 +2160,7 @@ codec_revert: } void -handshake_switch_options(int dns_fd, int lazy, int compression, char denc) +handshake_switch_options(int lazy, int compression, char denc) { char in[4096]; int read; @@ -2185,9 +2185,9 @@ handshake_switch_options(int dns_fd, int lazy, int compression, char denc) lazy_status, dname, comp_status); for (int i = 0; this.running && i < 5; i++) { - send_server_options(dns_fd, this.userid, lazy, compression, denc, opts); + send_server_options(lazy, compression, denc, opts); - read = handshake_waitdns(dns_fd, in, sizeof(in) - 1, 'O', i + 1); + read = handshake_waitdns(in, sizeof(in) - 1, 'O', i + 1); if (read > 0) { if (strncmp("BADLEN", in, 6) == 0) { @@ -2295,7 +2295,7 @@ fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) static int -handshake_autoprobe_fragsize(int dns_fd) +handshake_autoprobe_fragsize() { char in[MAX_FRAGSIZE]; int i; @@ -2310,9 +2310,9 @@ handshake_autoprobe_fragsize(int dns_fd) /* stop the slow probing early when we have enough bytes anyway */ for (i=0; this.running && i<3 ;i++) { - send_fragsize_probe(dns_fd, proposed_fragsize); + send_fragsize_probe(proposed_fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'R', 1); + read = handshake_waitdns(in, sizeof(in), 'R', 1); if (read > 0) { /* We got a reply */ @@ -2367,7 +2367,7 @@ handshake_autoprobe_fragsize(int dns_fd) } static void -handshake_set_fragsize(int dns_fd, int fragsize) +handshake_set_fragsize(int fragsize) { char in[4096]; int i; @@ -2376,9 +2376,9 @@ handshake_set_fragsize(int dns_fd, int fragsize) fprintf(stderr, "Setting downstream fragment size to max %d...\n", fragsize); for (i=0; this.running && i<5 ;i++) { - send_set_downstream_fragsize(dns_fd, fragsize); + send_set_downstream_fragsize(fragsize); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'N', i+1); + read = handshake_waitdns(in, sizeof(in), 'N', i+1); if (read > 0) { @@ -2404,7 +2404,7 @@ handshake_set_fragsize(int dns_fd, int fragsize) } static void -handshake_set_timeout(int dns_fd) +handshake_set_timeout() { char in[4096]; int read, id; @@ -2419,9 +2419,9 @@ handshake_set_timeout(int dns_fd) for (int i = 0; this.running && i < 5; i++) { id = this.autodetect_server_timeout ? - update_server_timeout(dns_fd, 1) : send_ping(dns_fd, 1, -1, 1); + update_server_timeout(1) : send_ping(1, -1, 1); - read = handshake_waitdns(dns_fd, in, sizeof(in), 'P', i + 1); + read = handshake_waitdns(in, sizeof(in), 'P', i + 1); got_response(id, 1, 0); fprintf(stderr, "."); @@ -2447,7 +2447,7 @@ handshake_set_timeout(int dns_fd) } int -client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize) +client_handshake() { int seed; int upcodec; @@ -2457,7 +2457,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz /* qtype message printed in handshake function */ if (this.do_qtype == T_UNSET) { - r = handshake_qtype_autodetect(dns_fd); + r = handshake_qtype_autodetect(); if (r) { return r; } @@ -2465,28 +2465,28 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz fprintf(stderr, "Using DNS type %s queries\n", client_get_qtype()); - r = handshake_version(dns_fd, &seed); + r = handshake_version(&seed); if (r) { return r; } - r = handshake_login(dns_fd, seed); + r = handshake_login(seed); if (r) { return r; } - if (raw_mode && handshake_raw_udp(dns_fd, seed)) { + if (this.raw_mode && handshake_raw_udp(seed)) { this.conn = CONN_RAW_UDP; this.max_timeout_ms = 10000; this.compression_down = 1; this.compression_up = 1; } else { - if (raw_mode == 0) { + if (this.raw_mode == 0) { fprintf(stderr, "Skipping raw mode\n"); } dnsc_use_edns0 = 1; - if (handshake_edns0_check(dns_fd) && this.running) { + if (handshake_edns0_check() && this.running) { fprintf(stderr, "Using EDNS0 extension\n"); } else if (!this.running) { return -1; @@ -2495,45 +2495,45 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz dnsc_use_edns0 = 0; } - upcodec = handshake_upenc_autodetect(dns_fd); + upcodec = handshake_upenc_autodetect(); if (!this.running) return -1; if (upcodec == 1) { /* Base64 */ - handshake_switch_codec(dns_fd, 6); + handshake_switch_codec(6); } else if (upcodec == 2) { /* Base64u */ - handshake_switch_codec(dns_fd, 26); + handshake_switch_codec(26); } else if (upcodec == 3) { /* Base128 */ - handshake_switch_codec(dns_fd, 7); + handshake_switch_codec(7); } if (!this.running) return -1; if (this.downenc == ' ') { - this.downenc = handshake_downenc_autodetect(dns_fd); + this.downenc = handshake_downenc_autodetect(); } if (!this.running) return -1; /* Set options for compression, this.lazymode and downstream codec */ - handshake_switch_options(dns_fd, this.lazymode, this.compression_down, this.downenc); + handshake_switch_options(this.lazymode, this.compression_down, this.downenc); if (!this.running) return -1; - if (autodetect_frag_size) { - fragsize = handshake_autoprobe_fragsize(dns_fd); - if (fragsize > MAX_FRAGSIZE) { + if (this.autodetect_frag_size) { + this.max_downstream_frag_size = handshake_autoprobe_fragsize(); + if (this.max_downstream_frag_size > MAX_FRAGSIZE) { /* This is very unlikely except perhaps over LAN */ fprintf(stderr, "Can transfer fragsize of %d, however iodine has been compiled with MAX_FRAGSIZE = %d." - " To fully utilize this connection, please recompile iodine/iodined.\n", fragsize, MAX_FRAGSIZE); - fragsize = MAX_FRAGSIZE; + " To fully utilize this connection, please recompile iodine/iodined.\n", this.max_downstream_frag_size, MAX_FRAGSIZE); + this.max_downstream_frag_size = MAX_FRAGSIZE; } - if (!fragsize) { + if (!this.max_downstream_frag_size) { return 1; } } - handshake_set_fragsize(dns_fd, fragsize); + handshake_set_fragsize(this.max_downstream_frag_size); if (!this.running) return -1; @@ -2551,7 +2551,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz this.pending_queries[i].id = -1; /* set server window/timeout parameters and calculate RTT */ - handshake_set_timeout(dns_fd); + handshake_set_timeout(); } return 0; diff --git a/src/client.h b/src/client.h index f76911a..1456073 100644 --- a/src/client.h +++ b/src/client.h @@ -153,12 +153,12 @@ char *format_qtype(); char parse_encoding(char *encoding); void client_set_hostname_maxlen(size_t i); -int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); -int client_tunnel(int tun_fd, int dns_fd); +int client_handshake(); +int client_tunnel(); int parse_data(uint8_t *data, size_t len, fragment *f, int *immediate); -int handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout); -void handshake_switch_options(int dns_fd, int lazy, int compression, char denc); -int send_ping(int fd, int ping_response, int ack, int timeout); +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); #endif diff --git a/src/iodine.c b/src/iodine.c index f963531..e27f23a 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -57,11 +58,20 @@ static char *__progname; struct client_instance this; +/* BEGIN PRESET DEFINITIONS */ + +/* static startup values - should not be changed in presets */ +#define PRESET_STATIC_VALUES \ + .conn = CONN_DNS_NULL, \ + .send_ping_soon = 1, \ + .downenc = ' ', \ + .do_qtype = T_UNSET, \ + .maxfragsize_up = 100, \ + .next_downstream_ack = -1, \ + .num_immediate = 1, \ + .rtt_total_ms = 200 + static struct client_instance preset_default = { - .foreground = 0, -#ifdef OPENBSD - .rtable = 0, -#endif .debug = 0, .stats = 0, .raw_mode = 1, @@ -79,18 +89,56 @@ static struct client_instance preset_default = { .windowsize_up = 8, .windowsize_down = 8, .hostname_maxlen = 0xFF, - - /* static startup values - should not be changed in presets */ - .conn = CONN_DNS_NULL, - .send_ping_soon = 1, - .downenc = ' ', - .do_qtype = T_UNSET, - .maxfragsize_up = 100, - .next_downstream_ack = -1, - .num_immediate = 1, - .rtt_total_ms = 200 + PRESET_STATIC_VALUES }; +static struct client_instance preset_original = { + .raw_mode = 1, + .lazymode = 1, + .max_timeout_ms = 4000, + .send_interval_ms = 0, + .server_timeout_ms = 3000, + .autodetect_server_timeout = 1, + .windowsize_down = 1, + .windowsize_up = 1, + .hostname_maxlen = 0xFF, + .downstream_timeout_ms = 4000, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = MAX_FRAGSIZE, + .compression_down = 1, + .compression_up = 0, + PRESET_STATIC_VALUES +}; + +struct option_presets { + size_t preset_data_len; /* sizeof(struct client/server_instance) */ + size_t num_presets; + struct option_preset { + void *preset_data; /* Pointer to client/server "instance" struct */ + char short_name; + char *long_name; + } presets[]; +}; + +#define NUM_CLIENT_PRESETS 2 + +static struct { + struct client_instance *preset_data; + char short_name; +} client_presets[NUM_CLIENT_PRESETS] = { + { + .preset_data = &preset_default, + .short_name = 'D' + }, + { + .preset_data = &preset_original, + .short_name = 'O' + } +}; + +/* END PRESET DEFINITIONS */ + static void sighandler(int sig) { @@ -151,14 +199,16 @@ help() fprintf(stderr, " -v, --version print version info and exit\n"); fprintf(stderr, " -h, --help print this help and exit\n"); fprintf(stderr, " -V, --stats print connection statistics at given intervals (default: 5 sec)\n"); - fprintf(stderr, " -X skip tun device and forward data to/from stdin/out, telling the iodined to\n"); + /*fprintf(stderr, " -X skip tun device and forward data to/from stdin/out, telling iodined to\n"); fprintf(stderr, " connect to the specified port listening on the server host.\n"); - fprintf(stderr, " Can be used with SSH ProxyCommand option. (-X 22)\n"); + fprintf(stderr, " Can be used with SSH ProxyCommand option. (-X 22)\n");*/ fprintf(stderr, " -f keep running in foreground\n"); fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); fprintf(stderr, " -d set tunnel device name\n"); fprintf(stderr, " -u drop privileges and run as specified user\n"); fprintf(stderr, " -F write PID to specified file\n"); + fprintf(stderr, " -Y, --preset use a set of predefined options (can be overridden manually)\n"); + fprintf(stderr, " Available presets: 'O' for iodine 0.7 behaviour, 'F' for fast/low latency settings\n"); fprintf(stderr, " --chroot chroot to given directory\n"); fprintf(stderr, " --context apply specified SELinux context after initialization\n"); fprintf(stderr, " --rdomain use specified routing domain (OpenBSD only)\n\n"); @@ -204,11 +254,6 @@ main(int argc, char **argv) WSAStartup(req_version, &wsa_data); #endif - srand((unsigned) time(NULL)); - this.rand_seed = (uint16_t) rand(); - this.chunkid = (uint16_t) rand(); - this.running = 1; - #if !defined(BSD) && !defined(__GLIBC__) __progname = strrchr(argv[0], '/'); if (__progname == NULL) @@ -227,13 +272,52 @@ main(int argc, char **argv) {"rdomain", required_argument, 0, 'R'}, {"chrootdir", required_argument, 0, 't'}, {"proxycommand", no_argument, 0, 'X'}, + {"preset", required_argument, 0, 'Y'}, {NULL, 0, 0, 0} }; - static char *iodine_args_short = "46vfDhrXs:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:"; + /* Pre-parse command line to get preset + * This is so that all options override preset values regardless of order in command line */ + int optind_orig = optind, preset_id = -1; + static char *iodine_args_short = "46vfDhrX:Y:s:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:"; + + while ((choice = getopt_long(argc, argv, iodine_args_short, iodine_args, NULL))) { + if (preset_id < 0) { + if (choice == -1) { + /* reached end of command line and no preset specified - use default */ + preset_id = 0; + } else if (choice == 'Y') { + /* find index of preset */ + if (optarg) { + for (int i = 0; i < NUM_CLIENT_PRESETS; i++) { + if (toupper(optarg[0] == client_presets[i].short_name)) { + preset_id = i; + break; + } + } + } + } else { + /* skip all other options until we find preset */ + continue; + } + + if (preset_id < 0) { + /* invalid preset or none specified */ + fprintf(stderr, "Invalid preset or none specified with -Y or --preset! Run 'iodine -h' to see available presets.\n"); + usage(); + /* not reached */ + } + + memcpy(&this, client_presets[preset_id].preset_data, sizeof(struct client_instance)); + + /* Reset optind to reparse command line */ + optind = optind_orig; + continue; + } else if (choice == -1) { + break; + } - while ((choice = getopt_long(argc, argv, iodine_args_short, iodine_args, NULL)) != -1) { switch (choice) { case '4': nameserv_family = AF_INET; @@ -318,6 +402,9 @@ main(int argc, char **argv) break; case 'I': this.max_timeout_ms = strtod(optarg, NULL) * 1000; + if (this.autodetect_server_timeout) { + this.server_timeout_ms = this.max_timeout_ms / 2; + } break; case 'i': this.server_timeout_ms = strtod(optarg, NULL) * 1000; @@ -346,6 +433,9 @@ main(int argc, char **argv) case 'C': this.compression_up = atoi(optarg) & 1; break; + case 'Y': + /* Already processed preset: ignore */ + continue; case 'X': // TODO implement option for remote host/port to pipe stdin/out default: @@ -354,6 +444,11 @@ main(int argc, char **argv) } } + srand((unsigned) time(NULL)); + this.rand_seed = (uint16_t) rand(); + this.chunkid = (uint16_t) rand(); + this.running = 1; + check_superuser(usage); argc -= optind; @@ -404,9 +499,6 @@ main(int argc, char **argv) usage(); } - // TODO remove client_set_... functions -// client_set_nameservers(nameserv_addrs, nameserv_addrs_len); - if (this.max_downstream_frag_size < 10 || this.max_downstream_frag_size > MAX_FRAGSIZE) { warnx("Use a max frag size between 10 and %d bytes.", MAX_FRAGSIZE); usage(); @@ -431,7 +523,8 @@ main(int argc, char **argv) usage(); } - if (this.server_timeout_ms < 100 || this.server_timeout_ms >= this.max_timeout_ms) { + if ((this.server_timeout_ms < 100 || this.server_timeout_ms >= this.max_timeout_ms) + && !this.autodetect_server_timeout) { warnx("Server timeout (-i) must be greater than 0.1 sec and less than target interval!"); usage(); } @@ -445,14 +538,6 @@ main(int argc, char **argv) fprintf(stderr, "Warning: Target interval of >1 second in immediate mode will cause high latency.\n"); } -// client_set_compression(up_compression, down_compression); -// client_set_dnstimeout(target_interval_sec, server_timeout_sec, downstream_timeout_sec, autodetect_server_timeout); -// client_set_interval(target_interval_sec * 1000.0, min_interval_ms); -// client_set_lazymode(lazymode); -// client_set_topdomain(topdomain); -// client_set_hostname_maxlen(hostname_maxlen); -// client_set_windowsize(up_windowsize, down_windowsize); - if (username != NULL) { #ifndef WINDOWS32 if ((pw = getpwnam(username)) == NULL) { @@ -494,8 +579,7 @@ main(int argc, char **argv) (a != this.nameserv_addrs_len - 1) ? ", " : ""); fprintf(stderr, "\n"); - // TODO not pass args to client stuff - use "this" as shared instance - if (client_handshake(this.dns_fd, this.raw_mode, this.autodetect_frag_size, this.max_downstream_frag_size)) { + if (client_handshake()) { retval = 1; goto cleanup2; } @@ -530,8 +614,7 @@ main(int argc, char **argv) if (context != NULL) do_setcon(context); - // todo don't pass args again. - client_tunnel(this.tun_fd, this.dns_fd); + client_tunnel(); cleanup2: close_dns(this.dns_fd); From 8232979a6e2ab1d16ba7033f9801615006014d4d Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 22:12:00 +0800 Subject: [PATCH 085/113] Added more presets - TODO: test to ensure they work as described --- src/iodine.c | 104 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index e27f23a..84bd666 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -46,8 +46,21 @@ #include "base32.h" #ifdef WINDOWS32 +#include "windows.h" WORD req_version = MAKEWORD(2, 2); WSADATA wsa_data; +#else +#include +#ifdef ANDROID +#include "android_dns.h" +#endif +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#include +#include +#include #endif #if !defined(BSD) && !defined(__GLIBC__) @@ -72,8 +85,6 @@ struct client_instance this; .rtt_total_ms = 200 static struct client_instance preset_default = { - .debug = 0, - .stats = 0, .raw_mode = 1, .lazymode = 1, .max_timeout_ms = 5000, @@ -93,7 +104,7 @@ static struct client_instance preset_default = { }; static struct client_instance preset_original = { - .raw_mode = 1, + .raw_mode = 0, .lazymode = 1, .max_timeout_ms = 4000, .send_interval_ms = 0, @@ -111,29 +122,72 @@ static struct client_instance preset_original = { PRESET_STATIC_VALUES }; -struct option_presets { - size_t preset_data_len; /* sizeof(struct client/server_instance) */ - size_t num_presets; - struct option_preset { - void *preset_data; /* Pointer to client/server "instance" struct */ - char short_name; - char *long_name; - } presets[]; +static struct client_instance preset_fast = { + .raw_mode = 0, + .lazymode = 1, + .max_timeout_ms = 3000, + .send_interval_ms = 0, + .server_timeout_ms = 2500, + .downstream_timeout_ms = 100, + .autodetect_server_timeout = 1, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = 1176, + .compression_up = 1, + .compression_down = 1, + .windowsize_up = 64, + .windowsize_down = 32, + .hostname_maxlen = 0xFF, + PRESET_STATIC_VALUES }; -#define NUM_CLIENT_PRESETS 2 +static struct client_instance preset_fallback = { + .raw_mode = 1, + .lazymode = 1, + .max_timeout_ms = 1000, + .send_interval_ms = 20, + .server_timeout_ms = 500, + .downstream_timeout_ms = 1000, + .autodetect_server_timeout = 1, + .dataenc = &base32_encoder, + .autodetect_frag_size = 1, + .max_downstream_frag_size = 500, + .compression_up = 1, + .compression_down = 1, + .windowsize_up = 1, + .windowsize_down = 1, + .hostname_maxlen = 100, + .downenc = 'T', + .do_qtype = T_CNAME, + PRESET_STATIC_VALUES +}; + +#define NUM_CLIENT_PRESETS 4 static struct { struct client_instance *preset_data; char short_name; + char *desc; } client_presets[NUM_CLIENT_PRESETS] = { { .preset_data = &preset_default, - .short_name = 'D' + .short_name = 'D', + .desc = "Defaults" }, { .preset_data = &preset_original, - .short_name = 'O' + .short_name = '7', + .desc = "Imitate iodine 0.7" + }, + { + .preset_data = &preset_fast, + .short_name = 'F', + .desc = "Fast and low latency" + }, + { + .preset_data = &preset_fallback, + .short_name = 'M', + .desc = "Minimal DNS queries and short DNS timeouts" } }; @@ -156,7 +210,7 @@ print_usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-V sec] [-X port] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + fprintf(stderr, "Usage: %s [-v] [-h] [-Y preset] [-V sec] [-X port] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-w downfrags] [-W upfrags] [-i sec -j sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-R rdomain] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [...]]]\n", __progname); @@ -169,6 +223,17 @@ usage() exit(2); } +static void +print_presets(int spaces) +{ +#define INDENT fprintf(stderr, "%*s", spaces, ""); + INDENT fprintf(stderr, "Available presets: (use -Y )\n"); + spaces += 2; + for (int i = 0; i < NUM_CLIENT_PRESETS; i++) { + INDENT fprintf(stderr, "'%c': %s\n", client_presets[i].short_name, client_presets[i].desc); + } +} + static void help() { @@ -207,8 +272,8 @@ help() fprintf(stderr, " -d set tunnel device name\n"); fprintf(stderr, " -u drop privileges and run as specified user\n"); fprintf(stderr, " -F write PID to specified file\n"); - fprintf(stderr, " -Y, --preset use a set of predefined options (can be overridden manually)\n"); - fprintf(stderr, " Available presets: 'O' for iodine 0.7 behaviour, 'F' for fast/low latency settings\n"); + fprintf(stderr, " -Y, --preset use a set of predefined options for DNS tunnel (can be overridden manually)\n"); + print_presets(6); fprintf(stderr, " --chroot chroot to given directory\n"); fprintf(stderr, " --context apply specified SELinux context after initialization\n"); fprintf(stderr, " --rdomain use specified routing domain (OpenBSD only)\n\n"); @@ -291,7 +356,7 @@ main(int argc, char **argv) /* find index of preset */ if (optarg) { for (int i = 0; i < NUM_CLIENT_PRESETS; i++) { - if (toupper(optarg[0] == client_presets[i].short_name)) { + if (toupper(optarg[0]) == client_presets[i].short_name) { preset_id = i; break; } @@ -304,7 +369,8 @@ main(int argc, char **argv) if (preset_id < 0) { /* invalid preset or none specified */ - fprintf(stderr, "Invalid preset or none specified with -Y or --preset! Run 'iodine -h' to see available presets.\n"); + fprintf(stderr, "Invalid preset or none specified with -Y or --preset!\n"); + print_presets(2); usage(); /* not reached */ } From 122e42a5f70f943b225e38fe13cd306b654ab3ce Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 22:13:13 +0800 Subject: [PATCH 086/113] Always print window sizes on startup --- src/client.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/client.c b/src/client.c index e9c661c..e2ac39e 100644 --- a/src/client.c +++ b/src/client.c @@ -170,7 +170,7 @@ update_server_timeout(int handshake) static size_t num_rtt_timeouts = 0; /* Get average RTT in ms */ - rtt_ms = this.rtt_total_ms / this.num_immediate; + rtt_ms = (this.num_immediate == 0) ? 1 : this.rtt_total_ms / this.num_immediate; if (rtt_ms >= this.max_timeout_ms && this.num_immediate > 5) { num_rtt_timeouts++; if (num_rtt_timeouts < 3) { @@ -308,7 +308,7 @@ got_response(int id, int immediate, int fail) this.rtt_total_ms += rtt_ms; this.num_immediate++; - if (this.autodetect_server_timeout) + if (this.autodetect_server_timeout && this.lazymode) update_server_timeout(0); } @@ -2409,12 +2409,14 @@ handshake_set_timeout() char in[4096]; int read, id; - if (this.autodetect_server_timeout && this.lazymode) { - fprintf(stderr, "Calculating round-trip time for optimum server timeout..."); - } else { - fprintf(stderr, "Setting window sizes to %lu frags upstream, %lu frags downstream...", - this.windowsize_up, this.windowsize_down); - } + fprintf(stderr, "Setting window sizes to %lu frags upstream, %lu frags downstream...\n", + this.windowsize_up, this.windowsize_down); + + fprintf(stderr, "Calculating round-trip time..."); + + /* Reset RTT stats */ + this.num_immediate = 0; + this.rtt_total_ms = 0; for (int i = 0; this.running && i < 5; i++) { @@ -2429,21 +2431,15 @@ handshake_set_timeout() if (strncmp("BADIP", in, 5) == 0) { fprintf(stderr, "Server rejected sender IP address.\n"); } - if (this.autodetect_server_timeout) - continue; - else - break; + continue; } } if (!this.running) return; - if (this.autodetect_server_timeout) - fprintf(stderr, "\nDetermined round-trip time of %ld ms, server timeout of %ld ms.\n", - this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); - else - fprintf(stderr, " done\n"); + fprintf(stderr, "\nDetermined round-trip time of %ld ms, using server timeout of %ld ms.\n", + this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); } int From ad4aa69be1079f5c7c5dc5c302ac4670c5648c50 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 22:36:34 +0800 Subject: [PATCH 087/113] Make user.c independent of server for tests, add util.o to testobjs --- src/server.c | 22 +++++++++++----------- src/user.c | 14 ++++---------- src/user.h | 4 ++-- tests/Makefile | 2 +- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/server.c b/src/server.c index a00ef0e..a4040c9 100644 --- a/src/server.c +++ b/src/server.c @@ -828,7 +828,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri } if (userid < 0 || userid >= created_users || - check_authenticated_user_and_ip(userid, q) != 0) { + check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { DEBUG(2, "User %d not authenticated, ignoring raw login!", userid); return; } @@ -857,7 +857,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri static void handle_raw_data(uint8_t *packet, size_t len, struct query *q, int userid) { - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { return; } if (!users[userid].authenticated_raw) return; @@ -875,7 +875,7 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, int userid) static void handle_raw_ping(struct query *q, int dns_fd, int userid) { - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { return; } if (!users[userid].authenticated_raw) return; @@ -1218,7 +1218,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) userid = unpacked[0]; DEBUG(2, "Received login request for user %d from %s.", userid, format_addr(&q->from, q->fromlen)); - if (check_user_and_ip(userid, q) != 0) { + if (check_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); syslog(LOG_WARNING, "dropped login request from user #%d from %s; expected source %s", userid, format_addr(&q->from, q->fromlen), format_addr(&users[userid].host, users[userid].hostlen)); @@ -1257,7 +1257,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) int length; userid = b32_8to5(in[1]); - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } @@ -1297,7 +1297,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) userid = b32_8to5(in[1]); - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } @@ -1343,7 +1343,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) userid = b32_8to5(in[1]); - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } @@ -1482,7 +1482,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) read = unpack_data(unpacked, sizeof(unpacked), in + 1, 5, b32); userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } @@ -1519,7 +1519,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) /* Downstream fragsize packet */ userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } @@ -1550,7 +1550,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) /* Check userid */ userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal id */ } @@ -1628,7 +1628,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) userid = code; /* Check user and sending IP address */ - if (check_authenticated_user_and_ip(userid, q) != 0) { + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); return; /* illegal IP */ } diff --git a/src/user.c b/src/user.c index 98aca0a..972abc9 100644 --- a/src/user.c +++ b/src/user.c @@ -35,7 +35,6 @@ #include "encoding.h" #include "user.h" #include "window.h" -#include "server.h" struct tun_user *users; unsigned usercount; @@ -78,11 +77,6 @@ init_users(in_addr_t my_ip, int netbits) snprintf(newip, sizeof(newip), "0.0.0.%d", i + skip + 1); ip = ipstart.s_addr + inet_addr(newip); } - if (server.debug >= 2) { - struct in_addr IP; - IP.s_addr = ip; - DEBUG(2, "User %d: IP %s", i, inet_ntoa(IP)); - } users[i].tun_ip = ip; net.s_addr = ip; @@ -191,7 +185,7 @@ user_set_conn_type(int userid, enum connection c) /* This will not check that user has passed login challenge */ int -check_user_and_ip(int userid, struct query *q) +check_user_and_ip(int userid, struct query *q, int check_ip) { /* Note: duplicate in handle_raw_login() except IP-address check */ @@ -201,7 +195,7 @@ check_user_and_ip(int userid, struct query *q) if (!user_active(userid)) return 1; /* return early if IP checking is disabled */ - if (!server.check_ip) { + if (!check_ip) { return 0; } @@ -229,11 +223,11 @@ check_user_and_ip(int userid, struct query *q) } int -check_authenticated_user_and_ip(int userid, struct query *q) +check_authenticated_user_and_ip(int userid, struct query *q, int check_ip) /* This checks that user has passed normal (non-raw) login challenge * Returns 0 on success, 1 if user is not authenticated/IP is wrong */ { - int res = check_user_and_ip(userid, q); + int res = check_user_and_ip(userid, q, check_ip); if (res) return res; diff --git a/src/user.h b/src/user.h index 1b19f38..f59f79c 100644 --- a/src/user.h +++ b/src/user.h @@ -54,8 +54,8 @@ extern int created_users; int user_sending(int user); int all_users_waiting_to_send(); int user_active(int i); -int check_authenticated_user_and_ip(int userid, struct query *q); -int check_user_and_ip(int userid, struct query *q); +int check_authenticated_user_and_ip(int userid, struct query *q, int check_ip); +int check_user_and_ip(int userid, struct query *q, int check_ip); int init_users(in_addr_t, int); const char* users_get_first_ip(); diff --git a/tests/Makefile b/tests/Makefile index 6d35a7a..7977947 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ TEST = test OBJS = test.o base32.o base64.o common.o read.o dns.o encoding.o login.o user.o fw_query.o window.o -SRCOBJS = ../src/base32.o ../src/base64.o ../src/window.o ../src/common.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o ../src/fw_query.o +SRCOBJS = ../src/base32.o ../src/base64.o ../src/window.o ../src/common.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o ../src/fw_query.o ../src/util.o OS = `uname | tr "a-z" "A-Z"` From 150bd5dedcd5d5c7109fd852362f63be78963e66 Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 9 Jan 2016 22:37:41 +0800 Subject: [PATCH 088/113] Fix clang reinitialization warning --- src/iodine.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index 84bd666..5bf8785 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -77,8 +77,6 @@ struct client_instance this; #define PRESET_STATIC_VALUES \ .conn = CONN_DNS_NULL, \ .send_ping_soon = 1, \ - .downenc = ' ', \ - .do_qtype = T_UNSET, \ .maxfragsize_up = 100, \ .next_downstream_ack = -1, \ .num_immediate = 1, \ @@ -100,6 +98,8 @@ static struct client_instance preset_default = { .windowsize_up = 8, .windowsize_down = 8, .hostname_maxlen = 0xFF, + .downenc = ' ', + .do_qtype = T_UNSET, PRESET_STATIC_VALUES }; @@ -119,6 +119,8 @@ static struct client_instance preset_original = { .max_downstream_frag_size = MAX_FRAGSIZE, .compression_down = 1, .compression_up = 0, + .downenc = ' ', + .do_qtype = T_UNSET, PRESET_STATIC_VALUES }; @@ -138,6 +140,8 @@ static struct client_instance preset_fast = { .windowsize_up = 64, .windowsize_down = 32, .hostname_maxlen = 0xFF, + .downenc = ' ', + .do_qtype = T_UNSET, PRESET_STATIC_VALUES }; @@ -301,8 +305,8 @@ main(int argc, char **argv) #ifndef WINDOWS32 struct passwd *pw = NULL; #endif - int choice; - int retval; + int choice = -1; + int retval = 0; char *username = NULL; char *newroot = NULL; From 34e2285f22935b7a25b4627fd89c39a4e942307e Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 10 Jan 2016 15:11:31 +0800 Subject: [PATCH 089/113] Updated inline dotify test: TODO adjustible DNS max label length --- tests/encoding.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/encoding.c b/tests/encoding.c index e736406..70b348c 100644 --- a/tests/encoding.c +++ b/tests/encoding.c @@ -33,12 +33,12 @@ static struct tuple char *b; } dottests[] = { { "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaa"}, + "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaa"}, { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."}, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaa"}, { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaa"}, - { "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaa"}, + { "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrtsuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVW.XYZ" }, { NULL, NULL } }; From 07181e6a6a36d9fd79429e1fd78335c5272000f8 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 10 Jan 2016 22:00:52 +0800 Subject: [PATCH 090/113] Fixed format string warnings when compiling for 32 bit systems --- src/client.c | 26 +++++++++++++------------- src/common.h | 26 ++++++++++++++++++++++++++ src/server.c | 30 +++++++++++++++--------------- src/window.c | 24 ++++++++++++------------ 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/client.c b/src/client.c index e2ac39e..5d67c43 100644 --- a/src/client.c +++ b/src/client.c @@ -152,7 +152,7 @@ immediate_mode_defaults() #ifdef DEBUG_BUILD #define QTRACK_DEBUG(l, ...) \ if (this.debug >= l) {\ - TIMEPRINT("[QTRACK (%lu/%lu), ? %lu, TO %lu, S %lu/%lu] ", this.num_pending, PENDING_QUERIES_LENGTH, \ + TIMEPRINT("[QTRACK (%" L "u/%" L "u), ? %" L "u, TO %" L "u, S %" L "u/%" L "u] ", this.num_pending, PENDING_QUERIES_LENGTH, \ this.num_untracked, this.num_timeouts, window_sending(this.outbuf, NULL), this.outbuf->numitems); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ @@ -333,7 +333,7 @@ send_query(uint8_t *hostname) struct query q; size_t len; - DEBUG(3, "TX: pkt len %lu: hostname '%s'", strlen((char *)hostname), hostname); + DEBUG(3, "TX: pkt len %" L "u: hostname '%s'", strlen((char *)hostname), hostname); this.chunkid += 7727; if (this.chunkid == 0) @@ -529,7 +529,7 @@ send_next_frag() if (datacmc >= 36) datacmc = 0; - DEBUG(3, " SEND DATA: seq %d, ack %d, len %lu, s%d e%d c%d flags %1X", + DEBUG(3, " SEND DATA: seq %d, ack %d, len %" L "u, s%d e%d c%d flags %1X", f->seqID, f->ack_other, f->len, f->start, f->end, f->compressed, hdr[2] >> 4); id = send_query(buf); @@ -910,7 +910,7 @@ parse_data(uint8_t *data, size_t len, fragment *f, int *immediate) up_wsize = data[4]; dn_start_seq = data[5]; up_start_seq = data[6]; - DEBUG(3, "PING pkt data=%lu WS: up=%u, dn=%u; Start: up=%u, dn=%u", + DEBUG(3, "PING pkt data=%" L "u WS: up=%u, dn=%u; Start: up=%u, dn=%u", len - headerlen, up_wsize, dn_wsize, up_start_seq, dn_start_seq); } f->len = len - headerlen; @@ -931,7 +931,7 @@ tunnel_tun() if ((read = read_tun(this.tun_fd, in, sizeof(in))) <= 0) return -1; - DEBUG(2, " IN: %lu bytes on tunnel, to be compressed: %d", read, this.compression_up); + DEBUG(2, " IN: %" L "u bytes on tunnel, to be compressed: %d", read, this.compression_up); if (this.conn != CONN_DNS_NULL || this.compression_up) { datalen = sizeof(out); @@ -945,7 +945,7 @@ tunnel_tun() if (this.conn == CONN_DNS_NULL) { /* Check if outgoing buffer can hold data */ if (window_buffer_available(this.outbuf) < (read / MAX_FRAGSIZE) + 1) { - DEBUG(1, " Outgoing buffer full (%lu/%lu), not adding data!", + DEBUG(1, " Outgoing buffer full (%" L "u/%" L "u), not adding data!", this.outbuf->numitems, this.outbuf->length); return -1; } @@ -1002,14 +1002,14 @@ tunnel_dns() if (this.lazymode) { if (this.send_query_recvcnt < 500 && this.num_servfail < 4) { - fprintf(stderr, "Hmm, that's %ld SERVFAILs. Your data should still go through...\n", this.num_servfail); + fprintf(stderr, "Hmm, that's %" L "d SERVFAILs. Your data should still go through...\n", this.num_servfail); } else if (this.send_query_recvcnt < 500 && this.num_servfail >= 10 && this.autodetect_server_timeout && this.max_timeout_ms >= 500 && this.num_servfail % 5 == 0) { this.max_timeout_ms -= 200; double target_timeout = (float) this.max_timeout_ms / 1000.0; - fprintf(stderr, "Too many SERVFAILs (%ld), reducing timeout to" + fprintf(stderr, "Too many SERVFAILs (%" L "d), reducing timeout to" " %.1f secs. (use -I%.1f next time on this network)\n", this.num_servfail, target_timeout, target_timeout); @@ -1041,7 +1041,7 @@ tunnel_dns() if (read == 5 && !strncmp("BADIP", (char *)cbuf, 5)) { this.num_badip++; if (this.num_badip % 5 == 1) { - fprintf(stderr, "BADIP (%ld): Server rejected sender IP address (maybe iodined -c will help), or server " + fprintf(stderr, "BADIP (%" L "d): Server rejected sender IP address (maybe iodined -c will help), or server " "kicked us due to timeout. Will exit if no downstream data is received in 60 seconds.\n", this.num_badip); } return -1; /* nothing done */ @@ -1059,7 +1059,7 @@ tunnel_dns() got_response(q.id, immediate, 0); if ((this.debug >= 3 && res) || (this.debug >= 2 && !res)) - fprintf(stderr, " RX %s; frag ID %3u, ACK %3d, compression %d, datalen %lu, s%d e%d\n", + 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); @@ -1100,7 +1100,7 @@ tunnel_dns() if (compressed) { buflen = sizeof(buf); if ((res = uncompress(buf, &buflen, cbuf, datalen)) != Z_OK) { - DEBUG(1, "Uncompress failed (%d) for data len %lu: reassembled data corrupted or incomplete!", res, datalen); + DEBUG(1, "Uncompress failed (%d) for data len %" L "u: reassembled data corrupted or incomplete!", res, datalen); datalen = 0; } else { datalen = buflen; @@ -1189,7 +1189,7 @@ client_tunnel() sending--; total--; - QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %lu, will send another %d", + QTRACK_DEBUG(3, "Sent a query to fill server lazy buffer to %" L "u, will send another %d", this.lazymode ? this.windowsize_down : 1, total); if (sending > 0 || (total > 0 && this.lazymode)) { @@ -2409,7 +2409,7 @@ handshake_set_timeout() char in[4096]; int read, id; - fprintf(stderr, "Setting window sizes to %lu frags upstream, %lu frags downstream...\n", + fprintf(stderr, "Setting window sizes to %" L "u frags upstream, %" L "u frags downstream...\n", this.windowsize_up, this.windowsize_down); fprintf(stderr, "Calculating round-trip time..."); diff --git a/src/common.h b/src/common.h index 44266ad..56c6078 100644 --- a/src/common.h +++ b/src/common.h @@ -45,6 +45,32 @@ extern const unsigned char raw_header[RAW_HDR_LEN]; #define DNS_PORT 53 +#if _WIN32 || _WIN64 +#if _WIN64 +#define BITS_64 +#else +#define BITS_32 +#endif +#endif + +#if __GNUC__ +#if __x86_64__ || __ppc64__ +#define BITS_64 1 +#else +#define BITS_32 1 +#endif +#endif + +/* Determine appropriate format specifier for long int on 32/64 bit systems */ +#if BITS_64 +#define FMT_LONG "l" +#else +#define FMT_LONG "" +#endif + +/* For convenience and shortness */ +#define L FMT_LONG + #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif diff --git a/src/server.c b/src/server.c index a4040c9..3d6161b 100644 --- a/src/server.c +++ b/src/server.c @@ -103,7 +103,7 @@ send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr #define QMEM_DEBUG(l, u, ...) \ if (server.debug >= l) {\ - TIMEPRINT("[QMEM u%d (%lu/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ + TIMEPRINT("[QMEM u%d (%" L "u/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \ fprintf(stderr, __VA_ARGS__);\ fprintf(stderr, "\n");\ } @@ -184,7 +184,7 @@ qmem_append(int userid, struct query *q) buf->start = (buf->start + 1) % QMEM_LEN; } - QMEM_DEBUG(5, userid, "add query ID %d, timeout %lu ms", q->id, timeval_to_ms(&users[userid].dns_timeout)); + QMEM_DEBUG(5, userid, "add query ID %d, timeout %" L "u ms", q->id, timeval_to_ms(&users[userid].dns_timeout)); /* Copy query into buffer */ memcpy(&buf->queries[buf->end].q, q, sizeof(struct query)); @@ -314,7 +314,7 @@ qmem_max_wait(int *touser, struct query **sendq) q->id, age_ms, immediate ? "immediate" : "lazy", timeval_to_ms(&u->dns_timeout)); sent++; - QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %lu of %lu + sending another %lu", + 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); @@ -339,7 +339,7 @@ qmem_max_wait(int *touser, struct query **sendq) if (server.debug >= 5) { time_t soonest_ms = timeval_to_ms(&soonest); if (nextq && nextuser >= 0) { - QMEM_DEBUG(5, nextuser, "can wait for %lu ms, will send id %d", soonest_ms, nextq->id); + QMEM_DEBUG(5, nextuser, "can wait for %" L "u ms, will send id %d", soonest_ms, nextq->id); } else { if (nextuser < 0) nextuser = 0; @@ -347,7 +347,7 @@ qmem_max_wait(int *touser, struct query **sendq) /* only if resending some frags */ QMEM_DEBUG(5, nextuser, "Resending some fragments") } else { - QMEM_DEBUG(2, nextuser, "Don't need to send anything to any users, waiting %lu ms", soonest_ms); + QMEM_DEBUG(2, nextuser, "Don't need to send anything to any users, waiting %" L "u ms", soonest_ms); } } } @@ -479,7 +479,7 @@ send_data_or_ping(int userid, struct query *q, int ping, int immediate) if (datalen + headerlen > sizeof(pkt)) { /* Should never happen, or at least user should be warned about * fragsize > MAX_FRAGLEN earlier on */ - warnx("send_frag_or_dataless: fragment too large to send! (%lu)", datalen); + warnx("send_frag_or_dataless: fragment too large to send! (%" L "u)", datalen); return; } if (f) @@ -535,7 +535,7 @@ user_send_data(int userid, uint8_t *indata, size_t len, int compressed) datalen = sizeof(out); ret = uncompress(out, &datalen, indata, len); if (ret != Z_OK) { - DEBUG(1, "FAIL: Uncompress == %d: %lu bytes to user %d!", ret, len, userid); + DEBUG(1, "FAIL: Uncompress == %d: %" L "u bytes to user %d!", ret, len, userid); return 0; } } @@ -800,7 +800,7 @@ 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: %lu bytes from user %d (touser %d)", len, userid, touser); + 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); @@ -823,7 +823,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri char myhash[16]; if (len < 16) { - DEBUG(2, "Invalid raw login packet: length %lu < 16 bytes!", len); + DEBUG(2, "Invalid raw login packet: length %" L "u < 16 bytes!", len); return; } @@ -833,7 +833,7 @@ handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int useri return; } - DEBUG(1, "RX-raw: login, len %lu, from user %d", len, userid); + DEBUG(1, "RX-raw: login, len %" L "u, from user %d", len, userid); /* User sends hash of seed + 1 */ login_calculate(myhash, 16, server.password, users[userid].seed + 1); @@ -867,7 +867,7 @@ handle_raw_data(uint8_t *packet, size_t len, struct query *q, int userid) /* copy to packet buffer, update length */ - DEBUG(3, "RX-raw: full pkt raw, length %lu, from user %d", len, userid); + DEBUG(3, "RX-raw: full pkt raw, length %" L "u, from user %d", len, userid); handle_full_packet(userid, packet, len, 1); } @@ -904,7 +904,7 @@ raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd) raw_cmd = RAW_HDR_GET_CMD(packet); raw_user = RAW_HDR_GET_USR(packet); - DEBUG(3, "RX-raw: client %s, user %d, raw command 0x%02X, length %lu", + DEBUG(3, "RX-raw: client %s, user %d, raw command 0x%02X, length %" L "u", format_addr(&q->from, q->fromlen), raw_user, raw_cmd, len); packet += RAW_HDR_LEN; @@ -1122,7 +1122,7 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) return; } - DEBUG(3, "TX: client %s ID %5d, %lu bytes data, type %d, name '%10s'", + DEBUG(3, "TX: client %s ID %5d, %" L "u bytes data, type %d, name '%10s'", format_addr(&q->from, q->fromlen), q->id, datalen, q->type, q->name); sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); @@ -1149,7 +1149,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) memcpy(in, q->name, MIN(domain_len, sizeof(in))); - DEBUG(3, "NULL request length %d/%lu, command '%c'", domain_len, sizeof(in), in[0]); + DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(in), in[0]); if(in[0] == 'V' || in[0] == 'v') { /* Version request */ uint32_t version = !PROTOCOL_VERSION; @@ -1544,7 +1544,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); if (read < UPSTREAM_PING) { - DEBUG(1, "Invalid ping! Length %lu", read); + DEBUG(1, "Invalid ping! Length %" L "u", read); return; } diff --git a/src/window.c b/src/window.c index 72a0ad5..3fe3c09 100644 --- a/src/window.c +++ b/src/window.c @@ -155,7 +155,7 @@ window_process_incoming_fragment(struct frag_buffer *w, fragment *f) } /* Place fragment into correct location in buffer */ ssize_t dest = WRAP(w->window_start + SEQ_OFFSET(startid, f->seqID)); - WDEBUG(" Putting frag seq %u into frags[%lu + %u = %lu]", + WDEBUG(" Putting frag seq %u into frags[%" L "u + %u = %" L "u]", f->seqID, w->window_start, SEQ_OFFSET(startid, f->seqID), dest); /* Check if fragment already received */ @@ -192,7 +192,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int if (w->direction != WINDOW_RECVING) return 0; if (w->frags[w->chunk_start].start == 0 && w->numitems > 0) { - WDEBUG("chunk_start (%lu) pointing to non-start fragment (seq %u, len %lu)!", + WDEBUG("chunk_start (%" L "u) pointing to non-start fragment (seq %u, len %" L "u)!", w->chunk_start, w->frags[w->chunk_start].seqID, w->frags[w->chunk_start].len); return 0; } @@ -208,12 +208,12 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int f = &w->frags[woffs]; fraglen = f->len; if (fraglen == 0 || f->seqID != curseq) { - WDEBUG("Missing next frag %u [%lu], got seq %u (%lu bytes) instead! Not reassembling!", + WDEBUG("Missing next frag %u [%" L "u], got seq %u (%" L "u bytes) instead! Not reassembling!", curseq, woffs, f->seqID, fraglen); return 0; } - WDEBUG(" Fragment seq %u, data length %lu, data offset %lu, total len %lu, maxlen %lu", + WDEBUG(" Fragment seq %u, data length %" L "u, data offset %" L "u, total len %" L "u, maxlen %" L "u", f->seqID, fraglen, dest - data, datalen, maxlen); memcpy(dest, f->data, MIN(fraglen, maxlen)); dest += fraglen; @@ -225,7 +225,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int } } if (fraglen > maxlen) { - WDEBUG("Data buffer too small! Reassembled %lu bytes.", datalen); + WDEBUG("Data buffer too small! Reassembled %" L "u bytes.", datalen); return 0; } @@ -233,7 +233,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int window_tick(w); if (f->end == 1) { - WDEBUG("Found end of chunk! (seqID %u, chunk len %lu, datalen %lu)", f->seqID, i, datalen); + WDEBUG("Found end of chunk! (seqID %u, chunk len %" L "u, datalen %" L "u)", f->seqID, i, datalen); end = 1; break; } @@ -249,7 +249,7 @@ window_reassemble_data(struct frag_buffer *w, uint8_t *data, size_t maxlen, int return 0; } - WDEBUG("Reassembled %lu bytes from %lu frags; %scompressed!", datalen, i + 1, *compression ? "" : "un"); + WDEBUG("Reassembled %" L "u bytes from %" L "u frags; %scompressed!", datalen, i + 1, *compression ? "" : "un"); /* Clear all used fragments */ size_t p; ITER_FORWARD(w->chunk_start, WRAP(w->chunk_start + i + 1), w->length, p, @@ -340,7 +340,7 @@ window_get_next_sending_fragment(struct frag_buffer *w, int *other_ack) } } if (f) - WDEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %lu)", + WDEBUG("Not sending any fragments (last frag checked: retries %u, seqid %u, len %" L "u)", f->retries, f->seqID, f->len); return NULL; @@ -383,7 +383,7 @@ window_ack(struct frag_buffer *w, int seqid) if (f->acks > 0) WDEBUG("DUPE ACK: %d ACKs for seqId %u", f->acks, seqid); f->acks ++; - WDEBUG(" ACK frag seq %u, ACKs %u, len %lu, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); + WDEBUG(" ACK frag seq %u, ACKs %u, len %" L "u, s %u e %u", f->seqID, f->acks, f->len, f->start, f->end); break; } } @@ -400,7 +400,7 @@ window_tick(struct frag_buffer *w) unsigned old_start_id = w->start_seq_id; #endif w->start_seq_id = (w->start_seq_id + 1) % MAX_SEQ_ID; - WDEBUG("moving window forwards; %lu-%lu (%u) to %lu-%lu (%u) len=%lu", + WDEBUG("moving window forwards; %" L "u-%" L "u (%u) to %" L "u-%" L "u (%u) len=%" L "u", w->window_start, w->window_end, old_start_id, AFTER(w, 1), AFTER(w, w->windowsize + 1), w->start_seq_id, w->length); if (w->direction == WINDOW_SENDING) { @@ -429,7 +429,7 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c compressed &= 1; size_t offset = 0; static fragment f; - WDEBUG("add data len %lu, %lu frags, max fragsize %u", len, n, w->maxfraglen); + WDEBUG("add data len %" L "u, %" L "u frags, max fragsize %u", len, n, w->maxfraglen); for (size_t i = 0; i < n; i++) { memset(&f, 0, sizeof(f)); f.len = MIN(len - offset, w->maxfraglen); @@ -441,7 +441,7 @@ window_add_outgoing_data(struct frag_buffer *w, uint8_t *data, size_t len, int c f.ack_other = -1; window_append_fragment(w, &f); w->cur_seq_id = (w->cur_seq_id + 1) % MAX_SEQ_ID; - WDEBUG(" fragment len %lu, seqID %u, s %u, end %u, dOffs %lu", f.len, f.seqID, f.start, f.end, offset); + WDEBUG(" fragment len %" L "u, seqID %u, s %u, end %u, dOffs %" L "u", f.len, f.seqID, f.start, f.end, offset); offset += f.len; } return n; From ea062a0525d37e11e446fea06c3dfd87c721e6ed Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 10 Jan 2016 22:07:50 +0800 Subject: [PATCH 091/113] Fixed stats format warnings --- src/client.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client.c b/src/client.c index 5d67c43..bcad2c4 100644 --- a/src/client.c +++ b/src/client.c @@ -1220,21 +1220,21 @@ client_tunnel() if (difftime(time(NULL), last_stats) >= this.stats) { /* print useful statistics report */ fprintf(stderr, "\n============ iodine connection statistics (user %1d) ============\n", this.userid); - fprintf(stderr, " Queries sent: %8lu" ", answered: %8lu" ", SERVFAILs: %4lu\n", + fprintf(stderr, " Queries sent: %8" L "u" ", answered: %8" L "u" ", SERVFAILs: %4" L "u\n", this.num_sent, this.num_recv, this.num_servfail); - fprintf(stderr, " last %3d secs: %7lu" " (%4lu/s), replies: %7lu" " (%4lu/s)\n", + fprintf(stderr, " last %3d secs: %7" L "u" " (%4" L "u/s), replies: %7" L "u" " (%4" L "u/s)\n", this.stats, this.num_sent - sent_since_report, (this.num_sent - sent_since_report) / this.stats, this.num_recv - recv_since_report, (this.num_recv - recv_since_report) / this.stats); - fprintf(stderr, " num IP rejected: %4lu, untracked: %4lu, lazy mode: %1d\n", + fprintf(stderr, " num IP rejected: %4" L "u, untracked: %4" L "u, lazy mode: %1d\n", this.num_badip, this.num_untracked, this.lazymode); - fprintf(stderr, " Min send: %5ld ms, Avg RTT: %5ld ms Timeout server: %4ld ms\n", + fprintf(stderr, " Min send: %5" L "d ms, Avg RTT: %5" L "d ms Timeout server: %4" L "d ms\n", this.min_send_interval_ms, this.rtt_total_ms / this.num_immediate, this.server_timeout_ms); - fprintf(stderr, " Queries immediate: %5lu, timed out: %4lu target: %4ld ms\n", + fprintf(stderr, " Queries immediate: %5" L "u, timed out: %4" L "u target: %4" L "d ms\n", this.num_immediate, this.num_timeouts, this.max_timeout_ms); if (this.conn == CONN_DNS_NULL) { - fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4ld ms\n", + fprintf(stderr, " Frags resent: %4u, OOS: %4u down frag: %4" L "d ms\n", this.outbuf->resends, this.inbuf->oos, this.downstream_timeout_ms); - fprintf(stderr, " TX fragments: %8lu" ", RX: %8lu" ", pings: %8lu" "\n\n", + fprintf(stderr, " TX fragments: %8" L "u" ", RX: %8" L "u" ", pings: %8" L "u" "\n\n", this.num_frags_sent, this.num_frags_recv, this.num_pings); } /* update since-last-report this.stats */ From 2850b6043d1ebea2c0c9eebfbdb23fbb2ee20538 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 11 Jan 2016 21:01:14 +0800 Subject: [PATCH 092/113] Added long option support for iodined --- src/iodined.c | 68 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/iodined.c b/src/iodined.c index e307d73..67732b4 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "common.h" #include "version.h" @@ -186,31 +187,31 @@ static void help() { fprintf(stderr, "iodine IP over DNS tunneling server\n"); print_usage(); - fprintf(stderr, " -v to print version info and exit\n"); - fprintf(stderr, " -h to print this help and exit\n"); - fprintf(stderr, " -4 to listen only on IPv4\n"); - fprintf(stderr, " -6 to listen only on IPv6\n"); - fprintf(stderr, " -c to disable check of client IP/port on each request\n"); - fprintf(stderr, " -s to skip creating and configuring the tun device, " + fprintf(stderr, " -v, --version print version info and exit\n"); + fprintf(stderr, " -h, --help print this help and exit\n"); + fprintf(stderr, " -4 listen only on IPv4\n"); + fprintf(stderr, " -6 listen only on IPv6\n"); + fprintf(stderr, " -c, --noipcheck disable check of client IP/port on each request\n"); + fprintf(stderr, " -s, --notun skip creating and configuring the tun device, " "which then has to be created manually\n"); - fprintf(stderr, " -f to keep running in foreground\n"); - fprintf(stderr, " -D to increase debug level\n"); + fprintf(stderr, " -f to keep running in foreground\n"); + fprintf(stderr, " -D increase debug level\n"); fprintf(stderr, " (using -DD in UTF-8 terminal: \"LC_ALL=C luit iodined -DD ...\")\n"); - fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); - fprintf(stderr, " -t dir to chroot to directory dir\n"); - fprintf(stderr, " -d device to set tunnel device name\n"); - fprintf(stderr, " -m mtu to set tunnel device mtu\n"); - fprintf(stderr, " -z context to apply SELinux context after initialization\n"); - fprintf(stderr, " -l IPv4 address to listen on for incoming dns traffic " + fprintf(stderr, " -u, --user drop privileges and run as user\n"); + fprintf(stderr, " -t, --chrootdir chroot to directory after init\n"); + fprintf(stderr, " -d specify tunnel device name\n"); + fprintf(stderr, " -m, --mtu specify tunnel device mtu\n"); + fprintf(stderr, " -z, --context apply SELinux context after initialization\n"); + fprintf(stderr, " -l, --listen4 IPv4 address to listen on for incoming dns traffic " "(default 0.0.0.0)\n"); - fprintf(stderr, " -L IPv6 address to listen on for incoming dns traffic " + fprintf(stderr, " -L, --listen6 IPv6 address to listen on for incoming dns traffic " "(default ::)\n"); - fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); - fprintf(stderr, " -n ip to respond with to NS queries\n"); - fprintf(stderr, " -b port to forward normal DNS queries to (on localhost)\n"); - fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); - fprintf(stderr, " -F pidfile to write pid to a file\n"); - fprintf(stderr, " -i maximum idle time before shutting down\n"); + fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); + fprintf(stderr, " -n, --nsip ip to respond with to NS queries\n"); + fprintf(stderr, " -b, --forwardto forward normal DNS queries to a UDP port on localhost\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + fprintf(stderr, " -F, --pidfile write pid to a file\n"); + fprintf(stderr, " -i, --idlequit maximum idle time before shutting down\n"); fprintf(stderr, "tunnel_ip is the IP number of the local tunnel interface.\n"); fprintf(stderr, " /netmask sets the size of the tunnel network.\n"); fprintf(stderr, "topdomain is the FQDN that is delegated to this server.\n"); @@ -279,7 +280,6 @@ main(int argc, char **argv) listen_ip4 = NULL; listen_ip6 = NULL; - ns_get_externalip = 0; skipipconfig = 0; pidfile = NULL; @@ -302,9 +302,31 @@ main(int argc, char **argv) // Load default values from preset memcpy(&server, &preset_default, sizeof(struct server_instance)); + /* each option has format: + char *name, int has_arg, int *flag, int val */ + static struct option iodined_args[] = { + {"version", no_argument, 0, 'v'}, + {"noipcheck", no_argument, 0, 'c'}, + {"notun", no_argument, 0, 's'}, + {"user", required_argument, 0, 'u'}, + {"listen4", required_argument, 0, 'l'}, + {"listen6", required_argument, 0, 'L'}, + {"nsip", required_argument, 0, 'n'}, + {"mtu", required_argument, 0, 'm'}, + {"idlequit", required_argument, 0, 'i'}, + {"forwardto", required_argument, 0, 'b'}, + {"help", no_argument, 0, 'h'}, + {"context", required_argument, 0, 'z'}, + {"chrootdir", required_argument, 0, 't'}, + {"pidfile", required_argument, 0, 'F'}, + {NULL, 0, 0, 0} + }; + + static char *iodined_args_short = "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:"; + server.running = 1; - while ((choice = getopt(argc, argv, "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { + while ((choice = getopt_long(argc, argv, iodined_args_short, iodined_args, NULL)) != -1) { switch(choice) { case '4': server.addrfamily = AF_INET; From 4b4f73d11e398634db187e5d11bb770b25ffc668 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 11 Jan 2016 21:02:56 +0800 Subject: [PATCH 093/113] Fixed duplicate unrecognised command line option error --- src/iodine.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/iodine.c b/src/iodine.c index 5bf8785..6b95d76 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -366,6 +366,9 @@ main(int argc, char **argv) } } } + } else if (choice == '?') { + usage(); + /* Not reached */ } else { /* skip all other options until we find preset */ continue; From ae55020b03e2d89ceb0d8b80e303509c6b077510 Mon Sep 17 00:00:00 2001 From: frekky Date: Mon, 18 Jan 2016 18:20:21 +0800 Subject: [PATCH 094/113] Fix sed regex for making base64u.c for compatibility with OS X --- src/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 68dd326..7bfe8d4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,11 +44,11 @@ base64u.o client.o iodined.o: base64u.h base64u.c: base64.c @echo Making $@ @echo '/* No use in editing, produced by Makefile! */' > $@ - @sed -e 's/\([Bb][Aa][Ss][Ee]64\|[Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/\([Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ base64u.h: base64.h @echo Making $@ @echo '/* No use in editing, produced by Makefile! */' > $@ - @sed -e 's/\([Bb][Aa][Ss][Ee]64\|[Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/\([Bb]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ clean: @echo "Cleaning src/" From 88b11bffeb5dfc377496d3ff32a9e1ef4f356781 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 22 Jan 2016 21:55:52 +0800 Subject: [PATCH 095/113] Cleaned up handle_null_request, some protocol changes --- src/server.c | 1049 ++++++++++++++++++++++++++------------------------ 1 file changed, 539 insertions(+), 510 deletions(-) diff --git a/src/server.c b/src/server.c index 3d6161b..7ff296a 100644 --- a/src/server.c +++ b/src/server.c @@ -420,10 +420,7 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s break; } - out[4] = ((payload >> 24) & 0xff); - out[5] = ((payload >> 16) & 0xff); - out[6] = ((payload >> 8) & 0xff); - out[7] = ((payload) & 0xff); + *(uint32_t *) (out + 4) = htonl(payload); out[8] = userid & 0xff; write_dns(fd, q, out, sizeof(out), users[userid].downenc); @@ -1128,333 +1125,106 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); } +#define CHECK_LEN(l, x) \ + if (l < x) { \ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); \ + return; \ + } + void -handle_null_request(int dns_fd, struct query *q, int domain_len) -/* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ +handle_dns_version(int dns_fd, struct query *q, int domain_len) { - struct in_addr tempip; - uint8_t in[512]; - char logindata[16]; - uint8_t out[64*1024]; - static uint8_t unpacked[64*1024]; - char *tmp[2]; - int userid; - size_t read; + uint8_t unpacked[512]; + uint32_t version = !PROTOCOL_VERSION; + int userid, read; - userid = -1; + read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)q->name + 1, domain_len - 1, b32); + printf("unpacked: %08X, %08X, %08X\n", *(uint32_t *) unpacked, *((uint32_t *) unpacked), ntohl(*((uint32_t *) unpacked))); + /* Version greeting, compare and send ack/nak */ + if (read >= 4) { + /* Received V + 32bits version (network byte order) */ + version = ntohl(*(uint32_t *) unpacked); + } /* if invalid pkt, just send VNAK */ - /* Everything here needs at least two chars in the name */ - if (domain_len < 2) + if (version != PROTOCOL_VERSION) { + send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q); + syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", + format_addr(&q->from, q->fromlen), version); return; + } - memcpy(in, q->name, MIN(domain_len, sizeof(in))); - - DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(in), in[0]); - - if(in[0] == 'V' || in[0] == 'v') { /* Version request */ - uint32_t version = !PROTOCOL_VERSION; - - read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); - /* Version greeting, compare and send ack/nak */ - if (read > 4) { - /* Received V + 32bits version (network byte order) */ - version = ntohl(*(uint32_t *) unpacked); - } /* if invalid pkt, just send VNAK */ - - if (version == PROTOCOL_VERSION) { - userid = find_available_user(); - if (userid >= 0) { - struct tun_user *u = &users[userid]; - u->seed = rand(); - /* Store remote IP number */ - memcpy(&(u->host), &(q->from), q->fromlen); - u->hostlen = q->fromlen; - - u->encoder = get_base32_encoder(); - - if (q->type == T_NULL || q->type == T_PRIVATE) { - u->downenc = 'R'; - u->downenc_bits = 8; - } else { - u->downenc = 'T'; - u->downenc_bits = 5; - } - u->down_compression = 1; - send_version_response(dns_fd, VERSION_ACK, u->seed, userid, q); - syslog(LOG_INFO, "Accepted version for user #%d from %s", - userid, format_addr(&q->from, q->fromlen)); - u->fragsize = 100; /* very safe */ - u->conn = CONN_DNS_NULL; - u->lazy = 0; - // TODO: client specified window size - u->outgoing->maxfraglen = u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR; - window_buffer_clear(u->outgoing); - window_buffer_clear(u->incoming); - u->next_upstream_ack = -1; - qmem_init(userid); - - DEBUG(1, "User %d connected with correct version from %s.", - userid, format_addr(&q->from, q->fromlen)); - } else { - /* No space for another user */ - send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); - syslog(LOG_INFO, "dropped user from %s, server full", - format_addr(&q->from, q->fromlen)); - } - } else { - send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q); - syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", - format_addr(&q->from, q->fromlen), version); - } + userid = find_available_user(); + if (userid < 0) { + /* No space for another user */ + send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); + syslog(LOG_INFO, "dropped user from %s, server full", + format_addr(&q->from, q->fromlen)); return; - } else if (in[0] == 'L' || in[0] == 'l') { /* Login request */ - read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); - if (read < 17) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } + } - /* Login phase, handle auth */ - userid = unpacked[0]; - DEBUG(2, "Received login request for user %d from %s.", - userid, format_addr(&q->from, q->fromlen)); - if (check_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - syslog(LOG_WARNING, "dropped login request from user #%d from %s; expected source %s", - userid, format_addr(&q->from, q->fromlen), format_addr(&users[userid].host, users[userid].hostlen)); - return; - } else { - users[userid].last_pkt = time(NULL); - login_calculate(logindata, 16, server.password, users[userid].seed); + struct tun_user *u = &users[userid]; + u->seed = rand(); + /* Store remote IP number */ + memcpy(&(u->host), &(q->from), q->fromlen); + u->hostlen = q->fromlen; + u->fragsize = 100; /* very safe */ + u->conn = CONN_DNS_NULL; + u->encoder = get_base32_encoder(); + u->down_compression = 1; + u->lazy = 0; + u->next_upstream_ack = -1; + u->outgoing->maxfraglen = u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR; + window_buffer_clear(u->outgoing); + window_buffer_clear(u->incoming); + qmem_init(userid); - if (read >= 18 && (memcmp(logindata, unpacked + 1, 16) == 0)) { - /* Store login ok */ - users[userid].authenticated = 1; + if (q->type == T_NULL || q->type == T_PRIVATE) { + u->downenc = 'R'; + u->downenc_bits = 8; + } else { + u->downenc = 'T'; + u->downenc_bits = 5; + } - /* Send ip/mtu/netmask info */ - tempip.s_addr = server.my_ip; - tmp[0] = strdup(inet_ntoa(tempip)); - tempip.s_addr = users[userid].tun_ip; - tmp[1] = strdup(inet_ntoa(tempip)); + send_version_response(dns_fd, VERSION_ACK, u->seed, userid, q); - read = snprintf((char *)out, sizeof(out), "%s-%s-%d-%d", - tmp[0], tmp[1], server.mtu, server.netmask); + syslog(LOG_INFO, "Accepted version for user #%d from %s", + userid, format_addr(&q->from, q->fromlen)); - write_dns(dns_fd, q, (char *)out, read, users[userid].downenc); - syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); + DEBUG(1, "User %d connected with correct version from %s.", + userid, format_addr(&q->from, q->fromlen)); +} - free(tmp[1]); - free(tmp[0]); - } else { - write_dns(dns_fd, q, "LNAK", 4, 'T'); - syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password", - userid, format_addr(&q->from, q->fromlen)); - } - } +void +handle_dns_downstream_codec_check(int dns_fd, struct query *q, int domain_len) +{ + int codec; + char *datap; + int datalen; + uint8_t unpacked[10]; + + unpack_data(unpacked, sizeof(unpacked), (uint8_t *)q->name + 2, MIN(domain_len - 2, 4), b32); + + switch (unpacked[0]) { /* check variant */ + case 1: + datap = DOWNCODECCHECK1; + datalen = DOWNCODECCHECK1_LEN; + break; + default: + write_dns(dns_fd, q, "BADLEN", 6, 'T'); return; - } else if(in[0] == 'I' || in[0] == 'i') { /* IP address request */ - char reply[17]; - int length; + } - userid = b32_8to5(in[1]); - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - reply[0] = 'I'; - if (q->from.ss_family == AF_INET) { - if (server.ns_ip != INADDR_ANY) { - /* If set, use assigned external ip (-n option) */ - memcpy(&reply[1], &server.ns_ip, sizeof(server.ns_ip)); - } else { - /* otherwise return destination ip from packet */ - struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; - memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr)); - } - length = 1 + sizeof(struct in_addr); - } else { - struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; - memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr)); - length = 1 + sizeof(struct in6_addr); - } - - write_dns(dns_fd, q, reply, length, 'T'); - } else if(in[0] == 'Z' || in[0] == 'z') { /* Upstream codec check */ - /* Check for case conservation and chars not allowed according to RFC */ - - /* Reply with received hostname as data */ - /* No userid here, reply with lowest-grade downenc */ - write_dns(dns_fd, q, (char *)in, domain_len, 'T'); - return; - } else if(in[0] == 'S' || in[0] == 's') { /* Switch upstream codec */ - int codec; - struct encoder *enc; - if (domain_len < 3) { /* len at least 3, example: "S15" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - userid = b32_8to5(in[1]); - - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - codec = b32_8to5(in[2]); - - switch (codec) { - case 5: /* 5 bits per byte = base32 */ - enc = b32; - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 6: /* 6 bits per byte = base64 */ - enc = b64; - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ - enc = b64u; - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - case 7: /* 7 bits per byte = base128 */ - enc = b128; - user_switch_codec(userid, enc); - write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - break; - } - return; - } else if(in[0] == 'O' || in[0] == 'o') { /* Protocol options */ - int bits = 0; - int numopts; - char *opts; - - int tmp_lazy, tmp_downenc, tmp_comp; - if (domain_len < 7) { /* len at least 7, example: "oa1tcmc" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - userid = b32_8to5(in[1]); - - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - numopts = in[2] - '0'; - - if (domain_len < numopts + 6 || numopts == 0 || numopts > 9) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; /* invalid packet */ - } - - /* Temporary variables: don't change anything until all options parsed */ - tmp_lazy = users[userid].lazy; - tmp_comp = users[userid].down_compression; - tmp_downenc = users[userid].downenc; - - opts = (char *) in + 3; - - for (int i = 0; i < numopts; i++) { - switch (toupper(opts[i])) { - case 'T': - tmp_downenc = 'T'; - bits = 5; - break; - case 'S': - tmp_downenc = 'S'; - bits = 6; - break; - case 'U': - tmp_downenc = 'U'; - bits = 6; - break; - case 'V': - tmp_downenc = 'V'; - bits = 7; - break; - case 'R': - tmp_downenc = 'R'; - bits = 8; - break; - case 'L': - tmp_lazy = 1; - break; - case 'I': - tmp_lazy = 0; - break; - case 'C': - tmp_comp = 1; - break; - case 'D': - tmp_comp = 0; - break; - default: - write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); - return; - } - } - - /* Automatically switch to raw encoding if PRIVATE or NULL request */ - if ((q->type == T_NULL || q->type == T_PRIVATE) && !bits) { - users[userid].downenc = 'R'; - bits = 8; - DEBUG(2, "Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid); - } - if (bits) { - int f = users[userid].fragsize; - users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; - users[userid].downenc_bits = bits; - } - - DEBUG(1, "Options for user %d: down compression %d, data bits %d/maxlen %u (enc '%c'), lazy %d.", - userid, tmp_comp, bits, users[userid].outgoing->maxfraglen, tmp_downenc, tmp_lazy); - - /* Store any changes */ - users[userid].down_compression = tmp_comp; - users[userid].downenc = tmp_downenc; - users[userid].lazy = tmp_lazy; - - write_dns(dns_fd, q, opts, numopts, users[userid].downenc); - return; - } else if(in[0] == 'Y' || in[0] == 'y') { /* Downstream codec check */ - int i; - char *datap; - int datalen; - - if (domain_len < 6) { /* len at least 6, example: "YTxCMC" */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - i = b32_8to5(in[2]); /* check variant */ - - switch (i) { - case 1: - datap = DOWNCODECCHECK1; - datalen = DOWNCODECCHECK1_LEN; - break; - default: - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - switch (toupper(in[1])) { + codec = toupper(q->name[1]); + switch (codec) { case 'T': case 'S': case 'U': case 'V': if (q->type == T_TXT || - q->type == T_SRV || q->type == T_MX || - q->type == T_CNAME || q->type == T_A) { - write_dns(dns_fd, q, datap, datalen, toupper(in[1])); + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, codec); return; } break; @@ -1464,217 +1234,476 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) return; } break; - } + } - /* if still here, then codec not available */ - write_dns(dns_fd, q, "BADCODEC", 8, 'T'); + /* if still here, then codec not available */ + write_dns(dns_fd, q, "BADCODEC", 8, 'T'); +} + +void +handle_dns_login(int dns_fd, struct query *q, int domain_len, int userid) +{ + uint8_t unpacked[512], flags; + char logindata[16], *tmp[2], out[512]; + struct in_addr tempip; + char remote_tcp, remote_isnt_localhost, use_ipv6, drop_packets; + int length = 18, read; + + read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *) q->name, domain_len, b32); + + /* Decode flags and calculate min. length */ + flags = unpacked[1]; + remote_tcp = flags & 1; + remote_isnt_localhost = (flags & 2) >> 1; + use_ipv6 = (flags & 4) >> 2; + drop_packets = (flags & 8) >> 3; + + length += (remote_tcp ? 2 : 0) + (remote_isnt_localhost ? (use_ipv6 ? 16 : 4) : 0); + + CHECK_LEN(read, length); + + DEBUG(2, "Received login request for user %d from %s.", + userid, format_addr(&q->from, q->fromlen)); + + if (check_user_and_ip(userid, q, server.check_ip) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + syslog(LOG_WARNING, "dropped login request from user #%d from %s; expected source %s", + userid, format_addr(&q->from, q->fromlen), format_addr(&users[userid].host, users[userid].hostlen)); return; + } else { + users[userid].last_pkt = time(NULL); + login_calculate(logindata, 16, server.password, users[userid].seed); - } else if(in[0] == 'R' || in[0] == 'r') { /* Downstream fragsize probe */ - int req_frag_size; + if (read >= 18 && (memcmp(logindata, unpacked + 1, 16) == 0)) { + /* Store login ok, count number of auth attempts */ + users[userid].authenticated++; + if (users[userid].authenticated > 1) + syslog(LOG_WARNING, "duplicate login request from user #%d from %s", + userid, format_addr(&users[userid].host, users[userid].hostlen)); - if (domain_len < 16) { /* we'd better have some chars for data... */ - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } + /* Send ip/mtu/netmask info */ + tempip.s_addr = server.my_ip; + tmp[0] = strdup(inet_ntoa(tempip)); + tempip.s_addr = users[userid].tun_ip; + tmp[1] = strdup(inet_ntoa(tempip)); - /* Downstream fragsize probe packet */ - read = unpack_data(unpacked, sizeof(unpacked), in + 1, 5, b32); + read = snprintf(out, sizeof(out), "%c-%s-%s-%d-%d", b32_5to8(flags), + tmp[0], tmp[1], server.mtu, server.netmask); - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } + write_dns(dns_fd, q, out, read, users[userid].downenc); + syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); - req_frag_size = ntohs(*(uint16_t *) (unpacked + 1)); - DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size); - - if (req_frag_size < 2 || req_frag_size > 2047) { - write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + free(tmp[1]); + free(tmp[0]); } else { - char buf[2048]; - int i; - unsigned int v = ((unsigned int) rand()) & 0xff; - - memset(buf, 0, sizeof(buf)); - buf[0] = (req_frag_size >> 8) & 0xff; - buf[1] = req_frag_size & 0xff; - /* make checkable pseudo-random sequence */ - buf[2] = 107; - for (i = 3; i < 2048; i++, v = (v + 107) & 0xff) - buf[i] = v; - write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); + write_dns(dns_fd, q, "LNAK", 4, 'T'); + if (--users[userid].authenticated >= 0) + users[userid].authenticated = -1; + syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password; incorrect attempts: %d", + userid, format_addr(&q->from, q->fromlen), abs(users[userid].authenticated)); } - return; - } else if(in[0] == 'N' || in[0] == 'n') { /* Downstream fragsize */ - int max_frag_size; - - read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); - - if (read < 3) { - write_dns(dns_fd, q, "BADLEN", 6, 'T'); - return; - } - - /* Downstream fragsize packet */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - max_frag_size = ntohs(*(uint16_t *)(unpacked + 1)); - if (max_frag_size < 2) { - write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); - } else { - users[userid].fragsize = max_frag_size; - users[userid].outgoing->maxfraglen = (users[userid].downenc_bits * max_frag_size) / - 8 - DOWNSTREAM_PING_HDR; - write_dns(dns_fd, q, (char *) unpacked + 1, 2, users[userid].downenc); - - DEBUG(1, "Setting max downstream data length to %u bytes for user %d; %d bits (%c)", - users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); - } - return; - } else if(in[0] == 'P' || in[0] == 'p') { /* Ping request */ - int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack; - int respond, set_qtimeout, set_wtimeout; - unsigned qtimeout_ms, wtimeout_ms; - - read = unpack_data(unpacked, sizeof(unpacked), in + 1, domain_len - 1, b32); - if (read < UPSTREAM_PING) { - DEBUG(1, "Invalid ping! Length %" L "u", read); - return; - } - - /* Check userid */ - userid = unpacked[0]; - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal id */ - } - - /* Check if cached */ - if (qmem_is_cached(dns_fd, userid, q)) - return; - - 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]; - - /* 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; - - DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, %swtime %u ms, respond %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]); - - if (set_qtimeout) { - /* update user's query timeout if timeout flag set */ - users[userid].dns_timeout = ms_to_timeval(qtimeout_ms); - - /* if timeout is 0, we do not enable lazy mode but it is effectively the same */ - int newlazy = !(qtimeout_ms == 0); - if (newlazy != users[userid].lazy) - DEBUG(2, "User %d: not setting lazymode to %d with timeout %u", - userid, newlazy, qtimeout_ms); - } - - if (set_wtimeout) { - /* update sending window fragment ACK timeout */ - users[userid].outgoing->timeout = ms_to_timeval(wtimeout_ms); - } - - qmem_append(userid, q); - - if (respond) { - /* ping handshake - set windowsizes etc, respond NOW using this query - * NOTE: not added to qmem */ - DEBUG(2, "PING HANDSHAKE set windowsizes (old/new) up: %d/%d, dn: %d/%d", - 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); - return; - } - - user_process_incoming_data(userid, dn_ack); - - /* if respond flag not set, query waits in qmem and is used later */ - } else if (isxdigit(in[0])) { /* Upstream data packet */ - int code = 0; - static fragment f; - size_t len; - - /* Need 6 char header + >=1 char data */ - if (domain_len < UPSTREAM_HDR + 1) - return; - - if ((in[0] >= '0' && in[0] <= '9')) - code = in[0] - '0'; - if ((in[0] >= 'a' && in[0] <= 'f')) - code = in[0] - 'a' + 10; - if ((in[0] >= 'A' && in[0] <= 'F')) - code = in[0] - 'A' + 10; - - userid = code; - /* Check user and sending IP address */ - if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { - write_dns(dns_fd, q, "BADIP", 5, 'T'); - return; /* illegal IP */ - } - - /* Check if cached */ - if (qmem_is_cached(dns_fd, userid, q)) { - /* if is cached, by this point it has already been answered */ - return; - } - - qmem_append(userid, q); - /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ - /* First byte (after userid) = CMC (ignored); skip 2 bytes */ - len = sizeof(unpacked); - read = b32->decode(unpacked, &len, in + 2, 5); - - f.seqID = unpacked[0]; - unpacked[2] >>= 4; /* Lower 4 bits are unused */ - f.ack_other = ((unpacked[2] >> 3) & 1) ? unpacked[1] : -1; - f.compressed = (unpacked[2] >> 2) & 1; - f.start = (unpacked[2] >> 1) & 1; - f.end = unpacked[2] & 1; - - /* Decode remainder of data with user encoding into fragment */ - f.len = unpack_data(f.data, MAX_FRAGSIZE, in + UPSTREAM_HDR, - domain_len - UPSTREAM_HDR, users[userid].encoder); - - DEBUG(3, "frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", - f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end); - - /* if already waiting for an ACK to be sent back upstream (on incoming buffer) */ - if (users[userid].next_upstream_ack >= 0) { - /* Shouldn't normally happen; will always be reset after sending a packet. */ - DEBUG(1, "[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); - } - - window_process_incoming_fragment(users[userid].incoming, &f); - users[userid].next_upstream_ack = f.seqID; - - user_process_incoming_data(userid, f.ack_other); - - /* Nothing to do. ACK for this fragment is sent later in qmem_max_wait, - * using an old query. This is left in qmem until needed/times out */ } } +void +handle_dns_ip_request(int dns_fd, struct query *q, int domain_len, int userid) +{ + char reply[17]; + int length; + reply[0] = 'I'; + if (q->from.ss_family == AF_INET) { + if (server.ns_ip != INADDR_ANY) { + /* If set, use assigned external ip (-n option) */ + memcpy(&reply[1], &server.ns_ip, sizeof(server.ns_ip)); + } else { + /* otherwise return destination ip from packet */ + struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination; + memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr)); + } + length = 1 + sizeof(struct in_addr); + } else { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination; + memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr)); + length = 1 + sizeof(struct in6_addr); + } + + write_dns(dns_fd, q, reply, length, 'T'); +} + +void +handle_dns_upstream_codec_switch(int dns_fd, struct query *q, int domain_len, int userid, + uint8_t *unpacked, size_t read) +{ + int codec; + struct encoder *enc; + + codec = unpacked[0]; + + switch (codec) { + case 5: /* 5 bits per byte = base32 */ + enc = b32; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 6: /* 6 bits per byte = base64 */ + enc = b64; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ + enc = b64u; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 7: /* 7 bits per byte = base128 */ + enc = b128; + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + break; + } +} + +void +handle_dns_set_options(int dns_fd, struct query *q, int domain_len, int userid, + uint8_t *unpacked, size_t read) +{ + uint8_t bits = 0; + char *encname = "BADCODEC"; + + int tmp_lazy, tmp_downenc, tmp_comp; + + /* Temporary variables: don't change anything until all options parsed */ + tmp_lazy = users[userid].lazy; + tmp_comp = users[userid].down_compression; + tmp_downenc = users[userid].downenc; + + switch (unpacked[0] & 0x7C) { + case (1 << 6): /* Base32 */ + tmp_downenc = 'T'; + encname = "Base32"; + bits = 5; + break; + case (1 << 5): /* Base64 */ + tmp_downenc = 'S'; + encname = "Base64"; + bits = 6; + break; + case (1 << 4): /* Base64u */ + tmp_downenc = 'U'; + encname = "Base64u"; + bits = 26; + break; + case (1 << 3): /* Base128 */ + tmp_downenc = 'V'; + encname = "Base128"; + bits = 7; + break; + case (1 << 2): /* Raw */ + tmp_downenc = 'R'; + encname = "Raw"; + bits = 8; + break; + default: /* Invalid (More than 1 encoding bit set) */ + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + return; + } + + tmp_comp = (unpacked[0] & 2) >> 1; /* compression flag */ + tmp_lazy = (unpacked[0] & 1); /* lazy mode flag */ + + /* Automatically switch to raw encoding if PRIVATE or NULL request */ + if ((q->type == T_NULL || q->type == T_PRIVATE) && !bits) { + users[userid].downenc = 'R'; + bits = 8; + DEBUG(2, "Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid); + } + if (bits) { + int f = users[userid].fragsize; + users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR; + users[userid].downenc_bits = bits; + } + + DEBUG(1, "Options for user %d: down compression %d, data bits %d/maxlen %u (enc '%c'), lazy %d.", + userid, tmp_comp, bits, users[userid].outgoing->maxfraglen, tmp_downenc, tmp_lazy); + + /* Store any changes */ + users[userid].down_compression = tmp_comp; + users[userid].downenc = tmp_downenc; + users[userid].lazy = tmp_lazy; + + write_dns(dns_fd, q, encname, strlen(encname), users[userid].downenc); +} + +void +handle_dns_fragsize_probe(int dns_fd, struct query *q, int domain_len, int userid, + uint8_t *unpacked, size_t read) +/* Downstream fragsize probe packet */ +{ + int req_frag_size; + + /* There should be some data in the query */ + CHECK_LEN(domain_len, 16); + + req_frag_size = ntohs(*(uint16_t *) (unpacked + 1)); + DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size); + + if (req_frag_size < 2 || req_frag_size > MAX_FRAGSIZE) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + char buf[MAX_FRAGSIZE]; + int i; + unsigned int v = ((unsigned int) rand()) & 0xff; + + memset(buf, 0, sizeof(buf)); + buf[0] = (req_frag_size >> 8) & 0xff; + buf[1] = req_frag_size & 0xff; + /* make checkable pseudo-random sequence */ + buf[2] = 107; + for (i = 3; i < MAX_FRAGSIZE; i++, v = (v + 107) & 0xff) + buf[i] = v; + write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); + } +} + +void +handle_dns_set_fragsize(int dns_fd, struct query *q, int domain_len, int userid, + uint8_t *unpacked, size_t read) + /* Downstream fragsize packet */ +{ + int max_frag_size; + max_frag_size = ntohs(*(uint16_t *)unpacked); + + if (max_frag_size < 2 || max_frag_size > MAX_FRAGSIZE) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + users[userid].fragsize = max_frag_size; + users[userid].outgoing->maxfraglen = (users[userid].downenc_bits * max_frag_size) / + 8 - DOWNSTREAM_PING_HDR; + write_dns(dns_fd, q, (char *)unpacked, 2, users[userid].downenc); + + DEBUG(1, "Setting max downstream data length to %u bytes for user %d; %d bits (%c)", + users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc); + } +} + +void +handle_dns_ping(int dns_fd, struct query *q, int domain_len, 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; + unsigned qtimeout_ms, wtimeout_ms; + + CHECK_LEN(read, UPSTREAM_PING); + + /* Check if query is cached */ + if (qmem_is_cached(dns_fd, userid, q)) + 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]; + + /* 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; + + DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, %swtime %u ms, respond %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]); + + if (set_qtimeout) { + /* update user's query timeout if timeout flag set */ + users[userid].dns_timeout = ms_to_timeval(qtimeout_ms); + + /* if timeout is 0, we do not enable lazy mode but it is effectively the same */ + int newlazy = !(qtimeout_ms == 0); + if (newlazy != users[userid].lazy) + DEBUG(2, "User %d: not changing lazymode to %d with timeout %u", + userid, newlazy, qtimeout_ms); + } + + if (set_wtimeout) { + /* update sending window fragment ACK timeout */ + users[userid].outgoing->timeout = ms_to_timeval(wtimeout_ms); + } + + qmem_append(userid, q); + + if (respond) { + /* ping handshake - set windowsizes etc, respond NOW using this query + * NOTE: still added to qmem (for cache) even though responded to immediately */ + DEBUG(2, "PING HANDSHAKE set windowsizes (old/new) up: %d/%d, dn: %d/%d", + 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); + return; + } + + /* if respond flag not set, query waits in qmem and is used later */ + user_process_incoming_data(userid, dn_ack); +} + +void +handle_dns_data(int dns_fd, struct query *q, int domain_len, int userid) +{ + uint8_t unpacked[512]; + static fragment f; + size_t len; + + /* Need 6 char header + >=1 char data */ + CHECK_LEN(domain_len, UPSTREAM_HDR + 1); + + /* Check if cached */ + if (qmem_is_cached(dns_fd, userid, q)) { + /* if is cached, by this point it has already been answered */ + return; + } + + qmem_append(userid, q); + /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ + /* First byte (after userid) = CMC (ignored); skip 2 bytes */ + len = sizeof(unpacked); + b32->decode(unpacked, &len, (uint8_t *)q->name + 2, 5); + + f.seqID = unpacked[0]; + unpacked[2] >>= 4; /* Lower 4 bits are unused */ + f.ack_other = ((unpacked[2] >> 3) & 1) ? unpacked[1] : -1; + f.compressed = (unpacked[2] >> 2) & 1; + f.start = (unpacked[2] >> 1) & 1; + f.end = unpacked[2] & 1; + + /* Decode remainder of data with user encoding into fragment */ + f.len = unpack_data(f.data, MAX_FRAGSIZE, (uint8_t *)q->name + UPSTREAM_HDR, + domain_len - UPSTREAM_HDR, users[userid].encoder); + + DEBUG(3, "frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", + f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end); + + /* if already waiting for an ACK to be sent back upstream (on incoming buffer) */ + if (users[userid].next_upstream_ack >= 0) { + /* Shouldn't normally happen; will always be reset after sending a packet. */ + DEBUG(1, "[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid); + } + + window_process_incoming_fragment(users[userid].incoming, &f); + users[userid].next_upstream_ack = f.seqID; + + user_process_incoming_data(userid, f.ack_other); + + /* Nothing to do. ACK for this fragment is sent later in qmem_max_wait, + * using an old query. This is left in qmem until needed/times out */ +} + +void +handle_null_request(int dns_fd, struct query *q, int domain_len) +/* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */ +{ + char cmd, userchar; + int userid = -1; + + /* Everything here needs at least 5 chars in the name: + * cmd, userid and more data or at least 3 bytes CMC */ + if (domain_len < 5) + return; + + cmd = toupper(q->name[0]); + DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(q->name), cmd); + + /* Commands that do not care about userid */ + if (cmd == 'V') { /* Version check - before userid is assigned*/ + handle_dns_version(dns_fd, q, domain_len); + return; + } + else if (cmd == 'Z') { /* Upstream codec check - user independent */ + /* Check for case conservation and chars not allowed according to RFC */ + + /* Reply with received hostname as data (encoded in base32) */ + write_dns(dns_fd, q, q->name, domain_len, 'T'); + return; + } + else if (cmd == 'Y') { /* Downstream codec check - user independent*/ + handle_dns_downstream_codec_check(dns_fd, q, domain_len); + return; + } + + /* Get userid from query (always 2nd byte in hex except for data packets) */ + if (isxdigit(cmd)) { + /* Upstream data packet - first byte is userid in hex */ + userchar = cmd; + cmd = 'd'; /* flag for data packet - not part of protocol */ + } else { + userchar = toupper(q->name[1]); + } + + if (isxdigit(userchar)) { + userid = (userchar >= 'A' && userchar <= 'F') ? + (userchar - 'A' + 10) : (userchar - '0'); + } else { + /* Invalid user ID or bad DNS query */ + write_dns(dns_fd, q, "BADLEN", 5, 'T'); + } + + /* Login request - after version check successful, do not check auth yet */ + if (cmd == 'L') { + handle_dns_login(dns_fd, q, domain_len, userid); + return; + } + + /* Check user IP and authentication status */ + if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; + } + + if (cmd == 'd') { /* Upstream data packet */ + handle_dns_data(dns_fd, q, domain_len, userid); + return; + } else if (cmd == 'I') { /* IP request packet - no base32 data */ + handle_dns_ip_request(dns_fd, q, domain_len, userid); + } + + /* Following commands have everything after cmd and userid in base32 + * All bytes that are not valid base32 are decoded to 0 */ + + uint8_t unpacked[512]; + size_t raw_len; + raw_len = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)q->name + 2, domain_len - 2, b32); + if (raw_len < 3) /* always at least 3 bytes after decoding at least 5 bytes */ + return; /* Just in case. */ + + switch (cmd) { + case 'S': + handle_dns_upstream_codec_switch(dns_fd, q, domain_len, userid, unpacked, raw_len); + break; + case 'O': + handle_dns_set_options(dns_fd, q, domain_len, userid, unpacked, raw_len); + break; + case 'R': + handle_dns_fragsize_probe(dns_fd, q, domain_len, userid, unpacked, raw_len); + break; + case 'N': + handle_dns_set_fragsize(dns_fd, q, domain_len, userid, unpacked, raw_len); + break; + case 'P': + handle_dns_ping(dns_fd, q, domain_len, userid, unpacked, raw_len); + break; + default: + DEBUG(2, "Invalid DNS query! cmd = %c, hostname = '%*s'", + cmd, domain_len, q->name); + } +} void handle_ns_request(int dns_fd, struct query *q) From 5ceb6312b4644ed3377396df3c8035ec53bce6c2 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 22 Jan 2016 21:56:22 +0800 Subject: [PATCH 096/113] Modified auth checking to count login attempts --- src/user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user.c b/src/user.c index 972abc9..e66ceb8 100644 --- a/src/user.c +++ b/src/user.c @@ -231,7 +231,7 @@ check_authenticated_user_and_ip(int userid, struct query *q, int check_ip) if (res) return res; - if (!users[userid].authenticated) + if (!(users[userid].authenticated >= 1)) return 1; return 0; From 5233c2ab1e33704f526cf0f3caf11d4e5b87feb2 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 22 Jan 2016 21:57:33 +0800 Subject: [PATCH 097/113] Cleaned up client send_packet functions, protocol changes --- src/client.c | 305 +++++++++++++++++++++++---------------------------- 1 file changed, 135 insertions(+), 170 deletions(-) diff --git a/src/client.c b/src/client.c index bcad2c4..c46eb27 100644 --- a/src/client.c +++ b/src/client.c @@ -426,13 +426,22 @@ send_raw_data(uint8_t *data, size_t datalen) static int send_packet(char cmd, const uint8_t *data, const size_t datalen) /* Base32 encodes data and sends as single DNS query + * cmd becomes first byte of query, followed by hex userid, encoded + * data and 3 bytes base32 encoded CMC * Returns ID of sent query */ { - uint8_t buf[4096]; + uint8_t buf[512], data_with_cmc[datalen + 2]; + + if (data) + memcpy(data_with_cmc, data, datalen); + *(uint16_t *) (data_with_cmc + datalen) = this.rand_seed; + this.rand_seed++; buf[0] = cmd; + buf[1] = this.userid_char; - build_hostname(buf, sizeof(buf), data, datalen, this.topdomain, b32, this.hostname_maxlen, 1); + build_hostname(buf, sizeof(buf), data_with_cmc, datalen + 2, + this.topdomain, b32, this.hostname_maxlen, 2); return send_query(buf); } @@ -442,27 +451,26 @@ send_ping(int ping_response, int ack, int set_timeout) { this.num_pings++; if (this.conn == CONN_DNS_NULL) { - uint8_t data[13]; + uint8_t data[12]; int id; /* Build ping header (see doc/proto_xxxxxxxx.txt) */ - data[0] = this.userid; - data[1] = ack & 0xFF; + data[0] = ack & 0xFF; if (this.outbuf && this.inbuf) { - data[2] = this.outbuf->windowsize & 0xff; /* Upstream window size */ - data[3] = this.inbuf->windowsize & 0xff; /* Downstream window size */ - data[4] = this.outbuf->start_seq_id & 0xff; /* Upstream window start */ - data[5] = this.inbuf->start_seq_id & 0xff; /* Downstream window start */ + data[1] = this.outbuf->windowsize & 0xff; /* Upstream window size */ + data[2] = this.inbuf->windowsize & 0xff; /* Downstream window size */ + data[3] = this.outbuf->start_seq_id & 0xff; /* Upstream window start */ + data[4] = this.inbuf->start_seq_id & 0xff; /* Downstream window start */ } - *(uint16_t *) (data + 6) = htons(this.server_timeout_ms); - *(uint16_t *) (data + 8) = htons(this.downstream_timeout_ms); + *(uint16_t *) (data + 5) = htons(this.server_timeout_ms); + *(uint16_t *) (data + 7) = htons(this.downstream_timeout_ms); /* update server frag/lazy timeout, ack flag, respond with ping flag */ - data[10] = ((set_timeout & 1) << 4) | ((set_timeout & 1) << 3) | ((ack < 0 ? 0 : 1) << 2) | (ping_response & 1); - data[11] = (this.rand_seed >> 8) & 0xff; - data[12] = (this.rand_seed >> 0) & 0xff; + data[9] = ((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", @@ -1309,116 +1317,17 @@ client_tunnel() return rv; } -static void -send_login(char *login, int len) -{ - uint8_t data[19]; - - memset(data, 0, sizeof(data)); - data[0] = this.userid; - memcpy(&data[1], login, MIN(len, 16)); - - data[17] = (this.rand_seed >> 8) & 0xff; - data[18] = (this.rand_seed >> 0) & 0xff; - - this.rand_seed++; - - send_packet('l', data, sizeof(data)); -} - -static void -send_fragsize_probe(uint16_t fragsize) -{ - uint8_t probedata[256]; - uint8_t buf[MAX_FRAGSIZE]; - uint8_t hdr[3]; - size_t hdr_len_enc = 6; - - buf[0] = 'r'; /* Probe downstream fragsize packet */ - - hdr[0] = this.userid; - *(uint16_t *) (hdr + 1) = htons(fragsize); - - b32->encode(buf + 1, &hdr_len_enc, hdr, 3); - /* build a large query domain which is random and maximum size, - * will also take up maximum space in the return packet */ - memset(probedata, MAX(1, this.rand_seed & 0xff), sizeof(probedata)); - probedata[1] = MAX(1, (this.rand_seed >> 8) & 0xff); - this.rand_seed++; - - /* Note: must either be same, or larger, than send_chunk() */ - build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), this.topdomain, - this.dataenc, this.hostname_maxlen, 6); - - send_query(buf); -} - -static void -send_set_downstream_fragsize(uint16_t fragsize) -{ - uint8_t data[5]; - - data[0] = this.userid; - *(uint16_t *) (data + 1) = htons(fragsize); - data[3] = (this.rand_seed >> 8) & 0xff; - data[4] = (this.rand_seed >> 0) & 0xff; - - this.rand_seed++; - - send_packet('n', data, sizeof(data)); -} - -static void -send_version(uint32_t version) -{ - uint8_t data[6]; - - version = htonl(version); - *(uint32_t *) data = version; - - data[4] = (this.rand_seed >> 8) & 0xff; - data[5] = (this.rand_seed >> 0) & 0xff; - - this.rand_seed++; - - send_packet('v', data, sizeof(data)); -} - -static void -send_ip_request() -{ - uint8_t buf[512] = "i____."; - buf[1] = b32_5to8(this.userid); - - buf[2] = b32_5to8((this.rand_seed >> 10) & 0x1f); - buf[3] = b32_5to8((this.rand_seed >> 5) & 0x1f); - buf[4] = b32_5to8((this.rand_seed ) & 0x1f); - this.rand_seed++; - - strncat((char *)buf, this.topdomain, 512 - strlen((char *)buf)); - send_query(buf); -} - -static void -send_raw_udp_login(int seed) -{ - char buf[16]; - login_calculate(buf, 16, this.password, seed + 1); - - send_raw((uint8_t *) buf, sizeof(buf), RAW_HDR_CMD_LOGIN); -} - static void send_upenctest(char *s) /* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */ { - char buf[512] = "z___"; + char buf[512] = "z___."; + size_t buf_space = 3; - buf[1] = b32_5to8((this.rand_seed >> 10) & 0x1f); - buf[2] = b32_5to8((this.rand_seed >> 5) & 0x1f); - buf[3] = b32_5to8((this.rand_seed ) & 0x1f); + b32->encode((uint8_t *)buf + 1, &buf_space,(uint8_t *)&this.rand_seed, sizeof(this.rand_seed)); this.rand_seed++; + /* Append test string without changing it */ strncat(buf, s, 512 - strlen(buf)); strncat(buf, ".", 512 - strlen(buf)); strncat(buf, this.topdomain, 512 - strlen(buf)); @@ -1426,59 +1335,110 @@ send_upenctest(char *s) } static void -send_downenctest(char downenc, int variant, char *s, int slen) -/* Note: content/handling of s is not defined yet. */ +send_downenctest(char downenc, int variant) { - char buf[512] = "y_____."; + uint8_t buf[512] = "y_____.", hdr[3]; buf[1] = tolower(downenc); - buf[2] = b32_5to8(variant); - buf[3] = b32_5to8((this.rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((this.rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((this.rand_seed ) & 0x1f); + hdr[0] = variant; + *(uint16_t *) (hdr + 1) = this.rand_seed; this.rand_seed++; - strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query((uint8_t *)buf); + build_hostname(buf, sizeof(buf), hdr, sizeof(hdr), + this.topdomain, b32, this.hostname_maxlen, 2); + + send_query(buf); } static void -send_codec_switch(int bits) +send_version(uint32_t version) { - char buf[512] = "s_____."; - buf[1] = b32_5to8(this.userid); - buf[2] = b32_5to8(bits); + uint8_t data[4], buf[512]; - buf[3] = b32_5to8((this.rand_seed >> 10) & 0x1f); - buf[4] = b32_5to8((this.rand_seed >> 5) & 0x1f); - buf[5] = b32_5to8((this.rand_seed ) & 0x1f); - this.rand_seed++; + *(uint32_t *) data = htonl(version); - strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query((uint8_t *)buf); + buf[0] = 'v'; + + build_hostname(buf, sizeof(buf), data, sizeof(data), + this.topdomain, b32, this.hostname_maxlen, 1); + + send_query(buf); } static void -send_server_options(int lazy, int compression, char denc, char *options) -/* Options must be length >=4 */ +send_login(char *login, int len) { - char buf[512] = "oU3___CMC."; - buf[1] = b32_5to8(this.userid); + send_packet('l', (uint8_t *) login, len); +} - options[0] = tolower(denc); - options[1] = lazy ? 'l' : 'i'; - options[2] = compression ? 'c' : 'd'; - options[3] = 0; - strncpy(buf + 3, options, 3); +static void +send_fragsize_probe(uint16_t fragsize) +{ + uint8_t data[256]; - buf[6] = b32_5to8((this.rand_seed >> 10) & 0x1f); - buf[7] = b32_5to8((this.rand_seed >> 5) & 0x1f); - buf[8] = b32_5to8((this.rand_seed) & 0x1f); + /* Probe downstream fragsize packet */ + + /* build a large query domain which is random and maximum size, + * will also take up maximum space in the return packet */ + memset(data, MAX(1, this.rand_seed & 0xff), sizeof(data)); + + *(uint16_t *) (data) = htons(fragsize); this.rand_seed++; - strncat(buf, this.topdomain, 512 - strlen(buf)); - send_query((uint8_t *)buf); + send_packet('r', data, sizeof(data)); +} + +static void +send_set_downstream_fragsize(uint16_t fragsize) +{ + uint8_t data[2]; + *(uint16_t *) data = htons(fragsize); + + send_packet('n', data, sizeof(data)); +} + +static void +send_ip_request() +{ + send_packet('i', NULL, 0); +} + +static void +send_raw_udp_login(int seed) +{ + char buf[16]; + login_calculate(buf, sizeof(buf), this.password, seed + 1); + + send_raw((uint8_t *) buf, sizeof(buf), RAW_HDR_CMD_LOGIN); +} + +static void +send_codec_switch(uint8_t bits) +{ + send_packet('s', &bits, 1); +} + +static void +send_server_options(int lazy, int compression, char denc) +{ + uint8_t optflags = 0; + + if (denc == 'T') /* Base32 */ + optflags |= 1 << 6; + else if (denc == 'S') /* Base64 */ + optflags |= 1 << 5; + else if (denc == 'U') /* Base64u */ + optflags |= 1 << 4; + else if (denc == 'V') /* Base128 */ + optflags |= 1 << 3; + else if (denc == 'R') /* Raw */ + optflags |= 1 << 2; + + optflags |= (compression & 1) << 1; + optflags |= lazy & 1; + + send_packet('o', &optflags, 1); } static int @@ -1498,25 +1458,27 @@ handshake_version(int *seed) read = handshake_waitdns(in, sizeof(in), 'V', i+1); if (read >= 9) { - payload = (((in[4] & 0xff) << 24) | - ((in[5] & 0xff) << 16) | - ((in[6] & 0xff) << 8) | - ((in[7] & 0xff))); + payload = ntohl(*(uint32_t *) (in + 4)); if (strncmp("VACK", (char *)in, 4) == 0) { + /* Payload is login challenge */ *seed = payload; this.userid = in[8]; this.userid_char = hex[this.userid & 15]; this.userid_char2 = hex2[this.userid & 15]; + DEBUG(2, "Login challenge: 0x%08x", *seed); + fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", PROTOCOL_VERSION, this.userid); return 0; } else if (strncmp("VNAK", (char *)in, 4) == 0) { + /* Payload is server version */ warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up", PROTOCOL_VERSION, payload); return 1; } else if (strncmp("VFUL", (char *)in, 4) == 0) { + /* Payload is max number of users on server */ warnx("Server full, all %d slots are taken. Try again later", payload); return 1; } @@ -1547,11 +1509,12 @@ handshake_login(int seed) send_login(login, 16); read = handshake_waitdns(in, sizeof(in), 'L', i+1); + in[MIN(read, sizeof(in))] = 0; /* Null terminate */ if (read > 0) { int netmask; if (strncmp("LNAK", in, 4) == 0) { - fprintf(stderr, "Bad this.password\n"); + fprintf(stderr, "Bad password\n"); return 1; } else if (sscanf(in, "%64[^-]-%64[^-]-%d-%d", server, client, &mtu, &netmask) == 4) { @@ -1567,7 +1530,7 @@ handshake_login(int seed) errx(4, "Failed to set IP and MTU"); } } else { - fprintf(stderr, "Received bad handshake\n"); + fprintf(stderr, "Received bad handshake: %.*s\n", read, in); } } @@ -1682,9 +1645,7 @@ handshake_upenctest(char *s) char in[4096]; unsigned char *uin = (unsigned char *) in; unsigned char *us = (unsigned char *) s; - int i; - int read; - int slen; + int i, read, slen; slen = strlen(s); for (i=0; this.running && i<3 ;i++) { @@ -1850,7 +1811,7 @@ handshake_downenctest(char trycodec) for (i=0; this.running && i<3 ;i++) { - send_downenctest(trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1); read = handshake_waitdns(in, sizeof(in), 'Y', i+1); @@ -1950,7 +1911,7 @@ handshake_qtypetest(int timeout) byte values can be returned, which is needed for NULL/PRIVATE to work. */ - send_downenctest(trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1); read = handshake_waitdns(in, sizeof(in), 'Y', timeout); @@ -2073,7 +2034,7 @@ handshake_edns0_check() for (i=0; this.running && i<3 ;i++) { - send_downenctest(trycodec, 1, NULL, 0); + send_downenctest(trycodec, 1); read = handshake_waitdns(in, sizeof(in), 'Y', i+1); @@ -2162,10 +2123,9 @@ codec_revert: void handshake_switch_options(int lazy, int compression, char denc) { - char in[4096]; + char in[100]; int read; char *dname, *comp_status, *lazy_status; - char opts[4]; comp_status = compression ? "enabled" : "disabled"; @@ -2185,11 +2145,13 @@ handshake_switch_options(int lazy, int compression, char denc) lazy_status, dname, comp_status); for (int i = 0; this.running && i < 5; i++) { - send_server_options(lazy, compression, denc, opts); + send_server_options(lazy, compression, denc); read = handshake_waitdns(in, sizeof(in) - 1, 'O', i + 1); if (read > 0) { + in[read] = 0; /* zero terminate */ + if (strncmp("BADLEN", in, 6) == 0) { fprintf(stderr, "Server got bad message length.\n"); goto opt_revert; @@ -2199,12 +2161,15 @@ handshake_switch_options(int lazy, int compression, char denc) } else if (strncmp("BADCODEC", in, 8) == 0) { fprintf(stderr, "Server rejected the selected options.\n"); goto opt_revert; + } else if (strcasecmp(dname, in) == 0) { + fprintf(stderr, "Switched server options, using downsteam codec %s.\n", in); + this.lazymode = lazy; + this.compression_down = compression; + this.downenc = denc; + return; + } else { + fprintf(stderr, "Got invalid response. "); } - fprintf(stderr, "Switched server options successfully. (%s)\n", opts); - this.lazymode = lazy; - this.compression_down = compression; - this.downenc = denc; - return; } fprintf(stderr, "Retrying options switch...\n"); From ffcd9da9801c8c4bafd98cdfeac85d7d4ec3d376 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 29 Jan 2016 22:12:05 +0800 Subject: [PATCH 098/113] Added client support for negotiating TCP forward options --- src/client.c | 72 +++++++++++++++++++++++++++++++++++++--------------- src/client.h | 5 ++++ 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/client.c b/src/client.c index c46eb27..8ab06ba 100644 --- a/src/client.c +++ b/src/client.c @@ -1321,13 +1321,14 @@ static void send_upenctest(char *s) /* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */ { - char buf[512] = "z___."; - size_t buf_space = 3; + char buf[512] = "zCMC"; + size_t buf_space = 10; + uint32_t cmc = rand(); - b32->encode((uint8_t *)buf + 1, &buf_space,(uint8_t *)&this.rand_seed, sizeof(this.rand_seed)); - this.rand_seed++; + b32->encode((uint8_t *)buf + 1, &buf_space, (uint8_t *) &cmc, 4); /* Append test string without changing it */ + strncat(buf, ".", 512 - strlen(buf)); strncat(buf, s, 512 - strlen(buf)); strncat(buf, ".", 512 - strlen(buf)); strncat(buf, this.topdomain, 512 - strlen(buf)); @@ -1337,13 +1338,12 @@ send_upenctest(char *s) static void send_downenctest(char downenc, int variant) { - uint8_t buf[512] = "y_____.", hdr[3]; + uint8_t buf[512] = "y_____.", hdr[5]; - buf[1] = tolower(downenc); + buf[1] = downenc; hdr[0] = variant; - *(uint16_t *) (hdr + 1) = this.rand_seed; - this.rand_seed++; + *(uint32_t *) (hdr + 1) = rand(); build_hostname(buf, sizeof(buf), hdr, sizeof(hdr), this.topdomain, b32, this.hostname_maxlen, 2); @@ -1354,9 +1354,10 @@ send_downenctest(char downenc, int variant) static void send_version(uint32_t version) { - uint8_t data[4], buf[512]; + uint8_t data[8], buf[512]; *(uint32_t *) data = htonl(version); + *(uint32_t *) (data + 4) = (uint32_t) rand(); /* CMC */ buf[0] = 'v'; @@ -1369,7 +1370,41 @@ send_version(uint32_t version) static void send_login(char *login, int len) { - send_packet('l', (uint8_t *) login, len); + uint8_t flags = 0, data[100]; + int length = 17, addrlen = 0; + + if (len != 16) + DEBUG(1, "Login calculated incorrect length hash! len=%d", len); + + memcpy(data + 1, login, 16); + + if (this.remote_forward_port > 0) { + flags |= 1; + *(uint16_t *) (data + length) = (uint16_t) this.remote_forward_port; + length += 2; + /* set remote IP to be non-localhost if this.remote_forward_addr set */ + if (this.remote_forward_addr_len) { + if (this.remote_forward_addr.ss_family == AF_INET6) { /* IPv6 address */ + addrlen = sizeof(struct in6_addr); + flags |= 4; + memcpy(data + length, &((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_addr, addrlen); + } else { /* IPv4 address */ + flags |= 2; + addrlen = sizeof(struct in_addr); + memcpy(data + length, &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr, addrlen); + } + length += addrlen; + } + DEBUG(2, "Sending TCP forward login request: port %d, length %d, addr %d", + this.remote_forward_port, length, addrlen); + } + + data[0] = flags; + + DEBUG(6, "Sending login request: length=%d, flags=0x%02x, hash=0x%016llx%016llx", + length, flags, *(unsigned long long *) (data + 1), *(unsigned long long *) (data + 9)); + + send_packet('l', data, length); } static void @@ -1494,21 +1529,16 @@ handshake_version(int *seed) static int handshake_login(int seed) { - char in[4096]; - char login[16]; - char server[65]; - char client[65]; - int mtu; - int i; - int read; + char in[4096], login[16], server[65], client[65], flag; + int mtu, read; login_calculate(login, 16, this.password, seed); - for (i=0; this.running && i<5 ;i++) { + for (int i = 0; this.running && i < 5; i++) { send_login(login, 16); - read = handshake_waitdns(in, sizeof(in), 'L', i+1); + read = handshake_waitdns(in, sizeof(in), 'L', i + 1); in[MIN(read, sizeof(in))] = 0; /* Null terminate */ if (read > 0) { @@ -1516,8 +1546,8 @@ handshake_login(int seed) if (strncmp("LNAK", in, 4) == 0) { fprintf(stderr, "Bad password\n"); return 1; - } else if (sscanf(in, "%64[^-]-%64[^-]-%d-%d", - server, client, &mtu, &netmask) == 4) { + } else if (sscanf(in, "%c-%64[^-]-%64[^-]-%d-%d", + &flag, server, client, &mtu, &netmask) == 4) { server[64] = 0; client[64] = 0; diff --git a/src/client.h b/src/client.h index 1456073..65027f7 100644 --- a/src/client.h +++ b/src/client.h @@ -44,6 +44,11 @@ struct client_instance { int raw_serv_len; char *topdomain; + /* Remote TCP forwarding stuff (for -R) */ + struct sockaddr_storage remote_forward_addr; + socklen_t remote_forward_addr_len; /* 0 if connecting to localhost */ + int remote_forward_port; /* 0 if no forwarding used */ + int tun_fd; int dns_fd; From 0c33c81e3db204fe15f8639155cf816cf6c78105 Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 29 Jan 2016 22:13:42 +0800 Subject: [PATCH 099/113] Added partial server support for TCP forward negotiation + login --- src/server.c | 218 +++++++++++++++++++++++++++++++++------------------ src/server.h | 3 + src/user.h | 4 + 3 files changed, 150 insertions(+), 75 deletions(-) diff --git a/src/server.c b/src/server.c index 7ff296a..f150612 100644 --- a/src/server.c +++ b/src/server.c @@ -1132,14 +1132,13 @@ write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc) } void -handle_dns_version(int dns_fd, struct query *q, int domain_len) +handle_dns_version(int dns_fd, struct query *q, uint8_t *domain, int domain_len) { uint8_t unpacked[512]; uint32_t version = !PROTOCOL_VERSION; int userid, read; - read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)q->name + 1, domain_len - 1, b32); - printf("unpacked: %08X, %08X, %08X\n", *(uint32_t *) unpacked, *((uint32_t *) unpacked), ntohl(*((uint32_t *) unpacked))); + read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)domain + 1, domain_len - 1, b32); /* Version greeting, compare and send ack/nak */ if (read >= 4) { /* Received V + 32bits version (network byte order) */ @@ -1162,11 +1161,15 @@ handle_dns_version(int dns_fd, struct query *q, int domain_len) return; } + /* Reset user options to safe defaults */ struct tun_user *u = &users[userid]; u->seed = rand(); /* Store remote IP number */ memcpy(&(u->host), &(q->from), q->fromlen); u->hostlen = q->fromlen; + u->remote_forward_connected = 0; + u->remote_port = 0; + u->remoteforward_addr_len = 0; u->fragsize = 100; /* very safe */ u->conn = CONN_DNS_NULL; u->encoder = get_base32_encoder(); @@ -1193,17 +1196,18 @@ handle_dns_version(int dns_fd, struct query *q, int domain_len) DEBUG(1, "User %d connected with correct version from %s.", userid, format_addr(&q->from, q->fromlen)); + DEBUG(3, "User %d has login challenge 0x%08x", userid, u->seed); } void -handle_dns_downstream_codec_check(int dns_fd, struct query *q, int domain_len) +handle_dns_downstream_codec_check(int dns_fd, struct query *q, uint8_t *domain, int domain_len) { int codec; char *datap; int datalen; uint8_t unpacked[10]; - unpack_data(unpacked, sizeof(unpacked), (uint8_t *)q->name + 2, MIN(domain_len - 2, 4), b32); + unpack_data(unpacked, sizeof(unpacked), (uint8_t *)domain + 2, MIN(domain_len - 2, 4), b32); switch (unpacked[0]) { /* check variant */ case 1: @@ -1215,7 +1219,7 @@ handle_dns_downstream_codec_check(int dns_fd, struct query *q, int domain_len) return; } - codec = toupper(q->name[1]); + codec = toupper(domain[1]); switch (codec) { case 'T': case 'S': @@ -1241,72 +1245,134 @@ handle_dns_downstream_codec_check(int dns_fd, struct query *q, int domain_len) } void -handle_dns_login(int dns_fd, struct query *q, int domain_len, int userid) +handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid) { uint8_t unpacked[512], flags; - char logindata[16], *tmp[2], out[512]; + char logindata[16], *tmp[2], out[512], *reason = NULL; struct in_addr tempip; char remote_tcp, remote_isnt_localhost, use_ipv6, drop_packets; - int length = 18, read; + int length = 17, read, addrlen, login_ok = 1; - read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *) q->name, domain_len, b32); + read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *) domain + 2, domain_len - 2, b32); /* Decode flags and calculate min. length */ - flags = unpacked[1]; + flags = unpacked[0]; remote_tcp = flags & 1; remote_isnt_localhost = (flags & 2) >> 1; use_ipv6 = (flags & 4) >> 2; - drop_packets = (flags & 8) >> 3; + drop_packets = (flags & 8) >> 3; /* currently unimplemented */ + addrlen = (remote_tcp && remote_isnt_localhost) ? (use_ipv6 ? 16 : 4) : 0; - length += (remote_tcp ? 2 : 0) + (remote_isnt_localhost ? (use_ipv6 ? 16 : 4) : 0); + length += (remote_tcp ? 2 : 0) + addrlen; CHECK_LEN(read, length); DEBUG(2, "Received login request for user %d from %s.", userid, format_addr(&q->from, q->fromlen)); + DEBUG(6, "Login: length=%d, flags=0x%02x, seed=0x%08x, hash=0x%016llx%016llx", + length, flags, users[userid].seed, *(unsigned long long *) (unpacked + 1), + *(unsigned long long *) (unpacked + 9)); + if (check_user_and_ip(userid, q, server.check_ip) != 0) { write_dns(dns_fd, q, "BADIP", 5, 'T'); - syslog(LOG_WARNING, "dropped login request from user #%d from %s; expected source %s", + syslog(LOG_WARNING, "rejected login request from user #%d from %s; expected source %s", userid, format_addr(&q->from, q->fromlen), format_addr(&users[userid].host, users[userid].hostlen)); + DEBUG(1, "Rejected login request from user %d: BADIP", userid); return; - } else { - users[userid].last_pkt = time(NULL); - login_calculate(logindata, 16, server.password, users[userid].seed); - - if (read >= 18 && (memcmp(logindata, unpacked + 1, 16) == 0)) { - /* Store login ok, count number of auth attempts */ - users[userid].authenticated++; - if (users[userid].authenticated > 1) - syslog(LOG_WARNING, "duplicate login request from user #%d from %s", - userid, format_addr(&users[userid].host, users[userid].hostlen)); - - /* Send ip/mtu/netmask info */ - tempip.s_addr = server.my_ip; - tmp[0] = strdup(inet_ntoa(tempip)); - tempip.s_addr = users[userid].tun_ip; - tmp[1] = strdup(inet_ntoa(tempip)); - - read = snprintf(out, sizeof(out), "%c-%s-%s-%d-%d", b32_5to8(flags), - tmp[0], tmp[1], server.mtu, server.netmask); - - write_dns(dns_fd, q, out, read, users[userid].downenc); - syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); - - free(tmp[1]); - free(tmp[0]); - } else { - write_dns(dns_fd, q, "LNAK", 4, 'T'); - if (--users[userid].authenticated >= 0) - users[userid].authenticated = -1; - syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password; incorrect attempts: %d", - userid, format_addr(&q->from, q->fromlen), abs(users[userid].authenticated)); - } } + + /* Check remote host/port options */ + if ((addrlen > 0 && !server.allow_forward_remote) || + (remote_tcp && !server.allow_forward_local_port)) { + login_ok = 0; + reason = "requested bad TCP forward options"; + } + + users[userid].last_pkt = time(NULL); + login_calculate(logindata, 16, server.password, users[userid].seed); + + if (memcmp(logindata, unpacked + 1, 16) != 0) { + login_ok = 0; + reason = "bad password"; + } + + if (remote_tcp && addrlen > 0) { + if (use_ipv6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &users[userid].remoteforward_addr; + addr6->sin6_family = AF_INET6; + users[userid].remoteforward_addr_len = sizeof(*addr6); + memcpy(&addr6->sin6_addr, unpacked + 19, MIN(sizeof(*addr6), addrlen)); + } else { + struct sockaddr_in *addr = (struct sockaddr_in *) &users[userid].remoteforward_addr; + addr->sin_family = AF_INET; + users[userid].remoteforward_addr_len = sizeof(*addr); + memcpy(&addr->sin_addr, unpacked + 19, MIN(sizeof(*addr), addrlen)); + } + + users[userid].remote_port = ntohs(*(uint16_t *) (unpacked + 17)); + DEBUG(1, "User %d requested TCP connection to %s:%d, %s.", userid, + format_addr(&users[userid].remoteforward_addr, users[userid].remoteforward_addr_len), + users[userid].remote_port, login_ok ? "allowed" : "rejected"); + } + + if (!login_ok) { + write_dns(dns_fd, q, "LNAK", 4, 'T'); + if (--users[userid].authenticated >= 0) + users[userid].authenticated = -1; + char *src_ip = format_addr(&q->from, q->fromlen); + int tries = abs(users[userid].authenticated); + DEBUG(1, "rejected login from user %d (%s), tries: %d, reason: %s", + userid, src_ip, tries, reason); + syslog(LOG_WARNING, "rejected login request from user #%d from %s, %s; incorrect attempts: %d", + userid, src_ip, reason, tries); + return; + } + + /* Store user auth OK, count number of logins */ + users[userid].authenticated++; + if (users[userid].authenticated > 1) + syslog(LOG_WARNING, "duplicate login request from user #%d from %s", + userid, format_addr(&users[userid].host, users[userid].hostlen)); + + if (remote_tcp) { + int retval; + + retval = socket(users[userid].remoteforward_addr.ss_family, + SOCK_STREAM, ); + + out[0] = 'W'; + read = 1; + + DEBUG(1, "User %d connected from %s, starting TCP connection.", userid, + format_addr(&q->from, q->fromlen), tmp[1]); + syslog(LOG_NOTICE, "accepted password from user #%d, connecting TCP forward", userid, tmp[1]); + + } else { + out[0] = 'I'; + + /* Send ip/mtu/netmask info */ + tempip.s_addr = server.my_ip; + tmp[0] = strdup(inet_ntoa(tempip)); + tempip.s_addr = users[userid].tun_ip; + tmp[1] = strdup(inet_ntoa(tempip)); + + read = snprintf(out, 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]); + syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); + + free(tmp[1]); + free(tmp[0]); + } + + write_dns(dns_fd, q, out, read + 1, users[userid].downenc); } void -handle_dns_ip_request(int dns_fd, struct query *q, int domain_len, int userid) +handle_dns_ip_request(int dns_fd, struct query *q, int userid) { char reply[17]; int length; @@ -1331,7 +1397,7 @@ handle_dns_ip_request(int dns_fd, struct query *q, int domain_len, int userid) } void -handle_dns_upstream_codec_switch(int dns_fd, struct query *q, int domain_len, int userid, +handle_dns_upstream_codec_switch(int dns_fd, struct query *q, int userid, uint8_t *unpacked, size_t read) { int codec; @@ -1367,7 +1433,7 @@ handle_dns_upstream_codec_switch(int dns_fd, struct query *q, int domain_len, in } void -handle_dns_set_options(int dns_fd, struct query *q, int domain_len, int userid, +handle_dns_set_options(int dns_fd, struct query *q, int userid, uint8_t *unpacked, size_t read) { uint8_t bits = 0; @@ -1438,16 +1504,13 @@ handle_dns_set_options(int dns_fd, struct query *q, int domain_len, int userid, } void -handle_dns_fragsize_probe(int dns_fd, struct query *q, int domain_len, int userid, +handle_dns_fragsize_probe(int dns_fd, struct query *q, int userid, uint8_t *unpacked, size_t read) /* Downstream fragsize probe packet */ { int req_frag_size; - /* There should be some data in the query */ - CHECK_LEN(domain_len, 16); - - req_frag_size = ntohs(*(uint16_t *) (unpacked + 1)); + req_frag_size = ntohs(*(uint16_t *) unpacked); DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size); if (req_frag_size < 2 || req_frag_size > MAX_FRAGSIZE) { @@ -1469,7 +1532,7 @@ handle_dns_fragsize_probe(int dns_fd, struct query *q, int domain_len, int useri } void -handle_dns_set_fragsize(int dns_fd, struct query *q, int domain_len, int userid, +handle_dns_set_fragsize(int dns_fd, struct query *q, int userid, uint8_t *unpacked, size_t read) /* Downstream fragsize packet */ { @@ -1490,7 +1553,7 @@ handle_dns_set_fragsize(int dns_fd, struct query *q, int domain_len, int userid, } void -handle_dns_ping(int dns_fd, struct query *q, int domain_len, int userid, +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; @@ -1556,7 +1619,7 @@ handle_dns_ping(int dns_fd, struct query *q, int domain_len, int userid, } void -handle_dns_data(int dns_fd, struct query *q, int domain_len, int userid) +handle_dns_data(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid) { uint8_t unpacked[512]; static fragment f; @@ -1575,7 +1638,7 @@ handle_dns_data(int dns_fd, struct query *q, int domain_len, int userid) /* Decode upstream data header - see docs/proto_XXXXXXXX.txt */ /* First byte (after userid) = CMC (ignored); skip 2 bytes */ len = sizeof(unpacked); - b32->decode(unpacked, &len, (uint8_t *)q->name + 2, 5); + b32->decode(unpacked, &len, (uint8_t *)domain + 2, 5); f.seqID = unpacked[0]; unpacked[2] >>= 4; /* Lower 4 bits are unused */ @@ -1585,7 +1648,7 @@ handle_dns_data(int dns_fd, struct query *q, int domain_len, int userid) f.end = unpacked[2] & 1; /* Decode remainder of data with user encoding into fragment */ - f.len = unpack_data(f.data, MAX_FRAGSIZE, (uint8_t *)q->name + UPSTREAM_HDR, + f.len = unpack_data(f.data, MAX_FRAGSIZE, (uint8_t *)domain + UPSTREAM_HDR, domain_len - UPSTREAM_HDR, users[userid].encoder); DEBUG(3, "frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d", @@ -1612,29 +1675,34 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) { char cmd, userchar; int userid = -1; + uint8_t in[QUERY_NAME_SIZE]; /* Everything here needs at least 5 chars in the name: * cmd, userid and more data or at least 3 bytes CMC */ if (domain_len < 5) return; - cmd = toupper(q->name[0]); - DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(q->name), cmd); + /* Duplicate domain name to prevent changing original query */ + memcpy(in, q->name, QUERY_NAME_SIZE + 1); + in[QUERY_NAME_SIZE] = 0; /* null terminate */ + + cmd = toupper(in[0]); + DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(in), cmd); /* Commands that do not care about userid */ if (cmd == 'V') { /* Version check - before userid is assigned*/ - handle_dns_version(dns_fd, q, domain_len); + handle_dns_version(dns_fd, q, in, domain_len); return; } else if (cmd == 'Z') { /* Upstream codec check - user independent */ /* Check for case conservation and chars not allowed according to RFC */ /* Reply with received hostname as data (encoded in base32) */ - write_dns(dns_fd, q, q->name, domain_len, 'T'); + write_dns(dns_fd, q, (char *)in, domain_len, 'T'); return; } else if (cmd == 'Y') { /* Downstream codec check - user independent*/ - handle_dns_downstream_codec_check(dns_fd, q, domain_len); + handle_dns_downstream_codec_check(dns_fd, q, in, domain_len); return; } @@ -1644,7 +1712,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) userchar = cmd; cmd = 'd'; /* flag for data packet - not part of protocol */ } else { - userchar = toupper(q->name[1]); + userchar = toupper(in[1]); } if (isxdigit(userchar)) { @@ -1657,7 +1725,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) /* Login request - after version check successful, do not check auth yet */ if (cmd == 'L') { - handle_dns_login(dns_fd, q, domain_len, userid); + handle_dns_login(dns_fd, q, in, domain_len, userid); return; } @@ -1668,10 +1736,10 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) } if (cmd == 'd') { /* Upstream data packet */ - handle_dns_data(dns_fd, q, domain_len, userid); + handle_dns_data(dns_fd, q, in, domain_len, userid); return; } else if (cmd == 'I') { /* IP request packet - no base32 data */ - handle_dns_ip_request(dns_fd, q, domain_len, userid); + handle_dns_ip_request(dns_fd, q, userid); } /* Following commands have everything after cmd and userid in base32 @@ -1679,29 +1747,29 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) uint8_t unpacked[512]; size_t raw_len; - raw_len = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)q->name + 2, domain_len - 2, b32); + raw_len = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)in + 2, domain_len - 2, b32); if (raw_len < 3) /* always at least 3 bytes after decoding at least 5 bytes */ return; /* Just in case. */ switch (cmd) { case 'S': - handle_dns_upstream_codec_switch(dns_fd, q, domain_len, userid, unpacked, raw_len); + handle_dns_upstream_codec_switch(dns_fd, q, userid, unpacked, raw_len); break; case 'O': - handle_dns_set_options(dns_fd, q, domain_len, userid, unpacked, raw_len); + handle_dns_set_options(dns_fd, q, userid, unpacked, raw_len); break; case 'R': - handle_dns_fragsize_probe(dns_fd, q, domain_len, userid, unpacked, raw_len); + handle_dns_fragsize_probe(dns_fd, q, userid, unpacked, raw_len); break; case 'N': - handle_dns_set_fragsize(dns_fd, q, domain_len, userid, unpacked, raw_len); + handle_dns_set_fragsize(dns_fd, q, userid, unpacked, raw_len); break; case 'P': - handle_dns_ping(dns_fd, q, domain_len, userid, unpacked, raw_len); + handle_dns_ping(dns_fd, q, userid, unpacked, raw_len); break; default: DEBUG(2, "Invalid DNS query! cmd = %c, hostname = '%*s'", - cmd, domain_len, q->name); + cmd, domain_len, in); } } diff --git a/src/server.h b/src/server.h index 4024b4c..9ceade0 100644 --- a/src/server.h +++ b/src/server.h @@ -108,6 +108,9 @@ struct server_instance { struct sockaddr_storage dns6addr; int dns6addr_len; + int allow_forward_local_port; + int allow_forward_remote; + /* settings for forwarding normal DNS to * local real DNS server */ int bind_fd; diff --git a/src/user.h b/src/user.h index f59f79c..8016036 100644 --- a/src/user.h +++ b/src/user.h @@ -35,6 +35,10 @@ struct tun_user { in_addr_t tun_ip; struct sockaddr_storage host; socklen_t hostlen; + struct sockaddr_storage remoteforward_addr; + socklen_t remoteforward_addr_len; /* 0 if connecting to localhost */ + uint16_t remote_port; /* 0 if no remote forwarding enabled */ + int remote_forward_connected; struct frag_buffer *incoming; struct frag_buffer *outgoing; int next_upstream_ack; From b1d7a78adfb0ce5f19ec3d73546b0add798519ea Mon Sep 17 00:00:00 2001 From: frekky Date: Fri, 29 Jan 2016 22:15:00 +0800 Subject: [PATCH 100/113] Added command line options for TCP forwarding - untested --- src/iodine.c | 88 ++++++++++++++++++++++++++++++++++++++++++++------- src/iodined.c | 21 +++++++++--- 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index 6b95d76..39bf2ec 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef WINDOWS32 #include "windows.h" @@ -137,8 +138,8 @@ static struct client_instance preset_fast = { .max_downstream_frag_size = 1176, .compression_up = 1, .compression_down = 1, - .windowsize_up = 64, - .windowsize_down = 32, + .windowsize_up = 30, + .windowsize_down = 30, .hostname_maxlen = 0xFF, .downenc = ' ', .do_qtype = T_UNSET, @@ -216,7 +217,7 @@ print_usage() fprintf(stderr, "Usage: %s [-v] [-h] [-Y preset] [-V sec] [-X port] [-f] [-r] [-u user] [-t chrootdir] [-d device] " "[-w downfrags] [-W upfrags] [-i sec -j sec] [-I sec] [-c 0|1] [-C 0|1] [-s ms] " - "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-R rdomain] " + "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-R port[,host] ] " "[-z context] [-F pidfile] topdomain [nameserver1 [nameserver2 [...]]]\n", __progname); } @@ -261,6 +262,7 @@ help() fprintf(stderr, " -W upstream fragment window size (default: 8 frags)\n"); fprintf(stderr, " -i server-side request timeout in lazy mode (default: auto)\n"); fprintf(stderr, " -j downstream fragment ACK timeout, implies -i4 (default: 2 sec)\n"); + //fprintf(stderr, " --nodrop disable TCP packet-dropping optimisations\n"); fprintf(stderr, " -c 1: use downstream compression (default), 0: disable\n"); fprintf(stderr, " -C 1: use upstream compression (default), 0: disable\n\n"); @@ -268,9 +270,6 @@ help() fprintf(stderr, " -v, --version print version info and exit\n"); fprintf(stderr, " -h, --help print this help and exit\n"); fprintf(stderr, " -V, --stats print connection statistics at given intervals (default: 5 sec)\n"); - /*fprintf(stderr, " -X skip tun device and forward data to/from stdin/out, telling iodined to\n"); - fprintf(stderr, " connect to the specified port listening on the server host.\n"); - fprintf(stderr, " Can be used with SSH ProxyCommand option. (-X 22)\n");*/ fprintf(stderr, " -f keep running in foreground\n"); fprintf(stderr, " -D enable debug mode (add more D's to increase debug level)\n"); fprintf(stderr, " -d set tunnel device name\n"); @@ -278,6 +277,11 @@ help() fprintf(stderr, " -F write PID to specified file\n"); fprintf(stderr, " -Y, --preset use a set of predefined options for DNS tunnel (can be overridden manually)\n"); print_presets(6); + fprintf(stderr, " -R, --remote [host:]port skip tun device and forward data to/from\n"); + fprintf(stderr, " stdin/out, telling iodined to forward data to a remote port\n"); + fprintf(stderr, " locally or to a specific host (accessed by server). Implies --nodrop.\n"); + fprintf(stderr, " To specify an IPv6 address, host must be enclosed in square brackets.\n"); + fprintf(stderr, " Can be used with SSH ProxyCommand option. ('iodine -R 22 ...')\n"); fprintf(stderr, " --chroot chroot to given directory\n"); fprintf(stderr, " --context apply specified SELinux context after initialization\n"); fprintf(stderr, " --rdomain use specified routing domain (OpenBSD only)\n\n"); @@ -314,6 +318,8 @@ main(int argc, char **argv) char *device = NULL; char *pidfile = NULL; + char *remote_host_str = NULL, *remote_port_str = NULL; + char *nameserv_host = NULL; struct sockaddr_storage nameservaddr; int nameservaddr_len = 0; @@ -331,6 +337,9 @@ main(int argc, char **argv) __progname++; #endif +#define OPT_RDOMAIN 0x80 +#define OPT_NODROP 0x81 + /* each option has format: * char *name, int has_arg, int *flag, int val */ static struct option iodine_args[] = { @@ -338,10 +347,12 @@ main(int argc, char **argv) {"help", no_argument, 0, 'h'}, {"stats", optional_argument, 0, 'V'}, {"context", required_argument, 0, 'z'}, - {"rdomain", required_argument, 0, 'R'}, + {"rdomain", required_argument, 0, OPT_RDOMAIN}, {"chrootdir", required_argument, 0, 't'}, - {"proxycommand", no_argument, 0, 'X'}, {"preset", required_argument, 0, 'Y'}, + {"proxycommand", no_argument, 0, 'R'}, +// {"nodrop", no_argument, 0, OPT_NODROP}, + {"remote", required_argument, 0, 'R'}, {NULL, 0, 0, 0} }; @@ -349,9 +360,10 @@ main(int argc, char **argv) * This is so that all options override preset values regardless of order in command line */ int optind_orig = optind, preset_id = -1; - static char *iodine_args_short = "46vfDhrX:Y:s:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:"; + static char *iodine_args_short = "46vfDhrY:s:V:c:C:i:j:u:t:d:R:P:w:W:m:M:F:T:O:L:I:"; while ((choice = getopt_long(argc, argv, iodine_args_short, iodine_args, NULL))) { + /* Check if preset has been found yet so we don't process any other options */ if (preset_id < 0) { if (choice == -1) { /* reached end of command line and no preset specified - use default */ @@ -391,6 +403,9 @@ main(int argc, char **argv) break; } + /* Once a preset is used, it is copied into memory. This way other + * options can override preset values regardless of order in command line */ + switch (choice) { case '4': nameserv_family = AF_INET; @@ -430,10 +445,51 @@ main(int argc, char **argv) device = optarg; break; #ifdef OPENBSD - case 'R': + case OPT_RDOMAIN: rtable = atoi(optarg); break; #endif + case 'R': + /* Argument format: [host:]port */ + if (!optarg) break; + + if (strrchr(optarg, ':')) { + remote_port_str = strrchr(optarg, ':') + 1; + if (optarg[0] == '[') { + /* IPv6 address enclosed in square brackets */ + remote_host_str = optarg + 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; + /* replace separator with null terminator */ + *strchr(remote_host_str, ':') = 0; + this.remote_forward_addr.ss_family = AF_INET; + retval = inet_aton(remote_host_str, + &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr); + } + } else { + /* no address specified, optarg is port */ + remote_port_str = optarg; + } + + /* parse port */ + this.remote_forward_port = atoi(remote_port_str); + if (this.remote_forward_port < 0 || this.remote_forward_port > 65535) + this.remote_forward_port = -1; + + if (retval <= 0) { + errx(12, "Invalid remote TCP forwarding specification! Check help for info."); + usage(); + /* not reached */ + } + break; + case OPT_NODROP: + // TODO implement TCP-over-tun optimisations + break; case 'P': strncpy(this.password, optarg, sizeof(this.password)); this.password[sizeof(this.password)-1] = 0; @@ -509,8 +565,6 @@ main(int argc, char **argv) case 'Y': /* Already processed preset: ignore */ continue; - case 'X': - // TODO implement option for remote host/port to pipe stdin/out default: usage(); /* NOTREACHED */ @@ -533,6 +587,12 @@ main(int argc, char **argv) this.foreground = 1; } + if (this.remote_forward_port == -1) { + fprintf(stderr, "Remote TCP port must be between 1 and 65535."); + usage(); + /* not reached */ + } + this.nameserv_hosts_len = argc - 1; if (this.nameserv_hosts_len <= 0) /* if no hosts specified, use resolv.conf */ @@ -652,6 +712,10 @@ main(int argc, char **argv) (a != this.nameserv_addrs_len - 1) ? ", " : ""); fprintf(stderr, "\n"); + if (this.remote_forward_port) + fprintf(stderr, "Requesting TCP data forwarding from server to %s:%d\n", + format_addr(&this.remote_forward_addr, sizeof(struct sockaddr_storage)), this.remote_forward_port); + if (client_handshake()) { retval = 1; goto cleanup2; diff --git a/src/iodined.c b/src/iodined.c index 67732b4..c3260f9 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -170,8 +170,8 @@ static void print_usage() { extern char *__progname; - fprintf(stderr, "Usage: %s [-v] [-h] [-4] [-6] [-c] [-s] [-f] [-D] " - "[-u user] [-t chrootdir] [-d device] [-m mtu] [-z context] " + fprintf(stderr, "Usage: %s [options] [-v] [-h] [-4] [-6] [-c] [-s] [-f] [-D] " + "[-u user] [-d device] [-m mtu] " "[-l ipv4 listen address] [-L ipv6 listen address] [-p port] " "[-n external ip] [-b dnsport] [-P password] [-F pidfile] " "[-i max idle time] tunnel_ip[/netmask] topdomain\n", __progname); @@ -198,10 +198,10 @@ help() { fprintf(stderr, " -D increase debug level\n"); fprintf(stderr, " (using -DD in UTF-8 terminal: \"LC_ALL=C luit iodined -DD ...\")\n"); fprintf(stderr, " -u, --user drop privileges and run as user\n"); - fprintf(stderr, " -t, --chrootdir chroot to directory after init\n"); + fprintf(stderr, " --chrootdir chroot to directory after init\n"); fprintf(stderr, " -d specify tunnel device name\n"); fprintf(stderr, " -m, --mtu specify tunnel device mtu\n"); - fprintf(stderr, " -z, --context apply SELinux context after initialization\n"); + fprintf(stderr, " --context apply SELinux context after initialization\n"); fprintf(stderr, " -l, --listen4 IPv4 address to listen on for incoming dns traffic " "(default 0.0.0.0)\n"); fprintf(stderr, " -L, --listen6 IPv6 address to listen on for incoming dns traffic " @@ -209,6 +209,8 @@ help() { fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); fprintf(stderr, " -n, --nsip ip to respond with to NS queries\n"); fprintf(stderr, " -b, --forwardto forward normal DNS queries to a UDP port on localhost\n"); + fprintf(stderr, " -A, --localforward allow TCP data pipe to local ports only (default: disabled)\n"); + fprintf(stderr, " -R, --remoteforward allow TCP data pipe to remote hosts (default: disabled)\n"); fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); fprintf(stderr, " -F, --pidfile write pid to a file\n"); fprintf(stderr, " -i, --idlequit maximum idle time before shutting down\n"); @@ -315,6 +317,8 @@ main(int argc, char **argv) {"mtu", required_argument, 0, 'm'}, {"idlequit", required_argument, 0, 'i'}, {"forwardto", required_argument, 0, 'b'}, + {"localforward", no_argument, 0, 'A'}, + {"remoteforward", no_argument, 0, 'R'}, {"help", no_argument, 0, 'h'}, {"context", required_argument, 0, 'z'}, {"chrootdir", required_argument, 0, 't'}, @@ -322,7 +326,7 @@ main(int argc, char **argv) {NULL, 0, 0, 0} }; - static char *iodined_args_short = "46vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:"; + static char *iodined_args_short = "46vcsfhDARu:t:d:m:l:L:p:n:b:P:z:F:i:"; server.running = 1; @@ -384,6 +388,13 @@ main(int argc, char **argv) server.bind_enable = 1; server.bind_port = atoi(optarg); break; + case 'A': + server.allow_forward_local_port = 1; + break; + case 'R': + server.allow_forward_local_port = 1; + server.allow_forward_remote = 1; + break; case 'F': pidfile = optarg; break; From d6b48fe4e1abc88f831535df6cc9c062eecb17d2 Mon Sep 17 00:00:00 2001 From: frekky Date: Thu, 4 Feb 2016 20:44:34 +0800 Subject: [PATCH 101/113] Updated client-side TCP forwarding command line options --- src/client.c | 26 ++++++++----- src/client.h | 3 +- src/iodine.c | 107 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/src/client.c b/src/client.c index 8ab06ba..ddcc7e4 100644 --- a/src/client.c +++ b/src/client.c @@ -1372,31 +1372,39 @@ send_login(char *login, int len) { uint8_t flags = 0, data[100]; int length = 17, addrlen = 0; + uint16_t port; if (len != 16) DEBUG(1, "Login calculated incorrect length hash! len=%d", len); memcpy(data + 1, login, 16); - if (this.remote_forward_port > 0) { + if (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; + + port = (this.remote_forward_addr.ss_family == AF_INET ? s->sin_port : s6->sin6_port); + + *(uint16_t *) (data + length) = port; + flags |= 1; - *(uint16_t *) (data + length) = (uint16_t) this.remote_forward_port; length += 2; /* set remote IP to be non-localhost if this.remote_forward_addr set */ - if (this.remote_forward_addr_len) { + if (this.remote_forward_addr.ss_family == AF_INET && s->sin_addr.s_addr != INADDR_LOOPBACK) { if (this.remote_forward_addr.ss_family == AF_INET6) { /* IPv6 address */ - addrlen = sizeof(struct in6_addr); + addrlen = sizeof(s6); flags |= 4; - memcpy(data + length, &((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_addr, addrlen); + memcpy(data + length, &s6->sin6_addr, addrlen); } else { /* IPv4 address */ flags |= 2; - addrlen = sizeof(struct in_addr); - memcpy(data + length, &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr, addrlen); + addrlen = sizeof(s); + memcpy(data + length, &s->sin_addr, addrlen); } + length += addrlen; } - DEBUG(2, "Sending TCP forward login request: port %d, length %d, addr %d", - this.remote_forward_port, length, addrlen); + DEBUG(2, "Sending TCP forward login request: port %hu, length %d, addrlen %d", + port, length, addrlen); } data[0] = flags; diff --git a/src/client.h b/src/client.h index 65027f7..eee25b0 100644 --- a/src/client.h +++ b/src/client.h @@ -46,8 +46,7 @@ struct client_instance { /* Remote TCP forwarding stuff (for -R) */ struct sockaddr_storage remote_forward_addr; - socklen_t remote_forward_addr_len; /* 0 if connecting to localhost */ - int remote_forward_port; /* 0 if no forwarding used */ + int use_remote_forward; /* 0 if no forwarding used */ int tun_fd; int dns_fd; diff --git a/src/iodine.c b/src/iodine.c index 39bf2ec..437e0e2 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -81,7 +81,8 @@ struct client_instance this; .maxfragsize_up = 100, \ .next_downstream_ack = -1, \ .num_immediate = 1, \ - .rtt_total_ms = 200 + .rtt_total_ms = 200, \ + .remote_forward_addr = {.ss_family = AF_UNSPEC} static struct client_instance preset_default = { .raw_mode = 1, @@ -302,6 +303,62 @@ version() exit(0); } +static int +parse_tcp_forward_option() +{ + char *remote_port_str, *remote_host_str; + int retval; + + if (strrchr(optarg, ':')) { + remote_port_str = strrchr(optarg, ':') + 1; + if (optarg[0] == '[') { + /* IPv6 address enclosed in square brackets */ + remote_host_str = optarg + 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; + /* replace separator with null terminator */ + *strchr(remote_host_str, ':') = 0; + this.remote_forward_addr.ss_family = AF_INET; + retval = inet_aton(remote_host_str, + &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr); + } + } else { + /* no address specified (use server localhost IPv4), optarg is port */ + remote_port_str = optarg; + this.remote_forward_addr.ss_family = AF_INET; + ((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr.s_addr = INADDR_LOOPBACK; + } + + if (retval <= 0) { + errx(12, "Invalid remote forward address (-R)! Must be [host:]port,\n" + "where IPv6 addresses are enclosed in literal square brackets []."); + usage(); + /* not reached */ + } + + /* parse port */ + int port = atoi(remote_port_str); + if (port < 1 || port > 65535) { + fprintf(stderr, "Remote forward (-R) TCP port must be between 1 and 65535."); + usage(); + /* not reached */ + } + + 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; + } else { + /* set port in IPv6 sockaddr */ + ((struct sockaddr_in6 *) &this.remote_forward_addr)->sin6_port = port; + } + return port; +} + int main(int argc, char **argv) { @@ -318,7 +375,7 @@ main(int argc, char **argv) char *device = NULL; char *pidfile = NULL; - char *remote_host_str = NULL, *remote_port_str = NULL; + int remote_forward_port; char *nameserv_host = NULL; struct sockaddr_storage nameservaddr; @@ -452,40 +509,8 @@ main(int argc, char **argv) case 'R': /* Argument format: [host:]port */ if (!optarg) break; - - if (strrchr(optarg, ':')) { - remote_port_str = strrchr(optarg, ':') + 1; - if (optarg[0] == '[') { - /* IPv6 address enclosed in square brackets */ - remote_host_str = optarg + 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; - /* replace separator with null terminator */ - *strchr(remote_host_str, ':') = 0; - this.remote_forward_addr.ss_family = AF_INET; - retval = inet_aton(remote_host_str, - &((struct sockaddr_in *) &this.remote_forward_addr)->sin_addr); - } - } else { - /* no address specified, optarg is port */ - remote_port_str = optarg; - } - - /* parse port */ - this.remote_forward_port = atoi(remote_port_str); - if (this.remote_forward_port < 0 || this.remote_forward_port > 65535) - this.remote_forward_port = -1; - - if (retval <= 0) { - errx(12, "Invalid remote TCP forwarding specification! Check help for info."); - usage(); - /* not reached */ - } + this.use_remote_forward = 1; + remote_forward_port = parse_tcp_forward_option(); break; case OPT_NODROP: // TODO implement TCP-over-tun optimisations @@ -587,11 +612,7 @@ main(int argc, char **argv) this.foreground = 1; } - if (this.remote_forward_port == -1) { - fprintf(stderr, "Remote TCP port must be between 1 and 65535."); - usage(); - /* not reached */ - } + this.nameserv_hosts_len = argc - 1; if (this.nameserv_hosts_len <= 0) @@ -712,9 +733,9 @@ main(int argc, char **argv) (a != this.nameserv_addrs_len - 1) ? ", " : ""); fprintf(stderr, "\n"); - if (this.remote_forward_port) + if (this.remote_forward_addr.ss_family != AF_UNSPEC) fprintf(stderr, "Requesting TCP data forwarding from server to %s:%d\n", - format_addr(&this.remote_forward_addr, sizeof(struct sockaddr_storage)), this.remote_forward_port); + format_addr(&this.remote_forward_addr, sizeof(struct sockaddr_storage)), remote_forward_port); if (client_handshake()) { retval = 1; From cfd2411e45547f9acf5f159bd7cfda742ca710fe Mon Sep 17 00:00:00 2001 From: frekky Date: Thu, 4 Feb 2016 20:44:57 +0800 Subject: [PATCH 102/113] Add utility functions for TCP sockets --- src/common.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/common.h | 3 +++ 2 files changed, 71 insertions(+) diff --git a/src/common.c b/src/common.c index 19b4461..459c063 100644 --- a/src/common.c +++ b/src/common.c @@ -397,6 +397,74 @@ check_topdomain(char *str, char **errormsg) return 0; } +int +socket_set_blocking(int fd, int blocking) +{ + /* Set non-blocking socket mode */ +#ifdef WINDOWS32 + if (ioctlsocket(fd, FIONBIO, &blocking) != 0) { + return WSAGetLastError(); + } +#else + int flags; + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { + return flags; + } + + if (fcntl(fd, F_SETFL, blocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))) == -1) + return errno; + +#endif + return 0; +} + +int +open_tcp_nonblocking(struct sockaddr_storage *addr, char **errormsg) +/* Open TCP connection to given address without blocking */ +{ + int fd, ret; + if ((fd = socket(addr->ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { + if (errormsg) + *errormsg = strerror(errno); + return -1; + } + + if ((ret = socket_set_blocking(fd, 0)) != 0) { + if (errormsg) + *errormsg = strerror(ret); + return -1; + } + + if ((ret = connect(fd, (struct sockaddr *)addr, sizeof(struct sockaddr_storage))) + == -1 && errno != EINPROGRESS) { + if (errormsg) + *errormsg = strerror(errno); + return -1; + } + + return fd; +} + +int +check_tcp_status(int fd, char **error) +/* checks connected status of given socket. + * returns error code. 0 if connected or EINPROGRESS if connecting */ +{ + int errornum = 0; + socklen_t len = sizeof(int); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errornum, &len) != 0) { + if (error) + *error = "getsockopt failed."; + return -1; + } + + if (error) + *error = strerror(errornum); + + return errornum; +} + #if defined(WINDOWS32) || defined(ANDROID) #ifndef ANDROID int diff --git a/src/common.h b/src/common.h index 56c6078..3064357 100644 --- a/src/common.h +++ b/src/common.h @@ -161,6 +161,9 @@ int open_dns_opt(struct sockaddr_storage *sockaddr, size_t sockaddr_len, int v6o int open_dns_from_host(char *host, int port, int addr_family, int flags); void close_dns(int); +int open_tcp_nonblocking(struct sockaddr_storage *addr, char **error); +int check_tcp_status(int fd, char **error); + void do_chroot(char *); void do_setcon(char *); void do_detach(); From d46766bcc94cd3fbfc00baf4ff4f4fb2699aee7c Mon Sep 17 00:00:00 2001 From: frekky Date: Thu, 4 Feb 2016 20:46:06 +0800 Subject: [PATCH 103/113] Update server login protocol handling for TCP forwarding --- src/iodined.c | 6 +-- src/server.c | 139 ++++++++++++++++++++++++++++++++++++-------------- src/user.h | 4 +- 3 files changed, 105 insertions(+), 44 deletions(-) diff --git a/src/iodined.c b/src/iodined.c index c3260f9..91942ef 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -485,14 +485,14 @@ main(int argc, char **argv) } } if (server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET6) { - server.dns6addr_len = get_addr(listen_ip6, server.port,AF_INET6, + server.dns6addr_len = get_addr(listen_ip6, server.port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &server.dns6addr); if (server.dns6addr_len < 0) { warnx("Bad IPv6 address to listen on."); usage(); } } - if(server.bind_enable) { + if (server.bind_enable) { in_addr_t dns_ip = ((struct sockaddr_in *) &server.dns4addr)->sin_addr.s_addr; if (server.bind_port < 1 || server.bind_port > 65535) { warnx("Bad DNS server port number given."); @@ -500,7 +500,7 @@ main(int argc, char **argv) /* NOTREACHED */ } /* Avoid forwarding loops */ - if (server.bind_port == server.port && (dns_ip == INADDR_ANY || dns_ip == htonl(0x7f000001L))) { + if (server.bind_port == server.port && (dns_ip == INADDR_ANY || dns_ip == INADDR_LOOPBACK)) { warnx("Forward port is same as listen port (%d), will create a loop!", server.bind_port); fprintf(stderr, "Use -l to set listen ip to avoid this.\n"); usage(); diff --git a/src/server.c b/src/server.c index f150612..49e72d9 100644 --- a/src/server.c +++ b/src/server.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "common.h" #include "version.h" @@ -1168,8 +1169,8 @@ handle_dns_version(int dns_fd, struct query *q, uint8_t *domain, int domain_len) memcpy(&(u->host), &(q->from), q->fromlen); u->hostlen = q->fromlen; u->remote_forward_connected = 0; - u->remote_port = 0; u->remoteforward_addr_len = 0; + u->remoteforward_addr.ss_family = AF_UNSPEC; u->fragsize = 100; /* very safe */ u->conn = CONN_DNS_NULL; u->encoder = get_base32_encoder(); @@ -1249,9 +1250,14 @@ 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; struct in_addr tempip; - char remote_tcp, remote_isnt_localhost, use_ipv6, drop_packets; + char remote_tcp, remote_isnt_localhost, use_ipv6, poll_status; //, drop_packets; int length = 17, read, addrlen, login_ok = 1; + uint16_t port; + struct tun_user *u = &users[userid]; + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &u->remoteforward_addr; + struct sockaddr_in *addr = (struct sockaddr_in *) &u->remoteforward_addr; read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *) domain + 2, domain_len - 2, b32); @@ -1260,24 +1266,30 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i remote_tcp = flags & 1; remote_isnt_localhost = (flags & 2) >> 1; use_ipv6 = (flags & 4) >> 2; - drop_packets = (flags & 8) >> 3; /* currently unimplemented */ + //drop_packets = (flags & 8) >> 3; /* currently unimplemented */ + poll_status = (flags & 0x10) >> 4; addrlen = (remote_tcp && remote_isnt_localhost) ? (use_ipv6 ? 16 : 4) : 0; length += (remote_tcp ? 2 : 0) + addrlen; + /* There should be no extra data if only polling forwarding status */ + if (poll_status) { + length = 17; + } + CHECK_LEN(read, length); - DEBUG(2, "Received login request for user %d from %s.", + DEBUG(2, "Received login request for user %d from %s", userid, format_addr(&q->from, q->fromlen)); DEBUG(6, "Login: length=%d, flags=0x%02x, seed=0x%08x, hash=0x%016llx%016llx", - length, flags, users[userid].seed, *(unsigned long long *) (unpacked + 1), + length, flags, u->seed, *(unsigned long long *) (unpacked + 1), *(unsigned long long *) (unpacked + 9)); 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(&users[userid].host, users[userid].hostlen)); + userid, format_addr(&q->from, q->fromlen), format_addr(&u->host, u->hostlen)); DEBUG(1, "Rejected login request from user %d: BADIP", userid); return; } @@ -1289,39 +1301,54 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i reason = "requested bad TCP forward options"; } - users[userid].last_pkt = time(NULL); - login_calculate(logindata, 16, server.password, users[userid].seed); + u->last_pkt = time(NULL); + login_calculate(logindata, 16, server.password, u->seed); if (memcmp(logindata, unpacked + 1, 16) != 0) { login_ok = 0; reason = "bad password"; } - if (remote_tcp && addrlen > 0) { - if (use_ipv6) { - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &users[userid].remoteforward_addr; - addr6->sin6_family = AF_INET6; - users[userid].remoteforward_addr_len = sizeof(*addr6); - memcpy(&addr6->sin6_addr, unpacked + 19, MIN(sizeof(*addr6), addrlen)); - } else { - struct sockaddr_in *addr = (struct sockaddr_in *) &users[userid].remoteforward_addr; - addr->sin_family = AF_INET; - users[userid].remoteforward_addr_len = sizeof(*addr); - memcpy(&addr->sin_addr, unpacked + 19, MIN(sizeof(*addr), addrlen)); - } + if (remote_tcp) { + port = ntohs(*(uint16_t *) (unpacked + 17)); + if (addrlen > 0) { + if (use_ipv6) { + addr6->sin6_family = AF_INET6; + addr6->sin6_port = 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; + u->remoteforward_addr_len = sizeof(*addr); + memcpy(&addr->sin_addr, unpacked + 19, MIN(sizeof(*addr), addrlen)); + } - users[userid].remote_port = ntohs(*(uint16_t *) (unpacked + 17)); - DEBUG(1, "User %d requested TCP connection to %s:%d, %s.", userid, - format_addr(&users[userid].remoteforward_addr, users[userid].remoteforward_addr_len), - users[userid].remote_port, login_ok ? "allowed" : "rejected"); + DEBUG(1, "User %d requested TCP connection to %s:%hu, %s.", userid, + format_addr(&u->remoteforward_addr, u->remoteforward_addr_len), + port, login_ok ? "allowed" : "rejected"); + } else { + addr->sin_family = AF_INET; + addr->sin_port = port; + addr->sin_addr.s_addr = INADDR_LOOPBACK; + DEBUG(1, "User %d requested TCP connection to localhost:%hu, %s.", userid, + port, login_ok ? "allowed" : "rejected"); + } + } + + if (poll_status && login_ok) { + if (addrlen > 0 || (flags ^ 0x10)) { + login_ok = 0; + reason = "invalid flags"; + } } if (!login_ok) { write_dns(dns_fd, q, "LNAK", 4, 'T'); - if (--users[userid].authenticated >= 0) - users[userid].authenticated = -1; + if (--u->authenticated >= 0) + u->authenticated = -1; char *src_ip = format_addr(&q->from, q->fromlen); - int tries = abs(users[userid].authenticated); + int tries = abs(u->authenticated); DEBUG(1, "rejected login from user %d (%s), tries: %d, reason: %s", userid, src_ip, tries, reason); syslog(LOG_WARNING, "rejected login request from user #%d from %s, %s; incorrect attempts: %d", @@ -1330,31 +1357,59 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i } /* Store user auth OK, count number of logins */ - users[userid].authenticated++; - if (users[userid].authenticated > 1) + u->authenticated++; + if (u->authenticated > 1 && !poll_status) syslog(LOG_WARNING, "duplicate login request from user #%d from %s", - userid, format_addr(&users[userid].host, users[userid].hostlen)); + userid, format_addr(&u->host, u->hostlen)); if (remote_tcp) { - int retval; + int tcp_fd; - retval = socket(users[userid].remoteforward_addr.ss_family, - SOCK_STREAM, ); + 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))); + syslog(LOG_NOTICE, "accepted password from user #%d, connecting TCP forward", userid); + + /* Open socket and connect to TCP forward host:port */ + tcp_fd = open_tcp_nonblocking(&u->remoteforward_addr, &errormsg); + if (tcp_fd < 0) { + if (!errormsg) + errormsg = "Error opening socket."; + goto tcp_forward_error; + } out[0] = 'W'; read = 1; + write_dns(dns_fd, q, out, read + 1, u->downenc); + u->tcp_fd = tcp_fd; + return; + } else if (poll_status) { + int retval; - DEBUG(1, "User %d connected from %s, starting TCP connection.", userid, - format_addr(&q->from, q->fromlen), tmp[1]); - syslog(LOG_NOTICE, "accepted password from user #%d, connecting TCP forward", userid, tmp[1]); + if ((retval = check_tcp_status(u->tcp_fd, &errormsg)) == -1) { + goto tcp_forward_error; + } + read = 1; + out[1] = 0; + + if (retval == 0) { + out[0] = 'C'; + u->remote_forward_connected = 1; + } else if (retval == EINPROGRESS) { + out[0] = 'W'; + } else { + goto tcp_forward_error; + } + + write_dns(dns_fd, q, out, read + 1, u->downenc); + return; } else { out[0] = 'I'; /* Send ip/mtu/netmask info */ tempip.s_addr = server.my_ip; tmp[0] = strdup(inet_ntoa(tempip)); - tempip.s_addr = users[userid].tun_ip; + tempip.s_addr = u->tun_ip; tmp[1] = strdup(inet_ntoa(tempip)); read = snprintf(out, sizeof(out) - 1, "-%s-%s-%d-%d", @@ -1366,9 +1421,15 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i free(tmp[1]); free(tmp[0]); + write_dns(dns_fd, q, out, read + 1, u->downenc); + return; } - - write_dns(dns_fd, q, out, read + 1, users[userid].downenc); +tcp_forward_error: + DEBUG(1, "Failed to connect TCP forward for user %d: %s", userid, errormsg); + out[0] = 'E'; + strncat(out + 1, errormsg, sizeof(out) - 1); + read = strlen(out); + write_dns(dns_fd, q, out, read + 1, u->downenc); } void diff --git a/src/user.h b/src/user.h index 8016036..3fc471c 100644 --- a/src/user.h +++ b/src/user.h @@ -36,8 +36,8 @@ struct tun_user { struct sockaddr_storage host; socklen_t hostlen; struct sockaddr_storage remoteforward_addr; - socklen_t remoteforward_addr_len; /* 0 if connecting to localhost */ - uint16_t remote_port; /* 0 if no remote forwarding enabled */ + socklen_t remoteforward_addr_len; /* 0 if no remote forwarding enabled */ + int tcp_fd; int remote_forward_connected; struct frag_buffer *incoming; struct frag_buffer *outgoing; From a5a936f4e4a46b2afe1e48759022b09ebf1157e8 Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 7 Feb 2016 21:59:00 +0800 Subject: [PATCH 104/113] 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 From c8105dcc0837dd511ad95a8e7322af52331ffcba Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 7 Feb 2016 21:59:27 +0800 Subject: [PATCH 105/113] Updated protocol docs for TCP forward support --- doc/proto_00000800.txt | 129 ++++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 47 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index fbec870..d683e5b 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -42,19 +42,46 @@ Server replies: Login: Client sends: First byte l or L + 1 byte userid char (hex) Rest encoded with base32: - 1 byte userid + 1 byte flags: (least to most significant bits) + 0: connect to remote TCP port (data pipe/ProxyCommand mode) + 1: use non-localhost remote IP + 2: remote IP is IPv6 + 3: use TCP-over-tun optimisation (drop extra packets) + 4: check forward connected status + 5-8: unused 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) - CMC + 2 bytes remote TCP port (big endian) + (TCP port appears only when flags bit 0 is set) + 4-16 bytes remote IP address (4 bytes if IPv4 or 16 for IPv6) + (IP address included only if flags bits 0, 1 are set) + 2 bytes CMC Server replies: - LNAK means not accepted - x.x.x.x-y.y.y.y-mtu-netmask means accepted (server ip, client ip, mtu, netmask bits) + LNAK means either auth or options not accepted + flag [-x.x.x.x-y.y.y.y-mtu-netmask]|[error message] means accepted + (server ip, client ip, mtu, netmask bits) + flag can be one of: + I: Login success (followed by IP addresses in [...]) + C: TCP forward connected + W: TCP forward connection waiting + E: TCP connection error - followed by human readable message string +If the requested TCP forwarding options are not accepted by the server, the +response is simply LNAK. +If TCP forwarding is requested, server opens a connection to the specified host +and IP in the background and responds with a flag corresponding to the current +connection status. +The client repeats login requests with the check flag (bit 4) set to poll TCP +connection status, not resending the remote host address or setting any other +flags. Once the server responds with 'C' or 'E', the client either continues +the handshake or prints the error message and exits. + -IP Request: (for where to try raw login or set data pipe mode) +IP Request: (for where to try raw login) Client sends: First byte i or I - 5 bits coded as Base32 char, meaning userid - CMC as 3 Base32 chars + 1 byte userid char (hex) + CMC as 4 Base32 chars Server replies BADIP if bad userid First byte I @@ -73,8 +100,9 @@ Downstream codec check: Client sends: First byte y or Y 1 char, meaning downstream codec to use - 5 bits coded as Base32 char, meaning check variant - CMC as 3 Base32 chars + rest encoded in base32: + 1 byte check variant + 2 bytes CMC Possibly extra data, depending on check variant Server sends: Data encoded with requested downstream codec; data content depending @@ -93,59 +121,58 @@ Server sends: Switch codec: Client sends: First byte s or S - 5 bits coded as Base32 char, meaning userid - 5 bits coded as Base32 char, representing number of raw bits per - encoded byte: + 1 byte userid char (hex) + rest encoded in base32: + 1 byte meaning number of bits per encoded byte in new codec: 5: Base32 (a-z0-5) 6: Base64 (a-zA-Z0-9+-) 26: Base64u (a-zA-Z0-9_-) 7: Base128 (a-zA-Z0-9\274-\375) - CMC as 3 Base32 chars + 2 bytes CMC Server sends: Name of codec if accepted. After this all upstream data packets must be encoded with the new codec. BADCODEC if not accepted. Client must then revert to previous codec BADLEN if length of query is too short -Options: + +Set Options: Client sends: First byte o or O - 5 bits coded as Base32 char, meaning userid - number of options (n) as decimal digit - n chars, each a valid option (to be processed in order) - CMC as 3 Base32 chars + 1 byte userid char (hex) + rest encoded in base32: + 1 byte option flags and 2 bytes CMC: + 0 1 - 3 + +76543210+---+ + |0TSUVRCL|CMC| + +--------+---+ Server sends: - Option chars in the same order as request, indicating that options were - accepted by the server. + Full name of encoding type used if successful (case insensitive). BADCODEC if not accepted. Previous situation remains. BADLEN if number of options doesn't match length of query. All options affect only the requesting client. - - Option chars: - t or T: Downstream encoding Base32, for TXT/CNAME/A/MX (default) - s or S: Downstream encoding Base64, for TXT/CNAME/A/MX - u or U: Downstream encoding Base64u, for TXT/CNAME/A/MX - v or V: Downstream encoding Base128, for TXT/CNAME/A/MX - r or R: Downstream encoding Raw, for PRIVATE/TXT/NULL (assumed for +Option flags: + T: Downstream encoding Base32, for TXT/CNAME/A/MX (default) + S: Downstream encoding Base64, for TXT/CNAME/A/MX + U: Downstream encoding Base64u, for TXT/CNAME/A/MX + V: Downstream encoding Base128, for TXT/CNAME/A/MX + R: Downstream encoding Raw, for PRIVATE/TXT/NULL (assumed for PRIVATE and NULL) + C: Downstream compression enabled (compressed before encoding) + L: Lazy mode enabled, server will keep a number of requests waiting until + data becomes available to send downstream or the requests time out. + The timeout value for requests is controlled by the client. + Applies only to data transfer; handshake is always answered immediately. If codec unsupported for request type, server will use Base32; note that server will answer any mix of request types that a client sends. - Server may disregard this option; client must always use the downstream - encoding type indicated in every downstream DNS packet. + Server may disregard the encoding options; client must always use the + downstream encoding type indicated in every downstream DNS packet. - c or C: Downstream compression enabled (compressed before encoding) - d or D: Downstream compression disabled - l or L: Lazy mode, server will keep a number of requests waiting until data - becomes available to send downstream or the requests time out. The - timeout value for requests is controlled by the client. - Applies only to data transfer; handshake is always answered immediately. - i or I: Immediate (non-lazy) mode, server will answer all requests - (nearly) immediately. Probe downstream fragment size: Client sends: First byte r or R - Second byte userid char + 1 byte userid char (hex) 2 bytes big-endian fragsize encoded as 4 bytes base32 Then follows a long random query which contents does not matter. Server sends: @@ -158,8 +185,8 @@ Server sends: Set downstream fragment size: Client sends: First byte n or N + 1 byte userid char (hex) Rest encoded with base32: - 1 byte userid 2 bytes new downstream fragment size (big-endian) CMC Server sends: @@ -177,7 +204,7 @@ Upstream data header: Downstream data header: |=> only if ping (P) flag set | 0 1 2 3 4 5 6 +--------+--------+76543210+--------+--------+--------+--------+ - | Seq ID | Up ACK |00IPACFL|Dn Wsize|Up Wsize|DnWstart|UpWstart| + | Seq ID | Up ACK |0EIPACFL|Dn Wsize|Up Wsize|DnWstart|UpWstart| +--------+--------+--------+--------+--------+--------+--------+ UUUU = Userid @@ -187,6 +214,7 @@ F = First fragment flag C = Compression enabled for downstream packet P = ping flag: extra header present I = responded to immediately (for RTT calculation) - downstream only +E = TCP Forward error (data following is text string reason) UDCMC = Upstream Data CMC char (base36 [a-z0-9]) Up/Dn Wsize/Wstart = upstream/downstream window size/window start Seq ID @@ -201,6 +229,9 @@ containing upstream and downstream window sizes and window start sequence IDs. The response does not need to contain data. If the server has no data to send, the response will always include the ping header and the ping flag will be set. +If the TCP forward error (E) flag is set, the TCP connection at the server is +closed and the client sends EOF to stdout and exits. + In NULL and PRIVATE responses, downstream data is always raw. In all other response types, downstream data is encoded (see Options above). Encoding type is indicated by 1 prefix char (before the data header): @@ -227,12 +258,12 @@ Ping: Client sends: First byte p or P Second byte CMC + 1 byte userid char (hex) Rest encoded with Base32: - 0 1 2...8 9-10 - +--------+--------+---+76543210+---+ - |0000UUUU|Dn SeqID|...|000WTANR|CMC| - +--------+--------+---+--------+---+ - 4 bits Userid + 0 1...7 8 - 9 + +--------+---+76543210+---+ + |Dn SeqID|...|00DWTANR|CMC| + +--------+---+--------+---+ 1 byte Downstream seq ID ACK 1 byte window size (upstream) 1 byte window size (downstream) @@ -242,6 +273,7 @@ Client sends: 2 bytes big-endian downstream fragment ACK timeout in ms 1 byte flags: + D = disconnect remote TCP forward (client should then exit) W = update window frag timeout T = update server timeout A = is ACKing downstream frag @@ -257,6 +289,9 @@ The server must also adjust its window sizes to those provided by the ping. If the T but is set, the server sets the user's DNS timeout to the value spec- ified by the packet. +If the bit corresponding to changing a particular value (ie. window timeout) is +not set, the value should be random. (note: this is disabled in debug mode). + In lazy mode, unless the R flag is set, the server will hold the ping until it times out or more data becomes available to send. @@ -299,8 +334,8 @@ pending queries) for ACKs or for new data. ====================================================== This protocol does not implement data windowing and does not guarantee data -delivery, however it is likely faster due to the fact that data is not split -into fragments for sending. Full packets are compressed and sent when they +delivery, however it is faster since the data is not encoded and transferred +on top of the DNS protocol. Full packets are compressed and sent when they arrive on the tun device, and are processed immediately on the other side. All Raw UDP protcol messages start with a 3 byte header: 0x10d19e From 6f43791ebc3cfceedf6fb8dace6210dbae7885be Mon Sep 17 00:00:00 2001 From: frekky Date: Sun, 7 Feb 2016 21:59:59 +0800 Subject: [PATCH 106/113] Fixed bug causing select invalid argument on iodined --- src/window.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/window.c b/src/window.c index 3fe3c09..a3bd346 100644 --- a/src/window.c +++ b/src/window.c @@ -273,8 +273,13 @@ window_sending(struct frag_buffer *w, struct timeval *nextresend) oldest.tv_sec = 0; oldest.tv_usec = 0; - if (w->numitems == 0) + if (w->numitems == 0) { + if (nextresend) { + nextresend->tv_sec = 0; + nextresend->tv_usec = 0; + } return 0; + } gettimeofday(&now, NULL); From 8384da195d4bf87dd4f722c98b84f4e57d1ef33d Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 13 Feb 2016 22:58:30 +0800 Subject: [PATCH 107/113] Fixed systemd warning and compile error --- src/iodine.c | 2 +- src/iodined.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iodine.c b/src/iodine.c index ac6f079..1e95f00 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -376,7 +376,7 @@ main(int argc, char **argv) char *device = NULL; char *pidfile = NULL; - int remote_forward_port; + int remote_forward_port = 0; char *nameserv_host = NULL; struct sockaddr_storage nameservaddr; diff --git a/src/iodined.c b/src/iodined.c index 53166b2..396fe62 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -561,7 +561,7 @@ main(int argc, char **argv) goto cleanup; } else if (nb_fds == 1) { /* XXX: assume we get IPv4 socket */ - dns_fds.v4fd = SD_LISTEN_FDS_START; + server.dns_fds.v4fd = SD_LISTEN_FDS_START; } else { #endif if ((server.addrfamily == AF_UNSPEC || server.addrfamily == AF_INET) && From 5dbe640ec577c4423130f5f48949ad7ea51a53e6 Mon Sep 17 00:00:00 2001 From: Anime4000 Date: Sun, 17 Apr 2016 23:24:34 +0900 Subject: [PATCH 108/113] mingw don't use arpa/inet.h (#2) don't use arpa/inet.h in mingw --- src/iodine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iodine.c b/src/iodine.c index 1e95f00..3b79c9c 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -27,7 +27,6 @@ #include #include #include -#include #ifdef WINDOWS32 #include "windows.h" @@ -36,6 +35,7 @@ #include #include #include +#include #endif #include "common.h" From 9ce4b28b3533eaf59ee99b2f9abcacf85b7ce5d3 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Fri, 29 Jul 2016 14:34:42 -0400 Subject: [PATCH 109/113] Fix build on Arch Linux --- src/osflags | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osflags b/src/osflags index 6af11aa..a56ebaa 100644 --- a/src/osflags +++ b/src/osflags @@ -19,7 +19,7 @@ link) Linux) FLAGS=""; [ -e /usr/include/selinux/selinux.h ] && FLAGS="$FLAGS -lselinux"; - [ -e /usr/include/systemd/sd-daemon.h ] && FLAGS="$FLAGS $(pkg-config --libs libsystemd-daemon)"; + [ -e /usr/include/systemd/sd-daemon.h ] && FLAGS="$FLAGS $(pkg-config --libs libsystemd)"; echo $FLAGS; ;; esac From 9640f3ede6c49cfe36cd8ba819ddf3c5dfe1dbf0 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Fri, 29 Jul 2016 14:35:05 -0400 Subject: [PATCH 110/113] Add window.c to Android.mk --- src/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android.mk b/src/Android.mk index 81136e5..cfbad40 100644 --- a/src/Android.mk +++ b/src/Android.mk @@ -16,7 +16,7 @@ HEAD_COMMIT = `git rev-parse --short HEAD` include $(CLEAR_VARS) LOCAL_MODULE := iodine -LOCAL_SRC_FILES := tun.c dns.c read.c encoding.c login.c base32.c base64.c base64u.c base128.c md5.c common.c iodine.c client.c util.c +LOCAL_SRC_FILES := tun.c dns.c read.c encoding.c login.c base32.c base64.c base64u.c base128.c md5.c common.c iodine.c client.c window.c util.c LOCAL_CFLAGS := -c -DANDROID -DLINUX -DIFCONFIGPATH=\"/system/bin/\" -Wall -DGITREVISION=\"$(HEAD_COMMIT)\" LOCAL_LDLIBS := -lz From 4c2f9bf4b7b164119b6a4f4e073c1f4599103d9a Mon Sep 17 00:00:00 2001 From: WGH Date: Wed, 3 Aug 2016 01:12:24 +0300 Subject: [PATCH 111/113] fixed buffer overflow in handle_null_request() --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index d5ec1bc..b8100f4 100644 --- a/src/server.c +++ b/src/server.c @@ -1854,7 +1854,7 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) { char cmd, userchar; int userid = -1; - uint8_t in[QUERY_NAME_SIZE]; + uint8_t in[QUERY_NAME_SIZE + 1]; /* Everything here needs at least 5 chars in the name: * cmd, userid and more data or at least 3 bytes CMC */ From 93cf0e67ba9fbf1ff50a746effd3d809ab56fd45 Mon Sep 17 00:00:00 2001 From: WGH Date: Wed, 3 Aug 2016 01:15:06 +0300 Subject: [PATCH 112/113] fixed buffer overflow and use of uninitialized memory in handle_dns_login --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index b8100f4..80c08ae 100644 --- a/src/server.c +++ b/src/server.c @@ -1532,7 +1532,7 @@ handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, i tcp_forward_error: DEBUG(1, "Failed to connect TCP forward for user %d: %s", userid, errormsg); out[0] = 'E'; - strncat(out + 1, errormsg, sizeof(out) - 1); + strncpy(out + 1, errormsg, sizeof(out) - 1); read = strlen(out); write_dns(dns_fd, q, out, read + 1, u->downenc); } From dbe9a10fc19250d81e003ce478ffe8a5c3d8fd2e Mon Sep 17 00:00:00 2001 From: frekky Date: Sat, 24 Sep 2016 21:39:28 +0800 Subject: [PATCH 113/113] Fix version check and codec check to be backwards compatible --- doc/proto_00000800.txt | 5 ++--- src/client.c | 20 +++++++++++--------- src/server.c | 20 ++++++++------------ 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/doc/proto_00000800.txt b/doc/proto_00000800.txt index d683e5b..b0e135f 100644 --- a/doc/proto_00000800.txt +++ b/doc/proto_00000800.txt @@ -100,9 +100,8 @@ Downstream codec check: Client sends: First byte y or Y 1 char, meaning downstream codec to use - rest encoded in base32: - 1 byte check variant - 2 bytes CMC + 5 bits coded as Base32 char, meaning check variant + CMC as 3 Base32 chars Possibly extra data, depending on check variant Server sends: Data encoded with requested downstream codec; data content depending diff --git a/src/client.c b/src/client.c index 0265587..4ca5f7d 100644 --- a/src/client.c +++ b/src/client.c @@ -862,7 +862,7 @@ handshake_waitdns(char *buf, size_t buflen, char cmd, int timeout) (q.name[0] == 'Y' || q.name[0] == 'y' || q.name[0] == 'V' || q.name[0] == 'v')) { fprintf(stderr, "Got empty reply. This nameserver may not be resolving recursively, use another.\n"); - fprintf(stderr, "Try \"iodine [options] ns.%s %s\" first, it might just work.\n", + fprintf(stderr, "Try \"iodine [options] %s ns.%s\" first, it might just work.\n", this.topdomain, this.topdomain); return -2; } @@ -1418,17 +1418,19 @@ send_upenctest(char *s) static void send_downenctest(char downenc, int variant) { - uint8_t buf[512] = "y_____.", hdr[5]; + char buf[512] = "yTaCMC."; + size_t buf_space = 4; - buf[1] = downenc; + buf[1] = tolower(downenc); + buf[2] = b32_5to8(variant); - hdr[0] = variant; - *(uint32_t *) (hdr + 1) = rand(); + /* add 3 bytes base32 CMC (random) */ + uint32_t cmc = rand(); - build_hostname(buf, sizeof(buf), hdr, sizeof(hdr), - this.topdomain, b32, this.hostname_maxlen, 2); - - send_query(buf); + b32->encode((uint8_t *)buf + 3, &buf_space, (uint8_t *) &cmc, 2); + strncat(buf, ".", 512 - strlen(buf)); + strncat(buf, this.topdomain, 512 - strlen(buf)); + send_query((uint8_t *)buf); } static void diff --git a/src/server.c b/src/server.c index 80c08ae..5c4bc88 100644 --- a/src/server.c +++ b/src/server.c @@ -1297,23 +1297,20 @@ handle_dns_version(int dns_fd, struct query *q, uint8_t *domain, int domain_len) void handle_dns_downstream_codec_check(int dns_fd, struct query *q, uint8_t *domain, int domain_len) { - int codec; char *datap; - int datalen; - uint8_t unpacked[10]; + int datalen, i, codec; - unpack_data(unpacked, sizeof(unpacked), (uint8_t *)domain + 2, MIN(domain_len - 2, 4), b32); + i = b32_8to5(domain[2]); /* check variant: second char in b32 */ - switch (unpacked[0]) { /* check variant */ - case 1: + if (i == 1) { datap = DOWNCODECCHECK1; datalen = DOWNCODECCHECK1_LEN; - break; - default: + } else { write_dns(dns_fd, q, "BADLEN", 6, 'T'); return; } + /* codec to test: first char raw */ codec = toupper(domain[1]); switch (codec) { case 'T': @@ -1868,19 +1865,18 @@ handle_null_request(int dns_fd, struct query *q, int domain_len) cmd = toupper(in[0]); DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(in), cmd); - /* Commands that do not care about userid */ + /* Commands that do not care about userid: also these need to be backwards + * compatible with older versions of iodine (at least down to 00000502) */ if (cmd == 'V') { /* Version check - before userid is assigned*/ handle_dns_version(dns_fd, q, in, domain_len); return; } else if (cmd == 'Z') { /* Upstream codec check - user independent */ - /* Check for case conservation and chars not allowed according to RFC */ - /* Reply with received hostname as data (encoded in base32) */ write_dns(dns_fd, q, (char *)in, domain_len, 'T'); return; } - else if (cmd == 'Y') { /* Downstream codec check - user independent*/ + else if (cmd == 'Y') { /* Downstream codec check - user independent */ handle_dns_downstream_codec_check(dns_fd, q, in, domain_len); return; }