vtkPacketFileReader是对libpcap的包装(windows系统下是winpcap,linux下是libpcap)。winpcap已经停止维护。可以使用Npcap。在LidarView中仍然使用的是winpcap(http://github.com/patmarion/winpcap.git)。
vtkPacketFileReader负责对pcap数据进行读取,并可以获取或者设定指定的文件位置。
在头文件中定义了存储数据和数据信息的结构体,声明了用于读取和构造报头的函数。然后声明了vtkPacketFileReader类。
// Some versions of libpcap do not have PCAP_NETMASK_UNKNOWN
#if !defined(PCAP_NETMASK_UNKNOWN)
#define PCAP_NETMASK_UNKNOWN 0xffffffff
#endif
//------------------------------------------------------------------------------
// IPv6 fragment IDs are 4 bytes, IPv4 are only 2.
typedef uint32_t FragmentIdentificationT;
//------------------------------------------------------------------------------
// Packet fragment offsets are measured in blocks/steps of 8 bytes.
constexpr unsigned int FRAGMENT_OFFSET_STEP = 8;
//------------------------------------------------------------------------------
/*!
* @brief Track fragment reassembly to confirm that all data has been
* reassembled before passing it on.
*/
struct FragmentTracker // 用于存储数据
{
// The incrementally reassembled data.
std::vector Data;
// Set when the final fragment is encountered.
unsigned int ExpectedSize = 0;
// Incremented as fragments are added to the data.
unsigned int CurrentSize = 0;
};
//------------------------------------------------------------------------------
/*!
* @brief Fragment info required for reconstructing fragmented IP packets.
*/
struct FragmentInfo
{
FragmentIdentificationT Identification = 0;
uint16_t Offset = 0;
bool MoreFragments = false;
void Reset()
{
this->Identification = 0;
this->Offset = 0;
this->MoreFragments = false;
}
};
namespace IPHeaderFunctions
{ // 辅助函数
//----------------------------------------------------------------------------
/*!
* @brief Inspect the IP header to get fragment information.
* @param[in] data A pointer to the bytes of the IP header.
* @param[out] fragmentInfo The collected fragment info.
* @return True if the information could be retrieved, false otherwise.
*/
LVIONETWORK_EXPORT bool getFragmentInfo(unsigned char const * data, FragmentInfo & fragmentInfo);
//----------------------------------------------------------------------------
/*!
* @brief Determine the IP header length by inspection.
* @param[in] data A pointer to the first byte of the IP header.
* @return The number of bytes in the IP header, or 0 if this could not be
* determined.
*/
LVIONETWORK_EXPORT unsigned int getIPHeaderLength(unsigned char const* data);
bool buildReassembledIPHeader(unsigned char* iphdrdata, const unsigned int ipHeaderLength, const unsigned int payloadSize);
}
//------------------------------------------------------------------------------
class LVIONETWORK_EXPORT vtkPacketFileReader
{
public:
vtkPacketFileReader() // 构造函数
{
this->PCAPFile = 0; // pcap_t* PCAPFile;
}
~vtkPacketFileReader() // 析构函数
{
this->Close(); // 关闭文件
}
/**
* @brief Open Pcap capture
* @param[in] filename pcap file name.
* @param[in] filter_arg kernel-level packet filter parameter.
* @param[in] reassemble if disabled, data will be unusable as is.
* This is solely used when saving a Pcap to file and original fragmentation is sought.
* Returns true is successful.
*/
bool Open(const std::string& filename, std::string filter_arg="udp", bool reassemble = true);
bool IsOpen() { return (this->PCAPFile != 0); }
void Close();
const std::string& GetLastError() { return this->LastError; }
const std::string& GetFileName() { return this->FileName; }
void GetFilePosition(fpos_t* position); // 获取当前文件流的位置
void SetFilePosition(fpos_t* position); // 设置文件流的位置
/**
* @brief Read next UDP payload of the capture.
* @param[in] data A pointer to the first byte of UDP Payload.
* @param[in] dataLength Size in bytes of the UDP Payload.
* @param[out] timeSinceStart Network timestamp relative to the capture start time.
* @param[out] headerReference A pointer to pointer to the Frame header (Optional).
* @param[out] dataHeaderLength A pointer to the size of the Frame header (Optional).
* Returns 'data' pointer to payload data of size 'dataLength'
* Full Frame of size 'headerReference' is available,
* header data is located at 'data - dataHeaderLength'
*/
bool NextPacket(const unsigned char*& data, unsigned int& dataLength, double& timeSinceStart,
pcap_pkthdr** headerReference = NULL, unsigned int* dataHeaderLength = NULL);
protected:
pcap_t* PCAPFile;
std::string FileName;
std::string LastError;
timeval StartTime;
unsigned int FrameHeaderLength;
bool Reassemble;
private:
//! @brief A map of fragmented packet IDs to the collected array of fragments.
std::unordered_map Fragments;
//! @brief The ID of the last completed fragment.
FragmentIdentificationT AssembledId = 0;
//! @brief True if there is a reassembled packet to remove.
bool RemoveAssembled = false;
};
该函数用于读取pcap文件流数据,并将数据存储在FragmentTracker结构体中。然后将传输的数据指针指向该数据,以便调用者使用。
// 读取文件流数据
// data 指针指向数据(UDP payload)
// dataLength说明数据的长度
// timeSinceStart数据获取时间,网络时间,相对于开始的时间差
bool vtkPacketFileReader::NextPacket(const unsigned char*& data, unsigned int& dataLength, double& timeSinceStart,
pcap_pkthdr** headerReference, unsigned int* dataHeaderLength )
{
if (!this->PCAPFile) // 检查是否已经打开文件
{
return false;
}
//Clear Previous Reassembly Data if needed
if (this->RemoveAssembled)
{
auto it = this->Fragments.find(this->AssembledId);
this->Fragments.erase(it);
this->AssembledId = 0;
this->RemoveAssembled = false;
}
dataLength = 0;
pcap_pkthdr* header;
FragmentInfo fragmentInfo;
fragmentInfo.MoreFragments = true;
while (fragmentInfo.MoreFragments)
{
unsigned char const * tmpData = nullptr; // 临时数据指针
// 获取数据
// this->PCAPFile:当前打开的文件 (传入参数)
// header:数据报头 (传出参数)
// temData:数据地址 (传出参数)
int returnValue = pcap_next_ex(this->PCAPFile, &header, &tmpData);
// 返回值:
// 1: 成功;
// 0:超时
// PCAP_ERROR(-1):读取数据时发生错误
// PCAP_ERROR_BREAK(-2):到达文件末尾,没有更多数据。
// PCAP_ERROR_NOT_ACTIVATED(-3):读取的文件已经创建但是没有被激活。
if (returnValue < 0)
{ // 无法读取文件,关闭文件并退出函数。
this->Close();
return false;
}
// Collect header values before they are removed.
// 获取数据信息
// this->FrameHeaderLength在打开文件时进行赋值。
// fragmentInfo保存报头信息,传出参数。
IPHeaderFunctions::getFragmentInfo(tmpData + this->FrameHeaderLength, fragmentInfo);
timeSinceStart = GetElapsedTime(header->ts, this->StartTime); // 将时间高位和低位组合成完整时间。
//Consts
// 获取报头长度
const unsigned int ipHeaderLength = IPHeaderFunctions::getIPHeaderLength(tmpData + this->FrameHeaderLength);
if (ipHeaderLength == 0)
{
continue;
}
const unsigned int ethHeaderLength = this->FrameHeaderLength;
const unsigned int udpHeaderLength = 8;
//CaptureLen cropping
const unsigned int frameLength = (std::min)(header->len,header->caplen); //windows.h conflict bypass
const unsigned int ipPayloadLength = frameLength - (ethHeaderLength + ipHeaderLength);
// 获取数据指针
unsigned char const * ipPayloadPtr = tmpData + ethHeaderLength + ipHeaderLength;
//Provide Header Info if requested
if (headerReference != NULL && dataHeaderLength != NULL)
{
*headerReference = header;
*dataHeaderLength = ethHeaderLength + ipHeaderLength + udpHeaderLength;
}
//IP Reassembly
if ( (fragmentInfo.MoreFragments || fragmentInfo.Offset > 0) && this->Reassemble )
{
const unsigned int offset_frag = fragmentInfo.Offset * FRAGMENT_OFFSET_STEP;
const unsigned int offset = ethHeaderLength + ipHeaderLength + offset_frag;
const unsigned int requiredSize = offset + ipPayloadLength;
auto & fragmentTracker = this->Fragments[fragmentInfo.Identification];
auto & reassembledData = fragmentTracker.Data;
if (requiredSize > reassembledData.size())
{
reassembledData.resize(requiredSize);
}
// 复制数据,数据保存在this->Fragments中。
std::copy(ipPayloadPtr, ipPayloadPtr + ipPayloadLength, reassembledData.begin() + offset);
// Update current collected payload size.
fragmentTracker.CurrentSize += ipPayloadLength;
// There may be gaps of size FRAGMENT_OFFSET_STEP between fragments.
// Add this to the current size, assumes that *
// Any given fragment is always larger than FRAGMENT_OFFSET_STEP,
// so that it cannot be omitted accidentally.
if (fragmentInfo.MoreFragments) // 在getFragmentInfo函数中进行判断,该信息在数据报头中。
{
// WIP I doubt this increment has any use, gap mentioned above is included within offset_frag
// WIP commenting this line and replacing >= with == has no effects
fragmentTracker.CurrentSize += FRAGMENT_OFFSET_STEP;
}else{
// Set the expected size if this is the final fragment.
fragmentTracker.ExpectedSize = offset_frag + ipPayloadLength;
}
// Return Reassembled Packet if complete.
if (fragmentTracker.ExpectedSize > 0 && fragmentTracker.CurrentSize >= fragmentTracker.ExpectedSize)
{
//Build Reassembled Packet Header off the last one received
// 拷贝报头
std::copy(
tmpData,
ipPayloadPtr,
reassembledData.begin()
);
//Frame Header
header->len = ethHeaderLength + ipHeaderLength + fragmentTracker.ExpectedSize ;
header->caplen = header->len;
//IP Header
IPHeaderFunctions::buildReassembledIPHeader(reassembledData.data() + ethHeaderLength, ipHeaderLength, fragmentTracker.ExpectedSize);
// Delete the associated data on the next iteration.
this->AssembledId = fragmentInfo.Identification;
this->RemoveAssembled = true;
//Point to dataPayload
data = reassembledData.data() + ethHeaderLength + ipHeaderLength + udpHeaderLength;
dataLength = fragmentTracker.ExpectedSize - udpHeaderLength;
return true;
}
}
//Self Standing IP packet or Reassembly is not desired
else
{
//Point to dataPayload
data = ipPayloadPtr + udpHeaderLength;
dataLength = ipPayloadLength - udpHeaderLength;
return true;
}
}
return true;
}
该函数用于打开pcap文件,调用了pcap库的打开文件函数。
// 打开pcap格式文件(tcpdump/libcap format)
// 使用winpcap库进行操作。
bool vtkPacketFileReader::Open(const std::string& filename, std::string filter_arg, bool reassemble)
{
//Open savefile in tcpdump/libcap format
char errbuff[PCAP_ERRBUF_SIZE];
// 调用winpcap函数,打开pcap文件
pcap_t* pcapFile = pcap_open_offline(filename.c_str(), errbuff);
if (!pcapFile)
{ // 打开文件失败,保存错误信息。退出函数。
this->LastError = errbuff;
return false;
}
//Compute filter for the kernel-level filtering engine
// 将用户制定的过滤策略编译到过滤程序中 filter_arg是过滤参数。
bpf_program filter;
if (pcap_compile(pcapFile, &filter, filter_arg.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1)
{
this->LastError = pcap_geterr(pcapFile);
return false;
}
//Associate filter to pcap
// 用于设置过滤器,将过滤器添加到该pcap文件中。
if (pcap_setfilter(pcapFile, &filter) == -1)
{
// 设置过滤器失败,保存失败信息,退出函数。
this->LastError = pcap_geterr(pcapFile);
return false;
}
//Determine Datalink header size
const unsigned int loopback_header_size = 4;
const unsigned int ethernet_header_size = 14;
auto linktype = pcap_datalink(pcapFile);
switch (linktype)
{
case DLT_EN10MB:
this->FrameHeaderLength = ethernet_header_size;
break;
case DLT_NULL:
this->FrameHeaderLength = loopback_header_size;
break;
default:
this->LastError = "Unknown link type in pcap file. Cannot tell where the payload is.";
return false;
}
this->FileName = filename;
this->PCAPFile = pcapFile;
this->StartTime.tv_sec = this->StartTime.tv_usec = 0;
this->Reassemble = reassemble;
return true;
}
关闭文件。
// 关闭文件。调用winpcap库中的关闭文件函数pcap_close。
void vtkPacketFileReader::Close()
{
if (this->PCAPFile)
{
pcap_close(this->PCAPFile);
this->PCAPFile = 0;
this->FileName.clear();
}
}
获取当前读取的位置,通过pcap_fgetpos 或者 pcap_file + fgetpos库进行获取。
// 获取当前文件流位置,调用winpcap库的pcap_fgetpos函数
// 如果是linux系统,则调用libpcap库中的pcap_file函数获取系统文件,然后调用fgetpos函数获取位置。
void vtkPacketFileReader::GetFilePosition(fpos_t* position)
{
#ifdef _MSC_VER
pcap_fgetpos(this->PCAPFile, position);
#else
FILE* f = pcap_file(this->PCAPFile);
fgetpos(f, position);
#endif
}
设置读取文件的位置。通过pcap_fsetpos或者pcap_file + fsetpos函数进行设置。
// 设置当前文件流的位置。调用winpcap库的pcap_fsetpos函数
// 如果是linux系统,则调用libpcap库中的pcap_file获取文件,然后调用fsetpos函数设置位置。
void vtkPacketFileReader::SetFilePosition(fpos_t* position)
{
#ifdef _MSC_VER
pcap_fsetpos(this->PCAPFile, position);
#else
FILE* f = pcap_file(this->PCAPFile);
fsetpos(f, position);
#endif
}