/*
 * tcpnice.c
 *
 * Slow down TCP connections already in progress.
 *
 * Copyright (c) 2000 Dug Song <dugsong@monkey.org>
 *
 * $Id: tcpnice.c,v 1.17 2001/03/17 07:41:51 dugsong Exp $
 *
 * Migrated to Libnet 1.2.x by Dotcom
 */

#include "config.h"

#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <err.h>
#include <libnet.h>
#include <pcap.h>

#include "pcaputil.h"
#include "version.h"

#define MIN_WIN		1	/* XXX */
#define MIN_MTU		68	/* RFC 1191 */
#define ICMP_QUENCH_H   8
static int	Opt_icmp;
static int	Opt_pmtu;
static int	Opt_win;
static int	pcap_off;
static u_char	buf[BUFSIZ];
libnet_t *l;
/*
// libnet packet ids
*/
libnet_ptag_t   ip_id;
libnet_ptag_t  tcp_id;

struct source_quench_message {
    u_char type;
    u_char code;
    u_short checksum;
    u_char ip_hdr[LIBNET_IPV4_H];
    u_char ip_payload[64];
};

static void
usage(void)
{
	fprintf(stderr, "Version: " VERSION "\n"
		"Usage: tcpnice [-A] [-I] [-M] [-i interface] expression\n");
	exit(1);
}

static void
send_tcp_window_advertisement(struct libnet_ipv4_hdr *ip,
			     struct libnet_tcp_hdr *tcp)
{
	int len;
	tcp->th_off = 5;
	
	tcp_id = libnet_build_tcp(ntohs(tcp->th_sport), ntohs(tcp->th_dport),ntohl(tcp->th_seq),ntohl(tcp->th_ack),tcp->th_flags,
	MIN_WIN,0,ntohs(tcp->th_urp),LIBNET_TCP_H,NULL,0,l,tcp_id);
	
	ip_id = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H,0,libnet_get_prand(LIBNET_PRu16),
	0,64,IPPROTO_TCP,0,ip->ip_src.s_addr,ip->ip_dst.s_addr,NULL,0,l,ip_id);
	
	len = LIBNET_IPV4_H + LIBNET_TCP_H;
	
	if (libnet_write(l) <0)
		warn("write");
	
	fprintf(stderr, "%s:%d > %s:%d: . ack %lu win %d\n",
		libnet_addr2name4(ip->ip_src.s_addr, 0), ntohs(tcp->th_sport),
		libnet_addr2name4(ip->ip_dst.s_addr, 0), ntohs(tcp->th_dport),
		ntohl(tcp->th_ack), 1);
}

static void
send_icmp_source_quench(struct libnet_ipv4_hdr *ip,u_char *pkt, int pkt_len)
{
	int len, payload_len;
	struct source_quench_message *quench;
	memset(buf,0x00,100);
	quench = (struct source_quench_message *)(buf + LIBNET_IPV4_H);
	//we build the source quench message
	quench->type = 4;
	quench->code = 0;
	quench->checksum = 0;
	//we copy the original packet - IP header + 64 bytes of payload at max

	if (pkt_len > 64 + LIBNET_IPV4_H ) {
	    memcpy( buf + LIBNET_IPV4_H + ICMP_QUENCH_H,pkt,LIBNET_IPV4_H + 64);
	    payload_len = ICMP_QUENCH_H + LIBNET_IPV4_H + 64;
	} else {
	    memcpy(buf +  LIBNET_IPV4_H + ICMP_QUENCH_H,pkt,pkt_len);
	    payload_len = ICMP_QUENCH_H + pkt_len;
	}
	//ICMP checksum
	quench->checksum = libnet_in_cksum(quench,payload_len);
	//total length of the packet
	len = LIBNET_IPV4_H + payload_len;
	
	ip_id = libnet_build_ipv4(len, 0, libnet_get_prand(LIBNET_PRu16),
			0, 64, IPPROTO_ICMP,0, ip->ip_dst.s_addr,
			ip->ip_src.s_addr, quench,payload_len,l,ip_id);
	
	if (libnet_write(l) < 0)
		warn("write");
	
	fprintf(stderr, "%s > %s: icmp: source quench (%i bytes)\n",
		libnet_addr2name4(ip->ip_dst.s_addr, 0),
		libnet_addr2name4(ip->ip_src.s_addr, 0),len);
}

static void
send_icmp_frag_needed(struct libnet_ipv4_hdr *ip)
{
	struct libnet_icmpv4_hdr *icmp;
	int len;
	len = (ip->ip_hl * 4) + 12;
	//building up the packet
	icmp = (struct libnet_icmpv4_hdr *)(buf + LIBNET_IPV4_H);
	icmp->icmp_type = ICMP_UNREACH;
	icmp->icmp_code = ICMP_UNREACH_NEEDFRAG;
	icmp->icmp_sum = 0;
	icmp->hun.frag.pad = 0;
	icmp->hun.frag.mtu = htons(MIN_MTU);
	//adding the original IP header
	memcpy((u_char *)icmp + 12, (u_char *)ip, ip->ip_hl * 4);
	//ICMP checksum
	icmp->icmp_sum = libnet_in_cksum(buf + LIBNET_IPV4_H, len);
	//building the IP layer
	ip_id = libnet_build_ipv4(len + LIBNET_IPV4_H, 4, libnet_get_prand(LIBNET_PRu16),
			0, 64, IPPROTO_ICMP,0, ip->ip_dst.s_addr,
			ip->ip_src.s_addr,icmp,len,l,ip_id);
	
	if (libnet_write(l) < 0)
		warn("write");
	
	fprintf(stderr, "%s > %s: icmp: ",
		libnet_addr2name4(ip->ip_dst.s_addr, 0),
		libnet_addr2name4(ip->ip_src.s_addr, 0));
	fprintf(stderr, "%s unreachable - need to frag (mtu %d)\n",
		libnet_addr2name4(ip->ip_src.s_addr, 0), MIN_MTU);
}

static void
tcp_nice_cb(u_char *user,const struct pcap_pkthdr *pcap, const u_char *pkt)
{
	struct libnet_ipv4_hdr *ip;
	struct libnet_tcp_hdr *tcp;
	int  len;
	pkt += pcap_off;
	len = pcap->caplen - pcap_off;

	ip = (struct libnet_ipv4_hdr *)pkt;
	if (ip->ip_p != IPPROTO_TCP)
		return;
	
	tcp = (struct libnet_tcp_hdr *)(pkt + LIBNET_IPV4_H);
	if (tcp->th_flags & (TH_SYN|TH_FIN|TH_RST))
		return;
	
	if (ntohs(ip->ip_len) > (ip->ip_hl << 2) + (tcp->th_off << 2)) {
		if (Opt_icmp)
			send_icmp_source_quench(ip, pkt, len);
		if (Opt_win)
			send_tcp_window_advertisement(ip, tcp);
		if (Opt_pmtu)
			send_icmp_frag_needed(ip);
	}
}

int
main(int argc, char *argv[])
{
	extern char *optarg;
	extern int optind;
	int c;
	char *intf, *filter, ebuf[PCAP_ERRBUF_SIZE];
	pcap_t *pd;
	u_char *user;
	
	intf = NULL;
	
	  char errbuf[LIBNET_ERRBUF_SIZE];

            l = libnet_init(
                    LIBNET_RAW4,    /* or LIBNET_LINK or LIBNET_RAW6 */
                    NULL,           /* or device if you using LIBNET_LINK */
                    errbuf);
	ip_id = LIBNET_PTAG_INITIALIZER;
	
	while ((c = getopt(argc, argv, "i:AIMh?V")) != -1) {
		switch (c) {
		case 'i':
			intf = optarg;
			break;
		case 'A':
			Opt_win = 1;
			break;
		case 'I':
			Opt_icmp = 1;
			break;
		case 'M':
			Opt_pmtu = 1;
			break;
		default:
			usage();
			break;
		}
	}
	if (intf == NULL && (intf = pcap_lookupdev(ebuf)) == NULL)
		errx(1, "%s", ebuf);
	
	argc -= optind;
	argv += optind;
	
	if (argc == 0)
		usage();

	if ((Opt_win | Opt_icmp | Opt_pmtu) == 0)
		Opt_win = Opt_icmp = Opt_pmtu = 1;
	
	filter = copy_argv(argv);
	
	if ((pd = pcap_init(intf, filter, 128)) == NULL)
		errx(1, "couldn't initialize sniffing");

	if ((pcap_off = pcap_dloff(pd)) < 0)
		errx(1, "couldn't determine link layer offset");
	
	libnet_seed_prand(l);
	
	warnx("listening on %s [%s]", intf, filter);
	
	pcap_loop(pd, -1, tcp_nice_cb,user);
	
	/* NOTREACHED */
	
	exit(0);
}
