aboutsummaryrefslogtreecommitdiff
path: root/pcap-util.c
blob: 8b5669e90b9afcaa7afbaf8bc0569ba624766c82 (plain)
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
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
/*
 * Copyright (c) 1993, 1994, 1995, 1996, 1997
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * pcap-common.c - common code for pcap and pcapng files
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <pcap-types.h>

#include "pcap-int.h"
#include "extract.h"
#include "pcap-usb-linux-common.h"

#include "pcap-util.h"

#include "pflog.h"
#include "pcap/can_socketcan.h"
#include "pcap/sll.h"
#include "pcap/usb.h"
#include "pcap/nflog.h"

/*
 * Most versions of the DLT_PFLOG pseudo-header have UID and PID fields
 * that are saved in host byte order.
 *
 * When reading a DLT_PFLOG packet, we need to convert those fields from
 * the byte order of the host that wrote the file to this host's byte
 * order.
 */
static void
swap_pflog_header(const struct pcap_pkthdr *hdr, u_char *buf)
{
	u_int caplen = hdr->caplen;
	u_int length = hdr->len;
	u_int pfloghdr_length;
	struct pfloghdr *pflhdr = (struct pfloghdr *)buf;

	if (caplen < (u_int) (offsetof(struct pfloghdr, uid) + sizeof pflhdr->uid) ||
	    length < (u_int) (offsetof(struct pfloghdr, uid) + sizeof pflhdr->uid)) {
		/* Not enough data to have the uid field */
		return;
	}

	pfloghdr_length = pflhdr->length;

	if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, uid) + sizeof pflhdr->uid)) {
		/* Header doesn't include uid field */
		return;
	}
	pflhdr->uid = SWAPLONG(pflhdr->uid);

	if (caplen < (u_int) (offsetof(struct pfloghdr, pid) + sizeof pflhdr->pid) ||
	    length < (u_int) (offsetof(struct pfloghdr, pid) + sizeof pflhdr->pid)) {
		/* Not enough data to have the pid field */
		return;
	}
	if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, pid) + sizeof pflhdr->pid)) {
		/* Header doesn't include pid field */
		return;
	}
	pflhdr->pid = SWAPLONG(pflhdr->pid);

	if (caplen < (u_int) (offsetof(struct pfloghdr, rule_uid) + sizeof pflhdr->rule_uid) ||
	    length < (u_int) (offsetof(struct pfloghdr, rule_uid) + sizeof pflhdr->rule_uid)) {
		/* Not enough data to have the rule_uid field */
		return;
	}
	if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, rule_uid) + sizeof pflhdr->rule_uid)) {
		/* Header doesn't include rule_uid field */
		return;
	}
	pflhdr->rule_uid = SWAPLONG(pflhdr->rule_uid);

	if (caplen < (u_int) (offsetof(struct pfloghdr, rule_pid) + sizeof pflhdr->rule_pid) ||
	    length < (u_int) (offsetof(struct pfloghdr, rule_pid) + sizeof pflhdr->rule_pid)) {
		/* Not enough data to have the rule_pid field */
		return;
	}
	if (pfloghdr_length < (u_int) (offsetof(struct pfloghdr, rule_pid) + sizeof pflhdr->rule_pid)) {
		/* Header doesn't include rule_pid field */
		return;
	}
	pflhdr->rule_pid = SWAPLONG(pflhdr->rule_pid);
}

/*
 * DLT_LINUX_SLL packets with a protocol type of LINUX_SLL_P_CAN or
 * LINUX_SLL_P_CANFD have SocketCAN headers in front of the payload,
 * with the CAN ID being in host byte order.
 *
 * When reading a DLT_LINUX_SLL packet, we need to check for those
 * packets and convert the CAN ID from the byte order of the host that
 * wrote the file to this host's byte order.
 */
static void
swap_linux_sll_header(const struct pcap_pkthdr *hdr, u_char *buf)
{
	u_int caplen = hdr->caplen;
	u_int length = hdr->len;
	struct sll_header *shdr = (struct sll_header *)buf;
	uint16_t protocol;
	pcap_can_socketcan_hdr *chdr;

	if (caplen < (u_int) sizeof(struct sll_header) ||
	    length < (u_int) sizeof(struct sll_header)) {
		/* Not enough data to have the protocol field */
		return;
	}

	protocol = EXTRACT_BE_U_2(&shdr->sll_protocol);
	if (protocol != LINUX_SLL_P_CAN && protocol != LINUX_SLL_P_CANFD)
		return;

	/*
	 * SocketCAN packet; fix up the packet's header.
	 */
	chdr = (pcap_can_socketcan_hdr *)(buf + sizeof(struct sll_header));
	if (caplen < (u_int) sizeof(struct sll_header) + sizeof(chdr->can_id) ||
	    length < (u_int) sizeof(struct sll_header) + sizeof(chdr->can_id)) {
		/* Not enough data to have the CAN ID */
		return;
	}
	chdr->can_id = SWAPLONG(chdr->can_id);
}

/*
 * The same applies for DLT_LINUX_SLL2.
 */
static void
swap_linux_sll2_header(const struct pcap_pkthdr *hdr, u_char *buf)
{
	u_int caplen = hdr->caplen;
	u_int length = hdr->len;
	struct sll2_header *shdr = (struct sll2_header *)buf;
	uint16_t protocol;
	pcap_can_socketcan_hdr *chdr;

	if (caplen < (u_int) sizeof(struct sll2_header) ||
	    length < (u_int) sizeof(struct sll2_header)) {
		/* Not enough data to have the protocol field */
		return;
	}

	protocol = EXTRACT_BE_U_2(&shdr->sll2_protocol);
	if (protocol != LINUX_SLL_P_CAN && protocol != LINUX_SLL_P_CANFD)
		return;

	/*
	 * SocketCAN packet; fix up the packet's header.
	 */
	chdr = (pcap_can_socketcan_hdr *)(buf + sizeof(struct sll2_header));
	if (caplen < (u_int) sizeof(struct sll2_header) + sizeof(chdr->can_id) ||
	    length < (u_int) sizeof(struct sll2_header) + sizeof(chdr->can_id)) {
		/* Not enough data to have the CAN ID */
		return;
	}
	chdr->can_id = SWAPLONG(chdr->can_id);
}

/*
 * The DLT_USB_LINUX and DLT_USB_LINUX_MMAPPED headers are in host
 * byte order when capturing (it's supplied directly from a
 * memory-mapped buffer shared by the kernel).
 *
 * When reading a DLT_USB_LINUX or DLT_USB_LINUX_MMAPPED packet, we
 * need to convert it from the byte order of the host that wrote the
 * file to this host's byte order.
 */
static void
swap_linux_usb_header(const struct pcap_pkthdr *hdr, u_char *buf,
    int header_len_64_bytes)
{
	pcap_usb_header_mmapped *uhdr = (pcap_usb_header_mmapped *)buf;
	bpf_u_int32 offset = 0;

	/*
	 * "offset" is the offset *past* the field we're swapping;
	 * we skip the field *before* checking to make sure
	 * the captured data length includes the entire field.
	 */

	/*
	 * The URB id is a totally opaque value; do we really need to
	 * convert it to the reading host's byte order???
	 */
	offset += 8;			/* skip past id */
	if (hdr->caplen < offset)
		return;
	uhdr->id = SWAPLL(uhdr->id);

	offset += 4;			/* skip past various 1-byte fields */

	offset += 2;			/* skip past bus_id */
	if (hdr->caplen < offset)
		return;
	uhdr->bus_id = SWAPSHORT(uhdr->bus_id);

	offset += 2;			/* skip past various 1-byte fields */

	offset += 8;			/* skip past ts_sec */
	if (hdr->caplen < offset)
		return;
	uhdr->ts_sec = SWAPLL(uhdr->ts_sec);

	offset += 4;			/* skip past ts_usec */
	if (hdr->caplen < offset)
		return;
	uhdr->ts_usec = SWAPLONG(uhdr->ts_usec);

	offset += 4;			/* skip past status */
	if (hdr->caplen < offset)
		return;
	uhdr->status = SWAPLONG(uhdr->status);

	offset += 4;			/* skip past urb_len */
	if (hdr->caplen < offset)
		return;
	uhdr->urb_len = SWAPLONG(uhdr->urb_len);

	offset += 4;			/* skip past data_len */
	if (hdr->caplen < offset)
		return;
	uhdr->data_len = SWAPLONG(uhdr->data_len);

	if (uhdr->transfer_type == URB_ISOCHRONOUS) {
		offset += 4;			/* skip past s.iso.error_count */
		if (hdr->caplen < offset)
			return;
		uhdr->s.iso.error_count = SWAPLONG(uhdr->s.iso.error_count);

		offset += 4;			/* skip past s.iso.numdesc */
		if (hdr->caplen < offset)
			return;
		uhdr->s.iso.numdesc = SWAPLONG(uhdr->s.iso.numdesc);
	} else
		offset += 8;			/* skip USB setup header */

	/*
	 * With the old header, there are no isochronous descriptors
	 * after the header.
	 *
	 * With the new header, the actual number of descriptors in
	 * the header is not s.iso.numdesc, it's ndesc - only the
	 * first N descriptors, for some value of N, are put into
	 * the header, and ndesc is set to the actual number copied.
	 * In addition, if s.iso.numdesc is negative, no descriptors
	 * are captured, and ndesc is set to 0.
	 */
	if (header_len_64_bytes) {
		/*
		 * This is either the "version 1" header, with
		 * 16 bytes of additional fields at the end, or
		 * a "version 0" header from a memory-mapped
		 * capture, with 16 bytes of zeroed-out padding
		 * at the end.  Byte swap them as if this were
		 * a "version 1" header.
		 */
		offset += 4;			/* skip past interval */
		if (hdr->caplen < offset)
			return;
		uhdr->interval = SWAPLONG(uhdr->interval);

		offset += 4;			/* skip past start_frame */
		if (hdr->caplen < offset)
			return;
		uhdr->start_frame = SWAPLONG(uhdr->start_frame);

		offset += 4;			/* skip past xfer_flags */
		if (hdr->caplen < offset)
			return;
		uhdr->xfer_flags = SWAPLONG(uhdr->xfer_flags);

		offset += 4;			/* skip past ndesc */
		if (hdr->caplen < offset)
			return;
		uhdr->ndesc = SWAPLONG(uhdr->ndesc);

		if (uhdr->transfer_type == URB_ISOCHRONOUS) {
			/* swap the values in struct linux_usb_isodesc */
			usb_isodesc *pisodesc;
			uint32_t i;

			pisodesc = (usb_isodesc *)(void *)(buf+offset);
			for (i = 0; i < uhdr->ndesc; i++) {
				offset += 4;		/* skip past status */
				if (hdr->caplen < offset)
					return;
				pisodesc->status = SWAPLONG(pisodesc->status);

				offset += 4;		/* skip past offset */
				if (hdr->caplen < offset)
					return;
				pisodesc->offset = SWAPLONG(pisodesc->offset);

				offset += 4;		/* skip past len */
				if (hdr->caplen < offset)
					return;
				pisodesc->len = SWAPLONG(pisodesc->len);

				offset += 4;		/* skip past padding */

				pisodesc++;
			}
		}
	}
}

/*
 * The DLT_NFLOG "packets" have a mixture of big-endian and host-byte-order
 * data.  They begin with a fixed-length header with big-endian fields,
 * followed by a set of TLVs, where the type and length are in host
 * byte order but the values are either big-endian or are a raw byte
 * sequence that's the same regardless of the host's byte order.
 *
 * When reading a DLT_NFLOG packet, we need to convert the type and
 * length values from the byte order of the host that wrote the file
 * to the byte order of this host.
 */
static void
swap_nflog_header(const struct pcap_pkthdr *hdr, u_char *buf)
{
	u_char *p = buf;
	nflog_hdr_t *nfhdr = (nflog_hdr_t *)buf;
	nflog_tlv_t *tlv;
	u_int caplen = hdr->caplen;
	u_int length = hdr->len;
	uint16_t size;

	if (caplen < (u_int) sizeof(nflog_hdr_t) ||
	    length < (u_int) sizeof(nflog_hdr_t)) {
		/* Not enough data to have any TLVs. */
		return;
	}

	if (nfhdr->nflog_version != 0) {
		/* Unknown NFLOG version */
		return;
	}

	length -= sizeof(nflog_hdr_t);
	caplen -= sizeof(nflog_hdr_t);
	p += sizeof(nflog_hdr_t);

	while (caplen >= sizeof(nflog_tlv_t)) {
		tlv = (nflog_tlv_t *) p;

		/* Swap the type and length. */
		tlv->tlv_type = SWAPSHORT(tlv->tlv_type);
		tlv->tlv_length = SWAPSHORT(tlv->tlv_length);

		/* Get the length of the TLV. */
		size = tlv->tlv_length;
		if (size % 4 != 0)
			size += 4 - size % 4;

		/* Is the TLV's length less than the minimum? */
		if (size < sizeof(nflog_tlv_t)) {
			/* Yes. Give up now. */
			return;
		}

		/* Do we have enough data for the full TLV? */
		if (caplen < size || length < size) {
			/* No. */
			return;
		}

		/* Skip over the TLV. */
		length -= size;
		caplen -= size;
		p += size;
	}
}

static void
swap_pseudo_headers(int linktype, struct pcap_pkthdr *hdr, u_char *data)
{
	/*
	 * Convert pseudo-headers from the byte order of
	 * the host on which the file was saved to our
	 * byte order, as necessary.
	 */
	switch (linktype) {

	case DLT_PFLOG:
		swap_pflog_header(hdr, data);
		break;

	case DLT_LINUX_SLL:
		swap_linux_sll_header(hdr, data);
		break;

	case DLT_LINUX_SLL2:
		swap_linux_sll2_header(hdr, data);
		break;

	case DLT_USB_LINUX:
		swap_linux_usb_header(hdr, data, 0);
		break;

	case DLT_USB_LINUX_MMAPPED:
		swap_linux_usb_header(hdr, data, 1);
		break;

	case DLT_NFLOG:
		swap_nflog_header(hdr, data);
		break;
	}
}

void
pcap_post_process(int linktype, int swapped, struct pcap_pkthdr *hdr,
    u_char *data)
{
	if (swapped)
		swap_pseudo_headers(linktype, hdr, data);

	fixup_pcap_pkthdr(linktype, hdr, data);
}

void
fixup_pcap_pkthdr(int linktype, struct pcap_pkthdr *hdr, const u_char *data)
{
	const pcap_usb_header_mmapped *usb_hdr;

	usb_hdr = (const pcap_usb_header_mmapped *) data;
	if (linktype == DLT_USB_LINUX_MMAPPED &&
	    hdr->caplen >= sizeof (pcap_usb_header_mmapped)) {
		/*
		 * In older versions of libpcap, in memory-mapped captures,
		 * the "on-the-bus length" for completion events for
		 * incoming isochronous transfers was miscalculated; it
		 * needed to be calculated based on the* offsets and lengths
		 * in the descriptors, not on the raw URB length, but it
		 * wasn't.
		 *
		 * If this packet contains transferred data (yes, data_flag
		 * is 0 if we *do* have data), and the total on-the-network
		 * length is equal to the value calculated from the raw URB
		 * length, then it might be one of those transfers.
		 *
		 * We only do this if we have the full USB pseudo-header.
		 */
		if (!usb_hdr->data_flag &&
		    hdr->len == sizeof(pcap_usb_header_mmapped) +
		      (usb_hdr->ndesc * sizeof (usb_isodesc)) + usb_hdr->urb_len) {
			/*
			 * It might need fixing; fix it if it's a completion
			 * event for an incoming isochronous transfer.
			 */
			fix_linux_usb_mmapped_length(hdr, data);
		}
	}
}