/* 
 * $smu-mark$ 
 * $name: waitpacket.c$ 
 * $author: Salvatore Sanfilippo <antirez@invece.org>$ 
 * $copyright: Copyright (C) 1999 by Salvatore Sanfilippo$ 
 * $license: This software is under GPL version 2 of license$ 
 * $date: Fri Nov  5 11:55:50 MET 1999$ 
 * $rev: 8$ 
 */ 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>

#include "hping2.h"
#include "globals.h"

static int icmp_unreach_rtt(void *quoted_ip, int size,
			    int *seqp, float *ms_delay);
static void print_tcp_timestamp(void *tcp, int tcpsize);


/*	FIXME:	wait_packet don't support ip and tcp options,
	it waits for 20 bytes IP hdr + 20 bytes TCP hdr.
	--> ip options fixed 31/08/99 (MK)
*/
void	wait_packet(void)
{
	char	packet	[LINK_PACKETSIZE],
		recvflags[1024],
		src_addr[1024],
		dst_addr[1024],
		*p;
	int	recv_sport,
		recv_dport,
		size,
		rstseq,
		sequence,
		winsize,
		status,
		match = FALSE,
		ip_id,
		rel_id;
	float	ms_delay;
	struct	in_addr	src, dst;
	struct  myiphdr ip;
	struct  mytcphdr tcp;
	struct  myudphdr udp;
	struct  myicmphdr icmp;
	struct  myiphdr icmp_iph;
	struct  icmp_tstamp_data icmp_tstamp;
	char	hlen;

#if (!defined OSTYPE_LINUX) || (defined FORCE_LIBPCAP)
	size = pcap_recv(packet, LINK_PACKETSIZE);
	if (size == -1) {
		perror("[wait_packet] pcap_recv()");
		return;
	}
#else
	size = recv(sockpacket, packet, LINK_PACKETSIZE, 0);
	if (size == -1) {
		if (errno != EINTR)
		{
			perror("[wait_packet] recv");
			print_statistics(-1);
		}
		else
			return;
	}
#endif

	if (size < linkhdr_size+IPHDR_SIZE)
		return;
	memcpy(&ip, packet+ABS_OFFSETIP, sizeof(ip));
	hlen	 = ip.ihl << 2;
	memcpy(&tcp, packet+ABS_OFFSETIP+hlen, sizeof(tcp));
	memcpy(&udp, packet+ABS_OFFSETIP+hlen, sizeof(udp));
	memcpy(&icmp, packet+ABS_OFFSETIP+hlen, sizeof(icmp));
	memcpy(&icmp_iph, packet+ABS_OFFSETIP+hlen+8, sizeof(icmp_iph));
	memcpy(&icmp_tstamp, packet+ABS_OFFSETIP+hlen+8, sizeof(icmp_tstamp));

	/* --------- HCMP --------- */
	if (opt_sign && (p = memstr(packet, rsign, size)))
	{
		struct hcmphdr *hcmph;
		__u16 seqnum;
		__u32 usec;

		if (opt_debug)
			fprintf(stderr, "HCMP received\n");

		p+=strlen(rsign);
		if ((size-(packet-p)) < sizeof(struct hcmphdr))
		{
			if (opt_verbose || opt_debug)
				fprintf(stderr, "bad HCMP len received\n");
			goto hcmp_exit;
		}

		hcmph = (struct hcmphdr*) p;

		switch(hcmph->type)
		{
		case HCMP_RESTART:
			seqnum = ntohs(hcmph->typedep.seqnum);
			break;
		case HCMP_SOURCE_QUENCH:
		case HCMP_SOURCE_STIRUP:
			usec = ntohl(hcmph->typedep.usec);
		default:
			if (opt_verbose || opt_debug)
				fprintf(stderr, "bad HCMP type received\n");
			goto hcmp_exit;
		}

		switch(hcmph->type)
		{
		case HCMP_RESTART:
			src_id = seqnum;		/* set id */
			datafiller(NULL, seqnum);	/* data seek */
			goto hcmp_exit;
		case HCMP_SOURCE_QUENCH:
		case HCMP_SOURCE_STIRUP:
			printf("Source quench / stirpu"
				" still not implemented!\n");
			goto hcmp_exit;
		/* default handled in the first switch */
		}
	}

hcmp_exit:

	/* get source and target address */
	memcpy(&src, &(ip.saddr), sizeof(struct in_addr));
	memcpy(&dst, &(ip.daddr), sizeof(struct in_addr));
	strncpy(src_addr, inet_ntoa(src), 1024);
	strncpy(dst_addr, inet_ntoa(dst), 1024);

	/* get ip->id */
	if (opt_winid_order)
		ip_id = ip.id;
	else
		ip_id = htons(ip.id);

	/* get source and target port */
	if (opt_udpmode)	/* UDP */
	{
		recv_sport = ntohs(udp.uh_sport);
		recv_dport = ntohs(udp.uh_dport);
	}
	else			/* TCP */
	{
		recv_sport = ntohs(tcp.th_sport);
		recv_dport = ntohs(tcp.th_dport);
	}

	/* --------- TCP --------- */
	if ( ip.protocol == IPPROTO_TCP &&
	     !memcmp(&ip.saddr, &remote.sin_addr, sizeof(ip.saddr)) &&
	     !memcmp(&ip.daddr, &local.sin_addr, sizeof(ip.daddr)) &&
	     (recv_sport == dst_port || opt_force_incdport)
	)
	{
		match = TRUE;

		winsize   = ntohs(tcp.th_win);
		tcp_exitcode = tcp.th_flags;

		recvflags[0] = '\0';
		if (tcp.th_flags & TH_RST)  strcat(recvflags, "R");
		if (tcp.th_flags & TH_SYN)  strcat(recvflags, "S");
		if (tcp.th_flags & TH_ACK)  strcat(recvflags, "A");
		if (tcp.th_flags & TH_FIN)  strcat(recvflags, "F");
		if (tcp.th_flags & TH_PUSH) strcat(recvflags, "P");
		if (tcp.th_flags & TH_URG)  strcat(recvflags, "U");
		if (tcp.th_flags & TH_X)    strcat(recvflags, "X");
		if (tcp.th_flags & TH_Y)    strcat(recvflags, "Y");
		if (recvflags[0] == '\0')    strcat(recvflags, "none");

		/* obtain round trip time */
		sequence = 0;
		status = rtt(&sequence, recv_dport, &ms_delay);

		/* relativize packets ip->id */
		if (opt_relid)
			rel_id = relativize_id(sequence, &ip_id);
		else
			rel_id = FALSE;

		if (opt_seqnum)
		{
			static __u32 old_th_seq = 0;
			__u32 seq_diff, tmp;

			tmp = ntohl(tcp.th_seq);
			if (tmp >= old_th_seq)
				seq_diff = tmp - old_th_seq;
			else
				seq_diff = (4294967295U - old_th_seq)
					+ tmp;
			old_th_seq = tmp;
			printf("%10lu +%lu\n",
				(unsigned long) tmp,
				(unsigned long) seq_diff);
		}

		if (!opt_quiet && !opt_seqnum) {
			if (status == S_RECV)
				printf("DUP! ");
			if (!opt_incdport)
				printf("len=%d ip=%s ",
					size - linkhdr_size, src_addr);
			else
				printf("len=%d ip=%s sport=%d ",
					size - linkhdr_size, src_addr,
					recv_sport);
			printf("flags=%s ", recvflags);
			if (ntohs(ip.frag_off) & DF)
				printf("DF ");
			printf("seq=%d ", sequence);
			printf("ttl=%d ", ip.ttl);
			printf("id");
			if (rel_id == TRUE)
				printf("=+");
			else
				putchar('=');
			printf("%d ", ip_id);
			printf("win=%d ", winsize);
			printf("rtt=%.1f ms", ms_delay);
			putchar('\n');
		}
		if (opt_verbose && !opt_quiet) {
			printf("tos=%x ", ip.tos);
			printf("iplen=%u ", htons(ip.tot_len));
			printf("seq=%lu ", (unsigned long) ntohl(tcp.th_seq));
			printf("ack=%lu ", (unsigned long) ntohl(tcp.th_ack));
			printf("sum=%x ", tcp.th_sum);
			printf("urp=%u ", tcp.th_urp);
			putchar('\n');
			putchar('\n');
		}

		if (opt_tcp_timestamp)
			print_tcp_timestamp(packet+ABS_OFFSETIP+hlen, size - linkhdr_size - hlen);

		fflush(stdout);
		recv_pkt++;
		if (opt_incdport && !opt_force_incdport)
			dst_port++;
	}

	/* --------- ICMP --------- */
	if ( ip.protocol == IPPROTO_ICMP )
	{
		match = TRUE;

		/* Echo reply */
		if ((icmp.type == 0  || icmp.type == ICMP_TIMESTAMPREPLY
		     || icmp.type == ICMP_ADDRESSREPLY)
		    && icmp.un.echo.id == (getpid() & 0xffff))
		{
			recv_pkt++;
			if (!opt_quiet)
			{
				rstseq = icmp.un.echo.sequence;

				/* obtain round trip time */
				status = rtt(&rstseq, 0, &ms_delay);

				/* relativize packets ip->id */
				if (opt_relid)
					rel_id = relativize_id(rstseq, &ip_id);
				else
					rel_id = FALSE;

				printf("%d bytes from %s: ",
				size - linkhdr_size, src_addr);
				printf("icmp_seq=%d ", rstseq);
				printf("ttl=%d ", ip.ttl);
				printf("id");
				if (rel_id == TRUE)
					printf("=+");
				else
					putchar('=');
				printf("%d ", ip_id);
				printf("rtt=%.1f ms", ms_delay);
				putchar('\n');
				if (icmp.type == ICMP_TIMESTAMPREPLY) {
					printf("ICMP timestamp: "
					       "Originate=%u "
					       "Receive=%u "
					       "Transmit=%u\n",
						(unsigned int)
						  ntohl(icmp_tstamp.orig),
						(unsigned int)
						  ntohl(icmp_tstamp.recv),
						(unsigned int)
						  ntohl(icmp_tstamp.tran));
					printf("ICMP timestamp RTT tsrtt=%lu\n",
						(get_midnight_ut_ms() -
						ntohl(icmp_tstamp.orig)));
				}
				if (icmp.type == ICMP_ADDRESSREPLY) {
					unsigned char *addr =
						(unsigned char*) packet+
							ABS_OFFSETIP+hlen+8;
					/* XXX: doesn't check the len */
					printf("ICMP address mask: "
					       "icmpam=%u.%u.%u.%u\n",
					       		addr[0], addr[1],
							addr[2], addr[3]);
				}
			}
		}
		else if ( icmp.type == 3 && /* Dest. Unreachable */
			!memcmp(&icmp_iph.daddr,
				&remote.sin_addr,
				sizeof(ip.daddr)) &&
			!memcmp(&ip.daddr,
				&local.sin_addr,
				sizeof(ip.daddr))
		)
		{
			if (!opt_quiet)
				log_icmp_unreach(src_addr, icmp.code);
		}
		else if (icmp.type == 11 && /* Time exceeded */
			 !memcmp(&icmp_iph.daddr,
				 &remote.sin_addr,
				 sizeof(ip.daddr)) &&
			 !memcmp(&ip.daddr,
				 &local.sin_addr,
				 sizeof(ip.daddr))
		)
		{
			/* traceroute mode */
			static unsigned char old_src_addr[4]={0,0,0,0};
			if (opt_traceroute)
			{
				if (opt_tr_keep_ttl ||
				    memcmp(&ip.saddr, old_src_addr, 4))
				{
					int sequence = 0, retval;
					float rtt;

					retval = icmp_unreach_rtt(
						packet+ABS_OFFSETIP+hlen+8,
						size - linkhdr_size - hlen - 8,
						&sequence, &rtt);
					memcpy(old_src_addr, &ip.saddr, 4);
					printf("hop=%d ", src_ttl);
					fflush(stdout);
					log_icmp_timeexc(src_addr, icmp.code);
					if (retval != -1)
						printf("hop=%d "
							"hoprtt=%.1f ms\n",
								src_ttl, rtt);
					if (!opt_tr_keep_ttl)
						src_ttl++;
				}
			}
			else
			{
				if (!opt_quiet)
					log_icmp_timeexc(src_addr, icmp.code);
			}
		}
		else
			match = FALSE;
	}

	/* --------- UDP --------- */
	if ( ip.protocol == IPPROTO_UDP &&
	     !memcmp(&ip.saddr, &remote.sin_addr, sizeof(ip.saddr)) &&
	     !memcmp(&ip.daddr, &local.sin_addr, sizeof(ip.daddr)) &&
	     recv_sport == dst_port )

	{
		match = TRUE;

		/* obtain round trip time */
		sequence = 0;
		status = rtt(&sequence, recv_dport, &ms_delay);

		/* relativize ip->id */
		if (opt_relid)
			rel_id = relativize_id(sequence, &ip_id);
		else
			rel_id = FALSE;

		if (!opt_quiet)
		{
			if (status == S_RECV)
				printf("DUP! ");
			if (!opt_incdport)
				printf("len=%d ip=%s ",
					size - linkhdr_size, src_addr);
			else
				printf("len=%d ip=%s sport=%d: ",
					size - linkhdr_size, src_addr,
					recv_sport);
			printf("seq=%d ", sequence);
			printf("ttl=%d ", ip.ttl);
			printf("id");
			if (rel_id == TRUE)
				printf("=+");
			else
				putchar('=');
			printf("%d ", ip_id);
			printf("rtt=%.1f ms", ms_delay);
			putchar('\n');
		}

		fflush(stdout);
		recv_pkt++;
		if (opt_incdport && !opt_force_incdport)
			dst_port++;
	}

	/* ---------------- COMMON ---------------- */

	/* dump the received packet in hex */
	if (opt_hexdump && match && !opt_quiet)
	{
		unsigned char *byte;
		int count = 0;

		printf("\t\t");
		for (byte = (unsigned char*) packet; byte < (unsigned char*) (packet+size); byte++)
		{
			count++;
			printf("%02x", *byte);
			if (count % 2 == 0) printf(" ");
			if (count % 16 == 0) printf("\n\t\t");
		}
		printf("\n\n");
	}

	/* dump the received packet printable characters */
	if (opt_contdump && match && !opt_quiet)
	{
		unsigned char *byte;
		int count = 0;

		printf("\t\t");
		for (byte = (unsigned char*) packet; byte < (unsigned char*) (packet+size); byte++)
		{
			count ++;
			if (isprint(*byte))
				printf("%c", *byte);
			else
				printf(".");
			if (count % 32 == 0) printf("\n\t\t");
		}
		printf("\n\n");
	}

	/* display IP options */
	if (opt_rroute && match && !opt_quiet)
	{
		display_ipopt(packet+ABS_OFFSETIP);
	}

	/* --stop-tr stops hping in traceroute mode when the
	 * first not ICMP time exceeded packet is received */
	if (opt_traceroute && opt_tr_stop && match) {
		if (ip.protocol != IPPROTO_ICMP ||
		    icmp.type != 11)
			print_statistics(0);
	}

	/* exit if 'count' is reached */
	if (count != -1 && count == recv_pkt)
		print_statistics(0);
}

/* Try to extract information about the original packet from the
 * ICMP error to obtain the round time trip
 *
 * Note that size is the the packet size starting from the
 * IP packet quoted in the ICMP error, it may be negative
 * if the ICMP is broken */
static int icmp_unreach_rtt(void *quoted_ip, int size, int *seqp, float *ms_delay)
{
	int src_port;
	int sequence = 0;
	int quoted_iphdr_size;
	struct myudphdr udp;
	struct myicmphdr icmp;
	struct myiphdr qip;

	/* The user specified --no-rtt */
	if (opt_tr_no_rtt)
		return -1;

	if (size < sizeof(struct myiphdr))
		return -1;
	memcpy(&qip, quoted_ip, sizeof(struct myiphdr));
	quoted_iphdr_size = qip.ihl << 2;
	/* Ok, enough room, try to get the rtt,
	 * but check if the original packet was an UDP/TCP one */
	if (qip.protocol == IPPROTO_TCP ||
	    qip.protocol == IPPROTO_UDP) {
		/* We need at least 2 bytes of the quoted UDP/TCP header
		 * for the source port */
		if ((size - quoted_iphdr_size) < 2)
			return -1;

		/* Use the UDP header for both UDP and TCP, they are
		* the same in the 4 first bytes (source and dest port) */
		memcpy(&udp, quoted_ip+quoted_iphdr_size, sizeof(udp));
		src_port = htons(udp.uh_sport);
		return rtt(&sequence, src_port, ms_delay);
	} else if (qip.protocol == IPPROTO_ICMP) {
		int s;

		/* We need the whole 8 byte ICMP header to get
		 * the sequence field, also the type must be
		 * ICMP_ECHO */
		memcpy(&icmp, quoted_ip+quoted_iphdr_size, sizeof(icmp));
		if ((size - quoted_iphdr_size) < 8 ||
		    icmp.type != ICMP_ECHO)
			return -1;

		s = icmp.un.echo.sequence;
		return rtt(&s, 0, ms_delay);
	}
	return -1; /* no way */
}

static void print_tcp_timestamp(void *tcp, int tcpsize)
{
	int optlen;
	unsigned char *opt;
	__u32 tstamp, echo;
	static __u32 last_tstamp = 0;
	struct mytcphdr tmptcphdr;
	unsigned int tcphdrlen;

	if (tcpsize < TCPHDR_SIZE)
		return;
	memcpy(&tmptcphdr, tcp, sizeof(struct mytcphdr));
	tcphdrlen = tmptcphdr.th_off * 4;

	/* bad len or no options in the TCP header */
	if (tcphdrlen <= 20 || tcphdrlen < tcpsize)
		return;
	optlen = tcphdrlen - TCPHDR_SIZE; 
	opt = (unsigned char*)tcp + TCPHDR_SIZE; /* skips the TCP fix header */
	while(optlen) {
		switch(*opt) {
		case 0: /* end of option */
			return;
		case 1: /* noop */
			opt++;
			optlen--;
			continue;
		default:
			if (optlen < 2)
				return;
			if (opt[1] > optlen)
				return;
			if (opt[0] != 8) { /* not timestamp */
				optlen -= opt[1];
				opt += opt[1];
				continue;
			}
			/* timestamp found */
			if (opt[1] != 10) /* bad len */
				return;
			memcpy(&tstamp, opt+2, 4);
			memcpy(&echo, opt+6, 4);
			tstamp = ntohl(tstamp);
			echo = ntohl(echo);
			goto found;
		}
	}
found:
	printf("  TCP timestamp: tcpts=%u\n", tstamp);
	if (last_tstamp && !opt_waitinusec) {
		int tsdiff = (tstamp - last_tstamp) / sending_wait;
		int hz_set[] = { 2, 10, 100, 1000, 0 };
		int hzdiff = -1;
		int hz = 0, sec;
		int days, hours, minutes;
		if (tsdiff > 0) {
			int i = 0;
			while(hz_set[i]) {
				if (hzdiff == -1) {
					hzdiff = ABS(tsdiff-hz_set[i]);
					hz = hz_set[i];
				} else if (hzdiff > ABS(tsdiff-hz_set[i])) {
					hzdiff = ABS(tsdiff-hz_set[i]);
					hz = hz_set[i];
				}
				i++;
			}
			printf("  HZ seems hz=%d\n", hz);
			sec = tstamp/hz; /* Get the uptime in seconds */
			days = sec / (3600*24);
			sec %= 3600*24;
			hours = sec / 3600;
			sec %= 3600;
			minutes = sec / 60;
			sec %= 60;
			printf("  System uptime seems: %d days, %d hours, "
			       "%d minutes, %d seconds\n",
			       		days, hours, minutes, sec);
		}
	}
	printf("\n");
	last_tstamp = tstamp;
}
