2009-08-16 16:30:26 +04:00
/*
* Copyright ( c ) 2006 - 2009 Bjorn Andersson < flex @ kryo . se > , Erik Ekman < yarrick @ kryo . se >
*
* Permission to use , copy , modify , and distribute this software for any
* purpose with or without fee is hereby granted , provided that the above
* copyright notice and this permission notice appear in all copies .
*
* THE SOFTWARE IS PROVIDED " AS IS " AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL , DIRECT , INDIRECT , OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE , DATA OR PROFITS , WHETHER IN AN
* ACTION OF CONTRACT , NEGLIGENCE OR OTHER TORTIOUS ACTION , ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE .
*/
2009-09-20 19:11:14 +04:00
# include <ctype.h>
2009-08-16 16:30:26 +04:00
# include <stdio.h>
# include <stdint.h>
# include <stdlib.h>
# include <string.h>
# include <signal.h>
# include <unistd.h>
# include <sys/param.h>
# include <sys/time.h>
# include <fcntl.h>
# include <zlib.h>
# include <time.h>
# ifdef WINDOWS32
# include "windows.h"
# include <winsock2.h>
# else
# include <arpa/nameser.h>
# ifdef DARWIN
2009-09-19 12:32:57 +04:00
# define BIND_8_COMPAT
# include <arpa/nameser_compat.h>
2009-08-16 16:30:26 +04:00
# endif
# include <grp.h>
# include <pwd.h>
# include <netdb.h>
# endif
# include "common.h"
# include "encoding.h"
# include "base32.h"
# include "base64.h"
# include "dns.h"
# include "login.h"
# include "tun.h"
# include "version.h"
# include "client.h"
2009-09-21 01:10:44 +04:00
static void handshake_lazyoff ( int dns_fd ) ;
2009-08-16 16:30:26 +04:00
static int running ;
static const char * password ;
static struct sockaddr_in nameserv ;
static struct sockaddr_in raw_serv ;
static const char * topdomain ;
static uint16_t rand_seed ;
/* Current up/downstream IP packet */
static struct packet outpkt ;
static struct packet inpkt ;
2009-09-21 01:10:44 +04:00
int outchunkresent = 0 ;
2009-08-16 16:30:26 +04:00
/* My userid at the server */
static char userid ;
2009-09-21 01:10:44 +04:00
static char userid_char ; /* used when sending (uppercase) */
static char userid_char2 ; /* also accepted when receiving (lowercase) */
2009-08-16 16:30:26 +04:00
/* DNS id for next packet */
static uint16_t chunkid ;
2009-09-21 01:10:44 +04:00
static uint16_t chunkid_prev ;
static uint16_t chunkid_prev2 ;
2009-08-16 16:30:26 +04:00
2009-09-20 19:11:14 +04:00
/* Base32 encoder used for non-data packets and replies */
2009-08-16 16:30:26 +04:00
static struct encoder * b32 ;
2009-09-20 19:11:14 +04:00
/* Base64 encoder for replies */
static struct encoder * b64 ;
2009-08-16 16:30:26 +04:00
/* The encoder used for data packets
* Defaults to Base32 , can be changed after handshake */
static struct encoder * dataenc ;
2009-09-20 19:11:14 +04:00
/* The encoder to use for downstream data */
static char downenc = ' ' ;
/* set query type to send */
static unsigned short do_qtype = T_NULL ;
2009-08-16 16:30:26 +04:00
/* My connection mode */
static enum connection conn ;
2009-09-21 01:10:44 +04:00
int selecttimeout ; /* RFC says timeout minimum 5sec */
int lazymode ;
long send_ping_soon ;
time_t lastdownstreamtime ;
2009-08-16 16:30:26 +04:00
void
client_init ( )
{
running = 1 ;
b32 = get_base32_encoder ( ) ;
2009-09-20 19:11:14 +04:00
b64 = get_base64_encoder ( ) ;
2009-08-16 16:30:26 +04:00
dataenc = get_base32_encoder ( ) ;
2009-09-20 19:11:14 +04:00
rand_seed = ( ( unsigned int ) rand ( ) ) & 0xFFFF ;
2009-09-21 01:10:44 +04:00
send_ping_soon = 1 ; /* send ping immediately after startup */
2009-08-16 16:30:26 +04:00
conn = CONN_DNS_NULL ;
2009-09-21 01:10:44 +04:00
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 ;
2009-08-16 16:30:26 +04:00
}
void
client_stop ( )
{
running = 0 ;
}
enum connection
client_get_conn ( )
{
return conn ;
}
void
2009-09-20 12:43:48 +04:00
client_set_nameserver ( const char * cp , int port )
2009-08-16 16:30:26 +04:00
{
struct in_addr addr ;
2009-09-26 01:47:14 +04:00
if ( inet_aton ( cp , & addr ) ! = 1 ) {
# ifndef WINDOWS32
/* MinGW only supports getaddrinfo on WinXP and higher..
* so turn it off in windows for now
*
* try resolving if domain a domain is given */
struct addrinfo * addrinfo ;
struct addrinfo * res ;
if ( getaddrinfo ( cp , NULL , NULL , & addrinfo ) = = 0 ) {
struct sockaddr_in * inaddr ;
for ( res = addrinfo ; res ! = NULL ; res = res - > ai_next ) {
inaddr = ( struct sockaddr_in * ) res - > ai_addr ;
addr = inaddr - > sin_addr ;
break ;
}
freeaddrinfo ( addrinfo ) ;
} else
# endif
errx ( 1 , " error parsing nameserver address: '%s' " , cp ) ;
}
2009-08-16 16:30:26 +04:00
memset ( & nameserv , 0 , sizeof ( nameserv ) ) ;
nameserv . sin_family = AF_INET ;
2009-09-20 12:43:48 +04:00
nameserv . sin_port = htons ( port ) ;
2009-08-16 16:30:26 +04:00
nameserv . sin_addr = addr ;
}
void
client_set_topdomain ( const char * cp )
{
topdomain = cp ;
}
void
client_set_password ( const char * cp )
{
password = cp ;
}
2009-09-20 19:11:14 +04:00
void
set_qtype ( char * qtype )
{
if ( ! strcasecmp ( qtype , " NULL " ) )
do_qtype = T_NULL ;
else if ( ! strcasecmp ( qtype , " CNAME " ) )
do_qtype = T_CNAME ;
else if ( ! strcasecmp ( qtype , " A " ) )
do_qtype = T_A ;
else if ( ! strcasecmp ( qtype , " MX " ) )
do_qtype = T_MX ;
else if ( ! strcasecmp ( qtype , " TXT " ) )
do_qtype = T_TXT ;
}
void
set_downenc ( char * encoding )
{
if ( ! strcasecmp ( encoding , " base32 " ) )
downenc = ' T ' ;
else if ( ! strcasecmp ( encoding , " base64 " ) )
downenc = ' S ' ;
else if ( ! strcasecmp ( encoding , " raw " ) )
downenc = ' R ' ;
}
2009-09-21 01:10:44 +04:00
void
client_set_selecttimeout ( int select_timeout )
{
selecttimeout = select_timeout ;
}
void
2009-09-21 01:10:46 +04:00
client_set_lazymode ( int lazy_mode )
{
lazymode = lazy_mode ;
2009-09-21 01:10:44 +04:00
}
2009-08-16 16:30:26 +04:00
const char *
client_get_raw_addr ( )
{
return inet_ntoa ( raw_serv . sin_addr ) ;
}
static void
send_query ( int fd , char * hostname )
{
char packet [ 4096 ] ;
struct query q ;
size_t len ;
2009-09-21 01:10:44 +04:00
chunkid_prev2 = chunkid_prev ;
chunkid_prev = chunkid ;
2009-09-20 19:11:14 +04:00
chunkid + = 7727 ;
if ( chunkid = = 0 )
/* 0 is used as "no-query" in iodined.c */
chunkid = 7727 ;
q . id = chunkid ;
q . type = do_qtype ;
2009-08-16 16:30:26 +04:00
len = dns_encode ( packet , sizeof ( packet ) , & q , QR_QUERY , hostname , strlen ( hostname ) ) ;
2009-09-20 19:11:14 +04:00
if ( len < 1 ) {
warnx ( " dns_encode doesn't fit " ) ;
return ;
}
2009-08-16 16:30:26 +04:00
sendto ( fd , packet , len , 0 , ( struct sockaddr * ) & nameserv , sizeof ( nameserv ) ) ;
}
static void
send_raw ( int fd , char * buf , int buflen , int user , int cmd )
{
char packet [ 4096 ] ;
int len ;
len = MIN ( sizeof ( packet ) - RAW_HDR_LEN , buflen ) ;
memcpy ( packet , raw_header , RAW_HDR_LEN ) ;
if ( len ) {
memcpy ( & packet [ RAW_HDR_LEN ] , buf , len ) ;
}
len + = RAW_HDR_LEN ;
packet [ RAW_HDR_CMD ] = cmd | ( user & 0x0F ) ;
sendto ( fd , packet , len , 0 , ( struct sockaddr * ) & raw_serv , sizeof ( raw_serv ) ) ;
}
static void
send_raw_data ( int dns_fd )
{
send_raw ( dns_fd , outpkt . data , outpkt . len , userid , RAW_HDR_CMD_DATA ) ;
2009-09-21 01:10:44 +04:00
outpkt . len = 0 ;
2009-08-16 16:30:26 +04:00
}
static void
send_packet ( int fd , char cmd , const char * data , const size_t datalen )
{
char buf [ 4096 ] ;
buf [ 0 ] = cmd ;
build_hostname ( buf + 1 , sizeof ( buf ) - 1 , data , datalen , topdomain , b32 ) ;
send_query ( fd , buf ) ;
}
2009-09-21 01:10:44 +04:00
static inline int
2009-08-16 16:30:26 +04:00
is_sending ( )
{
return ( outpkt . len ! = 0 ) ;
}
static void
send_chunk ( int fd )
{
char buf [ 4096 ] ;
int avail ;
int code ;
char * p ;
p = outpkt . data ;
p + = outpkt . offset ;
avail = outpkt . len - outpkt . offset ;
outpkt . sentlen = build_hostname ( buf + 4 , sizeof ( buf ) - 4 , p , avail , topdomain , dataenc ) ;
/* Build upstream data header (see doc/proto_xxxxxxxx.txt) */
2009-09-21 01:10:44 +04:00
buf [ 0 ] = userid_char ; /* First byte is hex userid */
2009-08-16 16:30:26 +04:00
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 */
2009-09-21 01:10:44 +04:00
code = ( ( outpkt . fragment & 3 ) < < 3 ) | ( inpkt . seqno & 7 ) ;
2009-08-16 16:30:26 +04:00
buf [ 2 ] = b32_5to8 ( code ) ; /* Third byte is 2 bits lower fragment count, 3 bits downstream packet seqno */
2009-09-21 01:10:44 +04:00
code = ( ( inpkt . fragment & 15 ) < < 1 ) | ( outpkt . sentlen = = avail ) ;
2009-08-16 16:30:26 +04:00
buf [ 3 ] = b32_5to8 ( code ) ; /* Fourth byte is 4 bits downstream fragment count, 1 bit last frag flag */
2009-09-21 01:10:44 +04:00
#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
2009-08-16 16:30:26 +04:00
send_query ( fd , buf ) ;
}
static void
send_ping ( int fd )
{
if ( conn = = CONN_DNS_NULL ) {
char data [ 4 ] ;
data [ 0 ] = userid ;
2009-09-21 01:10:44 +04:00
data [ 1 ] = ( ( inpkt . seqno & 7 ) < < 4 ) | ( inpkt . fragment & 15 ) ;
2009-08-16 16:30:26 +04:00
data [ 2 ] = ( rand_seed > > 8 ) & 0xff ;
data [ 3 ] = ( rand_seed > > 0 ) & 0xff ;
rand_seed + + ;
2009-09-21 01:10:44 +04:00
#if 0
fprintf ( stderr , " Send: down %d/%d (ping) \n " ,
inpkt . seqno , inpkt . fragment ) ;
# endif
2009-09-20 19:11:14 +04:00
send_packet ( fd , ' p ' , data , sizeof ( data ) ) ;
2009-08-16 16:30:26 +04:00
} else {
send_raw ( fd , NULL , 0 , userid , RAW_HDR_CMD_PING ) ;
}
}
2009-09-22 00:06:30 +04:00
static void
write_dns_error ( struct query * q )
{
if ( ! q ) return ;
switch ( q - > rcode ) {
case NOERROR : /* 0 */
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 */
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 ;
}
}
2009-08-16 16:30:26 +04:00
static int
2009-09-21 01:10:44 +04:00
read_dns_withq ( int dns_fd , int tun_fd , char * buf , int buflen , struct query * q ) /* FIXME: tun_fd needed for raw handling */
2009-08-16 16:30:26 +04:00
{
struct sockaddr_in from ;
char data [ 64 * 1024 ] ;
socklen_t addrlen ;
int r ;
addrlen = sizeof ( struct sockaddr ) ;
if ( ( r = recvfrom ( dns_fd , data , sizeof ( data ) , 0 ,
2009-09-21 01:10:44 +04:00
( struct sockaddr * ) & from , & addrlen ) ) < 0 ) {
2009-08-16 16:30:26 +04:00
warn ( " recvfrom " ) ;
2009-09-21 01:10:44 +04:00
return - 1 ;
2009-08-16 16:30:26 +04:00
}
if ( conn = = CONN_DNS_NULL ) {
int rv ;
2009-09-21 01:10:44 +04:00
if ( r < = 0 )
/* useless packet */
return 0 ;
2009-08-16 16:30:26 +04:00
2009-09-21 01:10:44 +04:00
rv = dns_decode ( buf , buflen , q , QR_ANSWER , data , r ) ;
if ( rv < = 0 )
return rv ;
2009-08-16 16:30:26 +04:00
2009-09-21 01:10:44 +04:00
if ( q - > type = = T_CNAME | | q - > type = = T_MX | | q - > type = = T_TXT )
2009-09-20 19:11:14 +04:00
/* CNAME an also be returned from an A (or MX) question */
{
size_t space ;
/*
* buf is a hostname or txt stream that we still need to
* decode to binary
*
* also update rv with the number of valid bytes
*
* data is unused here , and will certainly hold the smaller binary
*/
switch ( buf [ 0 ] ) {
case ' h ' : /* Hostname with base32 */
case ' H ' :
if ( rv < 5 ) {
/* 1 byte H, 3 bytes ".xy", >=1 byte data */
rv = 0 ;
break ;
}
rv - = 3 ; /* rv=strlen, strip ".xy" */
rv = unpack_data ( data , sizeof ( data ) , buf + 1 , rv - 1 , b32 ) ;
/* this also does undotify */
rv = MIN ( rv , buflen ) ;
memcpy ( buf , data , rv ) ;
break ;
case ' i ' : /* Hostname++ with base64 */
case ' I ' :
if ( rv < 5 ) {
/* 1 byte H, 3 bytes ".xy", >=1 byte data */
rv = 0 ;
break ;
}
rv - = 3 ; /* rv=strlen, strip ".xy" */
rv = unpack_data ( data , sizeof ( data ) , buf + 1 , rv - 1 , b64 ) ;
/* this also does undotify */
rv = MIN ( rv , buflen ) ;
memcpy ( buf , data , rv ) ;
break ;
case ' t ' : /* plain base32(Thirty-two) from TXT */
case ' T ' :
if ( rv < 2 ) {
rv = 0 ;
break ;
}
space = sizeof ( data ) ;
rv = b32 - > decode ( data , & space , buf + 1 , rv - 1 ) ;
rv = MIN ( rv , buflen ) ;
memcpy ( buf , data , rv ) ;
break ;
case ' s ' : /* plain base64(Sixty-four) from TXT */
case ' S ' :
if ( rv < 2 ) {
rv = 0 ;
break ;
}
space = sizeof ( data ) ;
rv = b64 - > decode ( data , & space , buf + 1 , rv - 1 ) ;
rv = MIN ( rv , buflen ) ;
memcpy ( buf , data , rv ) ;
break ;
case ' r ' : /* Raw binary from TXT */
case ' R ' :
rv - - ; /* rv>=1 already checked */
memmove ( buf , buf + 1 , rv ) ;
break ;
default :
warnx ( " Received unsupported encoding " ) ;
rv = 0 ;
break ;
}
}
2009-08-16 16:30:26 +04:00
return rv ;
} else { /* CONN_RAW_UDP */
unsigned long datalen ;
char buf [ 64 * 1024 ] ;
/* minimum length */
if ( r < RAW_HDR_LEN ) return 0 ;
/* should start with header */
if ( memcmp ( data , raw_header , RAW_HDR_IDENT_LEN ) ) return 0 ;
/* should be data packet */
if ( RAW_HDR_GET_CMD ( data ) ! = RAW_HDR_CMD_DATA ) return 0 ;
/* should be my user id */
if ( RAW_HDR_GET_USR ( data ) ! = userid ) return 0 ;
r - = RAW_HDR_LEN ;
datalen = sizeof ( buf ) ;
if ( uncompress ( ( uint8_t * ) buf , & datalen , ( uint8_t * ) & data [ RAW_HDR_LEN ] , r ) = = Z_OK ) {
write_tun ( tun_fd , buf , datalen ) ;
}
return 0 ;
}
}
2009-09-21 01:10:44 +04:00
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 ) ;
2009-09-22 00:06:30 +04:00
/* Filter out any other replies */
if ( q . name [ 0 ] ! = c1 & & q . name [ 0 ] ! = c2 )
2009-09-21 01:10:44 +04:00
return 0 ;
2009-09-22 00:06:30 +04:00
/* Print rcode errors */
if ( rv < 0 ) {
write_dns_error ( & q ) ;
}
2009-09-21 01:10:44 +04:00
return rv ; /* may also be 0 = useless or -1 = error (printed) */
}
2009-08-16 16:30:26 +04:00
static int
tunnel_tun ( int tun_fd , int dns_fd )
{
unsigned long outlen ;
unsigned long inlen ;
char out [ 64 * 1024 ] ;
char in [ 64 * 1024 ] ;
ssize_t read ;
if ( ( read = read_tun ( tun_fd , in , sizeof ( in ) ) ) < = 0 )
return - 1 ;
2009-09-21 01:10:44 +04:00
/* We may be here only to empty the tun device; then return -1
to force continue in select loop . */
if ( is_sending ( ) )
return - 1 ;
2009-08-16 16:30:26 +04:00
outlen = sizeof ( out ) ;
inlen = read ;
compress2 ( ( uint8_t * ) out , & outlen , ( uint8_t * ) in , inlen , 9 ) ;
memcpy ( outpkt . data , out , MIN ( outlen , sizeof ( outpkt . data ) ) ) ;
outpkt . sentlen = 0 ;
outpkt . offset = 0 ;
2009-09-21 01:10:44 +04:00
outpkt . seqno = ( outpkt . seqno + 1 ) & 7 ;
2009-08-16 16:30:26 +04:00
outpkt . len = outlen ;
outpkt . fragment = 0 ;
2009-09-21 01:10:44 +04:00
outchunkresent = 0 ;
2009-08-16 16:30:26 +04:00
if ( conn = = CONN_DNS_NULL ) {
send_chunk ( dns_fd ) ;
2009-09-21 01:10:44 +04:00
send_ping_soon = 0 ;
2009-08-16 16:30:26 +04:00
} else {
send_raw_data ( dns_fd ) ;
}
return read ;
}
static int
tunnel_dns ( int tun_fd , int dns_fd )
{
2009-09-21 01:10:44 +04:00
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 ;
2009-08-16 16:30:26 +04:00
unsigned long datalen ;
char buf [ 64 * 1024 ] ;
2009-09-21 01:10:44 +04:00
int read ;
int send_something_now = 0 ;
2009-08-16 16:30:26 +04:00
2009-09-22 00:06:30 +04:00
memset ( q . name , 0 , sizeof ( q . name ) ) ;
read = read_dns_withq ( dns_fd , tun_fd , buf , sizeof ( buf ) , & q ) ;
/* Don't process anything that isn't data for us */
if ( q . name [ 0 ] ! = ' P ' & & q . name [ 0 ] ! = ' p ' & &
q . name [ 0 ] ! = userid_char & & q . name [ 0 ] ! = userid_char2 )
return - 1 ; /* nothing done */
if ( read < 2 ) {
2009-09-21 01:10:44 +04:00
/* Maybe SERVFAIL etc. Send ping to get things back in order,
but wait a bit to prevent fast ping - pong loops . */
2009-09-22 00:06:30 +04:00
write_dns_error ( & q ) ;
2009-09-21 01:10:44 +04:00
send_ping_soon = 900 ;
return - 1 ; /* nothing done */
}
2009-08-16 16:30:26 +04:00
2009-09-21 01:10:44 +04:00
if ( read = = 5 & & ! strncmp ( " BADIP " , buf , 5 ) ) {
warnx ( " BADIP: Server rejected sender IP address (maybe iodined -c will help), or server kicked us due to timeout. Will exit if no downstream data is received in 60 seconds. " ) ;
return - 1 ; /* nothing done */
2009-08-16 16:30:26 +04:00
}
2009-09-21 01:10:44 +04:00
if ( send_ping_soon ) {
send_something_now = 1 ;
send_ping_soon = 0 ;
}
2009-08-16 16:30:26 +04:00
2009-09-21 01:10:44 +04:00
/* 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 ) ;
2009-08-16 16:30:26 +04:00
}
2009-09-21 01:10:44 +04:00
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 ( read = = 2 & & new_down_seqno ! = inpkt . seqno & &
! recent_seqno ( inpkt . seqno , new_down_seqno ) ) {
/* This is a seqno that we didn't see yet, but it has
no data any more . Possible since iodined will send
fitting packs just once and not wait for ack .
Real data got lost , or will arrive shortly .
Update our idea of the seqno , and drop any waiting
old pack . Send ping to get things back on track . */
inpkt . seqno = new_down_seqno ;
inpkt . fragment = new_down_fragment ;
2009-08-16 16:30:26 +04:00
inpkt . len = 0 ;
2009-09-21 01:10:44 +04:00
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 .
5 msec = 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 .
20 msec 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 ( 1 sec if is_sending ( ) ) . */
2009-08-16 16:30:26 +04:00
}
2009-09-21 01:10:44 +04:00
/* Send ping if we didn't send anything yet */
if ( send_something_now ) {
2009-08-16 16:30:26 +04:00
send_ping ( dns_fd ) ;
2009-09-21 01:10:44 +04:00
send_ping_soon = 0 ;
}
2009-08-16 16:30:26 +04:00
return read ;
}
int
client_tunnel ( int tun_fd , int dns_fd )
{
struct timeval tv ;
fd_set fds ;
int rv ;
int i ;
rv = 0 ;
2009-09-21 01:10:44 +04:00
lastdownstreamtime = time ( NULL ) ;
2009-08-16 16:30:26 +04:00
while ( running ) {
2009-09-21 01:10:44 +04:00
tv . tv_sec = selecttimeout ;
2009-08-16 16:30:26 +04:00
tv . tv_usec = 0 ;
2009-09-21 01:10:44 +04:00
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 ;
}
2009-08-16 16:30:26 +04:00
FD_ZERO ( & fds ) ;
2009-09-21 01:10:44 +04:00
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 . */
2009-08-16 16:30:26 +04:00
FD_SET ( tun_fd , & fds ) ;
}
FD_SET ( dns_fd , & fds ) ;
i = select ( MAX ( tun_fd , dns_fd ) + 1 , & fds , NULL , NULL , & tv ) ;
2009-09-21 01:10:44 +04:00
if ( lastdownstreamtime + 60 < time ( NULL ) ) {
warnx ( " No downstream data received in 60 seconds, shutting down. " ) ;
running = 0 ;
}
2009-08-16 16:30:26 +04:00
if ( running = = 0 )
break ;
if ( i < 0 )
err ( 1 , " select " ) ;
2009-09-21 01:10:44 +04:00
if ( i = = 0 ) {
/* timeout */
if ( is_sending ( ) ) {
/* Re-send current fragment; either frag
or ack probably dropped somewhere .
But problem : no cache - miss - counter ,
so hostname will be identical .
Just drop whole packet after 3 retries ,
and TCP retransmit will solve it .
NOTE : tun dropping above should be
> = ( value_here - 1 ) */
if ( outchunkresent < 3 ) {
outchunkresent + + ;
send_chunk ( dns_fd ) ;
} else {
outpkt . offset = 0 ;
outpkt . len = 0 ;
outpkt . sentlen = 0 ;
outchunkresent = 0 ;
send_ping ( dns_fd ) ;
}
} else {
send_ping ( dns_fd ) ;
}
send_ping_soon = 0 ;
2009-08-16 16:30:26 +04:00
} else {
2009-09-21 01:10:44 +04:00
2009-08-16 16:30:26 +04:00
if ( FD_ISSET ( tun_fd , & fds ) ) {
if ( tunnel_tun ( tun_fd , dns_fd ) < = 0 )
continue ;
2009-09-21 01:10:44 +04: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. */
2009-08-16 16:30:26 +04:00
}
if ( FD_ISSET ( dns_fd , & fds ) ) {
if ( tunnel_dns ( tun_fd , dns_fd ) < = 0 )
continue ;
}
}
}
return rv ;
}
static void
send_login ( int fd , char * login , int len )
{
char data [ 19 ] ;
memset ( data , 0 , sizeof ( data ) ) ;
data [ 0 ] = userid ;
memcpy ( & data [ 1 ] , login , MIN ( len , 16 ) ) ;
data [ 17 ] = ( rand_seed > > 8 ) & 0xff ;
data [ 18 ] = ( rand_seed > > 0 ) & 0xff ;
rand_seed + + ;
2009-09-20 19:11:14 +04:00
send_packet ( fd , ' l ' , data , sizeof ( data ) ) ;
2009-08-16 16:30:26 +04:00
}
static void
send_fragsize_probe ( int fd , int fragsize )
{
char probedata [ 256 ] ;
char buf [ 4096 ] ;
2009-09-20 19:11:14 +04:00
/*
* build a large query domain which is random and maximum size ,
* will also take up maximal space in the return packet
*/
memset ( probedata , MAX ( 1 , rand_seed & 0xff ) , sizeof ( probedata ) ) ;
probedata [ 1 ] = MAX ( 1 , ( rand_seed > > 8 ) & 0xff ) ;
2009-08-16 16:30:26 +04:00
rand_seed + + ;
build_hostname ( buf + 4 , sizeof ( buf ) - 4 , probedata , sizeof ( probedata ) , topdomain , dataenc ) ;
fragsize & = 2047 ;
buf [ 0 ] = ' r ' ; /* Probe downstream fragsize packet */
buf [ 1 ] = b32_5to8 ( ( userid < < 1 ) | ( ( fragsize > > 10 ) & 1 ) ) ;
buf [ 2 ] = b32_5to8 ( ( fragsize > > 5 ) & 31 ) ;
buf [ 3 ] = b32_5to8 ( fragsize & 31 ) ;
send_query ( fd , buf ) ;
}
static void
send_set_downstream_fragsize ( int fd , int fragsize )
{
char data [ 5 ] ;
data [ 0 ] = userid ;
data [ 1 ] = ( fragsize & 0xff00 ) > > 8 ;
data [ 2 ] = ( fragsize & 0x00ff ) ;
data [ 3 ] = ( rand_seed > > 8 ) & 0xff ;
data [ 4 ] = ( rand_seed > > 0 ) & 0xff ;
rand_seed + + ;
2009-09-20 19:11:14 +04:00
send_packet ( fd , ' n ' , data , sizeof ( data ) ) ;
2009-08-16 16:30:26 +04:00
}
static void
send_version ( int fd , uint32_t version )
{
char data [ 6 ] ;
2009-09-20 19:11:14 +04:00
2009-08-16 16:30:26 +04:00
data [ 0 ] = ( version > > 24 ) & 0xff ;
data [ 1 ] = ( version > > 16 ) & 0xff ;
data [ 2 ] = ( version > > 8 ) & 0xff ;
data [ 3 ] = ( version > > 0 ) & 0xff ;
data [ 4 ] = ( rand_seed > > 8 ) & 0xff ;
data [ 5 ] = ( rand_seed > > 0 ) & 0xff ;
rand_seed + + ;
send_packet ( fd , ' V ' , data , sizeof ( data ) ) ;
}
static void
send_ip_request ( int fd , int userid )
{
char buf [ 512 ] = " I____. " ;
buf [ 1 ] = b32_5to8 ( userid ) ;
buf [ 2 ] = b32_5to8 ( ( rand_seed > > 10 ) & 0x1f ) ;
buf [ 3 ] = b32_5to8 ( ( rand_seed > > 5 ) & 0x1f ) ;
buf [ 4 ] = b32_5to8 ( ( rand_seed ) & 0x1f ) ;
rand_seed + + ;
strncat ( buf , topdomain , 512 - strlen ( buf ) ) ;
send_query ( fd , buf ) ;
}
static void
send_raw_udp_login ( int dns_fd , int userid , int seed )
{
char buf [ 16 ] ;
login_calculate ( buf , 16 , password , seed + 1 ) ;
send_raw ( dns_fd , buf , sizeof ( buf ) , userid , RAW_HDR_CMD_LOGIN ) ;
}
static void
send_case_check ( int fd )
{
/* The '+' plus character is not allowed according to RFC.
* Expect to get SERVFAIL or similar if it is rejected .
*/
char buf [ 512 ] = " zZ+-aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyY1234. " ;
strncat ( buf , topdomain , 512 - strlen ( buf ) ) ;
send_query ( fd , buf ) ;
}
static void
send_codec_switch ( int fd , int userid , int bits )
{
2009-09-20 19:11:14 +04:00
char buf [ 512 ] = " s_____. " ;
2009-08-16 16:30:26 +04:00
buf [ 1 ] = b32_5to8 ( userid ) ;
buf [ 2 ] = b32_5to8 ( bits ) ;
buf [ 3 ] = b32_5to8 ( ( rand_seed > > 10 ) & 0x1f ) ;
buf [ 4 ] = b32_5to8 ( ( rand_seed > > 5 ) & 0x1f ) ;
buf [ 5 ] = b32_5to8 ( ( rand_seed ) & 0x1f ) ;
rand_seed + + ;
strncat ( buf , topdomain , 512 - strlen ( buf ) ) ;
send_query ( fd , buf ) ;
}
2009-09-20 19:11:14 +04: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 ) ;
rand_seed + + ;
strncat ( buf , topdomain , 512 - strlen ( buf ) ) ;
send_query ( fd , buf ) ;
}
2009-09-21 01:10:44 +04:00
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 ) ;
}
2009-08-16 16:30:26 +04:00
static int
handshake_version ( int dns_fd , int * seed )
{
2009-09-21 01:10:44 +04:00
char hex [ ] = " 0123456789abcdef " ;
char hex2 [ ] = " 0123456789ABCDEF " ;
2009-08-16 16:30:26 +04:00
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
uint32_t payload ;
int i ;
int r ;
int read ;
for ( i = 0 ; running & & i < 5 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_version ( dns_fd , VERSION ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' v ' , ' V ' ) ;
if ( read < = 0 )
2009-08-16 16:30:26 +04:00
continue ;
if ( read > = 9 ) {
payload = ( ( ( in [ 4 ] & 0xff ) < < 24 ) |
( ( in [ 5 ] & 0xff ) < < 16 ) |
( ( in [ 6 ] & 0xff ) < < 8 ) |
( ( in [ 7 ] & 0xff ) ) ) ;
if ( strncmp ( " VACK " , in , 4 ) = = 0 ) {
* seed = payload ;
userid = in [ 8 ] ;
2009-09-21 01:10:44 +04:00
userid_char = hex [ userid & 15 ] ;
userid_char2 = hex2 [ userid & 15 ] ;
2009-08-16 16:30:26 +04:00
fprintf ( stderr , " Version ok, both using protocol v 0x%08x. You are user #%d \n " , VERSION , userid ) ;
return 0 ;
} else if ( strncmp ( " VNAK " , in , 4 ) = = 0 ) {
warnx ( " You use protocol v 0x%08x, server uses v 0x%08x. Giving up " ,
VERSION , payload ) ;
return 1 ;
} else if ( strncmp ( " VFUL " , in , 4 ) = = 0 ) {
warnx ( " Server full, all %d slots are taken. Try again later " , payload ) ;
return 1 ;
}
} else
warnx ( " did not receive proper login challenge " ) ;
}
fprintf ( stderr , " Retrying version check... \n " ) ;
}
2009-09-20 19:11:14 +04:00
warnx ( " couldn't connect to server (maybe other -T options will work) " ) ;
2009-08-16 16:30:26 +04:00
return 1 ;
}
static int
handshake_login ( int dns_fd , int seed )
{
struct timeval tv ;
char in [ 4096 ] ;
char login [ 16 ] ;
char server [ 65 ] ;
char client [ 65 ] ;
int mtu ;
fd_set fds ;
int i ;
int r ;
int read ;
login_calculate ( login , 16 , password , seed ) ;
for ( i = 0 ; running & & i < 5 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_login ( dns_fd , login , 16 ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' l ' , ' L ' ) ;
if ( read < = 0 )
2009-08-16 16:30:26 +04:00
continue ;
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 , netmask ) = = 0 & &
tun_setmtu ( mtu ) = = 0 ) {
2009-09-19 12:24:59 +04:00
fprintf ( stderr , " Server tunnel IP is %s \n " , server ) ;
2009-08-16 16:30:26 +04:00
return 0 ;
} else {
errx ( 4 , " Failed to set IP and MTU " ) ;
}
} else {
fprintf ( stderr , " Received bad handshake \n " ) ;
}
}
}
fprintf ( stderr , " Retrying login... \n " ) ;
}
warnx ( " couldn't login to server " ) ;
return 1 ;
}
static int
handshake_raw_udp ( int dns_fd , int seed )
{
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
int i ;
int r ;
int len ;
unsigned remoteaddr = 0 ;
struct in_addr server ;
fprintf ( stderr , " Testing raw UDP data to the server (skip with -r) \n " ) ;
for ( i = 0 ; running & & i < 3 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_ip_request ( dns_fd , userid ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
len = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' i ' , ' I ' ) ;
2009-08-16 16:30:26 +04:00
if ( len = = 5 & & in [ 0 ] = = ' I ' ) {
/* Received IP address */
remoteaddr = ( in [ 1 ] & 0xff ) ;
remoteaddr < < = 8 ;
remoteaddr | = ( in [ 2 ] & 0xff ) ;
remoteaddr < < = 8 ;
remoteaddr | = ( in [ 3 ] & 0xff ) ;
remoteaddr < < = 8 ;
remoteaddr | = ( in [ 4 ] & 0xff ) ;
server . s_addr = ntohl ( remoteaddr ) ;
break ;
}
} else {
fprintf ( stderr , " . " ) ;
fflush ( stderr ) ;
}
}
if ( ! remoteaddr ) {
fprintf ( stderr , " Failed to get raw server IP, will use DNS mode. \n " ) ;
return 0 ;
}
fprintf ( stderr , " Server is at %s, trying raw login: " , inet_ntoa ( server ) ) ;
fflush ( stderr ) ;
/* Store address to iodined server */
memset ( & raw_serv , 0 , sizeof ( raw_serv ) ) ;
raw_serv . sin_family = AF_INET ;
raw_serv . sin_port = htons ( 53 ) ;
raw_serv . sin_addr = server ;
/* do login against port 53 on remote server
* based on the old seed . If reply received ,
* switch to raw udp mode */
for ( i = 0 ; running & & i < 4 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_raw_udp_login ( dns_fd , userid , seed ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
/* recv() needed for windows, dont change to read() */
len = recv ( dns_fd , in , sizeof ( in ) , 0 ) ;
if ( len > = ( 16 + RAW_HDR_LEN ) ) {
char hash [ 16 ] ;
login_calculate ( hash , 16 , password , seed - 1 ) ;
if ( memcmp ( in , raw_header , RAW_HDR_IDENT_LEN ) = = 0
& & RAW_HDR_GET_CMD ( in ) = = RAW_HDR_CMD_LOGIN
& & memcmp ( & in [ RAW_HDR_LEN ] , hash , sizeof ( hash ) ) = = 0 ) {
fprintf ( stderr , " OK \n " ) ;
return 1 ;
}
}
}
fprintf ( stderr , " . " ) ;
fflush ( stderr ) ;
}
fprintf ( stderr , " failed \n " ) ;
return 0 ;
}
static int
handshake_case_check ( int dns_fd )
{
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
int i ;
int r ;
int read ;
int case_preserved ;
case_preserved = 0 ;
for ( i = 0 ; running & & i < 5 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_case_check ( dns_fd ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' z ' , ' Z ' ) ;
2009-08-16 16:30:26 +04:00
if ( read > 0 ) {
2009-09-21 01:10:44 +04:00
if ( read < ( 27 * 2 ) ) {
fprintf ( stderr , " Received short case check reply. Will use base32 encoder \n " ) ;
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 ;
2009-08-16 16:30:26 +04:00
}
}
2009-09-21 01:10:44 +04:00
return case_preserved ;
2009-08-16 16:30:26 +04:00
}
} else {
fprintf ( stderr , " Got error on case check, will use base32 \n " ) ;
return case_preserved ;
}
}
fprintf ( stderr , " Retrying case check... \n " ) ;
}
fprintf ( stderr , " No reply on case check, continuing \n " ) ;
return case_preserved ;
}
static void
handshake_switch_codec ( int dns_fd )
{
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
int i ;
int r ;
int read ;
dataenc = get_base64_encoder ( ) ;
2009-09-20 19:11:14 +04:00
fprintf ( stderr , " Switching upstream to %s codec \n " , dataenc - > name ) ;
2009-08-16 16:30:26 +04:00
/* Send to server that this user will use base64 from now on */
for ( i = 0 ; running & & i < 5 ; i + + ) {
int bits ;
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
bits = 6 ; /* base64 = 6 bits per byte */
send_codec_switch ( dns_fd , userid , bits ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' s ' , ' S ' ) ;
2009-08-16 16:30:26 +04:00
if ( read > 0 ) {
if ( strncmp ( " BADLEN " , in , 6 ) = = 0 ) {
fprintf ( stderr , " Server got bad message length. " ) ;
goto codec_revert ;
} else if ( strncmp ( " BADIP " , in , 5 ) = = 0 ) {
fprintf ( stderr , " Server rejected sender IP address. " ) ;
goto codec_revert ;
} else if ( strncmp ( " BADCODEC " , in , 8 ) = = 0 ) {
fprintf ( stderr , " Server rejected the selected codec. " ) ;
goto codec_revert ;
}
in [ read ] = 0 ; /* zero terminate */
2009-09-20 19:11:14 +04:00
fprintf ( stderr , " Server switched upstream to codec %s \n " , in ) ;
2009-08-16 16:30:26 +04:00
return ;
}
}
fprintf ( stderr , " Retrying codec switch... \n " ) ;
}
fprintf ( stderr , " No reply from server on codec switch. " ) ;
codec_revert :
fprintf ( stderr , " Falling back to base32 \n " ) ;
dataenc = get_base32_encoder ( ) ;
}
2009-09-20 19:11:14 +04:00
static void
handshake_switch_downenc ( int dns_fd )
{
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
int i ;
int r ;
int read ;
char * dname ;
dname = " Base32 " ;
if ( downenc = = ' S ' )
dname = " Base64 " ;
else if ( downenc = = ' R ' )
dname = " Raw " ;
fprintf ( stderr , " Switching downstream to codec %s \n " , dname ) ;
for ( i = 0 ; running & & i < 5 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_downenc_switch ( dns_fd , userid ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' o ' , ' O ' ) ;
2009-09-20 19:11:14 +04:00
if ( read > 0 ) {
if ( strncmp ( " BADLEN " , in , 6 ) = = 0 ) {
fprintf ( stderr , " Server got bad message length. " ) ;
goto codec_revert ;
} else if ( strncmp ( " BADIP " , in , 5 ) = = 0 ) {
fprintf ( stderr , " Server rejected sender IP address. " ) ;
goto codec_revert ;
} else if ( strncmp ( " BADCODEC " , in , 8 ) = = 0 ) {
fprintf ( stderr , " Server rejected the selected codec. " ) ;
goto codec_revert ;
}
in [ read ] = 0 ; /* zero terminate */
fprintf ( stderr , " Server switched downstream to codec %s \n " , in ) ;
return ;
}
}
fprintf ( stderr , " Retrying codec switch... \n " ) ;
}
fprintf ( stderr , " No reply from server on codec switch. " ) ;
codec_revert :
fprintf ( stderr , " Falling back to base32 \n " ) ;
}
2009-09-21 01:10:44 +04:00
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 ;
}
}
}
}
}
2009-09-20 19:11:14 +04: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 ) ;
static int nocheck_warned = 0 ;
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; */
/* Check for corruption */
if ( ( in [ 2 ] & 0xff ) = = 107 ) {
int okay = 1 ;
int i ;
unsigned int v = in [ 3 ] & 0xff ;
for ( i = 3 ; i < read ; i + + , v + = 107 )
if ( ( in [ i ] & 0xff ) ! = ( v & 0xff ) ) {
okay = 0 ;
break ;
}
if ( okay ) {
fprintf ( stderr , " %d ok.. " , acked_fragsize ) ;
fflush ( stderr ) ;
* max_fragsize = acked_fragsize ;
return 1 ;
} else {
2009-09-20 20:54:29 +04:00
if ( downenc ! = ' ' & & downenc ! = ' T ' ) {
2009-09-20 19:11:14 +04:00
fprintf ( stderr , " %d corrupted at %d.. (Try -O Base32) \n " , acked_fragsize , i ) ;
2009-09-20 20:54:29 +04:00
} else {
2009-09-20 19:11:14 +04:00
fprintf ( stderr , " %d corrupted at %d.. " , acked_fragsize , i ) ;
fflush ( stderr ) ;
2009-09-20 20:54:29 +04:00
}
2009-09-20 19:11:14 +04:00
return 1 ;
}
} /* always returns */
/* here when uncheckable, so assume correct */
if ( read > = 3 & & nocheck_warned = = 0 ) {
fprintf ( stderr , " (Old server version, cannot check for corruption) \n " ) ;
fflush ( stderr ) ;
nocheck_warned = 1 ;
}
fprintf ( stderr , " %d ok.. " , acked_fragsize ) ;
fflush ( stderr ) ;
* max_fragsize = acked_fragsize ;
return 1 ;
}
2009-08-16 16:30:26 +04:00
static int
handshake_autoprobe_fragsize ( int dns_fd )
{
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
int i ;
int r ;
int read ;
int proposed_fragsize = 768 ;
int range = 768 ;
2009-09-20 19:11:14 +04:00
int max_fragsize ;
2009-08-16 16:30:26 +04:00
max_fragsize = 0 ;
fprintf ( stderr , " Autoprobing max downstream fragment size... (skip with -m fragsize) \n " ) ;
2009-09-20 19:11:14 +04:00
while ( running & & range > 0 & & ( range > = 8 | | max_fragsize < 300 ) ) {
/* stop the slow probing early when we have enough bytes anyway */
2009-08-16 16:30:26 +04:00
for ( i = 0 ; running & & i < 3 ; i + + ) {
tv . tv_sec = 1 ;
tv . tv_usec = 0 ;
send_fragsize_probe ( dns_fd , proposed_fragsize ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
2009-09-20 20:54:29 +04:00
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' r ' , ' R ' ) ;
2009-08-16 16:30:26 +04:00
if ( read > 0 ) {
/* We got a reply */
2009-09-20 19:11:14 +04:00
if ( fragsize_check ( in , read , proposed_fragsize , & max_fragsize ) = = 1 )
break ;
2009-08-16 16:30:26 +04:00
}
}
fprintf ( stderr , " . " ) ;
fflush ( stderr ) ;
}
range > > = 1 ;
if ( max_fragsize = = proposed_fragsize ) {
/* Try bigger */
proposed_fragsize + = range ;
} else {
/* Try smaller */
fprintf ( stderr , " %d not ok.. " , proposed_fragsize ) ;
fflush ( stderr ) ;
proposed_fragsize - = range ;
}
}
if ( ! running ) {
fprintf ( stderr , " \n " ) ;
2009-09-20 19:11:14 +04:00
warnx ( " stopped while autodetecting fragment size (Try setting manually with -m) " ) ;
2009-08-16 16:30:26 +04:00
return 0 ;
}
2009-09-20 19:11:14 +04:00
if ( max_fragsize < = 2 ) {
2009-08-16 16:30:26 +04:00
/* Tried all the way down to 2 and found no good size */
fprintf ( stderr , " \n " ) ;
2009-09-20 19:11:14 +04:00
warnx ( " found no accepted fragment size. (Try forcing with -m, or try other -T or -O options) " ) ;
2009-08-16 16:30:26 +04:00
return 0 ;
}
2009-09-20 19:11:14 +04:00
/* data header adds 2 bytes */
fprintf ( stderr , " will use %d-2=%d \n " , max_fragsize , max_fragsize - 2 ) ;
if ( do_qtype ! = T_NULL & & downenc = = ' ' )
fprintf ( stderr , " (Maybe other -O options will increase throughput) \n " ) ;
return max_fragsize - 2 ;
2009-08-16 16:30:26 +04:00
}
static void
handshake_set_fragsize ( int dns_fd , int fragsize )
{
struct timeval tv ;
char in [ 4096 ] ;
fd_set fds ;
int i ;
int r ;
int read ;
fprintf ( stderr , " Setting downstream fragment size to max %d... \n " , fragsize ) ;
for ( i = 0 ; running & & i < 5 ; i + + ) {
tv . tv_sec = i + 1 ;
tv . tv_usec = 0 ;
send_set_downstream_fragsize ( dns_fd , fragsize ) ;
FD_ZERO ( & fds ) ;
FD_SET ( dns_fd , & fds ) ;
r = select ( dns_fd + 1 , & fds , NULL , NULL , & tv ) ;
if ( r > 0 ) {
2009-09-21 01:10:44 +04:00
read = read_dns_namecheck ( dns_fd , 0 , in , sizeof ( in ) , ' n ' , ' N ' ) ;
2009-08-16 16:30:26 +04:00
if ( read > 0 ) {
int accepted_fragsize ;
if ( strncmp ( " BADFRAG " , in , 7 ) = = 0 ) {
fprintf ( stderr , " Server rejected fragsize. Keeping default. " ) ;
return ;
} else if ( strncmp ( " BADIP " , in , 5 ) = = 0 ) {
fprintf ( stderr , " Server rejected sender IP address. \n " ) ;
return ;
}
accepted_fragsize = ( ( in [ 0 ] & 0xff ) < < 8 ) | ( in [ 1 ] & 0xff ) ;
return ;
}
}
fprintf ( stderr , " Retrying set fragsize... \n " ) ;
}
fprintf ( stderr , " No reply from server when setting fragsize. Keeping default. \n " ) ;
}
int
client_handshake ( int dns_fd , int raw_mode , int autodetect_frag_size , int fragsize )
{
int seed ;
int case_preserved ;
int r ;
r = handshake_version ( dns_fd , & seed ) ;
if ( r ) {
return r ;
}
r = handshake_login ( dns_fd , seed ) ;
if ( r ) {
return r ;
}
if ( raw_mode & & handshake_raw_udp ( dns_fd , seed ) ) {
conn = CONN_RAW_UDP ;
2009-09-21 01:10:44 +04:00
selecttimeout = 20 ;
2009-08-16 16:30:26 +04:00
} else {
if ( raw_mode = = 0 ) {
fprintf ( stderr , " Skipping raw mode \n " ) ;
}
case_preserved = handshake_case_check ( dns_fd ) ;
if ( case_preserved ) {
handshake_switch_codec ( dns_fd ) ;
}
2009-09-20 19:11:14 +04:00
if ( downenc ! = ' ' ) {
handshake_switch_downenc ( dns_fd ) ;
}
2009-09-21 01:10:44 +04:00
if ( lazymode ) {
handshake_try_lazy ( dns_fd ) ;
}
2009-08-16 16:30:26 +04:00
if ( autodetect_frag_size ) {
fragsize = handshake_autoprobe_fragsize ( dns_fd ) ;
if ( ! fragsize ) {
return 1 ;
}
}
handshake_set_fragsize ( dns_fd , fragsize ) ;
}
return 0 ;
}