Coverage Report

Created: 2026-06-30 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-iperf3.c
Line
Count
Source
1
/* packet-iperf3.c
2
 *
3
 * Routines for iPerf3 dissection
4
 * by Daniel Mendes <dmendes@redhat.com>,
5
 * Jaap Keuter <jaap.keuter@xs4all.nl>
6
 *
7
 * loosely based off iPerf2 dissector
8
 * by Anish Bhatt <anish@gatech.edu>
9
 * and the iperf3 source code at
10
 * https://github.com/esnet/iperf
11
 *
12
 * Wireshark - Network traffic analyzer
13
 * By Gerald Combs <gerald@wireshark.org>
14
 * Copyright 1998 Gerald Combs
15
 *
16
 * SPDX-License-Identifier: GPL-2.0-or-later
17
 */
18
19
#include "config.h"
20
#include <wireshark.h>
21
#include <epan/packet.h>
22
#include <epan/proto_data.h>
23
#include <epan/wmem_scopes.h>
24
#include <epan/expert.h>
25
#include <epan/prefs.h>
26
#include <epan/conversation.h>
27
#include <epan/dissectors/packet-tcp.h>
28
29
/* From iperf3 source code src/iperf_api.h */
30
#define TEST_START 1
31
#define TEST_RUNNING 2
32
#define RESULT_REQUEST 3 /* not used */
33
#define TEST_END 4
34
#define STREAM_BEGIN 5 /* not used */
35
#define STREAM_RUNNING 6 /* not used */
36
#define STREAM_END 7 /* not used */
37
#define ALL_STREAMS_END 8 /* not used */
38
0
#define PARAM_EXCHANGE 9
39
#define CREATE_STREAMS 10
40
#define SERVER_TERMINATE 11
41
#define CLIENT_TERMINATE 12
42
0
#define EXCHANGE_RESULTS 13
43
#define DISPLAY_RESULTS 14
44
#define IPERF_START 15
45
#define IPERF_DONE 16
46
#define ACCESS_DENIED (-1)
47
#define SERVER_ERROR (-2)
48
49
typedef enum
50
{
51
    INIT,
52
    GENERIC_STATE,
53
    PARAM_EXCHANGE_LENGTH,
54
    PARAM_EXCHANGE_JSON,
55
    EXCHANGE_RESULTS_LENGTH_1,
56
    EXCHANGE_RESULTS_JSON_1,
57
    EXCHANGE_RESULTS_LENGTH_2,
58
    EXCHANGE_RESULTS_JSON_2,
59
    DATA
60
} pdu_sequence;
61
62
void proto_register_iperf3(void);
63
void proto_reg_handoff_iperf3(void);
64
65
static int proto_iperf3;
66
67
static int hf_iperf3_sequence;
68
static int hf_iperf3_sec;
69
static int hf_iperf3_usec;
70
static int hf_iperf3_udp_init_msg;
71
static int hf_iperf3_state;
72
static int hf_iperf3_prejson;
73
static int hf_iperf3_cookie;
74
75
static int ett_iperf3;
76
static int ett_time;
77
78
static dissector_handle_t iperf3_handle_tcp;
79
static dissector_handle_t iperf3_handle_udp;
80
static dissector_handle_t json_handle;
81
82
static const value_string iperf3_state_vals[] = {
83
    { TEST_START,       "TEST_START" },
84
    { TEST_RUNNING,     "TEST_RUNNING" },
85
    { RESULT_REQUEST,   "RESULT_REQUEST" },
86
    { TEST_END,         "TEST_END" },
87
    { STREAM_BEGIN,     "STREAM_BEGIN" },
88
    { STREAM_RUNNING,   "STREAM_RUNNING" },
89
    { STREAM_END,       "STREAM_END" },
90
    { ALL_STREAMS_END,  "ALL_STREAMS_END" },
91
    { PARAM_EXCHANGE,   "PARAM_EXCHANGE" },
92
    { CREATE_STREAMS,   "CREATE_STREAMS" },
93
    { SERVER_TERMINATE, "SERVER_TERMINATE" },
94
    { CLIENT_TERMINATE, "CLIENT_TERMINATE" },
95
    { EXCHANGE_RESULTS, "EXCHANGE_RESULTS" },
96
    { DISPLAY_RESULTS,  "DISPLAY_RESULTS" },
97
    { IPERF_START,      "IPERF_START" },
98
    { IPERF_DONE,       "IPERF_DONE" },
99
    { ACCESS_DENIED,    "ACCESS_DENIED" },
100
    { SERVER_ERROR,     "SERVER_ERROR" },
101
    { 0, NULL }
102
};
103
104
/* TCP conversation */
105
typedef struct
106
{
107
    bool control_connection;
108
    // Ephemeral packet data
109
    unsigned pdu_size;
110
    pdu_sequence sequence;
111
} iperf3_tcp_conversation_data;
112
113
typedef struct
114
{
115
    unsigned pdu_size;
116
    pdu_sequence sequence;
117
} iperf3_tcp_packet_data;
118
119
/* UDP out-of-order tracking */
120
typedef struct {
121
    uint64_t prev_seq_no;
122
    wmem_map_t *out_of_order;
123
} udp_conversation_data;
124
125
static void udp_detect_and_report_out_of_order(packet_info *, proto_item *, uint64_t);
126
static udp_conversation_data *udp_set_conversation_data(packet_info *);
127
128
129
/* protocol preferences */
130
static bool iperf3_pref_64bit_seq_no;
131
static bool iperf3_pref_detect_udp_order = true;
132
/* expert info */
133
static expert_field ei_udp_out_of_order;
134
135
8
#define IPERF3_UDP_HDR_SIZE 12
136
4
#define COOKIE_SIZE 37
137
138
19
#define IPERF3_INIT_UDP_MSG_SIZE 4
139
140
22
#define UDP_CONNECT_MSG 0x36373839              // iperf3 doesn't htonl() convert either
141
22
#define UDP_CONNECT_REPLY 0x39383736            // the MSG or REPLY so we must accept
142
22
#define LEGACY_UDP_CONNECT_MSG 0x075bcd15       // accept either endian representation.
143
22
#define LEGACY_MSG_OPPOSITE_ENDIAN 0x15cd5b07   // luckily current msg and reply are
144
22
#define LEGACY_UDP_CONNECT_REPLY 0x3ade68b1     // already opposites. this is also why
145
11
#define LEGACY_REPLY_OPPOSITE_ENDIAN 0xb168de3a // we can't distinguish msg from reply
146
147
148
#define DEFINE_CONTROL_PREFACE(protocol)                                \
149
    static void col_info_preface_##protocol(packet_info *pinfo)         \
150
6
    {                                                                   \
151
6
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "iPerf3");              \
152
6
        col_clear(pinfo->cinfo, COL_INFO);                              \
153
6
        col_append_ports(pinfo->cinfo, COL_INFO, PT_##protocol,         \
154
6
                            pinfo->srcport, pinfo->destport);           \
155
6
    }
Unexecuted instantiation: packet-iperf3.c:col_info_preface_TCP
packet-iperf3.c:col_info_preface_UDP
Line
Count
Source
150
6
    {                                                                   \
151
6
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "iPerf3");              \
152
6
        col_clear(pinfo->cinfo, COL_INFO);                              \
153
6
        col_append_ports(pinfo->cinfo, COL_INFO, PT_##protocol,         \
154
6
                            pinfo->srcport, pinfo->destport);           \
155
6
    }
156
157
DEFINE_CONTROL_PREFACE(TCP) /* invoke as col_info_preface_TCP(pinfo) */
158
DEFINE_CONTROL_PREFACE(UDP) /* invoke as col_info_preface_UDP(pinfo) */
159
160
// Collection of cookies used to differentiate between control and data connections.
161
// See dissect_iperf3_tcp() for details.
162
static wmem_map_t *cookiejar;
163
164
static int
165
dissect_iperf3_control_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
166
0
{
167
0
    iperf3_tcp_conversation_data *conversation_data = (iperf3_tcp_conversation_data *)data;
168
169
0
    proto_item *ti          = proto_tree_add_item(tree, proto_iperf3, tvb, 0, -1, ENC_NA);
170
0
    proto_tree *iperf3_tree = proto_item_add_subtree(ti, ett_iperf3);
171
172
0
    switch (conversation_data->sequence)
173
0
    {
174
        // ------
175
0
        case INIT:
176
0
        {
177
0
            const uint8_t *cookie;
178
0
            proto_tree_add_item_ret_string(iperf3_tree, hf_iperf3_cookie, tvb, 0, COOKIE_SIZE,
179
0
                                                        ENC_ASCII, pinfo->pool, &cookie);
180
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " Cookie: \"%s\"", cookie);
181
182
0
            conversation_data->pdu_size = 1;
183
0
            conversation_data->sequence = GENERIC_STATE;
184
0
            break;
185
0
        }
186
0
        case GENERIC_STATE:
187
0
        {
188
0
            int8_t state_code = tvb_get_int8(tvb, 0);
189
0
            const char *msg = val_to_str(pinfo->pool, state_code, iperf3_state_vals, "Unknown %d");
190
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " %s(%" PRIi8 ")", msg, state_code);
191
0
            col_set_fence(pinfo->cinfo, COL_INFO);
192
0
            proto_tree_add_item(iperf3_tree, hf_iperf3_state, tvb, 0, 1, ENC_BIG_ENDIAN);
193
194
0
            switch (state_code)
195
0
            {
196
0
                case PARAM_EXCHANGE:
197
0
                    conversation_data->pdu_size = 4;
198
0
                    conversation_data->sequence = PARAM_EXCHANGE_LENGTH;
199
0
                    break;
200
0
                case EXCHANGE_RESULTS:
201
0
                    conversation_data->pdu_size = 4;
202
0
                    conversation_data->sequence = EXCHANGE_RESULTS_LENGTH_1;
203
0
                    break;
204
0
                default:
205
0
                    break;
206
0
            }
207
0
            break;
208
0
        }
209
        // ------
210
0
        case PARAM_EXCHANGE_LENGTH:
211
0
        {
212
0
            uint32_t json_size = tvb_get_uint32(tvb, 0, ENC_BIG_ENDIAN);
213
0
            col_append_fstr(pinfo->cinfo, COL_INFO,
214
0
                " Next message is JSON of this length: %" PRIu32, json_size);
215
0
            proto_tree_add_item(iperf3_tree, hf_iperf3_prejson, tvb, 0, 4, ENC_BIG_ENDIAN);
216
217
0
            conversation_data->pdu_size = json_size;
218
0
            conversation_data->sequence = PARAM_EXCHANGE_JSON;
219
0
            break;
220
0
        }
221
0
        case PARAM_EXCHANGE_JSON:
222
0
        {
223
0
            uint32_t nbytes = tvb_reported_length(tvb);
224
0
            uint8_t *buffer = tvb_get_string_enc(pinfo->pool, tvb, 0, (int)nbytes, ENC_UTF_8);
225
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " %s", buffer);
226
0
            call_dissector(json_handle, tvb, pinfo, iperf3_tree);
227
228
0
            conversation_data->pdu_size = 1;
229
0
            conversation_data->sequence = GENERIC_STATE;
230
0
            break;
231
0
        }
232
        // ------
233
0
        case EXCHANGE_RESULTS_LENGTH_1:
234
0
        {
235
0
            uint32_t json_size = tvb_get_uint32(tvb, 0, ENC_BIG_ENDIAN);
236
0
            col_append_fstr(pinfo->cinfo, COL_INFO,
237
0
                " Next message is JSON of this length: %" PRIu32, json_size);
238
0
            proto_tree_add_item(iperf3_tree, hf_iperf3_prejson, tvb, 0, 4, ENC_BIG_ENDIAN);
239
240
0
            conversation_data->pdu_size = json_size;
241
0
            conversation_data->sequence = EXCHANGE_RESULTS_JSON_1;
242
0
            break;
243
0
        }
244
0
        case EXCHANGE_RESULTS_JSON_1:
245
0
        {
246
0
            uint32_t nbytes = tvb_reported_length(tvb);
247
0
            uint8_t *buffer = tvb_get_string_enc(pinfo->pool, tvb, 0, (int)nbytes, ENC_UTF_8);
248
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " %s", buffer);
249
0
            call_dissector(json_handle, tvb, pinfo, iperf3_tree);
250
251
0
            conversation_data->pdu_size = 4;
252
0
            conversation_data->sequence = EXCHANGE_RESULTS_LENGTH_2;
253
0
            break;
254
0
        }
255
0
        case EXCHANGE_RESULTS_LENGTH_2:
256
0
        {
257
0
            uint32_t json_size = tvb_get_uint32(tvb, 0, ENC_BIG_ENDIAN);
258
0
            col_append_fstr(pinfo->cinfo, COL_INFO,
259
0
                " Next message is JSON of this length: %" PRIu32, json_size);
260
0
            proto_tree_add_item(iperf3_tree, hf_iperf3_prejson, tvb, 0, 4, ENC_BIG_ENDIAN);
261
262
0
            conversation_data->pdu_size = json_size;
263
0
            conversation_data->sequence = EXCHANGE_RESULTS_JSON_2;
264
0
            break;
265
0
        }
266
0
        case EXCHANGE_RESULTS_JSON_2:
267
0
        {
268
0
            uint32_t nbytes = tvb_reported_length(tvb);
269
0
            uint8_t *buffer = tvb_get_string_enc(pinfo->pool, tvb, 0, (int)nbytes, ENC_UTF_8);
270
0
            col_append_fstr(pinfo->cinfo, COL_INFO, " %s", buffer);
271
0
            call_dissector(json_handle, tvb, pinfo, iperf3_tree);
272
273
0
            conversation_data->pdu_size = 1;
274
0
            conversation_data->sequence = GENERIC_STATE;
275
0
            break;
276
0
        }
277
        // ------
278
0
        default:
279
0
            DISSECTOR_ASSERT_NOT_REACHED();
280
0
    }
281
282
0
    return tvb_reported_length(tvb);
283
0
}
284
285
static int
286
dissect_iperf3_data_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
287
1
{
288
1
    iperf3_tcp_conversation_data *conversation_data = (iperf3_tcp_conversation_data *)data;
289
290
1
    proto_item *ti          = proto_tree_add_item(tree, proto_iperf3, tvb, 0, -1, ENC_NA);
291
1
    proto_tree *iperf3_tree = proto_item_add_subtree(ti, ett_iperf3);
292
293
1
    switch (conversation_data->sequence)
294
1
    {
295
1
        case INIT:
296
1
        {
297
1
            const uint8_t *cookie;
298
1
            proto_tree_add_item_ret_string(iperf3_tree, hf_iperf3_cookie, tvb, 0, COOKIE_SIZE,
299
1
                                                        ENC_ASCII, pinfo->pool, &cookie);
300
1
            col_info_preface_TCP(pinfo);
301
1
            col_append_fstr(pinfo->cinfo, COL_INFO, " Cookie: \"%s\"", cookie);
302
303
1
            conversation_data->pdu_size = 0;  // The whole TVBuff
304
1
            conversation_data->sequence = DATA;
305
1
            break;
306
0
        }
307
0
        case DATA:
308
0
        {
309
0
            call_data_dissector(tvb, pinfo, tree);
310
0
            break;
311
0
        }
312
0
        default:
313
0
            DISSECTOR_ASSERT_NOT_REACHED();
314
1
    }
315
316
0
    return tvb_reported_length(tvb);
317
1
}
318
319
static unsigned
320
get_iperf3_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb _U_, int offset _U_, void *data)
321
0
{
322
0
    iperf3_tcp_conversation_data *conversation_data = (iperf3_tcp_conversation_data *)data;
323
324
0
    return conversation_data->pdu_size ? conversation_data->pdu_size : tvb_reported_length(tvb);
325
0
}
326
327
static int
328
dissect_iperf3_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
329
1
{
330
    /*
331
     * Determine control connection for *any* packet
332
     *
333
     * - Get the conversation data
334
     * - If present, retrieve control connection status
335
     * - If missing, determine as follows:
336
     *   - Take cookie from the packet (which is assumed to be first in the data packet)
337
     *   - Lookup cookie in cookiejar
338
     *   - If cookie present in jar, this is not a control connection
339
     *   - If cookie abssent from jar, this is a control connection
340
     *     - Add cookie to jar
341
     *   - Create conversation data
342
     */
343
344
1
    uint8_t cookie[COOKIE_SIZE];
345
346
1
    iperf3_tcp_conversation_data *conversation_data = conversation_get_proto_data(find_conversation_pinfo(pinfo, 0), proto_iperf3);
347
1
    iperf3_tcp_packet_data       *packet_data       = p_get_proto_data(wmem_file_scope(), pinfo, proto_iperf3, 0);
348
349
1
    if (!conversation_data) {
350
1
        conversation_data = wmem_new0(wmem_file_scope(), iperf3_tcp_conversation_data);
351
1
        conversation_data->control_connection = false;
352
        // Data required for PDU determination
353
1
        conversation_data->pdu_size = COOKIE_SIZE;
354
1
        conversation_data->sequence = INIT;
355
356
1
        if (tvb_get_raw_bytes_as_stringz(tvb, 0, COOKIE_SIZE, cookie) == COOKIE_SIZE-1) {
357
0
            if (!wmem_map_contains(cookiejar, cookie)) {
358
                // This is a new control connection (or a data connection for which the control connection has not been seen, fail)
359
0
                char *cookie_save = wmem_strndup(wmem_file_scope(), (char*)cookie, COOKIE_SIZE);
360
0
                if (wmem_map_insert(cookiejar, cookie_save, NULL)) {  // No value to store, only the cookie
361
                    // We insert a new cookie in the jar, which now says there's already one.
362
0
                    DISSECTOR_ASSERT_NOT_REACHED();
363
0
                }
364
0
                conversation_data->control_connection = true;
365
0
            }
366
0
        }
367
368
1
        conversation_add_proto_data(find_conversation_pinfo(pinfo, 0), proto_iperf3, conversation_data);
369
1
    }
370
371
    /*
372
     * Determine the first PDU for *any* packet
373
     *
374
     * - If the packet has not been seen before, take PDU size and sequence from the conversation.
375
     *   (This either comes from a newly initialized conversation, or from
376
     *    the completion of the dissection of the payload of the previously dissected TCP packet.)
377
     *   - Store the PDU size and sequence with the packet.
378
     * - If the packet was seen before, take the PDU size and sequence from the stored packet data
379
     *   and store it with the conversation.
380
     *   (This guarantees that the PDU size and sequence for this particular packet is available,
381
     *    even if dissected out of order.)
382
     * - Call the dissection function with the conversation data, which is relevant for the
383
     *   current TCP payload.
384
     */
385
386
1
    if (!PINFO_FD_VISITED(pinfo)) {
387
1
        packet_data = wmem_new0(wmem_file_scope(), iperf3_tcp_packet_data);
388
1
        packet_data->pdu_size = conversation_data->pdu_size;
389
1
        packet_data->sequence = conversation_data->sequence;
390
1
        p_add_proto_data(wmem_file_scope(), pinfo, proto_iperf3, 0, packet_data);
391
1
    } else {
392
0
        DISSECTOR_ASSERT(packet_data);
393
394
0
        conversation_data->pdu_size = packet_data->pdu_size;
395
0
        conversation_data->sequence = packet_data->sequence;
396
0
    }
397
398
1
    if (conversation_data->control_connection) {
399
0
        col_info_preface_TCP(pinfo);
400
0
        tcp_dissect_pdus(tvb, pinfo, tree, false, 1, get_iperf3_pdu_len, dissect_iperf3_control_pdu, conversation_data);
401
1
    } else {
402
1
        dissect_iperf3_data_pdu(tvb, pinfo, tree, conversation_data);
403
1
    }
404
405
1
    return tvb_reported_length(tvb);
406
1
}
407
408
static int
409
dissect_iperf3_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
410
19
{
411
19
    proto_item *ti;
412
19
    proto_tree *iperf3_tree, *time_tree;
413
19
    uint32_t offset = 0;
414
19
    uint32_t nbytes = tvb_reported_length(tvb);
415
416
19
    uint64_t maybe_sequence_num;
417
418
    /************** UDP CONTROL *****************/
419
19
    if (nbytes == IPERF3_INIT_UDP_MSG_SIZE)
420
11
    {
421
        /* Due to the fact that UDP_CONNECT_MSG and UDP_CONNECT_REPLY are each others
422
           reverse it does not matter which endianness is used. */
423
11
        uint32_t init_cxn_msg = tvb_get_uint32(tvb, offset, ENC_HOST_ENDIAN);
424
11
        if (init_cxn_msg != UDP_CONNECT_MSG &&
425
11
                init_cxn_msg != UDP_CONNECT_REPLY &&
426
11
                init_cxn_msg != LEGACY_UDP_CONNECT_MSG &&
427
11
                init_cxn_msg != LEGACY_MSG_OPPOSITE_ENDIAN &&
428
11
                init_cxn_msg != LEGACY_UDP_CONNECT_REPLY &&
429
11
                init_cxn_msg != LEGACY_REPLY_OPPOSITE_ENDIAN)
430
11
            return 0;
431
0
        col_info_preface_UDP(pinfo);
432
0
        col_append_str(pinfo->cinfo, COL_INFO, " Establishing UDP connection...");
433
434
0
        ti = proto_tree_add_item(tree, proto_iperf3, tvb, offset, -1, ENC_NA);
435
0
        iperf3_tree = proto_item_add_subtree(ti, ett_iperf3);
436
0
        proto_tree_add_item(iperf3_tree, hf_iperf3_udp_init_msg, tvb, offset, 4, ENC_BIG_ENDIAN);
437
438
0
        return IPERF3_INIT_UDP_MSG_SIZE;
439
11
    }
440
441
    /************** UDP DATA ********************/
442
443
8
    if (tvb_reported_length(tvb) < IPERF3_UDP_HDR_SIZE)
444
2
        return 0;
445
446
6
    col_info_preface_UDP(pinfo);
447
6
    ti = proto_tree_add_item(tree, proto_iperf3, tvb, offset, -1, ENC_NA);
448
6
    iperf3_tree = proto_item_add_subtree(ti, ett_iperf3);
449
450
6
    time_tree = proto_tree_add_subtree(iperf3_tree, tvb, offset, 8, ett_time, &ti, "");
451
452
6
    int32_t seconds;
453
6
    uint32_t useconds;
454
455
6
    proto_tree_add_item_ret_int(time_tree, hf_iperf3_sec, tvb, offset, 4, ENC_BIG_ENDIAN, &seconds);
456
6
    offset += 4;
457
6
    proto_tree_add_item_ret_uint(time_tree, hf_iperf3_usec, tvb, offset, 4, ENC_BIG_ENDIAN, &useconds);
458
6
    offset += 4;
459
460
6
    proto_item_set_text(ti, "Time Sent: %.7f seconds", seconds + (useconds / 1000000.0));
461
    /* empirically: no precision below 7 digits */
462
463
    /* let users choose if they're using 64 bit sequence numbers, but still want sensible
464
        default... so we detect if the top 32 bits are all zero. if so then they must
465
        be using 64 bit numbers regardless */
466
6
    maybe_sequence_num = tvb_get_uint32(tvb, offset, ENC_BIG_ENDIAN);
467
6
    if (iperf3_pref_64bit_seq_no || maybe_sequence_num == 0) {
468
1
        proto_tree_add_item_ret_uint64(iperf3_tree, hf_iperf3_sequence, tvb, offset, 8, ENC_BIG_ENDIAN, &maybe_sequence_num);
469
1
        offset += 8;
470
5
    } else {
471
5
        proto_tree_add_item(iperf3_tree, hf_iperf3_sequence, tvb, offset, 4, ENC_BIG_ENDIAN);
472
5
        offset += 4;
473
5
    }
474
475
6
    col_append_fstr(pinfo->cinfo, COL_INFO, " [%" PRIu64 "] Time sent=%.7f length=%" PRIu32 " bytes",
476
6
        maybe_sequence_num, seconds + (useconds / 1000000.0), nbytes);
477
478
6
    if (iperf3_pref_detect_udp_order)
479
6
        udp_detect_and_report_out_of_order(pinfo, ti, maybe_sequence_num);
480
481
6
    tvbuff_t *next_tvb = tvb_new_subset_remaining(tvb, offset);
482
6
    call_data_dissector(next_tvb, pinfo, tree); /* deals with payload size = 0
483
                                 which happens with -l=16 and --udp-64bit-sequence number */
484
485
6
    return nbytes;
486
8
}
487
488
static void
489
udp_detect_and_report_out_of_order(packet_info *pinfo, proto_item *ti, uint64_t sequence_num)
490
6
{
491
    /*  Record out of order packets by keeping a per-conversation set of
492
        lost packets. The first time the packets are dissected we add them to the set
493
        based on the comparison to the per-conversation "prev_seq_no" variable that gets
494
        initialized to 0 when the connection is first seen
495
496
        per-conversation state works well with bidirectional and parallel connections
497
            and fast even with ~500k packets out-of-order */
498
499
6
    udp_conversation_data *conversation;
500
6
    conversation = conversation_get_proto_data(find_conversation_pinfo(pinfo, 0), proto_iperf3);
501
6
    if (!conversation)
502
5
        conversation = udp_set_conversation_data(pinfo);
503
504
    /* detect */
505
6
    if (!PINFO_FD_VISITED(pinfo)) {
506
6
        bool is_out_of_order = (sequence_num != conversation->prev_seq_no + 1);
507
6
        conversation->prev_seq_no = sequence_num;
508
509
6
        if (is_out_of_order) {
510
6
            wmem_map_insert(conversation->out_of_order, GUINT_TO_POINTER(pinfo->num), NULL);
511
6
        }
512
6
    }
513
    /* report */
514
6
    if (wmem_map_contains(conversation->out_of_order, GUINT_TO_POINTER(pinfo->num))) {
515
6
        col_prepend_fstr(pinfo->cinfo, COL_INFO, "(Loss or out-of-order delivery) ");
516
6
        expert_add_info(pinfo, ti, &ei_udp_out_of_order);
517
6
    }
518
6
}
519
520
static udp_conversation_data *
521
udp_set_conversation_data(packet_info *pinfo)
522
5
{
523
5
    udp_conversation_data *conversation;
524
5
    conversation = wmem_new0(wmem_file_scope(), udp_conversation_data);
525
5
    conversation->out_of_order = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
526
5
    conversation->prev_seq_no = 0;
527
5
    conversation_add_proto_data(find_conversation_pinfo(pinfo, 0), proto_iperf3, conversation);
528
5
    return conversation;
529
    /* could (very improbably) run iperf3 test multiple times in same capture
530
        and get the same random port.. would be seen as the same conversation and so you'd
531
        over count out of order packets... they can just turn off out-of-order detection */
532
5
}
533
534
void proto_register_iperf3(void)
535
14
{
536
    /* Setup list of header fields */
537
14
    static hf_register_info hf[] = {
538
        // TCP
539
14
        {&hf_iperf3_state,
540
14
         {"State ID", "iperf3.state", FT_INT8, BASE_DEC, VALS(iperf3_state_vals),
541
14
         0, NULL, HFILL}},
542
14
        {&hf_iperf3_prejson,
543
14
         {"Pre-JSON length identifier", "iperf3.prejson", FT_UINT32, BASE_DEC,
544
14
          NULL, 0, NULL, HFILL}},
545
14
        {&hf_iperf3_cookie,
546
14
         {"Cookie", "iperf3.cookie", FT_STRINGZ, BASE_NONE,
547
14
          NULL, 0, NULL, HFILL}},
548
        // UDP
549
14
        {&hf_iperf3_sec,
550
14
         {"iPerf3 sec", "iperf3.sec", FT_INT32, BASE_DEC,
551
14
          NULL, 0, NULL, HFILL}},
552
14
        {&hf_iperf3_usec,
553
14
         {"iPerf3 usec", "iperf3.usec", FT_UINT32, BASE_DEC,
554
14
          NULL, 0, NULL, HFILL}},
555
14
        {&hf_iperf3_sequence,
556
14
         {"iPerf3 sequence", "iperf3.sequence", FT_UINT64, BASE_DEC,
557
14
          NULL, 0, NULL, HFILL}},
558
14
        {&hf_iperf3_udp_init_msg,
559
14
         {"UDP initialization message", "iperf3.init_msg", FT_UINT32, BASE_HEX,
560
14
          NULL, 0, NULL, HFILL}},
561
14
    };
562
563
    /* Setup protocol subtree array */
564
14
    static int *ett[] = {
565
14
        &ett_iperf3,
566
14
        &ett_time
567
14
    };
568
569
    /* Register the protocol name and description */
570
14
    proto_iperf3 = proto_register_protocol("iPerf3 Speed Test", "iPerf3", "iperf3");
571
572
    /* Register configuration preferences */
573
14
    module_t *iperf3_module = prefs_register_protocol(proto_iperf3, NULL);
574
14
    prefs_register_bool_preference(iperf3_module, "udp_sequence_64bit",
575
14
        "Use 64 bit sequence numbers for UDP data",
576
14
        "Whether iPerf3 was run with --udp-counters-64bit flag set",
577
14
        &iperf3_pref_64bit_seq_no);
578
14
    prefs_register_bool_preference(iperf3_module, "detect_udp_errors",
579
14
        "Detect packet loss and out of order delivery for UDP data",
580
14
        "Attempt to detect when a packets sequence number does not match the previous ones +1",
581
14
        &iperf3_pref_detect_udp_order);
582
583
    /* Setup list of expert warnings */
584
14
    static ei_register_info ei[] = {
585
14
        {&ei_udp_out_of_order,
586
14
        {"iperf3.outoforder", PI_SEQUENCE, PI_NOTE,
587
14
            "UDP packet loss or out of order delivery", EXPFILL}},
588
14
    };
589
    /* Register expert mode warnings */
590
14
    expert_module_t* expert_iperf3 = expert_register_protocol(proto_iperf3);
591
14
    expert_register_field_array(expert_iperf3, ei, array_length(ei));
592
593
14
    proto_register_field_array(proto_iperf3, hf, array_length(hf));
594
14
    proto_register_subtree_array(ett, array_length(ett));
595
596
14
    iperf3_handle_tcp = register_dissector("iperf3_tcp", dissect_iperf3_tcp, proto_iperf3);
597
14
    iperf3_handle_udp = register_dissector("iperf3_udp", dissect_iperf3_udp, proto_iperf3);
598
599
14
    cookiejar = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), wmem_str_hash, g_str_equal);
600
14
}
601
602
void proto_reg_handoff_iperf3(void)
603
14
{
604
14
    json_handle = find_dissector("json");
605
606
14
    dissector_add_uint_range_with_preference("tcp.port", "5201", iperf3_handle_tcp);
607
14
    dissector_add_uint_range_with_preference("udp.port", "5201", iperf3_handle_udp);
608
14
}
609
610
/*
611
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
612
 *
613
 * Local variables:
614
 * c-basic-offset: 4
615
 * tab-width: 8
616
 * indent-tabs-mode: nil
617
 * End:
618
 *
619
 * vi: set shiftwidth=4 tabstop=8 expandtab:
620
 * :indentSize=4:tabSize=8:noTabs=true:
621
 */