Jan 2009
In the previous two texts reading and manipulating packets with
libpcap 1 and 2; a simple packet reader and handler was
constructed leveraging the libpcap library. In this the final
part of the series the full source from the examples is
provided along with a simple Makefile plus a small
bonus, a rudimentry packet injector.
MakefileNote that the injector target is included:
CC=gcc
LIBS=-lpcap
BINS=nject nread
all: Linux
Linux linux:
${CC} -DLINUX nject.c ${LIBS} -o nject
${CC} -DLINUX nread.c ${LIBS} -o nread
osx osX OSX FreeBSD freebsd:
${CC} -DFREEBSD nject.c ${LIBS} -o nject
${CC} -DFREEBSD nread.c ${LIBS} -o nread
clean:
rm -f a.out ${BINS}
pkt.h
#ifndef _PKT_H
#define _PKT_H
#define _BSD_SOURCE 1
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#ifdef LINUX
#include <netinet/ether.h>
#endif
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <fcntl.h>
#include <getopt.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <pcap.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
struct nread_ip {
u_int8_t ip_vhl; /* header length, version */
#define IP_V(ip) (((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip) ((ip)->ip_vhl & 0x0f)
u_int8_t ip_tos; /* type of service */
u_int16_t ip_len; /* total length */
u_int16_t ip_id; /* identification */
u_int16_t ip_off; /* fragment offset field */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_int16_t ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
};
struct nread_tcp {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2:4, /* (unused) */
th_off:4; /* data offset */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off:4, /* data offset */
th_x2:4; /* (unused) */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
u_short th_win; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
};
#endif
nread.c
#include "pkt.h"
static short int eflag; /* Ethernet flag */
static short int vflag; /* Verbosity flag */
/*
* copy_argv - Copy the rest of an argument string into a new buffer for
* processing.
*/
char * copy_argv (char **argv)
{
char **p;
u_int len = 0;
char *buf;
char *src, *dst;
p = argv;
if (*p == 0)
return 0;
while (*p)
len += strlen(*p++) + 1;
buf = (char *)malloc(len);
if (buf == NULL) {
fprintf(stdout,"copy_argv: malloc");
exit (1);
}
p = argv;
dst = buf;
while ((src = *p++) != NULL) {
while ((*dst++ = *src++) != '\0')
;
dst[-1] = ' ';
}
dst[-1] = '\0';
return buf;
}
/* Ethernet Handler */
u_int16_t ethernet_handler (u_char *args, const struct pcap_pkthdr* pkthdr,
const u_char* packet)
{
u_int caplen = pkthdr->caplen; /* length of portion present from bpf */
u_int length = pkthdr->len; /* length of this packet off the wire */
struct ether_header *eptr; /* net/ethernet.h */
u_short ether_type; /* the type of packet (we return this) */
eptr = (struct ether_header *) packet;
ether_type = ntohs(eptr->ether_type);
if (caplen < 14) {
fprintf(stderr,"Packet length is less than header length\n");
return -1;
}
if (eflag) {
fprintf(stdout,"eth: ");
fprintf(stdout,
"%s ",ether_ntoa((struct ether_addr*)eptr->ether_shost));
fprintf(stdout,
"%s ",ether_ntoa((struct ether_addr*)eptr->ether_dhost));
/* get type and use as the beginning of the message line */
if (ether_type == ETHERTYPE_IP) {
fprintf(stdout,"(ip)");
} else if (ether_type == ETHERTYPE_ARP) {
fprintf(stdout,"(arp)");
} else if (eptr->ether_type == ETHERTYPE_REVARP) {
fprintf(stdout,"(rarp)");
} else {
fprintf(stdout,"(?)");
}
}
return ether_type;
}
/* IP Handler */
u_char* ip_handler (u_char *args,const struct pcap_pkthdr* pkthdr,
const u_char* packet)
{
const struct nread_ip* ip; /* packet structure */
const struct nread_tcp* tcp; /* tcp structure */
u_int length = pkthdr->len; /* packet header length */
u_int hlen, off, version; /* offset, version */
int len; /* length holder */
ip = (struct nread_ip*)(packet + sizeof(struct ether_header));
length -= sizeof(struct ether_header);
tcp = (struct nread_tcp*)(packet + sizeof(struct ether_header) +
sizeof(struct nread_ip));
hlen = IP_HL(ip); /* get header length */
len = ntohs(ip->ip_len); /* get packer length */
version = IP_V(ip); /* get ip version */
if (hlen < 5 ) {
fprintf(stderr,"Alert: %s bad header length %d\n",
inet_ntoa(ip->ip);
}
if (length < len)
fprintf(stderr,"Alert: %s truncated %d bytes missing.\n");
off = ntohs(ip->ip_off);
if ((off & 0x1fff) == 0 ) { /* aka no 1's in first 13 bits */
if (vflag > 3)
fprintf(stdout,"ip: ");
if (vflag > 0)
fprintf(stdout,"%s:%u->%s:%u ",
inet_ntoa(ip->ip_src), tcp->th_sport,
inet_ntoa(ip->ip_dst), tcp->th_dport);
if (vflag > 1)
fprintf(stdout,
"tos %u len %u off %u ttl %u prot %u cksum %u ",
ip->ip_tos, len, off, ip->ip_ttl,
ip->ip_p, ip->ip_sum);
if (vflag > 2)
fprintf(stdout,"seq %u ack %u win %u ",
tcp->th_seq, tcp->th_ack, tcp->th_win);
if (vflag > 3)
fprintf(stdout,"%s", payload);
if (vflag > 0)
printf("\n");
}
return NULL;
}
/* Callback */
void pcap_callback(u_char *args, const struct pcap_pkthdr* pkthdr,
const u_char* packet)
{
u_int16_t type = ethernet_handler(args, pkthdr, packet);
if (type == ETHERTYPE_IP) {
ip_handler(args, pkthdr, packet);
} else if (type == ETHERTYPE_ARP) {
/* noop */
} else if (type == ETHERTYPE_REVARP) {
/* noop */
}
}
int main (int argc, char **argv)
{
char *oper; /* Filter or Operation */
int npkts; /* Number of polls */
char *dev; /* Device */
dev = NULL;
eflag = 0;
npkts = -1;
oper = NULL;
vflag = 3;
while (1) {
static struct option long_options[] = {
{"ethernet", no_argument, 0, 'e'},
{"interface", required_argument, 0, 'i'},
{"polls", required_argument, 0, 'p'},
{"verbose", required_argument, 0, 'v'},
{0,0,0,0}
};
int option_index = 0;
c = getopt_long (argc, argv, "ei:p:v:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'e':
eflag = 1;
break;
case 'i':
dev = optarg;
break;
case 'p':
npkts = atoi(optarg);
break;
case 'v':
vflag = atoi(optarg);
break;
default:
break;
}
}
if (getuid()) {
printf("Error! Must be root ... exiting\n");
return (1);
}
dev = pcap_lookupdev(errbuf);
if (dev == NULL) {
printf("%s\n", errbuf);
return (1);
}
descr = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
if (descr == NULL) {
printf("pcap_open_live(): %s\n", errbuf);
return (1);
}
pcap_lookupnet(dev, &net, &mask, errbuf);
oper = copu_argv(oper);
if (oper) {
if (pcap_compile(descr, &filter, oper, 0, net) == -1) {
errorlog(opmode, PACKAGE, "Error calling pcap_compile");
exit (1);
}
if (pcap_setfilter(descr, &filter)) {
errorlog(opmode, PACKAGE, "Error setting filter");
exit (1);
}
}
pcap_loop(descr, -1, pcap_callback, args); /* Loop pcap */
return 0;
}
There are few missing items - most notably a usage message which is easy enough to implement.
Following is a very primitive packet injector. It is
just good enough to cause errors to throw using
nread in conjunction. The code is heavily commented
for convienence:
#include "pkt.h"
#define PACKAGE "nject"
int main (int argc, char **argv)
{
char *dev; /* Network Device(s) driver */
pcap_t *descr; /* Session(s) description */
void * contents; /* Data to inject */
int delay; /* Delay in seconds */
int npkts; /* How many packets? */
int i; /* Injector loop counter */
int c; /* Shared getopt counter */
int vflag; /* Verbosity flag */
/* Reasonable Defaults */
contents = NULL;
delay = 0;
dev = NULL;
npkts = 1;
vflag = 0;
descr = NULL;
/*
* Options-
* c -contents A string to cram into the packet header and payload
* d -delay Delay between injections in seconds
* i -interface The device
* p -packets Number of packets to inject
* u -usage Print usage message
* v -verbose Be verbose
*/
while (1) {
static struct option long_options[] = {
{"contents", required_argument, 0, 'c'},
{"delay", required_argument, 0, 'd'},
{"packets", required_argument, 0, 'p'},
{"usage", no_argument, 0, 'u'},
{"verbose", no_argument, 0, 'v'},
{0,0,0,0}
};
int option_index = 0;
c = getopt_long (argc, argv, "c:d:i:p:uv",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'c':
contents = optarg;
break;
case 'd':
delay = atoi(optarg);
break;
case 'i':
dev = optarg;
break;
case 'p':
npkts = atoi(optarg);
break;
case 'u':
usage();
return 0;
break;
case 'v':
vflag = 1;
default:
break;
}
}
/* Only root may fake up data on the interface */
if (getuid()) {
printf("Error! Must be root ... exiting\n");
return (1);
}
if (dev == NULL) {
/* Try to lookup the device if it is not defined */
dev = pcap_lookupdev(errbuf);
if (dev == NULL) {
printf("%s\n", errbuf);
return (1);
}
}
/* Set the pcap description */
descr = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
if (descr == NULL) {
printf("pcap_open_live(): %s\n", errbuf);
return (1);
}
/* Here we loop the injector */
for (i = 1; i <= npkts; i++) {
if (vflag)
printf("Injecting packet %i on %s: %i second delay\n",
i, dev, delay);
pcap_sendpacket (descr, contents, sizeof(contents));
sleep(delay);
}
return 0;
}
/*
* usage - Simple usage print. Prototyped in ntools.h
*/
void usage(void)
{
printf(PACKAGE " [option][arguments]\n"
PACKAGE " [-c|--contents string][-d|--delay seconds]\n"
PACKAGE " [-p|--packets npackets][-u|--usage][-v|--verbose]\n"
"Options:\n"
" -c|--contents string Packet contents.\n"
" -d|--delay seconds Delay between injections.\n"
" -i|--interface dev Use the specified interface.\n"
" -p|--packets number Number of packets to inject.\n"
" -u|--usage Print usage and exit.\n"
" -v|--verbose Be verbose.\n"
);
}
Hopefully this series provides enough of a glimpse into not only how to leverage libpcap but understanding that programmers and hackers alike do not always have to jump through a lot of hoops to get at certain data. If there is a particular system related data set that is needed more than likely someone else has too.