#76 merge client code

This commit is contained in:
Erik Ekman 2009-09-20 21:10:44 +00:00
parent 6e438b4fe4
commit 282a508f2b
3 changed files with 551 additions and 153 deletions

View File

@ -51,7 +51,7 @@
#include "version.h" #include "version.h"
#include "client.h" #include "client.h"
#define PING_TIMEOUT(t) ((t) >= (conn == CONN_DNS_NULL ? 1 : 20)) static void handshake_lazyoff(int dns_fd);
static int running; static int running;
static const char *password; static const char *password;
@ -61,20 +61,21 @@ static struct sockaddr_in raw_serv;
static const char *topdomain; static const char *topdomain;
static uint16_t rand_seed; static uint16_t rand_seed;
static int downstream_seqno;
static int downstream_fragment;
static int down_ack_seqno;
static int down_ack_fragment;
/* Current up/downstream IP packet */ /* Current up/downstream IP packet */
static struct packet outpkt; static struct packet outpkt;
static struct packet inpkt; static struct packet inpkt;
int outchunkresent = 0;
/* My userid at the server */ /* My userid at the server */
static char userid; static char userid;
static char userid_char; /* used when sending (uppercase) */
static char userid_char2; /* also accepted when receiving (lowercase) */
/* DNS id for next packet */ /* DNS id for next packet */
static uint16_t chunkid; static uint16_t chunkid;
static uint16_t chunkid_prev;
static uint16_t chunkid_prev2;
/* Base32 encoder used for non-data packets and replies */ /* Base32 encoder used for non-data packets and replies */
static struct encoder *b32; static struct encoder *b32;
@ -94,22 +95,36 @@ static unsigned short do_qtype = T_NULL;
/* My connection mode */ /* My connection mode */
static enum connection conn; static enum connection conn;
int selecttimeout; /* RFC says timeout minimum 5sec */
int lazymode;
long send_ping_soon;
time_t lastdownstreamtime;
void void
client_init() client_init()
{ {
running = 1; running = 1;
outpkt.seqno = 0;
inpkt.len = 0;
downstream_seqno = 0;
downstream_fragment = 0;
down_ack_seqno = 0;
down_ack_fragment = 0;
chunkid = ((unsigned int) rand()) & 0xFFFF;
b32 = get_base32_encoder(); b32 = get_base32_encoder();
b64 = get_base64_encoder(); b64 = get_base64_encoder();
dataenc = get_base32_encoder(); dataenc = get_base32_encoder();
rand_seed = ((unsigned int) rand()) & 0xFFFF; rand_seed = ((unsigned int) rand()) & 0xFFFF;
send_ping_soon = 1; /* send ping immediately after startup */
conn = CONN_DNS_NULL; 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 void
@ -176,6 +191,17 @@ set_downenc(char *encoding)
downenc = 'R'; downenc = 'R';
} }
void
client_set_selecttimeout(int select_timeout)
{
selecttimeout = select_timeout;
}
void
client_set_lazymode(int lazy_mode) {
lazymode = lazymode;
}
const char * const char *
client_get_raw_addr() client_get_raw_addr()
{ {
@ -189,6 +215,8 @@ send_query(int fd, char *hostname)
struct query q; struct query q;
size_t len; size_t len;
chunkid_prev2 = chunkid_prev;
chunkid_prev = chunkid;
chunkid += 7727; chunkid += 7727;
if (chunkid == 0) if (chunkid == 0)
/* 0 is used as "no-query" in iodined.c */ /* 0 is used as "no-query" in iodined.c */
@ -229,6 +257,7 @@ static void
send_raw_data(int dns_fd) send_raw_data(int dns_fd)
{ {
send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA);
outpkt.len = 0;
} }
@ -243,7 +272,7 @@ send_packet(int fd, char cmd, const char *data, const size_t datalen)
send_query(fd, buf); send_query(fd, buf);
} }
static int static inline int
is_sending() is_sending()
{ {
return (outpkt.len != 0); return (outpkt.len != 0);
@ -252,7 +281,6 @@ is_sending()
static void static void
send_chunk(int fd) send_chunk(int fd)
{ {
char hex[] = "0123456789abcdef";
char buf[4096]; char buf[4096];
int avail; int avail;
int code; int code;
@ -266,21 +294,23 @@ send_chunk(int fd)
/* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */
buf[0] = hex[userid & 15]; /* First byte is 4 bits userid */ buf[0] = userid_char; /* First byte is hex userid */
code = ((outpkt.seqno & 7) << 2) | ((outpkt.fragment & 15) >> 2); code = ((outpkt.seqno & 7) << 2) | ((outpkt.fragment & 15) >> 2);
buf[1] = b32_5to8(code); /* Second byte is 3 bits seqno, 2 upper bits fragment count */ buf[1] = b32_5to8(code); /* Second byte is 3 bits seqno, 2 upper bits fragment count */
code = ((outpkt.fragment & 3) << 3) | (downstream_seqno & 7); 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 */ buf[2] = b32_5to8(code); /* Third byte is 2 bits lower fragment count, 3 bits downstream packet seqno */
code = ((downstream_fragment & 15) << 1) | (outpkt.sentlen == avail); 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[3] = b32_5to8(code); /* Fourth byte is 4 bits downstream fragment count, 1 bit last frag flag */
#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
down_ack_seqno = downstream_seqno;
down_ack_fragment = downstream_fragment;
outpkt.fragment++;
send_query(fd, buf); send_query(fd, buf);
} }
@ -290,22 +320,18 @@ send_ping(int fd)
if (conn == CONN_DNS_NULL) { if (conn == CONN_DNS_NULL) {
char data[4]; char data[4];
if (is_sending()) {
outpkt.sentlen = 0;
outpkt.offset = 0;
outpkt.len = 0;
}
data[0] = userid; data[0] = userid;
data[1] = ((downstream_seqno & 7) << 4) | (downstream_fragment & 15); data[1] = ((inpkt.seqno & 7) << 4) | (inpkt.fragment & 15);
data[2] = (rand_seed >> 8) & 0xff; data[2] = (rand_seed >> 8) & 0xff;
data[3] = (rand_seed >> 0) & 0xff; data[3] = (rand_seed >> 0) & 0xff;
down_ack_seqno = downstream_seqno;
down_ack_fragment = downstream_fragment;
rand_seed++; rand_seed++;
#if 0
fprintf(stderr, " Send: down %d/%d (ping)\n",
inpkt.seqno, inpkt.fragment);
#endif
send_packet(fd, 'p', data, sizeof(data)); send_packet(fd, 'p', data, sizeof(data));
} else { } else {
send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING);
@ -313,28 +339,31 @@ send_ping(int fd)
} }
static int static int
read_dns(int dns_fd, int tun_fd, char *buf, int buflen) /* FIXME: tun_fd needed for raw handling */ read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) /* FIXME: tun_fd needed for raw handling */
{ {
struct sockaddr_in from; struct sockaddr_in from;
char data[64*1024]; char data[64*1024];
socklen_t addrlen; socklen_t addrlen;
struct query q;
int r; int r;
addrlen = sizeof(struct sockaddr); addrlen = sizeof(struct sockaddr);
if ((r = recvfrom(dns_fd, data, sizeof(data), 0, if ((r = recvfrom(dns_fd, data, sizeof(data), 0,
(struct sockaddr*)&from, &addrlen)) == -1) { (struct sockaddr*)&from, &addrlen)) < 0) {
warn("recvfrom"); warn("recvfrom");
return 0; return -1;
} }
if (conn == CONN_DNS_NULL) { if (conn == CONN_DNS_NULL) {
int rv; int rv;
if (r <= 0)
/* useless packet */
return 0;
rv = dns_decode(buf, buflen, &q, QR_ANSWER, data, r); rv = dns_decode(buf, buflen, q, QR_ANSWER, data, r);
if (rv <= 0)
return rv;
if ((q.type == T_CNAME || q.type == T_MX || q.type == T_TXT) if (q->type == T_CNAME || q->type == T_MX || q->type == T_TXT)
&& rv >= 1)
/* CNAME an also be returned from an A (or MX) question */ /* CNAME an also be returned from an A (or MX) question */
{ {
size_t space; size_t space;
@ -415,38 +444,6 @@ read_dns(int dns_fd, int tun_fd, char *buf, int buflen) /* FIXME: tun_fd needed
} }
} }
/* decode the data header, update seqno and frag before next request */
if (rv >= 2) {
downstream_seqno = (buf[1] >> 5) & 7;
downstream_fragment = (buf[1] >> 1) & 15;
}
if (is_sending()) {
if (chunkid == q.id) {
/* Got ACK on sent packet */
outpkt.offset += outpkt.sentlen;
if (outpkt.offset == outpkt.len) {
/* Packet completed */
outpkt.offset = 0;
outpkt.len = 0;
outpkt.sentlen = 0;
/* If the ack contains unacked frag number but no data,
* send a ping to ack the frag number and get more data*/
if (rv == 2 && (
downstream_seqno != down_ack_seqno ||
downstream_fragment != down_ack_fragment
)) {
send_ping(dns_fd);
}
} else {
/* More to send */
send_chunk(dns_fd);
}
}
}
return rv; return rv;
} else { /* CONN_RAW_UDP */ } else { /* CONN_RAW_UDP */
unsigned long datalen; unsigned long datalen;
@ -470,6 +467,22 @@ read_dns(int dns_fd, int tun_fd, char *buf, int buflen) /* FIXME: tun_fd needed
} }
} }
static inline int
read_dns_namecheck(int dns_fd, int tun_fd, char *buf, int buflen, char c1, char c2)
/* Only returns >0 when the query hostname in the received packet matches
either c1 or c2; used to tell handshake-dupes apart.
*/
{
struct query q;
int rv;
rv = read_dns_withq(dns_fd, tun_fd, buf, buflen, &q);
if (rv > 0 && q.name[0] != c1 && q.name[0] != c2)
return 0;
return rv; /* may also be 0 = useless or -1 = error (printed) */
}
static int static int
tunnel_tun(int tun_fd, int dns_fd) tunnel_tun(int tun_fd, int dns_fd)
@ -483,6 +496,11 @@ tunnel_tun(int tun_fd, int dns_fd)
if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0)
return -1; 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); outlen = sizeof(out);
inlen = read; inlen = read;
compress2((uint8_t*)out, &outlen, (uint8_t*)in, inlen, 9); compress2((uint8_t*)out, &outlen, (uint8_t*)in, inlen, 9);
@ -490,12 +508,15 @@ tunnel_tun(int tun_fd, int dns_fd)
memcpy(outpkt.data, out, MIN(outlen, sizeof(outpkt.data))); memcpy(outpkt.data, out, MIN(outlen, sizeof(outpkt.data)));
outpkt.sentlen = 0; outpkt.sentlen = 0;
outpkt.offset = 0; outpkt.offset = 0;
outpkt.seqno = (outpkt.seqno + 1) & 7;
outpkt.len = outlen; outpkt.len = outlen;
outpkt.seqno++;
outpkt.fragment = 0; outpkt.fragment = 0;
outchunkresent = 0;
if (conn == CONN_DNS_NULL) { if (conn == CONN_DNS_NULL) {
send_chunk(dns_fd); send_chunk(dns_fd);
send_ping_soon = 0;
} else { } else {
send_raw_data(dns_fd); send_raw_data(dns_fd);
} }
@ -506,43 +527,251 @@ tunnel_tun(int tun_fd, int dns_fd)
static int static int
tunnel_dns(int tun_fd, int dns_fd) tunnel_dns(int tun_fd, int dns_fd)
{ {
static long packrecv = 0;
static long packrecv_oos = 0;
int up_ack_seqno;
int up_ack_fragment;
int new_down_seqno;
int new_down_fragment;
struct query q;
unsigned long datalen; unsigned long datalen;
char buf[64*1024]; char buf[64*1024];
size_t read; int read;
int send_something_now = 0;
if ((read = read_dns(dns_fd, tun_fd, buf, sizeof(buf))) <= 2) if ((read = read_dns_withq(dns_fd, tun_fd, buf, sizeof(buf), &q)) < 2) {
return -1; /* Maybe SERVFAIL etc. Send ping to get things back in order,
but wait a bit to prevent fast ping-pong loops. */
if (downstream_seqno != inpkt.seqno) { send_ping_soon = 900;
/* New packet */ return -1; /* nothing done */
inpkt.seqno = downstream_seqno;
inpkt.fragment = downstream_fragment;
inpkt.len = 0;
} else if (downstream_fragment <= inpkt.fragment) {
/* Duplicate fragment */
return -1;
} }
inpkt.fragment = downstream_fragment;
datalen = MIN(read - 2, sizeof(inpkt.data) - inpkt.len); /* Don't process anything that isn't data; already checked read>=2 */
if (q.name[0] != 'P' && q.name[0] != 'p' &&
q.name[0] != userid_char && q.name[0] != userid_char2)
return -1; /* nothing done */
/* Skip 2 byte data header and append to packet */ if (read == 5 && !strncmp("BADIP", buf, 5)) {
memcpy(&inpkt.data[inpkt.len], &buf[2], datalen); 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.");
inpkt.len += datalen; return -1; /* nothing done */
}
if (buf[1] & 1) { /* If last fragment flag is set */
/* Uncompress packet and send to tun */ if (send_ping_soon) {
datalen = sizeof(buf); send_something_now = 1;
if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) inpkt.data, inpkt.len) == Z_OK) { send_ping_soon = 0;
write_tun(tun_fd, buf, datalen); }
/* 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: down %d/%d up %d/%d, %d bytes\n",
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 */
}
packrecv++;
/* Don't process any non-recent stuff any further */
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 < 600 && packrecv_oos == 5)
warnx("Hmm, getting some out-of-sequence DNS replies. You may want to try -I1 or -L0 if you notice hiccups in the data traffic.");
if (lazymode && packrecv < 600 && packrecv_oos == 15) {
warnx("Your DNS server connection causes severe re-ordering of DNS traffic. Lazy mode doesn't work well here, switching off. Next time on this network, start with -L0.");
lazymode = 0;
selecttimeout = 1;
handshake_lazyoff(dns_fd);
} }
inpkt.len = 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. Except when severe packet
reordering occurs, such as opendns... 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 we have nothing to send, send a ping to get more data */ if (read == 2 && new_down_seqno != inpkt.seqno &&
if (!is_sending()) !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(dns_fd);
send_ping_soon = 0;
}
return read; return read;
} }
@ -553,23 +782,43 @@ client_tunnel(int tun_fd, int dns_fd)
fd_set fds; fd_set fds;
int rv; int rv;
int i; int i;
int seconds;
rv = 0; rv = 0;
seconds = 0; lastdownstreamtime = time(NULL);
while (running) { while (running) {
tv.tv_sec = 1; tv.tv_sec = selecttimeout;
tv.tv_usec = 0; 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); FD_ZERO(&fds);
if ((!is_sending()) || conn == CONN_RAW_UDP) { 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(tun_fd, &fds);
} }
FD_SET(dns_fd, &fds); FD_SET(dns_fd, &fds);
i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); 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) if (running == 0)
break; break;
@ -577,24 +826,48 @@ client_tunnel(int tun_fd, int dns_fd)
if (i < 0) if (i < 0)
err(1, "select"); err(1, "select");
if (i == 0) { /* timeout */ if (i == 0) {
seconds++; /* 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 { } else {
if (FD_ISSET(tun_fd, &fds)) { if (FD_ISSET(tun_fd, &fds)) {
seconds = 0;
if (tunnel_tun(tun_fd, dns_fd) <= 0) if (tunnel_tun(tun_fd, dns_fd) <= 0)
continue; 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 (FD_ISSET(dns_fd, &fds)) {
if (tunnel_dns(tun_fd, dns_fd) <= 0) if (tunnel_dns(tun_fd, dns_fd) <= 0)
continue; continue;
} }
} }
if (PING_TIMEOUT(seconds)) {
send_ping(dns_fd);
seconds = 0;
}
} }
return rv; return rv;
@ -744,10 +1017,27 @@ send_downenc_switch(int fd, int userid)
strncat(buf, topdomain, 512 - strlen(buf)); strncat(buf, topdomain, 512 - strlen(buf));
send_query(fd, 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';
strncat(buf, topdomain, 512 - strlen(buf));
send_query(fd, buf);
}
static int static int
handshake_version(int dns_fd, int *seed) handshake_version(int dns_fd, int *seed)
{ {
char hex[] = "0123456789abcdef";
char hex2[] = "0123456789ABCDEF";
struct timeval tv; struct timeval tv;
char in[4096]; char in[4096];
fd_set fds; fd_set fds;
@ -768,15 +1058,10 @@ handshake_version(int dns_fd, int *seed)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'v', 'V');
if(read <= 0) { if(read <= 0)
if (read == 0) {
warn("handshake read");
}
/* if read < 0 then warning has been printed already */
continue; continue;
}
if (read >= 9) { if (read >= 9) {
payload = (((in[4] & 0xff) << 24) | payload = (((in[4] & 0xff) << 24) |
@ -787,6 +1072,8 @@ handshake_version(int dns_fd, int *seed)
if (strncmp("VACK", in, 4) == 0) { if (strncmp("VACK", in, 4) == 0) {
*seed = payload; *seed = payload;
userid = in[8]; 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); fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", VERSION, userid);
return 0; return 0;
@ -836,12 +1123,10 @@ handshake_login(int dns_fd, int seed)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'l', 'L');
if(read <= 0) { if(read <= 0)
warn("read");
continue; continue;
}
if (read > 0) { if (read > 0) {
int netmask; int netmask;
@ -898,7 +1183,7 @@ handshake_raw_udp(int dns_fd, int seed)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
len = read_dns(dns_fd, 0, in, sizeof(in)); len = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'i', 'I');
if (len == 5 && in[0] == 'I') { if (len == 5 && in[0] == 'I') {
/* Received IP address */ /* Received IP address */
remoteaddr = (in[1] & 0xff); remoteaddr = (in[1] & 0xff);
@ -991,28 +1276,24 @@ handshake_case_check(int dns_fd)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'z', 'Z');
if (read > 0) { if (read > 0) {
if (in[0] == 'z' || in[0] == 'Z') { if (read < (27 * 2)) {
if (read < (27 * 2)) { fprintf(stderr, "Received short case check reply. Will use base32 encoder\n");
fprintf(stderr, "Received short case check reply. Will use base32 encoder\n"); return case_preserved;
return case_preserved;
} else {
int k;
/* TODO enhance this, base128 is probably also possible */
case_preserved = 1;
for (k = 0; k < 27 && case_preserved; k += 2) {
if (in[k] == in[k+1]) {
/* test string: zZ+-aAbBcCdDeE... */
case_preserved = 0;
}
}
return case_preserved;
}
} else { } else {
fprintf(stderr, "Received bad case check reply\n"); int k;
/* TODO enhance this, base128 is probably also possible */
case_preserved = 1;
for (k = 0; k < 27 && case_preserved; k += 2) {
if (in[k] == in[k+1]) {
/* test string: zZ+-aAbBcCdDeE... */
case_preserved = 0;
}
}
return case_preserved;
} }
} else { } else {
fprintf(stderr, "Got error on case check, will use base32\n"); fprintf(stderr, "Got error on case check, will use base32\n");
@ -1055,7 +1336,7 @@ handshake_switch_codec(int dns_fd)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 's', 'S');
if (read > 0) { if (read > 0) {
if (strncmp("BADLEN", in, 6) == 0) { if (strncmp("BADLEN", in, 6) == 0) {
@ -1112,7 +1393,7 @@ handshake_switch_downenc(int dns_fd)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'o', 'O');
if (read > 0) { if (read > 0) {
if (strncmp("BADLEN", in, 6) == 0) { if (strncmp("BADLEN", in, 6) == 0) {
@ -1138,6 +1419,94 @@ codec_revert:
fprintf(stderr, "Falling back to base32\n"); fprintf(stderr, "Falling back to base32\n");
} }
static void
handshake_try_lazy(int dns_fd)
{
struct timeval tv;
char in[4096];
fd_set fds;
int i;
int r;
int read;
fprintf(stderr, "Switching to lazy mode for low-latency\n");
for (i=0; running && i<3; i++) {
tv.tv_sec = i + 1;
tv.tv_usec = 0;
send_lazy_switch(dns_fd, userid);
FD_ZERO(&fds);
FD_SET(dns_fd, &fds);
r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) {
read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'o', 'O');
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;
}
}
}
fprintf(stderr, "Retrying lazy mode switch...\n");
}
fprintf(stderr, "No reply from server on lazy switch, probably old server version. ");
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 */
{
struct timeval tv;
char in[4096];
fd_set fds;
int i;
int r;
int read;
for (i=0; running && i<5; i++) {
tv.tv_sec = 0;
tv.tv_usec = 500000;
send_lazy_switch(dns_fd, userid);
FD_ZERO(&fds);
FD_SET(dns_fd, &fds);
r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) {
read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'o', 'O');
if (read > 0) {
if (read == 4 && strncmp("Immediate", in, 9) == 0) {
fprintf(stderr, "Server switched back to legacy mode.\n");
lazymode = 0;
selecttimeout = 1;
return;
}
}
}
}
}
static int static int
fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize)
@ -1243,7 +1612,7 @@ handshake_autoprobe_fragsize(int dns_fd)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'r', 'R');
if (read > 0) { if (read > 0) {
/* We got a reply */ /* We got a reply */
@ -1308,7 +1677,7 @@ handshake_set_fragsize(int dns_fd, int fragsize)
r = select(dns_fd + 1, &fds, NULL, NULL, &tv); r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
if(r > 0) { if(r > 0) {
read = read_dns(dns_fd, 0, in, sizeof(in)); read = read_dns_namecheck(dns_fd, 0, in, sizeof(in), 'n', 'N');
if (read > 0) { if (read > 0) {
int accepted_fragsize; int accepted_fragsize;
@ -1349,6 +1718,7 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz
if (raw_mode && handshake_raw_udp(dns_fd, seed)) { if (raw_mode && handshake_raw_udp(dns_fd, seed)) {
conn = CONN_RAW_UDP; conn = CONN_RAW_UDP;
selecttimeout = 20;
} else { } else {
if (raw_mode == 0) { if (raw_mode == 0) {
fprintf(stderr, "Skipping raw mode\n"); fprintf(stderr, "Skipping raw mode\n");
@ -1363,6 +1733,10 @@ client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsiz
handshake_switch_downenc(dns_fd); handshake_switch_downenc(dns_fd);
} }
if (lazymode) {
handshake_try_lazy(dns_fd);
}
if (autodetect_frag_size) { if (autodetect_frag_size) {
fragsize = handshake_autoprobe_fragsize(dns_fd); fragsize = handshake_autoprobe_fragsize(dns_fd);
if (!fragsize) { if (!fragsize) {

View File

@ -28,6 +28,8 @@ void client_set_topdomain(const char *cp);
void client_set_password(const char *cp); void client_set_password(const char *cp);
void set_qtype(char *qtype); void set_qtype(char *qtype);
void set_downenc(char *encoding); void set_downenc(char *encoding);
void client_set_selecttimeout(int select_timeout);
void client_set_lazymode(int lazy_mode);
int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize);
int client_tunnel(int tun_fd, int dns_fd); int client_tunnel(int tun_fd, int dns_fd);

View File

@ -61,8 +61,8 @@ usage() {
extern char *__progname; extern char *__progname;
fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] "
"[-P password] [-m maxfragsize] [-T type] [-O enc] [-z context] [-F pidfile] " "[-P password] [-m maxfragsize] [-T type] [-O enc] [-L 0|1] [-I sec] "
"[nameserver] topdomain\n", __progname); "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname);
exit(2); exit(2);
} }
@ -72,8 +72,8 @@ help() {
fprintf(stderr, "iodine IP over DNS tunneling client\n"); fprintf(stderr, "iodine IP over DNS tunneling client\n");
fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] "
"[-P password] [-m maxfragsize] [-T type] [-O enc] [-z context] [-F pidfile] " "[-P password] [-m maxfragsize] [-T type] [-O enc] [-L 0|1] [-I sec] "
"[nameserver] topdomain\n", __progname); "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname);
fprintf(stderr, " -v to print version info and exit\n"); fprintf(stderr, " -v to print version info and exit\n");
fprintf(stderr, " -h to print this help and exit\n"); fprintf(stderr, " -h to print this help and exit\n");
fprintf(stderr, " -f to keep running in foreground\n"); fprintf(stderr, " -f to keep running in foreground\n");
@ -85,6 +85,8 @@ help() {
fprintf(stderr, " -m maxfragsize, to limit size of downstream packets\n"); fprintf(stderr, " -m maxfragsize, to limit size of downstream packets\n");
fprintf(stderr, " -T dns type: NULL (default, fastest), TXT, CNAME, A (CNAME answer), MX\n"); fprintf(stderr, " -T dns type: NULL (default, fastest), TXT, CNAME, A (CNAME answer), MX\n");
fprintf(stderr, " -O downstream encoding (!NULL): Base32(default), Base64, or Raw (only TXT)\n"); fprintf(stderr, " -O downstream encoding (!NULL): Base32(default), Base64, or Raw (only TXT)\n");
fprintf(stderr, " -L 1: try lazy mode for low-latency (default). 0: don't (implies -I1)\n");
fprintf(stderr, " -I max interval between requests (default 4 sec) to prevent server timeouts\n");
fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n");
fprintf(stderr, " -F pidfile to write pid to a file\n"); fprintf(stderr, " -F pidfile to write pid to a file\n");
fprintf(stderr, "nameserver is the IP number of the relaying nameserver, if absent /etc/resolv.conf is used\n"); fprintf(stderr, "nameserver is the IP number of the relaying nameserver, if absent /etc/resolv.conf is used\n");
@ -127,6 +129,8 @@ main(int argc, char **argv)
int autodetect_frag_size; int autodetect_frag_size;
int retval; int retval;
int raw_mode; int raw_mode;
int lazymode;
int selecttimeout;
nameserv_addr = NULL; nameserv_addr = NULL;
topdomain = NULL; topdomain = NULL;
@ -146,6 +150,8 @@ main(int argc, char **argv)
max_downstream_frag_size = 3072; max_downstream_frag_size = 3072;
retval = 0; retval = 0;
raw_mode = 1; raw_mode = 1;
lazymode = 1;
selecttimeout = 4;
#ifdef WINDOWS32 #ifdef WINDOWS32
WSAStartup(req_version, &wsa_data); WSAStartup(req_version, &wsa_data);
@ -162,7 +168,7 @@ main(int argc, char **argv)
__progname++; __progname++;
#endif #endif
while ((choice = getopt(argc, argv, "vfhru:t:d:P:m:F:T:O:")) != -1) { while ((choice = getopt(argc, argv, "vfhru:t:d:P:m:F:T:O:L:I:")) != -1) {
switch(choice) { switch(choice) {
case 'v': case 'v':
version(); version();
@ -209,6 +215,20 @@ main(int argc, char **argv)
case 'O': /* not -D, is Debug in server */ case 'O': /* not -D, is Debug in server */
set_downenc(optarg); set_downenc(optarg);
break; break;
case 'L':
lazymode = atoi(optarg);
if (lazymode > 1)
lazymode = 1;
if (lazymode < 0)
lazymode = 0;
if (!lazymode)
selecttimeout = 1;
break;
case 'I':
selecttimeout = atoi(optarg);
if (selecttimeout < 1)
selecttimeout = 1;
break;
default: default:
usage(); usage();
/* NOTREACHED */ /* NOTREACHED */
@ -260,6 +280,8 @@ main(int argc, char **argv)
/* NOTREACHED */ /* NOTREACHED */
} }
client_set_selecttimeout(selecttimeout);
client_set_lazymode(lazymode);
client_set_topdomain(topdomain); client_set_topdomain(topdomain);
if (username != NULL) { if (username != NULL) {