Annotated Code - Adapter.cpp - Using libpcap
The following code is from the original RIPPS prototype. It drew upon the
CheapLogger code as well. Notable demonstrations of functionality from libpcap include reading packets, writing packets, and interacting with the timestamp.
Quick Links
/* Adapter.cpp
**********************************************************
* This code is part of the RIPPS (Rogue Identifying
* Packet Payload Slicer) system developed at the University
* of Notre Dame.
*
* PI: Dr. Aaron Striegel striegel@nd.edu
* Students: Chad Mano (lead) Yingxin Jiang
* Dave Salyers Dave Cieslak
* Qi Liao Andrew Blaich
**********************************************************
* Current Version: $Revision: 1.2 $ $Date: 2008/05/08 18:48:44 $
* checked in by $Author: AaronStriegel $
**********************************************************
*/
#include <iostream>
using namespace std;
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread_Archive.h"
#include "Adapter.h"
#include "Thread_Input.h"
#include "Thread_Output.h"
#include "Input.h"
#include "MemoryPool.h"
#include "em_proto.h"
#include "Utils.h"
#include "Monitor.h"
Callback Function
The function listed below is for callback from libpcap. In short, one provides a
callback function that libpcap then invokes when a packet has been received. For those of your familiar with interrupt service routines, the code will be quite similar. If your are less than familiar with callback routines, consider the following:
- When you call a function, you typically list myFunction() to call it.
- If you leave off the parentheses, the name of the function is an address much like when you leave off the brackets when accessing a pointer. The address of the function is a pointer, just like a normal pointer in the code. The trick is the arguments, i.e. how the function gets called.
- For example, you could write in your code:
void * pPointer;
pPointer = (void *) pkt_callback;
While the above discussion is not really necessary to understand what the function is doing, let's continue by decomposing the function.
- The adapter (an object in the RIPPS code encapsulating a libpcap-based adapter) is passed in as "argument" in the args pointer. Remember, a pointer is a pointer is a pointer and we can cast it however we see fit. The passing in of the adapter object allows the callback function to put the packet somewhere (i.e. in the queue of the adapter).
- A placeholder object is retrieved from the memory pool. Remember, dynamic memory allocation in global memory is expensive (malloc, new, etc.). Allocate once and then re-use the object or use a local variable.
- Check to make sure that libpcap really got the entire packet. I have yet to see this error ever in practice.
- Grab the timestamp from the libpcap struct. Note that libpcap stamps it when it receives it, not when we process it in our callback function. For all practical purposes, these numbers should be very similar.
- Copy (ugh) the entirety of the packet into another array. This is unfortunately a necessary evil unless we want to do all of our processing in the callback function. As RIPPS uses producer/consumer queues and slices packets, it goes for paying the performance penalty in exchange for easier programming.
- Check the packet to make sure it did not come from this host. This step is largely optional as libcap will detect application-level packets coming down as well.
void pkt_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
Adapter * pAdapter;
PacketHolder * pPacket;
pAdapter = (Adapter *) args;
pPacket = g_MemPool.getPacket();
if(pPacket == NULL) {
cerr << "Packet callback failed, no valid packet in the memory pool" << endl;
return;
}
if(header->caplen != header->len) {
cerr << "Warning: libpcap burped and didn't get us the whole packet, bailing on this packet" << endl;
pAdapter->addInputPacket(NULL);
g_MemPool.releasePacket(pPacket);
g_theMonitor.addStat(MONITOR_STAT_PCAP_ERR,1);
return;
}
// For now, just do a full memory copy and eat it for simplicity until the code is modded
memcpy(&pPacket->capTime, &header->ts, sizeof(struct timeval));
memcpy(pPacket->byData, packet, header->caplen);
pPacket->nLength = header->caplen;
pPacket->nCapAdp = pAdapter->getLocation();
//cout << " Rcvd a packet of " << header->caplen << " bytes on " << pAdapter->getDevName() << "!" << endl;
if(memcmp(pPacket->byData+OFFSET_ETH_SRCMAC, pAdapter->getMAC(), 6) == 0) {
// This packet came from us!
//cout << " Self!" << endl;
pAdapter->addInputPacket(NULL);
g_MemPool.releasePacket(pPacket);
}
else {
//pPacket->dumpPacketParse();
//g_theArchive.archiveString("R Pkt: ");
//g_theArchive.archivePacket(pPacket);
pAdapter->addInputPacket(pPacket);
//cout << " Pkt Arr: " << pPacket->capTime.tv_sec << " s, " << pPacket->capTime.tv_usec << endl;
}
}
Adapter::Adapter () {
strncpy(m_szDevName, "UNKNOWN",PCAP_DEVICE_NAME_LEN);
m_nLocation = LOCATION_UNDEFINED;
m_pDevice = NULL;
m_bRunning = 1;
m_pLinkAdapter = NULL;
m_pInput = NULL;
m_pInputPacket = NULL;
}
Adapter::~Adapter () {
}
ether_addr * Adapter::getMAC () {
return &m_MAC;
}
void Adapter::dumpInfo () {
int j;
cout << "Adapter: " << m_szDevName << endl;
printf(" MAC: ");
for(j=0; j<6; j++) {
printf("%02X", (unsigned char) m_MAC.ether_addr_octet[j]);
}
printf(" IP: ");
dumpIPv4(m_byIP);
cout << endl;
cout << " Out Queue: " << getOutQueueSize() << " (RQ: " << m_OutBuffer.size() << ")" << endl;
printf("\n");
}
void Adapter::addInputPacket (PacketHolder * pPacket) {
//pthread_mutex_lock(&m_MutexInBuf);
m_pInputPacket = pPacket;
//pthread_mutex_unlock(&m_MutexInBuf);
}
Opening the Lipbcap Device
The openDevice functions covers much of the base functionality of opening a libpcap device. Annotations are included in-line with the code.
int Adapter::openDevice () {
char errbuf[PCAP_ERRBUF_SIZE];
int nResult;
The m_szDevName variable contains the C-style string of the name of the device (eth1, bge0, etc.). The name itself MUST correspond to an adapter on the machine that is visible via
ifconfig.
if(strcmp(m_szDevName, "UNKNOWN") == 0) {
cerr << "No name specified for adapter, exiting...." << endl;
exit(-1);
}
cout << "Device Name: " << m_szDevName << endl;
RIPPS employs several mutexes (mutual exclusion protection) for the input and output buffers. In short, a mutex allows a specific operation to be made atomic such that a given sequence of commands will be thread-safe. RIPPS employs the pthread mutex.
// Initialize mutex on output buffer
pthread_mutex_init(&m_MutexOutBuf, NULL);
pthread_mutex_init(&m_MutexInBuf, NULL);
The libpcap device is opened for live (i.e. running right now, not reading from a file) operation.
Update (May 2008): Do not use BUFSIZ, use your own define value as this can vary on non-Linux systems. Hack away though if you are on Linux.
m_pDevice = pcap_open_live(m_szDevName,BUFSIZ,1,1,errbuf);
if(m_pDevice == NULL) {
cerr << "Libpcap open live device failed for " << m_szDevName << endl;
cerr << " " << errbuf << endl;
return -1;
}
cout << "Opened up device " << m_szDevName << " successfully..." << endl;
Support for setting the device into non-blocking mode (i.e. a call to read a packet does not block until it gets one) was problematic depending upon the system employed.
/* if(pcap_setnonblock(m_pDevice, 1, errbuf) <= 0) {
cerr << "Unable to set " << m_szDevName << " into non-blocking mode" << endl;
cerr << " Error is " << errbuf << endl;
pcap_perror(m_pDevice, errbuf);
cerr << "Try #2: " << errbuf << endl;
exit(-1);
} */
find_my_address(pcap_fileno(m_pDevice), m_szDevName, &m_MAC);
// Start up the output thread
nResult = pthread_create(&m_ThreadOutput, NULL, Thread_Output, (void *) this);
if(nResult) {
cerr << "* Error creating output thread for adapter " << m_szDevName << endl;
cerr << " Code: " << nResult << endl;
exit(-1);
}
cout << " Output thread started for " << m_szDevName << endl;
// Start up the input thread
nResult = pthread_create(&m_ThreadInput, NULL, Thread_Input, this);
if(nResult) {
cerr << "* Error creating output thread for adapter " << m_szDevName << endl;
cerr << " Code: " << nResult << endl;
exit(-1);
}
cout << " Input thread started for " << m_szDevName << endl;
return 0;
}
int Adapter::writePacket (PacketHolder * pPacket) {
int nBytes;
char bCallback;
bCallback = 0;
//pPacket->dumpPacketParse();
//sleep(5);
if(g_theMonitor.getDebug_ShowWrite()) {
cout << "Output adapter " << m_szDevName << " write ( " << pPacket->nLength << " bytes)" << endl;
}
//dumpInfo();
//pPacket->dumpPacketParse();
//sleep(1);
if(pPacket->bCallBack) {
// Get the system time and re-apply it as the capture time
struct timeval curTime;
// What is the current system time?
gettimeofday(&curTime, NULL);
pPacket->capTime = curTime;
pPacket->doCallback();
bCallback = 1;
}
nBytes = write(pcap_fileno(m_pDevice), pPacket->byData, pPacket->nLength);
//g_theArchive.archiveString("W Pkt: ");
//g_theArchive.archivePacket(pPacket);
if(bCallback) {
//cout << " Callback initiated" << endl;
}
//cout << "Write complete!" << endl;
return nBytes;
}
InterfaceInput * Adapter::getInterfaceInput () {
return m_pInput;
}
void Adapter::setInterfaceInput (InterfaceInput * pInput) {
m_pInput = pInput;
if(m_pInput != NULL) {
m_pInput->setAdapter(this);
}
}
char * Adapter::getDevName () {
return m_szDevName;
}
void Adapter::setDevName (char * pDevName) {
cout << "Setting device name to " << pDevName << endl;
strncpy(m_szDevName, pDevName,PCAP_DEVICE_NAME_LEN);
}
char Adapter::getLocation () {
return m_nLocation;
}
void Adapter::setLocation (char bLoc) {
m_nLocation = bLoc;
}
int Adapter::getPacket (PacketHolder ** ppPacket) {
if(m_pInput == NULL) {
cerr << "Warning: Input interface not yet enabled for adapter " << m_szDevName << endl;
return -1;
}
if(ppPacket == NULL) {
cerr << "Error: Packet pointer was NULL in Adapter::getPacket" << endl;
return -2;
}
//pcap_loop(m_pDevice,1,pkt_callback,(u_char *) this);
pcap_dispatch(m_pDevice,1,pkt_callback,(u_char *) this);
if(m_pInputPacket != NULL) {
*ppPacket = m_pInputPacket;
if(m_nLocation == LOCATION_EXTERNAL && g_theMonitor.getDebug_ShowRead_External()) {
cout << m_szDevName << " ";
(*ppPacket)->dumpBrief();
} else if (m_nLocation == LOCATION_MONITOR && g_theMonitor.getDebug_ShowRead_Internal()) {
cout << m_szDevName << " ";
(*ppPacket)->dumpBrief();
}
return 0;
} else {
*ppPacket = NULL;
return -1;
}
}
void Adapter::addOutPacket (PacketHolder * pPacket) {
pthread_mutex_lock(&m_MutexOutBuf);
if(pPacket->bRestrictedRelease) {
if(g_theMonitor.getSliceDelay() > 0) {
pushShapeBuffer(pPacket);
} else {
m_OutBuffer.push_back(pPacket);
}
} else {
m_OutBuffer.push_back(pPacket);
}
pthread_mutex_unlock(&m_MutexOutBuf);
}
void Adapter::pushShapeBuffer (PacketHolder * pPacket) {
m_ShapeBuffer.push_back(pPacket);
}
char Adapter::releaseShapeBuffer () {
struct timeval curTime;
int j;
vector<PacketHolder *>::iterator theIt;
PacketHolder * pPacket;
char bRelease = 0;
pthread_mutex_lock(&m_MutexOutBuf);
// What is the current system time?
gettimeofday(&curTime, NULL);
while(m_ShapeBuffer.size() > 0) {
theIt = m_ShapeBuffer.begin();
pPacket = *(theIt);
//if(m_ShapeBuffer.size() > 1) {
// cout << "Attempting to release the shape buffer (" << m_ShapeBuffer.size() << " pkts)" << endl;
// cout << " Cur Time: " << curTime.tv_sec << "." << curTime.tv_usec << endl;
// cout << " 1st packet has time of " << pPacket->releaseTime.tv_sec << "." << pPacket->releaseTime.tv_usec << endl;
//}
if(pPacket->canRelease(&curTime)) {
//if(m_ShapeBuffer.size() > 1) {
// cout << " Shape buffer release" << endl;
//}
pPacket->clearRestrict();
pPacket->capTime = curTime;
m_OutBuffer.push_back(pPacket);
m_ShapeBuffer.erase(theIt);
bRelease = 1;
// Should this be limited to one packet per iteration of the thread such that we don't
// stamp a bunch enqueue with the wrong release time
if(g_theMonitor.getLimitShapeRelease()) {
// Limiting release to one per packet
break;
}
} else {
break;
}
}
pthread_mutex_unlock(&m_MutexOutBuf);
return bRelease;
}
PacketHolder * Adapter::getNextOutPacket () {
PacketHolder * pPacket;
vector<PacketHolder *>::iterator theIt;
pthread_mutex_lock(&m_MutexOutBuf);
if(m_OutBuffer.size() > 0) {
theIt = m_OutBuffer.begin();
pPacket = *(theIt);
m_OutBuffer.erase(m_OutBuffer.begin());
pthread_mutex_unlock(&m_MutexOutBuf);
// Return the first packet in the queue
return pPacket;
} else {
pthread_mutex_unlock(&m_MutexOutBuf);
return NULL;
}
}
void Adapter::setIP (char * pIP, int nLength) {
if(nLength > DEFAULT_IP_LEN) {
cerr << " Unable to set IP address to something more than IPv4 (4 bytes) " << endl;
return;
}
if(nLength < DEFAULT_IP_LEN) {
cerr << " Bad length of IPv4 address, less than 4 bytes" << endl;
cerr << " Length = " << nLength << endl;
return;
}
memcpy(m_byIP, pIP, DEFAULT_IP_LEN);
m_nIPLength = DEFAULT_IP_LEN;
}
int Adapter::getOutQueueSize () {
int nQueue;
nQueue = m_OutBuffer.size() + m_ShapeBuffer.size();
return nQueue;
}
void Adapter::checkOutReleaseShape () {
}
void Adapter::setRunning (char bRunning) {
m_bRunning = bRunning;
}
char Adapter::isRunning () {
return m_bRunning;
}
void Adapter::linkAdapter (Adapter * pAdapter) {
m_pLinkAdapter = pAdapter;
if(m_pInput != NULL) {
m_pInput->linkAdapter(m_pLinkAdapter);
}
}
void Adapter::stampSrcMAC (PacketHolder * pPacket) {
memcpy(pPacket->byData+OFFSET_ETH_SRCMAC, m_MAC.ether_addr_octet, ETH_MAC_LENGTH);
}
void Adapter::dumpARP () {
int j;
ARPEntry * pEntry;
cout << "ARP Table for Adapter " << m_szDevName << endl;
cout << " " << m_ARPTable.size() << " entries" << endl;
for(j=0; j<m_ARPTable.size(); j++) {
pEntry = m_ARPTable[j];
printf(" %3d ", j);
if(pEntry->m_bResolved) {
cout << "R ";
} else {
cout << "P ";
}
dumpIPv4(pEntry->m_byIP);
cout << " ";
if(pEntry->m_bResolved) {
dumpHex(pEntry->m_byMAC, ':', 6);
}
cout << endl;
}
}
ARPEntry * Adapter::findARP (char * pIP) {
int j;
ARPEntry * pEntry;
//cout << " Searching ARP table for IP address ";
//dumpIPv4(pIP);
//cout << endl;
for(j=0; j<m_ARPTable.size(); j++) {
pEntry = m_ARPTable[j];
if(memcmp(pEntry->m_byIP, pIP, DEFAULT_IP_LEN) == 0) {
return pEntry;
}
}
return NULL;
}
void Adapter::sendARP (ARPEntry * pARP) {
PacketHolder * pHolder;
// Get a fresh packet
pHolder = g_MemPool.getPacket();
// Stamp the destination address with the broadcast address
memset(pHolder->byData+OFFSET_ETH_DSTMAC, 0xFF, DEFAULT_MAC_LEN);
// Stamp the source as us
stampSrcMAC(pHolder);
pHolder->byData[OFFSET_ETH_TYPELEN+0] = ETH_TYPELEN_ARP_B1;
pHolder->byData[OFFSET_ETH_TYPELEN+1] = ETH_TYPELEN_ARP_B2;
// Stamp the packet now with pre-defined fields
pHolder->byData[L2_OFFSET+0] = 0x00;
pHolder->byData[L2_OFFSET+1] = 0x01;
pHolder->byData[L2_OFFSET+2] = ETH_TYPELEN_IP_B1;
pHolder->byData[L2_OFFSET+3] = ETH_TYPELEN_IP_B2;
pHolder->byData[L2_OFFSET+4] = DEFAULT_MAC_LEN;
pHolder->byData[L2_OFFSET+5] = DEFAULT_IP_LEN;
pHolder->byData[L2_OFFSET+6] = 0x00;
pHolder->byData[L2_OFFSET+7] = 0x01;
// OK, add in our info
// Our Src MAC address
memcpy(pHolder->byData+L2_OFFSET+8, m_MAC.ether_addr_octet, DEFAULT_MAC_LEN);
// Our adapter IP address
memcpy(pHolder->byData+L2_OFFSET+8+DEFAULT_MAC_LEN, m_byIP, DEFAULT_IP_LEN);
// Don't know this field, this is the field we will get our response in
memset(pHolder->byData+L2_OFFSET+8+DEFAULT_MAC_LEN+DEFAULT_IP_LEN, 0, DEFAULT_MAC_LEN);
// What IP do we want?
memcpy(pHolder->byData+L2_OFFSET+8+DEFAULT_MAC_LEN+DEFAULT_IP_LEN+DEFAULT_MAC_LEN, pARP->m_byIP, DEFAULT_IP_LEN);
pHolder->nLength = L2_OFFSET+8+DEFAULT_MAC_LEN*2+DEFAULT_IP_LEN*2;
//cout << "Sent ARP packet breakdown on adapter " << m_szDevName << "!" << endl;
//dumpParseARP(pHolder->byData);
//cout << "-> Raw Packet Breakdown" << endl;
//pHolder->dumpPacketParse();
writePacket(pHolder);
g_MemPool.releasePacket(pHolder);
}
void Adapter::processARP (PacketHolder * pPacket) {
//
//cout << "*************************" << endl;
//cout << "Processing an ARP response" << endl;
//pPacket->dumpPacketParse();
// Double check a few of the fields just to be sure
if(pPacket->byData[L2_OFFSET+0] != 0x00 || pPacket->byData[L2_OFFSET+1] != 0x01) {
cout << " Ignoring ARP - unknown H/W type" << endl;
g_MemPool.releasePacket(pPacket);
return;
}
if(pPacket->byData[L2_OFFSET+2] != ETH_TYPELEN_IP_B1 || pPacket->byData[L2_OFFSET+3] != ETH_TYPELEN_IP_B2) {
cout << " Ignoring ARP - unknown protocol type" << endl;
g_MemPool.releasePacket(pPacket);
return;
}
if(pPacket->byData[L2_OFFSET+4] != DEFAULT_MAC_LEN) {
cout << " Ignoring ARP - MAC address is not 6 bytes" << endl;
g_MemPool.releasePacket(pPacket);
return;
}
if(pPacket->byData[L2_OFFSET+5] != DEFAULT_IP_LEN) {
cout << " Ignoring ARP - IP address is not 4 bytes" << endl;
g_MemPool.releasePacket(pPacket);
return;
}
if(pPacket->byData[L2_OFFSET+6] != 0x00 || pPacket->byData[L2_OFFSET+7] != 0x02) {
// Silently discard this, it is likely an ARP request which we don't care about
//cout << " Ignoring request ARP" << endl;
//printf(" Op Field: %02X\n", pPacket->byData[L2_OFFSET+7]);
//pPacket->dumpPacketParse();
//dumpParseARP(pPacket->byData);
g_MemPool.releasePacket(pPacket);
return;
}
/*
// Whoot, if we got this far, we should be good
cout << " Breaking down the ARP" << endl;
cout << " Src MAC: ";
dumpHex(pPacket->byData+L2_OFFSET+8, ':', DEFAULT_MAC_LEN);
cout << endl;
cout << " Src IP: ";
dumpIPv4(pPacket->byData+8+L2_OFFSET+DEFAULT_MAC_LEN);
cout << endl;
cout << " Dst MAC: ";
dumpHex(pPacket->byData+8+L2_OFFSET+DEFAULT_MAC_LEN+DEFAULT_IP_LEN, ':', DEFAULT_MAC_LEN);
cout << endl;
cout << " Dst IP: ";
dumpIPv4(pPacket->byData+8+L2_OFFSET+DEFAULT_MAC_LEN*2+DEFAULT_IP_LEN);
cout << endl;
*/
ARPEntry * pEntry;
pEntry = findARP(pPacket->byData+8+L2_OFFSET+DEFAULT_MAC_LEN);
if(pEntry == NULL) {
cout << "Error - no entry exists in the ARP table despite us being the one" << endl;
cout << " that requested it. Hmmmmm" << endl;
g_MemPool.releasePacket(pPacket);
return;
}
memcpy(pEntry->m_byMAC, pPacket->byData+L2_OFFSET+8, DEFAULT_MAC_LEN);
pEntry->m_bResolved = 1;
// Need to dispatch pending packets too
PacketHolder * pARPQueue;
unsigned int k;
for(k=0; k<pEntry->m_Pending.size(); k++) {
pARPQueue = pEntry->m_Pending[k];
if(!routePacket(pARPQueue)) {
// Gobble it and silently discard
g_MemPool.releasePacket(pARPQueue);
continue;
} else {
writePacket(pARPQueue);
g_MemPool.releasePacket(pARPQueue);
}
}
pEntry->m_Pending.clear();
g_MemPool.releasePacket(pPacket);
}
// 0 means we gobbled it, 1 means it is yours to take
char Adapter::routePacket (PacketHolder * pPacket) {
// If we are in pass through, our life is extremly simple
if(!g_theMonitor.shouldRoutePackets()) {
return 1;
}
// In routing mode, if so do lots of things
stampSrcMAC(pPacket);
// If the packet is not IP, squelch it entirely
if(isPacketIP(pPacket->byData, pPacket->nLength) == 0) {
cout << "Squelching non-IP packet" << endl;
cout.flush();
//pPacket->dumpPacketParse();
// Make sure it is not an ARP response packet
if(isPacketARP (pPacket->byData, pPacket->nLength) == 0) {
// Is it coming to us?
if(memcmp(pPacket->byData+OFFSET_ETH_DSTMAC, m_MAC.ether_addr_octet, DEFAULT_MAC_LEN) == 0) {
processARP(pPacket);
return 0;
} else {
// Nope, just ignore it as we weren't the source
}
}
// Eat the packet, mmmm P is for packet!
g_MemPool.releasePacket(pPacket);
return 0;
}
// OK, let's see if the destination is local (1 hop) or will be reached via the gateway (not local)
// It is local
// See if we have this in our local ARP cache (yes, it is redundant but hey, what can you do)?
ARPEntry * pARP;
pARP = findARP (pPacket->byData+L2_OFFSET+OFFSET_IPV4_PROTOCOL_DSTIP);
if(pARP == NULL) {
//cout << " Need to ARP for this one!" << endl;
// Gobble it into our own personal queue and send out an ARP request
// Create a new ARP entry
pARP = new ARPEntry();
memcpy(pARP->m_byIP, pPacket->byData+L2_OFFSET+OFFSET_IPV4_PROTOCOL_DSTIP, DEFAULT_IP_LEN);
pARP->m_nIPLength = DEFAULT_IP_LEN;
pARP->m_bResolved = 0;
m_ARPTable.push_back(pARP);
// Add this to a queue of pending ARP requests
pARP->m_Pending.push_back(pPacket);
// Send out an ARP for that address
sendARP (pARP);
return 0;
} else if(!pARP->m_bResolved) {
// Waiting on a response, gobble it
pARP->m_Pending.push_back(pPacket);
return 0;
} else {
// Got it, stamp that puppy with a destination address
memcpy(pPacket->byData+OFFSET_ETH_DSTMAC, pARP->m_byMAC, ETH_MAC_LENGTH);
return 1;
}
}