#include <protoString.h>
#include <protoCap.h>
#include <protoNet.h>
#include <protoPktETH.h>
#include <protoPktIP.h>
#include <protoApp.h>
#include <protoDebug.h>
#include <stdio.h>
#include <pcap.h>
#define FRAME_MAX 8192
class PcapReplay : public ProtoApp
{
public:
PcapReplay();
~PcapReplay();
bool OnStartup(int argc, const char*const* argv);
bool ProcessCommands(int argc, const char*const* argv);
void OnShutdown();
private:
enum CmdType {CMD_INVALID, CMD_ARG, CMD_NOARG};
static const char* const CMD_LIST[];
static CmdType GetCmdType(const char* string);
bool OnCommand(const char* cmd, const char* val);
void Usage();
bool ParseAddrPort(const char* string, ProtoAddress& addr);
double ReadFrame();
void OnTxTimeout(ProtoTimer& theTimer);
const char* pcap_file;
ProtoCap* tx_cap;
ProtoTimer tx_timer;
ProtoAddress dst_addr;
ProtoAddress dst_mac;
ProtoAddress src_addr;
pcap_t* pcap_device;
int pcap_link_type;
ProtoTime last_time;
UINT32 aligned_buffer[(FRAME_MAX+4)/4];
void* ip_buffer;
UINT16 ip_length;
};
PROTO_INSTANTIATE_APP(PcapReplay)
PcapReplay::PcapReplay()
: pcap_file(NULL), tx_cap(NULL), pcap_device(NULL), ip_buffer(NULL), ip_length(0)
{
tx_timer.SetListener(this, &PcapReplay::OnTxTimeout);
tx_timer.SetRepeat(-1);
memset(aligned_buffer, 0, FRAME_MAX+4);
}
PcapReplay::~PcapReplay()
{
}
void PcapReplay::Usage()
{
fprintf(stderr, "pcapReplay interface <ifaceName> [input <pcapFile>][dst <addr[/<port>]>][edst <dstMac>][src <addr[/<port>]>]\n");
}
const char* const PcapReplay::CMD_LIST[] =
{
"+input", "+interface", "+dst", "+src", "+edst", NULL
};
PcapReplay::CmdType PcapReplay::GetCmdType(const char* cmd)
{
if (!cmd) return CMD_INVALID;
unsigned int len = strlen(cmd);
bool matched = false;
CmdType type = CMD_INVALID;
const char* const* nextCmd = CMD_LIST;
while (*nextCmd)
{
if (!strncmp(cmd, *nextCmd+1, len))
{
if (matched)
{
return CMD_INVALID;
}
else
{
matched = true;
if ('+' == *nextCmd[0])
type = CMD_ARG;
else
type = CMD_NOARG;
}
}
nextCmd++;
}
return type;
}
bool PcapReplay::ProcessCommands(int argc, const char*const* argv)
{
int i = 1;
while ( i < argc)
{
switch (GetCmdType(argv[i]))
{
case CMD_INVALID:
{
PLOG(PL_ERROR, "PcapReplay::ProcessCommands() Invalid command:%s\n",
argv[i]);
return false;
}
case CMD_NOARG:
if (!OnCommand(argv[i], NULL))
{
PLOG(PL_ERROR, "PcapReplay::ProcessCommands() ProcessCommand(%s) error\n",
argv[i]);
return false;
}
i++;
break;
case CMD_ARG:
if (!OnCommand(argv[i], argv[i+1]))
{
PLOG(PL_ERROR, "PcapReplay::ProcessCommands() ProcessCommand(%s, %s) error\n",
argv[i], argv[i+1]);
return false;
}
i += 2;
break;
}
}
return true;
}
bool PcapReplay::ParseAddrPort(const char* string, ProtoAddress& addr)
{
ProtoTokenator tk(string, '/');
const char* token = tk.GetNextItem();
if (!addr.ResolveFromString(token))
{
PLOG(PL_ERROR, "pcapReplay error: invalid address: %s\n", token);
return false;
}
token = tk.GetNextItem();
if (NULL != token)
{
UINT16 port;
if (1 != sscanf(token, "%hu", &port))
{
PLOG(PL_ERROR, "pcapReplay error: invalid port: %s\n", token);
return false;
}
addr.SetPort(port);
}
else
{
addr.SetPort(0);
}
return true;
}
bool PcapReplay::OnCommand(const char* cmd, const char* val)
{
CmdType type = GetCmdType(cmd);
ASSERT(CMD_INVALID != type);
unsigned int len = strlen(cmd);
if ((CMD_ARG == type) && !val)
{
PLOG(PL_ERROR, "PcapReplay::ProcessCommand(%s) missing argument\n", cmd);
return false;
}
else if (!strncmp("input", cmd, len))
{
pcap_file = val;
}
else if (!strncmp("interface", cmd, len))
{
int ifIndex = ProtoNet::GetInterfaceIndex(val);
char ifName[256];
ifName[255] = '\0';
if (!ProtoNet::GetInterfaceName(ifIndex, ifName, 255))
{
PLOG(PL_ERROR, "pcapReplay: invalid <interfaceName>\n");
return false;
}
if (NULL == tx_cap)
{
if (NULL == (tx_cap = ProtoCap::Create()))
{
PLOG(PL_ERROR, "pcapReplay: new ProtoCap error: %s\n", GetErrorString());
return false;
}
} else if (tx_cap->IsOpen())
{
tx_cap->Close();
}
if (!tx_cap->Open(ifName))
{
PLOG(PL_ERROR, "pcapReplay: ProtoCap::Open() error.\n");
return false;
}
}
else if (!strncmp("dst", cmd, len))
{
if (!ParseAddrPort(val, dst_addr))
{
PLOG(PL_ERROR, "pcapReplay error: invalid destination address: \"%s\"\n", val);
return false;
}
}
else if (!strncmp("edst", cmd, len))
{
if (!dst_mac.ResolveEthFromString(val))
{
PLOG(PL_ERROR, "pcapReplay error: invalid mac address: \"%s\"\n", val);
return false;
}
}
else if (!strncmp("src", cmd, len))
{
if (!ParseAddrPort(val, src_addr))
{
PLOG(PL_ERROR, "pcapReplay error: invalid source address: \"%s\"\n", val);
return false;
}
}
else
{
PLOG(PL_ERROR, "pcapReplay:: invalid command\n");
return false;
}
return true;
}
bool PcapReplay::OnStartup(int argc, const char*const* argv)
{
if (!ProcessCommands(argc, argv))
{
PLOG(PL_ERROR, "protoCapExample: Error! bad command line\n");
return false;
}
if (NULL == tx_cap)
{
PLOG(PL_ERROR, "pcapReplay error: no transmit 'interface' specified?!\n");
Usage();
return false;
}
FILE* infile = stdin; if (NULL != pcap_file)
{
if (NULL == (infile = fopen(pcap_file, "r")))
{
PLOG(PL_ERROR, "pcapReplay error: invalid pcap file \"%s\"\n", pcap_file);
return false;
}
}
char pcapErrBuf[PCAP_ERRBUF_SIZE+1];
pcapErrBuf[PCAP_ERRBUF_SIZE] = '\0';
pcap_device = pcap_fopen_offline(infile, pcapErrBuf);
if (NULL == pcap_device)
{
fprintf(stderr, "pcap2mgen: pcap_fopen_offline() error: %s\n", pcapErrBuf);
if (stdin != infile) fclose(infile);
return false;
}
pcap_link_type = pcap_datalink(pcap_device);
tx_timer.SetInterval(0.0);
ActivateTimer(tx_timer);
return true;
}
double PcapReplay::ReadFrame()
{
pcap_pkthdr hdr;
const u_char* pktData;
if (NULL != (pktData = pcap_next(pcap_device, &hdr)))
{
if (hdr.len > hdr.caplen)
{
PLOG(PL_ERROR, "pcapRelay error: pcap snap length was too small!\n");
return -1.0;
}
unsigned int numBytes = hdr.caplen;
if (numBytes > FRAME_MAX)
{
PLOG(PL_ERROR, "pcapRelay error: encountered oversized frame length %u\n", numBytes);
return -1.0;
}
if (DLT_LINUX_SLL == pcap_link_type)
{
memcpy(aligned_buffer, pktData, numBytes);
ip_buffer = aligned_buffer + 4; ip_length = numBytes - 16;
}
else {
UINT16* ethBuffer = ((UINT16*)aligned_buffer) + 1;
memcpy(ethBuffer, pktData, numBytes);
ProtoPktETH ethPkt(ethBuffer, FRAME_MAX);
if (!ethPkt.InitFromBuffer(hdr.len))
{
fprintf(stderr, "pcapRelay error: invalid Ether frame in pcap file\n");
return -1.0;
}
ip_buffer = ethPkt.AccessPayload();
ip_length = ethPkt.GetPayloadLength();
}
ProtoTime thisTime(hdr.ts);
double interval;
if (last_time.IsValid())
{
interval = ProtoTime::Delta(thisTime, last_time);
if (interval < 0.0)
{
PLOG(PL_WARN, "pcapRelay warning: negative pcap interval detected?!\n");
interval = 0.0;
}
}
else
{
interval = 0.0; }
last_time = thisTime;
return interval;
}
else
{
return -1.0;
}
}
void PcapReplay::OnTxTimeout(ProtoTimer& theTimer)
{
if (0 != ip_length)
{
ProtoPktIP ipPkt;
if (ipPkt.InitFromBuffer(ip_length, ip_buffer, ip_length))
{
if (dst_addr.IsValid())
ipPkt.SetDstAddr(dst_addr);
if (0 != dst_addr.GetPort())
PLOG(PL_WARN, "pcapRelay warning: port translation not yet supported!\n");
if (src_addr.IsValid())
ipPkt.SetSrcAddr(src_addr);
if (0 != src_addr.GetPort())
PLOG(PL_WARN, "pcapRelay warning: port translation not yet supported!\n");
ProtoPktIP::Protocol protocol;
switch (ipPkt.GetVersion())
{
case 4:
{
ProtoPktIPv4 ip4Pkt(ipPkt);
protocol = ip4Pkt.GetProtocol();;
break;
}
case 6:
{
ProtoPktIPv6 ip6Pkt(ipPkt);
protocol = ip6Pkt.GetNextHeader();;
break;
}
default:
PLOG(PL_WARN, "pcapRelay warning: unknown IP version!\n");
protocol = ProtoPktIP::RESERVED;
break;
}
if (ProtoPktIP::UDP == protocol)
{
ProtoPktUDP udpPkt;
if (udpPkt.InitFromPacket(ipPkt))
udpPkt.FinalizeChecksum(ipPkt);
else
PLOG(PL_WARN, "pcapRelay warning: invalid UDP packet?!\n");
}
UINT16* ethBuffer = (UINT16*)ip_buffer - 7;
ProtoPktETH ethPkt(ethBuffer, ip_length + 14);
ethPkt.SetDstAddr(dst_mac);
ethPkt.SetType(ProtoPktETH::IP);
ethPkt.SetPayloadLength(ip_length);
unsigned int numBytes = ethPkt.GetLength();
if (!tx_cap->Forward((char*)ethPkt.GetBuffer(), numBytes))
{
PLOG(PL_ERROR, "pcapRelay error: unable to forward frame!\n");
}
}
else
{
PLOG(PL_ERROR, "pcapRelay error: bad IP packet!\n");
}
}
double interval = ReadFrame(); if (interval < 0.0)
Stop();
else
theTimer.SetInterval(interval);
}
void PcapReplay::OnShutdown()
{
if (tx_timer.IsActive()) tx_timer.Deactivate();
if (NULL != tx_cap)
{
tx_cap->Close();
delete tx_cap;
tx_cap = NULL;
}
if (NULL != pcap_device)
{
pcap_close(pcap_device);
pcap_device = NULL;
}
PLOG(PL_ERROR, "PcapReplay: Done.\n");
CloseDebugLog();
}