/*
 esp.c

 Author: Liraz Siri <liraz@bigfoot.com>

 Copyright (c) 1998 Liraz Siri <liraz@bigfoot.com>, Ariel, Israel
		    All rights reserved.

 Created: Tue Sep 15 IST 1998
 Updated: Tue Sep 22 17:36:31 GMT 1998
 Last updated: Mon Oct  5 05:03:04 GMT 1998

 Code for the implementation of the esp data transfer protocol.
 The protocol handles (in extension to ssh) the control of an encrypted 
 data stream, via a covert method of delivery, spanning tcp, udp and icmp. 
 The code handles intialization, data delivery and recovery, assuming a 
 hostile network enviorment. 
*/

#include "esp.h" 
#include "includes.h"  
#include "xmalloc.h"
#include "buffer.h"
#include "getput.h"
#include "randoms.h"

#ifdef SOLARIS
#include <netinet/icmp_var.h>
#endif

/* Home of the infamous network packet loss simulation */
#ifdef PACKET_LOSS
RandomState	random_state;
#define PACKET_LOSS_PROBABILITY		2
#endif

static int icmpfd = -1, udpfd = -1, tcpfd = -1;
static int buffers_initialized = 0;

static unsigned int	max_packet_size, 
			esp_window_size = PHANTOM_WINDOW_SIZE_DEFAULT,
                        esp_sync_ratio = PHANTOM_SYNC_RATIO_DEFAULT;

static Buffer	transmit_packet, receive_packet;

/* Accepts packets from any source if this is nonzero, otherwise we
   filter what we recognize */

static int esp_proto = -1, sessionfd = -1;
static unsigned int esp_timeout = PHANTOM_SESSION_TIMEOUT;
static int accept_any;
struct sockaddr_in lastfrom;

static int icmp_srcport = -1,
	   icmp_dstport = -1,
	   udp_srcport = -1,
	   udp_dstport = -1,
	   tcp_srcport = -1,
           tcp_dstport = -1,
	   esp_srcport = -1,
           esp_dstport = -1;

static u_short esp_transmit_sequence = -1;
static unsigned long esp_transmit_delay = PHANTOM_TRANSMIT_DELAY;
static unsigned long esp_rtt = 0;

struct sockaddr_in destination, source;

static int queued_received_packets = 0, queued_transmitted_packets = 0,
	   waiting_for_resend = 0, esp_packets_lost = 0,
           esp_sync_switch = 0;

static int esp_last_received_sequence = 0,
	   esp_last_transmitted_sequence = 0;

/* FIFO packet queue buffer handlers */
static Buffer queued_received_packets_buffer, queued_transmitted_packets_buffer;

/* Get a packet from the queue, the packet can be of maximum maxlen bytes,
   and should be copied into cp. Note, there's a more descriptive
   commentation on the actual implementation of this function. */
int esp_recvpacket(Buffer *packet_queue, char *cp, unsigned int maxlen);

/* Put a packet into the queue, starting at 'cp' and of len bytes */
void esp_queuepacket(Buffer *packet_queue, char *cp, unsigned int len);

#ifndef NO_LOGGING
void log_msg(const char *fmt, ...);
void debug(const char *fmt, ...);
void error(const char *fmt, ...);
#endif

/*
 * in_cksum --
 *      Checksum routine for Internet Protocol family headers (C Version)
 */
int esp_in_cksum(u_short *addr, int len)
{
        register int nleft = len;
        register u_short *w = addr;
        register int sum = 0;
        u_short answer = 0;

        /*
         * Our algorithm is simple, using a 32 bit accumulator (sum), we add
         * sequential 16 bit words to it, and at the end, fold back all the
         * carry bits from the top 16 bits into the lower 16 bits.
         */
        while (nleft > 1)  {
                sum += *w++;
                nleft -= 2;
        }
        /* mop up an odd byte, if necessary */
        if (nleft == 1) {
                *(u_char *)(&answer) = *(u_char *)w ;
                sum += answer;
        }

        /* add back carry outs from top 16 bits to low 16 bits */
        sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
        sum += (sum >> 16);                     /* add carry */
        answer = ~sum;                          /* truncate to 16 bits */
        return(answer);
}


/* shuts down all esp sockets */
void esp_shutdown(void)
{
 if(udpfd != -1) close(udpfd); 
 if(icmpfd != -1) close(icmpfd);
 if(tcpfd != -1) close(tcpfd); 

 icmpfd = udpfd = tcpfd = -1;
}

/* esp_receive_raw is called to filter/handle packets from the packet
   pacture mechanism. If the packet meets up to all expectations from
   the esp_connect mechanism, then it's ssh 'packet payload' is
   buffered to incoming, to be handled by ssh's packet.c packet routines */
int esp_receive_raw(Buffer *incoming, const u_char *packet, u_int length)
{
 int protocol;
 int n, hdrlen, len, padded_len;

 struct iprec *ip_hdr;
 struct icmp_echo *icmp_hdr;
 struct udprec *udp_hdr; 
 struct tcprec *tcp_hdr;

 const u_char *cp;

 ip_hdr = (struct iprec *)packet;

 switch((protocol = ip_hdr->ip_p)) {
#ifdef ICMP_SUPPORT
  case IPPROTO_ICMP	:
   icmp_hdr = (struct icmp_echo *)(packet + sizeof(struct iprec));
   if( icmp_hdr -> icmp_type != ICMP_RECEIVE_TYPE ||  
       icmp_hdr -> icmp_dstport != (icmp_srcport & 0xFFFF) ||
       icmp_hdr -> icmp_srcport != (icmp_dstport & 0xFFFF) ||
       icmp_hdr -> icmp_seq != 0 ||
       icmp_hdr -> icmp_code != PHANTOM_DATA_CODE) {
    return 0;  
   }
   hdrlen = ICMP_HDR_SIZE;
   break;
#endif
#ifdef UDP_SUPPORT
  case IPPROTO_UDP	:
   udp_hdr = (struct udprec *)(packet + sizeof(struct iprec));
   if(udp_hdr -> udp_dstport != (udp_srcport & 0xFFFF) ||
      udp_hdr -> udp_srcport != (udp_dstport & 0xFFFF) ||
      udp_hdr -> udp_seq != 0 ||
      udp_hdr -> udp_code != PHANTOM_DATA_CODE) {
    return 0;
   }
   hdrlen = UDP_HDR_SIZE;
   break;
#endif
#ifdef TCP_SUPPORT
  case IPPROTO_TCP	:
   tcp_hdr = (struct tcprec *)(packet + sizeof(struct iprec));
   if(tcp_hdr -> tcp_type != TCP_RECEIVE_TYPE ||
      tcp_hdr -> tcp_dstport != (tcp_srcport & 0xFFFF) ||
      tcp_hdr -> tcp_srcport != (tcp_dstport & 0xFFFF) ||
      tcp_hdr -> tcp_seq != 0 ||
      tcp_hdr -> tcp_code != PHANTOM_DATA_CODE) {
    return 0;
   }
   hdrlen = TCP_HDR_SIZE;
   break;
#endif
   default		: 
    return 0;
 }

 memset(&lastfrom, 0, sizeof(lastfrom));
 memcpy(&lastfrom.sin_addr, &ip_hdr->ip_src, sizeof(struct in_addr));
 lastfrom.sin_family = AF_INET;

 cp = (const u_char *)(packet + hdrlen);
 length -= hdrlen;

 if(length < 4 + 8) {
  error("packet buffer is smaller then the minimum, only %d", length);
  return 0;
 }

 len = GET_32BIT(cp);
 padded_len = (len + 8) & ~7;

 if (len < 1 + 2 + 2 || length < 4 + padded_len ) {
  error("bad packet length, illegal data len (%u). pkt length (%u)", 
        len, length);
  return 0;
 }

 buffer_append(incoming, cp, padded_len + 4);
 return protocol;
}

/* creates a raw icmp socket and returns its descriptor, or -1 on 
   failure */
int esp_init(int proto)
{
 int hold, protocol, *sockfd;

 switch(proto) {

  case IPPROTO_TCP  :
   if(buffers_initialized && tcpfd != -1) 
    return tcpfd;

   protocol = IPPROTO_TCP;
   sockfd = &tcpfd;
   break;

  case IPPROTO_UDP  : 

   if(buffers_initialized && udpfd != -1) 
    return udpfd;

   protocol = IPPROTO_UDP;
   sockfd = &udpfd;
   break;

  case IPPROTO_ICMP :
   if(buffers_initialized && icmpfd != -1) 
    return icmpfd;

   protocol = IPPROTO_ICMP;
   sockfd = &icmpfd;
   break;

  default: 
   error("The protocol requested is not supported.");
   return -1;
 }
 
 if((*sockfd = socket(AF_INET, SOCK_RAW, protocol)) < 0) {
  error("Cannot create raw protocol (%u) socket", protocol);
  return -1;
 }

 hold = 48 * 1024;
 setsockopt(*sockfd, SOL_SOCKET, SO_RCVBUF, (char *)&hold, sizeof(hold));    

  /* Set the socket into non-blocking mode. */
#if defined(O_NONBLOCK) && !defined(O_NONBLOCK_BROKEN)
  if (fcntl(*sockfd, F_SETFL, O_NONBLOCK) < 0)
    error("fcntl O_NONBLOCK: %.100s", strerror(errno));
#else /* O_NONBLOCK && !O_NONBLOCK_BROKEN */
  if (fcntl(*sockfd, F_SETFL, O_NDELAY) < 0)
    error("fcntl O_NDELAY: %.100s", strerror(errno));
#endif /* O_NONBLOCK && !O_NONBLOCK_BROKEN */

 if(!buffers_initialized) {
#ifdef PACKET_LOSS
  random_initialize(&random_state, 0, "/dev/null");
#endif
  buffer_init(&receive_packet);
  buffer_init(&transmit_packet);
  buffer_init(&queued_received_packets_buffer);
  buffer_init(&queued_transmitted_packets_buffer);
  max_packet_size = receive_packet.alloc;
  buffers_initialized = 1;
 } 

 return *sockfd;
}

/* closes the old icmp socket and returns a new one, note that we don't
   reinitialized the receive/transmit buffers, unless they haven't been
   previously initialized, return the new icmp socket, -1 on failure */
int esp_reinit(int proto)
{
 if(udpfd != -1) close(udpfd); 
 if(icmpfd != -1) close(icmpfd);
 if(tcpfd != -1) close(tcpfd); 
 icmpfd = udpfd = tcpfd = -1;

 return esp_init(proto);
}

/* set the destination and the that will be used in esp_transmit,
   and while your at it, set the transmit_id that will be used to
   mark our outgoing icmp packets */

void esp_set_destination(struct sockaddr_in *destaddr, int proto)
{
 esp_set_proto(proto);
 memcpy(&destination, destaddr, sizeof(destination));

 destination.sin_port = 0;
 destination.sin_family = AF_INET;
}

/* set the expected receive source, if srcaddr is NULL then
   accept packets from any source, otherwise we accept packets only
   from srcaddr. Both types of receive still filter out any packets
   that aren't transmited under receive_id  */

void esp_set_source(struct sockaddr_in *srcaddr, int srcport, 
			int dstport, int proto)
{
 srcport &= 0xFFFF;
 dstport &= 0xFFFF;

 switch(proto) {
  case	IPPROTO_ICMP	: icmp_srcport = srcport;
			  icmp_dstport = dstport;
			  break;
  case  IPPROTO_UDP	: udp_srcport = srcport;
			  udp_dstport = dstport;
			  break;
  case  IPPROTO_TCP	: tcp_srcport = srcport;
			  tcp_dstport = dstport;
			  break;
  default		: 
   fatal("esp_set_source, unknown protocol.");
   return;
 }

 if((accept_any = !srcaddr ? 1 : 0))
  return;

 esp_set_proto(proto);
 memcpy(&source, srcaddr, sizeof(source));
}

void esp_set_proto(int proto)
{
 esp_proto = proto;
 switch(proto) {
  case IPPROTO_TCP	: esp_dstport = tcp_dstport;
			  esp_srcport = tcp_srcport;
			  sessionfd = tcpfd;
			  break;
  case IPPROTO_UDP	: esp_dstport = udp_dstport;
			  esp_srcport = udp_srcport;
			  sessionfd = udpfd;
			  break;
  case IPPROTO_ICMP	: esp_dstport = icmp_dstport;
			  esp_srcport = icmp_srcport;
			  sessionfd = icmpfd;
			  break;
 }
}

/* set the icmp sequence, note that this will be incremented with each
   transmit */
void esp_set_sequence(u_short seq)
{
 esp_transmit_sequence = seq - 1;
 esp_last_received_sequence = 0;
 buffer_clear(&queued_transmitted_packets_buffer);
 buffer_clear(&queued_received_packets_buffer);
 queued_transmitted_packets = 0;
 queued_received_packets = 0;
}

/* set the maximum packet size. this actually just resizes the receive
   buffers, and sets max_packet_size value to len */
void esp_set_max_packet_size(unsigned int len)
{
 char *cp;
 max_packet_size = len;
 if( len > max_packet_size ) {
  buffer_clear(&receive_packet);
  switch(esp_proto) {
   case IPPROTO_ICMP : 
    buffer_append_space(&receive_packet, &cp, len + ICMP_HDR_SIZE); break;
   case IPPROTO_UDP  :
    buffer_append_space(&receive_packet, &cp, len + UDP_HDR_SIZE); break;
   case IPPROTO_TCP  : 
    buffer_append_space(&receive_packet, &cp, len + TCP_HDR_SIZE); break;
  }
  buffer_clear(&receive_packet);
 }
}

/* get the maximum icmp packet size */
unsigned int esp_get_max_packet_size(void)
{
 return max_packet_size;
}

/* set the transmit send delay */
void esp_set_delay(unsigned long microseconds)
{
 esp_transmit_delay = microseconds;
}

/* assemble a esp packet, tag it with icmp_id, fill it's buffer with
   the data from outgoing and transmit it on a raw icmp socket.
   Notice we only transmit one packet at a time. So if there are 10
   packages in outgoing, esp_transmit would need to be called 10 times
   to get all the packets through */

int esp_transmit(Buffer *outgoing)
{
 int n, last_sequence;
 struct icmp_echo *icmp_hdr;
 struct udprec *udp_hdr;
 struct tcprec *tcp_hdr;
 unsigned len, padded_len;
 char *cp;

/* calculate the size of our packet */
 if(!buffers_initialized || esp_dstport == -1 || 
    esp_proto == -1 || sessionfd == -1) {
  error("raw socket not initialized, esp_transmit failed");
  return -1;
 }

 switch(esp_proto) {
  case IPPROTO_ICMP	:	break;
  case IPPROTO_TCP	:	
  case IPPROTO_UDP	:
   if(esp_srcport == -1) {
    error("esp_set_source() not called. cannot set srcport");
    return -1;
   }
 }

 if(esp_transmit_blocked()) {
  error("esp_transmit returning error. transmit is blocked!");
  return -1;
 }

 cp = buffer_ptr(outgoing);

 len = GET_32BIT(cp);
 padded_len = (len + 8) & ~7;

 if( buffer_len(outgoing) < (padded_len + 4) )
  fatal("esp_transmit: trying to transmit an incomplete packet.");

 if( len < 1 + 2 + 2 || (padded_len + 4) > max_packet_size ) 
  fatal("esp_transmit: illegal packet size: size %u, minimum, %u, limit %u.",
	padded_len + 4, ((1 + 2 + 2 + 8) & ~7), max_packet_size);

 buffer_clear(&transmit_packet);
 switch(esp_proto) {

  case IPPROTO_ICMP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct icmp_echo));

   icmp_hdr = (struct icmp_echo *)cp;

   icmp_hdr -> icmp_type = ICMP_TRANSMIT_TYPE;
   icmp_hdr -> icmp_code = PHANTOM_DATA_CODE;
   icmp_hdr -> icmp_dstport = esp_dstport & 0xFFFF;
   icmp_hdr -> icmp_srcport = esp_srcport & 0xFFFF;
   last_sequence = icmp_hdr -> icmp_seq = ++esp_transmit_sequence;

   buffer_append(&transmit_packet, buffer_ptr(outgoing), padded_len + 4);

   icmp_hdr -> icmp_cksum = 0;
   icmp_hdr -> icmp_cksum = esp_in_cksum((u_short *)
				             buffer_ptr(&transmit_packet),
					     buffer_len(&transmit_packet));
   break;

  case IPPROTO_UDP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct udprec));

   udp_hdr = (struct udprec *) cp;

   udp_hdr -> udp_code = PHANTOM_DATA_CODE;
   last_sequence = udp_hdr -> udp_seq = ++esp_transmit_sequence;
   udp_hdr -> udp_dstport = esp_dstport & 0xFFFF;
   udp_hdr -> udp_srcport = esp_srcport & 0xFFFF;
   udp_hdr -> udp_len = htons(sizeof(struct udprec) + padded_len + 4);

   buffer_append(&transmit_packet, buffer_ptr(outgoing), padded_len + 4);

   udp_hdr -> udp_cksum = 0;
   udp_hdr -> udp_cksum = esp_in_cksum((u_short *)
				           buffer_ptr(&transmit_packet),
					   buffer_len(&transmit_packet));
   break;

  case IPPROTO_TCP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct tcprec));
   tcp_hdr = (struct tcprec *) cp;

   tcp_hdr -> tcp_code = PHANTOM_DATA_CODE;
   last_sequence = tcp_hdr -> tcp_seq = ++esp_transmit_sequence;
   tcp_hdr -> tcp_dstport = esp_dstport & 0xFFFF;
   tcp_hdr -> tcp_srcport = esp_srcport & 0xFFFF;
   tcp_hdr -> tcp_type = TCP_TRANSMIT_TYPE;

   buffer_append(&transmit_packet, buffer_ptr(outgoing), padded_len + 4);

   tcp_hdr -> tcp_cksum = 0;
   tcp_hdr -> tcp_cksum = esp_in_cksum((u_short *)
				           buffer_ptr(&transmit_packet),
					   buffer_len(&transmit_packet));
   break;
 }

 usleep(esp_transmit_delay);
 debug("esp_transmit() called. transmitted seq %d", last_sequence);
 if((n = PL_sendto(sessionfd, buffer_ptr(&transmit_packet),
                   buffer_len(&transmit_packet), 0, 
		   (struct sockaddr *)&destination, 
                   sizeof(struct sockaddr))) < 0) {
  error("Failed transmiting packet, (%s)", strerror(errno));
  return -1;
 }

 esp_queuepacket(&queued_transmitted_packets_buffer, 
		  buffer_ptr(&transmit_packet),
		  buffer_len(&transmit_packet));
 queued_transmitted_packets++;
 esp_last_transmitted_sequence = last_sequence;
 if(esp_transmit_blocked()) 
  esp_request_sync();

 buffer_consume(outgoing, padded_len + 4);
 return n;
}

/* receive on our raw icmp socket any incoming packets, and buffer it's
   contents to incoming if the packet is coming from our source, has
   our icmp_id, and is legal in it's size. Note that is our icmp socket
   is non blocking, it is up to the caller to select for receive,
   otherwise I think recvfrom will return -1, errno == EWOULDBLOCK */

int esp_receive(Buffer *incoming)
{
 int n, fromlen;
 char *cp;
 unsigned len, padded_len, hdr_len;

 struct sockaddr_in from;
 struct iprec *ip_hdr;

 struct icmp_echo *icmp_hdr;
 struct udprec *udp_hdr;
 struct tcprec *tcp_hdr;

 unsigned int esp_sequence, esp_code, esp_hdr_len;
 static int queued_loop, sync_loop = 0;

 if(!buffers_initialized || esp_srcport == -1 || esp_proto == -1 ||
    sessionfd == -1) 
  return -1;

 buffer_clear(&receive_packet);
 switch(esp_proto) {
  case IPPROTO_ICMP : hdr_len = ICMP_HDR_SIZE; break;
  case IPPROTO_UDP  : hdr_len = UDP_HDR_SIZE; break;
  case IPPROTO_TCP  : hdr_len = TCP_HDR_SIZE; break;
 }

 buffer_append_space(&receive_packet, &cp, max_packet_size + hdr_len);

 if(queued_received_packets && !waiting_for_resend) {
  if((n = esp_recvpacket(&queued_received_packets_buffer, cp, 
			  max_packet_size + hdr_len)) < 0) 
   fatal("received packet queue buffers seriously fucked up.");

  queued_received_packets--;
 }
 else {
  fromlen = sizeof(from);
  if((n = recvfrom(sessionfd, cp, max_packet_size + hdr_len, 0, 
	           (struct sockaddr *)&from, &fromlen)) <= 0) {
   error("Failed receiving icmp packet (%s)", strerror(errno));
   return -1;
  }  

  if( n < hdr_len ) {
   error("Packet received is smaller then minimum, only %d bytes", n);
   return -1; 
  }
 
  if( !accept_any && from.sin_addr.s_addr != source.sin_addr.s_addr ) 
   return 0;
 
 }

 ip_hdr = (struct iprec *) cp;
 if(esp_proto != ip_hdr->ip_p)
  return 0;

 switch(esp_proto) {
  case	IPPROTO_TCP	:
   tcp_hdr = (struct tcprec *) (cp + (ip_hdr -> ip_hl << 2));

   if(tcp_hdr -> tcp_type != TCP_RECEIVE_TYPE ||
      tcp_hdr -> tcp_dstport != (esp_srcport & 0xFFFF) ||
      tcp_hdr -> tcp_srcport != (esp_dstport & 0xFFFF)) {
    return 0;
   }

   esp_sequence = tcp_hdr -> tcp_seq;
   esp_code = tcp_hdr -> tcp_code;
   esp_hdr_len = sizeof(struct tcprec);
   break;

  case  IPPROTO_UDP	:
   udp_hdr = (struct udprec *) (cp + (ip_hdr -> ip_hl << 2));

   if(udp_hdr -> udp_dstport != (esp_srcport & 0xFFFF) ||
      udp_hdr -> udp_srcport != (esp_dstport & 0xFFFF)) {
    return 0;
   }

   esp_sequence = udp_hdr -> udp_seq;
   esp_code = udp_hdr -> udp_code;
   esp_hdr_len = sizeof(struct udprec);
   break;

  case	IPPROTO_ICMP	:
   icmp_hdr = (struct icmp_echo *) (cp + (ip_hdr -> ip_hl << 2));

   if( icmp_hdr -> icmp_type != ICMP_RECEIVE_TYPE ||  
       icmp_hdr -> icmp_dstport != (esp_srcport & 0xFFFF) ||
       icmp_hdr -> icmp_srcport != (esp_dstport & 0xFFFF)) {
    return 0;  
   }

   esp_sequence = icmp_hdr -> icmp_seq;
   esp_code = icmp_hdr -> icmp_code;
   esp_hdr_len = sizeof(struct icmp_echo);
   break;
 }

 if(accept_any) {
  if(esp_code != PHANTOM_DATA_CODE || esp_sequence != 0) {
   error("Received an invalid protocol packet from a non-connected source.");
   return 0;
  }
  memcpy(&lastfrom, &from, sizeof(struct sockaddr_in));
 }

 if(!accept_any) {

  switch(esp_code) {
   case  PHANTOM_SYNC_CODE		: 
    debug("received PHANTOM_SYNC_CODE seq %d", esp_sequence);
    esp_sync(esp_sequence); 
    return 0;

   case  PHANTOM_REQUEST_SYNC_CODE	:
    debug("received PHANTOM_REQUEST_SYNC_CODE seq %d", esp_sequence);
    esp_transmit_sync();
    goto evaluate_sequence;

   case  PHANTOM_REQUEST_SEQ_CODE	:
    debug("received PHANTOM_REQUEST_SEQ_CODE seq %d", esp_sequence);
    esp_transmit_seq();
    goto evaluate_sequence;

   case  PHANTOM_SEQ_CODE		: 
   debug("received PHANTOM_SEQ_CODE seq %d", esp_sequence);

evaluate_sequence :
    if(waiting_for_resend) { 
     esp_request_resend(waiting_for_resend);
     return 0;
    }

    if(!waiting_for_resend && 
       (esp_sequence > esp_last_received_sequence)) {
     waiting_for_resend = esp_last_received_sequence + 1;
     esp_packets_lost = esp_sequence - esp_last_received_sequence;
     debug("PACKET LOSS detected, sequences %d-%d lost", waiting_for_resend, waiting_for_resend + esp_packets_lost - 1);
     esp_request_resend(waiting_for_resend);
    }

    return 0;

   case	PHANTOM_RESEND_CODE		: 
    debug("received PHANTOM_RESEND_CODE, retransmitting %d", esp_sequence);
    if(esp_resend_packet(esp_sequence) <= 0) return -1;
    return 0;

   case	PHANTOM_DATA_CODE	:
    debug("received data packet, seq %d", esp_sequence);
    break;

   default			: 
    return 0;				 
  }

/* OK, now we're handling a standard data packet */

  if(waiting_for_resend) 
  {
   if(esp_sequence == waiting_for_resend) 
   {  
    if(--esp_packets_lost) 
    {
     debug("received lost packet, waiting for more!");
     waiting_for_resend++;
     queued_loop = 0;
     esp_request_resend(waiting_for_resend);
    } 
    else 
    {
     waiting_for_resend = 0;
     debug("recieved lost packet, lossfree channel resumed");
    }

    goto handle_packet;
   } 

   if(!(queued_loop = (queued_loop+1)%PHANTOM_QUEUE_RESEND)) 
    esp_request_resend(waiting_for_resend);
  
   debug("channel broken up, waiting for resend, received seq %d queued.", esp_sequence);
   esp_queuepacket(&queued_received_packets_buffer, cp, n);
   queued_received_packets++;

   return 0;
  }


  if(esp_sequence > esp_last_received_sequence + 1) {
   waiting_for_resend = esp_last_received_sequence + 1;
   esp_packets_lost = esp_sequence - waiting_for_resend;
   debug("PACKET LOSS detected, sequences %d-%d lost", waiting_for_resend, waiting_for_resend + esp_packets_lost - 1);
   esp_queuepacket(&queued_received_packets_buffer, cp, n);
   queued_received_packets++;

   queued_loop = 0;

   esp_request_resend(waiting_for_resend);
   return 0;
  }

  if(esp_sequence < esp_last_received_sequence + 1 &&
     esp_sequence != 0) 
   return 0;

 } /* !accept_any */

handle_packet:

 esp_last_received_sequence = esp_sequence;

 sync_loop = (sync_loop+1)%(esp_window_size/esp_sync_ratio); 
 if(esp_sync_switch && !sync_loop) 
  esp_transmit_sync();

 buffer_consume(&receive_packet, esp_hdr_len + (ip_hdr->ip_hl << 2));
 buffer_consume_end(&receive_packet, hdr_len + max_packet_size - n);

repeat:

 if(buffer_len(&receive_packet) < 4 + 8) {
  error("packet buffer is smaller then the minimum, only %d", 
        buffer_len(&receive_packet));
  return 0;
 }

 cp = (unsigned char *)buffer_ptr(&receive_packet);

 len = GET_32BIT(cp);
 padded_len = (len + 8) & ~7;

 if (len < 1 + 2 + 2 || buffer_len(&receive_packet) < 4 + padded_len ) {
  error("bad packet length, illegal len (%u)", len);
  return -1;
 }

 buffer_append(incoming, buffer_ptr(&receive_packet), padded_len + 4);

 buffer_consume(&receive_packet, padded_len + 4);
 if(buffer_len(&receive_packet) >= 4 + 8) goto repeat;

 return 1;
}

/* copy the address of the last incoming packet to from. note that the
   packet address is only copied when accept_any is true, so calling
   icmp_get_source will return the address of the last global packet
   received. Returns false if no packets have yet been received */

void esp_get_source(struct sockaddr_in *from)
{
 memcpy(from, &lastfrom, sizeof(struct sockaddr_in));
}

/* return the session protocol */
int esp_get_proto(void)
{
 return esp_proto;
}

/* return esp_receive_id */
int esp_get_receive_id(void)
{
 return esp_srcport;
}

/* return esp_transmit_id */
int esp_get_transmit_id(void)
{
 return esp_dstport;
}

/* Get a packet from the queue, the packet can be of maximum maxlen bytes,
   and should be copied into cp, that is of course, in the condition
   that cp is not NULL. if cp is NULL, we just consume the length
   of the queued packet without of copying it's contents */

int esp_recvpacket(Buffer *packet_queue, char *cp, unsigned int maxlen)
{
 unsigned int len;

/* This should never return negative */
 if((len = GET_16BIT(buffer_ptr(packet_queue))) > maxlen) return -1;

 buffer_consume(packet_queue, 2);

 if(!cp) buffer_consume(packet_queue, len);
 else buffer_get(packet_queue, cp, len);

 return len;
}

/* Put a packet into the queue, starting at 'cp' and of len bytes */
void esp_queuepacket(Buffer *packet_queue, char *cp, unsigned int len)
{
 char *qcp;

 buffer_append_space(packet_queue, &qcp, 2);
 PUT_16BIT(qcp, len);
 buffer_append(packet_queue, cp, len);  

}

/* Returns true is there are any queued packets in the mechanisms buffers */
int esp_packets_available(void)
{
 return (queued_received_packets && !waiting_for_resend);
}

/* Returns true if our queue has reached maximum size and we need to
   clear it before we transmit any new packets */
int esp_transmit_blocked(void)
{
 if(esp_sync_switch && queued_transmitted_packets >= 
    esp_window_size) return 1;
 return 0;
}

/* resend the last packet with the sequence of seq. Returns true if
   we actually found the packet in queued_transmitted_packets_buffer,
   otherwise returns -1. note that we do not remove the packet from 
   the queue since we may need to transmit it again. Note that this 
   also syncs our connection. */

int esp_resend_packet(u_short seq)
{
 int n, qindex, len, sequence;
 struct icmp_echo *icmp_hdr;
 struct udprec *udp_hdr;
 struct tcprec *tcp_hdr;

 debug("esp resending packet seq %d", seq);

 if( esp_last_transmitted_sequence < seq ) {
  error("esp_resend_packet: weird! sequence %u request, highest %u.",
	seq, esp_last_transmitted_sequence);
  return -1;
 }

 for(qindex = 0; qindex < queued_transmitted_packets; qindex++)
 {
  buffer_clear(&transmit_packet);
 
 /* we examine the packet without removing it from the queue */
  len = GET_16BIT(buffer_ptr(&queued_transmitted_packets_buffer));

  buffer_append(&transmit_packet, 
                &(buffer_ptr(&queued_transmitted_packets_buffer)[2]), 
 	        len);

  switch(esp_proto) {
   case IPPROTO_ICMP	: 
    icmp_hdr = (struct icmp_echo *) buffer_ptr(&transmit_packet);
    sequence = icmp_hdr -> icmp_seq;
    break;

   case IPPROTO_UDP	:
    udp_hdr = (struct udprec *) buffer_ptr(&transmit_packet); 
    sequence = udp_hdr -> udp_seq;
    break;

   case IPPROTO_TCP	:
    tcp_hdr = (struct tcprec *) buffer_ptr(&transmit_packet);
    sequence = tcp_hdr -> tcp_seq;
    break;

   default		: 
    fatal("esp_resend_packet, unknown esp protocol.");
  }

  if(sequence == seq) {
   usleep(esp_transmit_delay);
   if(PL_sendto(sessionfd, buffer_ptr(&transmit_packet), 
	     buffer_len(&transmit_packet), 0, (struct sockaddr *)&destination, 
	     sizeof(struct sockaddr)) < 0) {
    error("Failed transmiting esp packet, (%s)", strerror(errno));
    queued_transmitted_packets -= qindex;
    return -1;
   }
   queued_transmitted_packets -= qindex;
   return 1;
  }

  if(seq < sequence) {
   error("reset packet: below boundries, "
	 "requested: %u, lowest: %u, qindex: %u", seq, sequence, qindex );
   queued_transmitted_packets -= qindex;
   return -1;
  }

/* ok, now we remove it. since sequence always grows, there is no need
   for this packet anymore. A higher sequence has been requested from
   the queue, so it must have been already received. */

  buffer_consume(&queued_transmitted_packets_buffer, len + 2);
 }

 error("esp_resend_packet: unable to find requested sequence in queue.");
 queued_transmitted_packets -= qindex;

 return 0; 
}

/* Synchronize data flow. this basicly means we 'sychronize' transmission,
   removing confirmed received packets from the transmit queue, allowing
   new data to be transmitted. Note that when the transmit queue is full
   we don't send any new packets */
void esp_sync(u_short seq)
{
 int q, sequence, len;
 struct icmp_echo *icmp_hdr;
 struct udprec *udp_hdr;
 struct tcprec *tcp_hdr;

 debug("syncing data flow by sequence %d", seq);
 if(esp_last_transmitted_sequence < seq) {
  error("esp_sync: weird. requested sync of %u, highest seq %u",
	 seq, esp_last_transmitted_sequence);
  return; 
 }

 for(q = 0; q<queued_transmitted_packets; q++) {
/* This actually has nothing to do with transmitting a packet, since we
   aren't really sending anything, just responding to a request, but why
   waste perfectly good allocated memory? */

  buffer_clear(&transmit_packet);  
  len = GET_16BIT(buffer_ptr(&queued_transmitted_packets_buffer));
  buffer_append(&transmit_packet,
	        &(buffer_ptr(&queued_transmitted_packets_buffer)[2]),
		len);

  switch(esp_proto) {
   case	IPPROTO_TCP	:
    tcp_hdr = (struct tcprec *) buffer_ptr(&transmit_packet);
    sequence = tcp_hdr -> tcp_seq;
    break;
   case IPPROTO_UDP	:
    udp_hdr = (struct udprec *) buffer_ptr(&transmit_packet);
    sequence = udp_hdr -> udp_seq;
    break;
   case IPPROTO_ICMP	:
    icmp_hdr = (struct icmp_echo *) buffer_ptr(&transmit_packet);
    sequence = icmp_hdr -> icmp_seq;
    break;
  }

  if(sequence > seq) break;
  buffer_consume(&queued_transmitted_packets_buffer, len + 2);
 }

 debug("removed %d packets from our queue", q); 
 queued_transmitted_packets -= q;
}

int esp_transmit_proto_msg(int type, int sequence)
{
 char *cp;
 struct icmp_echo *icmp_hdr;
 struct tcprec	*tcp_hdr;
 struct udprec	*udp_hdr;

 if(!buffers_initialized || esp_dstport == -1 ||
    esp_proto == -1) {
  error("sock not initialized, esp_transmit_proto_msg failed");
  return -1;
 }

 buffer_clear(&transmit_packet);

 switch(esp_proto) {
  case	IPPROTO_TCP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct tcprec) +
					      TCP_CONTROLMSG_SIZE);
   tcp_hdr = (struct tcprec *) cp;
   tcp_hdr -> tcp_code = type;
   tcp_hdr -> tcp_seq = sequence;
   tcp_hdr -> tcp_dstport = esp_dstport & 0xFFFF;
   tcp_hdr -> tcp_srcport = esp_srcport & 0xFFFF;   
   tcp_hdr -> tcp_type = TCP_TRANSMIT_TYPE;
   tcp_hdr -> tcp_cksum = 0;
   tcp_hdr -> tcp_cksum = esp_in_cksum((u_short *)
                                            buffer_ptr(&transmit_packet),
                                            buffer_len(&transmit_packet));
   break;

  case  IPPROTO_UDP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct udprec) +
					      TCP_CONTROLMSG_SIZE);
   udp_hdr = (struct udprec *) cp;
   udp_hdr -> udp_code = type;
   udp_hdr -> udp_seq = sequence;
   udp_hdr -> udp_dstport = esp_dstport & 0xFFFF;
   udp_hdr -> udp_srcport = esp_srcport & 0xFFFF;
   udp_hdr -> udp_len = htons(buffer_len(&transmit_packet));
   udp_hdr -> udp_cksum = 0;
   udp_hdr -> udp_cksum = esp_in_cksum((u_short *)
					    buffer_ptr(&transmit_packet),
					    buffer_len(&transmit_packet));
   break;

  case  IPPROTO_ICMP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct icmp_echo) +
					      ICMP_CONTROLMSG_SIZE);
   icmp_hdr = (struct icmp_echo *) cp;

   icmp_hdr -> icmp_code = type;
   icmp_hdr -> icmp_seq = sequence;
   icmp_hdr -> icmp_type = ICMP_TRANSMIT_TYPE;
   icmp_hdr -> icmp_dstport = esp_dstport & 0xFFFF;
   icmp_hdr -> icmp_srcport = esp_srcport & 0xFFFF;
   icmp_hdr -> icmp_cksum = 0;
   icmp_hdr -> icmp_cksum = esp_in_cksum((u_short *)
					     buffer_ptr(&transmit_packet),
                                             buffer_len(&transmit_packet));
   break;
 }

 if(PL_sendto(sessionfd, buffer_ptr(&transmit_packet),
		      buffer_len(&transmit_packet), 0,
		      (struct sockaddr *)&destination,
		      sizeof(struct sockaddr)) < 0) {
  error("Failed transmiting esp packet, (%s)", strerror(errno));
  return -1;
 }

 return 1;
}

/* send a esp sequence request. basicly, we transmit our last
transmitted sequence, and request the other side to do the same */
int esp_request_seq(void)
{
 debug("sequencing: (local transmit seq %d), request remote transmit seq", esp_transmit_sequence);  
 return esp_transmit_proto_msg(PHANTOM_REQUEST_SEQ_CODE,
				   esp_transmit_sequence);
}


/* Transmit a sequence packet that should be ignore. We tag the packet
   with the sequence of the last transmitted DATA packet. esp_receive
   should be prepared to handle the situation that would occur if the
   sequence is higher then expected, unless already waiting for a resend. */

int esp_transmit_seq(void)
{
 debug("sequencing: sequence request, responding local transmit seq %d", esp_transmit_sequence);
 return esp_transmit_proto_msg(PHANTOM_SEQ_CODE,
				   esp_transmit_sequence);
}

/* Request esp_transmit_sync from the other side of the connection. We do
   this when reaching the unfortunate event a full queue. We block, and
   ask the other side to send us sync. We broadcast our last transmitted
   sequence, so if the receiving side detects a missing packet, he can
   request it. */

int esp_request_sync(void)
{
 debug("requesting sync (blocked connection?) sending local transmit seq %d", esp_transmit_sequence);
 return esp_transmit_proto_msg(PHANTOM_REQUEST_SYNC_CODE, 
				   esp_transmit_sequence);
}

/* Transmit our connection sync. Transmitted is the last sequence
   received. We tag the packet with the PHANTOM_SYNC_CODE. We keep it very
   simple, assemble a new packet, mark it, and send it along */
int esp_transmit_sync(void)
{
 debug("transmitting sync, last received remote sequence %d", esp_last_received_sequence);
 return esp_transmit_proto_msg(PHANTOM_SYNC_CODE,
				   esp_last_received_sequence);
}

/* request the resend of the last packet with the sequence of seq */
int esp_request_resend(u_short seq)
{
 debug("detected remote packet loss, requesting retransmit SEQ %d", seq);
 return esp_transmit_proto_msg(PHANTOM_RESEND_CODE, seq);
}

/* get the maximum icmp timeout. No use. ditto. maybe I'll remove it. */
unsigned int esp_get_timeout(void)
{
 return esp_timeout;
}

/* set the maximum icmp timeout. This is actually kind of important. */
void esp_set_timeout(unsigned int timeout)
{
 esp_timeout = timeout;
}

/* get the round trip time */
unsigned long esp_get_rtt(void)
{
 return esp_rtt;
}

/* set the round trip time */
void esp_set_rtt(unsigned long rtt)
{
 esp_rtt = rtt;
}

/* turn packet syncronization on/off (useless/wastefull at negotiation, 
   needed for flow control/packet loss recovery in the main connection loops */
void esp_set_sync(int onoff)
{
 esp_sync_switch = onoff;
}

/* set our packet window size */
void esp_set_window_size(int window_size)
{
 esp_window_size = window_size;
}

/* get window size */
int esp_get_window_size(void)
{
 return esp_window_size;
}

/* set sync ratio */
void esp_set_sync_ratio(int sync_ratio)
{
 esp_sync_ratio = sync_ratio;
}

/* get sync ratio */
int esp_get_sync_ratio(void)
{
 return esp_sync_ratio;
}

/* wrapper for sendto that simulates packet loss */
int PL_sendto(int s, const void *msg, int len, unsigned int flags, 
              const struct sockaddr *to, int tolen)
{
#ifdef PACKET_LOSS
/* if we're zero the transmission doesn't get through */
 if((random_get_byte(&random_state)%PACKET_LOSS_PROBABILITY)) 
#endif
 {
  return sendto(s, msg, len, flags, to, tolen);
 }
#ifdef PACKET_LOSS
 else debug("simulated packet loss. dropped packet.");
 return len;
#endif
}
