Here is a program called Easy Sniff. Easy Sniff is a packet analyzer that I wrote on FreeBSD. It makes use of the Berkeley Packet Filter (BPF). The BPF is an interface to the raw data packets that arrive at the network card, bypassing any packet decomposition which happens on the way up to the client application, which usually sees nothing else but the actual data. This program is a bare bones implementation of what tcpdump does. With a little bit of work, the program could just as well be modified to work as a packet injector.
If you want more information on the BPF interface you can go here.
Feel free to make use of this code for research purposes. Just remember to mention my name in the sources :).
Anyways, here goes the code for EasySniff:
/*
* Copyright (c) 2009 Oliver Mahmoudi
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <net/if.h> /* struct ifreq */
#include <net/bpf.h> /* BIOCSETIF */
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#define OPTS "12hespxc:n:"
#define BPFHDRSIZ (sizeof(struct bpf_hdr))
#define ETHHDRSIZ (sizeof(struct ether_header))
#define IPHDRSIZ (sizeof(struct ip))
#define TCPHDRSIZ (sizeof(struct tcphdr))
#define UDPHDRSIZ (sizeof(struct udphdr))
#define USAGE \
"usage: ./easysniff [-12esphx] [-n counts] [-c capturefile] device\n \
type: ./easysniff -h for help\n"
/* globals */
char *fbuff; /* char array for read() */
FILE *fp; /* for cap_file */
struct bpf_hdr *p; /* */
struct bpf_stat bpf_sta; /* */
int n; /* bytes read */
int pfd; /* bpf file descriptor */
int sflag; /* flag for printing bpfstats */
int nflag; /* flag for packet count */
int cflag; /* cflag for cap_file */
int pflag; /* force the if into promiscous mode */
int xflag; /* print the packet in hex flag */
int eflag; /* don't print the ethernet data */
int oneflag; /* alternate print MAC address 1 */
int twoflag; /* alternate print MAC address 2 */
int threeflag; /* default version for print MAC address */
/* functions */
void printhelp(void);
void printpacketinhex(char *, int);
void printinhex(char *, char *, int);
void prntbpfstats(char *);
void parse_eth_header(char *, int);
void parse_ip_header(char *, int);
/* void parse_icmp_header(char *, int); */
void parse_tcp_header(char *, int);
void parse_udp_header(char *, int);
void parse_tcp_data(char *, int);
void parse_udp_data(char *, int);
void exit_handler();
void
printhelp(void)
{
printf("\t-1 prints the MAC address in an alternate form \n");
printf("\t-2 prints the MAC address in yet another alternate form\n");
printf("\t-s prints the bpf statistics \n");
printf("\t-e turns the printing of the ethernet header information off\n");
printf("\t-p takes the interface out of promiscuous mode\n");
printf("\t-n the number of packets you wish to capture on the interface\n");
printf("\t-c the capture file to which the captured data will be written\n");
printf("\t-h prints this help screen\n");
printf("\t-x prints the packet in hex format\n");
printf("\n\tdevice is the NIC you wish to listen on: e.g. ed0\n");
}
void
printpacketinhex(char *packet, int len)
{
char *p = packet;
printf("\n\n--------Start-of-Packet--------\n\n");
while(len--) {
printf("%.2x ", *p);
p++;
}
printf("\n\n--------End-of-Packet--------\n\n");
}
void
printinhex(char *mesg, char *p, int len)
{
printf(mesg);
while(len--) {
printf("%.2x ", *p);
p++;
}
}
void
prntbpfstats(char *readbuff)
{
char *newbuff = readbuff;
struct bpf_hdr *buf;
buf = (struct bpf_hdr *)newbuff;
printf("Read occured at: %d:%d\n",
buf->bh_tstamp.tv_sec,
buf->bh_tstamp.tv_usec);
printf("Length of the captured portion: %d\n",
buf->bh_caplen);
printf("Original length of packet: %d\n", buf->bh_datalen);
printf("Length of bpf header: %d\n\n", buf->bh_hdrlen);
if(cflag) {
fprintf(fp, "Read occured at: %d:%d\n",
buf->bh_tstamp.tv_sec,
buf->bh_tstamp.tv_usec);
fprintf(fp, "Length of the captured portion: %d\n",
buf->bh_caplen);
fprintf(fp, "Original length of the packet: %d\n",
buf->bh_datalen);
fprintf(fp, "Length of the bpf header: %d\n\n",
buf->bh_hdrlen);
}
}
void
parse_eth_header(char *packet, int len)
{
struct ether_header *ethernet_h;
int i = ETHER_ADDR_LEN;
printf("Decoding the Ethernet Header:\n");
if(len >= (p->bh_hdrlen + ETHHDRSIZ)) {
ethernet_h = (struct ether_header *)(packet + p->bh_hdrlen);
/* print the MAC Address alternate version 1 */
if(oneflag) {
printinhex("Destination MAC: ", ethernet_h->ether_dhost, 6);
printf("\n");
printinhex("Source MAC: ", ethernet_h->ether_shost, 6);
printf("\n");
}
/* print the MAC Address alternate version 2 */
if(twoflag) {
do {
printf("Destination MAC: %s%x",
i == ETHER_ADDR_LEN ? " " : ":",
ethernet_h->ether_dhost);
} while(--i > 0);
printf("\n");
do {
printf("Source MAC: %s%x",
i == ETHER_ADDR_LEN ? " " : ":",
ethernet_h->ether_shost);
}while(--i > 0);
printf("\n");
}
/*
* print the MAC Address version 3. This is the default.
*/
if(threeflag) {
printf("Destination MAC: %s\n",
ether_ntoa((struct ether_addr *)ðernet_h->ether_dhost));
printf("Source MAC: %s\n",
ether_ntoa((struct ether_addr *)ðernet_h->ether_shost));
}
printf("Protocol: dec %d hex %x",
ntohs(ethernet_h->ether_type),
ntohs(ethernet_h->ether_type));
if(ntohs(ethernet_h->ether_type) == ETHERTYPE_IP)
printf(" -> IP\n");
else if(ntohs(ethernet_h->ether_type) == ETHERTYPE_ARP)
printf(" -> ARP\n");
else
printf(" -> neither IP nor ARP\n");
}
else
printf("the packet does not contain an ethernet header\n");
}
void
parse_ip_header(char *packet, int len)
{
int type;
struct ether_header *ethernet_h;
struct ip *ip_h;
printf("\nDecoding the IP Header:\n");
ethernet_h = (struct ether_header *)(packet + p->bh_hdrlen);
type = ntohs(ethernet_h->ether_type);
if(type == ETHERTYPE_IP)
if(len >= (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ)) {
ip_h = (struct ip *)(packet + p->bh_hdrlen + ETHHDRSIZ);
printf("Source address: %s\n", inet_ntoa(ip_h->ip_src.s_addr));
printf("Destination address: %s\n", inet_ntoa(ip_h->ip_dst.s_addr));
printf("Protocol: %d ", ip_h->ip_p);
switch(ip_h->ip_p) {
case IPPROTO_TCP:
printf("-> TCP\n");
parse_tcp_header(fbuff, n);
break;
case IPPROTO_UDP:
printf("-> UDP\n");
parse_udp_header(fbuff, n);
break;
case IPPROTO_ICMP:
printf("-> ICMP\n");
break;
default:
printf("neither TCP nor UDP nor ICMP\n");
}
printf("\n");
}
else
printf("the IP packet is too short\n");
}
else
printf("the packet does not contain an ip header\n");
}
void
parse_tcp_header(char *packet, int len)
struct tcphdr *tcph;
printf("\nDecoding the TCP Header:\n");
if(len >= (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + TCPHDRSIZ)) {
tcph = (struct tcphdr *)(packet + p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ);
printf("Source Port: %d\n", ntohs(tcph->th_sport));
printf("Destination Port: %d\n", ntohs(tcph->th_dport));
printf("\n");
/* call the data parsing function */
parse_tcp_data(fbuff, len);
}
else
printf("the tcp packet is too short\n");
}
void
parse_udp_header(char *packet, int len)
{
struct udphdr *udph;
printf("Decoding the UDP Header:\n");
if(len >= (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + UDPHDRSIZ)) {
udph = (struct udphdr *)(packet + p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ);
printf("Source Port: %d\n", ntohs(udph->uh_sport));
printf("Destination Port: %d\n", ntohs(udph->uh_dport));
printf("\n");
/* call the data parsing function */
parse_udp_data(fbuff, len);
}
else
printf("the udp packet is too short\n");
}
void
parse_tcp_data(char *packet, int len)
{
char *data_packet;
int data_length;
struct ip *ip_h;
printf("Decoding the Data:\n");
/* is there data in the packet ? */
if(len > (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + TCPHDRSIZ)) {
ip_h = (struct ip *)(packet + p->bh_hdrlen + ETHHDRSIZ);
data_packet = (char *)(packet + p->bh_hdrlen + ETHHDRSIZ + ip_h->ip_hl*4 + TCPHDRSIZ);
data_length = ntohs(ip_h->ip_len) - (ip_h->ip_hl*4 + TCPHDRSIZ);
while(data_length--) {
/* print it */
printf("%c", *data_packet);
data_packet++;
}
}
else
printf("no data in the tcp packet\n");
}
void
parse_udp_data(char *packet, int len)
{
char *data_packet;
int packet_length;
struct ip *ip_h;
printf("Decoding the Data:\n");
/* is there data in the packet ? */
if(len > (p->bh_hdrlen + ETHHDRSIZ + IPHDRSIZ + UDPHDRSIZ)) {
ip_h = (struct ip *)(packet + p->bh_hdrlen + ETHHDRSIZ);
data_packet = (packet + p->bh_hdrlen + ETHHDRSIZ + ip_h->ip_hl*4 + UDPHDRSIZ);
packet_length = ntohs(ip_h->ip_len) - (ip_h->ip_hl*4 + UDPHDRSIZ);
while(packet_length--) {
/* print it */
printf("%c", *data_packet);
data_packet++;
}
}
else
printf("no data in the udp packet\n");
}
int
main(int argc, char **argv)
{
int ch, num, buflen, counts, packetcount = 0;
char file_path[100];
char *cap_file = NULL, *geten;
struct ifreq ifrq;
if(getuid() != 0) {
printf("sorry, you need to be root to run this program\n");
return(1);
}
eflag = cflag = nflag = sflag = pflag = xflag = oneflag = twoflag = 0;
threeflag = 1;
while((ch = getopt(argc, argv, OPTS)) != -1)
switch(ch) {
case '1':
oneflag = 1;
threeflag = 0;
break;
case '2':
twoflag = 1;
threeflag = 0;
break;
case 'e':
eflag = 1;
break;
case 'c':
cap_file = optarg;
cflag = 1;
if((geten = getenv("HOME")) == NULL) {
printf("couldn't determine the users HOME environment variable\n");
return(1);
}
sprintf(file_path,"%s/%s", geten, cap_file);
if((fp = fopen(file_path, "r+")) == NULL) {
printf("couldn't create the capture file\n");
return(1);
}
break;
case 'n':
counts = atoi(optarg);
nflag = 1;
if((counts < 0) || (counts == 0)) {
printf("you cannot capture %d packets\n", counts);
return(1);
}
break;
case 's':
sflag = 1;
break;
case 'p':
pflag = 1;
break;
case 'h':
printhelp();
return(0);
break;
case 'x':
xflag = 1;
break;
case '?':
default:
fputs(USAGE, stderr);
return(1);
}
argc -= optind;
argv += optind;
if(argc != 1) {
fputs(USAGE, stderr);
return(1);
}
/* zero the interface request structure */
bzero(&ifrq, sizeof(ifrq));
bzero(&bpf_sta, sizeof(bpf_sta));
/* open /dev/bpfx */
if((pfd = open("/dev/bpf0", O_RDWR)) < 0) {
printf("couldn't open /dev/bpf\n");
return(1);
}
/* bind the bpf the to interface with the BIOCSETIF ioctl */
strncpy(ifrq.ifr_name, argv[0], IFNAMSIZ);
if(ioctl(pfd, BIOCSETIF, &ifrq) < 0) {
printf("the BIOCSETIF ioctl failed\n");
return(1);
}
/* get the required buffer length for the bpf */
if(ioctl(pfd, BIOCGBLEN, &buflen) < 0) {
printf("the BIOCGBLEN ioctl failed\n");
return(1);
}
/* force the interface into promiscuous mode */
if(!pflag)
if(ioctl(pfd, BIOCPROMISC) < 0) {
printf("the BIOCPROMISC ioctl failed");
return(1);
}
/*
* turn on immediate reading
* num being one means turn BIOCIMMEDIATE on
* num being two means turn BIOCIMMEDIATE off
*/
num = 1;
if(ioctl(pfd, BIOCIMMEDIATE, &num) < 0) {
printf("the BIOCIMMEDIATE ioctl failed\n");
return(1);
}
/*
* Get the interface which the bpf is listening on.
* Actually this is not necessary as the interface has
* been specifically set with the BIOCSETIF ioctl.
* We'll do it anyways. Sorta like a safety check.
*/
if(ioctl(pfd, BIOCGETIF, &ifrq) < 0) {
printf("the BIOCGETIF ioctl has failed\n");
return(1);
}
printf("listening on %s\n\n", ifrq.ifr_name);
/* printf("buffer length %d\n\n", buflen); */
fbuff = (char *)malloc(buflen);
bzero(fbuff, buflen);
/* set our signal handlers */
(void)signal(SIGINT, exit_handler);
(void)signal(SIGTERM, exit_handler);
/* now we are able to read from the bpf */
while((n = read(pfd, fbuff, buflen)) > 0) {
printf("\n\n---------- New Packet Captured ----------\n");
printf("Packet %d\n", ++packetcount);
printf("Number of bytes read %d\n", n);
if(cflag) {
fprintf(fp, "\n\n---------- New Packet Captured ----------\n");
fprintf(fp, "Number of bytes read %d\n", n);
}
if(xflag)
printpacketinhex(fbuff, n);
if(sflag)
prntbpfstats(fbuff);
else
printf("\n");
p = (struct bpf_hdr *)fbuff;
/* parse the ethernet header and get the protocol type */
if(!eflag)
parse_eth_header(fbuff, n);
/*
* parse the ip header, determine the type of protocol
* and call the appropriate transport layer function
*/
parse_ip_header(fbuff, n);
if(nflag) {
counts--;
if(!counts)
break;
}
} /* while() */
exit_handler();
/* not reached */
return(0);
}
void
exit_handler()
{
if((ioctl(pfd, BIOCGSTATS, &bpf_sta)) < 0) {
printf("couldn't get bpf statistics\n");
exit(1);
}
printf("\n");
printf("Capture Statistics:\n");
printf("number of packets received: %d\n", bpf_sta.bs_recv);
printf("number of packets dropped: %d\n", bpf_sta.bs_drop);
if(cflag) {
fprintf(fp, "number of packets received: %d\n", bpf_sta.bs_recv);
fprintf(fp, "number of packets dropped: %d\n", bpf_sta.bs_drop);
}
/* fflush */
close(pfd);
if(cflag)
fclose(fp);
exit(0);
}
To compile and link, simply issue:
% gcc -o easysniff easysniff.c
on the command line.
And run it with:
% ./easysniff -s -n 5 rl0
to capture 5 packets on the interface "rl0" and additionally print the BPF statistics.
Here is a gzipped tarball of the easysniff source code:
Version 1.0:
File: easysniff-1.0.tar.gz
sha256sum: 2974a55451254a78e754a85c419dfb281093a4564e1ba3d3b620d695ffaf3bbf