pcapplusplus-sys 0.1.0

Compile PcapPlusPlus and make its library and header files available. See also https://github.com/seladb/PcapPlusPlus
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
#define LOG_MODULE NetworkUtils

#include <condition_variable>
#include <errno.h>
#include <mutex>
#include <stdlib.h>
#include "Logger.h"
#include "Packet.h"
#include "EthLayer.h"
#include "ArpLayer.h"
#include "IPv4Layer.h"
#include "UdpLayer.h"
#include "DnsLayer.h"
#include "PcapFilter.h"
#include "NetworkUtils.h"
#include "EndianPortable.h"
#ifdef _MSC_VER
#include "SystemUtils.h"
#endif
#ifndef ETIMEDOUT
#define ETIMEDOUT  10060
#endif

#define DNS_PORT   53


namespace pcpp
{

const int NetworkUtils::DefaultTimeout = 5;


struct ArpingReceivedData
{
	std::mutex &mutex;
	std::condition_variable &cond;
	IPv4Address ipAddr;
	clock_t start;
	MacAddress result;
	double arpResponseTime;
};


static void arpPacketReceived(RawPacket* rawPacket, PcapLiveDevice*, void* userCookie)
{
	// extract timestamp of packet
	clock_t receiveTime = clock();

	// get the data from the main thread
	ArpingReceivedData* data = (ArpingReceivedData*)userCookie;

	// parse the response packet
	Packet packet(rawPacket);

	// verify that it's an ARP packet (although it must be because I set an ARP reply filter on the interface)
	if (!packet.isPacketOfType(ARP))
		return;

	// extract the ARP layer from the packet
	ArpLayer* arpReplyLayer = packet.getLayerOfType<ArpLayer>(true); // lookup in reverse order
	if (arpReplyLayer == nullptr)
		return;

	// verify it's the right ARP response
	if (arpReplyLayer->getArpHeader()->hardwareType != htobe16(1) /* Ethernet */
			|| arpReplyLayer->getArpHeader()->protocolType != htobe16(PCPP_ETHERTYPE_IP))
		return;

	// verify the ARP response is the response for out request (and not some arbitrary ARP response)
	if (arpReplyLayer->getSenderIpAddr() != data->ipAddr)
		return;

	// measure response time
	double diffticks = receiveTime-data->start;
	double diffms = (diffticks*1000)/CLOCKS_PER_SEC;

	data->arpResponseTime = diffms;
	data->result = arpReplyLayer->getSenderMacAddress();

	// signal the main thread the ARP reply was received
	data->cond.notify_one();
}


MacAddress NetworkUtils::getMacAddress(IPv4Address ipAddr, PcapLiveDevice* device, double& arpResponseTimeMS,
		MacAddress sourceMac, IPv4Address sourceIP, int arpTimeout) const
{
	MacAddress result = MacAddress::Zero;

	// open the device if not already opened
	bool closeDeviceAtTheEnd = false;
	if (!device->isOpened())
	{
		closeDeviceAtTheEnd = true;
		if (!device->open())
		{
			PCPP_LOG_ERROR("Cannot open device");
			return result;
		}
	}

	if (sourceMac == MacAddress::Zero)
		sourceMac = device->getMacAddress();

	if (sourceIP == IPv4Address::Zero)
		sourceIP = device->getIPv4Address();

	if (arpTimeout <= 0)
		arpTimeout = NetworkUtils::DefaultTimeout;

	// create an ARP request from sourceMac and sourceIP and ask for target IP

	Packet arpRequest(100);

	MacAddress destMac(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
	EthLayer ethLayer(sourceMac, destMac);

	ArpLayer arpLayer(ARP_REQUEST, sourceMac, destMac, sourceIP, ipAddr);

	if (!arpRequest.addLayer(&ethLayer))
	{
		PCPP_LOG_ERROR("Couldn't build Eth layer for ARP request");
		return result;
	}

	if (!arpRequest.addLayer(&arpLayer))
	{
		PCPP_LOG_ERROR("Couldn't build ARP layer for ARP request");
		return result;
	}

	arpRequest.computeCalculateFields();

	// set a filter for the interface to intercept only ARP response packets
	ArpFilter arpFilter(ARP_REPLY);
	if (!device->setFilter(arpFilter))
	{
		PCPP_LOG_ERROR("Couldn't set ARP filter for device");
		return result;
	}

	// since packet capture is done on another thread, I use a conditional mutex with timeout to synchronize between the capture
	// thread and the main thread. When the capture thread starts running the main thread is blocking on the conditional mutex.
	// When the ARP response is captured the capture thread signals the main thread and the main thread stops capturing and continues
	// to the next iteration. If a timeout passes and no ARP response is captured, the main thread stops capturing

	std::mutex mutex;
	std::condition_variable cond;

	// this is the token that passes between the 2 threads. It contains pointers to the conditional mutex, the target IP for identifying
	// the ARP response, the iteration index and a timestamp to calculate the response time
	ArpingReceivedData data = {
			mutex,
			cond,
			ipAddr,
			clock(),
			MacAddress::Zero,
			0
	};

	struct timeval now;
	gettimeofday(&now,nullptr);

	// start capturing. The capture is done on another thread, hence "arpPacketReceived" is running on that thread
	device->startCapture(arpPacketReceived, &data);

	// send the ARP request
	device->sendPacket(&arpRequest);

	// block on the conditional mutex until capture thread signals or until timeout expires
	// cppcheck-suppress localMutex
	std::unique_lock<std::mutex> lock(mutex);
	std::cv_status res = cond.wait_for(lock, std::chrono::seconds(arpTimeout));

	// stop the capturing thread
	device->stopCapture();

	// check if timeout expired
	if (res == std::cv_status::timeout)
	{
		PCPP_LOG_ERROR("ARP request time out");
		return result;
	}

	if (closeDeviceAtTheEnd)
		device->close();
	else
		device->clearFilter();

	result = data.result;
	arpResponseTimeMS = data.arpResponseTime;

	return result;
}



struct DNSReceivedData
{
	std::mutex &mutex;
	std::condition_variable &cond;
	std::string hostname;
	uint16_t transactionID;
	clock_t start;
	IPv4Address result;
	uint32_t ttl;
	double dnsResponseTime;
};

static void dnsResponseReceived(RawPacket* rawPacket, PcapLiveDevice*, void* userCookie)
{
	// extract timestamp of packet
	clock_t receiveTime = clock();

	// get data from the main thread
	DNSReceivedData* data = (DNSReceivedData*)userCookie;

	// parse the response packet
	Packet packet(rawPacket);

	// verify that it's an DNS packet (although it must be because DNS port filter was set on the interface)
	if (!packet.isPacketOfType(DNS))
		return;

	// extract the DNS layer from the packet
	DnsLayer* dnsResponseLayer = packet.getLayerOfType<DnsLayer>(true); // lookup in reverse order
	if (dnsResponseLayer == nullptr)
		return;

	// verify it's the right DNS response
	if (dnsResponseLayer->getDnsHeader()->queryOrResponse != 1 /* DNS response */
			|| dnsResponseLayer->getDnsHeader()->numberOfAnswers < htobe16(1)
			|| dnsResponseLayer->getDnsHeader()->transactionID != htobe16(data->transactionID))
	{
		return;
	}

	// DNS resolving can be recursive as many DNS responses contain multiple answers with recursive canonical names (CNAME) for
	// the hostname. For example: a DNS response for www.a.com can have multiple answers:
	//- First with CNAME: www.a.com -> www.b.com
	//- Second with CNAME: www.b.com -> www.c.com
	//- Third with resolving: www.c.com -> 1.1.1.1
	// So the search must be recursive until an IPv4 resolving is found or until no hostname or canonical name are found (and then return)

	std::string hostToFind = data->hostname;

	DnsResource* dnsAnswer = nullptr;

	while (true)
	{
		dnsAnswer = dnsResponseLayer->getAnswer(hostToFind, true);

		// if response doesn't contain hostname or cname - return
		if (dnsAnswer == nullptr)
		{
			PCPP_LOG_DEBUG("DNS answer doesn't contain hostname '" << hostToFind << "'");
			return;
		}

		DnsType dnsType = dnsAnswer->getDnsType();
		// if answer contains IPv4 resolving - break the loop and return the IP address
		if (dnsType == DNS_TYPE_A)
		{
			PCPP_LOG_DEBUG("Found IPv4 resolving for hostname '" << hostToFind << "'");
			break;
		}
		// if answer contains a cname - continue to search this cname in the packet - hopefully find the IP resolving
		else if (dnsType == DNS_TYPE_CNAME)
		{
			PCPP_LOG_DEBUG("Got a DNS response for hostname '" << hostToFind << "' with CNAME '" << dnsAnswer->getData()->toString() << "'");
			hostToFind = dnsAnswer->getData()->toString();
		}
		// if answer is of type other than A or CNAME (for example AAAA - IPv6) - type is not supported - return
		else
		{
			PCPP_LOG_DEBUG("Got a DNS response with type which is not A or CNAME");
			return;
		}
	}
	// if we got here it means an IPv4 resolving was found

	// measure response time
	clock_t diffticks = receiveTime-data->start;
	double diffms = (diffticks*1000.0)/CLOCKS_PER_SEC;

	data->dnsResponseTime = diffms;
	data->result = dnsAnswer->getData()->castAs<IPv4DnsResourceData>()->getIpAddress();
	data->ttl = dnsAnswer->getTTL();

	// signal the main thread the ARP reply was received
	data->cond.notify_one();
}


IPv4Address NetworkUtils::getIPv4Address(const std::string& hostname, PcapLiveDevice* device, double& dnsResponseTimeMS, uint32_t& dnsTTL,
		int dnsTimeout, IPv4Address dnsServerIP, IPv4Address gatewayIP) const
{
	IPv4Address result = IPv4Address::Zero;

	// open the device if not already opened
	bool closeDeviceAtTheEnd = false;
	if (!device->isOpened())
	{
		closeDeviceAtTheEnd = true;
		if (!device->open())
		{
			PCPP_LOG_ERROR("Cannot open device");
			return result;
		}
	}

	// first - resolve gateway MAC address

	// if gateway IP wasn't provided - try to find the default gateway
	if (gatewayIP == IPv4Address::Zero)
	{
		gatewayIP = device->getDefaultGateway();
	}

	if (!gatewayIP.isValid() || gatewayIP == IPv4Address::Zero)
	{
		PCPP_LOG_ERROR("Gateway address isn't valid or couldn't find default gateway");
		return result;
	}

	// send the ARP request to find gateway MAC address
	double arpResTime;
	MacAddress gatewayMacAddress = getMacAddress(gatewayIP, device, arpResTime);

	if (gatewayMacAddress == MacAddress::Zero)
	{
		PCPP_LOG_ERROR("Couldn't resolve gateway MAC address");
		return result;
	}

	if (dnsTimeout <= 0)
		dnsTimeout = NetworkUtils::DefaultTimeout;

	// validate DNS server IP. If it wasn't provided - set the system-configured DNS server
	if (dnsServerIP == IPv4Address::Zero && device->getDnsServers().size() > 0)
	{
		dnsServerIP = device->getDnsServers().at(0);
	}

	if (!dnsServerIP.isValid())
	{
		PCPP_LOG_ERROR("DNS server IP isn't valid");
		return result;
	}

	// create DNS request

	Packet dnsRequest(100);
	MacAddress sourceMac = device->getMacAddress();
	EthLayer ethLayer(sourceMac, gatewayMacAddress, PCPP_ETHERTYPE_IP);
	IPv4Layer ipLayer(device->getIPv4Address(), dnsServerIP);
	ipLayer.getIPv4Header()->timeToLive = 128;

	// randomize source port to a number >= 10000
	int srcPortLowest = 10000;
	int srcPortRange = 65535 - srcPortLowest;
	uint16_t srcPort = (rand() % srcPortRange) + srcPortLowest;
	UdpLayer udpLayer(srcPort, DNS_PORT);

	// create the DNS request for the hostname
	DnsLayer dnsLayer;

	// randomize transaction ID
	uint16_t transactionID = rand() % 65535;
	dnsLayer.getDnsHeader()->transactionID = htobe16(transactionID);
	dnsLayer.addQuery(hostname, DNS_TYPE_A, DNS_CLASS_IN);

	// add all layers to packet
	if (!dnsRequest.addLayer(&ethLayer) || !dnsRequest.addLayer(&ipLayer) || !dnsRequest.addLayer(&udpLayer) || !dnsRequest.addLayer(&dnsLayer))
	{
		PCPP_LOG_ERROR("Couldn't construct DNS query");
		return result;
	}

	dnsRequest.computeCalculateFields();

	// set a DNS response filter on the device
	PortFilter dnsResponseFilter(53, SRC);
	if (!device->setFilter(dnsResponseFilter))
	{
		PCPP_LOG_ERROR("Couldn't set DNS response filter");
		return result;
	}

	// since packet capture is done on another thread, I use a conditional mutex with timeout to synchronize between the capture
	// thread and the main thread. When the capture thread starts running the main thread is blocking on the conditional mutex.
	// When the DNS response are captured the capture thread signals the main thread and the main thread stops capturing and continues
	// to the next iteration. if a timeout passes and no DNS response is captured, the main thread stops capturing

	std::mutex mutex;
	std::condition_variable cond;

	// this is the token that passes between the 2 threads
	DNSReceivedData data = {
			mutex,
			cond,
			hostname,
			transactionID,
			clock(),
			IPv4Address::Zero,
			0,
			0
	};


	struct timeval now;
	gettimeofday(&now,nullptr);

	// start capturing. The capture is done on another thread, hence "dnsResponseReceived" is running on that thread
	device->startCapture(dnsResponseReceived, &data);

	// send the DNS request
	device->sendPacket(&dnsRequest);

	// block on the conditional mutex until capture thread signals or until timeout expires
	// cppcheck-suppress localMutex
	std::unique_lock<std::mutex> lock(mutex);
	std::cv_status res = cond.wait_for(lock, std::chrono::seconds(dnsTimeout));

	// stop the capturing thread
	device->stopCapture();

	// check if timeout expired
	if (res == std::cv_status::timeout)
	{
		PCPP_LOG_ERROR("DNS request time out");
		return result;
	}

	if (closeDeviceAtTheEnd)
		device->close();
	else
		device->clearFilter();

	result = data.result;
	dnsResponseTimeMS = data.dnsResponseTime;
	dnsTTL = data.ttl;

	return result;
}

} // namespace pcpp