
/* cutredump_banzai.c
 * Guillem Cantallops Ramis, BkP 2002
 * GNU GPL */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/bpf.h>
#include <netpacket/packet.h>
#include <signal.h>
#include <pthread.h>

#include "cutredump_banzai.h"


/* Variables globals */

extern int					errno;						/* pels codis d'error */
static int					raw;						/* descriptor pel raw socket */
static u_char				buf[MAX_FRAME_LEN];			/* buffer pel paquet capturat */
static u_int				rebuts=0;					/* comptador de paquets capturats */
static struct timeval		tx, ty;						/* temps inicial i temps final de la captura */


/* Funció que s'executa sempre al principi... */

inline void init (u_char *device)
{
	struct ifreq		ifr;
	struct sockaddr_ll	sll;
	struct packet_mreq	pmr;
	struct bpf_insn		programa[] = {	/* no pens comentar això, és entre el kernel i jo :-P */
		{40,	0,	0,	12		},
		{21,	12,	0,	2054	},
		{21,	11,	0,	32821	},
		{21,	0,	2,	2048	},
		{48,	0,	0,	23		},
		{21,	3,	9,	6		},
		{21,	0,	8,	34525	},
		{48,	0,	0,	20		},
		{21,	0,	6,	6		},
		{40,	0,	0,	20		},
		{69,	4,	0,	8191	},
		{177,	0,	0,	14		},
		{80,	0,	0,	27		},
		{21,	0,	1,	18		},
		{6,		0,	0,	65535	},
		{6,		0,	0,	0		}
	};
	struct bpf_program	filtre = {		/* aquest filtre deixa passar arp, rarp, i tcp syn+ack */
		sizeof(programa)/sizeof(struct bpf_insn),
		programa
	};

	/* Obtenim el descriptor de raw socket */
	printf("socket: ");
	if((raw=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)))<0)
		PANIC("socket");
	printf("ok\n");

	/* Obtenim l'index de l'interface que ens interessa */
	memset((void *)&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
	printf("ioctl: ");
	if(ioctl(raw, SIOCGIFINDEX, &ifr)<0)
		PANIC("ioctl");
	printf("ok\n");

	/* Lligam el socket a l'interface en questió */
	memset((void *)&sll, 0, sizeof(sll));
	sll.sll_family=AF_PACKET;
	sll.sll_ifindex=ifr.ifr_ifindex;
	sll.sll_protocol=htons(ETH_P_ALL);
	printf("bind: ");
	if(bind(raw, (struct sockaddr *)&sll, sizeof(sll))<0)
		PANIC("bind");
	printf("ok\n");

	/* El posam en mode promiscu */
	memset((void *)&pmr, 0, sizeof(pmr));
	pmr.mr_ifindex=ifr.ifr_ifindex;
	pmr.mr_type=PACKET_MR_PROMISC;
	printf("setsockopt: ");
	if(setsockopt(raw, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&pmr, (socklen_t)sizeof(pmr))<0)
		PANIC("setsockopt");
	printf("ok\n");

	/* Li posam un LSF (Linux Socket Filter) definit en BPF (Berkeley Packet Filter) */
	printf("setsockopt: ");
	if(setsockopt(raw, SOL_SOCKET, SO_ATTACH_FILTER, &filtre, sizeof(filtre))<0)
		PANIC("setsockopt");
	printf("ok\n");
}


/* Funció que s'executa al final per sortir bé... */

inline void acabar (int i)
{
	/* Tancam el raw socket */
	printf("close: ");
	close(raw);
	printf("ok\n");

	/* Mostram un resum */
	printf("En promig s'ha capturat un paquet cada %f segons\n",
		(TV(ty)-TV(tx))/((float)rebuts));

	/* That's all, folks! (aqui acaba realment el programa; es un lloc original, eh? ;-) */
	exit(0);
}


/* Funció cridada per processar_captura() cada vegada que reb un missatge ARP */

inline void processar_arp (const u_char *sp)
{
	arp_message *m;						/* 'm' de Missatge */

	char		s_ip[IP_TXT_LEN];		/* Les adreces en format "string" */
	char		s_ha[HA_TXT_LEN];

	/* Extraiem les adreces dels camps corresponents */
	m=(arp_message *)(sp+ETH_HDR_LEN);

	sprintf(s_ip,IP_FORMAT,S_IP(m));		/* Ara 's_ip' és una "string" amb l'adreça IP */
	sprintf(s_ha,HA_FORMAT,S_HA(m));		/* Ara 's_ha' és una "string" amb l'adreça Ethernet */

	/* Mostram missatge */
	printf("ARP %15s = %17s\n", s_ip, s_ha);
}


/* Funció cridada per processar_captura() cada vegada que reb un segment TCP */

inline void processar_tcp (const u_char *sp)
{
	eth_hdr		*t;					/* "t" de Trama */
	ip_hdr		*p;					/* "p" de Paquet */
	tcp_hdr		*s;					/* "s" de Segment */
	
	char		s_ip[IP_TXT_LEN];		/* Les adreces IP d'origen i destí, en format "string" */
	char		t_ip[IP_TXT_LEN];

	/* Amb això podrem accedir facilment als camps de les capçaleres */
	t=(eth_hdr *)(sp);
	p=(ip_hdr *)(sp+ETH_HDR_LEN);
	s=(tcp_hdr *)(sp+ETH_HDR_LEN+IP_HDR_LEN);

	sprintf(s_ip,IP_FORMAT,S_IP(p));
	sprintf(t_ip,IP_FORMAT,T_IP(p));
	printf("TCP %15s:%-5u -> %15s:%-5u [%c%c%c%c%c%c]\n",
		s_ip, ntohs(s->s_port), t_ip, ntohs(s->t_port),
		(s->urg)?('U'):('u'), (s->ack)?('A'):('a'), (s->psh)?('P'):('p'),
		(s->rst)?('R'):('r'), (s->syn)?('S'):('s'), (s->fin)?('F'):('f') );
}


/* Funció cridada per processar_captura() cada vegada que reb un datagrama UDP */

inline void processar_udp (const u_char *sp)
{
	ip_hdr *p;
	udp_hdr *d;

	p=(ip_hdr *)(sp+ETH_HDR_LEN);
	d=(udp_hdr *)(sp+ETH_HDR_LEN+IP_HDR_LEN);


	printf("UDP ");
	printf(IP_FORMAT, S_IP(p));
	printf(":%u\t", ntohs(d->s_port));
	printf(IP_FORMAT, T_IP(p));
	printf(":%u\n", ntohs(d->t_port));
}


/* Funció cridada cada vegada que pillam un paquet */

inline void processar_captura (const u_char *sp)
{
	eth_hdr *t;		/* "t" de Trama */
	ip_hdr *p;		/* "p" de Paquet */

	/* Si es la primera captura, guardam el seu TimeStamp: començam a comptar */
	if(!rebuts)
		gettimeofday(&tx, NULL);

	/* Incrementam comptador de paquets rebuts */
	rebuts++;

	/* Guardam el TimeStamp del darrer paquet capturat */
	gettimeofday(&ty, NULL);

	/* Anem a veure què diu la capçalera Ethernet sobre l'info que du la trama */
	t=(eth_hdr *)sp;
	switch (ntohs(t->type)) {
		case ETH_P_ARP:						/* Tant si és ARP */
		case ETH_P_RARP:					/* com si és RARP */
			processar_arp(sp);				/* els processam igual */
			break;
		case ETH_P_IP:						/* Si és IP, encara hem d'afinar més */
			p=(ip_hdr *)(sp+ETH_HDR_LEN);	/* Anem a veure que diu la capçalera IP sobre l'info que du el paquet */
			switch (p->proto) {
				case IP_P_TCP:				/* Si és TCP */
					processar_tcp(sp);		/* ja sabem el que hem de fer */
					break;
				case IP_P_UDP:				/* Si és UDP */
					processar_udp(sp);		/* també */
					break;
				default:
					break;
			}
			break;
		default:
			break;
	}
}


/* Aquest es el codi del thread "capturador" */
inline void capturador (void)
{
	int rebut;

	for(;;) {
		do {
			rebut=recvfrom(raw, buf, sizeof(buf), MSG_TRUNC, NULL, 0);		/* Capturam un paquet */
		} while ((rebut==-1)&&(errno==EINTR));								/* Evitant fals desbloqueig */
		if(rebut==-1) {														/* Els altres errors son fatals */
			PANIC("recvfrom");
		}
		processar_captura(buf);												/* Si arribam aqui, el processam */
	}
}



/* Programa Principal! */

int main (int argc, char **argv)
{
	/* IMPORTANT: fem stdout "unbuffered". NO posar cap instrucció abans d'aquesta. */
	setvbuf(stdout, (char *)NULL, _IONBF, 0);

	/* Inicialitzam capturador */
	init (((argc==2)&&(strlen(argv[1])>0))?(argv[1]):(DEFAULT_DEVICE));
	
	/* Acabarem "civilitzadament" si rebem aquests senyals */
	signal (SIGINT, acabar);
	signal (SIGQUIT, acabar);
	signal (SIGTERM, acabar);
	
	/* Ja podem començar a capturar paquets */
	capturador();

	/* No arribam aquí, però així evitam el "warning" */
	exit(-1);
}



