aboutsummaryrefslogblamecommitdiffstats
path: root/gg_sniff/pcap.c
blob: 2e31769b60d61bf2ed996f208bcb6bd29a373467 (plain) (tree)























                             
                

                 


                      




                        
                        
                                                                     


                     
                                     



                                          


                                 

                       


               








                                       
  
 
                                                                  
                                      
                                                                  
                                                  
                                                                               
                                             
                                            
                                                            
                        
                                           
                                                            

                      
                                          
                                                            
      

                                                     
                                                                     

                                      

                                        
                        
                                     

                      
                                          
      
                              


                          
 
   
                                                                      
                                                                         
 


                                      
 
                        

                                                                        
      





































                                                                                      

 
    

                           

                              








                                                                    
               
                                                                    
                                                     

                        































































                                                                       
                               

                                                                      
 


                                           
 



                                          
      






























                                                                             
                                
                                                               

      
 
           
                                      
 

                                                                 
 


                                                                                  

 
           

                                           




































                                                                                                                       

 
           

                                                    











                                                                               

 







                                                                  
                                

                                                            

                                                           












































































































































                                                                                                          

 
           

                              
                             
 




                                         

 
           

                                                                           



















                                                                                                  

 
                   

                         








                                                          

 
           

                                                                          


                                
 
                                                      
 

                                                                       
 


                                                   
 
                                    

 










                                                                                          
           

                                                                        



























                                                                                        








                               
           

                                                                         





























                                                                                
 
                        
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/time.h>

#if !defined(__OpenBSD__)
#define __FAVOR_BSD
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <err.h>

#include <pcap.h>
#if defined(__linux__)
#include <pcap/sll.h>
#endif
#include <event.h>

#include <libglouglou.h>
#include <libggnet.h>

#define PCAP_SNAPLEN 100
#define PCAP_FILTER "not port 4430 and not port 4431 and not port 53"
#define PCAP_COUNT 20
#define PCAP_TO 300

#define NODE_MAX_WITHOUT_TIMEOUT 1000
#define NODE_TIMEOUT 60 // XXX conf ?
#define CONN_TIMEOUT 30 // XXX conf ?
#define CONN_TIMEOUT_UDP 30 // XXX conf ?
#define CONN_TIMEOUT_ICMP 30 // XXX conf ?
#define CONNTIMER 5 // XXX conf ?

struct phandler {
	pcap_handler f;
	int type;
};

struct _cap_t {
	pcap_t           *pcap;
	pcap_handler      handler;
	struct event     *ev;
	struct event     *conntimer_ev;
	struct timeval    conntimer_tv;
	struct gg_client *ggcli;
	struct ggnet     *net;
	int               pinvalid;
	int               ptruncated;
};

static pcap_t      *my_pcap_open_live(const char *, int, int, int,
		char *, u_int, u_int);
static void         ip_handle(struct ip *, const u_char *, u_int);
static void         conn_del(struct ggnet_conn *);
static void         ether_handle(struct ether_header *, const u_char *, u_int);
static pcap_handler	lookup_phandler(int);
static void         phandler_ether(u_char *,
		const struct pcap_pkthdr *, const u_char *);
#if defined(__OpenBSD__)
static void         phandler_loop(u_char *,
		const struct pcap_pkthdr *, const u_char *);
#endif
#if defined(__linux__)
static void         phandler_sll(u_char *,
		const struct pcap_pkthdr *, const u_char *);
#endif
static void         cb_pcap(int, short, void *);
static void         cb_conntimer(int, short, void *);
static void         cb_nodename(struct ggnet *, struct ggnet_node *);

static struct phandler phandlers[] = {
	{ phandler_ether, DLT_EN10MB },
	{ phandler_ether, DLT_IEEE802 },
#if defined(__OpenBSD__)
	{ phandler_loop,  DLT_LOOP },
#endif
#if defined(__linux__)
	{ phandler_sll,   DLT_LINUX_SLL },
#endif
	{ NULL,           0 },
};

static struct _cap_t _cap;

int
ggsniff_pcap_init(struct event_base *ev_base, struct gg_client *ggcli,
		struct ggnet *net, char *iface, int active, char *filter)
{
	char errbuf[PCAP_ERRBUF_SIZE];
	struct bpf_program bprog;
	pcap_t *pcap;

#if defined(__OpenBSD__)
	if (!iface)
		err(1, "On OpenBSD you cannot listen on ANY interface");
#endif
	pcap = my_pcap_open_live(iface, PCAP_SNAPLEN, 1, PCAP_TO,
			errbuf, -1, 0);
	if (pcap == NULL)
		err(1, "capture: pcap_open_live failed on interface %s\n"
				"with snaplen %d : %s",
				iface, PCAP_SNAPLEN, errbuf);
	if (!filter)
		filter = strndup(PCAP_FILTER, 256);
	if (pcap_compile(pcap, &bprog, filter, 0, 0) < 0)
		err(1, "capture: pcap_compile failed with filter %s : %s",
				filter, pcap_geterr(pcap));
	if (pcap_setfilter(pcap, &bprog) < 0)
		err(1, "capture: pcap_setfilter failed : %s",
				pcap_geterr(pcap));

	_cap.pcap = pcap;
	_cap.handler = lookup_phandler(pcap_datalink(pcap));
	//_cap.tv.tv_sec = 0;
	//_cap.tv.tv_usec = PCAP_TO;
	fd_nonblock(pcap_fileno(pcap));
	_cap.ev = event_new(ev_base, pcap_fileno(pcap), EV_READ|EV_PERSIST,
			cb_pcap, NULL);
	//event_add(_cap.ev, &_cap->tv);
	event_add(_cap.ev, NULL);

	_cap.conntimer_tv.tv_sec = CONNTIMER;
	_cap.conntimer_tv.tv_usec = 0;
	_cap.conntimer_ev = evtimer_new(ev_base, cb_conntimer, NULL);
	if (evtimer_add(_cap.conntimer_ev, &_cap.conntimer_tv) == -1)
		gg_log_fatal("user: event_add conntimer failed: %s", strerror(errno));

	if (active)
		ggnet_set_dns(net, 1, ev_base, cb_nodename);

	_cap.ggcli = ggcli;
	_cap.net = net;

	return 1;
}

void
ggsniff_pcap_shutdown(void)
{
	event_del(_cap.ev);
	pcap_close(_cap.pcap);
}

/*
 * reimplement pcap_open_live with more restrictions on the bpf fd :
 * - open device read only
 * - lock the fd
 * based on OpenBSD tcpdump, privsep_pcap.c v1.16
 */

static pcap_t *
my_pcap_open_live(const char *dev, int slen, int promisc, int to_ms,
		char *ebuf, u_int dlt, u_int dirfilt)
{
#if defined(__OpenBSD__)
	struct bpf_version bv;
	u_int v;
	pcap_t *p;
	char		bpf[sizeof "/dev/bpf0000000000"];
	int		fd, n = 0;
	struct ifreq	ifr;

	p = xmalloc(sizeof(*p));
	bzero(p, sizeof(*p));

	/* priv part */

	do {
		snprintf(bpf, sizeof(bpf), "/dev/bpf%d", n++);
		fd = open(bpf, O_RDONLY);
	} while (fd < 0 && errno == EBUSY);
	if (fd < 0)
		return NULL;

	v = 32768;	/* XXX this should be a user-accessible hook */
	ioctl(fd, BIOCSBLEN, &v);

	strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
	if (ioctl(fd, BIOCSETIF, &ifr) < 0)
		return NULL;

	if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt))
		return NULL;

	if (promisc)
		/* this is allowed to fail */
		ioctl(fd, BIOCPROMISC, NULL);
	if (ioctl(fd, BIOCSDIRFILT, &dirfilt) < 0)
		return NULL;

	/* lock the descriptor */
	if (ioctl(fd, BIOCLOCK, NULL) < 0)
		return NULL;

	/* end of priv part */

	/* fd is locked, can only use 'safe' ioctls */
	if (ioctl(fd, BIOCVERSION, &bv) < 0) {
		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
				pcap_strerror(errno));
		return NULL;
	}

	if (bv.bv_major != BPF_MAJOR_VERSION ||
			bv.bv_minor < BPF_MINOR_VERSION) {
		snprintf(ebuf, PCAP_ERRBUF_SIZE,
				"kernel bpf filter out of date");
		return NULL;
	}

	p->fd = fd;
	p->snapshot = slen;

	/* Get the data link layer type. */
	if (ioctl(fd, BIOCGDLT, &v) < 0) {
		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGDLT: %s",
				pcap_strerror(errno));
		return NULL;
	}
#if _BSDI_VERSION - 0 >= 199510
	/* The SLIP and PPP link layer header changed in BSD/OS 2.1 */
	switch (v) {

		case DLT_SLIP:
			v = DLT_SLIP_BSDOS;
			break;

		case DLT_PPP:
			v = DLT_PPP_BSDOS;
			break;
	}
#endif
	p->linktype = v;

	/* XXX hack from tcpdump */
	if (p->linktype == DLT_PFLOG && p->snapshot < 160)
		p->snapshot = 160;

	/* set timeout */
	if (to_ms != 0) {
		struct timeval to;
		to.tv_sec = to_ms / 1000;
		to.tv_usec = (to_ms * 1000) % 1000000;
		if (ioctl(p->fd, BIOCSRTIMEOUT, &to) < 0) {
			snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s",
					pcap_strerror(errno));
			return NULL;
		}
	}

	if (ioctl(fd, BIOCGBLEN, &v) < 0) {
		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
				pcap_strerror(errno));
		return NULL;
	}
	p->bufsize = v;
	p->buffer = (u_char *)malloc(p->bufsize);
	if (p->buffer == NULL) {
		snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
				pcap_strerror(errno));
		return NULL;
	}
	return p;
#else /* defined(__OpenBSD__) */
	return pcap_open_live(dev, slen, promisc, to_ms, ebuf);
#endif
}

static void
cb_pcap(int fd, short why, void *data)
{
	//gg_log_tmp("cb_pcap");
	pcap_dispatch(_cap.pcap, PCAP_COUNT, _cap.handler, NULL);

	/* reschedule */
	//if (event_add(&_cap->ev, &_cap->tv) == -1)
	//	gg_log_fatal("user: event_add pcap failed : %s", strerror(errno));
}

static void
cb_conntimer(int fd, short why, void *data)
{
	struct ggnet_conn *c, *ctmp;
	struct ggnet_node *n, *ntmp;
	int i, to;

	gg_log_debug("ev_timer");
	ggnet_time_update(_cap.net, time(NULL));

	i = 0;
	LIST_FOREACH_SAFE(c, &_cap.net->conn_list, entry, ctmp) {
		switch (c->proto) {
			case IPPROTO_UDP:
				to = CONN_TIMEOUT_UDP;
				break;
			case IPPROTO_ICMP:
				to = CONN_TIMEOUT_ICMP;
				break;
			default:
				to = CONN_TIMEOUT;
				break;
		}
		if (_cap.net->time > c->lastseen + to)
			conn_del(c);
		else
			i++;
	}

	if (_cap.net->node_count > NODE_MAX_WITHOUT_TIMEOUT) {
		LIST_FOREACH_SAFE(n, &_cap.net->node_list, entry, ntmp) {
			if (n->used == 0 &&
					_cap.net->time > n->lastseen + NODE_TIMEOUT)
				ggnet_node_del(_cap.net, n);
		}
	}

	gg_log_debug("user: ev_timer leaving with %d active connections and %d active nodes", i, _cap.net->node_count);
	if (evtimer_add(_cap.conntimer_ev, &_cap.conntimer_tv) == -1)
		gg_log_fatal("user: event_add conntimer failed : %s", strerror(errno));
}

static void
cb_nodename(struct ggnet *net, struct ggnet_node *n)
{
	struct gg_packet pkt;
	int len;

	len = strnlen(n->fqdn, GGNET_DNSNAME_MAX);
	if (len > 0) {
		pkt.ver = PACKET_VERSION;
		pkt.type = PACKET_NAME;
		pkt.name_addr = n->addr.s_addr;
		pkt.name_len = len;
		strncpy((char *)pkt.name_fqdn, n->fqdn, sizeof(pkt.name_fqdn));
		gg_client_send(_cap.ggcli, &pkt);
	}
}

/*
 * Parse an IP packet and descide what to do with it.
 * 'ip' is a pointer the the captured IP packet
 * 'pend' is a pointer to the end of the captured IP packet
 * 'wirelen' is the size of the IP packet off the wire
 */
#define NOTCAPTURED(v) ((u_char *)v > (u_char *)pend - sizeof(*v))
#define NOTRECEIVED(v) (wirelen < sizeof(v))
#define log_pinvalid(fmt, ...) \
	gg_log_info("ggsniff pinvalid: " fmt, ##__VA_ARGS__)
static void
ip_handle(struct ip *ip, const u_char *pend, u_int wirelen)
{
	u_int		len, ip_hlen, off;
	const u_char	*cp;
	struct tcphdr	*tcph;
	struct udphdr	*udph;
	struct icmp	*icmp;
	struct in_addr	src, dst;
	u_int		src_port, dst_port;
	u_int		proto, close;
	int response;
	struct ggnet_conn	*conn;
	struct gg_packet pkt;

	if (NOTCAPTURED(ip)) {
		log_pinvalid("user: ip truncated (ip %x pend %x sizeof(ip) %d",
				ip, pend, sizeof(ip));
		_cap.ptruncated++;
		return;
	}

	if (ip->ip_v != IPVERSION) {
		log_pinvalid("user: invalid ip version");
		_cap.pinvalid++;
		return;
	}

	len = ntohs(ip->ip_len);
	if (wirelen < len) {
		log_pinvalid("user: ip too small");
		_cap.pinvalid++;
		len = wirelen;
	}

	ip_hlen = ip->ip_hl * 4;
	if (ip_hlen < sizeof(struct ip) || ip_hlen > len) {
		log_pinvalid("user: ip_hlen invalid, %d", ip_hlen);
		_cap.pinvalid++;
		return;
	}
	len -= ip_hlen;

	src.s_addr = ntohl(ip->ip_src.s_addr);
	dst.s_addr = ntohl(ip->ip_dst.s_addr);
	src_port = 0;
	dst_port = 0;
	proto = IPPROTO_IP;
	close = 0;

	off = ntohs(ip->ip_off);
	if ((off & IP_OFFMASK) == 0) {
		cp = (const u_char *)ip + ip_hlen;
		switch (ip->ip_p) {

			case IPPROTO_TCP:
				tcph = (struct tcphdr *)cp;
				if (NOTCAPTURED(&tcph->th_flags)) {
					log_pinvalid("user: tcp truncated");
					_cap.ptruncated++;
					return;
				}
				if (NOTRECEIVED(*tcph)) {
					log_pinvalid("user: tcp too small");
					_cap.pinvalid++;
					return;
				}
				src_port = ntohs(tcph->th_sport);
				dst_port = ntohs(tcph->th_dport);
				proto = IPPROTO_TCP;
				if ((tcph->th_flags & TH_FIN) &&
						(tcph->th_flags & TH_ACK))
					close = 1;
				break;

			case IPPROTO_UDP:
				udph = (struct udphdr *)cp;
				if (NOTCAPTURED(&udph->uh_dport)) {
					log_pinvalid("user: udp truncated, "
							"ip %x, udph %x, uh_port %x, pend %x, ip_hlen %d",
							ip, udph, &udph->uh_dport, pend, ip_hlen);
					_cap.ptruncated++;
					return;
				}
				if (NOTRECEIVED(*udph)) {
					log_pinvalid("user: udp too small");
					_cap.pinvalid++;
					return;
				}
				src_port = ntohs(udph->uh_sport);
				dst_port = ntohs(udph->uh_dport);
				proto = IPPROTO_UDP;
				break;

			case IPPROTO_ICMP:
				icmp = (struct icmp *)cp;
				if (NOTRECEIVED(*icmp)) {
					log_pinvalid("user: icmp too small");
					_cap.pinvalid++;
					return;
				}
				proto = IPPROTO_ICMP;
				break;

			default:
				gg_log_warn("user: unknown ip protocol !");
				break;
		}
	} else {
		/*
		 * if this isn't the first frag, we're missing the
		 * next level protocol header.
		 */
		gg_log_debug("user: got a fragmented ip packet !");
	}

	pkt.ver = PACKET_VERSION;
	conn = ggnet_conn_find(_cap.net, &src, src_port, &dst, dst_port,
			proto, &response);
	if (conn) {
		if (!close) {
			pkt.type = PACKET_DATA;
			pkt.data_connid = conn->id;
			GG_PKTDATA_SIZE_ENCODE(pkt.data_size, ip->ip_len, response);
			gg_client_send(_cap.ggcli, &pkt);
			ggnet_conn_data(_cap.net, conn, ip->ip_len, response);
		} else {
			conn_del(conn);
		}
	} else {
		if (!close) {
			conn = ggnet_conn_add(_cap.net, &src, src_port, &dst, dst_port, proto,
					ip->ip_len, -1);
			pkt.type = PACKET_NEWCONN;
			pkt.newconn_id = conn->id;
			pkt.newconn_src = src.s_addr;
			pkt.newconn_dst = dst.s_addr;
			pkt.newconn_proto = proto;
			GG_PKTDATA_SIZE_ENCODE(pkt.newconn_size, ip->ip_len, 0);
			gg_client_send(_cap.ggcli, &pkt);
		} else {
			gg_log_warn("user: captured connection close w/o open !");
		}
	}
}

static void
conn_del(struct ggnet_conn *c)
{
	struct gg_packet pkt;

	pkt.ver = PACKET_VERSION;
	pkt.type = PACKET_DELCONN;
	pkt.delconn_id = c->id;
	gg_client_send(_cap.ggcli, &pkt);
	ggnet_conn_del(_cap.net, c);
}

static void
ether_handle(struct ether_header *ether, const u_char *pend, u_int wirelen)
{
	struct ip *ip;
	u_short ether_type;

	wirelen -= sizeof(struct ether_header);

	ether_type = ntohs(ether->ether_type);
	if (ether_type <= ETHERMTU)
		gg_log_debug("llc packet !");
	else {
		switch (ether_type) {
			case ETHERTYPE_IP:
				gg_log_debug("loop family AF_LINK IP");
				ip = (struct ip *)((u_char *)ether + sizeof(struct ether_header));
				ip_handle(ip, pend, wirelen);
				break;
			default:
				gg_log_debug("loop non ip packet !");
				break;
		}
	}
}

static pcap_handler
lookup_phandler(int type)
{
	struct phandler *p;

	for (p = phandlers; p->f; ++p) {
		if (type == p->type)
			return p->f;
	}
	err(1, "user: unknown data link type 0x%x", type);
	/* NOTREACHED */
	return NULL;
}

static void
phandler_ether(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
	struct ether_header *ep;
	const u_char *pend;
	u_int	len;

	gg_log_debug("user: pcap handler ethernet !");

	/* XXX here i assume that packets are alligned, which might not
	 * be the case when using dump files, says tcpdump sources */

	ep = (struct ether_header *)p;
	pend = p + h->caplen;
	len = h->len - sizeof(struct ether_header);

	ether_handle(ep, pend, len);
}

/*
 * Handler for Linux cooked, used when capturing on any interface
 */
#if defined(__linux__)
#define LINUX_SLL_P_802_3	0x0001	/* Novell 802.3 frames without 802.2 LLC header */
#define LINUX_SLL_P_ETHERNET	0x0003	/* Ethernet */
#define LINUX_SLL_P_802_2	0x0004	/* 802.2 frames (not D/I/X Ethernet) */
#define LINUX_SLL_P_PPPHDLC	0x0007	/* PPP HDLC frames */
#define LINUX_SLL_P_CAN		0x000C	/* Controller Area Network */
#define LINUX_SLL_P_IRDA_LAP	0x0017	/* IrDA Link Access Protocol */

static void
phandler_sll(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
	struct ip *ip;
	struct ether_header *ep;
	u_int family;
	const u_char *pend;
	u_int	len;

	gg_log_debug("user: phandler_sll !");

	/* XXX here i assume that packets are alligned, which might not
	 * be the case when using dump files, says tcpdump sources */

	pend = p + h->caplen;
	len = h->len - SLL_HDR_LEN;

	family = ntohs(p[14]);
	if (family < 1536) { /* linux and wireshark are good for you */
		switch (family) {
			case LINUX_SLL_P_ETHERNET:
				ep = (struct ether_header *)((u_char *)p + SLL_HDR_LEN);
				ether_handle(ep, pend, len);
			default:
				gg_log_debug("unknown family %x !", family);
				break;
		}
	} else {
		ip = (struct ip *)(p + SLL_HDR_LEN);
		ip_handle(ip, pend, len);
	}
}
#endif /* __linux__ */

/*
 * Handler for OpenBSD Loopback
 */
#if defined(__OpenBSD__)
#define NULL_HDRLEN 4

static void
phandler_loop(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
	struct ip *ip;
	struct ether_header *ep;
	u_int family;
	const u_char *pend;
	u_int	len;

	gg_log_debug("user: pcap handler loop !");

	/* XXX here i assume that packets are alligned, which might not
	 * be the case when using dump files, says tcpdump sources */

	pend = p + h->caplen;
	len = h->len - NULL_HDRLEN;

	memcpy((char *)&family, (char *)p, sizeof(family));
	family = ntohl(family);
	switch (family) {
		case AF_INET:
			gg_log_debug("loop family AF_INET");
			ip = (struct ip *)(p + NULL_HDRLEN);
			ip_handle(ip, pend, len);
			break;
		case AF_LINK:
			ep = (struct ether_header *)((u_char *)p + NULL_HDRLEN);
			ether_handle(ep, pend, len);
			break;
		default:
			gg_log_debug("unknown family %x !", family);
			break;
	}
}
#endif /* __OpenBSD__ */