Remove packet sorter. Addresses BIT-700

This commit is contained in:
Bernhard Amann 2014-02-25 19:20:42 -08:00
parent 58eb9bbf28
commit 3f584a08fd
13 changed files with 65 additions and 650 deletions

View file

@ -1028,13 +1028,6 @@ const rpc_timeout = 24 sec &redef;
## means "forever", which resists evasion, but can lead to state accrual.
const frag_timeout = 0.0 sec &redef;
## Time window for reordering packets. This is used for dealing with timestamp
## discrepancy between multiple packet sources.
##
## .. note:: Setting this can have a major performance impact as now packets
## need to be potentially copied and buffered.
const packet_sort_window = 0 usecs &redef;
## If positive, indicates the encapsulation header size that should
## be skipped. This applies to all packets.
const encap_hdr_size = 0 &redef;

View file

@ -293,7 +293,6 @@ set(bro_SRCS
OpaqueVal.cc
OSFinger.cc
PacketFilter.cc
PacketSort.cc
PersistenceSerializer.cc
PktSrc.cc
PolicyFile.cc

View file

@ -27,7 +27,6 @@
#include "Reporter.h"
#include "Net.h"
#include "Anon.h"
#include "PacketSort.h"
#include "Serializer.h"
#include "PacketDumper.h"
@ -58,8 +57,6 @@ double bro_start_network_time; // timestamp of first packet
double last_watchdog_proc_time = 0.0; // value of above during last watchdog
bool terminating = false; // whether we're done reading and finishing up
PacketSortGlobalPQ* packet_sorter = 0;
const struct pcap_pkthdr* current_hdr = 0;
const u_char* current_pkt = 0;
int current_dispatched = 0;
@ -286,9 +283,6 @@ void net_init(name_list& interfaces, name_list& readfiles,
init_ip_addr_anonymizers();
if ( packet_sort_window > 0 )
packet_sorter = new PacketSortGlobalPQ();
sessions = new NetSessions();
if ( do_watchdog )
@ -313,7 +307,7 @@ void expire_timers(PktSrc* src_ps)
void net_packet_dispatch(double t, const struct pcap_pkthdr* hdr,
const u_char* pkt, int hdr_size,
PktSrc* src_ps, PacketSortElement* pkt_elem)
PktSrc* src_ps)
{
if ( ! bro_start_network_time )
bro_start_network_time = t;
@ -351,7 +345,7 @@ void net_packet_dispatch(double t, const struct pcap_pkthdr* hdr,
}
}
sessions->DispatchPacket(t, hdr, pkt, hdr_size, src_ps, pkt_elem);
sessions->DispatchPacket(t, hdr, pkt, hdr_size, src_ps);
mgr.Drain();
if ( sp )
@ -367,62 +361,11 @@ void net_packet_dispatch(double t, const struct pcap_pkthdr* hdr,
current_pktsrc = 0;
}
int process_packet_sorter(double latest_packet_time)
{
if ( ! packet_sorter )
return 0;
double min_t = latest_packet_time - packet_sort_window;
int num_pkts_dispatched = 0;
PacketSortElement* pkt_elem;
// Dispatch packets in the packet_sorter until timestamp min_t.
// It's possible that zero or multiple packets are dispatched.
while ( (pkt_elem = packet_sorter->RemoveMin(min_t)) != 0 )
{
net_packet_dispatch(pkt_elem->TimeStamp(),
pkt_elem->Hdr(), pkt_elem->Pkt(),
pkt_elem->HdrSize(), pkt_elem->Src(),
pkt_elem);
++num_pkts_dispatched;
delete pkt_elem;
}
return num_pkts_dispatched;
}
void net_packet_arrival(double t, const struct pcap_pkthdr* hdr,
const u_char* pkt, int hdr_size,
PktSrc* src_ps)
{
if ( packet_sorter )
{
// Note that when we enable packet sorter, there will
// be a small window between the time packet arrives
// to Bro and when it is processed ("dispatched"). We
// define network_time to be the latest timestamp for
// packets *dispatched* so far (usually that's the
// timestamp of the current packet).
// Add the packet to the packet_sorter.
packet_sorter->Add(
new PacketSortElement(src_ps, t, hdr, pkt, hdr_size));
// Do we have any packets to dispatch from packet_sorter?
process_packet_sorter(t);
}
else
// Otherwise we dispatch the packet immediately
net_packet_dispatch(t, hdr, pkt, hdr_size, src_ps, 0);
}
void net_run()
{
set_processing_status("RUNNING", "net_run");
while ( io_sources.Size() ||
(packet_sorter && ! packet_sorter->Empty()) ||
(BifConst::exit_only_after_terminate && ! terminating) )
{
double ts;
@ -445,14 +388,12 @@ void net_run()
current_iosrc = src;
if ( src )
src->Process(); // which will call net_packet_arrival()
src->Process(); // which will call net_packet_dispatch()
else if ( reading_live && ! pseudo_realtime)
{ // live but no source is currently active
double ct = current_time();
if ( packet_sorter && ! packet_sorter->Empty() )
process_packet_sorter(ct);
else if ( ! net_is_processing_suspended() )
if ( ! net_is_processing_suspended() )
{
// Take advantage of the lull to get up to
// date on timers and events.
@ -462,15 +403,6 @@ void net_run()
}
}
else if ( packet_sorter && ! packet_sorter->Empty() )
{
// We are no longer reading live; done with all the
// sources.
// Drain packets remaining in the packet sorter.
process_packet_sorter(
network_time + packet_sort_window + 1000000);
}
else if ( (have_pending_timers || using_communication) &&
! pseudo_realtime )
{
@ -581,7 +513,6 @@ void net_delete()
set_processing_status("TERMINATING", "net_delete");
delete sessions;
delete packet_sorter;
for ( int i = 0; i < NUM_ADDR_ANONYMIZATION_METHODS; ++i )
delete ip_anonymizer[i];

View file

@ -20,7 +20,7 @@ extern void net_run();
extern void net_get_final_stats();
extern void net_finish(int drain_events);
extern void net_delete(); // Reclaim all memory, etc.
extern void net_packet_arrival(double t, const struct pcap_pkthdr* hdr,
extern void net_packet_dispatch(double t, const struct pcap_pkthdr* hdr,
const u_char* pkt, int hdr_size,
PktSrc* src_ps);
extern int net_packet_match(BPF_Program* fp, const u_char* pkt,

View file

@ -156,8 +156,6 @@ int table_incremental_step;
RecordType* packet_type;
double packet_sort_window;
double connection_status_update_interval;
StringVal* state_dir;
@ -481,8 +479,6 @@ void init_net_var()
packet_type = internal_type("packet")->AsRecordType();
packet_sort_window = opt_internal_double("packet_sort_window");
orig_addr_anonymization = opt_internal_int("orig_addr_anonymization");
resp_addr_anonymization = opt_internal_int("resp_addr_anonymization");
other_addr_anonymization = opt_internal_int("other_addr_anonymization");

View file

@ -159,8 +159,6 @@ extern int table_incremental_step;
extern RecordType* packet_type;
extern double packet_sort_window;
extern int orig_addr_anonymization, resp_addr_anonymization;
extern int other_addr_anonymization;
extern TableVal* preserve_orig_addr;

View file

@ -1,364 +0,0 @@
#include "IP.h"
#include "PacketSort.h"
const bool DEBUG_packetsort = false;
PacketSortElement::PacketSortElement(PktSrc* arg_src,
double arg_timestamp, const struct pcap_pkthdr* arg_hdr,
const u_char* arg_pkt, int arg_hdr_size)
{
src = arg_src;
timestamp = arg_timestamp;
hdr = *arg_hdr;
hdr_size = arg_hdr_size;
pkt = new u_char[hdr.caplen];
memcpy(pkt, arg_pkt, hdr.caplen);
is_tcp = 0;
ip_hdr = 0;
tcp_flags = 0;
endp = 0;
payload_length = 0;
key = 0;
// Now check if it is a "parsable" TCP packet.
uint32 caplen = hdr.caplen;
uint32 tcp_offset;
if ( caplen >= sizeof(struct ip) + hdr_size )
{
const struct ip* ip = (const struct ip*) (pkt + hdr_size);
if ( ip->ip_v == 4 )
ip_hdr = new IP_Hdr(ip, false);
else if ( ip->ip_v == 6 && (caplen >= sizeof(struct ip6_hdr) + hdr_size) )
ip_hdr = new IP_Hdr((const struct ip6_hdr*) ip, false, caplen - hdr_size);
else
// Weird will be generated later in NetSessions::NextPacket.
return;
if ( ip_hdr->NextProto() == IPPROTO_TCP &&
// Note: can't sort fragmented packets
( ! ip_hdr->IsFragment() ) )
{
tcp_offset = hdr_size + ip_hdr->HdrLen();
if ( caplen >= tcp_offset + sizeof(struct tcphdr) )
{
const struct tcphdr* tp = (const struct tcphdr*)
(pkt + tcp_offset);
id.src_addr = ip_hdr->SrcAddr();
id.dst_addr = ip_hdr->DstAddr();
id.src_port = tp->th_sport;
id.dst_port = tp->th_dport;
id.is_one_way = 0;
endp = addr_port_canon_lt(id.src_addr,
id.src_port,
id.dst_addr,
id.dst_port) ? 0 : 1;
seq[endp] = ntohl(tp->th_seq);
if ( tp->th_flags & TH_ACK )
seq[1-endp] = ntohl(tp->th_ack);
else
seq[1-endp] = 0;
tcp_flags = tp->th_flags;
// DEBUG_MSG("%.6f: %u, %u\n", timestamp, seq[0], seq[1]);
payload_length = ip_hdr->PayloadLen() - tp->th_off * 4;
key = BuildConnIDHashKey(id);
is_tcp = 1;
}
}
}
if ( DEBUG_packetsort && ! is_tcp )
DEBUG_MSG("%.6f non-TCP packet\n", timestamp);
}
PacketSortElement::~PacketSortElement()
{
delete [] pkt;
delete ip_hdr;
delete key;
}
int PacketSortPQ::Timestamp_Cmp(PacketSortElement* a, PacketSortElement* b)
{
double d = a->timestamp - b->timestamp;
if ( d > 0 ) return 1;
else if ( d < 0 ) return -1;
else return 0;
}
int PacketSortPQ::UpdatePQ(PacketSortElement* prev_e, PacketSortElement* new_e)
{
int index = prev_e->pq_index[pq_level];
new_e->pq_index[pq_level] = index;
pq[index] = new_e;
if ( Cmp(prev_e, new_e) > 0 )
return FixUp(new_e, index);
else
{
FixDown(new_e, index);
return index == 0;
}
}
int PacketSortPQ::AddToPQ(PacketSortElement* new_e)
{
int index = pq.size();
new_e->pq_index[pq_level] = index;
pq.push_back(new_e);
return FixUp(new_e, index);
}
int PacketSortPQ::RemoveFromPQ(PacketSortElement* prev_e)
{
if ( pq.size() > 1 )
{
PacketSortElement* new_e = pq[pq.size() - 1];
pq.pop_back();
return UpdatePQ(prev_e, new_e);
}
else
{
pq.pop_back();
return 1;
}
}
void PacketSortPQ::Assign(int k, PacketSortElement* e)
{
pq[k] = e;
e->pq_index[pq_level] = k;
}
PacketSortConnPQ::~PacketSortConnPQ()
{
// Delete elements only in ConnPQ (not in GlobalPQ) to avoid
// double delete.
for ( int i = 0; i < (int) pq.size(); ++i )
{
delete pq[i];
pq[i] = 0;
}
}
int PacketSortConnPQ::Cmp(PacketSortElement* a, PacketSortElement* b)
{
// Note: here we do not distinguish between packets without
// an ACK and packets with seq/ack of 0. The later will sorted
// only by their timestamps.
if ( a->seq[0] && b->seq[0] && a->seq[0] != b->seq[0] )
return (a->seq[0] > b->seq[0]) ? 1 : -1;
else if ( a->seq[1] && b->seq[1] && a->seq[1] != b->seq[1] )
return (a->seq[1] > b->seq[1]) ? 1 : -1;
else
return Timestamp_Cmp(a, b);
}
int PacketSortPQ::FixUp(PacketSortElement* e, int k)
{
if ( k == 0 )
{
Assign(0, e);
return 1;
}
int parent = (k-1) / 2;
if ( Cmp(pq[parent], e) > 0 )
{
Assign(k, pq[parent]);
return FixUp(e, parent);
}
else
{
Assign(k, e);
return 0;
}
}
void PacketSortPQ::FixDown(PacketSortElement* e, int k)
{
uint32 kid = k * 2 + 1;
if ( kid >= pq.size() )
{
Assign(k, e);
return;
}
if ( kid + 1 < pq.size() && Cmp(pq[kid], pq[kid+1]) > 0 )
++kid;
if ( Cmp(e, pq[kid]) > 0 )
{
Assign(k, pq[kid]);
FixDown(e, kid);
}
else
Assign(k, e);
}
int PacketSortConnPQ::Add(PacketSortElement* e)
{
#if 0
int endp = e->endp;
uint32 end_seq = e->seq[endp] + e->payload_length;
int p = 1 - endp;
if ( (e->tcp_flags & TH_RST) && ! (e->tcp_flags & TH_ACK) )
{
DEBUG_MSG("%.6f %c: %u -> %u\n",
e->TimeStamp(), (p == endp) ? 'S' : 'A',
e->seq[p], next_seq[p]);
e->seq[p] = next_seq[p];
}
if ( end_seq > next_seq[endp] )
next_seq[endp] = end_seq;
#endif
return AddToPQ(e);
}
void PacketSortConnPQ::UpdateDeliveredSeq(int endp, int seq, int len, int ack)
{
if ( delivered_seq[endp] == 0 || delivered_seq[endp] == seq )
delivered_seq[endp] = seq + len;
if ( ack > delivered_seq[1 - endp] )
delivered_seq[endp] = ack;
}
bool PacketSortConnPQ::IsContentGapSafe(PacketSortElement* e)
{
int ack = e->seq[1 - e->endp];
return ack <= delivered_seq[1 - e->endp];
}
int PacketSortConnPQ::Remove(PacketSortElement* e)
{
int ret = RemoveFromPQ(e);
UpdateDeliveredSeq(e->endp, e->seq[e->endp], e->payload_length,
e->seq[1 - e->endp]);
return ret;
}
static void DeleteConnPQ(void* p)
{
delete (PacketSortConnPQ*) p;
}
PacketSortGlobalPQ::PacketSortGlobalPQ()
{
pq_level = GLOBAL_PQ;
conn_pq_table.SetDeleteFunc(DeleteConnPQ);
}
PacketSortGlobalPQ::~PacketSortGlobalPQ()
{
// Destruction of PacketSortConnPQ will delete all conn_pq's.
}
int PacketSortGlobalPQ::Add(PacketSortElement* e)
{
if ( e->is_tcp )
{
// TCP packets are sorted by sequence numbers
PacketSortConnPQ* conn_pq = FindConnPQ(e);
PacketSortElement* prev_min = conn_pq->Min();
if ( conn_pq->Add(e) )
{
ASSERT(conn_pq->Min() != prev_min);
if ( prev_min )
return UpdatePQ(prev_min, e);
else
return AddToPQ(e);
}
else
{
ASSERT(conn_pq->Min() == prev_min);
return 0;
}
}
else
return AddToPQ(e);
}
PacketSortElement* PacketSortGlobalPQ::RemoveMin(double timestamp)
{
PacketSortElement* e = Min();
if ( ! e )
return 0;
if ( e->is_tcp )
{
PacketSortConnPQ* conn_pq = FindConnPQ(e);
#if 0
// Note: the content gap safety check does not work
// because we remove the state for a connection once
// it has no packet in the priority queue.
// Do not deliver e if it arrives later than timestamp,
// and is not content-gap-safe.
if ( e->timestamp > timestamp &&
! conn_pq->IsContentGapSafe(e) )
return 0;
#else
if ( e->timestamp > timestamp )
return 0;
#endif
conn_pq->Remove(e);
PacketSortElement* new_e = conn_pq->Min();
if ( new_e )
UpdatePQ(e, new_e);
else
{
RemoveFromPQ(e);
conn_pq_table.Remove(e->key);
delete conn_pq;
}
}
else
RemoveFromPQ(e);
return e;
}
PacketSortConnPQ* PacketSortGlobalPQ::FindConnPQ(PacketSortElement* e)
{
if ( ! e->is_tcp )
reporter->InternalError("cannot find a connection for an invalid id");
PacketSortConnPQ* pq = (PacketSortConnPQ*) conn_pq_table.Lookup(e->key);
if ( ! pq )
{
pq = new PacketSortConnPQ();
conn_pq_table.Insert(e->key, pq);
}
return pq;
}

View file

@ -1,132 +0,0 @@
#ifndef packetsort_h
#define packetsort_h
// Timestamps can be imprecise and even inconsistent among packets
// from different sources. This class tries to guess a "correct"
// order by looking at TCP sequence numbers.
//
// In particular, it tries to eliminate "false" content gaps.
#include "Dict.h"
#include "Conn.h"
enum {
CONN_PQ,
GLOBAL_PQ,
NUM_OF_PQ_LEVEL,
};
class PktSrc;
class PacketSortElement {
public:
PacketSortElement(PktSrc* src, double timestamp,
const struct pcap_pkthdr* hdr,
const u_char* pkt, int hdr_size);
~PacketSortElement();
PktSrc* Src() const { return src; }
double TimeStamp() const { return timestamp; }
const struct pcap_pkthdr* Hdr() const { return &hdr; }
const u_char* Pkt() const { return pkt; }
int HdrSize() const { return hdr_size; }
const IP_Hdr* IPHdr() const { return ip_hdr; }
protected:
PktSrc* src;
double timestamp;
struct pcap_pkthdr hdr;
u_char* pkt;
int hdr_size;
IP_Hdr* ip_hdr;
int is_tcp;
ConnID id;
uint32 seq[2]; // indexed by endpoint
int tcp_flags;
int endp; // 0 or 1
int payload_length;
HashKey* key;
int pq_index[NUM_OF_PQ_LEVEL];
friend class PacketSortPQ;
friend class PacketSortConnPQ;
friend class PacketSortGlobalPQ;
};
class PacketSortPQ {
public:
PacketSortPQ()
{ pq_level = -1; }
virtual ~PacketSortPQ() {}
PacketSortElement* Min() const { return (pq.size() > 0) ? pq[0] : 0; }
protected:
virtual int Cmp(PacketSortElement* a, PacketSortElement* b) = 0;
int Timestamp_Cmp(PacketSortElement* a, PacketSortElement* b);
int UpdatePQ(PacketSortElement* prev_e, PacketSortElement* new_e);
int AddToPQ(PacketSortElement* e);
int RemoveFromPQ(PacketSortElement* e);
void Assign(int k, PacketSortElement* e);
int FixUp(PacketSortElement* e, int k);
void FixDown(PacketSortElement* e, int k);
vector<PacketSortElement*> pq;
int pq_level;
};
// Sort by sequence numbers within a connection
class PacketSortConnPQ : public PacketSortPQ {
public:
PacketSortConnPQ()
{
pq_level = CONN_PQ;
delivered_seq[0] = delivered_seq[1] = 0;
}
~PacketSortConnPQ();
int Add(PacketSortElement* e);
int Remove(PacketSortElement* e);
bool IsContentGapSafe(PacketSortElement* e);
protected:
int Cmp(PacketSortElement* a, PacketSortElement* b);
void UpdateDeliveredSeq(int endp, int seq, int len, int ack);
int delivered_seq[2];
};
declare(PDict, PacketSortConnPQ);
// Sort by timestamps.
class PacketSortGlobalPQ : public PacketSortPQ {
public:
PacketSortGlobalPQ();
~PacketSortGlobalPQ();
int Add(PacketSortElement* e);
int Empty() const { return conn_pq_table.Length() == 0; }
// Returns the next packet to dispatch if it arrives earlier than the
// given timestamp, otherwise returns 0.
// The packet, if to be returned, is also removed from the
// priority queue.
PacketSortElement* RemoveMin(double timestamp);
protected:
int Cmp(PacketSortElement* a, PacketSortElement* b)
{ return Timestamp_Cmp(a, b); }
PacketSortConnPQ* FindConnPQ(PacketSortElement* e);
PDict(PacketSortConnPQ) conn_pq_table;
};
#endif

View file

@ -220,6 +220,12 @@ void PktSrc::Process()
break;
}
case DLT_IEEE802_11:
{
printf("Here\n");
exit(0);
}
case DLT_EN10MB:
{
// Get protocol being carried from the ethernet frame.
@ -317,13 +323,13 @@ void PktSrc::Process()
if ( pseudo_realtime )
{
current_pseudo = CheckPseudoTime();
net_packet_arrival(current_pseudo, &hdr, data, pkt_hdr_size, this);
net_packet_dispatch(current_pseudo, &hdr, data, pkt_hdr_size, this);
if ( ! first_wallclock )
first_wallclock = current_time(true);
}
else
net_packet_arrival(current_timestamp, &hdr, data, pkt_hdr_size, this);
net_packet_dispatch(current_timestamp, &hdr, data, pkt_hdr_size, this);
data = 0;
}

View file

@ -1466,7 +1466,7 @@ void RemoteSerializer::Process()
current_pkt = p->pkt;
current_pktsrc = 0;
current_iosrc = this;
sessions->NextPacket(p->time, p->hdr, p->pkt, p->hdr_size, 0);
sessions->NextPacket(p->time, p->hdr, p->pkt, p->hdr_size);
mgr.Drain();
current_hdr = 0; // done with these

View file

@ -30,7 +30,6 @@
#include "Discard.h"
#include "RuleMatcher.h"
#include "PacketSort.h"
#include "TunnelEncapsulation.h"
#include "analyzer/Manager.h"
@ -168,7 +167,7 @@ void NetSessions::Done()
void NetSessions::DispatchPacket(double t, const struct pcap_pkthdr* hdr,
const u_char* pkt, int hdr_size,
PktSrc* src_ps, PacketSortElement* pkt_elem)
PktSrc* src_ps)
{
const struct ip* ip_hdr = 0;
const u_char* ip_data = 0;
@ -186,14 +185,13 @@ void NetSessions::DispatchPacket(double t, const struct pcap_pkthdr* hdr,
hdr_size += encap_hdr_size;
if ( src_ps->FilterType() == TYPE_FILTER_NORMAL )
NextPacket(t, hdr, pkt, hdr_size, pkt_elem);
NextPacket(t, hdr, pkt, hdr_size);
else
NextPacketSecondary(t, hdr, pkt, hdr_size, src_ps);
}
void NetSessions::NextPacket(double t, const struct pcap_pkthdr* hdr,
const u_char* const pkt, int hdr_size,
PacketSortElement* pkt_elem)
const u_char* const pkt, int hdr_size)
{
SegmentProfiler(segment_logger, "processing-packet");
if ( pkt_profiler )
@ -206,17 +204,6 @@ void NetSessions::NextPacket(double t, const struct pcap_pkthdr* hdr,
if ( record_all_packets )
DumpPacket(hdr, pkt);
if ( pkt_elem && pkt_elem->IPHdr() )
// Fast path for "normal" IP packets if an IP_Hdr is
// already extracted when doing PacketSort. Otherwise
// the code below tries to extract the IP header, the
// difference here is that header extraction in
// PacketSort does not generate Weird events.
DoNextPacket(t, hdr, pkt_elem->IPHdr(), pkt, hdr_size, 0);
else
{
// ### The following isn't really correct. What we *should*
// do is understanding the different link layers in order to
// find the network-layer protocol ID. That's a big
@ -270,7 +257,6 @@ void NetSessions::NextPacket(double t, const struct pcap_pkthdr* hdr,
Weird("unknown_packet_type", hdr, pkt);
return;
}
}
if ( dump_this_packet && ! record_all_packets )
DumpPacket(hdr, pkt);

View file

@ -28,7 +28,6 @@ declare(PDict,FragReassembler);
class Discarder;
class PacketFilter;
class PacketSortElement;
namespace analyzer { namespace stepping_stone { class SteppingStoneManager; } }
namespace analyzer { namespace arp { class ARP_Analyzer; } }
@ -74,7 +73,7 @@ public:
// employing the packet sorter first.
void DispatchPacket(double t, const struct pcap_pkthdr* hdr,
const u_char* const pkt, int hdr_size,
PktSrc* src_ps, PacketSortElement* pkt_elem);
PktSrc* src_ps);
void Done(); // call to drain events before destructing
@ -220,8 +219,7 @@ protected:
uint8 tcp_flags, bool& flip_roles);
void NextPacket(double t, const struct pcap_pkthdr* hdr,
const u_char* const pkt, int hdr_size,
PacketSortElement* pkt_elem);
const u_char* const pkt, int hdr_size);
void NextPacketSecondary(double t, const struct pcap_pkthdr* hdr,
const u_char* const pkt, int hdr_size,

View file

@ -24,7 +24,11 @@
#endif
#include "NetVar.h"
#include "PacketSort.h"
// for pcap_pkthdr
extern "C" {
#include <pcap.h>
}
namespace analyzer { namespace arp {