summaryrefslogtreecommitdiff
path: root/jni/iodine/src/client.c
diff options
context:
space:
mode:
authorYves Fischer <yvesf-git@xapek.org>2014-01-11 18:44:50 +0100
committerYves Fischer <yvesf-git@xapek.org>2014-01-11 18:48:48 +0100
commit002a2c3e1d0f091a48f8cc3eb7dce519870debaf (patch)
tree64140ef20603bcf66dc33b8f2c5416d006547cb1 /jni/iodine/src/client.c
downloadandiodine-002a2c3e1d0f091a48f8cc3eb7dce519870debaf.tar.gz
andiodine-002a2c3e1d0f091a48f8cc3eb7dce519870debaf.zip
import code
Diffstat (limited to 'jni/iodine/src/client.c')
-rw-r--r--jni/iodine/src/client.c2527
1 files changed, 2527 insertions, 0 deletions
diff --git a/jni/iodine/src/client.c b/jni/iodine/src/client.c
new file mode 100644
index 0000000..552344b
--- /dev/null
+++ b/jni/iodine/src/client.c
@@ -0,0 +1,2527 @@
+/*
+ * Copyright (c) 2006-2009 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
+ *
+ * Permission to use, copy, modify, and 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 <ctype.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <zlib.h>
+#include <time.h>
+
+#ifdef WINDOWS32
+#include "windows.h"
+#include <winsock2.h>
+#else
+#include <arpa/nameser.h>
+#ifdef DARWIN
+#define BIND_8_COMPAT
+#include <arpa/nameser_compat.h>
+#endif
+#include <grp.h>
+#include <pwd.h>
+#include <netdb.h>
+#endif
+
+#include "common.h"
+#include "encoding.h"
+#include "base32.h"
+#include "base64.h"
+#include "base64u.h"
+#include "base128.h"
+#include "dns.h"
+#include "login.h"
+#include "tun.h"
+#include "version.h"
+#include "client.h"
+
+static void handshake_lazyoff(int dns_fd);
+
+static int running;
+static const char *password;
+
+static struct sockaddr_in nameserv;
+static struct sockaddr_in raw_serv;
+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;
+
+/* 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;
+/* 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;
+
+/* The encoder to use for downstream data */
+static char downenc = ' ';
+
+/* set query type to send */
+static unsigned short 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 int 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 = ((unsigned int) rand()) & 0xFFFF;
+ send_ping_soon = 1; /* send ping immediately after startup */
+ 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;
+}
+
+void
+client_stop()
+{
+ running = 0;
+}
+
+enum connection
+client_get_conn()
+{
+ return conn;
+}
+
+void
+client_set_nameserver(const char *cp, int port)
+{
+ struct in_addr addr;
+
+ if (inet_aton(cp, &addr) != 1) {
+ /* try resolving if a domain is given */
+ struct hostent *host;
+ const char *err;
+ host = gethostbyname(cp);
+ if (host != NULL && h_errno > 0) {
+ int i = 0;
+ while (host->h_addr_list[i] != 0) {
+ addr = *(struct in_addr *) host->h_addr_list[i++];
+ fprintf(stderr, "Resolved %s to %s\n", cp, inet_ntoa(addr));
+ goto setaddr;
+ }
+ }
+#ifndef WINDOWS32
+ err = hstrerror(h_errno);
+#else
+ {
+ DWORD wserr = WSAGetLastError();
+ switch (wserr) {
+ case WSAHOST_NOT_FOUND:
+ err = "Host not found";
+ break;
+ case WSANO_DATA:
+ err = "No data record found";
+ break;
+ default:
+ err = "Unknown error";
+ break;
+ }
+ }
+#endif /* !WINDOWS32 */
+ errx(1, "error resolving nameserver '%s': %s", cp, err);
+ }
+
+setaddr:
+ memset(&nameserv, 0, sizeof(nameserv));
+ nameserv.sin_family = AF_INET;
+ nameserv.sin_port = htons(port);
+ nameserv.sin_addr = addr;
+}
+
+void
+client_set_topdomain(const char *cp)
+{
+ topdomain = cp;
+}
+
+void
+client_set_password(const char *cp)
+{
+ password = cp;
+}
+
+void
+set_qtype(char *qtype)
+{
+ if (!strcasecmp(qtype, "NULL"))
+ do_qtype = T_NULL;
+ else if (!strcasecmp(qtype, "CNAME"))
+ do_qtype = T_CNAME;
+ else if (!strcasecmp(qtype, "A"))
+ do_qtype = T_A;
+ else if (!strcasecmp(qtype, "MX"))
+ do_qtype = T_MX;
+ else if (!strcasecmp(qtype, "SRV"))
+ do_qtype = T_SRV;
+ else if (!strcasecmp(qtype, "TXT"))
+ do_qtype = T_TXT;
+}
+
+char *
+get_qtype()
+{
+ char *c = "UNDEFINED";
+
+ if (do_qtype == T_NULL) c = "NULL";
+ 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";
+
+ return c;
+}
+
+void
+set_downenc(char *encoding)
+{
+ if (!strcasecmp(encoding, "base32"))
+ downenc = 'T';
+ else if (!strcasecmp(encoding, "base64"))
+ downenc = 'S';
+ else if (!strcasecmp(encoding, "base64u"))
+ downenc = 'U';
+ else if (!strcasecmp(encoding, "base128"))
+ downenc = 'V';
+ else if (!strcasecmp(encoding, "raw"))
+ downenc = 'R';
+}
+
+void
+client_set_selecttimeout(int select_timeout)
+{
+ selecttimeout = select_timeout;
+}
+
+void
+client_set_lazymode(int lazy_mode)
+{
+ lazymode = lazy_mode;
+}
+
+void
+client_set_hostname_maxlen(int i)
+{
+ if (i <= 0xFF)
+ hostname_maxlen = i;
+}
+
+const char *
+client_get_raw_addr()
+{
+ return inet_ntoa(raw_serv.sin_addr);
+}
+
+static void
+send_query(int fd, char *hostname)
+{
+ char packet[4096];
+ 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 */
+ chunkid = 7727;
+
+ q.id = chunkid;
+ q.type = do_qtype;
+
+ len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, hostname, strlen(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
+
+ sendto(fd, packet, len, 0, (struct sockaddr*)&nameserv, sizeof(nameserv));
+
+ /* There are DNS relays that time out quickly but don't send anything
+ back on timeout.
+ 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.
+ 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.)
+ */
+
+ 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;
+ /* 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.");
+ lazymode = 0;
+ selecttimeout = 1;
+ handshake_lazyoff(fd);
+ }
+ }
+ }
+}
+
+static void
+send_raw(int fd, char *buf, int buflen, int user, int cmd)
+{
+ 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);
+
+ sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv));
+}
+
+static void
+send_raw_data(int dns_fd)
+{
+ 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)
+{
+ char buf[4096];
+
+ buf[0] = cmd;
+
+ build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain,
+ b32, hostname_maxlen);
+ send_query(fd, buf);
+}
+
+static inline int
+is_sending()
+{
+ return (outpkt.len != 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;
+
+ /* 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);
+
+ /* 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 */
+
+ 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 = ((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 */
+
+ 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)
+{
+ 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;
+
+ 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);
+ }
+}
+
+static void
+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.
+*/
+{
+ if (!q) return;
+
+ switch (q->rcode) {
+ case NOERROR: /* 0 */
+ if (!ignore_some_errors)
+ warnx("Got reply without error, but also without question and/or answer");
+ break;
+ case FORMERR: /* 1 */
+ warnx("Got FORMERR as reply: server does not understand our request");
+ break;
+ case SERVFAIL: /* 2 */
+ if (!ignore_some_errors)
+ warnx("Got SERVFAIL as reply: server failed or recursion timeout");
+ break;
+ case NXDOMAIN: /* 3 */
+ warnx("Got NXDOMAIN as reply: domain does not exist");
+ break;
+ case NOTIMP: /* 4 */
+ warnx("Got NOTIMP as reply: server does not support our request");
+ break;
+ case REFUSED: /* 5 */
+ warnx("Got REFUSED as reply");
+ break;
+ default:
+ warnx("Got RCODE %u as reply", q->rcode);
+ break;
+ }
+}
+
+static int
+dns_namedec(char *outdata, int outdatalen, char *buf, int buflen)
+/* Decodes *buf to *outdata.
+ * *buf WILL be changed by undotify.
+ * Note: buflen must be _exactly_ strlen(buf) before undotifying.
+ * (undotify of reduced-len won't copy \0, base-X decode will decode too much.)
+ * Returns #bytes usefully filled in outdata.
+ */
+{
+ size_t outdatalenu = outdatalen;
+
+ switch (buf[0]) {
+ case 'h': /* Hostname with base32 */
+ case 'H':
+ /* Need 1 byte H, 3 bytes ".xy", >=1 byte data */
+ if (buflen < 5)
+ return 0;
+
+ /* this also does undotify */
+ return unpack_data(outdata, outdatalen, buf + 1, buflen - 4,
+ b32);
+
+ case 'i': /* Hostname++ with base64 */
+ case 'I':
+ /* Need 1 byte I, 3 bytes ".xy", >=1 byte data */
+ if (buflen < 5)
+ return 0;
+
+ /* this also does undotify */
+ return unpack_data(outdata, outdatalen, buf + 1, buflen - 4,
+ b64);
+
+ case 'j': /* Hostname++ with base64u */
+ case 'J':
+ /* Need 1 byte J, 3 bytes ".xy", >=1 byte data */
+ if (buflen < 5)
+ return 0;
+
+ /* this also does undotify */
+ return unpack_data(outdata, outdatalen, buf + 1, buflen - 4,
+ b64u);
+
+ case 'k': /* Hostname++ with base128 */
+ case 'K':
+ /* Need 1 byte J, 3 bytes ".xy", >=1 byte data */
+ if (buflen < 5)
+ return 0;
+
+ /* this also does undotify */
+ return unpack_data(outdata, outdatalen, buf + 1, buflen - 4,
+ b128);
+
+ case 't': /* plain base32(Thirty-two) from TXT */
+ case 'T':
+ if (buflen < 2)
+ return 0;
+
+ return b32->decode(outdata, &outdatalenu, buf + 1, buflen - 1);
+
+ case 's': /* plain base64(Sixty-four) from TXT */
+ case 'S':
+ if (buflen < 2)
+ return 0;
+
+ return b64->decode(outdata, &outdatalenu, buf + 1, buflen - 1);
+
+ case 'u': /* plain base64u (Underscore) from TXT */
+ case 'U':
+ if (buflen < 2)
+ return 0;
+
+ return b64u->decode(outdata, &outdatalenu, buf + 1, buflen - 1);
+
+ case 'v': /* plain base128 from TXT */
+ case 'V':
+ if (buflen < 2)
+ return 0;
+
+ return b128->decode(outdata, &outdatalenu, buf + 1, buflen - 1);
+
+ case 'r': /* Raw binary from TXT */
+ case 'R':
+ /* buflen>=1 already checked */
+ buflen--;
+ buflen = MIN(buflen, outdatalen);
+ memcpy(outdata, buf + 1, buflen);
+ return buflen;
+
+ default:
+ warnx("Received unsupported encoding");
+ return 0;
+ }
+
+ /* notreached */
+ return 0;
+}
+
+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 */
+/* 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.
+ Returns >0 on correct replies; value is #valid bytes in *buf.
+*/
+{
+ struct sockaddr_in from;
+ char data[64*1024];
+ socklen_t addrlen;
+ int r;
+
+ addrlen = sizeof(struct sockaddr);
+ if ((r = recvfrom(dns_fd, data, sizeof(data), 0,
+ (struct sockaddr*)&from, &addrlen)) < 0) {
+ warn("recvfrom");
+ return -1;
+ }
+
+ if (conn == CONN_DNS_NULL) {
+ int rv;
+ if (r <= 0)
+ /* useless packet */
+ return 0;
+
+ rv = dns_decode(buf, buflen, q, QR_ANSWER, data, r);
+ if (rv <= 0)
+ return rv;
+
+ if (q->type == T_CNAME || q->type == T_TXT)
+ /* CNAME can also be returned from an A question */
+ {
+ /*
+ * buf is a hostname or txt stream that we still need to
+ * decode to binary
+ *
+ * also update rv with the number of valid bytes
+ *
+ * data is unused here, and will certainly hold the smaller binary
+ */
+
+ rv = dns_namedec(data, sizeof(data), buf, rv);
+
+ rv = MIN(rv, buflen);
+ if (rv > 0)
+ memcpy(buf, data, rv);
+
+ } else if (q->type == T_MX || q->type == T_SRV) {
+ /* buf is like "Hname.com\0Hanother.com\0\0" */
+ int buftotal = rv; /* idx of last \0 */
+ int bufoffset = 0;
+ int dataoffset = 0;
+ int thispartlen, dataspace, datanew;
+
+ while (1) {
+ thispartlen = strlen(buf);
+ thispartlen = MIN(thispartlen, buftotal-bufoffset);
+ dataspace = sizeof(data) - dataoffset;
+ if (thispartlen <= 0 || dataspace <= 0)
+ break;
+
+ datanew = dns_namedec(data + dataoffset, dataspace,
+ buf + bufoffset, thispartlen);
+ if (datanew <= 0)
+ break;
+
+ bufoffset += thispartlen + 1;
+ dataoffset += datanew;
+ }
+ rv = dataoffset;
+ rv = MIN(rv, buflen);
+ if (rv > 0)
+ memcpy(buf, data, rv);
+ }
+
+ return rv;
+ } else { /* CONN_RAW_UDP */
+ unsigned long datalen;
+ char buf[64*1024];
+
+ /* minimum length */
+ if (r < RAW_HDR_LEN) return 0;
+ /* should start with header */
+ 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_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;
+
+ 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);
+ }
+
+ /* don't process any further */
+ return 0;
+ }
+}
+
+static int
+handshake_waitdns(int dns_fd, char *buf, int 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.
+ Returns -2 on (at least) DNS error that fits to our latest query,
+ error message already printed.
+ Returns -3 on timeout (given in seconds).
+ Returns -1 on other errors.
+
+ Timeout is restarted when "wrong" (previous/delayed) replies are received,
+ so effective timeout may be longer than specified.
+*/
+{
+ struct query q;
+ int r, rv;
+ fd_set fds;
+ struct timeval tv;
+
+ while (1) {
+ 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);
+
+ if (r < 0)
+ return -1; /* select error */
+ if (r == 0)
+ return -3; /* select timeout */
+
+ q.id = 0;
+ q.name[0] = '\0';
+ rv = read_dns_withq(dns_fd, 0, 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
+ continue;
+ }
+
+ /* if still here: reply matches our latest query */
+
+ /* Non-recursive DNS servers (such as [a-m].root-servers.net)
+ return no answer, but only additional and authority records.
+ Can't explicitly test for that here, just assume that
+ NOERROR is such situation. Only trigger on the very first
+ requests (Y or V, depending if -T given).
+ */
+ if (rv < 0 && q.rcode == NOERROR &&
+ (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",
+ topdomain, topdomain);
+ return -2;
+ }
+
+ /* If we get an immediate SERVFAIL on the handshake query
+ we're waiting for, wait a while before sending the next.
+ SERVFAIL reliably happens during fragsize autoprobe, but
+ 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
+ is the only option that works.
+ */
+ if (rv < 0 && q.rcode == SERVFAIL)
+ sleep(1);
+
+ if (rv < 0) {
+ write_dns_error(&q, 1);
+ return -2;
+ }
+ /* rv either 0 or >0, return it as is. */
+ return rv;
+ }
+
+ /* not reached */
+ return -1;
+}
+
+static int
+tunnel_tun(int tun_fd, int dns_fd)
+{
+ unsigned long outlen;
+ unsigned long inlen;
+ char out[64*1024];
+ char 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())
+ return -1;
+
+ outlen = sizeof(out);
+ inlen = read;
+ compress2((uint8_t*)out, &outlen, (uint8_t*)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;
+
+ if (conn == CONN_DNS_NULL) {
+ send_chunk(dns_fd);
+
+ send_ping_soon = 0;
+ } else {
+ send_raw_data(dns_fd);
+ }
+
+ return read;
+}
+
+static int
+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;
+ struct query q;
+ unsigned long datalen;
+ char buf[64*1024];
+ int read;
+ int send_something_now = 0;
+
+ memset(q.name, 0, sizeof(q.name));
+ read = read_dns_withq(dns_fd, tun_fd, buf, sizeof(buf), &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
+
+ /* 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
+ 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;
+ return -1; /* nothing done */
+ }
+
+ if (read < 2) {
+ /* Maybe SERVFAIL etc. Send ping to get things back in order,
+ but wait a bit to prevent fast ping-pong loops. */
+
+ if (read < 0)
+ write_dns_error(&q, 0);
+
+ if (read < 0 && q.rcode == SERVFAIL && lazymode &&
+ selecttimeout > 1) {
+ 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) {
+ 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;
+ send_query_sendcnt = 0;
+ send_query_recvcnt = 0;
+ } else if (packrecv >= 500 && packrecv_servfail > 0) {
+ warnx("(Sorry, stopped counting; try -I1 if you experience hiccups.)");
+ packrecv_servfail = 0;
+ }
+ }
+
+ /* 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 */
+ }
+
+ if (read == 5 && !strncmp("BADIP", buf, 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 */
+ }
+
+ if (send_ping_soon) {
+ send_something_now = 1;
+ 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;
+
+#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 (!(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) {
+ write_tun(tun_fd, buf, datalen);
+ }
+ 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;
+ }
+
+ /* 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()). */
+ }
+
+
+ /* Send ping if we didn't send anything yet */
+ if (send_something_now) {
+ send_ping(dns_fd);
+ send_ping_soon = 0;
+ }
+
+ return read;
+}
+
+static int
+always_1()
+{
+ return 1;
+}
+
+int
+client_tunnel(int tun_fd, int dns_fd)
+{
+ return client_tunnel_cb(tun_fd, dns_fd, &always_1);
+}
+
+int
+client_tunnel_cb(int tun_fd, int dns_fd, int
+ (*cb_ask_continue)())
+{
+ struct timeval tv;
+ fd_set fds;
+ int rv;
+ int i;
+
+ rv = 0;
+ lastdownstreamtime = time(NULL);
+ send_query_sendcnt = 0; /* start counting now */
+
+ while (running && cb_ask_continue()) {
+ tv.tv_sec = selecttimeout;
+ tv.tv_usec = 0;
+
+ if (is_sending()) {
+ /* fast timeout for retransmits */
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ }
+
+ if (send_ping_soon) {
+ tv.tv_sec = 0;
+ tv.tv_usec = send_ping_soon * 1000;
+ }
+
+ 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. */
+ 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)) {
+ warnx("No downstream data received in 60 seconds, shutting down.");
+ running = 0;
+ }
+
+ if (running == 0)
+ break;
+
+ if (i < 0)
+ err(1, "select");
+
+ 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);
+ } else {
+ outpkt.offset = 0;
+ outpkt.len = 0;
+ outpkt.sentlen = 0;
+ outchunkresent = 0;
+
+ send_ping(dns_fd);
+ }
+ } else {
+ send_ping(dns_fd);
+ }
+ send_ping_soon = 0;
+
+ } else {
+
+ if (FD_ISSET(tun_fd, &fds)) {
+ if (tunnel_tun(tun_fd, dns_fd) <= 0)
+ continue;
+ /* 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 (FD_ISSET(dns_fd, &fds)) {
+ if (tunnel_dns(tun_fd, dns_fd) <= 0)
+ continue;
+ }
+ }
+ }
+
+ return rv;
+}
+
+static void
+send_login(int fd, char *login, int len)
+{
+ char data[19];
+
+ memset(data, 0, sizeof(data));
+ data[0] = userid;
+ memcpy(&data[1], login, MIN(len, 16));
+
+ data[17] = (rand_seed >> 8) & 0xff;
+ data[18] = (rand_seed >> 0) & 0xff;
+
+ rand_seed++;
+
+ send_packet(fd, 'l', data, sizeof(data));
+}
+
+static void
+send_fragsize_probe(int fd, int fragsize)
+{
+ char probedata[256];
+ char buf[4096];
+
+ /*
+ * build a large query domain which is random and maximum size,
+ * will also take up maximal 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 + 5, sizeof(buf) - 5, probedata, sizeof(probedata),
+ topdomain, dataenc, hostname_maxlen);
+
+ fragsize &= 2047;
+
+ 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() */
+
+ send_query(fd, buf);
+}
+
+static void
+send_set_downstream_fragsize(int fd, int fragsize)
+{
+ char data[5];
+
+ data[0] = userid;
+ data[1] = (fragsize & 0xff00) >> 8;
+ data[2] = (fragsize & 0x00ff);
+ data[3] = (rand_seed >> 8) & 0xff;
+ data[4] = (rand_seed >> 0) & 0xff;
+
+ rand_seed++;
+
+ send_packet(fd, 'n', data, sizeof(data));
+}
+
+static void
+send_version(int fd, uint32_t version)
+{
+ char data[6];
+
+ data[0] = (version >> 24) & 0xff;
+ data[1] = (version >> 16) & 0xff;
+ data[2] = (version >> 8) & 0xff;
+ data[3] = (version >> 0) & 0xff;
+
+ data[4] = (rand_seed >> 8) & 0xff;
+ data[5] = (rand_seed >> 0) & 0xff;
+
+ rand_seed++;
+
+ send_packet(fd, 'v', data, sizeof(data));
+}
+
+static void
+send_ip_request(int fd, int userid)
+{
+ char buf[512] = "i____.";
+ buf[1] = b32_5to8(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++;
+
+ strncat(buf, topdomain, 512 - strlen(buf));
+ send_query(fd, buf);
+}
+
+static void
+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);
+}
+
+static void
+send_upenctest(int fd, char *s)
+/* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */
+{
+ 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++;
+
+ strncat(buf, s, 512);
+ strncat(buf, ".", 512);
+ strncat(buf, topdomain, 512 - strlen(buf));
+ send_query(fd, buf);
+}
+
+static void
+send_downenctest(int fd, char downenc, int variant, char *s, int slen)
+/* Note: content/handling of s is not defined yet. */
+{
+ char buf[512] = "y_____.";
+
+ buf[1] = tolower(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++;
+
+ strncat(buf, topdomain, 512 - strlen(buf));
+ send_query(fd, buf);
+}
+
+static void
+send_codec_switch(int fd, int userid, int bits)
+{
+ char buf[512] = "s_____.";
+ buf[1] = b32_5to8(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++;
+
+ strncat(buf, topdomain, 512 - strlen(buf));
+ send_query(fd, 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, buf);
+}
+
+static void
+send_lazy_switch(int fd, int userid)
+{
+ char buf[512] = "o_____.";
+ buf[1] = b32_5to8(userid);
+
+ if (lazymode)
+ buf[2] = 'l';
+ else
+ buf[2] = 'i';
+
+ 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, buf);
+}
+
+static int
+handshake_version(int dns_fd, int *seed)
+{
+ char hex[] = "0123456789abcdef";
+ char hex2[] = "0123456789ABCDEF";
+ char in[4096];
+ uint32_t payload;
+ int i;
+ int read;
+
+ for (i = 0; running && i < 5; i++) {
+
+ send_version(dns_fd, VERSION);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'v', 'V', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read >= 9) {
+ payload = (((in[4] & 0xff) << 24) |
+ ((in[5] & 0xff) << 16) |
+ ((in[6] & 0xff) << 8) |
+ ((in[7] & 0xff)));
+
+ if (strncmp("VACK", in, 4) == 0) {
+ *seed = payload;
+ userid = in[8];
+ userid_char = hex[userid & 15];
+ userid_char2 = hex2[userid & 15];
+
+ fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", VERSION, userid);
+ return 0;
+ } else if (strncmp("VNAK", in, 4) == 0) {
+ warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up",
+ VERSION, payload);
+ return 1;
+ } else if (strncmp("VFUL", in, 4) == 0) {
+ warnx("Server full, all %d slots are taken. Try again later", payload);
+ return 1;
+ }
+ } else if (read > 0)
+ warnx("did not receive proper login challenge");
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, "Retrying version check...\n");
+ }
+ warnx("couldn't connect to server (maybe other -T options will work)");
+ return 1;
+}
+
+static int
+handshake_login(int dns_fd, int seed)
+{
+ char in[4096];
+ char login[16];
+ char server[65];
+ char client[65];
+ int mtu;
+ int i;
+ int read;
+
+ login_calculate(login, 16, password, seed);
+
+ for (i=0; running && i<5 ;i++) {
+
+ send_login(dns_fd, login, 16);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'l', 'L', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read > 0) {
+ int netmask;
+ 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) {
+
+ 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\n");
+ }
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, "Retrying login...\n");
+ }
+ warnx("couldn't login to server");
+ return 1;
+}
+
+static int
+handshake_raw_udp(int dns_fd, int seed)
+{
+ struct timeval tv;
+ char in[4096];
+ fd_set fds;
+ int i;
+ int r;
+ int len;
+ unsigned remoteaddr = 0;
+ struct in_addr server;
+
+ fprintf(stderr, "Testing raw UDP data to the server (skip with -r)");
+ for (i=0; running && i<3 ;i++) {
+
+ send_ip_request(dns_fd, userid);
+
+ len = handshake_waitdns(dns_fd, in, sizeof(in), 'i', 'I', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (len == 5 && in[0] == 'I') {
+ /* Received IP address */
+ remoteaddr = (in[1] & 0xff);
+ remoteaddr <<= 8;
+ remoteaddr |= (in[2] & 0xff);
+ remoteaddr <<= 8;
+ remoteaddr |= (in[3] & 0xff);
+ remoteaddr <<= 8;
+ remoteaddr |= (in[4] & 0xff);
+ server.s_addr = ntohl(remoteaddr);
+ break;
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, ".");
+ fflush(stderr);
+ }
+ fprintf(stderr, "\n");
+ if (!running)
+ return 0;
+
+ if (!remoteaddr) {
+ fprintf(stderr, "Failed to get raw server IP, will use DNS mode.\n");
+ return 0;
+ }
+ fprintf(stderr, "Server is at %s, trying raw login: ", inet_ntoa(server));
+ fflush(stderr);
+
+ /* Store address to iodined server */
+ memset(&raw_serv, 0, sizeof(raw_serv));
+ raw_serv.sin_family = AF_INET;
+ raw_serv.sin_port = htons(53);
+ raw_serv.sin_addr = server;
+
+ /* 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++) {
+ tv.tv_sec = i + 1;
+ tv.tv_usec = 0;
+
+ send_raw_udp_login(dns_fd, userid, seed);
+
+ FD_ZERO(&fds);
+ FD_SET(dns_fd, &fds);
+
+ r = select(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);
+ if (len >= (16 + RAW_HDR_LEN)) {
+ char hash[16];
+ login_calculate(hash, 16, 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) {
+
+ fprintf(stderr, "OK\n");
+ return 1;
+ }
+ }
+ }
+ fprintf(stderr, ".");
+ fflush(stderr);
+ }
+
+ fprintf(stderr, "failed\n");
+ return 0;
+}
+
+static int
+handshake_upenctest(int dns_fd, 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
+ 0: not identical or error or timeout
+ 1: identical string returned
+*/
+{
+ char in[4096];
+ unsigned char *uin = (unsigned char *) in;
+ unsigned char *us = (unsigned char *) s;
+ int i;
+ int read;
+ int slen;
+
+ slen = strlen(s);
+ for (i=0; running && i<3 ;i++) {
+
+ send_upenctest(dns_fd, s);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'z', 'Z', i+1);
+
+ if (read == -2)
+ return 0; /* hard error */
+
+ if (read > 0 && read < slen + 4)
+ return 0; /* reply too short (chars dropped) */
+
+ 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");
+ return -1;
+ }
+ if (in[5] == 'a') {
+ fprintf(stderr, "DNS queries get changed to lowercase, keeping upstream codec Base32\n");
+ return -1;
+ }
+
+ for (k = 0; k < slen; k++) {
+ if (in[k+4] != s[k]) {
+ /* Definitely not reliable */
+ if (in[k+4] >= ' ' && in[k+4] <= '~' &&
+ s[k] >= ' ' && s[k] <= '~') {
+ fprintf(stderr, "DNS query char '%c' gets changed into '%c'\n",
+ s[k], in[k+4]);
+ } else {
+ fprintf(stderr, "DNS query char 0x%02X gets changed into 0x%02X\n",
+ (unsigned int) us[k],
+ (unsigned int) uin[k+4]);
+ }
+ return 0;
+ }
+ }
+ /* if still here, then all okay */
+ return 1;
+ }
+
+ fprintf(stderr, "Retrying upstream codec test...\n");
+ }
+
+ if (!running)
+ return -1;
+
+ /* timeout */
+ return 0;
+}
+
+static int
+handshake_upenc_autodetect(int dns_fd)
+/* Returns:
+ 0: keep Base32
+ 1: Base64 is okay
+ 2: Base64u is okay
+ 3: Base128 is okay
+*/
+{
+ /* Note: max 59 chars, must start with "aA".
+ pat64: If 0129 work, assume 3-8 are okay too.
+
+ RFC1035 par 2.3.1 states that [A-Z0-9-] allowed, but only
+ [A-Z] as first, and [A-Z0-9] as last char _per label_.
+ Test by having '-' as last char.
+ */
+ char *pat64="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ+0129-";
+ char *pat64u="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ_0129-";
+ char *pat128a="aA-Aaahhh-Drink-mal-ein-J\344germeister-";
+ char *pat128b="aA-La-fl\373te-na\357ve-fran\347aise-est-retir\351-\340-Cr\350te";
+ char *pat128c="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
+ char *pat128d="aA0123456789\274\275\276\277"
+ "\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317";
+ char *pat128e="aA"
+ "\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";
+ int res;
+
+ /* Try Base128, starting very gently to not draw attention */
+ while (1) {
+ res = handshake_upenctest(dns_fd, pat128a);
+ if (res < 0) {
+ /* DNS swaps case, msg already printed; or Ctrl-C */
+ return 0;
+ } else if (res == 0) {
+ /* Probably not okay, skip Base128 entirely */
+ break;
+ }
+
+ res = handshake_upenctest(dns_fd, pat128b);
+ if (res < 0)
+ return 0;
+ else if (res == 0)
+ break;
+
+ /* if this works, we can test the real stuff */
+
+ res = handshake_upenctest(dns_fd, pat128c);
+ if (res < 0)
+ return 0;
+ else if (res == 0)
+ break;
+
+ res = handshake_upenctest(dns_fd, pat128d);
+ if (res < 0)
+ return 0;
+ else if (res == 0)
+ break;
+
+ res = handshake_upenctest(dns_fd, pat128e);
+ if (res < 0)
+ return 0;
+ else if (res == 0)
+ break;
+
+ /* if still here, then base128 works completely */
+ return 3;
+ }
+
+ /* Try Base64 (with plus sign) */
+ res = handshake_upenctest(dns_fd, pat64);
+ if (res < 0) {
+ /* DNS swaps case, msg already printed; or Ctrl-C */
+ return 0;
+ } else if (res > 0) {
+ /* All okay, Base64 msg will be printed later */
+ return 1;
+ }
+
+ /* Try Base64u (with _u_nderscore) */
+ res = handshake_upenctest(dns_fd, pat64u);
+ if (res < 0) {
+ /* DNS swaps case, msg already printed; or Ctrl-C */
+ return 0;
+ } else if (res > 0) {
+ /* All okay, Base64u msg will be printed later */
+ return 2;
+ }
+
+ /* if here, then nonthing worked */
+ fprintf(stderr, "Keeping upstream codec Base32\n");
+ return 0;
+}
+
+static int
+handshake_downenctest(int dns_fd, char trycodec)
+/* Returns:
+ 0: not identical or error or timeout
+ 1: identical string returned
+*/
+{
+ char in[4096];
+ int i;
+ int read;
+ char *s = DOWNCODECCHECK1;
+ int slen = DOWNCODECCHECK1_LEN;
+
+ for (i=0; running && i<3 ;i++) {
+
+ send_downenctest(dns_fd, trycodec, 1, NULL, 0);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1);
+
+ if (read == -2)
+ return 0; /* hard error */
+
+ if (read > 0 && read != slen)
+ return 0; /* reply incorrect = unreliable */
+
+ if (read > 0) {
+ int k;
+ for (k = 0; k < slen; k++) {
+ if (in[k] != s[k]) {
+ /* Definitely not reliable */
+ return 0;
+ }
+ }
+ /* if still here, then all okay */
+ return 1;
+ }
+
+ fprintf(stderr, "Retrying downstream codec test...\n");
+ }
+
+ /* timeout */
+ return 0;
+}
+
+static char
+handshake_downenc_autodetect(int dns_fd)
+/* Returns codec char (or ' ' if no advanced codec works) */
+{
+ int base64ok = 0;
+ int base64uok = 0;
+ int base128ok = 0;
+
+ if (do_qtype == T_NULL) {
+ /* no other choice than raw */
+ fprintf(stderr, "No alternative downstream codec available, using default (Raw)\n");
+ return ' ';
+ }
+
+ fprintf(stderr, "Autodetecting downstream codec (use -O to override)\n");
+
+ /* Try Base64 */
+ if (handshake_downenctest(dns_fd, 'S'))
+ base64ok = 1;
+ else if (running && handshake_downenctest(dns_fd, 'U'))
+ base64uok = 1;
+
+ /* Try Base128 only if 64 gives us some perspective */
+ if (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 (handshake_downenctest(dns_fd, 'R'))
+ return 'R';
+ }
+
+ if (!running)
+ return ' ';
+
+ if (base128ok)
+ return 'V';
+ if (base64ok)
+ return 'S';
+ if (base64uok)
+ return 'U';
+
+ fprintf(stderr, "No advanced downstream codecs seem to work, using default (Base32)\n");
+ return ' ';
+}
+
+static int
+handshake_qtypetest(int dns_fd, int timeout)
+/* Returns:
+ 0: doesn't work with this timeout
+ 1: works properly
+*/
+{
+ char in[4096];
+ int read;
+ char *s = DOWNCODECCHECK1;
+ int slen = DOWNCODECCHECK1_LEN;
+ int trycodec;
+ int k;
+
+ if (do_qtype == T_NULL)
+ trycodec = 'R';
+ else
+ trycodec = 'T';
+
+ /* We could use 'Z' bouncing here, but 'Y' also tests that 0-255
+ byte values can be returned, which is needed for NULL to work. */
+
+ send_downenctest(dns_fd, trycodec, 1, NULL, 0);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', timeout);
+
+ if (read != slen)
+ return 0; /* incorrect */
+
+ for (k = 0; k < slen; k++) {
+ if (in[k] != s[k]) {
+ /* corrupted */
+ return 0;
+ }
+ }
+
+ /* if still here, then all okay */
+ return 1;
+}
+
+static int
+handshake_qtype_numcvt(int num)
+{
+ switch (num) {
+ case 0: return T_NULL;
+ case 1: return T_TXT;
+ case 2: return T_SRV;
+ case 3: return T_MX;
+ case 4: return T_CNAME;
+ case 5: return T_A;
+ }
+ return T_UNSET;
+}
+
+static int
+handshake_qtype_autodetect(int dns_fd)
+/* Returns:
+ 0: okay, do_qtype set
+ 1: problem, program exit
+*/
+{
+ int highestworking = 100;
+ int timeout;
+ int qtypenum;
+
+ fprintf(stderr, "Autodetecting DNS query type (use -T to override)");
+ fflush(stderr);
+
+ /* Method: try all "interesting" qtypes with a 1-sec timeout, then try
+ all "still-interesting" qtypes with a 2-sec timeout, etc.
+ "Interesting" means: qtypes that (are expected to) have higher
+ bandwidth than what we know is working already (highestworking).
+
+ Note that DNS relays may not immediately resolve the first (NULL)
+ query in 1 sec, due to long recursive lookups, so we keep trying
+ 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)
+ break; /* this round finished */
+
+ fprintf(stderr, ".");
+ fflush(stderr);
+
+ if (handshake_qtypetest(dns_fd, timeout)) {
+ /* okay */
+ highestworking = qtypenum;
+#if 0
+ fprintf(stderr, " Type %s timeout %d works\n",
+ get_qtype(), timeout);
+#endif
+ break;
+ /* try others with longer timeout */
+ }
+ /* else: try next qtype with same timeout */
+ }
+ if (highestworking == 0)
+ /* good, we have NULL; abort immediately */
+ break;
+ }
+
+ fprintf(stderr, "\n");
+
+ if (!running) {
+ warnx("Stopped while autodetecting DNS query type (try setting manually with -T)");
+ return 1; /* problem */
+ }
+
+ /* finished */
+ do_qtype = handshake_qtype_numcvt(highestworking);
+
+ if (do_qtype == T_UNSET) {
+ /* also catches highestworking still 100 */
+ warnx("No suitable DNS query type found. Are you 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 */
+ }
+
+ /* "using qtype" message printed in handshake function */
+ return 0; /* okay */
+}
+
+static int
+handshake_edns0_check(int dns_fd)
+/* Returns:
+ 0: EDNS0 not supported; or Ctrl-C
+ 1: EDNS0 works
+*/
+{
+ char in[4096];
+ int i;
+ int read;
+ char *s = DOWNCODECCHECK1;
+ int slen = DOWNCODECCHECK1_LEN;
+ char trycodec;
+
+ if (do_qtype == T_NULL)
+ trycodec = 'R';
+ else
+ trycodec = 'T';
+
+ for (i=0; running && i<3 ;i++) {
+
+ send_downenctest(dns_fd, trycodec, 1, NULL, 0);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1);
+
+ if (read == -2)
+ return 0; /* hard error */
+
+ if (read > 0 && read != slen)
+ return 0; /* reply incorrect = unreliable */
+
+ if (read > 0) {
+ int k;
+ for (k = 0; k < slen; k++) {
+ if (in[k] != s[k]) {
+ /* Definitely not reliable */
+ return 0;
+ }
+ }
+ /* if still here, then all okay */
+ return 1;
+ }
+
+ fprintf(stderr, "Retrying EDNS0 support test...\n");
+ }
+
+ /* timeout or Ctrl-C */
+ return 0;
+}
+
+static void
+handshake_switch_codec(int dns_fd, int bits)
+{
+ char in[4096];
+ int i;
+ int read;
+ struct encoder *tempenc;
+
+ if (bits == 5)
+ tempenc = get_base32_encoder();
+ else if (bits == 6)
+ tempenc = get_base64_encoder();
+ else if (bits == 26) /* "2nd" 6 bits per byte, with underscore */
+ tempenc = get_base64u_encoder();
+ else if (bits == 7)
+ tempenc = get_base128_encoder();
+ else return;
+
+ fprintf(stderr, "Switching upstream to codec %s\n", tempenc->name);
+
+ for (i=0; running && i<5 ;i++) {
+
+ send_codec_switch(dns_fd, userid, bits);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 's', 'S', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read > 0) {
+ if (strncmp("BADLEN", in, 6) == 0) {
+ fprintf(stderr, "Server got bad message length. ");
+ goto codec_revert;
+ } else if (strncmp("BADIP", in, 5) == 0) {
+ fprintf(stderr, "Server rejected sender IP address. ");
+ goto codec_revert;
+ } else if (strncmp("BADCODEC", in, 8) == 0) {
+ fprintf(stderr, "Server rejected the selected codec. ");
+ goto codec_revert;
+ }
+ in[read] = 0; /* zero terminate */
+ fprintf(stderr, "Server switched upstream to codec %s\n", in);
+ dataenc = tempenc;
+ return;
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, "Retrying codec switch...\n");
+ }
+ if (!running)
+ return;
+
+ fprintf(stderr, "No reply from server on codec switch. ");
+
+codec_revert:
+ fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name);
+}
+
+static void
+handshake_switch_downenc(int dns_fd)
+{
+ char in[4096];
+ int i;
+ int read;
+ char *dname;
+
+ dname = "Base32";
+ if (downenc == 'S')
+ dname = "Base64";
+ else if (downenc == 'U')
+ dname = "Base64u";
+ else if (downenc == 'V')
+ dname = "Base128";
+ else if (downenc == 'R')
+ dname = "Raw";
+
+ fprintf(stderr, "Switching downstream to codec %s\n", dname);
+ for (i=0; running && i<5 ;i++) {
+
+ send_downenc_switch(dns_fd, userid);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read > 0) {
+ if (strncmp("BADLEN", in, 6) == 0) {
+ fprintf(stderr, "Server got bad message length. ");
+ goto codec_revert;
+ } else if (strncmp("BADIP", in, 5) == 0) {
+ fprintf(stderr, "Server rejected sender IP address. ");
+ goto codec_revert;
+ } else if (strncmp("BADCODEC", in, 8) == 0) {
+ fprintf(stderr, "Server rejected the selected codec. ");
+ goto codec_revert;
+ }
+ in[read] = 0; /* zero terminate */
+ fprintf(stderr, "Server switched downstream to codec %s\n", in);
+ return;
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, "Retrying codec switch...\n");
+ }
+ if (!running)
+ return;
+
+ fprintf(stderr, "No reply from server on codec switch. ");
+
+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', 'O', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read > 0) {
+ if (strncmp("BADLEN", in, 6) == 0) {
+ fprintf(stderr, "Server got bad message length. ");
+ goto codec_revert;
+ } else if (strncmp("BADIP", in, 5) == 0) {
+ fprintf(stderr, "Server rejected sender IP address. ");
+ goto codec_revert;
+ } else if (strncmp("BADCODEC", in, 8) == 0) {
+ fprintf(stderr, "Server rejected lazy mode. ");
+ goto codec_revert;
+ } else if (strncmp("Lazy", in, 4) == 0) {
+ fprintf(stderr, "Server switched to lazy mode\n");
+ lazymode = 1;
+ return;
+ }
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, "Retrying lazy mode switch...\n");
+ }
+ if (!running)
+ return;
+
+ fprintf(stderr, "No reply from server on lazy switch. ");
+
+codec_revert:
+ fprintf(stderr, "Falling back to legacy mode\n");
+ lazymode = 0;
+ selecttimeout = 1;
+}
+
+static 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', 'O', 1);
+
+ /*XXX START adjust indent 2 tabs back*/
+ if (read == 9 && strncmp("Immediate", in, 9) == 0) {
+ warnx("Server switched back to legacy mode.\n");
+ lazymode = 0;
+ selecttimeout = 1;
+ return;
+ }
+ /*XXX END adjust indent 2 tabs back*/
+ }
+ if (!running)
+ return;
+
+ warnx("No reply from server on legacy mode switch.\n");
+}
+
+static int
+fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize)
+/* Returns: 0: keep checking, 1: break loop (either okay or definitely wrong) */
+{
+ int acked_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff);
+ int okay;
+ int i;
+ unsigned int v;
+
+ if (read >= 5 && strncmp("BADIP", in, 5) == 0) {
+ fprintf(stderr, "got BADIP (Try iodined -c)..\n");
+ fflush(stderr);
+ return 0; /* maybe temporary error */
+ }
+
+ if (acked_fragsize != proposed_fragsize) {
+ /*
+ * got ack for wrong fragsize, maybe late response for
+ * earlier query, or ack corrupted
+ */
+ return 0;
+ }
+
+ if (read != proposed_fragsize) {
+ /*
+ * correctly acked fragsize but read too little (or too
+ * much): this fragsize is definitely not reliable
+ */
+ return 1;
+ }
+
+ /* here: read == proposed_fragsize == acked_fragsize */
+
+ /* test: */
+ /* 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.");
+ *max_fragsize = -1;
+ return 1;
+ }
+
+ /* Check for corruption */
+ okay = 1;
+ v = in[3] & 0xff;
+
+ /*XXX START adjust indent 1 tab back*/
+ for (i = 3; i < read; i++, v = (v + 107) & 0xff)
+ if ((in[i] & 0xff) != v) {
+ okay = 0;
+ break;
+ }
+
+ if (okay) {
+ fprintf(stderr, "%d ok.. ", acked_fragsize);
+ fflush(stderr);
+ *max_fragsize = acked_fragsize;
+ return 1;
+ } else {
+ if (downenc != ' ' && 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);
+ }
+ fflush(stderr);
+ return 1;
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ /* notreached */
+ return 1;
+}
+
+
+static int
+handshake_autoprobe_fragsize(int dns_fd)
+{
+ char in[4096];
+ int i;
+ int read;
+ int proposed_fragsize = 768;
+ int range = 768;
+ int max_fragsize;
+
+ max_fragsize = 0;
+ fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)\n");
+ 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++) {
+
+ send_fragsize_probe(dns_fd, proposed_fragsize);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'r', 'R', 1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read > 0) {
+ /* We got a reply */
+ if (fragsize_check(in, read, proposed_fragsize, &max_fragsize) == 1)
+ break;
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, ".");
+ fflush(stderr);
+ }
+ if (max_fragsize < 0)
+ break;
+
+ range >>= 1;
+ if (max_fragsize == proposed_fragsize) {
+ /* Try bigger */
+ proposed_fragsize += range;
+ } else {
+ /* Try smaller */
+ fprintf(stderr, "%d not ok.. ", proposed_fragsize);
+ fflush(stderr);
+ proposed_fragsize -= range;
+ }
+ }
+ if (!running) {
+ fprintf(stderr, "\n");
+ warnx("stopped while autodetecting fragment size (Try setting manually with -m)");
+ return 0;
+ }
+ if (max_fragsize <= 2) {
+ /* 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("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);
+
+ /* need 1200 / 16frags = 75 bytes fragsize */
+ if (max_fragsize < 82) {
+ 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_TXT ||
+ do_qtype == T_SRV || 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");
+ }
+
+ return max_fragsize - 2;
+}
+
+static void
+handshake_set_fragsize(int dns_fd, int fragsize)
+{
+ char in[4096];
+ int i;
+ int read;
+
+ fprintf(stderr, "Setting downstream fragment size to max %d...\n", fragsize);
+ for (i=0; running && i<5 ;i++) {
+
+ send_set_downstream_fragsize(dns_fd, fragsize);
+
+ read = handshake_waitdns(dns_fd, in, sizeof(in), 'n', 'N', i+1);
+
+ /*XXX START adjust indent 1 tab back*/
+ if (read > 0) {
+ int accepted_fragsize;
+
+ if (strncmp("BADFRAG", in, 7) == 0) {
+ fprintf(stderr, "Server rejected fragsize. Keeping default.");
+ return;
+ } else if (strncmp("BADIP", in, 5) == 0) {
+ fprintf(stderr, "Server rejected sender IP address.\n");
+ return;
+ }
+
+ accepted_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff);
+ return;
+ }
+ /*XXX END adjust indent 1 tab back*/
+
+ fprintf(stderr, "Retrying set fragsize...\n");
+ }
+ if (!running)
+ return;
+
+ fprintf(stderr, "No reply from server when setting fragsize. Keeping default.\n");
+}
+
+int
+client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize)
+{
+ int seed;
+ int upcodec;
+ int r;
+
+ dnsc_use_edns0 = 0;
+
+ /* qtype message printed in handshake function */
+ if (do_qtype == T_UNSET) {
+ r = handshake_qtype_autodetect(dns_fd);
+ if (r) {
+ return r;
+ }
+ }
+
+ fprintf(stderr, "Using DNS type %s queries\n", get_qtype());
+
+ r = handshake_version(dns_fd, &seed);
+ if (r) {
+ return r;
+ }
+
+ r = handshake_login(dns_fd, seed);
+ if (r) {
+ return r;
+ }
+
+ if (raw_mode && handshake_raw_udp(dns_fd, seed)) {
+ conn = CONN_RAW_UDP;
+ selecttimeout = 20;
+ } else {
+ if (raw_mode == 0) {
+ fprintf(stderr, "Skipping raw mode\n");
+ }
+
+ dnsc_use_edns0 = 1;
+ if (handshake_edns0_check(dns_fd) && running) {
+ fprintf(stderr, "Using EDNS0 extension\n");
+ } else if (!running) {
+ return -1;
+ } else {
+ fprintf(stderr, "DNS relay does not support EDNS0 extension\n");
+ dnsc_use_edns0 = 0;
+ }
+
+ upcodec = handshake_upenc_autodetect(dns_fd);
+ if (!running)
+ return -1;
+
+ if (upcodec == 1) {
+ handshake_switch_codec(dns_fd, 6);
+ } else if (upcodec == 2) {
+ handshake_switch_codec(dns_fd, 26);
+ } else if (upcodec == 3) {
+ handshake_switch_codec(dns_fd, 7);
+ }
+ if (!running)
+ return -1;
+
+ if (downenc == ' ') {
+ downenc = handshake_downenc_autodetect(dns_fd);
+ }
+ if (!running)
+ return -1;
+
+ if (downenc != ' ') {
+ handshake_switch_downenc(dns_fd);
+ }
+ if (!running)
+ return -1;
+
+ if (lazymode) {
+ handshake_try_lazy(dns_fd);
+ }
+ if (!running)
+ return -1;
+
+ if (autodetect_frag_size) {
+ fragsize = handshake_autoprobe_fragsize(dns_fd);
+ if (!fragsize) {
+ return 1;
+ }
+ }
+
+ handshake_set_fragsize(dns_fd, fragsize);
+ if (!running)
+ return -1;
+ }
+
+ return 0;
+}
+