/src/wireshark/wiretap/packetlogger.c
Line | Count | Source |
1 | | /* packetlogger.c |
2 | | * Routines for opening Apple's (Bluetooth) PacketLogger file format captures |
3 | | * Copyright 2008-2009, Stephen Fisher (see AUTHORS file) |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * Based on commview.c, Linux's BlueZ-Gnome Analyzer program and hexdumps of |
10 | | * the output files from Apple's PacketLogger tool. |
11 | | * |
12 | | * SPDX-License-Identifier: GPL-2.0-or-later |
13 | | */ |
14 | | |
15 | | #include "config.h" |
16 | | #include "packetlogger.h" |
17 | | |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | |
21 | | #include "wtap_module.h" |
22 | | #include "file_wrappers.h" |
23 | | |
24 | | typedef struct { |
25 | | bool byte_swapped; |
26 | | } packetlogger_t; |
27 | | |
28 | | typedef struct packetlogger_header { |
29 | | uint32_t len; |
30 | | uint32_t ts_secs; |
31 | | uint32_t ts_usecs; |
32 | | } packetlogger_header_t; |
33 | | |
34 | | /* Packet types. */ |
35 | 0 | #define PKT_HCI_COMMAND 0x00 |
36 | 0 | #define PKT_HCI_EVENT 0x01 |
37 | 0 | #define PKT_SENT_ACL_DATA 0x02 |
38 | 0 | #define PKT_RECV_ACL_DATA 0x03 |
39 | 0 | #define PKT_SENT_SCO_DATA 0x08 |
40 | 0 | #define PKT_RECV_SCO_DATA 0x09 |
41 | 0 | #define PKT_LMP_SEND 0x0A |
42 | 0 | #define PKT_LMP_RECV 0x0B |
43 | 0 | #define PKT_SYSLOG 0xF7 |
44 | 0 | #define PKT_KERNEL 0xF8 |
45 | 0 | #define PKT_KERNEL_DEBUG 0xF9 |
46 | 0 | #define PKT_ERROR 0xFA |
47 | 0 | #define PKT_POWER 0xFB |
48 | 0 | #define PKT_NOTE 0xFC |
49 | 0 | #define PKT_CONFIG 0xFD |
50 | 0 | #define PKT_NEW_CONTROLLER 0xFE |
51 | | |
52 | | static bool packetlogger_read(wtap *wth, wtap_rec *rec, |
53 | | int *err, char **err_info, |
54 | | int64_t *data_offset); |
55 | | static bool packetlogger_seek_read(wtap *wth, int64_t seek_off, |
56 | | wtap_rec *rec, |
57 | | int *err, char **err_info); |
58 | | static bool packetlogger_read_header(packetlogger_header_t *pl_hdr, |
59 | | FILE_T fh, bool byte_swapped, |
60 | | int *err, char **err_info); |
61 | | static void packetlogger_byte_swap_header(packetlogger_header_t *pl_hdr); |
62 | | static wtap_open_return_val packetlogger_check_record(wtap *wth, |
63 | | packetlogger_header_t *pl_hdr, |
64 | | int *err, |
65 | | char **err_info); |
66 | | static bool packetlogger_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec, |
67 | | int *err, |
68 | | char **err_info); |
69 | | |
70 | | static int packetlogger_file_type_subtype = -1; |
71 | | |
72 | | void register_packetlogger(void); |
73 | | |
74 | | /* |
75 | | * Number of packets to try reading. |
76 | | */ |
77 | 0 | #define PACKETS_TO_CHECK 5 |
78 | | |
79 | | wtap_open_return_val packetlogger_open(wtap *wth, int *err, char **err_info) |
80 | 0 | { |
81 | 0 | bool byte_swapped = false; |
82 | 0 | packetlogger_header_t pl_hdr; |
83 | 0 | wtap_open_return_val ret; |
84 | 0 | packetlogger_t *packetlogger; |
85 | | |
86 | | /* |
87 | | * Try to read the first record. |
88 | | */ |
89 | 0 | if(!packetlogger_read_header(&pl_hdr, wth->fh, byte_swapped, |
90 | 0 | err, err_info)) { |
91 | | /* |
92 | | * Either an immediate EOF or a short read indicates |
93 | | * that the file is probably not a PacketLogger file. |
94 | | */ |
95 | 0 | if (*err != 0 && *err != WTAP_ERR_SHORT_READ) |
96 | 0 | return WTAP_OPEN_ERROR; |
97 | 0 | return WTAP_OPEN_NOT_MINE; |
98 | 0 | } |
99 | | |
100 | | /* |
101 | | * If the upper 16 bits of the length are non-zero and the lower |
102 | | * 16 bits are zero, assume the file is byte-swapped from our |
103 | | * byte order. |
104 | | */ |
105 | 0 | if ((pl_hdr.len & 0x0000FFFF) == 0 && |
106 | 0 | (pl_hdr.len & 0xFFFF0000) != 0) { |
107 | | /* |
108 | | * Byte-swap the header. |
109 | | */ |
110 | 0 | packetlogger_byte_swap_header(&pl_hdr); |
111 | 0 | byte_swapped = true; |
112 | 0 | } |
113 | | |
114 | | /* |
115 | | * Check whether the first record looks like a PacketLogger |
116 | | * record. |
117 | | */ |
118 | 0 | ret = packetlogger_check_record(wth, &pl_hdr, err, err_info); |
119 | 0 | if (ret != WTAP_OPEN_MINE) { |
120 | | /* |
121 | | * Either we got an error or it's not valid. |
122 | | */ |
123 | 0 | if (ret == WTAP_OPEN_NOT_MINE) { |
124 | | /* |
125 | | * Not valid, so not a PacketLogger file. |
126 | | */ |
127 | 0 | return WTAP_OPEN_NOT_MINE; |
128 | 0 | } |
129 | | |
130 | | /* |
131 | | * Error. If it failed with a short read, we don't fail, |
132 | | * so we treat it as a valid file and can then report |
133 | | * it as a truncated file. |
134 | | */ |
135 | 0 | if (*err != WTAP_ERR_SHORT_READ) |
136 | 0 | return WTAP_OPEN_ERROR; |
137 | 0 | } else { |
138 | | /* |
139 | | * Now try reading a few more packets. |
140 | | */ |
141 | 0 | for (int i = 1; i < PACKETS_TO_CHECK; i++) { |
142 | | /* |
143 | | * Read and check the file header; we've already |
144 | | * decided whether this would be a byte-swapped file |
145 | | * or not, so we swap iff we decided it was. |
146 | | */ |
147 | 0 | if (!packetlogger_read_header(&pl_hdr, wth->fh, |
148 | 0 | byte_swapped, err, err_info)) { |
149 | 0 | if (*err == 0) { |
150 | | /* EOF; no more packets to try. */ |
151 | 0 | break; |
152 | 0 | } |
153 | | |
154 | | /* |
155 | | * A short read indicates that the file |
156 | | * is probably not a PacketLogger file. |
157 | | */ |
158 | 0 | if (*err != WTAP_ERR_SHORT_READ) |
159 | 0 | return WTAP_OPEN_ERROR; |
160 | 0 | return WTAP_OPEN_NOT_MINE; |
161 | 0 | } |
162 | | |
163 | | /* |
164 | | * Check whether this record looks like a PacketLogger |
165 | | * record. |
166 | | */ |
167 | 0 | ret = packetlogger_check_record(wth, &pl_hdr, err, |
168 | 0 | err_info); |
169 | 0 | if (ret != WTAP_OPEN_MINE) { |
170 | | /* |
171 | | * Either we got an error or it's not valid. |
172 | | */ |
173 | 0 | if (ret == WTAP_OPEN_NOT_MINE) { |
174 | | /* |
175 | | * Not valid, so not a PacketLogger |
176 | | * file. |
177 | | */ |
178 | 0 | return WTAP_OPEN_NOT_MINE; |
179 | 0 | } |
180 | | |
181 | | /* |
182 | | * Error. If it failed with a short read, |
183 | | * we don't fail, we just stop checking |
184 | | * records, so we treat it as a valid file |
185 | | * and can then report it as a truncated file. |
186 | | */ |
187 | 0 | if (*err != WTAP_ERR_SHORT_READ) |
188 | 0 | return WTAP_OPEN_ERROR; |
189 | 0 | break; |
190 | 0 | } |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | /* No file header. Reset the fh to 0 so we can read the first packet */ |
195 | 0 | if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) |
196 | 0 | return WTAP_OPEN_ERROR; |
197 | | |
198 | | /* This is a PacketLogger file */ |
199 | 0 | packetlogger = g_new(packetlogger_t, 1); |
200 | 0 | packetlogger->byte_swapped = byte_swapped; |
201 | 0 | wth->priv = (void *)packetlogger; |
202 | | |
203 | | /* Set up the pointers to the handlers for this file type */ |
204 | 0 | wth->subtype_read = packetlogger_read; |
205 | 0 | wth->subtype_seek_read = packetlogger_seek_read; |
206 | |
|
207 | 0 | wth->file_type_subtype = packetlogger_file_type_subtype; |
208 | 0 | wth->file_encap = WTAP_ENCAP_PACKETLOGGER; |
209 | 0 | wth->file_tsprec = WTAP_TSPREC_USEC; |
210 | | |
211 | | /* |
212 | | * Add an IDB; we don't know how many interfaces were |
213 | | * involved, so we just say one interface, about which |
214 | | * we only know the link-layer type, snapshot length, |
215 | | * and time stamp resolution. |
216 | | */ |
217 | 0 | wtap_add_generated_idb(wth); |
218 | |
|
219 | 0 | return WTAP_OPEN_MINE; /* Our kind of file */ |
220 | 0 | } |
221 | | |
222 | | static bool |
223 | | packetlogger_read(wtap *wth, wtap_rec *rec, int *err, char **err_info, |
224 | | int64_t *data_offset) |
225 | 0 | { |
226 | 0 | *data_offset = file_tell(wth->fh); |
227 | |
|
228 | 0 | return packetlogger_read_packet(wth, wth->fh, rec, err, err_info); |
229 | 0 | } |
230 | | |
231 | | static bool |
232 | | packetlogger_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, |
233 | | int *err, char **err_info) |
234 | 0 | { |
235 | 0 | if(file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) |
236 | 0 | return false; |
237 | | |
238 | 0 | if(!packetlogger_read_packet(wth, wth->random_fh, rec, err, err_info)) { |
239 | 0 | if(*err == 0) |
240 | 0 | *err = WTAP_ERR_SHORT_READ; |
241 | |
|
242 | 0 | return false; |
243 | 0 | } |
244 | 0 | return true; |
245 | 0 | } |
246 | | |
247 | | static bool |
248 | | packetlogger_read_header(packetlogger_header_t *pl_hdr, FILE_T fh, |
249 | | bool byte_swapped, int *err, char **err_info) |
250 | 0 | { |
251 | 0 | if (!wtap_read_bytes_or_eof(fh, &pl_hdr->len, 4, err, err_info)) |
252 | 0 | return false; |
253 | 0 | if (!wtap_read_bytes(fh, &pl_hdr->ts_secs, 4, err, err_info)) |
254 | 0 | return false; |
255 | 0 | if (!wtap_read_bytes(fh, &pl_hdr->ts_usecs, 4, err, err_info)) |
256 | 0 | return false; |
257 | | |
258 | | /* Convert multi-byte values to host endian */ |
259 | 0 | if (byte_swapped) |
260 | 0 | packetlogger_byte_swap_header(pl_hdr); |
261 | |
|
262 | 0 | return true; |
263 | 0 | } |
264 | | |
265 | | static void |
266 | | packetlogger_byte_swap_header(packetlogger_header_t *pl_hdr) |
267 | 0 | { |
268 | 0 | pl_hdr->len = GUINT32_SWAP_LE_BE(pl_hdr->len); |
269 | 0 | pl_hdr->ts_secs = GUINT32_SWAP_LE_BE(pl_hdr->ts_secs); |
270 | 0 | pl_hdr->ts_usecs = GUINT32_SWAP_LE_BE(pl_hdr->ts_usecs); |
271 | 0 | } |
272 | | |
273 | | static wtap_open_return_val |
274 | | packetlogger_check_record(wtap *wth, packetlogger_header_t *pl_hdr, int *err, |
275 | | char **err_info) |
276 | 0 | { |
277 | 0 | uint32_t length; |
278 | 0 | uint8_t type; |
279 | | |
280 | | /* Is the header length valid? If not, assume it's not ours. */ |
281 | 0 | if (pl_hdr->len < 8 || pl_hdr->len >= 65536) |
282 | 0 | return WTAP_OPEN_NOT_MINE; |
283 | | |
284 | | /* Is the microseconds field of the time stap out of range? */ |
285 | 0 | if (pl_hdr->ts_usecs >= 1000000) |
286 | 0 | return WTAP_OPEN_NOT_MINE; |
287 | | |
288 | | /* |
289 | | * If we have any payload, it's a type field; read and check it. |
290 | | */ |
291 | 0 | length = pl_hdr->len - 8; |
292 | 0 | if (length != 0) { |
293 | | /* |
294 | | * Check the type field. |
295 | | */ |
296 | 0 | if (!wtap_read_bytes(wth->fh, &type, 1, err, err_info)) { |
297 | 0 | if (*err != WTAP_ERR_SHORT_READ) |
298 | 0 | return WTAP_OPEN_ERROR; |
299 | 0 | return WTAP_OPEN_NOT_MINE; |
300 | 0 | } |
301 | | |
302 | | /* Verify this file belongs to us */ |
303 | 0 | switch (type) { |
304 | | |
305 | 0 | case PKT_HCI_COMMAND: |
306 | 0 | case PKT_HCI_EVENT: |
307 | 0 | case PKT_SENT_ACL_DATA: |
308 | 0 | case PKT_RECV_ACL_DATA: |
309 | 0 | case PKT_SENT_SCO_DATA: |
310 | 0 | case PKT_RECV_SCO_DATA: |
311 | 0 | case PKT_LMP_SEND: |
312 | 0 | case PKT_LMP_RECV: |
313 | 0 | case PKT_SYSLOG: |
314 | 0 | case PKT_KERNEL: |
315 | 0 | case PKT_KERNEL_DEBUG: |
316 | 0 | case PKT_ERROR: |
317 | 0 | case PKT_POWER: |
318 | 0 | case PKT_NOTE: |
319 | 0 | case PKT_CONFIG: |
320 | 0 | case PKT_NEW_CONTROLLER: |
321 | 0 | break; |
322 | | |
323 | 0 | default: |
324 | 0 | return WTAP_OPEN_NOT_MINE; |
325 | 0 | } |
326 | | |
327 | 0 | length--; |
328 | |
|
329 | 0 | if (length != 0) { |
330 | | /* |
331 | | * Now try to read past the rest of the packet bytes; |
332 | | * if that fails with a short read, we don't fail, |
333 | | * so that we can report the file as a truncated |
334 | | * PacketLogger file. |
335 | | */ |
336 | 0 | if (!wtap_read_bytes(wth->fh, NULL, length, |
337 | 0 | err, err_info)) |
338 | 0 | return WTAP_OPEN_ERROR; |
339 | 0 | } |
340 | 0 | } |
341 | 0 | return WTAP_OPEN_MINE; |
342 | 0 | } |
343 | | |
344 | | static bool |
345 | | packetlogger_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec, |
346 | | int *err, char **err_info) |
347 | 0 | { |
348 | 0 | packetlogger_t *packetlogger = (packetlogger_t *)wth->priv; |
349 | 0 | packetlogger_header_t pl_hdr; |
350 | |
|
351 | 0 | if(!packetlogger_read_header(&pl_hdr, fh, packetlogger->byte_swapped, |
352 | 0 | err, err_info)) |
353 | 0 | return false; |
354 | | |
355 | 0 | if (pl_hdr.len < 8) { |
356 | 0 | *err = WTAP_ERR_BAD_FILE; |
357 | 0 | *err_info = ws_strdup_printf("packetlogger: record length %u is too small", pl_hdr.len); |
358 | 0 | return false; |
359 | 0 | } |
360 | 0 | if (pl_hdr.len - 8 > WTAP_MAX_PACKET_SIZE_STANDARD) { |
361 | | /* |
362 | | * Probably a corrupt capture file; don't blow up trying |
363 | | * to allocate space for an immensely-large packet. |
364 | | */ |
365 | 0 | *err = WTAP_ERR_BAD_FILE; |
366 | 0 | *err_info = ws_strdup_printf("packetlogger: File has %u-byte packet, bigger than maximum of %u", |
367 | 0 | pl_hdr.len - 8, WTAP_MAX_PACKET_SIZE_STANDARD); |
368 | 0 | return false; |
369 | 0 | } |
370 | | |
371 | 0 | wtap_setup_packet_rec(rec, wth->file_encap); |
372 | 0 | rec->block = wtap_block_create(WTAP_BLOCK_PACKET); |
373 | 0 | rec->presence_flags = WTAP_HAS_TS; |
374 | |
|
375 | 0 | rec->rec_header.packet_header.len = pl_hdr.len - 8; |
376 | 0 | rec->rec_header.packet_header.caplen = pl_hdr.len - 8; |
377 | |
|
378 | 0 | rec->ts.secs = (time_t)pl_hdr.ts_secs; |
379 | 0 | rec->ts.nsecs = (int)(pl_hdr.ts_usecs * 1000); |
380 | |
|
381 | 0 | return wtap_read_bytes_buffer(fh, &rec->data, rec->rec_header.packet_header.caplen, err, err_info); |
382 | 0 | } |
383 | | |
384 | | static const struct supported_block_type packetlogger_blocks_supported[] = { |
385 | | /* |
386 | | * We support packet blocks, with no comments or other options. |
387 | | */ |
388 | | { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } |
389 | | }; |
390 | | |
391 | | static const struct file_type_subtype_info packetlogger_info = { |
392 | | "macOS PacketLogger", "pklg", "pklg", NULL, |
393 | | false, BLOCKS_SUPPORTED(packetlogger_blocks_supported), |
394 | | NULL, NULL, NULL |
395 | | }; |
396 | | |
397 | | void register_packetlogger(void) |
398 | 14 | { |
399 | 14 | packetlogger_file_type_subtype = wtap_register_file_type_subtype(&packetlogger_info); |
400 | | |
401 | | /* |
402 | | * Register name for backwards compatibility with the |
403 | | * wtap_filetypes table in Lua. |
404 | | */ |
405 | 14 | wtap_register_backwards_compatibility_lua_name("PACKETLOGGER", |
406 | 14 | packetlogger_file_type_subtype); |
407 | 14 | } |
408 | | |
409 | | /* |
410 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
411 | | * |
412 | | * Local variables: |
413 | | * c-basic-offset: 8 |
414 | | * tab-width: 8 |
415 | | * indent-tabs-mode: t |
416 | | * End: |
417 | | * |
418 | | * vi: set shiftwidth=8 tabstop=8 noexpandtab: |
419 | | * :indentSize=8:tabSize=8:noTabs=false: |
420 | | */ |