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
19 #include <frame_tvbuff.h>
21 #include <epan/epan_dissect.h>
22 #include <epan/packet.h>
25 #include <epan/dissectors/packet-tcp.h>
27 #include "ui/simple_dialog.h"
29 #include "tap-tcp-stream.h"
31 typedef struct _tcp_scan_t
{
38 static tap_packet_status
39 tapall_tcpip_packet(void *pct
, packet_info
*pinfo
, epan_dissect_t
*edt _U_
, const void *vip
, tap_flags_t flags _U_
)
41 tcp_scan_t
*ts
= (tcp_scan_t
*)pct
;
42 struct tcp_graph
*tg
= ts
->tg
;
43 const struct tcpheader
*tcphdr
= (const struct tcpheader
*)vip
;
45 if (tg
->stream
== tcphdr
->th_stream
46 && (tg
->src_address
.type
== AT_NONE
|| tg
->dst_address
.type
== AT_NONE
)) {
48 * We only know the stream number. Fill in our connection data.
49 * We assume that the server response is more interesting.
52 if (tcphdr
->th_flags
& TH_SYN
) {
53 if (tcphdr
->th_flags
& TH_ACK
) {
54 /* SYN-ACK packet, so the server is the source. */
57 /* SYN packet, so the server is the destination. */
58 server_is_src
= false;
61 /* Fallback to assuming the lower numbered port is the server. */
62 server_is_src
= tcphdr
->th_sport
< tcphdr
->th_dport
;
65 copy_address(&tg
->src_address
, &tcphdr
->ip_src
);
66 tg
->src_port
= tcphdr
->th_sport
;
67 copy_address(&tg
->dst_address
, &tcphdr
->ip_dst
);
68 tg
->dst_port
= tcphdr
->th_dport
;
70 copy_address(&tg
->src_address
, &tcphdr
->ip_dst
);
71 tg
->src_port
= tcphdr
->th_dport
;
72 copy_address(&tg
->dst_address
, &tcphdr
->ip_src
);
73 tg
->dst_port
= tcphdr
->th_sport
;
77 if (compare_headers(&tg
->src_address
, &tg
->dst_address
,
78 tg
->src_port
, tg
->dst_port
,
79 &tcphdr
->ip_src
, &tcphdr
->ip_dst
,
80 tcphdr
->th_sport
, tcphdr
->th_dport
,
82 && tg
->stream
== tcphdr
->th_stream
)
84 struct segment
*segment
= g_new(struct segment
, 1);
86 segment
->num
= pinfo
->num
;
87 segment
->rel_secs
= (uint32_t)pinfo
->rel_ts
.secs
;
88 segment
->rel_usecs
= pinfo
->rel_ts
.nsecs
/1000;
90 segment->abs_secs = pinfo->abs_ts.secs;
91 segment->abs_usecs = pinfo->abs_ts.nsecs/1000;
93 /* tcphdr->th_rawseq is always the absolute sequence number.
94 * tcphdr->th_seq is either the relative or absolute sequence number
95 * depending on the TCP dissector preferences.
96 * The sack entries are also either the relative or absolute sequence
97 * number depending on the TCP dissector preferences.
98 * The TCP stream graphs have their own action / button press to
99 * switch between relative and absolute sequence numbers on the fly;
100 * if the TCP dissector hasn't calculated the relative sequence numbers,
101 * the tap will do so. (XXX - The calculation is cheap enough that we
102 * could do it here and store the offsets at the graph level to save
103 * memory. The TCP dissector could include its calculated base seq in
104 * the tap information to ensure consistency.)
106 segment
->th_seq
= tcphdr
->th_seq
;
107 segment
->th_ack
= tcphdr
->th_ack
;
108 segment
->th_rawseq
= tcphdr
->th_rawseq
;
109 segment
->th_rawack
= tcphdr
->th_rawack
;
110 segment
->th_win
= tcphdr
->th_win
;
111 segment
->th_flags
= tcphdr
->th_flags
;
112 segment
->th_sport
= tcphdr
->th_sport
;
113 segment
->th_dport
= tcphdr
->th_dport
;
114 segment
->th_seglen
= tcphdr
->th_seglen
;
115 copy_address(&segment
->ip_src
, &tcphdr
->ip_src
);
116 copy_address(&segment
->ip_dst
, &tcphdr
->ip_dst
);
118 segment
->num_sack_ranges
= MIN(MAX_TCP_SACK_RANGES
, tcphdr
->num_sack_ranges
);
119 if (segment
->num_sack_ranges
> 0) {
120 /* Copy entries in the order they happen */
121 memcpy(&segment
->sack_left_edge
, &tcphdr
->sack_left_edge
, sizeof(segment
->sack_left_edge
));
122 memcpy(&segment
->sack_right_edge
, &tcphdr
->sack_right_edge
, sizeof(segment
->sack_right_edge
));
125 if (ts
->tg
->segments
) {
126 ts
->last
->next
= segment
;
128 ts
->tg
->segments
= segment
;
133 return TAP_PACKET_DONT_REDRAW
;
136 /* here we collect all the external data we will ever need */
138 graph_segment_list_get(capture_file
*cf
, struct tcp_graph
*tg
)
140 GString
*error_string
;
147 /* rescan all the packets and pick up all interesting tcp headers.
148 * we only filter for TCP here for speed and do the actual compare
149 * in the tap listener
151 ts
.direction
= COMPARE_ANY_DIR
;
154 error_string
= register_tap_listener("tcp", &ts
, "tcp", 0, NULL
, tapall_tcpip_packet
, NULL
, NULL
);
156 fprintf(stderr
, "wireshark: Couldn't register tcp_graph tap: %s\n",
158 g_string_free(error_string
, TRUE
);
159 exit(1); /* XXX: fix this */
161 cf_retap_packets(cf
);
162 remove_tap_listener(&ts
);
166 graph_segment_list_free(struct tcp_graph
*tg
)
168 struct segment
*segment
;
170 free_address(&tg
->src_address
);
171 free_address(&tg
->dst_address
);
173 while (tg
->segments
) {
174 segment
= tg
->segments
->next
;
175 free_address(&tg
->segments
->ip_src
);
176 free_address(&tg
->segments
->ip_dst
);
177 g_free(tg
->segments
);
178 tg
->segments
= segment
;
183 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
)
187 dir1
= ((!(cmp_address(saddr1
, saddr2
))) &&
188 (!(cmp_address(daddr1
, daddr2
))) &&
192 if (dir
== COMPARE_CURR_DIR
) {
195 dir2
= ((!(cmp_address(saddr1
, daddr2
))) &&
196 (!(cmp_address(daddr1
, saddr2
))) &&
197 (sport1
== dport2
) &&
204 get_num_dsegs(struct tcp_graph
*tg
)
209 for (tmp
=tg
->segments
, count
=0; tmp
; tmp
=tmp
->next
) {
210 if (compare_headers(&tg
->src_address
, &tg
->dst_address
,
211 tg
->src_port
, tg
->dst_port
,
212 &tmp
->ip_src
, &tmp
->ip_dst
,
213 tmp
->th_sport
, tmp
->th_dport
,
222 get_num_acks(struct tcp_graph
*tg
, int *num_sack_ranges
)
227 for (tmp
= tg
->segments
, count
=0; tmp
; tmp
= tmp
->next
) {
228 if (!compare_headers(&tg
->src_address
, &tg
->dst_address
,
229 tg
->src_port
, tg
->dst_port
,
230 &tmp
->ip_src
, &tmp
->ip_dst
,
231 tmp
->th_sport
, tmp
->th_dport
,
234 *num_sack_ranges
+= tmp
->num_sack_ranges
;
240 typedef struct _th_t
{
242 #define MAX_SUPPORTED_TCP_HEADERS 8
243 struct tcpheader
*tcphdrs
[MAX_SUPPORTED_TCP_HEADERS
];
246 static tap_packet_status
247 tap_tcpip_packet(void *pct
, packet_info
*pinfo _U_
, epan_dissect_t
*edt _U_
, const void *vip
, tap_flags_t flags _U_
)
250 bool is_unique
= true;
251 th_t
*th
= (th_t
*)pct
;
252 const struct tcpheader
*header
= (const struct tcpheader
*)vip
;
254 /* Check new header details against any/all stored ones */
255 for (n
=0; n
< th
->num_hdrs
; n
++) {
256 struct tcpheader
*stored
= th
->tcphdrs
[n
];
258 if (compare_headers(&stored
->ip_src
, &stored
->ip_dst
,
259 stored
->th_sport
, stored
->th_dport
,
260 &header
->ip_src
, &header
->ip_dst
,
261 header
->th_sport
, stored
->th_dport
,
268 /* Add address if unique and have space for it */
269 if (is_unique
&& (th
->num_hdrs
< MAX_SUPPORTED_TCP_HEADERS
)) {
270 /* Need to take a deep copy of the tap struct, it may not be valid
271 to read after this function returns? */
272 th
->tcphdrs
[th
->num_hdrs
] = g_new(struct tcpheader
, 1);
273 *(th
->tcphdrs
[th
->num_hdrs
]) = *header
;
274 copy_address(&th
->tcphdrs
[th
->num_hdrs
]->ip_src
, &header
->ip_src
);
275 copy_address(&th
->tcphdrs
[th
->num_hdrs
]->ip_dst
, &header
->ip_dst
);
280 return TAP_PACKET_DONT_REDRAW
;
283 /* XXX should be enhanced so that if we have multiple TCP layers in the trace
284 * then present the user with a dialog where the user can select WHICH tcp
288 select_tcpip_session(capture_file
*cf
)
295 GString
*error_string
;
296 th_t th
= {0, {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
}};
302 /* no real filter yet */
303 if (!dfilter_compile("tcp", &sfcode
, &df_err
)) {
304 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
, "%s", df_err
->msg
);
305 df_error_free(&df_err
);
309 /* dissect the current record */
310 if (!cf_read_current_record(cf
)) {
311 return UINT32_MAX
; /* error reading the record */
314 fdata
= cf
->current_frame
;
316 error_string
= register_tap_listener("tcp", &th
, NULL
, 0, NULL
, tap_tcpip_packet
, NULL
, NULL
);
318 fprintf(stderr
, "wireshark: Couldn't register tcp_graph tap: %s\n",
320 g_string_free(error_string
, TRUE
);
324 epan_dissect_init(&edt
, cf
->epan
, true, false);
325 epan_dissect_prime_with_dfilter(&edt
, sfcode
);
326 epan_dissect_run_with_taps(&edt
, cf
->cd_t
, &cf
->rec
,
327 frame_tvbuff_new_buffer(&cf
->provider
, fdata
, &cf
->buf
),
329 epan_dissect_cleanup(&edt
);
330 remove_tap_listener(&th
);
331 dfilter_free(sfcode
);
333 if (th
.num_hdrs
== 0) {
334 /* This "shouldn't happen", as our menu items shouldn't
335 * even be enabled if the selected packet isn't a TCP
336 * segment, as tcp_graph_selected_packet_enabled() is used
337 * to determine whether to enable any of our menu items. */
338 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
,
339 "Selected packet isn't a TCP segment or is truncated");
342 /* XXX fix this later, we should show a dialog allowing the user
343 to select which session he wants here
345 if (th
.num_hdrs
> 1) {
346 /* can only handle a single tcp layer yet */
347 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
,
348 "The selected packet has more than one TCP unique conversation "
353 /* For now, still always choose the first/only one */
354 th_stream
= th
.tcphdrs
[0]->th_stream
;
356 for (int n
= 0; n
< th
.num_hdrs
; n
++) {
357 free_address(&th
.tcphdrs
[n
]->ip_src
);
358 free_address(&th
.tcphdrs
[n
]->ip_dst
);
359 g_free(th
.tcphdrs
[n
]);
365 int rtt_is_retrans(struct rtt_unack
*list
, unsigned int seqno
)
369 for (u
=list
; u
; u
=u
->next
) {
370 if (tcp_seq_eq_or_after(seqno
, u
->seqno
) &&
371 tcp_seq_before(seqno
, u
->end_seqno
)) {
379 rtt_get_new_unack(double time_val
, unsigned int seqno
, unsigned int seglen
)
383 u
= g_new(struct rtt_unack
, 1);
387 u
->end_seqno
= seqno
+ seglen
;
391 void rtt_put_unack_on_list(struct rtt_unack
**l
, struct rtt_unack
*new_unack
)
393 struct rtt_unack
*u
, *list
= *l
;
395 for (u
=list
; u
; u
=u
->next
) {
407 void rtt_delete_unack_from_list(struct rtt_unack
**l
, struct rtt_unack
*dead
)
409 struct rtt_unack
*u
, *list
= *l
;
411 if (!dead
|| !list
) {
419 for (u
=list
; u
; u
=u
->next
) {
420 if (u
->next
== dead
) {
421 u
->next
= u
->next
->next
;
429 void rtt_destroy_unack_list(struct rtt_unack
**l
) {
431 struct rtt_unack
*head
= *l
;