Coverage Report

Created: 2026-03-02 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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, &eth, 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
}