| File: | builds/wireshark/wireshark/epan/dissectors/packet-udx.c |
| Warning: | line 1043, column 9 Value stored to 'offset' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* packet-udx.c |
| 2 | * Routines for UDX dissection |
| 3 | * Copyright 2026, Frank <frankolien123@gmail.com> |
| 4 | * |
| 5 | * UDX is a reliable, multiplexed, UDP-based transport protocol used by the |
| 6 | * Holepunch peer-to-peer stack. Reference implementation: |
| 7 | * https://github.com/holepunchto/libudx |
| 8 | * |
| 9 | * Wireshark - Network traffic analyzer |
| 10 | * By Gerald Combs <gerald@wireshark.org> |
| 11 | * Copyright 1998 Gerald Combs |
| 12 | * |
| 13 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 14 | */ |
| 15 | |
| 16 | #include "config.h" |
| 17 | |
| 18 | #include <math.h> |
| 19 | |
| 20 | #include <epan/packet.h> |
| 21 | #include <epan/conversation.h> |
| 22 | #include <epan/expert.h> |
| 23 | #include <epan/prefs.h> |
| 24 | #include <epan/proto_data.h> |
| 25 | #include <epan/follow.h> |
| 26 | #include <epan/addr_resolv.h> |
| 27 | #include <epan/tap.h> |
| 28 | #include "packet-udp.h" |
| 29 | #include <wsutil/wmem/wmem_map.h> |
| 30 | #include <wsutil/wmem/wmem_tree.h> |
| 31 | |
| 32 | /* |
| 33 | * UDX wire format (all multi-byte fields little-endian): |
| 34 | * |
| 35 | * offset size field |
| 36 | * 0 1 magic (0xff) |
| 37 | * 1 1 version (1) |
| 38 | * 2 1 type flags |
| 39 | * 3 1 data offset: bytes between the fixed header and the payload, |
| 40 | * occupied by SACK blocks, or by padding on MTU probes |
| 41 | * 4 4 id - the *receiver's* stream id |
| 42 | * 8 4 window - sender's receive window in bytes |
| 43 | * 12 4 seq - per-PACKET sequence counter (not bytes) |
| 44 | * 16 4 ack - next seq expected from the peer |
| 45 | * 20 8*n SACK blocks: pairs of uint32 (start, end) seq ranges |
| 46 | * ... payload |
| 47 | */ |
| 48 | |
| 49 | #define UDX_HEADER_SIZE20 20 |
| 50 | #define UDX_MAGIC_BYTE0xff 0xff |
| 51 | #define UDX_VERSION1 1 |
| 52 | |
| 53 | #define UDX_FLAG_DATA0x01 0x01 |
| 54 | #define UDX_FLAG_END0x02 0x02 |
| 55 | #define UDX_FLAG_SACK0x04 0x04 |
| 56 | #define UDX_FLAG_MESSAGE0x08 0x08 |
| 57 | #define UDX_FLAG_DESTROY0x10 0x10 |
| 58 | #define UDX_FLAG_HEARTBEAT0x20 0x20 |
| 59 | #define UDX_FLAG_MASK0x3f 0x3f |
| 60 | |
| 61 | /* |
| 62 | * Sequence numbers count packets and wrap at 2^32, so all comparisons are |
| 63 | * made in circular arithmetic. |
| 64 | */ |
| 65 | #define UDX_SEQ_LT(a, b)((int32_t)((a) - (b)) < 0) ((int32_t)((a) - (b)) < 0) |
| 66 | #define UDX_SEQ_GT(a, b)((int32_t)((a) - (b)) > 0) ((int32_t)((a) - (b)) > 0) |
| 67 | #define UDX_SEQ_GEQ(a, b)((int32_t)((a) - (b)) >= 0) ((int32_t)((a) - (b)) >= 0) |
| 68 | |
| 69 | /* Bounds the SACK blocks examined per packet; data_offset caps the area at |
| 70 | * 255 bytes, i.e. 31 blocks. */ |
| 71 | #define UDX_MAX_SACK_BLOCKS32 32 |
| 72 | |
| 73 | /* Floor for the derived retransmission timeout, in seconds. Below this a |
| 74 | * repeat is attributed to loss recovery rather than to a timer firing. */ |
| 75 | #define UDX_MIN_RTO0.2 0.2 |
| 76 | |
| 77 | /* A repeat arriving within this window of the original is a duplicate |
| 78 | * datagram rather than anything the sender chose to send again. */ |
| 79 | #define UDX_DUP_WINDOW0.0005 0.0005 |
| 80 | |
| 81 | /* Shortest pause credited to a tail loss probe timer when no round-trip |
| 82 | * time has been measured yet. */ |
| 83 | #define UDX_MIN_PROBE_DELAY0.010 0.010 |
| 84 | |
| 85 | void proto_register_udx(void); |
| 86 | void proto_reg_handoff_udx(void); |
| 87 | |
| 88 | static dissector_handle_t udx_handle; |
| 89 | |
| 90 | static int proto_udx; |
| 91 | |
| 92 | static bool_Bool udx_analyze_sequence_numbers = true1; |
| 93 | |
| 94 | static int udx_follow_tap; |
| 95 | |
| 96 | /* Stream numbers are handed out across the whole capture so that a filter |
| 97 | * such as "udx.stream eq 3" identifies exactly one stream. */ |
| 98 | static uint32_t udx_stream_count; |
| 99 | |
| 100 | /* Queued for the follow taps: one packet's payload, where it belongs in the |
| 101 | * stream and which side sent it. */ |
| 102 | typedef struct udx_follow_tap_data { |
| 103 | tvbuff_t *tvb; |
| 104 | uint32_t stream; |
| 105 | uint32_t offset; /* position of this packet within its direction */ |
| 106 | bool_Bool from_server; |
| 107 | } udx_follow_tap_data_t; |
| 108 | |
| 109 | /* One transmitted packet, remembered so that a later acknowledgement can be |
| 110 | * linked back to it. */ |
| 111 | typedef struct udx_seg { |
| 112 | uint32_t frame; |
| 113 | nstime_t ts; |
| 114 | uint32_t len; |
| 115 | uint32_t acked_in_frame; |
| 116 | nstime_t ack_ts; |
| 117 | unsigned retrans; |
| 118 | bool_Bool sacked; |
| 119 | } udx_seg_t; |
| 120 | |
| 121 | /* One direction of one stream: the packets one endpoint sends bearing the |
| 122 | * peer's stream id. */ |
| 123 | typedef struct udx_flow { |
| 124 | uint32_t id; |
| 125 | unsigned dir; |
| 126 | unsigned order; /* creation order within this direction */ |
| 127 | nstime_t first_ts; |
| 128 | wmem_tree_t *segs; /* seq -> udx_seg_t */ |
| 129 | uint32_t base_seq; /* first sequence number seen on this flow */ |
| 130 | uint32_t max_seq; /* highest sequence number sent */ |
| 131 | bool_Bool have_seq; |
| 132 | uint32_t outstanding_bytes; |
| 133 | uint32_t outstanding_pkts; |
| 134 | uint32_t max_ack; /* highest acknowledgement this flow emitted */ |
| 135 | bool_Bool have_ack; |
| 136 | uint32_t max_sacked; /* highest sequence the peer selectively acked */ |
| 137 | bool_Bool have_sacked; |
| 138 | uint32_t last_rwnd; |
| 139 | bool_Bool rwnd_zero; |
| 140 | double srtt; |
| 141 | double rttvar; |
| 142 | bool_Bool have_rtt; |
| 143 | uint32_t stream_num; |
| 144 | struct udx_flow *paired; |
| 145 | struct udx_flow *cand; /* pairing candidate under consideration */ |
| 146 | unsigned cand_hits; |
| 147 | } udx_flow_t; |
| 148 | |
| 149 | typedef struct udx_conv { |
| 150 | wmem_map_t *flows; /* (dir << 32 | id) -> udx_flow_t */ |
| 151 | unsigned n_flows[2]; |
| 152 | unsigned client_dir; /* the side that sent first is the client */ |
| 153 | bool_Bool have_client_dir; |
| 154 | } udx_conv_t; |
| 155 | |
| 156 | /* Verdicts reached on the first pass and replayed on every later visit. */ |
| 157 | #define UDX_A_RETRANS0x0001 0x0001 |
| 158 | #define UDX_A_FAST_RETRANS0x0002 0x0002 |
| 159 | #define UDX_A_RTO_RETRANS0x0004 0x0004 |
| 160 | #define UDX_A_TLP0x0008 0x0008 |
| 161 | #define UDX_A_SPURIOUS0x0010 0x0010 |
| 162 | #define UDX_A_OUT_OF_ORDER0x0020 0x0020 |
| 163 | #define UDX_A_LOST_SEGMENT0x0040 0x0040 |
| 164 | #define UDX_A_KEEPALIVE0x0080 0x0080 |
| 165 | #define UDX_A_ZERO_WIN_PROBE0x0100 0x0100 |
| 166 | #define UDX_A_ZERO_WIN0x0200 0x0200 |
| 167 | #define UDX_A_WINDOW_UPDATE0x0400 0x0400 |
| 168 | #define UDX_A_MTU_PROBE0x0800 0x0800 |
| 169 | #define UDX_A_END0x1000 0x1000 |
| 170 | #define UDX_A_DESTROY0x2000 0x2000 |
| 171 | #define UDX_A_DUPLICATE0x4000 0x4000 |
| 172 | |
| 173 | typedef struct udx_ppd { |
| 174 | uint32_t flags; |
| 175 | uint32_t acks_frame; /* frame this packet acknowledges */ |
| 176 | nstime_t ack_rtt; |
| 177 | bool_Bool have_ack_rtt; |
| 178 | uint32_t bytes_in_flight; |
| 179 | uint32_t packets_in_flight; |
| 180 | uint32_t seq; /* sender sequence, to find our own segment */ |
| 181 | bool_Bool tracked; /* this packet consumed a sequence number */ |
| 182 | uint32_t stream; /* stream number at analysis time */ |
| 183 | uint32_t follow_offset; /* position within this direction */ |
| 184 | bool_Bool from_server; |
| 185 | bool_Bool follow_ok; /* payload belongs in the reassembled stream */ |
| 186 | udx_flow_t *flow; |
| 187 | } udx_ppd_t; |
| 188 | |
| 189 | static int hf_udx_magic; |
| 190 | static int hf_udx_version; |
| 191 | static int hf_udx_flags; |
| 192 | static int hf_udx_flags_data; |
| 193 | static int hf_udx_flags_end; |
| 194 | static int hf_udx_flags_sack; |
| 195 | static int hf_udx_flags_message; |
| 196 | static int hf_udx_flags_destroy; |
| 197 | static int hf_udx_flags_heartbeat; |
| 198 | static int hf_udx_data_offset; |
| 199 | static int hf_udx_id; |
| 200 | static int hf_udx_window; |
| 201 | static int hf_udx_seq; |
| 202 | static int hf_udx_ack; |
| 203 | static int hf_udx_sacks; |
| 204 | static int hf_udx_sack_block; |
| 205 | static int hf_udx_sack_start; |
| 206 | static int hf_udx_sack_end; |
| 207 | static int hf_udx_padding; |
| 208 | static int hf_udx_payload; |
| 209 | static int hf_udx_payload_len; |
| 210 | static int hf_udx_stream; |
| 211 | static int hf_udx_analysis; |
| 212 | static int hf_udx_analysis_acks_frame; |
| 213 | static int hf_udx_analysis_acked_in; |
| 214 | static int hf_udx_analysis_ack_rtt; |
| 215 | static int hf_udx_analysis_bytes_in_flight; |
| 216 | static int hf_udx_analysis_pkts_in_flight; |
| 217 | static int hf_udx_analysis_no_reverse; |
| 218 | |
| 219 | static int ett_udx; |
| 220 | static int ett_udx_flags; |
| 221 | static int ett_udx_sacks; |
| 222 | static int ett_udx_sack_block; |
| 223 | static int ett_udx_analysis; |
| 224 | |
| 225 | static expert_field ei_udx_retrans; |
| 226 | static expert_field ei_udx_fast_retrans; |
| 227 | static expert_field ei_udx_rto_retrans; |
| 228 | static expert_field ei_udx_tlp; |
| 229 | static expert_field ei_udx_spurious_retrans; |
| 230 | static expert_field ei_udx_duplicate; |
| 231 | static expert_field ei_udx_out_of_order; |
| 232 | static expert_field ei_udx_lost_segment; |
| 233 | static expert_field ei_udx_keepalive; |
| 234 | static expert_field ei_udx_zero_window_probe; |
| 235 | static expert_field ei_udx_zero_window; |
| 236 | static expert_field ei_udx_window_update; |
| 237 | static expert_field ei_udx_mtu_probe; |
| 238 | static expert_field ei_udx_end; |
| 239 | static expert_field ei_udx_destroy; |
| 240 | |
| 241 | static int * const udx_flag_fields[] = { |
| 242 | &hf_udx_flags_data, |
| 243 | &hf_udx_flags_end, |
| 244 | &hf_udx_flags_sack, |
| 245 | &hf_udx_flags_message, |
| 246 | &hf_udx_flags_destroy, |
| 247 | &hf_udx_flags_heartbeat, |
| 248 | NULL((void*)0) |
| 249 | }; |
| 250 | |
| 251 | /* Build a "DATA,SACK"-style summary of the flags byte; bare 0 is an ACK. */ |
| 252 | static void |
| 253 | udx_flags_to_str(uint8_t flags, char *buf, size_t buf_len) |
| 254 | { |
| 255 | static const struct { |
| 256 | uint8_t bit; |
| 257 | const char *name; |
| 258 | } bits[] = { |
| 259 | { UDX_FLAG_DATA0x01, "DATA" }, |
| 260 | { UDX_FLAG_END0x02, "END" }, |
| 261 | { UDX_FLAG_SACK0x04, "SACK" }, |
| 262 | { UDX_FLAG_MESSAGE0x08, "MESSAGE" }, |
| 263 | { UDX_FLAG_DESTROY0x10, "DESTROY" }, |
| 264 | { UDX_FLAG_HEARTBEAT0x20, "HEARTBEAT" }, |
| 265 | }; |
| 266 | size_t pos = 0; |
| 267 | |
| 268 | if (flags == 0) { |
| 269 | (void) g_strlcpy(buf, "ACK", buf_len); |
| 270 | return; |
| 271 | } |
| 272 | buf[0] = '\0'; |
| 273 | for (size_t i = 0; i < array_length(bits)(sizeof (bits) / sizeof (bits)[0]); i++) { |
| 274 | if (flags & bits[i].bit) { |
| 275 | if (pos > 0) |
| 276 | pos += g_strlcpy(buf + pos, ",", buf_len - pos); |
| 277 | pos += g_strlcpy(buf + pos, bits[i].name, buf_len - pos); |
| 278 | } |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | /* |
| 283 | * A stable label for the two endpoints of the enclosing UDP conversation. |
| 284 | * Which endpoint gets 0 does not matter; only that a given endpoint keeps |
| 285 | * the same label for the whole capture. |
| 286 | */ |
| 287 | static unsigned |
| 288 | udx_direction(const packet_info *pinfo) |
| 289 | { |
| 290 | int c = cmp_address(&pinfo->src, &pinfo->dst); |
| 291 | |
| 292 | if (c != 0) |
| 293 | return (c < 0) ? 0 : 1; |
| 294 | return (pinfo->srcport < pinfo->destport) ? 0 : 1; |
| 295 | } |
| 296 | |
| 297 | static udx_flow_t * |
| 298 | udx_get_flow(udx_conv_t *conv, unsigned dir, uint32_t id, const nstime_t *ts) |
| 299 | { |
| 300 | uint64_t key = ((uint64_t) dir << 32) | id; |
| 301 | udx_flow_t *flow = (udx_flow_t *) wmem_map_lookup(conv->flows, &key); |
| 302 | uint64_t *key_copy; |
| 303 | |
| 304 | if (flow != NULL((void*)0)) |
| 305 | return flow; |
| 306 | |
| 307 | flow = wmem_new0(wmem_file_scope(), udx_flow_t)((udx_flow_t*)wmem_alloc0((wmem_file_scope()), sizeof(udx_flow_t ))); |
| 308 | flow->id = id; |
| 309 | flow->dir = dir; |
| 310 | flow->order = conv->n_flows[dir]++; |
| 311 | flow->first_ts = *ts; |
| 312 | flow->segs = wmem_tree_new(wmem_file_scope()); |
| 313 | flow->stream_num = udx_stream_count++; |
| 314 | |
| 315 | key_copy = wmem_new(wmem_file_scope(), uint64_t)((uint64_t*)wmem_alloc((wmem_file_scope()), sizeof(uint64_t)) ); |
| 316 | *key_copy = key; |
| 317 | wmem_map_insert(conv->flows, key_copy, flow); |
| 318 | |
| 319 | return flow; |
| 320 | } |
| 321 | |
| 322 | /* |
| 323 | * Pairing a flow with its reverse. |
| 324 | * |
| 325 | * A UDX conversation is a six tuple, but a packet carries only the receiver's |
| 326 | * stream id: the ids are exchanged in an encrypted handshake that is not |
| 327 | * visible here. The reverse flow therefore has to be inferred. |
| 328 | * |
| 329 | * Candidates are the flows travelling the other way. Each is scored on |
| 330 | * - plausibility: the acknowledgement carried by this packet must fall |
| 331 | * within the sequence numbers the candidate has actually sent; |
| 332 | * - creation order: streams are set up in pairs, so the n'th flow in one |
| 333 | * direction usually answers the n'th flow in the other; |
| 334 | * - proximity: the two flows should have appeared at about the same time. |
| 335 | * |
| 336 | * A candidate is adopted once it has been the sole best choice twice, which |
| 337 | * keeps a single ambiguous packet from binding the wrong pair. Once bound, |
| 338 | * a pairing is never revised. |
| 339 | */ |
| 340 | typedef struct udx_pair_scan { |
| 341 | udx_flow_t *self; |
| 342 | uint32_t ack; |
| 343 | udx_flow_t *best; |
| 344 | int best_score; |
| 345 | bool_Bool tie; |
| 346 | } udx_pair_scan_t; |
| 347 | |
| 348 | static void |
| 349 | udx_score_candidate(void *key _U___attribute__((unused)), void *value, void *userdata) |
| 350 | { |
| 351 | udx_flow_t *cand = (udx_flow_t *) value; |
| 352 | udx_pair_scan_t *scan = (udx_pair_scan_t *) userdata; |
| 353 | double dt; |
| 354 | int score = 0; |
| 355 | |
| 356 | if (cand->dir == scan->self->dir || cand->paired != NULL((void*)0)) |
| 357 | return; |
| 358 | |
| 359 | /* The acknowledgement must not reach past what the candidate has sent. */ |
| 360 | if (cand->have_seq) { |
| 361 | if (UDX_SEQ_GT(scan->ack, cand->max_seq + 1)((int32_t)((scan->ack) - (cand->max_seq + 1)) > 0)) |
| 362 | return; |
| 363 | score += 4; |
| 364 | } |
| 365 | |
| 366 | if (cand->order == scan->self->order) |
| 367 | score += 2; |
| 368 | |
| 369 | dt = nstime_to_sec(&cand->first_ts) - nstime_to_sec(&scan->self->first_ts); |
| 370 | if (dt < 0) |
| 371 | dt = -dt; |
| 372 | if (dt < 0.050) |
| 373 | score += 1; |
| 374 | |
| 375 | if (score > scan->best_score) { |
| 376 | scan->best_score = score; |
| 377 | scan->best = cand; |
| 378 | scan->tie = false0; |
| 379 | } else if (score == scan->best_score && scan->best != NULL((void*)0)) { |
| 380 | scan->tie = true1; |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | static void |
| 385 | udx_try_pair(udx_conv_t *conv, udx_flow_t *flow, uint32_t ack) |
| 386 | { |
| 387 | udx_pair_scan_t scan; |
| 388 | |
| 389 | scan.self = flow; |
| 390 | scan.ack = ack; |
| 391 | scan.best = NULL((void*)0); |
| 392 | scan.best_score = 0; |
| 393 | scan.tie = false0; |
| 394 | |
| 395 | wmem_map_foreach(conv->flows, udx_score_candidate, &scan); |
| 396 | |
| 397 | if (scan.best == NULL((void*)0) || scan.tie) { |
| 398 | flow->cand = NULL((void*)0); |
| 399 | flow->cand_hits = 0; |
| 400 | return; |
| 401 | } |
| 402 | |
| 403 | /* |
| 404 | * One unambiguous winner is adopted at once, so that a stream is paired |
| 405 | * from its first packet on. An ambiguous scan binds nothing and is |
| 406 | * retried on the next packet, by which time the acknowledgements have |
| 407 | * usually separated the candidates. |
| 408 | */ |
| 409 | flow->cand = scan.best; |
| 410 | flow->cand_hits++; |
| 411 | |
| 412 | flow->paired = scan.best; |
| 413 | scan.best->paired = flow; |
| 414 | |
| 415 | /* Both halves report the lower of the two numbers. */ |
| 416 | if (scan.best->stream_num < flow->stream_num) |
| 417 | flow->stream_num = scan.best->stream_num; |
| 418 | else |
| 419 | scan.best->stream_num = flow->stream_num; |
| 420 | } |
| 421 | |
| 422 | /* RFC 6298 smoothing, fed only by segments that were never retransmitted. */ |
| 423 | static void |
| 424 | udx_update_rtt(udx_flow_t *flow, double sample) |
| 425 | { |
| 426 | if (!flow->have_rtt) { |
| 427 | flow->srtt = sample; |
| 428 | flow->rttvar = sample / 2; |
| 429 | flow->have_rtt = true1; |
| 430 | return; |
| 431 | } |
| 432 | flow->rttvar = 0.75 * flow->rttvar + 0.25 * fabs(flow->srtt - sample); |
| 433 | flow->srtt = 0.875 * flow->srtt + 0.125 * sample; |
| 434 | } |
| 435 | |
| 436 | static double |
| 437 | udx_rto(const udx_flow_t *flow) |
| 438 | { |
| 439 | double rto; |
| 440 | |
| 441 | if (!flow->have_rtt) |
| 442 | return UDX_MIN_RTO0.2; |
| 443 | rto = flow->srtt + 4 * flow->rttvar; |
| 444 | return (rto < UDX_MIN_RTO0.2) ? UDX_MIN_RTO0.2 : rto; |
| 445 | } |
| 446 | |
| 447 | /* |
| 448 | * Retire every segment of the acknowledged flow below "ack", link the last |
| 449 | * of them to the acknowledging packet, and take an RTT sample from it. |
| 450 | */ |
| 451 | static void |
| 452 | udx_process_ack(packet_info *pinfo, udx_flow_t *acked_flow, udx_flow_t *acking_flow, |
| 453 | uint32_t ack, udx_ppd_t *ppd) |
| 454 | { |
| 455 | udx_seg_t *newest = NULL((void*)0); |
| 456 | uint32_t seq = ack - 1; |
| 457 | unsigned guard; |
| 458 | |
| 459 | if (!acked_flow->have_seq) |
| 460 | return; |
| 461 | |
| 462 | /* Walk back from the acknowledgement over the segments it covers. The |
| 463 | * walk stops at the first segment already retired by an earlier |
| 464 | * acknowledgement; the counter only bounds pathological captures. */ |
| 465 | for (guard = 0; guard < 1024; guard++, seq--) { |
| 466 | udx_seg_t *seg = (udx_seg_t *) wmem_tree_lookup32(acked_flow->segs, seq); |
| 467 | |
| 468 | if (seg == NULL((void*)0) || seg->acked_in_frame != 0) |
| 469 | break; |
| 470 | |
| 471 | seg->acked_in_frame = pinfo->num; |
| 472 | seg->ack_ts = pinfo->abs_ts; |
| 473 | |
| 474 | if (acked_flow->outstanding_pkts > 0) { |
| 475 | acked_flow->outstanding_pkts--; |
| 476 | acked_flow->outstanding_bytes -= seg->len; |
| 477 | } |
| 478 | if (newest == NULL((void*)0)) |
| 479 | newest = seg; |
| 480 | } |
| 481 | |
| 482 | if (newest != NULL((void*)0)) { |
| 483 | nstime_t rtt; |
| 484 | |
| 485 | nstime_delta(&rtt, &pinfo->abs_ts, &newest->ts); |
| 486 | ppd->acks_frame = newest->frame; |
| 487 | ppd->ack_rtt = rtt; |
| 488 | ppd->have_ack_rtt = true1; |
| 489 | |
| 490 | /* Karn's algorithm: a retransmitted segment yields no usable sample. */ |
| 491 | if (newest->retrans == 0) |
| 492 | udx_update_rtt(acking_flow, nstime_to_sec(&rtt)); |
| 493 | } |
| 494 | } |
| 495 | |
| 496 | static void |
| 497 | udx_analyze(packet_info *pinfo, udx_conv_t *conv, uint8_t flags, uint8_t data_offset, |
| 498 | uint32_t id, uint32_t window, uint32_t seq, uint32_t ack, |
| 499 | uint32_t payload_len, const uint32_t *sack_start, const uint32_t *sack_end, |
| 500 | unsigned n_sacks, udx_ppd_t *ppd) |
| 501 | { |
| 502 | unsigned dir = udx_direction(pinfo); |
| 503 | udx_flow_t *flow = udx_get_flow(conv, dir, id, &pinfo->abs_ts); |
| 504 | udx_flow_t *rflow; |
| 505 | udx_seg_t *seg; |
| 506 | bool_Bool consumes_seq; |
| 507 | |
| 508 | if (!conv->have_client_dir) { |
| 509 | conv->client_dir = dir; |
| 510 | conv->have_client_dir = true1; |
| 511 | } |
| 512 | |
| 513 | ppd->flow = flow; |
| 514 | ppd->seq = seq; |
| 515 | ppd->from_server = (dir != conv->client_dir); |
| 516 | |
| 517 | if (flow->paired == NULL((void*)0)) |
| 518 | udx_try_pair(conv, flow, ack); |
| 519 | rflow = flow->paired; |
| 520 | |
| 521 | /* DATA and END occupy a sequence number; MESSAGE is an unordered |
| 522 | * datagram outside the stream and a bare ACK only reports one. */ |
| 523 | consumes_seq = (flags & (UDX_FLAG_DATA0x01 | UDX_FLAG_END0x02)) != 0; |
| 524 | |
| 525 | if (consumes_seq) { |
| 526 | seg = (udx_seg_t *) wmem_tree_lookup32(flow->segs, seq); |
| 527 | |
| 528 | if (seg == NULL((void*)0)) { |
| 529 | if (flow->have_seq && UDX_SEQ_GT(seq, flow->max_seq + 1)((int32_t)((seq) - (flow->max_seq + 1)) > 0)) |
| 530 | ppd->flags |= UDX_A_LOST_SEGMENT0x0040; |
| 531 | else if (flow->have_seq && UDX_SEQ_LT(seq, flow->max_seq)((int32_t)((seq) - (flow->max_seq)) < 0)) |
| 532 | ppd->flags |= UDX_A_OUT_OF_ORDER0x0020; |
| 533 | |
| 534 | seg = wmem_new0(wmem_file_scope(), udx_seg_t)((udx_seg_t*)wmem_alloc0((wmem_file_scope()), sizeof(udx_seg_t ))); |
| 535 | seg->frame = pinfo->num; |
| 536 | seg->ts = pinfo->abs_ts; |
| 537 | seg->len = payload_len; |
| 538 | wmem_tree_insert32(flow->segs, seq, seg); |
| 539 | |
| 540 | if (!flow->have_seq) { |
| 541 | flow->base_seq = seq; |
| 542 | flow->max_seq = seq; |
| 543 | flow->have_seq = true1; |
| 544 | } else if (UDX_SEQ_GT(seq, flow->max_seq)((int32_t)((seq) - (flow->max_seq)) > 0)) { |
| 545 | flow->max_seq = seq; |
| 546 | } |
| 547 | flow->outstanding_pkts++; |
| 548 | flow->outstanding_bytes += payload_len; |
| 549 | } else { |
| 550 | double dt = nstime_to_sec(&pinfo->abs_ts) - nstime_to_sec(&seg->ts); |
| 551 | |
| 552 | seg->retrans++; |
| 553 | ppd->flags |= UDX_A_RETRANS0x0001; |
| 554 | |
| 555 | if (seg->acked_in_frame != 0) { |
| 556 | ppd->flags |= UDX_A_SPURIOUS0x0010; |
| 557 | } else if (flow->have_sacked && UDX_SEQ_GT(flow->max_sacked, seq)((int32_t)((flow->max_sacked) - (seq)) > 0)) { |
| 558 | /* The peer has selectively acknowledged later packets, so |
| 559 | * this one was resent because it was reported missing rather |
| 560 | * than because a timer expired. */ |
| 561 | ppd->flags |= UDX_A_FAST_RETRANS0x0002; |
| 562 | } else if (dt >= udx_rto(flow)) { |
| 563 | ppd->flags |= UDX_A_RTO_RETRANS0x0004; |
| 564 | } else if (dt < UDX_DUP_WINDOW0.0005) { |
| 565 | /* Too soon to be any sender timer: the datagram was |
| 566 | * delivered, or captured, twice. */ |
| 567 | ppd->flags |= UDX_A_DUPLICATE0x4000; |
| 568 | } else if (seq == flow->max_seq && |
| 569 | dt >= (flow->have_rtt ? 2 * flow->srtt : UDX_MIN_PROBE_DELAY0.010)) { |
| 570 | /* A repeat of the tail after a probe-sized pause, with |
| 571 | * nothing newer sent, is how a tail loss probe looks here. */ |
| 572 | ppd->flags |= UDX_A_TLP0x0008; |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | ppd->tracked = true1; |
| 577 | ppd->bytes_in_flight = flow->outstanding_bytes; |
| 578 | ppd->packets_in_flight = flow->outstanding_pkts; |
| 579 | |
| 580 | /* |
| 581 | * Position within the stream, counted from the first packet seen on |
| 582 | * this flow. Anything before that point arrived out of order at the |
| 583 | * very start of the capture and cannot be placed. |
| 584 | */ |
| 585 | if (payload_len > 0 && UDX_SEQ_GEQ(seq, flow->base_seq)((int32_t)((seq) - (flow->base_seq)) >= 0)) { |
| 586 | ppd->follow_offset = seq - flow->base_seq; |
| 587 | ppd->follow_ok = true1; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | if (flags & UDX_FLAG_END0x02) |
| 592 | ppd->flags |= UDX_A_END0x1000; |
| 593 | if (flags & UDX_FLAG_DESTROY0x10) |
| 594 | ppd->flags |= UDX_A_DESTROY0x2000; |
| 595 | |
| 596 | /* An MTU probe pads between the header and the payload; the same byte |
| 597 | * delimits SACK blocks when they are present. */ |
| 598 | if (data_offset > 0 && !(flags & UDX_FLAG_SACK0x04)) |
| 599 | ppd->flags |= UDX_A_MTU_PROBE0x0800; |
| 600 | |
| 601 | /* Acknowledgement side: retire the peer's segments, then record the |
| 602 | * selective ranges so a later repeat can be recognised as recovery. */ |
| 603 | if (rflow != NULL((void*)0)) { |
| 604 | if (!flow->have_ack || UDX_SEQ_GT(ack, flow->max_ack)((int32_t)((ack) - (flow->max_ack)) > 0)) |
| 605 | udx_process_ack(pinfo, rflow, flow, ack, ppd); |
| 606 | |
| 607 | for (unsigned i = 0; i < n_sacks; i++) { |
| 608 | uint32_t s; |
| 609 | unsigned guard = 0; |
| 610 | |
| 611 | for (s = sack_start[i]; UDX_SEQ_LT(s, sack_end[i])((int32_t)((s) - (sack_end[i])) < 0) && guard < 1024; |
| 612 | s++, guard++) { |
| 613 | udx_seg_t *ss = (udx_seg_t *) wmem_tree_lookup32(rflow->segs, s); |
| 614 | |
| 615 | if (ss != NULL((void*)0)) |
| 616 | ss->sacked = true1; |
| 617 | } |
| 618 | |
| 619 | /* Remember how far the selective acknowledgements reach: a |
| 620 | * retransmission below this point is loss recovery. */ |
| 621 | if (!rflow->have_sacked || UDX_SEQ_GT(sack_end[i] - 1, rflow->max_sacked)((int32_t)((sack_end[i] - 1) - (rflow->max_sacked)) > 0 )) { |
| 622 | rflow->max_sacked = sack_end[i] - 1; |
| 623 | rflow->have_sacked = true1; |
| 624 | } |
| 625 | } |
| 626 | } |
| 627 | |
| 628 | if (!flow->have_ack || UDX_SEQ_GT(ack, flow->max_ack)((int32_t)((ack) - (flow->max_ack)) > 0)) { |
| 629 | flow->max_ack = ack; |
| 630 | flow->have_ack = true1; |
| 631 | } |
| 632 | |
| 633 | /* Receive window transitions. */ |
| 634 | if (window == 0) { |
| 635 | ppd->flags |= UDX_A_ZERO_WIN0x0200; |
| 636 | flow->rwnd_zero = true1; |
| 637 | } else if (flow->rwnd_zero) { |
| 638 | ppd->flags |= UDX_A_WINDOW_UPDATE0x0400; |
| 639 | flow->rwnd_zero = false0; |
| 640 | } |
| 641 | flow->last_rwnd = window; |
| 642 | |
| 643 | /* |
| 644 | * Keepalives and zero-window probes are the same bytes on the wire: a |
| 645 | * bare heartbeat. Only the peer's advertised window tells them apart. |
| 646 | */ |
| 647 | if ((flags & UDX_FLAG_HEARTBEAT0x20) && payload_len == 0) { |
| 648 | if (rflow != NULL((void*)0) && rflow->rwnd_zero) |
| 649 | ppd->flags |= UDX_A_ZERO_WIN_PROBE0x0100; |
| 650 | else |
| 651 | ppd->flags |= UDX_A_KEEPALIVE0x0080; |
| 652 | } |
| 653 | |
| 654 | ppd->stream = (flow->paired != NULL((void*)0) && flow->paired->stream_num < flow->stream_num) |
| 655 | ? flow->paired->stream_num |
| 656 | : flow->stream_num; |
| 657 | } |
| 658 | |
| 659 | /* |
| 660 | * Render the verdicts reached on the first pass. Nothing here computes: on a |
| 661 | * revisit the stored results are simply replayed, so what is shown never |
| 662 | * depends on how the packet was reached. |
| 663 | */ |
| 664 | static void |
| 665 | udx_show_analysis(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, udx_ppd_t *ppd) |
| 666 | { |
| 667 | proto_item *ti; |
| 668 | proto_tree *an_tree; |
| 669 | udx_seg_t *seg; |
| 670 | |
| 671 | if (ppd->flow == NULL((void*)0)) |
| 672 | return; |
| 673 | |
| 674 | ti = proto_tree_add_uint(tree, hf_udx_stream, tvb, 0, 0, |
| 675 | (ppd->flow->paired != NULL((void*)0) && |
| 676 | ppd->flow->paired->stream_num < ppd->flow->stream_num) |
| 677 | ? ppd->flow->paired->stream_num |
| 678 | : ppd->flow->stream_num); |
| 679 | proto_item_set_generated(ti); |
| 680 | |
| 681 | /* Nothing to report on a packet that neither carries data nor advances |
| 682 | * an acknowledgement, so leave the subtree out entirely rather than |
| 683 | * showing an empty one. */ |
| 684 | if (ppd->flags == 0 && ppd->acks_frame == 0 && !ppd->tracked && |
| 685 | ppd->flow->paired != NULL((void*)0)) |
| 686 | return; |
| 687 | |
| 688 | ti = proto_tree_add_item(tree, hf_udx_analysis, tvb, 0, 0, ENC_NA0x00000000); |
| 689 | proto_item_set_generated(ti); |
| 690 | an_tree = proto_item_add_subtree(ti, ett_udx_analysis); |
| 691 | |
| 692 | if (ppd->flow->paired == NULL((void*)0)) { |
| 693 | proto_item *rev_ti = proto_tree_add_item(an_tree, hf_udx_analysis_no_reverse, |
| 694 | tvb, 0, 0, ENC_NA0x00000000); |
| 695 | proto_item_set_generated(rev_ti); |
| 696 | } |
| 697 | |
| 698 | if (ppd->acks_frame != 0) { |
| 699 | ti = proto_tree_add_uint(an_tree, hf_udx_analysis_acks_frame, tvb, 0, 0, |
| 700 | ppd->acks_frame); |
| 701 | proto_item_set_generated(ti); |
| 702 | |
| 703 | if (ppd->have_ack_rtt) { |
| 704 | ti = proto_tree_add_time(an_tree, hf_udx_analysis_ack_rtt, tvb, 0, 0, |
| 705 | &ppd->ack_rtt); |
| 706 | proto_item_set_generated(ti); |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | /* A packet that carried data learns only later which packet acked it. */ |
| 711 | if (ppd->tracked) { |
| 712 | seg = (udx_seg_t *) wmem_tree_lookup32(ppd->flow->segs, ppd->seq); |
| 713 | if (seg != NULL((void*)0) && seg->frame == pinfo->num && seg->acked_in_frame != 0) { |
| 714 | nstime_t rtt; |
| 715 | |
| 716 | ti = proto_tree_add_uint(an_tree, hf_udx_analysis_acked_in, tvb, 0, 0, |
| 717 | seg->acked_in_frame); |
| 718 | proto_item_set_generated(ti); |
| 719 | |
| 720 | nstime_delta(&rtt, &seg->ack_ts, &seg->ts); |
| 721 | ti = proto_tree_add_time(an_tree, hf_udx_analysis_ack_rtt, tvb, 0, 0, &rtt); |
| 722 | proto_item_set_generated(ti); |
| 723 | } |
| 724 | |
| 725 | ti = proto_tree_add_uint(an_tree, hf_udx_analysis_bytes_in_flight, tvb, 0, 0, |
| 726 | ppd->bytes_in_flight); |
| 727 | proto_item_set_generated(ti); |
| 728 | ti = proto_tree_add_uint(an_tree, hf_udx_analysis_pkts_in_flight, tvb, 0, 0, |
| 729 | ppd->packets_in_flight); |
| 730 | proto_item_set_generated(ti); |
| 731 | } |
| 732 | |
| 733 | /* Expert notes, most specific classification first. */ |
| 734 | if (ppd->flags & UDX_A_LOST_SEGMENT0x0040) |
| 735 | expert_add_info(pinfo, ti, &ei_udx_lost_segment); |
| 736 | if (ppd->flags & UDX_A_OUT_OF_ORDER0x0020) |
| 737 | expert_add_info(pinfo, ti, &ei_udx_out_of_order); |
| 738 | |
| 739 | if (ppd->flags & UDX_A_SPURIOUS0x0010) |
| 740 | expert_add_info(pinfo, ti, &ei_udx_spurious_retrans); |
| 741 | else if (ppd->flags & UDX_A_FAST_RETRANS0x0002) |
| 742 | expert_add_info(pinfo, ti, &ei_udx_fast_retrans); |
| 743 | else if (ppd->flags & UDX_A_RTO_RETRANS0x0004) |
| 744 | expert_add_info(pinfo, ti, &ei_udx_rto_retrans); |
| 745 | else if (ppd->flags & UDX_A_TLP0x0008) |
| 746 | expert_add_info(pinfo, ti, &ei_udx_tlp); |
| 747 | else if (ppd->flags & UDX_A_DUPLICATE0x4000) |
| 748 | expert_add_info(pinfo, ti, &ei_udx_duplicate); |
| 749 | else if (ppd->flags & UDX_A_RETRANS0x0001) |
| 750 | expert_add_info(pinfo, ti, &ei_udx_retrans); |
| 751 | |
| 752 | if (ppd->flags & UDX_A_ZERO_WIN_PROBE0x0100) |
| 753 | expert_add_info(pinfo, ti, &ei_udx_zero_window_probe); |
| 754 | else if (ppd->flags & UDX_A_KEEPALIVE0x0080) |
| 755 | expert_add_info(pinfo, ti, &ei_udx_keepalive); |
| 756 | |
| 757 | if (ppd->flags & UDX_A_ZERO_WIN0x0200) |
| 758 | expert_add_info(pinfo, ti, &ei_udx_zero_window); |
| 759 | if (ppd->flags & UDX_A_WINDOW_UPDATE0x0400) |
| 760 | expert_add_info(pinfo, ti, &ei_udx_window_update); |
| 761 | if (ppd->flags & UDX_A_MTU_PROBE0x0800) |
| 762 | expert_add_info(pinfo, ti, &ei_udx_mtu_probe); |
| 763 | if (ppd->flags & UDX_A_END0x1000) |
| 764 | expert_add_info(pinfo, ti, &ei_udx_end); |
| 765 | if (ppd->flags & UDX_A_DESTROY0x2000) |
| 766 | expert_add_info(pinfo, ti, &ei_udx_destroy); |
| 767 | } |
| 768 | |
| 769 | |
| 770 | /* |
| 771 | * Follow stream. |
| 772 | * |
| 773 | * Payload is delivered in sequence order per direction. A packet that |
| 774 | * arrives early is held until the gap before it is filled, and a payload |
| 775 | * already delivered - a retransmission - is dropped, so the reassembled |
| 776 | * conversation reads the way the application saw it rather than the way the |
| 777 | * network happened to deliver it. |
| 778 | */ |
| 779 | |
| 780 | /* Stream numbers restart with every capture file, as they do for TCP. */ |
| 781 | static void |
| 782 | udx_init(void) |
| 783 | { |
| 784 | udx_stream_count = 0; |
| 785 | } |
| 786 | |
| 787 | static char * |
| 788 | udx_follow_conv_filter(epan_dissect_t *edt _U___attribute__((unused)), packet_info *pinfo, |
| 789 | unsigned *stream, unsigned *sub_stream _U___attribute__((unused))) |
| 790 | { |
| 791 | udx_ppd_t *ppd = (udx_ppd_t *) p_get_proto_data(wmem_file_scope(), pinfo, proto_udx, 0); |
| 792 | |
| 793 | if (ppd == NULL((void*)0) || ppd->flow == NULL((void*)0)) |
| 794 | return NULL((void*)0); |
| 795 | |
| 796 | *stream = ppd->stream; |
| 797 | return ws_strdup_printf("udx.stream eq %u", ppd->stream)wmem_strdup_printf(((void*)0), "udx.stream eq %u", ppd->stream ); |
| 798 | } |
| 799 | |
| 800 | static char * |
| 801 | udx_follow_index_filter(unsigned stream, unsigned sub_stream _U___attribute__((unused))) |
| 802 | { |
| 803 | return ws_strdup_printf("udx.stream eq %u", stream)wmem_strdup_printf(((void*)0), "udx.stream eq %u", stream); |
| 804 | } |
| 805 | |
| 806 | static unsigned |
| 807 | udx_get_stream_count(void) |
| 808 | { |
| 809 | return udx_stream_count; |
| 810 | } |
| 811 | |
| 812 | static void |
| 813 | udx_follow_append(follow_info_t *follow_info, follow_record_t *record) |
| 814 | { |
| 815 | follow_info->payload = g_list_prepend(follow_info->payload, record); |
| 816 | follow_info->bytes_written[record->is_server ? 1 : 0] += record->data->len; |
| 817 | } |
| 818 | |
| 819 | static int |
| 820 | udx_follow_seq_cmp(const void *a, const void *b) |
| 821 | { |
| 822 | const follow_record_t *ra = (const follow_record_t *) a; |
| 823 | const follow_record_t *rb = (const follow_record_t *) b; |
| 824 | |
| 825 | if (ra->seq == rb->seq) |
| 826 | return 0; |
| 827 | return (ra->seq < rb->seq) ? -1 : 1; |
| 828 | } |
| 829 | |
| 830 | /* |
| 831 | * Release held payload that now continues the stream. The pending list is |
| 832 | * kept in sequence order, so this only ever walks its front. |
| 833 | */ |
| 834 | static void |
| 835 | udx_follow_drain(follow_info_t *follow_info, int dir) |
| 836 | { |
| 837 | while (follow_info->fragments[dir] != NULL((void*)0)) { |
| 838 | follow_record_t *held = (follow_record_t *) follow_info->fragments[dir]->data; |
| 839 | |
| 840 | if (held->seq != follow_info->seq[dir]) |
| 841 | break; |
| 842 | |
| 843 | follow_info->seq[dir]++; |
| 844 | follow_info->fragments[dir] = g_list_delete_link(follow_info->fragments[dir], |
| 845 | follow_info->fragments[dir]); |
| 846 | udx_follow_append(follow_info, held); |
| 847 | } |
| 848 | } |
| 849 | |
| 850 | static tap_packet_status |
| 851 | udx_follow_tap_listener(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U___attribute__((unused)), |
| 852 | const void *data, tap_flags_t flags _U___attribute__((unused))) |
| 853 | { |
| 854 | follow_info_t *follow_info = (follow_info_t *) tapdata; |
| 855 | const udx_follow_tap_data_t *follow_data = (const udx_follow_tap_data_t *) data; |
| 856 | follow_record_t *record; |
| 857 | unsigned length = tvb_captured_length(follow_data->tvb); |
| 858 | int dir = follow_data->from_server ? 1 : 0; |
| 859 | |
| 860 | if (follow_info->stream_id != follow_data->stream) |
| 861 | return TAP_PACKET_DONT_REDRAW; |
| 862 | |
| 863 | /* Already delivered: a retransmission or a duplicate. */ |
| 864 | if (follow_data->offset < follow_info->seq[dir]) |
| 865 | return TAP_PACKET_DONT_REDRAW; |
| 866 | |
| 867 | record = g_new0(follow_record_t, 1)((follow_record_t *) g_malloc0_n ((1), sizeof (follow_record_t ))); |
| 868 | record->is_server = follow_data->from_server; |
| 869 | record->packet_num = pinfo->fd->num; |
| 870 | record->abs_ts = pinfo->fd->abs_ts; |
| 871 | record->seq = follow_data->offset; |
| 872 | record->data = g_byte_array_sized_new(length); |
| 873 | record->data = g_byte_array_append(record->data, |
| 874 | tvb_get_ptr(follow_data->tvb, 0, length), length); |
| 875 | |
| 876 | if (follow_data->from_server) { |
| 877 | if (follow_info->server_port == 0) { |
| 878 | follow_info->server_port = pinfo->srcport; |
| 879 | copy_address(&follow_info->server_ip, &pinfo->src); |
| 880 | follow_info->client_port = pinfo->destport; |
| 881 | copy_address(&follow_info->client_ip, &pinfo->dst); |
| 882 | } |
| 883 | } else { |
| 884 | if (follow_info->client_port == 0) { |
| 885 | follow_info->client_port = pinfo->srcport; |
| 886 | copy_address(&follow_info->client_ip, &pinfo->src); |
| 887 | follow_info->server_port = pinfo->destport; |
| 888 | copy_address(&follow_info->server_ip, &pinfo->dst); |
| 889 | } |
| 890 | } |
| 891 | |
| 892 | if (follow_data->offset == follow_info->seq[dir]) { |
| 893 | follow_info->seq[dir]++; |
| 894 | udx_follow_append(follow_info, record); |
| 895 | udx_follow_drain(follow_info, dir); |
| 896 | } else { |
| 897 | /* Arrived early: hold it, in order, until the gap ahead is filled. |
| 898 | * The framework frees whatever is still pending when the stream is |
| 899 | * reset, so an unfilled gap leaks nothing. */ |
| 900 | follow_info->fragments[dir] = g_list_insert_sorted(follow_info->fragments[dir], |
| 901 | record, udx_follow_seq_cmp); |
| 902 | } |
| 903 | |
| 904 | return TAP_PACKET_DONT_REDRAW; |
| 905 | } |
| 906 | |
| 907 | static int |
| 908 | dissect_udx(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U___attribute__((unused))) |
| 909 | { |
| 910 | proto_item *ti; |
| 911 | proto_tree *udx_tree; |
| 912 | uint8_t flags, data_offset; |
| 913 | uint32_t id, window, seq, ack; |
| 914 | int offset = 0; |
| 915 | int sack_end_offset; |
| 916 | unsigned payload_len; |
| 917 | char flags_str[64]; |
| 918 | uint32_t sack_start[UDX_MAX_SACK_BLOCKS32]; |
| 919 | uint32_t sack_end[UDX_MAX_SACK_BLOCKS32]; |
| 920 | unsigned n_sacks = 0; |
| 921 | udx_ppd_t *ppd = NULL((void*)0); |
| 922 | |
| 923 | /* |
| 924 | * Reached either from the heuristic, which has already validated the |
| 925 | * header, or directly once a conversation has been claimed or through |
| 926 | * "Decode As". The latter routes make this check load bearing. |
| 927 | */ |
| 928 | if (tvb_reported_length(tvb) < UDX_HEADER_SIZE20) |
| 929 | return 0; |
| 930 | |
| 931 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "UDX"); |
| 932 | col_clear(pinfo->cinfo, COL_INFO); |
| 933 | |
| 934 | flags = tvb_get_uint8(tvb, 2); |
| 935 | data_offset = tvb_get_uint8(tvb, 3); |
| 936 | id = tvb_get_letohl(tvb, 4); |
| 937 | window = tvb_get_letohl(tvb, 8); |
| 938 | seq = tvb_get_letohl(tvb, 12); |
| 939 | ack = tvb_get_letohl(tvb, 16); |
| 940 | |
| 941 | udx_flags_to_str(flags, flags_str, sizeof(flags_str)); |
| 942 | |
| 943 | /* Collect the selective acknowledgement ranges before anything is added |
| 944 | * to the tree: the analysis below needs them, and the display needs the |
| 945 | * analysis. */ |
| 946 | if (flags & UDX_FLAG_SACK0x04) { |
| 947 | /* Blocks fill the area delimited by data_offset; a packet with no |
| 948 | * payload may leave that byte zero and run to the end instead. */ |
| 949 | sack_end_offset = (data_offset > 0) |
| 950 | ? UDX_HEADER_SIZE20 + data_offset |
| 951 | : (int) tvb_reported_length(tvb); |
| 952 | } else { |
| 953 | /* Anything reserved without SACK blocks is MTU probe padding. */ |
| 954 | sack_end_offset = UDX_HEADER_SIZE20 + data_offset; |
| 955 | } |
| 956 | |
| 957 | /* data_offset is not trustworthy on a packet this dissector did not |
| 958 | * validate, so never let it point past the datagram. */ |
| 959 | sack_end_offset = MIN(sack_end_offset, (int) tvb_reported_length(tvb))(((sack_end_offset) < ((int) tvb_reported_length(tvb))) ? ( sack_end_offset) : ((int) tvb_reported_length(tvb))); |
| 960 | |
| 961 | if (flags & UDX_FLAG_SACK0x04) { |
| 962 | int pos = UDX_HEADER_SIZE20; |
| 963 | |
| 964 | while (pos + 8 <= sack_end_offset && n_sacks < UDX_MAX_SACK_BLOCKS32) { |
| 965 | sack_start[n_sacks] = tvb_get_letohl(tvb, pos); |
| 966 | sack_end[n_sacks] = tvb_get_letohl(tvb, pos + 4); |
| 967 | n_sacks++; |
| 968 | pos += 8; |
| 969 | } |
| 970 | } |
| 971 | |
| 972 | payload_len = (unsigned) MAX(0, (int) tvb_reported_length(tvb) - sack_end_offset)(((0) > ((int) tvb_reported_length(tvb) - sack_end_offset) ) ? (0) : ((int) tvb_reported_length(tvb) - sack_end_offset)); |
| 973 | |
| 974 | if (udx_analyze_sequence_numbers) { |
| 975 | if (!PINFO_FD_VISITED(pinfo)((pinfo)->fd->visited)) { |
| 976 | conversation_t *conversation = find_or_create_conversation(pinfo); |
| 977 | udx_conv_t *conv; |
| 978 | |
| 979 | conv = (udx_conv_t *) conversation_get_proto_data(conversation, proto_udx); |
| 980 | if (conv == NULL((void*)0)) { |
| 981 | conv = wmem_new0(wmem_file_scope(), udx_conv_t)((udx_conv_t*)wmem_alloc0((wmem_file_scope()), sizeof(udx_conv_t ))); |
| 982 | conv->flows = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal); |
| 983 | conversation_add_proto_data(conversation, proto_udx, conv); |
| 984 | } |
| 985 | |
| 986 | ppd = wmem_new0(wmem_file_scope(), udx_ppd_t)((udx_ppd_t*)wmem_alloc0((wmem_file_scope()), sizeof(udx_ppd_t ))); |
| 987 | udx_analyze(pinfo, conv, flags, data_offset, id, window, seq, ack, |
| 988 | payload_len, sack_start, sack_end, n_sacks, ppd); |
| 989 | p_add_proto_data(wmem_file_scope(), pinfo, proto_udx, 0, ppd); |
| 990 | } else { |
| 991 | ppd = (udx_ppd_t *) p_get_proto_data(wmem_file_scope(), pinfo, proto_udx, 0); |
| 992 | } |
| 993 | } |
| 994 | |
| 995 | ti = proto_tree_add_item(tree, proto_udx, tvb, 0, -1, ENC_NA0x00000000); |
| 996 | proto_item_append_text(ti, ", %s, Id: %u, Seq: %u, Ack: %u", flags_str, id, seq, ack); |
| 997 | udx_tree = proto_item_add_subtree(ti, ett_udx); |
| 998 | |
| 999 | proto_tree_add_item(udx_tree, hf_udx_magic, tvb, offset, 1, ENC_NA0x00000000); |
| 1000 | offset += 1; |
| 1001 | proto_tree_add_item(udx_tree, hf_udx_version, tvb, offset, 1, ENC_NA0x00000000); |
| 1002 | offset += 1; |
| 1003 | proto_tree_add_bitmask(udx_tree, tvb, offset, hf_udx_flags, ett_udx_flags, |
| 1004 | udx_flag_fields, ENC_NA0x00000000); |
| 1005 | offset += 1; |
| 1006 | proto_tree_add_item(udx_tree, hf_udx_data_offset, tvb, offset, 1, ENC_NA0x00000000); |
| 1007 | offset += 1; |
| 1008 | proto_tree_add_item(udx_tree, hf_udx_id, tvb, offset, 4, ENC_LITTLE_ENDIAN0x80000000); |
| 1009 | offset += 4; |
| 1010 | proto_tree_add_item(udx_tree, hf_udx_window, tvb, offset, 4, ENC_LITTLE_ENDIAN0x80000000); |
| 1011 | offset += 4; |
| 1012 | proto_tree_add_item(udx_tree, hf_udx_seq, tvb, offset, 4, ENC_LITTLE_ENDIAN0x80000000); |
| 1013 | offset += 4; |
| 1014 | proto_tree_add_item(udx_tree, hf_udx_ack, tvb, offset, 4, ENC_LITTLE_ENDIAN0x80000000); |
| 1015 | offset += 4; |
| 1016 | |
| 1017 | if (n_sacks > 0) { |
| 1018 | proto_item *sacks_ti; |
| 1019 | proto_tree *sacks_tree, *block_tree; |
| 1020 | |
| 1021 | sacks_ti = proto_tree_add_item(udx_tree, hf_udx_sacks, tvb, offset, |
| 1022 | sack_end_offset - offset, ENC_NA0x00000000); |
| 1023 | proto_item_append_text(sacks_ti, " (%u)", n_sacks); |
| 1024 | sacks_tree = proto_item_add_subtree(sacks_ti, ett_udx_sacks); |
| 1025 | |
| 1026 | for (unsigned i = 0; i < n_sacks; i++) { |
| 1027 | block_tree = proto_tree_add_subtree_format(sacks_tree, tvb, offset, 8, |
| 1028 | ett_udx_sack_block, NULL((void*)0), |
| 1029 | "SACK: %u-%u", |
| 1030 | sack_start[i], sack_end[i]); |
| 1031 | proto_tree_add_item(block_tree, hf_udx_sack_start, tvb, offset, 4, |
| 1032 | ENC_LITTLE_ENDIAN0x80000000); |
| 1033 | proto_tree_add_item(block_tree, hf_udx_sack_end, tvb, offset + 4, 4, |
| 1034 | ENC_LITTLE_ENDIAN0x80000000); |
| 1035 | offset += 8; |
| 1036 | } |
| 1037 | } else if (!(flags & UDX_FLAG_SACK0x04) && data_offset > 0) { |
| 1038 | /* |
| 1039 | * Padding between header and payload with no SACK blocks: inserted by |
| 1040 | * mtu_probeify_packet() in libudx - this datagram is an MTU probe. |
| 1041 | */ |
| 1042 | proto_tree_add_item(udx_tree, hf_udx_padding, tvb, offset, data_offset, ENC_NA0x00000000); |
| 1043 | offset += data_offset; |
Value stored to 'offset' is never read | |
| 1044 | } |
| 1045 | |
| 1046 | if (payload_len > 0) { |
| 1047 | ti = proto_tree_add_uint(udx_tree, hf_udx_payload_len, tvb, 0, 0, payload_len); |
| 1048 | proto_item_set_generated(ti); |
| 1049 | proto_tree_add_item(udx_tree, hf_udx_payload, tvb, sack_end_offset, |
| 1050 | (int) payload_len, ENC_NA0x00000000); |
| 1051 | } |
| 1052 | |
| 1053 | if (ppd != NULL((void*)0)) { |
| 1054 | udx_show_analysis(tvb, pinfo, udx_tree, ppd); |
| 1055 | |
| 1056 | /* MESSAGE payloads travel outside the ordered stream, so they are |
| 1057 | * shown per packet but left out of the reassembled conversation. */ |
| 1058 | if (ppd->follow_ok && !(flags & UDX_FLAG_MESSAGE0x08) && |
| 1059 | have_tap_listener(udx_follow_tap)) { |
| 1060 | udx_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, udx_follow_tap_data_t)((udx_follow_tap_data_t*)wmem_alloc0((pinfo->pool), sizeof (udx_follow_tap_data_t))); |
| 1061 | |
| 1062 | follow_data->tvb = tvb_new_subset_length(tvb, sack_end_offset, (int) payload_len); |
| 1063 | follow_data->stream = ppd->stream; |
| 1064 | follow_data->offset = ppd->follow_offset; |
| 1065 | follow_data->from_server = ppd->from_server; |
| 1066 | tap_queue_packet(udx_follow_tap, pinfo, follow_data); |
| 1067 | } |
| 1068 | } |
| 1069 | |
| 1070 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s Id=%u Seq=%u Ack=%u Rwnd=%u", |
| 1071 | flags_str, id, seq, ack, window); |
| 1072 | if (payload_len > 0) |
| 1073 | col_append_fstr(pinfo->cinfo, COL_INFO, " Len=%u", payload_len); |
| 1074 | if (ppd != NULL((void*)0) && (ppd->flags & UDX_A_RETRANS0x0001)) |
| 1075 | col_append_str(pinfo->cinfo, COL_INFO, " [retransmission]"); |
| 1076 | |
| 1077 | return tvb_reported_length(tvb); |
| 1078 | } |
| 1079 | |
| 1080 | static bool_Bool |
| 1081 | test_udx(tvbuff_t *tvb) |
| 1082 | { |
| 1083 | uint8_t flags, data_offset; |
| 1084 | |
| 1085 | if (tvb_captured_length(tvb) < UDX_HEADER_SIZE20) |
| 1086 | return false0; |
| 1087 | if (tvb_get_uint8(tvb, 0) != UDX_MAGIC_BYTE0xff) |
| 1088 | return false0; |
| 1089 | if (tvb_get_uint8(tvb, 1) != UDX_VERSION1) |
| 1090 | return false0; |
| 1091 | |
| 1092 | flags = tvb_get_uint8(tvb, 2); |
| 1093 | if (flags & ~UDX_FLAG_MASK0x3f) |
| 1094 | return false0; |
| 1095 | |
| 1096 | data_offset = tvb_get_uint8(tvb, 3); |
| 1097 | if (UDX_HEADER_SIZE20 + (unsigned) data_offset > tvb_reported_length(tvb)) |
| 1098 | return false0; |
| 1099 | /* The area delimited by data_offset holds SACK blocks (uint32 pairs) when |
| 1100 | * the SACK flag is set - anything not a multiple of 8 is not UDX. */ |
| 1101 | if ((flags & UDX_FLAG_SACK0x04) && data_offset > 0 && (data_offset % 8) != 0) |
| 1102 | return false0; |
| 1103 | |
| 1104 | /* |
| 1105 | * Only DATA and MESSAGE packets carry a payload. Everything else is the |
| 1106 | * fixed header followed at most by selective acknowledgement blocks, so |
| 1107 | * its length is known exactly and anything else is not UDX. |
| 1108 | */ |
| 1109 | if (!(flags & (UDX_FLAG_DATA0x01 | UDX_FLAG_MESSAGE0x08))) { |
| 1110 | unsigned trailing = tvb_reported_length(tvb) - UDX_HEADER_SIZE20; |
| 1111 | |
| 1112 | if (flags & UDX_FLAG_SACK0x04) { |
| 1113 | if ((trailing % 8) != 0) |
| 1114 | return false0; |
| 1115 | } else if (trailing != 0) { |
| 1116 | return false0; |
| 1117 | } |
| 1118 | } |
| 1119 | |
| 1120 | return true1; |
| 1121 | } |
| 1122 | |
| 1123 | static bool_Bool |
| 1124 | dissect_udx_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
| 1125 | { |
| 1126 | conversation_t *conversation; |
| 1127 | |
| 1128 | if (!test_udx(tvb)) |
| 1129 | return false0; |
| 1130 | |
| 1131 | /* Claim the whole UDP conversation so weaker frames (e.g. bare 20-byte |
| 1132 | * heartbeats) and future packets skip the heuristic. */ |
| 1133 | conversation = find_or_create_conversation(pinfo); |
| 1134 | conversation_set_dissector(conversation, udx_handle); |
| 1135 | |
| 1136 | dissect_udx(tvb, pinfo, tree, data); |
| 1137 | return true1; |
| 1138 | } |
| 1139 | |
| 1140 | void |
| 1141 | proto_register_udx(void) |
| 1142 | { |
| 1143 | static hf_register_info hf[] = { |
| 1144 | { &hf_udx_magic, |
| 1145 | { "Magic Byte", "udx.magic_byte", FT_UINT8, BASE_HEX, NULL((void*)0), 0x0, |
| 1146 | "Always 0xff", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1147 | }, |
| 1148 | { &hf_udx_version, |
| 1149 | { "Version", "udx.version", FT_UINT8, BASE_DEC, NULL((void*)0), 0x0, |
| 1150 | NULL((void*)0), HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1151 | }, |
| 1152 | { &hf_udx_flags, |
| 1153 | { "Type", "udx.type", FT_UINT8, BASE_HEX, NULL((void*)0), 0x0, |
| 1154 | "Packet type flags", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1155 | }, |
| 1156 | { &hf_udx_flags_data, |
| 1157 | { "Data", "udx.type.data", FT_BOOLEAN, 8, NULL((void*)0), UDX_FLAG_DATA0x01, |
| 1158 | "Carries stream payload", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1159 | }, |
| 1160 | { &hf_udx_flags_end, |
| 1161 | { "End", "udx.type.end", FT_BOOLEAN, 8, NULL((void*)0), UDX_FLAG_END0x02, |
| 1162 | "Graceful end of stream (consumes a sequence number)", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1163 | }, |
| 1164 | { &hf_udx_flags_sack, |
| 1165 | { "SACK", "udx.type.sack", FT_BOOLEAN, 8, NULL((void*)0), UDX_FLAG_SACK0x04, |
| 1166 | "Carries selective acknowledgement blocks", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1167 | }, |
| 1168 | { &hf_udx_flags_message, |
| 1169 | { "Message", "udx.type.message", FT_BOOLEAN, 8, NULL((void*)0), UDX_FLAG_MESSAGE0x08, |
| 1170 | "Unordered datagram outside the byte stream", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1171 | }, |
| 1172 | { &hf_udx_flags_destroy, |
| 1173 | { "Destroy", "udx.type.destroy", FT_BOOLEAN, 8, NULL((void*)0), UDX_FLAG_DESTROY0x10, |
| 1174 | "Abrupt stream termination", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1175 | }, |
| 1176 | { &hf_udx_flags_heartbeat, |
| 1177 | { "Heartbeat", "udx.type.heartbeat", FT_BOOLEAN, 8, NULL((void*)0), UDX_FLAG_HEARTBEAT0x20, |
| 1178 | "Keepalive or zero-window probe", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1179 | }, |
| 1180 | { &hf_udx_data_offset, |
| 1181 | { "Data Offset", "udx.data_offset", FT_UINT8, BASE_DEC, NULL((void*)0), 0x0, |
| 1182 | "Bytes between the fixed header and the payload", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1183 | }, |
| 1184 | { &hf_udx_id, |
| 1185 | { "Id", "udx.id", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1186 | "Receiver's stream id", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1187 | }, |
| 1188 | { &hf_udx_window, |
| 1189 | { "Window", "udx.rwnd", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1190 | "Sender's receive window in bytes", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1191 | }, |
| 1192 | { &hf_udx_seq, |
| 1193 | { "Seq", "udx.seq", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1194 | "Packet sequence number (counts packets, not bytes)", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1195 | }, |
| 1196 | { &hf_udx_ack, |
| 1197 | { "Ack", "udx.ack", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1198 | "Next sequence number expected from the peer", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1199 | }, |
| 1200 | { &hf_udx_sacks, |
| 1201 | { "SACK Blocks", "udx.sacks", FT_NONE, BASE_NONE, NULL((void*)0), 0x0, |
| 1202 | "Selective acknowledgement ranges", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1203 | }, |
| 1204 | { &hf_udx_sack_block, |
| 1205 | { "SACK Block", "udx.sack", FT_NONE, BASE_NONE, NULL((void*)0), 0x0, |
| 1206 | NULL((void*)0), HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1207 | }, |
| 1208 | { &hf_udx_sack_start, |
| 1209 | { "Start", "udx.sack.start", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1210 | "First sequence number in the acknowledged range", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1211 | }, |
| 1212 | { &hf_udx_sack_end, |
| 1213 | { "End", "udx.sack.end", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1214 | "One past the last acknowledged sequence number", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1215 | }, |
| 1216 | { &hf_udx_padding, |
| 1217 | { "Padding", "udx.padding", FT_BYTES, BASE_NONE, NULL((void*)0), 0x0, |
| 1218 | "MTU probe padding", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1219 | }, |
| 1220 | { &hf_udx_payload, |
| 1221 | { "Payload", "udx.payload", FT_BYTES, BASE_NONE, NULL((void*)0), 0x0, |
| 1222 | NULL((void*)0), HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1223 | }, |
| 1224 | { &hf_udx_payload_len, |
| 1225 | { "Payload Length", "udx.length", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1226 | NULL((void*)0), HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1227 | }, |
| 1228 | { &hf_udx_stream, |
| 1229 | { "Stream index", "udx.stream", FT_UINT32, BASE_DEC, NULL((void*)0), 0x0, |
| 1230 | "Index of the paired flows carrying this stream", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1231 | }, |
| 1232 | { &hf_udx_analysis, |
| 1233 | { "SEQ/ACK analysis", "udx.analysis", FT_NONE, BASE_NONE, NULL((void*)0), 0x0, |
| 1234 | "Results of the sequence number analysis", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1235 | }, |
| 1236 | { &hf_udx_analysis_acks_frame, |
| 1237 | { "This is an ACK to the packet in frame", "udx.analysis.acks_frame", |
| 1238 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_ACK)((gpointer) (glong) (FT_FRAMENUM_ACK)), 0x0, |
| 1239 | NULL((void*)0), HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1240 | }, |
| 1241 | { &hf_udx_analysis_acked_in, |
| 1242 | { "ACKed in frame", "udx.analysis.acked_in", FT_FRAMENUM, BASE_NONE, |
| 1243 | FRAMENUM_TYPE(FT_FRAMENUM_NONE)((gpointer) (glong) (FT_FRAMENUM_NONE)), 0x0, |
| 1244 | "The frame that acknowledged this packet", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1245 | }, |
| 1246 | { &hf_udx_analysis_ack_rtt, |
| 1247 | { "Time to ACK", "udx.analysis.ack_rtt", FT_RELATIVE_TIME, BASE_NONE, NULL((void*)0), 0x0, |
| 1248 | "Time between the packet and its acknowledgement", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1249 | }, |
| 1250 | { &hf_udx_analysis_bytes_in_flight, |
| 1251 | { "Bytes in flight", "udx.analysis.bytes_in_flight", FT_UINT32, BASE_DEC, |
| 1252 | NULL((void*)0), 0x0, "Unacknowledged payload bytes on this flow", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1253 | }, |
| 1254 | { &hf_udx_analysis_pkts_in_flight, |
| 1255 | { "Packets in flight", "udx.analysis.packets_in_flight", FT_UINT32, BASE_DEC, |
| 1256 | NULL((void*)0), 0x0, "Unacknowledged packets on this flow", HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1257 | }, |
| 1258 | { &hf_udx_analysis_no_reverse, |
| 1259 | { "Reverse flow not identified", "udx.analysis.no_reverse", FT_NONE, BASE_NONE, |
| 1260 | NULL((void*)0), 0x0, "The stream carrying the other direction has not been paired", |
| 1261 | HFILL-1, 0, HF_REF_TYPE_NONE, -1, ((void*)0) } |
| 1262 | }, |
| 1263 | }; |
| 1264 | |
| 1265 | static int *ett[] = { |
| 1266 | &ett_udx, |
| 1267 | &ett_udx_flags, |
| 1268 | &ett_udx_sacks, |
| 1269 | &ett_udx_sack_block, |
| 1270 | &ett_udx_analysis, |
| 1271 | }; |
| 1272 | |
| 1273 | static ei_register_info ei[] = { |
| 1274 | { &ei_udx_retrans, |
| 1275 | { "udx.analysis.retransmission", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1276 | "This packet was retransmitted", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1277 | }, |
| 1278 | { &ei_udx_fast_retrans, |
| 1279 | { "udx.analysis.fast_retransmission", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1280 | "Fast retransmission: resent while later packets were selectively" |
| 1281 | " acknowledged", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1282 | }, |
| 1283 | { &ei_udx_rto_retrans, |
| 1284 | { "udx.analysis.rto_retransmission", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1285 | "Retransmission timeout: resent after more than the estimated RTO", |
| 1286 | EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1287 | }, |
| 1288 | { &ei_udx_tlp, |
| 1289 | { "udx.analysis.tail_loss_probe", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1290 | "Tail loss probe: the last packet of a burst was resent to elicit an" |
| 1291 | " acknowledgement", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1292 | }, |
| 1293 | { &ei_udx_spurious_retrans, |
| 1294 | { "udx.analysis.spurious_retransmission", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1295 | "Spurious retransmission: this packet was already acknowledged", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1296 | }, |
| 1297 | { &ei_udx_duplicate, |
| 1298 | { "udx.analysis.duplicate", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1299 | "Duplicate packet: the same packet was seen twice in quick succession", |
| 1300 | EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1301 | }, |
| 1302 | { &ei_udx_out_of_order, |
| 1303 | { "udx.analysis.out_of_order", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1304 | "Out-of-order packet", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1305 | }, |
| 1306 | { &ei_udx_lost_segment, |
| 1307 | { "udx.analysis.lost_segment", PI_SEQUENCE0x02000000, PI_WARN0x00600000, |
| 1308 | "Previous packet not captured: a sequence number was skipped", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1309 | }, |
| 1310 | { &ei_udx_keepalive, |
| 1311 | { "udx.analysis.keepalive", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1312 | "Keepalive", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1313 | }, |
| 1314 | { &ei_udx_zero_window_probe, |
| 1315 | { "udx.analysis.zero_window_probe", PI_SEQUENCE0x02000000, PI_NOTE0x00400000, |
| 1316 | "Zero window probe: sent while the peer advertised no receive window", |
| 1317 | EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1318 | }, |
| 1319 | { &ei_udx_zero_window, |
| 1320 | { "udx.analysis.zero_window", PI_SEQUENCE0x02000000, PI_WARN0x00600000, |
| 1321 | "Zero window: the sender cannot accept more data", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1322 | }, |
| 1323 | { &ei_udx_window_update, |
| 1324 | { "udx.analysis.window_update", PI_SEQUENCE0x02000000, PI_CHAT0x00200000, |
| 1325 | "Window update: the receive window reopened", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1326 | }, |
| 1327 | { &ei_udx_mtu_probe, |
| 1328 | { "udx.analysis.mtu_probe", PI_SEQUENCE0x02000000, PI_CHAT0x00200000, |
| 1329 | "MTU probe: padded to test a larger path MTU", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1330 | }, |
| 1331 | { &ei_udx_end, |
| 1332 | { "udx.analysis.end", PI_SEQUENCE0x02000000, PI_CHAT0x00200000, |
| 1333 | "End of stream", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1334 | }, |
| 1335 | { &ei_udx_destroy, |
| 1336 | { "udx.analysis.destroy", PI_SEQUENCE0x02000000, PI_WARN0x00600000, |
| 1337 | "Stream destroyed: abrupt termination", EXPFILL0, ((void*)0), 0, ((void*)0), {0, {((void*)0), ((void*)0), FT_NONE , BASE_NONE, ((void*)0), 0, ((void*)0), -1, 0, HF_REF_TYPE_NONE , -1, ((void*)0)}} } |
| 1338 | }, |
| 1339 | }; |
| 1340 | |
| 1341 | expert_module_t *expert_udx; |
| 1342 | module_t *udx_module; |
| 1343 | |
| 1344 | proto_udx = proto_register_protocol("UDX Protocol", "UDX", "udx"); |
| 1345 | proto_register_field_array(proto_udx, hf, array_length(hf)(sizeof (hf) / sizeof (hf)[0])); |
| 1346 | proto_register_subtree_array(ett, array_length(ett)(sizeof (ett) / sizeof (ett)[0])); |
| 1347 | |
| 1348 | expert_udx = expert_register_protocol(proto_udx); |
| 1349 | expert_register_field_array(expert_udx, ei, array_length(ei)(sizeof (ei) / sizeof (ei)[0])); |
| 1350 | |
| 1351 | udx_handle = register_dissector("udx", dissect_udx, proto_udx); |
| 1352 | |
| 1353 | register_init_routine(udx_init); |
| 1354 | |
| 1355 | udx_follow_tap = register_tap("udx_follow"); |
| 1356 | register_follow_stream(proto_udx, "udx_follow", |
| 1357 | udx_follow_conv_filter, udx_follow_index_filter, |
| 1358 | udp_follow_address_filter, udp_port_to_display, |
| 1359 | udx_follow_tap_listener, udx_get_stream_count, NULL((void*)0)); |
| 1360 | |
| 1361 | udx_module = prefs_register_protocol(proto_udx, NULL((void*)0)); |
| 1362 | prefs_register_bool_preference(udx_module, "analyze_sequence_numbers", |
| 1363 | "Analyze UDX sequence numbers", |
| 1364 | "Track sequence and acknowledgement numbers to pair flows, measure " |
| 1365 | "round-trip times and flag retransmissions", |
| 1366 | &udx_analyze_sequence_numbers); |
| 1367 | } |
| 1368 | |
| 1369 | void |
| 1370 | proto_reg_handoff_udx(void) |
| 1371 | { |
| 1372 | heur_dissector_add("udp", dissect_udx_heur, "UDX over UDP", "udx_udp", |
| 1373 | proto_udx, HEURISTIC_DISABLE); |
| 1374 | dissector_add_for_decode_as_with_preference("udp.port", udx_handle); |
| 1375 | } |
| 1376 | |
| 1377 | /* |
| 1378 | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
| 1379 | * |
| 1380 | * Local variables: |
| 1381 | * c-basic-offset: 4 |
| 1382 | * tab-width: 8 |
| 1383 | * indent-tabs-mode: nil |
| 1384 | * End: |
| 1385 | * |
| 1386 | * vi: set shiftwidth=4 tabstop=8 expandtab: |
| 1387 | * :indentSize=4:tabSize=8:noTabs=true: |
| 1388 | */ |