iodine/src/client.c

2636 lines
65 KiB
C
Raw Normal View History

/*
2014-06-01 06:46:42 +00:00
* Copyright (c) 2006-2014 Erik Ekman <yarrick@kryo.se>,
* 2006-2009 Bjorn Andersson <flex@kryo.se>,
* 2015 Frekk van Blagh <frekk@frekkworks.com>
*
* 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.
*/
2009-09-20 15:11:14 +00:00
#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <unistd.h>
#include <sys/param.h>
#include <fcntl.h>
#include <zlib.h>
#include <sys/time.h>
#include <time.h>
#ifdef WINDOWS32
#include "windows.h"
#else
#include <arpa/nameser.h>
#ifdef ANDROID
#include "android_dns.h"
#endif
#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"
2009-12-29 20:00:57 +00:00
#include "base64u.h"
#include "base128.h"
#include "dns.h"
#include "login.h"
#include "tun.h"
#include "version.h"
#include "window.h"
#include "client.h"
int debug;
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 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;
2009-12-29 20:00:57 +00:00
static char userid_char; /* used when sending (lowercase) */
static char userid_char2; /* also accepted when receiving (uppercase) */
static uint16_t chunkid;
2009-09-20 15:11:14 +00:00
/* Base32 encoder used for non-data packets and replies */
static struct encoder *b32;
2009-12-29 20:00:57 +00:00
/* Base64 etc encoders for replies */
2009-09-20 15:11:14 +00:00
static struct encoder *b64;
2009-12-29 20:00:57 +00:00
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;
2014-06-01 06:34:18 +00:00
/* Upstream/downstream compression flags */
static int compression_up;
static int compression_down;
2009-09-20 15:11:14 +00:00
/* The encoder to use for downstream data */
static char downenc = ' ';
2014-06-01 06:34:18 +00:00
2009-09-20 15:11:14 +00:00
/* set query type to send */
static uint16_t do_qtype = T_UNSET;
/* My connection mode */
static enum connection conn;
2009-12-29 20:00:57 +00:00
static int lazymode;
static long send_ping_soon;
static time_t lastdownstreamtime;
static size_t hostname_maxlen = 0xFF;
2009-09-20 21:10:44 +00:00
void
client_init()
{
running = 1;
b32 = get_base32_encoder();
2009-09-20 15:11:14 +00:00
b64 = get_base64_encoder();
2009-12-29 20:00:57 +00:00
b64u = get_base64u_encoder();
b128 = get_base128_encoder();
dataenc = get_base32_encoder();
rand_seed = (uint16_t) rand();
2009-09-20 21:10:44 +00:00
send_ping_soon = 1; /* send ping immediately after startup */
conn = CONN_DNS_NULL;
2009-09-20 21:10:44 +00:00
chunkid = (uint16_t) rand();
2009-09-20 21:10:44 +00:00
/* 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
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)
2009-09-20 15:11:14 +00:00
{
if (!strcasecmp(qtype, "NULL"))
do_qtype = T_NULL;
else if (!strcasecmp(qtype, "PRIVATE"))
do_qtype = T_PRIVATE;
2009-09-20 15:11:14 +00:00
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;
2009-12-29 20:00:57 +00:00
else if (!strcasecmp(qtype, "SRV"))
do_qtype = T_SRV;
2009-09-20 15:11:14 +00:00
else if (!strcasecmp(qtype, "TXT"))
do_qtype = T_TXT;
return (do_qtype == T_UNSET);
2009-09-20 15:11:14 +00:00
}
2009-12-29 20:00:57 +00:00
char *
client_get_qtype()
2009-12-29 20:00:57 +00:00
{
char *c = "UNDEFINED";
if (do_qtype == T_NULL) c = "NULL";
else if (do_qtype == T_PRIVATE) c = "PRIVATE";
2009-12-29 20:00:57 +00:00
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;
}
2009-09-20 15:11:14 +00:00
void
client_set_downenc(char *encoding)
2009-09-20 15:11:14 +00:00
{
if (!strcasecmp(encoding, "base32"))
downenc = 'T';
else if (!strcasecmp(encoding, "base64"))
downenc = 'S';
2009-12-29 20:00:57 +00:00
else if (!strcasecmp(encoding, "base64u"))
downenc = 'U';
else if (!strcasecmp(encoding, "base128"))
downenc = 'V';
2009-09-20 15:11:14 +00:00
else if (!strcasecmp(encoding, "raw"))
downenc = 'R';
}
2014-06-01 06:34:18 +00:00
void
client_set_compression(int up, int down)
{
compression_up = up;
compression_down = down;
}
void
client_set_dnstimeout(double timeout, double servertimeout, int autodetect)
2009-09-20 21:10:44 +00:00
{
max_timeout_ms = timeout * 1000;
server_timeout_ms = servertimeout * 1000;
autodetect_server_timeout = autodetect;
2009-09-20 21:10:44 +00:00
}
void
2009-09-20 21:10:46 +00:00
client_set_lazymode(int lazy_mode)
{
lazymode = lazy_mode;
2009-09-20 21:10:44 +00:00
}
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;
}
2009-12-29 20:00:57 +00:00
void
client_set_hostname_maxlen(size_t i)
2009-12-29 20:00:57 +00:00
{
if (i <= 0xFF && i != hostname_maxlen) {
2009-12-29 20:00:57 +00:00
hostname_maxlen = i;
maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain);
if (outbuf)
outbuf->maxfraglen = maxfragsize_up;
}
2009-12-29 20:00:57 +00:00
}
const char *
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;
}
/* 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;
size_t len;
if (debug >= 2)
fprintf(stderr, "TX: pkt len %lu: hostname '%s'\n", strlen((char *)hostname), hostname);
2009-09-20 15:11:14 +00:00
chunkid += 7727;
if (chunkid == 0)
/* 0 is used as "no-query" in iodined.c */
chunkid = rand() & 0xFF;
2009-09-20 15:11:14 +00:00
q.id = chunkid;
q.type = do_qtype;
len = dns_encode((char *)packet, sizeof(packet), &q, QR_QUERY, (char *)hostname, strlen((char *)hostname));
2009-09-20 15:11:14 +00:00
if (len < 1) {
warnx("dns_encode doesn't fit");
return -1;
2009-09-20 15:11:14 +00:00
}
if (debug >= 3)
fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]);
2009-12-29 20:00:57 +00:00
sendto(fd, packet, len, 0, (struct sockaddr*) &nameserv_addrs[current_nameserver],
sizeof(struct sockaddr_storage));
client_rotate_nameserver();
2009-12-29 20:00:57 +00:00
/* 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.) */
2009-12-29 20:00:57 +00:00
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);
2009-12-29 20:00:57 +00:00
/* 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);
} 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.");
2009-12-29 20:00:57 +00:00
lazymode = 0;
server_timeout_ms = 0;
2009-12-29 20:00:57 +00:00
handshake_lazyoff(fd);
}
}
}
return q.id;
}
static void
send_raw(int fd, uint8_t *buf, size_t 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 & 0xF0) | (user & 0x0F);
sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(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);
}
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
* Returns ID of sent query */
{
uint8_t buf[4096];
buf[0] = cmd;
2014-06-01 06:34:18 +00:00
build_hostname(buf, sizeof(buf), data, datalen, topdomain, b32, hostname_maxlen, 1);
return send_query(fd, buf);
}
2009-09-20 21:10:44 +00:00
static inline int
is_sending()
{
return window_sending(outbuf) > 0;
}
void
send_ping(int fd, int ping_response, int ack)
{
if (conn == CONN_DNS_NULL) {
uint8_t data[11];
int id;
/* Build ping header (see doc/proto_xxxxxxxx.txt) */
data[0] = userid;
data[1] = ack & 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;
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);
}
}
static void
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, id;
2009-12-29 20:00:57 +00:00
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) {
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 */
}
/* Build upstream data header (see doc/proto_xxxxxxxx.txt) */
2009-09-20 21:10:44 +00:00
buf[0] = userid_char; /* First byte is hex userid */
2014-06-01 06:34:18 +00:00
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->compressed << 2)
| (f->start << 1) | f->end;
hdr[0] = f->seqID & 0xFF;
hdr[1] = f->ack_other & 0xFF;
hdr[2] = code << 4; /* Flags are in upper 4 bits - lower 4 unused */
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);
/* 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);
2009-12-29 20:00:57 +00:00
datacmc++;
if (datacmc >= 36)
datacmc = 0;
2014-06-01 06:34:18 +00:00
id = send_query(fd, buf);
/* Log query ID as being sent now */
query_sent_now(id);
2015-08-21 15:24:53 +00:00
window_tick(outbuf);
}
static void
2009-12-29 20:00:57 +00:00
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 */
2009-12-29 20:00:57 +00:00
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 */
2009-12-29 20:00:57 +00:00
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 size_t
dns_namedec(uint8_t *outdata, size_t outdatalen, uint8_t *buf, size_t buflen)
2009-12-29 20:00:57 +00:00
/* 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);
2009-12-29 20:00:57 +00:00
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);
2009-12-29 20:00:57 +00:00
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);
2009-12-29 20:00:57 +00:00
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);
2009-12-29 20:00:57 +00:00
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, uint8_t *buf, size_t buflen, struct query *q)
2009-12-29 20:00:57 +00:00
/* 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_storage from;
uint8_t data[64*1024];
socklen_t addrlen;
int r;
addrlen = sizeof(from);
2014-06-01 06:34:18 +00:00
if ((r = recvfrom(dns_fd, data, sizeof(data), 0,
2009-09-20 21:10:44 +00:00
(struct sockaddr*)&from, &addrlen)) < 0) {
warn("recvfrom");
2009-09-20 21:10:44 +00:00
return -1;
}
if (conn == CONN_DNS_NULL) {
int rv;
2009-09-20 21:10:44 +00:00
if (r <= 0)
/* useless packet */
return 0;
rv = dns_decode((char *)buf, buflen, q, QR_ANSWER, (char *)data, r);
2009-09-20 21:10:44 +00:00
if (rv <= 0)
return rv;
2009-12-29 20:00:57 +00:00
if (q->type == T_CNAME || q->type == T_TXT)
/* CNAME can also be returned from an A question */
2009-09-20 15:11:14 +00:00
{
/*
* buf is a hostname or txt stream that we still need to
* decode to binary
2014-06-01 06:34:18 +00:00
*
2009-09-20 15:11:14 +00:00
* also update rv with the number of valid bytes
2014-06-01 06:34:18 +00:00
*
2009-09-20 15:11:14 +00:00
* data is unused here, and will certainly hold the smaller binary
*/
2009-12-29 20:00:57 +00:00
rv = dns_namedec(data, sizeof(data), buf, rv);
2009-09-20 15:11:14 +00:00
2009-12-29 20:00:57 +00:00
rv = MIN(rv, buflen);
if (rv > 0)
2009-09-20 15:11:14 +00:00
memcpy(buf, data, rv);
2009-12-29 20:00:57 +00:00
} 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((char *)buf);
2009-12-29 20:00:57 +00:00
thispartlen = MIN(thispartlen, buftotal-bufoffset);
dataspace = sizeof(data) - dataoffset;
if (thispartlen <= 0 || dataspace <= 0)
2009-09-20 15:11:14 +00:00
break;
2009-12-29 20:00:57 +00:00
datanew = dns_namedec(data + dataoffset, dataspace,
buf + bufoffset, thispartlen);
if (datanew <= 0)
2009-09-20 15:11:14 +00:00
break;
2009-12-29 20:00:57 +00:00
bufoffset += thispartlen + 1;
dataoffset += datanew;
2009-09-20 15:11:14 +00:00
}
2009-12-29 20:00:57 +00:00
rv = dataoffset;
rv = MIN(rv, buflen);
if (rv > 0)
memcpy(buf, data, rv);
2009-09-20 15:11:14 +00:00
}
if (debug >= 2)
fprintf(stderr, "RX: id %5d name[0]='%c'\n", q->id, q->name[0]);
return rv;
} else { /* CONN_RAW_UDP */
size_t datalen;
uint8_t 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;
2009-12-29 20:00:57 +00:00
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;
2009-12-29 20:00:57 +00:00
r -= RAW_HDR_LEN;
datalen = sizeof(buf);
if (uncompress(buf, &datalen, data + RAW_HDR_LEN, r) == Z_OK) {
write_tun(tun_fd, buf, datalen);
}
2009-12-29 20:00:57 +00:00
/* all done */
return 0;
}
}
2009-12-29 20:00:57 +00:00
static int
handshake_waitdns(int dns_fd, char *buf, size_t buflen, char cmd, int timeout)
2009-12-29 20:00:57 +00:00
/* 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.
2009-09-20 21:10:44 +00:00
*/
{
struct query q;
2009-12-29 20:00:57 +00:00
int r, rv;
fd_set fds;
struct timeval tv;
char qcmd;
cmd = toupper(cmd);
2009-12-29 20:00:57 +00:00
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);
2009-09-20 21:10:44 +00:00
2009-12-29 20:00:57 +00:00
if (r < 0)
return -1; /* select error */
if (r == 0)
return -3; /* select timeout */
2009-09-20 21:10:44 +00:00
2009-12-29 20:00:57 +00:00
q.id = 0;
q.name[0] = '\0';
rv = read_dns_withq(dns_fd, 0, (uint8_t *)buf, buflen, &q);
2009-12-29 20:00:57 +00:00
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]);
2009-12-29 20:00:57 +00:00
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;
}
2009-12-29 20:00:57 +00:00
/* not reached */
return -1;
2009-09-20 21:10:44 +00:00
}
static int
parse_data(uint8_t *data, size_t len, fragment *f, int *immediate)
{
size_t headerlen = DOWNSTREAM_HDR;
int ping;
memset(f, 0, sizeof(fragment));
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 */
/* 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) {
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 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)
return -1;
/* 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);
2009-09-20 21:10:44 +00:00
return -1;
}
if (debug >= 2)
fprintf(stderr, " IN: %lu bytes on tunnel, compression %d\n", read, compression_up);
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);
} else {
send_raw_data(dns_fd, data, datalen);
}
return read;
}
static int
tunnel_dns(int tun_fd, int dns_fd)
{
2009-09-20 21:10:44 +00:00
static long packrecv = 0;
2009-12-29 20:00:57 +00:00
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, immediate;
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);
2009-12-29 20:00:57 +00:00
if (conn != CONN_DNS_NULL)
return 1; /* everything already done */
/* 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' &&
2009-12-29 20:00:57 +00:00
q.name[0] != userid_char && q.name[0] != userid_char2) {
send_ping_soon = 700;
return -1; /* nothing done */
2009-12-29 20:00:57 +00:00
}
if (read < DOWNSTREAM_HDR) {
2009-09-20 21:10:44 +00:00
/* Maybe SERVFAIL etc. Send ping to get things back in order,
but wait a bit to prevent fast ping-pong loops. */
2009-12-29 20:00:57 +00:00
if (read < 0)
write_dns_error(&q, 0);
if (read < 0 && q.rcode == SERVFAIL && lazymode && server_timeout_ms > 500) {
2009-12-29 20:00:57 +00:00
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) {
2009-12-29 20:00:57 +00:00
packrecv_servfail++;
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);
2009-12-29 20:00:57 +00:00
send_query_sendcnt = 0;
send_query_recvcnt = 0;
rtt_total_ms = 1000;
num_immediate = 1;
send_ping(dns_fd, 0, -1);
2009-12-29 20:00:57 +00:00
} else if (packrecv >= 500 && packrecv_servfail > 0) {
fprintf(stderr, "(Sorry, stopped counting; try -I1 if you experience hiccups.)");
2009-12-29 20:00:57 +00:00
packrecv_servfail = 0;
}
}
2009-09-20 21:10:44 +00:00
send_ping_soon = 900;
return -1; /* nothing done */
}
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.");
2009-09-20 21:10:44 +00:00
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 */
/* Decode the downstream data header and fragment-ify ready for processing */
res = parse_data(cbuf, read, &f, &immediate);
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);
2009-09-20 21:10:44 +00:00
window_ack(outbuf, f.ack_other);
2009-09-20 21:10:44 +00:00
/* 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 && debug >= 1)
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;
2009-09-20 21:10:44 +00:00
}
/* 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);
}
2009-09-20 21:10:44 +00:00
/* Downstream data traffic + get ack for that data */
next_downstream_ack = window_process_incoming_fragment(inbuf, &f);
2009-09-20 21:10:44 +00:00
datalen = window_reassemble_data(inbuf, cbuf, sizeof(cbuf), &compressed);
if (datalen > 0) {
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);
datalen = 0;
} else {
datalen = buflen;
2009-09-20 21:10:44 +00:00
}
}
if (datalen)
write_tun(tun_fd, buf, datalen);
2009-09-20 21:10:44 +00:00
}
/* Move window along after doing all data processing */
window_tick(inbuf);
2009-09-20 21:10:44 +00:00
return read;
}
int
client_tunnel(int tun_fd, int dns_fd)
{
struct timeval tv;
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;
2009-09-20 21:10:44 +00:00
lastdownstreamtime = time(NULL);
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 = 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;
}
tv.tv_sec = 0;
tv.tv_usec = 2000;
send_ping_soon = 0;
}
2009-09-20 21:10:44 +00:00
if (send_ping_soon) {
tv.tv_sec = 0;
tv.tv_usec = send_ping_soon * 1000;
}
FD_ZERO(&fds);
if (window_buffer_available(outbuf) > 16) {
/* Fill up outgoing buffer with available data
* The windowing protocol manages data retransmits, timeouts etc. */
FD_SET(tun_fd, &fds);
}
FD_SET(dns_fd, &fds);
i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv);
2009-09-20 21:10:44 +00:00
if (difftime(time(NULL), lastdownstreamtime) > 60) {
2009-09-20 21:10:44 +00:00
warnx("No downstream data received in 60 seconds, shutting down.");
running = 0;
}
2014-06-01 06:34:18 +00:00
if (running == 0)
break;
2014-06-01 06:34:18 +00:00
if (i < 0)
err(1, "select < 0");
2009-09-20 21:10:44 +00:00
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);
2009-09-20 21:10:44 +00:00
} else {
outbuf->resends = 0;
send_ping(dns_fd, 1, -1);
2009-09-20 21:10:44 +00:00
}
} else {
send_ping(dns_fd, 0, next_downstream_ack);
next_downstream_ack = -1;
2009-09-20 21:10:44 +00:00
}
send_ping_soon = 0;
} else {
2009-09-20 21:10:44 +00:00
if (FD_ISSET(tun_fd, &fds)) {
if (tunnel_tun(tun_fd, dns_fd) <= 0)
continue;
2009-09-20 21:10:44 +00:00
/* 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)) {
tunnel_dns(tun_fd, dns_fd);
2014-06-01 06:34:18 +00:00
}
}
}
return rv;
}
static void
send_login(int fd, char *login, int len)
{
uint8_t 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;
2014-06-01 06:34:18 +00:00
rand_seed++;
2009-09-20 15:11:14 +00:00
send_packet(fd, 'l', data, sizeof(data));
}
static void
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;
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 */
2009-09-20 15:11:14 +00:00
memset(probedata, MAX(1, rand_seed & 0xff), sizeof(probedata));
probedata[1] = MAX(1, (rand_seed >> 8) & 0xff);
rand_seed++;
2009-12-29 20:00:57 +00:00
/* Note: must either be same, or larger, than send_chunk() */
build_hostname(buf, sizeof(buf), probedata, sizeof(probedata), topdomain,
dataenc, hostname_maxlen, 6);
send_query(fd, buf);
}
static void
send_set_downstream_fragsize(int fd, uint16_t fragsize)
{
uint8_t data[5];
2014-06-01 06:34:18 +00:00
data[0] = userid;
*(uint16_t *) (data + 1) = htons(fragsize);
data[3] = (rand_seed >> 8) & 0xff;
data[4] = (rand_seed >> 0) & 0xff;
2014-06-01 06:34:18 +00:00
rand_seed++;
2009-09-20 15:11:14 +00:00
send_packet(fd, 'n', data, sizeof(data));
}
2014-06-01 06:34:18 +00:00
static void
send_version(int fd, uint32_t version)
{
uint8_t data[6];
2014-06-01 06:34:18 +00:00
version = htonl(version);
*(uint32_t *) data = version;
data[4] = (rand_seed >> 8) & 0xff;
data[5] = (rand_seed >> 0) & 0xff;
2014-06-01 06:34:18 +00:00
rand_seed++;
2009-12-29 20:00:57 +00:00
send_packet(fd, 'v', data, sizeof(data));
}
static void
send_ip_request(int fd, int userid)
{
uint8_t buf[512] = "i____.";
buf[1] = b32_5to8(userid);
2014-06-01 06:34:18 +00:00
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((char *)buf, topdomain, 512 - strlen((char *)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, (uint8_t *) buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN);
}
static void
2009-12-29 20:00:57 +00:00
send_upenctest(int fd, char *s)
/* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */
{
2009-12-29 20:00:57 +00:00
char buf[512] = "z___";
2014-06-01 06:34:18 +00:00
2009-12-29 20:00:57 +00:00
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, (uint8_t *)buf);
2009-12-29 20:00:57 +00:00
}
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, (uint8_t *)buf);
}
static void
send_codec_switch(int fd, int userid, int bits)
{
2009-09-20 15:11:14 +00:00
char buf[512] = "s_____.";
buf[1] = b32_5to8(userid);
buf[2] = b32_5to8(bits);
2014-06-01 06:34:18 +00:00
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);
}
2009-09-20 15:11:14 +00:00
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);
}
2009-09-20 15:11:14 +00:00
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);
2009-09-20 15:11:14 +00:00
rand_seed++;
strncat(buf, topdomain, 512 - strlen(buf));
send_query(fd, (uint8_t *)buf);
2009-09-20 15:11:14 +00:00
}
2009-09-20 21:10:44 +00:00
static void
send_lazy_switch(int fd, int userid)
{
2009-12-29 20:00:57 +00:00
char buf[512] = "o_____.";
2009-09-20 21:10:44 +00:00
buf[1] = b32_5to8(userid);
if (lazymode)
buf[2] = 'l';
else
buf[2] = 'i';
2009-12-29 20:00:57 +00:00
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++;
2009-09-20 21:10:44 +00:00
strncat(buf, topdomain, 512 - strlen(buf));
send_query(fd, (uint8_t *)buf);
2009-09-20 21:10:44 +00:00
}
static int
handshake_version(int dns_fd, int *seed)
{
2009-09-20 21:10:44 +00:00
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, PROTOCOL_VERSION);
read = handshake_waitdns(dns_fd, in, sizeof(in), 'V', i+1);
2012-02-06 19:28:42 +00:00
if (read >= 9) {
payload = (((in[4] & 0xff) << 24) |
((in[5] & 0xff) << 16) |
((in[6] & 0xff) << 8) |
((in[7] & 0xff)));
if (strncmp("VACK", (char *)in, 4) == 0) {
2012-02-06 19:28:42 +00:00
*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",
PROTOCOL_VERSION, userid);
2012-02-06 19:28:42 +00:00
return 0;
} else if (strncmp("VNAK", (char *)in, 4) == 0) {
2014-06-01 06:34:18 +00:00
warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up",
PROTOCOL_VERSION, payload);
2012-02-06 19:28:42 +00:00
return 1;
} else if (strncmp("VFUL", (char *)in, 4) == 0) {
2012-02-06 19:28:42 +00:00
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");
2014-06-01 06:34:18 +00:00
fprintf(stderr, "Retrying version check...\n");
}
2009-09-20 15:11:14 +00:00
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);
2014-06-01 06:34:18 +00:00
for (i=0; running && i<5 ;i++) {
send_login(dns_fd, login, 16);
read = handshake_waitdns(dns_fd, in, sizeof(in), 'L', i+1);
2012-02-06 19:28:42 +00:00
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 {
2012-02-06 19:28:42 +00:00
errx(4, "Failed to set IP and MTU");
}
2012-02-06 19:28:42 +00:00
} else {
fprintf(stderr, "Received bad handshake\n");
}
2012-02-06 19:28:42 +00:00
}
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;
int got_addr;
memset(&raw_serv, 0, sizeof(raw_serv));
got_addr = 0;
2009-12-29 20:00:57 +00:00
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+1);
2012-02-06 19:28:42 +00:00
if (len == 5 && in[0] == 'I') {
/* Received IPv4 address */
struct sockaddr_in *raw4_serv = (struct sockaddr_in *) &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);
got_addr = 1;
break;
}
if (len == 17 && in[0] == 'I') {
/* Received IPv6 address */
struct sockaddr_in6 *raw6_serv = (struct sockaddr_in6 *) &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);
got_addr = 1;
2012-02-06 19:28:42 +00:00
break;
}
2009-12-29 20:00:57 +00:00
fprintf(stderr, ".");
fflush(stderr);
}
2009-12-29 20:00:57 +00:00
fprintf(stderr, "\n");
if (!running)
return 0;
2014-06-01 06:34:18 +00:00
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));
fflush(stderr);
2014-06-01 06:34:18 +00:00
/* 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);
2014-06-01 06:34:18 +00:00
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
2014-06-01 06:34:18 +00:00
&& 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);
}
2014-06-01 06:34:18 +00:00
fprintf(stderr, "failed\n");
return 0;
}
static int
2009-12-29 20:00:57 +00:00
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];
2009-12-29 20:00:57 +00:00
unsigned char *uin = (unsigned char *) in;
unsigned char *us = (unsigned char *) s;
int i;
int read;
2009-12-29 20:00:57 +00:00
int slen;
2009-12-29 20:00:57 +00:00
slen = strlen(s);
for (i=0; running && i<3 ;i++) {
2009-12-29 20:00:57 +00:00
send_upenctest(dns_fd, s);
read = handshake_waitdns(dns_fd, in, sizeof(in), 'Z', i+1);
2009-12-29 20:00:57 +00:00
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;
/* 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]);
}
2009-12-29 20:00:57 +00:00
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', i+1);
2009-12-29 20:00:57 +00:00
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;
}
}
2009-12-29 20:00:57 +00:00
/* if still here, then all okay */
return 1;
}
2009-12-29 20:00:57 +00:00
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 || do_qtype == T_PRIVATE) {
2009-12-29 20:00:57 +00:00
/* no other choice than raw */
fprintf(stderr, "No alternative downstream codec available, using default (Raw)\n");
return 'R';
2009-12-29 20:00:57 +00:00
}
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;
}
2009-12-29 20:00:57 +00:00
/* 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 || do_qtype == T_PRIVATE)
2009-12-29 20:00:57 +00:00
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/PRIVATE
to work. */
2009-12-29 20:00:57 +00:00
send_downenctest(dns_fd, trycodec, 1, NULL, 0);
read = handshake_waitdns(dns_fd, in, sizeof(in), 'Y', timeout);
2009-12-29 20:00:57 +00:00
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_PRIVATE;
case 2: return T_TXT;
case 3: return T_SRV;
case 4: return T_MX;
case 5: return T_CNAME;
case 6: return T_A;
2009-12-29 20:00:57 +00:00
}
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 (debug >= 1)
fprintf(stderr, " Type %s timeout %d works\n", client_get_qtype(), timeout);
2009-12-29 20:00:57 +00:00
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', i+1);
2009-12-29 20:00:57 +00:00
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
2009-12-29 20:00:57 +00:00
handshake_switch_codec(int dns_fd, int bits)
{
char in[4096];
int i;
int read;
2009-12-29 20:00:57 +00:00
struct encoder *tempenc;
2009-12-29 20:00:57 +00:00
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;
2009-12-29 20:00:57 +00:00
fprintf(stderr, "Switching upstream to codec %s\n", tempenc->name);
for (i=0; running && i<5 ;i++) {
send_codec_switch(dns_fd, userid, bits);
2014-06-01 06:34:18 +00:00
read = handshake_waitdns(dns_fd, in, sizeof(in), 'S', i+1);
2012-02-06 19:28:42 +00:00
if (read > 0) {
if (strncmp("BADLEN", in, 6) == 0) {
fprintf(stderr, "Server got bad message length.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("BADIP", in, 5) == 0) {
fprintf(stderr, "Server rejected sender IP address.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("BADCODEC", in, 8) == 0) {
fprintf(stderr, "Server rejected the selected codec.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
}
2012-02-06 19:28:42 +00:00
in[read] = 0; /* zero terminate */
fprintf(stderr, "Server switched upstream to codec %s\n", in);
dataenc = tempenc;
/* Update outgoing buffer max (decoded) fragsize */
maxfragsize_up = get_raw_length_from_dns(hostname_maxlen - UPSTREAM_HDR, dataenc, topdomain);
2012-02-06 19:28:42 +00:00
return;
}
2009-12-29 20:00:57 +00:00
fprintf(stderr, "Retrying codec switch...\n");
}
2009-12-29 20:00:57 +00:00
if (!running)
return;
fprintf(stderr, "No reply from server on codec switch.\n");
2014-06-01 06:34:18 +00:00
codec_revert:
2009-12-29 20:00:57 +00:00
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);
}
2009-09-20 15:11:14 +00:00
static void
handshake_switch_downenc(int dns_fd)
{
char in[4096];
int i;
int read;
char *dname;
dname = "Base32";
if (downenc == 'S')
dname = "Base64";
2009-12-29 20:00:57 +00:00
else if (downenc == 'U')
dname = "Base64u";
else if (downenc == 'V')
dname = "Base128";
2009-09-20 15:11:14 +00:00
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', i+1);
2009-09-20 15:11:14 +00:00
2012-02-06 19:28:42 +00:00
if (read > 0) {
if (strncmp("BADLEN", in, 6) == 0) {
fprintf(stderr, "Server got bad message length.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("BADIP", in, 5) == 0) {
fprintf(stderr, "Server rejected sender IP address.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("BADCODEC", in, 8) == 0) {
fprintf(stderr, "Server rejected the selected codec.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
2009-09-20 15:11:14 +00:00
}
2012-02-06 19:28:42 +00:00
in[read] = 0; /* zero terminate */
fprintf(stderr, "Server switched downstream to codec %s\n", in);
return;
}
2009-12-29 20:00:57 +00:00
2009-09-20 15:11:14 +00:00
fprintf(stderr, "Retrying codec switch...\n");
}
2009-12-29 20:00:57 +00:00
if (!running)
return;
fprintf(stderr, "No reply from server on codec switch.\n");
2009-09-20 15:11:14 +00:00
2014-06-01 06:34:18 +00:00
codec_revert:
2009-12-29 20:00:57 +00:00
fprintf(stderr, "Falling back to downstream codec Base32\n");
2009-09-20 15:11:14 +00:00
}
2009-09-20 21:10:44 +00:00
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");
2009-12-29 20:00:57 +00:00
for (i=0; running && i<5; i++) {
2009-09-20 21:10:44 +00:00
send_lazy_switch(dns_fd, userid);
read = handshake_waitdns(dns_fd, in, sizeof(in), 'O', i+1);
2009-09-20 21:10:44 +00:00
2012-02-06 19:28:42 +00:00
if (read > 0) {
if (strncmp("BADLEN", in, 6) == 0) {
fprintf(stderr, "Server got bad message length.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("BADIP", in, 5) == 0) {
fprintf(stderr, "Server rejected sender IP address.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("BADCODEC", in, 8) == 0) {
fprintf(stderr, "Server rejected lazy mode.\n");
2012-02-06 19:28:42 +00:00
goto codec_revert;
} else if (strncmp("Lazy", in, 4) == 0) {
fprintf(stderr, "Server switched to lazy mode\n");
lazymode = 1;
return;
2009-09-20 21:10:44 +00:00
}
2012-02-06 19:28:42 +00:00
}
2009-12-29 20:00:57 +00:00
2009-09-20 21:10:44 +00:00
fprintf(stderr, "Retrying lazy mode switch...\n");
}
2009-12-29 20:00:57 +00:00
if (!running)
return;
fprintf(stderr, "No reply from server on lazy switch.\n");
2009-09-20 21:10:44 +00:00
2014-06-01 06:34:18 +00:00
codec_revert:
2009-09-20 21:10:44 +00:00
fprintf(stderr, "Falling back to legacy mode\n");
lazymode = 0;
max_timeout_ms = 1000;
2009-09-20 21:10:44 +00:00
}
void
2009-09-20 21:10:44 +00:00
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);
2009-09-20 21:10:44 +00:00
2012-02-06 19:28:42 +00:00
if (read == 9 && strncmp("Immediate", in, 9) == 0) {
warnx("Server switched back to legacy mode.");
2012-02-06 19:28:42 +00:00
lazymode = 0;
max_timeout_ms = 1000;
2012-02-06 19:28:42 +00:00
return;
}
2009-09-20 21:10:44 +00:00
}
2009-12-29 20:00:57 +00:00
if (!running)
return;
warnx("No reply from server on legacy mode switch.");
2009-09-20 21:10:44 +00:00
}
2009-09-20 15:11:14 +00:00
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);
2009-12-29 20:00:57 +00:00
int okay;
int i;
unsigned int v;
2009-09-20 15:11:14 +00:00
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; */
2009-12-29 20:00:57 +00:00
if ((in[2] & 0xff) != 107) {
warnx("\ncorruption at byte 2, this won't work. Try -O Base32, or other -T options.");
2009-12-29 20:00:57 +00:00
*max_fragsize = -1;
return 1;
}
2009-09-20 15:11:14 +00:00
/* Check for corruption */
2009-12-29 20:00:57 +00:00
okay = 1;
v = in[3] & 0xff;
2009-09-20 15:11:14 +00:00
2012-02-06 19:28:42 +00:00
for (i = 3; i < read; i++, v = (v + 107) & 0xff)
if ((in[i] & 0xff) != v) {
okay = 0;
break;
}
2009-09-20 15:11:14 +00:00
2012-02-06 19:28:42 +00:00
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);
2009-09-20 15:11:14 +00:00
} else {
2012-02-06 19:28:42 +00:00
fprintf(stderr, "%d corrupted at %d.. ", acked_fragsize, i);
2009-09-20 15:11:14 +00:00
}
2012-02-06 19:28:42 +00:00
fflush(stderr);
return 1;
}
2009-09-20 15:11:14 +00:00
2009-12-29 20:00:57 +00:00
/* notreached */
2009-09-20 15:11:14 +00:00
return 1;
}
static int
handshake_autoprobe_fragsize(int dns_fd)
{
char in[MAX_FRAGSIZE];
int i;
int read;
int proposed_fragsize = 768;
int range = 768;
2009-09-20 15:11:14 +00:00
int max_fragsize;
max_fragsize = 0;
fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)");
2009-09-20 15:11:14 +00:00
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++) {
2009-12-29 20:00:57 +00:00
send_fragsize_probe(dns_fd, proposed_fragsize);
read = handshake_waitdns(dns_fd, in, sizeof(in), 'R', 1);
2014-06-01 06:34:18 +00:00
2012-02-06 19:28:42 +00:00
if (read > 0) {
/* We got a reply */
if (fragsize_check(in, read, proposed_fragsize, &max_fragsize) == 1)
break;
}
2009-12-29 20:00:57 +00:00
fprintf(stderr, ".");
fflush(stderr);
}
2009-12-29 20:00:57 +00:00
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) {
warnx("\nstopped while autodetecting fragment size (Try setting manually with -m)");
return 0;
}
if (max_fragsize <= 6) {
2009-12-29 20:00:57 +00:00
/* 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. */
warnx("\nfound no accepted fragment size.");
2009-12-29 20:00:57 +00:00
warnx("try setting -M to 200 or lower, or try other -T or -O options.");
return 0;
}
/* data header adds 6 bytes */
fprintf(stderr, "will use %d-6=%d\n", max_fragsize, max_fragsize - 6);
2009-09-20 15:11:14 +00:00
2009-12-29 20:00:57 +00:00
/* 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_PRIVATE || do_qtype == T_TXT ||
2009-12-29 20:00:57 +00:00
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");
}
2009-09-20 15:11:14 +00:00
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', i+1);
2012-02-06 19:28:42 +00:00
if (read > 0) {
2012-02-06 19:28:42 +00:00
if (strncmp("BADFRAG", in, 7) == 0) {
fprintf(stderr, "Server rejected fragsize. Keeping default.\n");
2012-02-06 19:28:42 +00:00
return;
} else if (strncmp("BADIP", in, 5) == 0) {
fprintf(stderr, "Server rejected sender IP address.\n");
return;
}
2012-02-06 19:28:42 +00:00
/* The server returns the accepted fragsize:
accepted_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff) */
2012-02-06 19:28:42 +00:00
return;
}
2009-12-29 20:00:57 +00:00
fprintf(stderr, "Retrying set fragsize...\n");
}
2009-12-29 20:00:57 +00:00
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;
2009-12-29 20:00:57 +00:00
int upcodec;
int r;
2009-12-29 20:00:57 +00:00
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", client_get_qtype());
2009-12-29 20:00:57 +00:00
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)) {
/* TODO: upstream fragsize based on max raw packet size */
conn = CONN_RAW_UDP;
max_timeout_ms = 10000;
} else {
if (raw_mode == 0) {
fprintf(stderr, "Skipping raw mode\n");
}
2009-12-29 20:00:57 +00:00
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) { /* Base64 */
2009-12-29 20:00:57 +00:00
handshake_switch_codec(dns_fd, 6);
} else if (upcodec == 2) { /* Base64u */
2009-12-29 20:00:57 +00:00
handshake_switch_codec(dns_fd, 26);
} else if (upcodec == 3) { /* Base128 */
2009-12-29 20:00:57 +00:00
handshake_switch_codec(dns_fd, 7);
}
if (!running)
return -1;
if (downenc == ' ') {
downenc = handshake_downenc_autodetect(dns_fd);
}
2009-12-29 20:00:57 +00:00
if (!running)
return -1;
handshake_switch_downenc(dns_fd);
2009-12-29 20:00:57 +00:00
if (!running)
return -1;
2009-09-20 15:11:14 +00:00
if (!compression_down)
handshake_switch_downcompression(dns_fd);
2009-09-20 21:10:44 +00:00
if (lazymode) {
handshake_try_lazy(dns_fd);
}
2009-12-29 20:00:57 +00:00
if (!running)
return -1;
2009-09-20 21:10:44 +00:00
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);
fragsize = MAX_FRAGSIZE;
}
if (!fragsize) {
return 1;
}
}
handshake_set_fragsize(dns_fd, fragsize);
2009-12-29 20:00:57 +00:00
if (!running)
return -1;
}
return 0;
}