/* Brain-dead userspace TCP pseudo implementation
 * just to test a bit ARS.
 * Without the module that drops the outgoing resets this can't work.
 *
 * Copytight(C) 2000-2001 Salvatore Sanfilippo
 */

#include <stdio.h>
#include <pcap.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "ars.h"

pcap_t *pcapfp;
char errbuf[PCAP_ERRBUF_SIZE];
struct pcap_pkthdr hdr;
struct ars_iface i;
int s;
char *dest;
int srcport, destport;
struct ars_packet pkt;
int debug = 0;
int fake_mru = 80;
int fake_mtu = 3000;
int dupack = 0;
int ackdiv = 0;
u_int32_t isn = 1;
u_int32_t host_seq, host_ack, my_seq, my_ack;
int bytes_received = 0;
time_t start_time = 0;
int fd;

void tcp(void);
void reset_handler(int sid);
void send_tcp(u_int32_t seq, u_int32_t ack, int flags, void *data, size_t len);
void send_icmp_fragneeded(void *quoted, size_t len);
struct ars_tcphdr *get_good(void);
int is_good(void);
void close_requested(void);

int main(int argc, char **argv)
{
	if (argc != 4)
		exit(1);

	srand(time(NULL));
	srcport = (rand() % 2000)+1024;
	s = ars_open_rawsocket(NULL);
	if (s == -ARS_ERROR) {
		perror("Opening raw socket");
		exit(1);
	}

	if (ars_get_iface(argv[1], &i) != ARS_OK) {
		fprintf(stderr, "Can't get any good interface\n");
		exit(1);
	}

	pcapfp = pcap_open_live(i.if_name, ARS_MAX_IP_SIZE, 1, 0, errbuf);
	if (pcapfp == NULL) {
		fprintf(stderr, "Opening libpcap: %s\n", errbuf);
		exit(1);
	}

	dest = argv[2];
	destport = atoi(argv[3]);

	signal(2, reset_handler);

	fd = open("output", O_RDWR|O_CREAT|O_TRUNC, 0644);
	if (fd == -1) {
		perror("open");
		exit(1);
	}

	tcp();

	return 0;
}

int get_next(void)
{
	unsigned char *p;
	int lhs;
	fd_set rfds;
	struct timeval tv;
	int retval;

	FD_ZERO(&rfds);
	FD_SET(pcap_fileno(pcapfp), &rfds);
	tv.tv_sec = 1;
	tv.tv_usec = 0;

	/* returns anyway after one second, for timeout */
	retval = select(pcap_fileno(pcapfp)+1, &rfds, NULL, NULL, &tv);
	if (!retval)
		return 0;

	/* Get the packet */
	p = (unsigned char*) pcap_next(pcapfp, &hdr);
	if (p == NULL) {
		printf("pcap timeout\n");
		return 0;
	}
	if (debug) { printf("."); fflush(stdout); }
	if (ars_guess_ipoff(p, hdr.caplen, &lhs) != -ARS_OK) {
		return 0;
	}

	/* Setup the ARS structure and convert the packet into layers */
	ars_destroy(&pkt);
	ars_split_packet(p, hdr.caplen, -1, &pkt);

	/* Handle the fake MRU -- only for our connection */
	if (((hdr.caplen - lhs) > fake_mru) && is_good()) {
		/* Send an ICMP fragmentation required but DF set,
		 * quoting the TCP packet received */
		send_icmp_fragneeded(p + lhs, hdr.caplen - lhs);
		printf("ICMP fragmentation needed but DF set sent (%d)\n",
			hdr.caplen - lhs);
		return 0;
	}

	return 1;
}

int get_layer(struct ars_packet *pkt, int layertype)
{
	int j;

	for (j = 0; j < ARS_MAX_LAYER; j++) {
		if (pkt->p_layer[j].l_type == layertype)
			return j;
	}
	return -1;
}

int is_good(void)
{
	int tcpl;
	struct ars_tcphdr *tcp;
	if (get_layer(&pkt, ARS_TYPE_ICMP) != -1)
		return 0;
	if ((tcpl = get_layer(&pkt, ARS_TYPE_TCP)) == -1)
		return 0;
	tcp = pkt.p_layer[tcpl].l_data;
	if (tcp->th_sport == htons(destport) &&
	    tcp->th_dport == htons(srcport))
		return 1;
	return 0;
}

/* Wait for a TCP packet regarding the connection */
struct ars_tcphdr *get_good(void)
{
	int tcpl;
	struct ars_tcphdr *tcp;
	time_t timestamp = time(NULL);

	while (time(NULL) - timestamp < 2) {
		if (get_next() == 0)
			continue;
		if (get_layer(&pkt, ARS_TYPE_ICMP) != -1)
			continue;
		if ((tcpl = get_layer(&pkt, ARS_TYPE_TCP)) == -1)
			continue;
		tcp = pkt.p_layer[tcpl].l_data;
		if (tcp->th_sport == htons(destport) &&
		tcp->th_dport == htons(srcport))
			return tcp;
	}
	return NULL; /* timeout */
}

void *get_data(void)
{
	int datal;

	if ((datal = get_layer(&pkt, ARS_TYPE_DATA)) == -1)
		return NULL;
	return pkt.p_layer[datal].l_data;
}

int get_data_size(void)
{
	int datal;

	if ((datal = get_layer(&pkt, ARS_TYPE_DATA)) == -1)
		return -1;
	return pkt.p_layer[datal].l_size;
}

int three_way_handshake(void)
{
	struct ars_tcphdr *tcp;

	/* SEND the SYN */
	printf("Send SYN\n");
	send_tcp(isn, 0, ARS_TCP_TH_SYN, NULL, 0);
	my_seq = isn + 1;

	/* WAIT the ACK -- retransmit the SYN if timeout occurs */
	while ((tcp = get_good()) == NULL) {
		printf("Timeout -- SEND SYN\n");
		send_tcp(isn, 0, ARS_TCP_TH_SYN, NULL, 0);
	}

	if (tcp->th_flags & ARS_TCP_TH_RST)
		return 0; /* connection refused */

	/* assume the other end isn't evil */
	else if (tcp->th_flags & ARS_TCP_TH_SYN &&
		 tcp->th_flags & ARS_TCP_TH_ACK) {
		printf("Got SYN/ACK\n");
	} else {
		printf("BAD flags\n");
		return 0;
	}

	/* SEND ACK */
	host_seq = ntohl(tcp->th_seq)+1;
	send_tcp(my_seq, host_seq, ARS_TCP_TH_ACK, NULL, 0);
	return 1;
}

void tcp_input(void)
{
	struct ars_tcphdr *tcp;

	if ((tcp = get_good()) == NULL)
		return;

	if (tcp->th_flags & ARS_TCP_TH_FIN)
		close_requested();

	if (debug) printf("%u == %u\n", ntohl(tcp->th_ack), my_seq);
	if (ntohl(tcp->th_ack) == my_seq) {
//		my_ack = ntohl(tcp->th_ack);
		if (debug) printf("ACK received %u\n", my_seq);
	}

	if (start_time == 0)
		start_time = time(NULL);

	if (debug) printf("%u == %u\n", ntohl(tcp->th_seq), host_seq);
	if (ntohl(tcp->th_seq) == host_seq) { /* FIXME -- this is too bad */
		void *data = get_data();
		int data_size = get_data_size();

		if (data == NULL)
			return;
		bytes_received += data_size;
		if (0) printf("Received new data (%d) at %lu bytes/s\n",
			data_size,
			bytes_received/((time(NULL) - start_time)+1));
//		if (1) write(fd, data, data_size);
		if (1) write(0, data, data_size);
		host_seq += data_size;

		/* send ack */
//		host_ack = host_seq + 1;
		if (!ackdiv) {
			send_tcp(my_seq, host_seq, ARS_TCP_TH_ACK, NULL, 0);
			if (dupack)
				send_tcp(my_seq, host_seq, ARS_TCP_TH_ACK, NULL, 0);
		} else {
			send_tcp(my_seq, host_seq-3, ARS_TCP_TH_ACK, NULL, 0);
			send_tcp(my_seq, host_seq-2, ARS_TCP_TH_ACK, NULL, 0);
			send_tcp(my_seq, host_seq-1, ARS_TCP_TH_ACK, NULL, 0);
			send_tcp(my_seq, host_seq, ARS_TCP_TH_ACK, NULL, 0);
		}
	}
}

void tcp_output(void)
{
	char buffer[1024], *b = buffer;
	int n_read;
	int max_data_size = fake_mtu - 40;
	int payload;

	n_read = read(0, buffer, 1024);
	b = buffer;
	while (n_read) {
		payload = MIN(max_data_size, n_read);
		send_tcp(my_seq, host_seq, ARS_TCP_TH_ACK|ARS_TCP_TH_PUSH,
			b, payload);
		my_seq += payload;
		b += payload;
		n_read -= payload;
	}
}

void send_tcp(u_int32_t seq, u_int32_t ack, int flags, void *data, size_t len)
{
	struct ars_packet pkt;
	struct ars_tcphdr *tcp;
	struct ars_iphdr *ip;
	void *tcpdata;

	ars_init(&pkt);
	/* IP header */
	ip = ars_add_iphdr(&pkt, 0);
	ars_resolve(&pkt, &ip->saddr, i.if_ipv4addr);
	ars_resolve(&pkt, &ip->daddr, dest);
	ip->id = htons(10);
	ip->ttl = 64;
	/* TCP header */
	tcp = ars_add_tcphdr(&pkt, 0);
	tcp->th_sport = htons(srcport);
	tcp->th_dport = htons(destport);
	tcp->th_flags = flags;
	tcp->th_win = htons(5840);
	tcp->th_seq = htonl(seq);
	tcp->th_ack = htonl(ack);

	/* TCP data */
	if (data != NULL) {
		tcpdata = ars_add_data(&pkt, len);
		memcpy(tcpdata, data, len);
	}

	if (ars_compile(&pkt) != -ARS_OK) {
		printf("Compiling: %s\n", pkt.p_error);
		exit(1);
	}
	if (ars_send(s, &pkt, NULL, 0) != -ARS_OK) {
		perror("sendto");
		printf("sending: %s\n", pkt.p_error);
		exit(1);
	}
	ars_destroy(&pkt);
}

void send_icmp_fragneeded(void *quoted, size_t len)
{
	struct ars_packet pkt;
	struct ars_icmphdr *icmp;
	struct ars_iphdr *ip;
	void *data;

	if (len >= 80) len = 80;

	ars_init(&pkt);
	/* IP header */
	ip = ars_add_iphdr(&pkt, 0);
	ars_resolve(&pkt, &ip->saddr, i.if_ipv4addr);
	ars_resolve(&pkt, &ip->daddr, dest);
	ip->id = htons(10);
	ip->ttl = 64;
	/* ICMP header */
	icmp = ars_add_icmphdr(&pkt, 0);
	icmp->type = ARS_ICMP_DEST_UNREACH;
	icmp->code = ARS_ICMP_UNR_FRAG_NEEDED;
	/* DATA (the quoted packet) */
	data = ars_add_data(&pkt, len);
	memcpy(data, quoted, len);

	if (ars_compile(&pkt) != -ARS_OK) {
		printf("Compiling: %s\n", pkt.p_error);
		exit(1);
	}
	if (ars_send(s, &pkt, NULL, 0) != -ARS_OK) {
		perror("sendto");
		printf("sending: %s\n", pkt.p_error);
		exit(1);
	}
	ars_destroy(&pkt);
}


void reset_handler(int sid)
{
	struct ars_tcphdr *tcp;

	printf("Closing the connection...\n");
	send_tcp(my_seq, host_seq, ARS_TCP_TH_FIN, NULL, 0);
	my_seq++;
	/* we don't check for the ACK reply, don't matter,
	 * just wait for the FIN|ACK */

	while ((tcp = get_good()) == NULL);

	printf("(FIN) %u == %u\n", ntohl(tcp->th_ack), my_seq);
	if (ntohl(tcp->th_ack) == my_seq) {
		printf("FIN|ACK received %u\n", my_ack);
		/* ACKnowledge it */
		send_tcp(my_seq, host_seq+1, ARS_TCP_TH_ACK, NULL, 0);
	}
	exit(1);
}

void close_requested(void)
{
	printf("Closing the connection... (server request)\n");
	host_seq++; /* FIN received */
	send_tcp(my_seq, host_seq, ARS_TCP_TH_ACK | ARS_TCP_TH_FIN, NULL, 0);
	send_tcp(my_seq, host_seq, ARS_TCP_TH_FIN, NULL, 0);
	/* don't wait for the ACK, useless */
	exit(1);
}

void tcp(void)
{
	fd_set rfds;
	int fdmax;
	int retval;

	if (three_way_handshake() == 0) {
		printf("Connection refused\n");
		exit(1);
	}

	while(1) {
		FD_ZERO(&rfds);
		FD_SET(0, &rfds);
		FD_SET(pcap_fileno(pcapfp), &rfds);
		fdmax = pcap_fileno(pcapfp) + 1;

		retval = select(fdmax, &rfds, NULL, NULL, NULL);
		if (FD_ISSET(pcap_fileno(pcapfp), &rfds))
			tcp_input();
		if (FD_ISSET(0, &rfds))
			tcp_output();
	}
}
