/* 
 * $smu-mark$ 
 * $name: sendicmp.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:49 MET 1999$ 
 * $rev: 8$ 
 */ 

#include <sys/types.h> /* this should be not needed, but ip_icmp.h lacks it */
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

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

void send_icmp_echo(void);
void send_icmp_other(void);
void send_icmp_timestamp(void);
void send_icmp_address(void);

void send_icmp(int signal_id)
{
	switch(opt_icmptype)
	{
		case ICMP_ECHO:			/* type 8 */
		case ICMP_ECHOREPLY:		/* type 0 */
			send_icmp_echo();
			break;
		case ICMP_DEST_UNREACH:		/* type 3 */
		case ICMP_SOURCE_QUENCH:	/* type 4 */
		case ICMP_REDIRECT:		/* type 5 */
		case ICMP_TIME_EXCEEDED:	/* type 11 */
			send_icmp_other();
			break;
		case ICMP_TIMESTAMP:
		case ICMP_TIMESTAMPREPLY:
			send_icmp_timestamp();
			break;
		case ICMP_ADDRESS:
		case ICMP_ADDRESSREPLY:
			send_icmp_address();
			break;
		default:
			printf("[send_icmp] Unsupported icmp type!\n");
			exit(1);
	}
}

void send_icmp_echo(void)
{
	char *packet, *data;
	struct myicmphdr *icmp;
	static int _icmp_seq = 0;
	int errno_save = errno;

	packet = malloc(ICMPHDR_SIZE + data_size);
	if (packet == NULL) {
		perror("[send_icmp] malloc");
		errno = errno_save;
		return;
	}

	memset(packet, 0, ICMPHDR_SIZE + data_size);

	icmp = (struct myicmphdr*) packet;
	data = packet + ICMPHDR_SIZE;

	/* fill icmp hdr */
	icmp->type = opt_icmptype;	/* echo replay or echo request */
	icmp->code = opt_icmpcode;	/* should be indifferent */
	icmp->checksum = 0;
	icmp->un.echo.id = getpid() & 0xffff;
	icmp->un.echo.sequence = _icmp_seq;

	/* data */
	data_handler(data, data_size);

	/* icmp checksum */
	if (icmp_cksum == -1)
		icmp->checksum = cksum((u_short*)packet, ICMPHDR_SIZE + data_size);
	else
		icmp->checksum = icmp_cksum;

	/* adds this pkt in delaytable */
	if (opt_icmptype == ICMP_ECHO)
	{
		delaytable[delaytable_index % TABLESIZE].seq = _icmp_seq;
		delaytable[delaytable_index % TABLESIZE].sec = time(NULL);
		delaytable[delaytable_index % TABLESIZE].usec = get_usec();
		delaytable[delaytable_index % TABLESIZE].status = S_SENT;
		delaytable_index++;
	}

	/* send packet */
	send_ip_handler(packet, ICMPHDR_SIZE + data_size);
	free (packet);

	_icmp_seq++;
	sent_pkt++;
	Signal(SIGALRM, send_icmp);
	
	if (count != -1 && count == sent_pkt)	/* count reached */
	{
		Signal(SIGALRM, print_statistics);
		alarm(COUNTREACHED_TIMEOUT);
	}
	else if (!opt_listenmode)
	{
		if (opt_waitinusec == FALSE)
			alarm(sending_wait);
		else
			setitimer(ITIMER_REAL, &usec_delay, NULL);
	}

	errno = errno_save;
}

void send_icmp_timestamp(void)
{
	char *packet;
	struct myicmphdr *icmp;
	static int _icmp_seq = 0;
	int errno_save = errno;
	struct icmp_tstamp_data *tstamp_data;

	packet = malloc(ICMPHDR_SIZE + sizeof(struct icmp_tstamp_data));
	if (packet == NULL) {
		perror("[send_icmp] malloc");
		errno = errno_save;
		return;
	}

	memset(packet, 0, ICMPHDR_SIZE + sizeof(struct icmp_tstamp_data));

	icmp = (struct myicmphdr*) packet;
	tstamp_data = (struct icmp_tstamp_data*) (packet + ICMPHDR_SIZE);

	/* fill icmp hdr */
	icmp->type = opt_icmptype;	/* echo replay or echo request */
	icmp->code = 0;
	icmp->checksum = 0;
	icmp->un.echo.id = getpid() & 0xffff;
	icmp->un.echo.sequence = _icmp_seq;
	tstamp_data->orig = htonl(get_midnight_ut_ms());
	tstamp_data->recv = tstamp_data->tran = 0;

	/* icmp checksum */
	if (icmp_cksum == -1)
		icmp->checksum = cksum((u_short*)packet, ICMPHDR_SIZE +
				sizeof(struct icmp_tstamp_data));
	else
		icmp->checksum = icmp_cksum;

	/* adds this pkt in delaytable */
	if (opt_icmptype == ICMP_TIMESTAMP)
	{
		delaytable[delaytable_index % TABLESIZE].seq = _icmp_seq;
		delaytable[delaytable_index % TABLESIZE].sec = time(NULL);
		delaytable[delaytable_index % TABLESIZE].usec = get_usec();
		delaytable[delaytable_index % TABLESIZE].status = S_SENT;
		delaytable_index++;
	}

	/* send packet */
	send_ip_handler(packet, ICMPHDR_SIZE + sizeof(struct icmp_tstamp_data));
	free (packet);

	_icmp_seq++;
	sent_pkt++;
	Signal(SIGALRM, send_icmp);
	
	if (count != -1 && count == sent_pkt)	/* count reached */
	{
		Signal(SIGALRM, print_statistics);
		alarm(COUNTREACHED_TIMEOUT);
	}
	else if (!opt_listenmode)
	{
		if (opt_waitinusec == FALSE)
			alarm(sending_wait);
		else
			setitimer(ITIMER_REAL, &usec_delay, NULL);
	}

	errno = errno_save;
}

void send_icmp_address(void)
{
	char *packet;
	struct myicmphdr *icmp;
	static int _icmp_seq = 0;
	int errno_save = errno;

	packet = malloc(ICMPHDR_SIZE + 4);
	if (packet == NULL) {
		perror("[send_icmp] malloc");
		errno = errno_save;
		return;
	}

	memset(packet, 0, ICMPHDR_SIZE + 4);

	icmp = (struct myicmphdr*) packet;

	/* fill icmp hdr */
	icmp->type = opt_icmptype;	/* echo replay or echo request */
	icmp->code = 0;
	icmp->checksum = 0;
	icmp->un.echo.id = getpid() & 0xffff;
	icmp->un.echo.sequence = _icmp_seq;
	memset(packet+ICMPHDR_SIZE, 0, 4);

	/* icmp checksum */
	if (icmp_cksum == -1)
		icmp->checksum = cksum((u_short*)packet, ICMPHDR_SIZE + 4);
	else
		icmp->checksum = icmp_cksum;

	/* adds this pkt in delaytable */
	if (opt_icmptype == ICMP_TIMESTAMP)
	{
		delaytable[delaytable_index % TABLESIZE].seq = _icmp_seq;
		delaytable[delaytable_index % TABLESIZE].sec = time(NULL);
		delaytable[delaytable_index % TABLESIZE].usec = get_usec();
		delaytable[delaytable_index % TABLESIZE].status = S_SENT;
		delaytable_index++;
	}

	/* send packet */
	send_ip_handler(packet, ICMPHDR_SIZE + 4);
	free (packet);

	_icmp_seq++;
	sent_pkt++;
	Signal(SIGALRM, send_icmp);
	
	if (count != -1 && count == sent_pkt)	/* count reached */
	{
		Signal(SIGALRM, print_statistics);
		alarm(COUNTREACHED_TIMEOUT);
	}
	else if (!opt_listenmode)
	{
		if (opt_waitinusec == FALSE)
			alarm(sending_wait);
		else
			setitimer(ITIMER_REAL, &usec_delay, NULL);
	}

	errno = errno_save;
}

void send_icmp_other(void)
{
	char *packet, *data;
	struct myicmphdr *icmp;
	struct myiphdr icmp_ip;
	struct myudphdr icmp_udp;
	int errno_save = errno;
	int left_space = data_size;

	packet = malloc(ICMPHDR_SIZE + data_size);
	if (packet == NULL) {
		perror("[send_icmp] malloc");
		errno = errno_save;
		return;
	}

	memset(packet, 0, ICMPHDR_SIZE + data_size);

	icmp = (struct myicmphdr*) packet;
	data = packet + ICMPHDR_SIZE;

	/* fill icmp hdr */
	icmp->type = opt_icmptype;	/* ICMP_TIME_EXCEEDED */
	icmp->code = opt_icmpcode;	/* should be 0 (TTL) or 1 (FRAGTIME) */
	icmp->checksum = 0;
	if (opt_icmptype == ICMP_REDIRECT)
		memcpy(&icmp->un.gateway, "CCCC", 4);
	else
		icmp->un.gateway = 0;	/* not used, MUST be 0 */

	/* concerned packet headers */
	/* IP header */
	icmp_ip.version  = icmp_ip_version;		/* 4 */
	icmp_ip.ihl      = icmp_ip_ihl;			/* IPHDR_SIZE >> 2 */
	icmp_ip.tos      = icmp_ip_tos;			/* 0 */
	icmp_ip.tot_len  = htons(icmp_ip_tot_len);	/* 0 FIXME: compute */
	icmp_ip.id       = htons(icmp_ip_id);		/* 0 FIXME: rand */
	icmp_ip.frag_off = 0;				/* 0 */
	icmp_ip.ttl      = 64;				/* 64 */
	icmp_ip.protocol = icmp_ip_protocol;		/* 6 (TCP) */
	icmp_ip.check	 = 255;				/* FIXME: compute */
	memcpy(&icmp_ip.saddr, "AAAA", 4);
	memcpy(&icmp_ip.daddr, "BBBB", 4);

	/* UDP header */
	icmp_udp.uh_sport = htons(1111);
	icmp_udp.uh_dport = htons(2222);
	icmp_udp.uh_ulen  = htons(8);
	icmp_udp.uh_sum   = htons(5555);

	/* filling icmp body with concerned packet header */

	/* fill IP */
	if (left_space == 0) goto no_space_left;
	memcpy(packet+ICMPHDR_SIZE, &icmp_ip, left_space);
	left_space -= IPHDR_SIZE;
	data += IPHDR_SIZE;
	if (left_space <= 0) goto no_space_left;

	/* fill UDP */
	memcpy(packet+ICMPHDR_SIZE+IPHDR_SIZE, &icmp_udp, left_space);
	left_space -= UDPHDR_SIZE;
	data += UDPHDR_SIZE;
	if (left_space <= 0) goto no_space_left;

	/* fill DATA */
	data_handler(data, left_space);
no_space_left:

	/* icmp checksum */
	if (icmp_cksum == -1)
		icmp->checksum = cksum((u_short*)packet, ICMPHDR_SIZE + data_size);
	else
		icmp->checksum = icmp_cksum;

	/* send packet */
	send_ip_handler(packet, ICMPHDR_SIZE + data_size);
	free (packet);

	sent_pkt++;
	Signal(SIGALRM, send_icmp);
	
	if (count != -1 && count == sent_pkt)	/* count reached */
	{
		Signal(SIGALRM, print_statistics);
		alarm(COUNTREACHED_TIMEOUT);
	}
	else if (!opt_listenmode)
	{
		if (opt_waitinusec == FALSE)
			alarm(sending_wait);
		else
			setitimer(ITIMER_REAL, &usec_delay, NULL);
	}

	errno = errno_save;
}
