/*
 * Copyright (c) 1997, 1998 The Regents of the University of California.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the MASH Research
 *	Group at the University of California, Berkeley.
 * 4. Neither the name of the University nor of the Research Group may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Header: /nfs/jade/vint/CVSROOT/ns-2/emulate/tcptap.cc,v 1.4 2002/09/23 23:25:05 alefiyah Exp $ (ISI)";
#endif

#include "tcptap.h"

static class TCPTapAgentClass : public TclClass {
 public:
	TCPTapAgentClass() : TclClass("Agent/TCPTap") {}
	TclObject* create(int, const char*const*) {
		return (new TCPTapAgent());
	}
} class_tcptap_agent;



TCPTapAgent::TCPTapAgent() {
    adv_window = DEFAULT_ADV_WINDOW;

    bzero(&extnode, sizeof(struct sockaddr_in));
    extnode.sin_port = DEFAULT_EXT_PORT;
    extnode.sin_addr.s_addr = inet_addr(DEFAULT_EXT_ADDR);
    extnode.sin_family = AF_INET;

    bzero(&nsnode, sizeof(struct sockaddr_in));
    nsnode.sin_port = DEFAULT_NS_PORT;
    nsnode.sin_addr.s_addr = inet_addr(DEFAULT_NS_ADDR);
    nsnode.sin_family = AF_INET;
    
    dropp = 0; 
}



/*
 * Methods to set ip addresses and port numbers from Tcl scripts.
 */
int
TCPTapAgent::command(int argc, const char*const* argv)
{
  if (argc == 3) {
    if (strcmp(argv[1], "nsipaddr") == 0) {
      if ((nsnode.sin_addr.s_addr = inet_addr(argv[2])) 
	  == INADDR_NONE) {
	printf("Error setting ns ip address");
	exit(1);
      }
      return (TCL_OK);
    }	
    
    
    if (strcmp(argv[1], "extipaddr") == 0) {
      if ((extnode.sin_addr.s_addr = inet_addr(argv[2])) 
	  == INADDR_NONE) {
	printf("Error setting external ip address");
	exit(1);
      }
      return (TCL_OK);
    }
    
    if (strcmp(argv[1], "extport") == 0) {
      extnode.sin_port = atoi(argv[2]);
      return (TCL_OK);
    }

    if (strcmp(argv[1], "advertised-window") == 0) {
      adv_window = atoi(argv[2]);
      return (TCL_OK);
    }


    
  } /* if (argc == 3) */
  return (TapAgent::command(argc, argv));
}



unsigned short 
TCPTapAgent::trans_check(unsigned char proto,
			 char *packet,
			 int length,
			 struct in_addr source_address,
			 struct in_addr dest_address)
{
  char *pseudo_packet;
  unsigned short answer;
  
  pseudohdr.protocol = proto;
  pseudohdr.length = htons(length);
  pseudohdr.place_holder = 0;

  pseudohdr.source_address = source_address;
  pseudohdr.dest_address = dest_address;
  
  if((pseudo_packet = (char *) malloc(sizeof(pseudohdr) + length)) == NULL)  {
    perror("malloc");
    exit(1);
  }
  
  memcpy(pseudo_packet,&pseudohdr,sizeof(pseudohdr));
  memcpy((pseudo_packet + sizeof(pseudohdr)),
	 packet,length);
  
  answer = (unsigned short)in_cksum((unsigned short *)pseudo_packet,
				    (length + sizeof(pseudohdr)));
  free(pseudo_packet);
  return answer;
}





/*
 * Taken from the raw-sock program
 */
unsigned short 
TCPTapAgent::in_cksum(unsigned short *addr, int len)
{
  register int sum = 0;
  u_short answer = 0;
  register u_short *w = addr;
  register int nleft = len;
  
  /*
   * 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);
  
}



void 
TCPTapAgent::ip_gen(char *packet, unsigned char protocol, 
	    struct in_addr saddr, struct in_addr daddr,
	    unsigned short length, unsigned char ttl)
{

  struct ip *ipheader;
  
  ipheader = (struct ip *) packet;
  bzero((void *) ipheader, IP_HEADER_LEN);

  ipheader->ip_hl = (IP_HEADER_LEN / 4);
  ipheader->ip_v = IPVERSION;

  ipheader->ip_len = length;
  ipheader->ip_id = 0;
  ipheader->ip_ttl = ttl;
  ipheader->ip_p = protocol;
  
  ipheader->ip_src = saddr;
  ipheader->ip_dst = daddr;

  ipheader->ip_len = ntohs(ipheader->ip_len);
  ipheader->ip_off = ntohs(ipheader->ip_off);

  ipheader->ip_sum = (unsigned short) in_cksum((unsigned short *) ipheader,
						sizeof(struct ip));

}




void
TCPTapAgent::tcp_gen(char *packet, unsigned short sport, unsigned short dport, 
	Packet *nsp)
{
  struct tcphdr *tcpheader;

  hdr_tcp* ns_tcphdr = HDR_TCP(nsp);

  tcpheader = (struct tcphdr *) packet;
  memset((char *)tcpheader, '\0', sizeof(struct tcphdr));

#ifndef LINUX_TCP_HEADER

  tcpheader->th_sport = htons(sport);
  tcpheader->th_dport = htons(dport);

  tcpheader->th_seq = htonl(ns_tcphdr->seqno_);
  tcpheader->th_ack = htonl(ns_tcphdr->ackno_);
  
  tcpheader->th_off = (TCP_HEADER_LEN / 4);
  
  tcpheader->th_win = htons(adv_window);

  /* 
     Here we only propogate the "well-known" flags.
     If you want to add other flags, uncomment this line
     and comment the rest.

  tcpheader->th_flags = ns_tcphdr->tcp_flags_;
  */

  tcpheader->th_flags = 0;
  if (ns_tcphdr->tcp_flags_ & TH_FIN) 
    tcpheader->th_flags |= TH_FIN;
  if (ns_tcphdr->tcp_flags_ & TH_SYN) 
    tcpheader->th_flags |= TH_SYN;
  if (ns_tcphdr->tcp_flags_ & TH_RST) 
    tcpheader->th_flags |= TH_RST;
  if (ns_tcphdr->tcp_flags_ & TH_PUSH) 
    tcpheader->th_flags |= TH_PUSH;
  if (ns_tcphdr->tcp_flags_ & TH_ACK) 
    tcpheader->th_flags |= TH_ACK;
  if (ns_tcphdr->tcp_flags_ & TH_URG) 
    tcpheader->th_flags |= TH_URG;

#else  /* LINUX_TCP_HEADER */

#define TH_FIN  0x01
#define TH_SYN  0x02
#define TH_RST  0x04
#define TH_PUSH 0x08
#define TH_ACK  0x10
#define TH_URG  0x20

  tcpheader->source = htons(sport);
  tcpheader->dest = htons(dport);

  tcpheader->seq = htonl(ns_tcphdr->seqno_);
  tcpheader->ack_seq = htonl(ns_tcphdr->ackno_);
  
  tcpheader->doff = (TCP_HEADER_LEN / 4);
  tcpheader->window = htons(adv_window);

  /* Set the appropriate flag bits */
 if (ns_tcphdr->tcp_flags_ & TH_FIN) 
    tcpheader->fin= 1;
  if (ns_tcphdr->tcp_flags_ & TH_SYN) 
    tcpheader->syn = 1;
  if (ns_tcphdr->tcp_flags_ & TH_RST) 
    tcpheader->rst =1;
  if (ns_tcphdr->tcp_flags_ & TH_PUSH) 
    tcpheader->psh = 1;
  if (ns_tcphdr->tcp_flags_ & TH_ACK) 
    tcpheader->ack = 1;
  if (ns_tcphdr->tcp_flags_ & TH_URG) 
    tcpheader->urg =1;

#endif /* End of LINUX_TCP_HEADER */


}

void
TCPTapAgent::pkt_handler(void *clientdata, Packet *p, const struct timeval &ts)
{
  TCPTapAgent *inst = (TCPTapAgent *)clientdata;
  inst->processpkt(p, ts);
}

void
TCPTapAgent::processpkt(Packet *p, const struct timeval &ts)
{
  struct ip *ipheader;
  struct tcphdr *tcpheader;
  unsigned char *buf;
  
  /* Ip header information from the grabbed packet. */
  unsigned char ttl;
  
  /* Code to drop packet, if needed 
  dropp++;
  if ((dropp % 10) == 0) {
    fprintf(stdout,
	    "Dropping packet number : %d\n", dropp);
    Packet::free(p);
    return ;
    
    } */
 

  ipheader = (struct ip *) p->accessdata();
  if (in_cksum((unsigned short *) ipheader, (ipheader->ip_hl * 4))) {
    fprintf(stderr,
	    "TCPTapAgent(%s): packet received with invalid IP checksum.\n",
	    name());
    drop(p);
    return;
  }

  ttl = ipheader->ip_ttl;
  if (!(--ttl)) {
    fprintf(stderr, 
	    "TCPTapAgent(%s): packet received with ttl zero.\n",
	    name());
    drop(p);
    return;
    
  }    

  buf = p->accessdata(); 
  tcpheader = (struct tcphdr *) (buf + (ipheader->ip_hl * 4));
  
  Packet *nspacket = allocpkt();

  hdr_ip *ns_iphdr = HDR_IP(nspacket);
  ns_iphdr->ttl() = ttl;

  hdr_tcp *ns_tcphdr = HDR_TCP(nspacket);	
#ifndef LINUX_TCP_HEADER 

  ns_tcphdr->seqno() = ntohl(tcpheader->th_seq);
  ns_tcphdr->ackno() = ntohl(tcpheader->th_ack);
  ns_tcphdr->hlen() = (ipheader->ip_hl + tcpheader->th_off) * 4;
  ns_tcphdr->ts() = Scheduler::instance().clock();
  ns_tcphdr->reason() |= REASON_UNKNOWN;

  /* 
     Here we only propogate the "well-known" flags.
     If you want to add other flags, uncomment this line
     and comment the rest.

     ns_tcphdr->flags() = tcpheader->th_flags;
  */

  ns_tcphdr->flags() = 0;
  if (tcpheader->th_flags & TH_FIN)
    ns_tcphdr->flags() |= TH_FIN;
  if (tcpheader->th_flags & TH_SYN) 
        ns_tcphdr->flags() |= TH_SYN;
  if (tcpheader->th_flags & TH_RST) 
        ns_tcphdr->flags() |= TH_RST;
  if (tcpheader->th_flags & TH_PUSH) 
        ns_tcphdr->flags() |= TH_PUSH;
  if (tcpheader->th_flags & TH_ACK) 
        ns_tcphdr->flags() |= TH_ACK;
  if (tcpheader->th_flags & TH_URG) 
        ns_tcphdr->flags() |= TH_URG;

#else /* LINUX_TCP_HEADER */
 
  ns_tcphdr->seqno() = ntohl(tcpheader->seq);
  ns_tcphdr->ackno() = ntohl(tcpheader->ack);
  ns_tcphdr->hlen() = (ipheader->ip_hl + tcpheader->doff) * 4;
  ns_tcphdr->ts() = Scheduler::instance().clock();
  ns_tcphdr->reason() |= REASON_UNKNOWN;

  /* 
     Here we only propogate the "well-known" flags.
     If you want to add other flags, uncomment this line
     and comment the rest.

     ns_tcphdr->flags() = tcpheader->th_flags;
  */

#define TH_FIN  0x01
#define TH_SYN  0x02
#define TH_RST  0x04
#define TH_PUSH 0x08
#define TH_ACK  0x10
#define TH_URG  0x20

  ns_tcphdr->flags() = 0;

  if (tcpheader->fin == 1 )
    ns_tcphdr->flags() |= TH_FIN;
  if (tcpheader->syn == 1 ) 
        ns_tcphdr->flags() |= TH_SYN;
  if (tcpheader->rst == 1 ) 
        ns_tcphdr->flags() |= TH_RST;
  if (tcpheader->psh == 1) 
        ns_tcphdr->flags() |= TH_PUSH;
  if (tcpheader->ack == 1) 
        ns_tcphdr->flags() |= TH_ACK;
  if (tcpheader->urg == 1) 
        ns_tcphdr->flags() |= TH_URG;

#endif  /* LINUX_TCP_HEADER */


  hdr_cmn *ns_cmnhdr = HDR_CMN(nspacket);
  ns_cmnhdr->size() = ntohs(ipheader->ip_len);

  Packet::free(p);

  // inject into simulator
  target_->recv(nspacket);
  return;
}

/*
 * ns scheduler calls TapAgent::dispatch which calls recvpkt.
 * 
 * recvpkt then calls the network (net_) to receive as many packets
 * as there are from the packet capture facility.
 * For every packet received through the callback, it converts to ns
 * FullTcp packet and injects it into the simulator by calling target_->recv
 * 
 */
void
TCPTapAgent::recvpkt()
{
  if (net_->mode() != O_RDWR && net_->mode() != O_RDONLY) {
    fprintf(stderr,
	    "TCPTapAgent(%s): recvpkt called while in write-only mode!\n",
	    name());
    return;
  }
  
  int cc = net_->recv(pkt_handler, this);
  if (cc <= 0) {
    if (cc < 0) {
      perror("recv");
    }
    return;
  }
  TDEBUG4("%f: TCPTapAgent(%s): recvpkt, cc:%d\n", now(), name(), cc);

  // nothing to do coz pkt_handler would have called processpkt()
  // that would have injected packets into the simulator
}




/*
 * simulator schedules TapAgent::recv which calls sendpkt
 *
 * Grabs a ns Full TCP packet, converts it into real TCP packet 
 * and injects onto the network using net_->send
 *
 */
int
TCPTapAgent::sendpkt(Packet* p)
{
  int byteswritten, datalen;
  unsigned char *packet;
  unsigned char received_ttl;
  int hlength = IP_HEADER_LEN + TCP_HEADER_LEN;
  struct tcphdr *tcpheader;

  if (net_->mode() != O_RDWR && net_->mode() != O_WRONLY) {
    fprintf(stderr,
	    "TCPTapAgent(%s): sendpkt called while in read-only mode!\n",
	    name());
    return (-1);
  }
  
  // send packet into the live network
  hdr_cmn* ns_cmnhdr = HDR_CMN(p);
  if (net_ == NULL) {
    fprintf(stderr,
	    "TCPTapAgent(%s): sendpkt attempted with NULL net\n",
	    name());
    drop(p);
    return (-1);
  }
  
  hdr_tcp* ns_tcphdr = HDR_TCP(p);

  hdr_ip * ns_iphdr = HDR_IP(p);
  received_ttl = ns_iphdr->ttl_;

  // Here we check if ns has sent any data in the packet.
  datalen = ns_cmnhdr->size() - ns_tcphdr->hlen();
  packet = (unsigned char *) calloc (1, sizeof(unsigned char) * 
				     (hlength + datalen));
  if (packet == NULL) {
    fprintf(stderr,
	    "TCPTapAgent(%s) : Error %d allocating memory.\n", name(), errno);
    return (-1);
  }


  // Create real world tcp packet.
  ip_gen((char *)packet, (unsigned char) IPPROTO_TCP, 
	 nsnode.sin_addr, extnode.sin_addr, 
	 hlength + datalen, received_ttl);

  tcpheader = (struct tcphdr*) (packet + IP_HEADER_LEN);

  tcp_gen((char *)tcpheader, nsnode.sin_port, extnode.sin_port, p);

#ifndef LINUX_TCP_HEADER
  tcpheader->th_sum = trans_check(IPPROTO_TCP, (char *) tcpheader,
				  sizeof(struct tcphdr) + datalen,
				  nsnode.sin_addr, extnode.sin_addr);
#else 
  tcpheader->check = trans_check(IPPROTO_TCP, (char *) tcpheader,
				  sizeof(struct tcphdr) + datalen,
				  nsnode.sin_addr, extnode.sin_addr);

#endif 

  /* 
     Limits the packets going out to only IP + TCP header. 
     ns will act as an ACK machine.
   */
  byteswritten = net_->send(packet, hlength + datalen);
  if (byteswritten < 0) {
    fprintf(stderr,"TCPTapAgent(%s): sendpkt (%p, %d): %s\n",
	    name(), p->accessdata(), ns_cmnhdr->size(), strerror(errno));
    Packet::free(p);
    free(packet);
    return (-1);
    
  }
  
  free(packet);
  TDEBUG3("TCPTapAgent(%s): sent packet (sz: %d)\n", name(), byteswritten);
  return 0;
}








