2 * TCP stream statistics
3 * Originally from tcp_graph.c by Pavel Mores <pvl@uh.cz>
4 * Win32 port: rwh@unifiedtech.com
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
20 #include <epan/epan_dissect.h>
21 #include <epan/packet.h>
24 #include <epan/dissectors/packet-tcp.h>
26 #include "ui/simple_dialog.h"
28 #include "tap-tcp-stream.h"
30 typedef struct _tcp_scan_t
{
37 static tap_packet_status
38 tapall_tcpip_packet(void *pct
, packet_info
*pinfo
, epan_dissect_t
*edt _U_
, const void *vip
, tap_flags_t flags _U_
)
40 tcp_scan_t
*ts
= (tcp_scan_t
*)pct
;
41 struct tcp_graph
*tg
= ts
->tg
;
42 const struct tcpheader
*tcphdr
= (const struct tcpheader
*)vip
;
44 if (tg
->stream
== tcphdr
->th_stream
45 && (tg
->src_address
.type
== AT_NONE
|| tg
->dst_address
.type
== AT_NONE
)) {
47 * We only know the stream number. Fill in our connection data.
48 * We assume that the server response is more interesting.
51 if (tcphdr
->th_flags
& TH_SYN
) {
52 if (tcphdr
->th_flags
& TH_ACK
) {
53 /* SYN-ACK packet, so the server is the source. */
56 /* SYN packet, so the server is the destination. */
57 server_is_src
= false;
60 /* Fallback to assuming the lower numbered port is the server. */
61 server_is_src
= tcphdr
->th_sport
< tcphdr
->th_dport
;
64 copy_address(&tg
->src_address
, &tcphdr
->ip_src
);
65 tg
->src_port
= tcphdr
->th_sport
;
66 copy_address(&tg
->dst_address
, &tcphdr
->ip_dst
);
67 tg
->dst_port
= tcphdr
->th_dport
;
69 copy_address(&tg
->src_address
, &tcphdr
->ip_dst
);
70 tg
->src_port
= tcphdr
->th_dport
;
71 copy_address(&tg
->dst_address
, &tcphdr
->ip_src
);
72 tg
->dst_port
= tcphdr
->th_sport
;
76 if (compare_headers(&tg
->src_address
, &tg
->dst_address
,
77 tg
->src_port
, tg
->dst_port
,
78 &tcphdr
->ip_src
, &tcphdr
->ip_dst
,
79 tcphdr
->th_sport
, tcphdr
->th_dport
,
81 && tg
->stream
== tcphdr
->th_stream
)
83 struct segment
*segment
= g_new(struct segment
, 1);
85 segment
->num
= pinfo
->num
;
86 segment
->rel_secs
= (uint32_t)pinfo
->rel_ts
.secs
;
87 segment
->rel_usecs
= pinfo
->rel_ts
.nsecs
/1000;
89 segment->abs_secs = pinfo->abs_ts.secs;
90 segment->abs_usecs = pinfo->abs_ts.nsecs/1000;
92 /* tcphdr->th_rawseq is always the absolute sequence number.
93 * tcphdr->th_seq is either the relative or absolute sequence number
94 * depending on the TCP dissector preferences.
95 * The sack entries are also either the relative or absolute sequence
96 * number depending on the TCP dissector preferences.
97 * The TCP stream graphs have their own action / button press to
98 * switch between relative and absolute sequence numbers on the fly;
99 * if the TCP dissector hasn't calculated the relative sequence numbers,
100 * the tap will do so. (XXX - The calculation is cheap enough that we
101 * could do it here and store the offsets at the graph level to save
102 * memory. The TCP dissector could include its calculated base seq in
103 * the tap information to ensure consistency.)
105 segment
->th_seq
= tcphdr
->th_seq
;
106 segment
->th_ack
= tcphdr
->th_ack
;
107 segment
->th_rawseq
= tcphdr
->th_rawseq
;
108 segment
->th_rawack
= tcphdr
->th_rawack
;
109 segment
->th_win
= tcphdr
->th_win
;
110 segment
->th_flags
= tcphdr
->th_flags
;
111 segment
->th_sport
= tcphdr
->th_sport
;
112 segment
->th_dport
= tcphdr
->th_dport
;
113 segment
->th_seglen
= tcphdr
->th_seglen
;
114 copy_address(&segment
->ip_src
, &tcphdr
->ip_src
);
115 copy_address(&segment
->ip_dst
, &tcphdr
->ip_dst
);
117 segment
->num_sack_ranges
= MIN(MAX_TCP_SACK_RANGES
, tcphdr
->num_sack_ranges
);
118 if (segment
->num_sack_ranges
> 0) {
119 /* Copy entries in the order they happen */
120 memcpy(&segment
->sack_left_edge
, &tcphdr
->sack_left_edge
, sizeof(segment
->sack_left_edge
));
121 memcpy(&segment
->sack_right_edge
, &tcphdr
->sack_right_edge
, sizeof(segment
->sack_right_edge
));
124 if (ts
->tg
->segments
) {
125 ts
->last
->next
= segment
;
127 ts
->tg
->segments
= segment
;
132 return TAP_PACKET_DONT_REDRAW
;
135 /* here we collect all the external data we will ever need */
137 graph_segment_list_get(capture_file
*cf
, struct tcp_graph
*tg
)
139 GString
*error_string
;
146 /* rescan all the packets and pick up all interesting tcp headers.
147 * we only filter for TCP here for speed and do the actual compare
148 * in the tap listener
150 ts
.direction
= COMPARE_ANY_DIR
;
153 error_string
= register_tap_listener("tcp", &ts
, "tcp", 0, NULL
, tapall_tcpip_packet
, NULL
, NULL
);
155 fprintf(stderr
, "wireshark: Couldn't register tcp_graph tap: %s\n",
157 g_string_free(error_string
, TRUE
);
158 exit(1); /* XXX: fix this */
160 cf_retap_packets(cf
);
161 remove_tap_listener(&ts
);
165 graph_segment_list_free(struct tcp_graph
*tg
)
167 struct segment
*segment
;
169 free_address(&tg
->src_address
);
170 free_address(&tg
->dst_address
);
172 while (tg
->segments
) {
173 segment
= tg
->segments
->next
;
174 free_address(&tg
->segments
->ip_src
);
175 free_address(&tg
->segments
->ip_dst
);
176 g_free(tg
->segments
);
177 tg
->segments
= segment
;
182 compare_headers(address
*saddr1
, address
*daddr1
, uint16_t sport1
, uint16_t dport1
, const address
*saddr2
, const address
*daddr2
, uint16_t sport2
, uint16_t dport2
, int dir
)
186 dir1
= ((!(cmp_address(saddr1
, saddr2
))) &&
187 (!(cmp_address(daddr1
, daddr2
))) &&
191 if (dir
== COMPARE_CURR_DIR
) {
194 dir2
= ((!(cmp_address(saddr1
, daddr2
))) &&
195 (!(cmp_address(daddr1
, saddr2
))) &&
196 (sport1
== dport2
) &&
203 get_num_dsegs(struct tcp_graph
*tg
)
208 for (tmp
=tg
->segments
, count
=0; tmp
; tmp
=tmp
->next
) {
209 if (compare_headers(&tg
->src_address
, &tg
->dst_address
,
210 tg
->src_port
, tg
->dst_port
,
211 &tmp
->ip_src
, &tmp
->ip_dst
,
212 tmp
->th_sport
, tmp
->th_dport
,
221 get_num_acks(struct tcp_graph
*tg
, int *num_sack_ranges
)
226 for (tmp
= tg
->segments
, count
=0; tmp
; tmp
= tmp
->next
) {
227 if (!compare_headers(&tg
->src_address
, &tg
->dst_address
,
228 tg
->src_port
, tg
->dst_port
,
229 &tmp
->ip_src
, &tmp
->ip_dst
,
230 tmp
->th_sport
, tmp
->th_dport
,
233 *num_sack_ranges
+= tmp
->num_sack_ranges
;
239 typedef struct _th_t
{
241 #define MAX_SUPPORTED_TCP_HEADERS 8
242 struct tcpheader
*tcphdrs
[MAX_SUPPORTED_TCP_HEADERS
];
245 static tap_packet_status
246 tap_tcpip_packet(void *pct
, packet_info
*pinfo _U_
, epan_dissect_t
*edt _U_
, const void *vip
, tap_flags_t flags _U_
)
249 bool is_unique
= true;
250 th_t
*th
= (th_t
*)pct
;
251 const struct tcpheader
*header
= (const struct tcpheader
*)vip
;
253 /* Check new header details against any/all stored ones */
254 for (n
=0; n
< th
->num_hdrs
; n
++) {
255 struct tcpheader
*stored
= th
->tcphdrs
[n
];
257 if (compare_headers(&stored
->ip_src
, &stored
->ip_dst
,
258 stored
->th_sport
, stored
->th_dport
,
259 &header
->ip_src
, &header
->ip_dst
,
260 header
->th_sport
, stored
->th_dport
,
267 /* Add address if unique and have space for it */
268 if (is_unique
&& (th
->num_hdrs
< MAX_SUPPORTED_TCP_HEADERS
)) {
269 /* Need to take a deep copy of the tap struct, it may not be valid
270 to read after this function returns? */
271 th
->tcphdrs
[th
->num_hdrs
] = g_new(struct tcpheader
, 1);
272 *(th
->tcphdrs
[th
->num_hdrs
]) = *header
;
273 copy_address(&th
->tcphdrs
[th
->num_hdrs
]->ip_src
, &header
->ip_src
);
274 copy_address(&th
->tcphdrs
[th
->num_hdrs
]->ip_dst
, &header
->ip_dst
);
279 return TAP_PACKET_DONT_REDRAW
;
282 /* XXX should be enhanced so that if we have multiple TCP layers in the trace
283 * then present the user with a dialog where the user can select WHICH tcp
287 select_tcpip_session(capture_file
*cf
)
294 GString
*error_string
;
295 th_t th
= {0, {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
}};
301 /* no real filter yet */
302 if (!dfilter_compile("tcp", &sfcode
, &df_err
)) {
303 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
, "%s", df_err
->msg
);
304 df_error_free(&df_err
);
308 /* dissect the current record */
309 if (!cf_read_current_record(cf
)) {
310 return UINT32_MAX
; /* error reading the record */
313 fdata
= cf
->current_frame
;
315 error_string
= register_tap_listener("tcp", &th
, NULL
, 0, NULL
, tap_tcpip_packet
, NULL
, NULL
);
317 fprintf(stderr
, "wireshark: Couldn't register tcp_graph tap: %s\n",
319 g_string_free(error_string
, TRUE
);
323 epan_dissect_init(&edt
, cf
->epan
, true, false);
324 epan_dissect_prime_with_dfilter(&edt
, sfcode
);
325 epan_dissect_run_with_taps(&edt
, cf
->cd_t
, &cf
->rec
,
326 ws_buffer_start_ptr(&cf
->buf
),
328 epan_dissect_cleanup(&edt
);
329 remove_tap_listener(&th
);
330 dfilter_free(sfcode
);
332 if (th
.num_hdrs
== 0) {
333 /* This "shouldn't happen", as our menu items shouldn't
334 * even be enabled if the selected packet isn't a TCP
335 * segment, as tcp_graph_selected_packet_enabled() is used
336 * to determine whether to enable any of our menu items. */
337 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
,
338 "Selected packet isn't a TCP segment or is truncated");
341 /* XXX fix this later, we should show a dialog allowing the user
342 to select which session he wants here
344 if (th
.num_hdrs
> 1) {
345 /* can only handle a single tcp layer yet */
346 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
,
347 "The selected packet has more than one TCP unique conversation "
352 /* For now, still always choose the first/only one */
353 th_stream
= th
.tcphdrs
[0]->th_stream
;
355 for (int n
= 0; n
< th
.num_hdrs
; n
++) {
356 free_address(&th
.tcphdrs
[n
]->ip_src
);
357 free_address(&th
.tcphdrs
[n
]->ip_dst
);
358 g_free(th
.tcphdrs
[n
]);
364 int rtt_is_retrans(struct rtt_unack
*list
, unsigned int seqno
)
368 for (u
=list
; u
; u
=u
->next
) {
369 if (tcp_seq_eq_or_after(seqno
, u
->seqno
) &&
370 tcp_seq_before(seqno
, u
->end_seqno
)) {
378 rtt_get_new_unack(double time_val
, unsigned int seqno
, unsigned int seglen
)
382 u
= g_new(struct rtt_unack
, 1);
386 u
->end_seqno
= seqno
+ seglen
;
390 void rtt_put_unack_on_list(struct rtt_unack
**l
, struct rtt_unack
*new_unack
)
392 struct rtt_unack
*u
, *list
= *l
;
394 for (u
=list
; u
; u
=u
->next
) {
406 void rtt_delete_unack_from_list(struct rtt_unack
**l
, struct rtt_unack
*dead
)
408 struct rtt_unack
*u
, *list
= *l
;
410 if (!dead
|| !list
) {
418 for (u
=list
; u
; u
=u
->next
) {
419 if (u
->next
== dead
) {
420 u
->next
= u
->next
->next
;
428 void rtt_destroy_unack_list(struct rtt_unack
**l
) {
430 struct rtt_unack
*head
= *l
;