/*

Part of the TCP Testing Tool (ttt).

Packet creation is done here.

Eloy Paris 

$Id: packet-factory.c,v 1.3 2003/05/17 22:36:10 peloy Exp $

*/

#include "includes.h"

struct pseudo_header {
	in_addr_t saddr;
	in_addr_t daddr;
	u_int8_t zero;
	u_int8_t protocol;
	u_int16_t len;
};

/* These two should be defined in netinet/tcp.h, but they aren't */

#define TCPOPT_SIGNATURE 19 /* As defined in RFC2385 */
#define TCPOLEN_SIGNATURE 18

/* We could malloc() this, but this is more efficient */

char *packet[IP_MAXPACKET + sizeof(struct pseudo_header)];
static char *tcp_sig(struct iphdr *ip, char *dest, char *key);
static inline u_short in_cksum(void *data, int len);

int send_segment(void)
{
	int sockfd;
	struct sockaddr_in sockaddr;
	char *tcp_payload_ptr, *tcp_options_ptr;
	int pkt_size, pseudo_pkt_size;
	struct iphdr *ip;
	struct tcphdr *tcp;
	struct pseudo_header *pseudoh;
	u_int16_t dest_port;
	int sent_bytes;
	int flood_count, flood_forever;
	char *tcp_sig_ptr;

	/*
	 * We need to calculate the *real* size of the TCP segment
	 * that we will send. For this, we start we the obvious stuff:
	 * size of the IP header + size of the TCP header with no
	 * options.
	 */
	pkt_size = sizeof(struct iphdr) + sizeof(struct tcphdr);

	/*
	 * Then, we consider the TCP options we will add to the
	 * segment and adjust the packet size accordingly.
	 */
	if (options.md5_secret)
		/* TCP signature option */
		pkt_size += TCPOLEN_SIGNATURE;

	if (options.wscale)
		/* Window scale */
		pkt_size += TCPOLEN_WINDOW;

	if (options.mss)
		/* Maximum segment size */
		pkt_size += TCPOLEN_MAXSEG;

	/*
	 * We then take into consideration any padding we need to keep the
	 * TCP header word-aligned. This weird operation rounds-up
	 * pkt_size to the next multiple of 4 - do the math :-)
	 */
	pkt_size += -pkt_size & 0x3;

	/*
	 * And finally, we add the size of the payload.
	 */
	pkt_size += options.payload.len;

	/*
	 * Size of the pseudo_pkt: IP hdr + TCP hdr (word-aligned
	 * and including TCP options) + TCP payload + pseudo header.
	 */
	pseudo_pkt_size = pkt_size + (pkt_size & 1) +
			  sizeof(struct pseudo_header);

	if ( (sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW) ) == -1)
		error(1, "Can't create socket");

	bzero(packet, pseudo_pkt_size);

	/*
	 * IP header
	 *
	 * See man raw(7) - saddr and id are filled in by the kernel
	 * if they are zero. cksum and length always filled-in by the
	 * kernel.
	 */
	ip = (struct iphdr *) packet;

	ip->version = 4;
	ip->ihl = 5;
	ip->ttl = u_int8_next(options.ip_ttl);
	ip->tos = u_int8_next(options.ip_tos);
	ip->id = u_int16_next(options.ip_id);
	ip->protocol = IPPROTO_TCP;
	ip->saddr = options.source_ip.s_addr;
	ip->daddr = options.destination_ip.s_addr;

	/*
	 * Need to assign to a temp. variable because u_int16_next()
	 * may return different values in successive calls.
	 */
	dest_port = u_int16_next(options.dst_port);

	/*
	 * TCP header - we take care of the header size (tcp->doff)
	 * later when we deal with TCP options.
	 */
	tcp = (struct tcphdr *) ( (char *) ip + sizeof(struct iphdr) );

	tcp->source = u_int16_next(options.src_port);
	tcp->dest = dest_port;
	tcp->seq = u_int32_next(options.sequence_number);
	tcp->ack_seq = u_int32_next(options.ack_seq);
	tcp->window = u_int16_next(options.window);
	tcp->urg_ptr = u_int16_next(options.urg_ptr);

	/* TCP flags */
	tcp->urg = options.urg;
	tcp->ack = options.ack;
	tcp->psh = options.psh;
	tcp->rst = options.rst;
	tcp->syn = options.syn;
	tcp->fin = options.fin;

	/*
	 * We're done with the basic TCP header. Now deal with the
	 * possible TCP options.
	 */

	tcp_options_ptr = (char *) tcp + sizeof(struct tcphdr);

	if (options.md5_secret) {
		/*
		 * Deal with TCP option 19.
		 */
		*tcp_options_ptr++ = TCPOPT_SIGNATURE;
		*tcp_options_ptr++ = TCPOLEN_SIGNATURE;
		/*
		 * We can't calculate the TCP signature now because we
		 * are not done yet building the packet. We just save
		 * the place where we will store the signature and
		 * calculate it later.
		 */
		tcp_sig_ptr = tcp_options_ptr;
		tcp_options_ptr += MD5_DIGEST_LENGTH;
	}

	if (options.mss) {
		/* TCP option 2 */
		*tcp_options_ptr++ = TCPOPT_MAXSEG;
		*tcp_options_ptr++ = TCPOLEN_MAXSEG;
		SSVAL(tcp_options_ptr, 0, u_int16_next(options.mss) );
		tcp_options_ptr += 2;
	}

	if (options.wscale) {
		/* TCP option 3 */
		*tcp_options_ptr++ = TCPOPT_WINDOW;
		*tcp_options_ptr++ = TCPOLEN_WINDOW;
		*tcp_options_ptr++ = u_int8_next(options.wscale);
	}

	/* Finally, pad the TCP header */
	while ( (u_int32_t) tcp_options_ptr & 0x3)
		*tcp_options_ptr++ = TCPOPT_EOL;

	/*
	 * Offset of the TCP payload - if there are no options this
	 * is obviously 5
	 */
	tcp->doff = (tcp_options_ptr - (char *) tcp)/4;

	tcp_payload_ptr = tcp_options_ptr;

	if (options.payload.data)
		memcpy(tcp_payload_ptr, options.payload.data,
		       options.payload.len);

	/*
	 * We now calculate the length of the entire IP packet. The
	 * kernel will do this for us anyway, but we need to have this
	 * length in place because the tcp_sig() function needs it.
	 */
	tcp_payload_ptr += options.payload.len;
	ip->tot_len = htons(tcp_payload_ptr - (char *) ip);

	/* Now we can calculate the MD5 signature */
	if (options.md5_secret)
		tcp_sig(ip, tcp_sig_ptr, options.md5_secret);

	/*
	 * Now deal with the TCP checksum. What a pain in the ass.
	 */
	pseudoh = (struct pseudo_header *) (tcp_payload_ptr +
					    options.payload.len +
					    options.payload.len % 2);
	pseudoh->saddr = ip->saddr;
	pseudoh->daddr = ip->daddr;
	pseudoh->zero = 0;
	pseudoh->protocol = ip->protocol;
	pseudoh->len = htons(pkt_size - sizeof(struct tcphdr) );

	tcp->check = 0;
	if (!options.no_cksum)
		tcp->check = in_cksum(tcp,
				      pseudo_pkt_size - sizeof(struct iphdr) );

	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = dest_port;
	sockaddr.sin_addr = options.destination_ip;

	flood_count = options.flood_count;
	flood_forever = flood_count == 0;

	while (flood_forever || flood_count--) {
		sent_bytes = sendto(sockfd, packet, pkt_size, 0,
				    (struct sockaddr *) &sockaddr,
				    sizeof(struct sockaddr_in) );
		if (sent_bytes == -1)
			error(1, "sendto()");
	}

	/*
	 * Heh, don't forget to close the file descriptor or we'll
	 * leak file descriptors. Heh.
	 */
	close(sockfd);

	return sent_bytes;
}

/*
 * This function calculates the MD5 digest of a TCP segment.
 * RFC2385 describes how the digest is calculated.
 */
char *tcp_sig(struct iphdr *ip, char *dest, char *key)
{
	struct pseudo_header pseudo_header;
	struct tcphdr *tcp;
	MD5_CTX c;
	int size_of_tcp_data;
	u_int16_t tmp;

	/*
	 * Minimal error checking - return NULL if any of the input
	 * parameters does not make sense.
	 */
	if (!ip || !dest || !key)
		return NULL;

	MD5_Init(&c);

	/*
	 * Calculate MD5 sum of pseudo header (step 1 in section 2.0 of
	 * RFC2385)
	 */
	pseudo_header.saddr = ip->saddr;
	pseudo_header.daddr = ip->daddr;
	pseudo_header.zero = 0;
	pseudo_header.protocol = ip->protocol;
	/*
	 * Remember: the length field in the pseudo-header is the length
	 * of the TCP segment, that is, size of the TCP header (which may
	 * or may not include TCP options) + the size of the TCP payload.
	 * This easy to calculate: total IP packet size - size of IP header.
	 */
	pseudo_header.len = htons(ntohs(ip->tot_len) - ip->ihl*4);
	MD5_Update(&c, &pseudo_header, sizeof(pseudo_header) );

	/*
	 * Calculate MD5 sum of TCP header (_excluding_ options!) (step 2 in
	 * section 2.0 of RFC2385).
	 */
	tcp = (struct tcphdr *) ( (char *) ip + ip->ihl*4);

	tmp = tcp->check;
	tcp->check = 0;
	MD5_Update(&c, tcp, sizeof(struct tcphdr) );
	tcp->check = tmp;

	/* Calculate MD5 sum of TCP data (step 3 in section 2.0 of RFC2385) */
	size_of_tcp_data = ntohs(ip->tot_len) - ip->ihl*4 - tcp->doff*4;
	MD5_Update(&c, (char *) tcp + tcp->doff*4, size_of_tcp_data);

	/*
	 * Now calculate MD5 sum of the secret (step 4 in section 2.0
	 * of RFC2385.
	 */
	MD5_Update(&c, key, strlen(key) );

	MD5_Final(dest, &c);

	return dest;
}

static inline u_short in_cksum(void *data, int len)
{
	int counter = len;
	u_short *w = data;
	int sum = 0;
	u_short tmp = 0;

	while (counter > 1) {
		sum += *w++;
		counter -= 2;
	}

	if (counter == 1) {
		/* Length is odd */
		*(u_char *) &tmp = *(u_char *) w;
		sum += tmp;
	}

	/* We want a 1's complement sum, so we must add whatever was
	   carried out from the low 16 bits to the low 16 bits */
	sum = (sum >> 16) + (u_short) sum;

	/* Add the carry from the previous sum */
	sum += sum >> 16;

	/* Now we take the 1's complement of the 1's complement sum */
	tmp = ~sum;

	return tmp;
}
