/src/openvswitch/lib/pcap-file.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2009, 2010, 2012, 2013, 2014, 2015 Nicira, Inc. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at: |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <config.h> |
18 | | #include "pcap-file.h" |
19 | | #include <errno.h> |
20 | | #include <inttypes.h> |
21 | | #include <stdlib.h> |
22 | | #include <string.h> |
23 | | #include <sys/stat.h> |
24 | | #include "byte-order.h" |
25 | | #include "compiler.h" |
26 | | #include "dp-packet.h" |
27 | | #include "flow.h" |
28 | | #include "openvswitch/hmap.h" |
29 | | #include "packets.h" |
30 | | #include "timeval.h" |
31 | | #include "unaligned.h" |
32 | | #include "util.h" |
33 | | #include "openvswitch/vlog.h" |
34 | | |
35 | | VLOG_DEFINE_THIS_MODULE(pcap); |
36 | | |
37 | | enum ts_resolution { |
38 | | PCAP_USEC, |
39 | | PCAP_NSEC, |
40 | | }; |
41 | | |
42 | | enum network_type { |
43 | | PCAP_ETHERNET = 1, |
44 | | PCAP_LINUX_SLL = 0x71 |
45 | | }; |
46 | | |
47 | | struct pcap_file { |
48 | | FILE *file; |
49 | | enum ts_resolution resolution; |
50 | | enum network_type network; |
51 | | }; |
52 | | |
53 | | struct pcap_hdr { |
54 | | uint32_t magic_number; /* magic number */ |
55 | | uint16_t version_major; /* major version number */ |
56 | | uint16_t version_minor; /* minor version number */ |
57 | | int32_t thiszone; /* GMT to local correction */ |
58 | | uint32_t sigfigs; /* accuracy of timestamps */ |
59 | | uint32_t snaplen; /* max length of captured packets */ |
60 | | uint32_t network; /* data link type */ |
61 | | }; |
62 | | BUILD_ASSERT_DECL(sizeof(struct pcap_hdr) == 24); |
63 | | |
64 | | struct pcaprec_hdr { |
65 | | uint32_t ts_sec; /* timestamp seconds */ |
66 | | uint32_t ts_subsec; /* timestamp subseconds */ |
67 | | uint32_t incl_len; /* number of octets of packet saved in file */ |
68 | | uint32_t orig_len; /* actual length of packet */ |
69 | | }; |
70 | | BUILD_ASSERT_DECL(sizeof(struct pcaprec_hdr) == 16); |
71 | | |
72 | | struct pcap_file * |
73 | | ovs_pcap_open(const char *file_name, const char *mode) |
74 | 0 | { |
75 | 0 | struct stat s; |
76 | 0 | struct pcap_file *p_file; |
77 | 0 | int error; |
78 | |
|
79 | 0 | ovs_assert(!strcmp(mode, "rb") || |
80 | 0 | !strcmp(mode, "wb") || |
81 | 0 | !strcmp(mode, "ab")); |
82 | |
|
83 | 0 | p_file = xmalloc(sizeof *p_file); |
84 | 0 | p_file->file = fopen(file_name, mode); |
85 | 0 | p_file->resolution = PCAP_USEC; |
86 | 0 | if (p_file->file == NULL) { |
87 | 0 | VLOG_WARN("%s: failed to open pcap file for %s (%s)", file_name, |
88 | 0 | (mode[0] == 'r' ? "reading" |
89 | 0 | : mode[0] == 'w' ? "writing" |
90 | 0 | : "appending"), |
91 | 0 | ovs_strerror(errno)); |
92 | 0 | free(p_file); |
93 | 0 | return NULL; |
94 | 0 | } |
95 | | |
96 | 0 | switch (mode[0]) { |
97 | 0 | case 'r': |
98 | 0 | error = ovs_pcap_read_header(p_file); |
99 | 0 | if (error) { |
100 | 0 | errno = error; |
101 | 0 | ovs_pcap_close(p_file); |
102 | 0 | return NULL; |
103 | 0 | } |
104 | 0 | break; |
105 | | |
106 | 0 | case 'w': |
107 | 0 | ovs_pcap_write_header(p_file); |
108 | 0 | break; |
109 | | |
110 | 0 | case 'a': |
111 | 0 | if (!fstat(fileno(p_file->file), &s) && !s.st_size) { |
112 | 0 | ovs_pcap_write_header(p_file); |
113 | 0 | } |
114 | 0 | break; |
115 | | |
116 | 0 | default: |
117 | 0 | OVS_NOT_REACHED(); |
118 | 0 | } |
119 | | |
120 | 0 | return p_file; |
121 | 0 | } |
122 | | |
123 | | struct pcap_file * |
124 | | ovs_pcap_stdout(void) |
125 | 0 | { |
126 | 0 | struct pcap_file *p_file = xmalloc(sizeof *p_file); |
127 | 0 | p_file->file = stdout; |
128 | 0 | return p_file; |
129 | 0 | } |
130 | | |
131 | | int |
132 | | ovs_pcap_read_header(struct pcap_file *p_file) |
133 | 0 | { |
134 | 0 | struct pcap_hdr ph; |
135 | 0 | if (fread(&ph, sizeof ph, 1, p_file->file) != 1) { |
136 | 0 | int error = ferror(p_file->file) ? errno : EOF; |
137 | 0 | VLOG_WARN("failed to read pcap header: %s", ovs_retval_to_string(error)); |
138 | 0 | return error; |
139 | 0 | } |
140 | 0 | bool byte_swap; |
141 | 0 | if (ph.magic_number == 0xa1b2c3d4 || ph.magic_number == 0xd4c3b2a1) { |
142 | 0 | byte_swap = ph.magic_number == 0xd4c3b2a1; |
143 | 0 | p_file->resolution = PCAP_USEC; |
144 | 0 | } else if (ph.magic_number == 0xa1b23c4d || |
145 | 0 | ph.magic_number == 0x4d3cb2a1) { |
146 | 0 | byte_swap = ph.magic_number == 0x4d3cb2a1; |
147 | 0 | p_file->resolution = PCAP_NSEC; |
148 | 0 | } else { |
149 | 0 | VLOG_WARN("bad magic 0x%08"PRIx32" reading pcap file " |
150 | 0 | "(expected 0xa1b2c3d4, 0xa1b23c4d, 0xd4c3b2a1, " |
151 | 0 | "or 0x4d3cb2a1)", ph.magic_number); |
152 | 0 | return EPROTO; |
153 | 0 | } |
154 | 0 | p_file->network = byte_swap ? uint32_byteswap(ph.network) : ph.network; |
155 | 0 | if (p_file->network != PCAP_ETHERNET && |
156 | 0 | p_file->network != PCAP_LINUX_SLL) { |
157 | 0 | VLOG_WARN("unknown network type %u reading pcap file", |
158 | 0 | (unsigned int) p_file->network); |
159 | 0 | return EPROTO; |
160 | 0 | } |
161 | 0 | return 0; |
162 | 0 | } |
163 | | |
164 | | void |
165 | | ovs_pcap_write_header(struct pcap_file *p_file) |
166 | 0 | { |
167 | | /* The pcap reader is responsible for figuring out endianness based on the |
168 | | * magic number, so the lack of htonX calls here is intentional. */ |
169 | 0 | struct pcap_hdr ph; |
170 | 0 | ph.magic_number = 0xa1b2c3d4; |
171 | 0 | ph.version_major = 2; |
172 | 0 | ph.version_minor = 4; |
173 | 0 | ph.thiszone = 0; |
174 | 0 | ph.sigfigs = 0; |
175 | 0 | ph.snaplen = 1518; |
176 | 0 | ph.network = 1; /* Ethernet */ |
177 | 0 | ovs_ignore(fwrite(&ph, sizeof ph, 1, p_file->file)); |
178 | 0 | fflush(p_file->file); |
179 | 0 | } |
180 | | |
181 | | int |
182 | | ovs_pcap_read(struct pcap_file *p_file, struct dp_packet **bufp, |
183 | | long long int *when) |
184 | 0 | { |
185 | 0 | struct pcaprec_hdr prh; |
186 | 0 | struct dp_packet *buf; |
187 | 0 | void *data; |
188 | 0 | size_t len; |
189 | 0 | bool swap; |
190 | |
|
191 | 0 | *bufp = NULL; |
192 | | |
193 | | /* Read header. */ |
194 | 0 | if (fread(&prh, sizeof prh, 1, p_file->file) != 1) { |
195 | 0 | if (ferror(p_file->file)) { |
196 | 0 | int error = errno; |
197 | 0 | VLOG_WARN("failed to read pcap record header: %s", |
198 | 0 | ovs_retval_to_string(error)); |
199 | 0 | return error; |
200 | 0 | } else { |
201 | 0 | return EOF; |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | /* Calculate length. */ |
206 | 0 | len = prh.incl_len; |
207 | 0 | swap = len > 0xffff; |
208 | 0 | if (swap) { |
209 | 0 | len = uint32_byteswap(len); |
210 | 0 | if (len > 0xffff) { |
211 | 0 | VLOG_WARN("bad packet length %"PRIuSIZE" or %"PRIu32" " |
212 | 0 | "reading pcap file", |
213 | 0 | len, uint32_byteswap(len)); |
214 | 0 | return EPROTO; |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | | /* Calculate time. */ |
219 | 0 | if (when) { |
220 | 0 | uint32_t ts_sec = swap ? uint32_byteswap(prh.ts_sec) : prh.ts_sec; |
221 | 0 | uint32_t ts_subsec = swap ? uint32_byteswap(prh.ts_subsec) |
222 | 0 | : prh.ts_subsec; |
223 | 0 | ts_subsec = p_file->resolution == PCAP_USEC ? ts_subsec / 1000 |
224 | 0 | : ts_subsec / 1000000; |
225 | 0 | *when = ts_sec * 1000LL + ts_subsec; |
226 | 0 | } |
227 | | |
228 | | /* Read packet. Packet type is Ethernet */ |
229 | 0 | buf = dp_packet_new(len); |
230 | 0 | data = dp_packet_put_uninit(buf, len); |
231 | 0 | if (fread(data, len, 1, p_file->file) != 1) { |
232 | 0 | int error = ferror(p_file->file) ? errno : EOF; |
233 | 0 | VLOG_WARN("failed to read pcap packet: %s", |
234 | 0 | ovs_retval_to_string(error)); |
235 | 0 | dp_packet_delete(buf); |
236 | 0 | return error; |
237 | 0 | } |
238 | | |
239 | 0 | if (p_file->network == PCAP_LINUX_SLL) { |
240 | | /* This format doesn't include the destination Ethernet address, which |
241 | | * is weird. */ |
242 | |
|
243 | 0 | struct sll_header { |
244 | 0 | ovs_be16 packet_type; |
245 | 0 | ovs_be16 arp_hrd; |
246 | 0 | ovs_be16 lla_len; |
247 | 0 | struct eth_addr dl_src; |
248 | 0 | ovs_be16 reserved; |
249 | 0 | ovs_be16 protocol; |
250 | 0 | }; |
251 | 0 | const struct sll_header *sll; |
252 | 0 | if (len < sizeof *sll) { |
253 | 0 | VLOG_WARN("pcap packet too short for SLL header"); |
254 | 0 | dp_packet_delete(buf); |
255 | 0 | return EPROTO; |
256 | 0 | } |
257 | | |
258 | | /* Pull Linux SLL header. */ |
259 | 0 | sll = dp_packet_pull(buf, sizeof *sll); |
260 | 0 | if (sll->lla_len != htons(6)) { |
261 | 0 | ovs_hex_dump(stdout, sll, sizeof *sll, 0, false); |
262 | 0 | VLOG_WARN("bad SLL header"); |
263 | 0 | dp_packet_delete(buf); |
264 | 0 | return EPROTO; |
265 | 0 | } |
266 | | |
267 | | /* Push Ethernet header. */ |
268 | 0 | struct eth_header eth = { |
269 | | /* eth_dst is all zeros because the format doesn't include it. */ |
270 | 0 | .eth_src = sll->dl_src, |
271 | 0 | .eth_type = sll->protocol, |
272 | 0 | }; |
273 | 0 | dp_packet_push(buf, ð, sizeof eth); |
274 | 0 | } |
275 | | |
276 | 0 | *bufp = buf; |
277 | 0 | return 0; |
278 | 0 | } |
279 | | |
280 | | void |
281 | | ovs_pcap_write(struct pcap_file *p_file, struct dp_packet *buf) |
282 | 0 | { |
283 | 0 | const void *data_dp = dp_packet_data(buf); |
284 | 0 | struct pcaprec_hdr prh; |
285 | 0 | struct timeval tv; |
286 | |
|
287 | 0 | ovs_assert(dp_packet_is_eth(buf)); |
288 | 0 | ovs_assert(data_dp); |
289 | |
|
290 | 0 | xgettimeofday(&tv); |
291 | 0 | prh.ts_sec = tv.tv_sec; |
292 | 0 | prh.ts_subsec = tv.tv_usec; |
293 | 0 | prh.incl_len = dp_packet_size(buf); |
294 | 0 | prh.orig_len = dp_packet_size(buf); |
295 | 0 | ovs_ignore(fwrite(&prh, sizeof prh, 1, p_file->file)); |
296 | 0 | ovs_ignore(fwrite(data_dp, dp_packet_size(buf), 1, p_file->file)); |
297 | 0 | fflush(p_file->file); |
298 | 0 | } |
299 | | |
300 | | void |
301 | | ovs_pcap_close(struct pcap_file *p_file) |
302 | 0 | { |
303 | 0 | if (p_file->file != stdout) { |
304 | 0 | fclose(p_file->file); |
305 | 0 | } |
306 | 0 | free(p_file); |
307 | 0 | } |
308 | | |
309 | | struct tcp_key { |
310 | | ovs_be32 nw_src, nw_dst; |
311 | | ovs_be16 tp_src, tp_dst; |
312 | | }; |
313 | | |
314 | | struct tcp_stream { |
315 | | struct hmap_node hmap_node; |
316 | | struct tcp_key key; |
317 | | uint32_t seq_no; |
318 | | struct dp_packet payload; |
319 | | }; |
320 | | |
321 | | struct tcp_reader { |
322 | | struct hmap streams; |
323 | | }; |
324 | | |
325 | | static void |
326 | | tcp_stream_destroy(struct tcp_reader *r, struct tcp_stream *stream) |
327 | 0 | { |
328 | 0 | hmap_remove(&r->streams, &stream->hmap_node); |
329 | 0 | dp_packet_uninit(&stream->payload); |
330 | 0 | free(stream); |
331 | 0 | } |
332 | | |
333 | | /* Returns a new data structure for extracting TCP stream data from an |
334 | | * Ethernet packet capture */ |
335 | | struct tcp_reader * |
336 | | tcp_reader_open(void) |
337 | 0 | { |
338 | 0 | struct tcp_reader *r; |
339 | |
|
340 | 0 | r = xmalloc(sizeof *r); |
341 | 0 | hmap_init(&r->streams); |
342 | 0 | return r; |
343 | 0 | } |
344 | | |
345 | | /* Closes and frees 'r'. */ |
346 | | void |
347 | | tcp_reader_close(struct tcp_reader *r) |
348 | 0 | { |
349 | 0 | struct tcp_stream *stream; |
350 | |
|
351 | 0 | HMAP_FOR_EACH_SAFE (stream, hmap_node, &r->streams) { |
352 | 0 | tcp_stream_destroy(r, stream); |
353 | 0 | } |
354 | 0 | hmap_destroy(&r->streams); |
355 | 0 | free(r); |
356 | 0 | } |
357 | | |
358 | | static struct tcp_stream * |
359 | | tcp_stream_lookup(struct tcp_reader *r, |
360 | | const struct tcp_key *key, uint32_t hash) |
361 | 0 | { |
362 | 0 | struct tcp_stream *stream; |
363 | |
|
364 | 0 | HMAP_FOR_EACH_WITH_HASH (stream, hmap_node, hash, &r->streams) { |
365 | 0 | if (!memcmp(&stream->key, key, sizeof *key)) { |
366 | 0 | return stream; |
367 | 0 | } |
368 | 0 | } |
369 | 0 | return NULL; |
370 | 0 | } |
371 | | |
372 | | static struct tcp_stream * |
373 | | tcp_stream_new(struct tcp_reader *r, const struct tcp_key *key, uint32_t hash) |
374 | 0 | { |
375 | 0 | struct tcp_stream *stream; |
376 | |
|
377 | 0 | stream = xmalloc(sizeof *stream); |
378 | 0 | hmap_insert(&r->streams, &stream->hmap_node, hash); |
379 | 0 | memcpy(&stream->key, key, sizeof *key); |
380 | 0 | stream->seq_no = 0; |
381 | 0 | dp_packet_init(&stream->payload, 2048); |
382 | 0 | return stream; |
383 | 0 | } |
384 | | |
385 | | /* Processes 'packet' through TCP reader 'r'. The caller must have already |
386 | | * extracted the packet's headers into 'flow', using flow_extract(). |
387 | | * |
388 | | * If 'packet' is a TCP packet, then the reader attempts to reconstruct the |
389 | | * data stream. If successful, it returns an dp_packet that represents the data |
390 | | * stream so far. The caller may examine the data in the dp_packet and pull off |
391 | | * any data that it has fully processed. The remaining data that the caller |
392 | | * does not pull off will be presented again in future calls if more data |
393 | | * arrives in the stream. |
394 | | * |
395 | | * Returns null if 'packet' doesn't add new data to a TCP stream. */ |
396 | | struct dp_packet * |
397 | | tcp_reader_run(struct tcp_reader *r, const struct flow *flow, |
398 | | const struct dp_packet *packet) |
399 | 0 | { |
400 | 0 | struct tcp_stream *stream; |
401 | 0 | struct tcp_header *tcp; |
402 | 0 | struct dp_packet *payload; |
403 | 0 | unsigned int l7_length; |
404 | 0 | struct tcp_key key; |
405 | 0 | uint32_t hash; |
406 | 0 | uint32_t seq; |
407 | 0 | uint8_t flags; |
408 | 0 | const char *l7 = dp_packet_get_tcp_payload(packet); |
409 | |
|
410 | 0 | if (flow->dl_type != htons(ETH_TYPE_IP) |
411 | 0 | || flow->nw_proto != IPPROTO_TCP |
412 | 0 | || !l7) { |
413 | 0 | return NULL; |
414 | 0 | } |
415 | 0 | tcp = dp_packet_l4(packet); |
416 | 0 | flags = TCP_FLAGS(tcp->tcp_ctl); |
417 | 0 | l7_length = dp_packet_get_tcp_payload_length(packet); |
418 | 0 | seq = ntohl(get_16aligned_be32(&tcp->tcp_seq)); |
419 | | |
420 | | /* Construct key. */ |
421 | 0 | memset(&key, 0, sizeof key); |
422 | 0 | key.nw_src = flow->nw_src; |
423 | 0 | key.nw_dst = flow->nw_dst; |
424 | 0 | key.tp_src = flow->tp_src; |
425 | 0 | key.tp_dst = flow->tp_dst; |
426 | 0 | hash = hash_bytes(&key, sizeof key, 0); |
427 | | |
428 | | /* Find existing stream or start a new one for a SYN or if there's data. */ |
429 | 0 | stream = tcp_stream_lookup(r, &key, hash); |
430 | 0 | if (!stream) { |
431 | 0 | if (flags & TCP_SYN || l7_length) { |
432 | 0 | stream = tcp_stream_new(r, &key, hash); |
433 | 0 | stream->seq_no = flags & TCP_SYN ? seq + 1 : seq; |
434 | 0 | } else { |
435 | 0 | return NULL; |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | 0 | payload = &stream->payload; |
440 | 0 | if (flags & TCP_SYN || !stream->seq_no) { |
441 | 0 | dp_packet_clear(payload); |
442 | 0 | stream->seq_no = seq + 1; |
443 | 0 | return NULL; |
444 | 0 | } else if (flags & (TCP_FIN | TCP_RST)) { |
445 | 0 | tcp_stream_destroy(r, stream); |
446 | 0 | return NULL; |
447 | 0 | } else if (seq == stream->seq_no) { |
448 | | /* Shift all of the existing payload to the very beginning of the |
449 | | * allocated space, so that we reuse allocated space instead of |
450 | | * continually expanding it. */ |
451 | 0 | dp_packet_shift(payload, (char *) dp_packet_base(payload) - (char *) dp_packet_data(payload)); |
452 | |
|
453 | 0 | dp_packet_put(payload, l7, l7_length); |
454 | 0 | stream->seq_no += l7_length; |
455 | 0 | return payload; |
456 | 0 | } else { |
457 | | return NULL; |
458 | 0 | } |
459 | 0 | } |