Qt: introduce sampling options for the RTT graph

This commit is contained in:
Eugène Adell 2025-01-07 06:37:50 +00:00 committed by AndersBroman
parent f215bd6a8c
commit ec09e1bbc7
6 changed files with 229 additions and 32 deletions

View File

@ -853,7 +853,18 @@ Throughput:: Average throughput and goodput.
Round Trip Time:: Round trip time vs time or sequence number. RTT is
based on the acknowledgment timestamp corresponding to a particular
segment.
segment. The sampling method selects which segments are taken into
account and how the RTT is computed:
* `All Data Packets`, all segments carrying data are computed, and when
present, SACK is ignored.
* `All Data Packets w/ SACK`, all segments carrying data are computed,
the RTT value is based on SACK if present.
* `Data Packets matching RTT`, only segments with a corresponding RTT
value in the packet list are computed.
* `Data Packets matching Karn RTT`, only segments with a corresponding RTT
value in the packet list are computed, ambiguous ACKs following Karn's
definition are excluded.
Window Scaling:: Window size and outstanding bytes.

View File

@ -158,6 +158,14 @@ TCPStreamDialog::TCPStreamDialog(QWidget *parent, capture_file *cf, tcp_graph_ty
if (graph_type == GRAPH_WSCALE) graph_idx = gtcb->count() - 1;
gtcb->setUpdatesEnabled(true);
QComboBox *smcb = ui->samplingMethodComboBox;
smcb->setUpdatesEnabled(false);
smcb->addItem(ui->actionSamplingAllPackets->text(), SAMPLING_ALL);
smcb->addItem(ui->actionSamplingAllPacketsSACK->text(), SAMPLING_ALL_SACK);
smcb->addItem(ui->actionSamplingRTT->text(), SAMPLING_RTT);
smcb->addItem(ui->actionSamplingKarn->text(), SAMPLING_KARN);
smcb->setUpdatesEnabled(true);
ui->dragRadioButton->setChecked(mouse_drags_);
ctx_menu_.addAction(ui->actionZoomIn);
@ -709,8 +717,12 @@ void TCPStreamDialog::showWidgetsForGraphType()
{
if (graph_.type == GRAPH_RTT) {
ui->bySeqNumberCheckBox->setVisible(true);
ui->samplingMethodComboBox->setVisible(true);
ui->samplingLabel->setVisible(true);
} else {
ui->bySeqNumberCheckBox->setVisible(false);
ui->samplingMethodComboBox->setVisible(false);
ui->samplingLabel->setVisible(false);
}
if (graph_.type == GRAPH_THROUGHPUT) {
#ifdef MA_1_SECOND
@ -1448,7 +1460,7 @@ rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber,
} else {
x_vals.append(cur->time);
}
rtt.append((rt_val - cur->time) * 1000.0);
rtt.append(rt_val - cur->time);
// in this case, we will delete current unack
// [ update "begin" if necessary - we will return it to the
// caller to let them know we deleted it ]
@ -1466,7 +1478,7 @@ rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber,
} else {
x_vals.append(cur->time);
}
rtt.append((rt_val - cur->time) * 1000.0);
rtt.append(rt_val - cur->time);
// in this case, "right" marks the start of remaining bytes
cur->seqno = right;
continue;
@ -1480,7 +1492,7 @@ rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber,
} else {
x_vals.append(cur->time);
}
rtt.append((rt_val - cur->time) * 1000.0);
rtt.append(rt_val - cur->time);
// in this case, "left" is just beyond the remaining bytes
cur->end_seqno = left;
continue;
@ -1496,7 +1508,7 @@ rtt_selectively_ack_range(QVector<double>& x_vals, bool bySeqNumber,
} else {
x_vals.append(cur->time);
}
rtt.append((rt_val - cur->time) * 1000.0);
rtt.append(rt_val - cur->time);
// then split cur into two unacked segments
// (linking the right-hand unack after the left)
cur->next = rtt_get_new_unack(cur->time, right, cur->end_seqno - right);
@ -1551,6 +1563,7 @@ void TCPStreamDialog::fillRoundTripTime()
}
}
for (struct segment *seg = graph_.segments; seg != NULL; seg = seg->next) {
/* sender traffic analysis */
if (compareHeaders(seg)) {
uint32_t seqno = seg->th_seq - seq_base;
if (seg->th_seglen && !rtt_is_retrans(unack_list, seqno)) {
@ -1564,48 +1577,90 @@ void TCPStreamDialog::fillRoundTripTime()
}
rtt_put_unack_on_list(&unack_list, u);
}
} else {
/* else: ignore redundant sequences (Keep-Alives, Spurious,..) */
}
/* receiver traffic analysis */
else {
uint32_t ack_no = seg->th_ack - seq_base;
double rt_val = seg->rel_secs + seg->rel_usecs / 1000000.0;
rt_val -= ts_offset_;
struct rtt_unack *v;
for (u = unack_list; u; u = v) {
// full or partial ack of seg by ack_no
if (tcp_seq_after(ack_no, u->seqno)) {
// full or partial ack of seg by ack_no
if (bySeqNumber) {
x_vals.append(u->seqno);
sequence_num_map_.insert(u->seqno, seg);
} else {
x_vals.append(u->time);
}
rtt.append(rt_val - u->time);
if (tcp_seq_eq_or_after(ack_no, u->end_seqno)) {
// fully acked segment - nothing more to see here
// fully acked segment, but we're also acking a newer one on the next round
if (tcp_seq_after(ack_no, u->end_seqno)) {
/* breach RFC, take more RTT samples */
if( graph_.rtt_sampling & RTT_ALL) {
if (bySeqNumber) {
x_vals.append(u->seqno);
sequence_num_map_.insert(u->seqno, seg);
} else {
x_vals.append(u->time);
}
rtt.append(rt_val - u->time);
}
v = u->next;
rtt_delete_unack_from_list(&unack_list, u);
// no need to compare SACK blocks for fully ACKed seg
continue;
} else {
// partial ack of GSO seg
u->seqno = ack_no;
// (keep going - still need to compare SACK blocks...)
}
// fully acked segment, currently the one being acked now
else if (tcp_seq_eq(ack_no, u->end_seqno)) {
if(!(graph_.rtt_sampling & RTT_KRN && (seg->ack_karn)) ) {
if (bySeqNumber) {
x_vals.append(u->seqno);
sequence_num_map_.insert(u->seqno, seg);
} else {
x_vals.append(u->time);
}
rtt.append(rt_val - u->time);
}
/* else: ignore Karn ambiguous ACKs */
v = u->next;
rtt_delete_unack_from_list(&unack_list, u);
// no need to compare SACK blocks for fully ACKed seg
continue;
}
// partial ack of seg by ack_no
else {
if(!(graph_.rtt_sampling & RTT_KRN && (seg->ack_karn)) ) {
if (bySeqNumber) {
x_vals.append(u->seqno);
sequence_num_map_.insert(u->seqno, seg);
} else {
x_vals.append(u->time);
}
rtt.append(rt_val - u->time);
// partial ack of GSO seg
u->seqno = ack_no;
// (keep going - still need to compare SACK blocks...)
}
/* else: ignore Karn ambiguous ACKs */
}
}
v = u->next;
// selectively acking u more than once
// can shatter it into multiple intervals.
// If we link those back into the list between u and v,
// then each subsequent SACK selectively ACKs that range.
for (int i = 0; i < seg->num_sack_ranges; ++i) {
uint32_t left = seg->sack_left_edge[i] - seq_base;
uint32_t right = seg->sack_right_edge[i] - seq_base;
u = rtt_selectively_ack_range(x_vals, bySeqNumber, rtt,
&unack_list, u, v,
left, right, rt_val);
// if range is empty after selective ack, we can
// skip the rest of the SACK blocks
if (u == v) break;
if( graph_.rtt_sampling & RTT_SAK ) {
for (int i = 0; i < seg->num_sack_ranges; ++i) {
uint32_t left = seg->sack_left_edge[i] - seq_base;
uint32_t right = seg->sack_right_edge[i] - seq_base;
u = rtt_selectively_ack_range(x_vals, bySeqNumber, rtt,
&unack_list, u, v,
left, right, rt_val);
// if range is empty after selective ack, we can
// skip the rest of the SACK blocks
if (u == v) break;
}
}
}
}
@ -2092,6 +2147,33 @@ void TCPStreamDialog::on_zoomRadioButton_toggled(bool checked)
}
}
void TCPStreamDialog::on_samplingMethodComboBox_currentIndexChanged(int index)
{
if (index < 0) return;
// reset flags
graph_.rtt_sampling = 0;
switch (index) {
case 0:
default:
graph_.rtt_sampling |= RTT_ALL;
break;
case 1:
graph_.rtt_sampling |= RTT_ALL;
graph_.rtt_sampling |= RTT_SAK;
break;
case 2:
graph_.rtt_sampling |= RTT_RTT;
break;
case 3:
graph_.rtt_sampling |= RTT_KRN;
break;
}
fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
}
void TCPStreamDialog::on_bySeqNumberCheckBox_stateChanged(int /* state */)
{
fillGraph(/*reset_axes=*/true, /*set_focus=*/false);
@ -2348,3 +2430,4 @@ void TCPStreamDialog::on_buttonBox_helpRequested()
{
mainApp->helpTopicAction(HELP_STATS_TCP_STREAM_GRAPHS_DIALOG);
}

View File

@ -157,6 +157,7 @@ private slots:
void on_dragRadioButton_toggled(bool checked);
void on_zoomRadioButton_toggled(bool checked);
void on_bySeqNumberCheckBox_stateChanged(int state);
void on_samplingMethodComboBox_currentIndexChanged(int index);
void on_showSegLengthCheckBox_stateChanged(int state);
void on_showThroughputCheckBox_stateChanged(int state);
void on_showGoodputCheckBox_stateChanged(int state);
@ -193,3 +194,4 @@ private slots:
};
#endif // TCP_STREAM_DIALOG_H

View File

@ -229,6 +229,26 @@
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="samplingLabel">
<property name="text">
<string>Sampling Method</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="samplingMethodComboBox">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Select which packets and how the RTT sampling is done</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="bySeqNumberCheckBox">
<property name="focusPolicy">
@ -607,6 +627,50 @@
<string>4</string>
</property>
</action>
<action name="actionSamplingAllPackets">
<property name="text">
<string>All Data Packets</string>
</property>
<property name="toolTip">
<string>Sampling from all data packets</string>
</property>
<property name="shortcut">
<string>1</string>
</property>
</action>
<action name="actionSamplingAllPacketsSACK">
<property name="text">
<string>All Data Packets w/ SACK</string>
</property>
<property name="toolTip">
<string>Sampling from all data packets w/ SACK</string>
</property>
<property name="shortcut">
<string>2</string>
</property>
</action>
<action name="actionSamplingRTT">
<property name="text">
<string>Data Packets matching RTT</string>
</property>
<property name="toolTip">
<string>Sampling from RTT packets</string>
</property>
<property name="shortcut">
<string>3</string>
</property>
</action>
<action name="actionSamplingKarn">
<property name="text">
<string>Data Packets matching Karn RTT</string>
</property>
<property name="toolTip">
<string>Sampling from Karn RTT packets</string>
</property>
<property name="shortcut">
<string>4</string>
</property>
</action>
<action name="actionZoomInX">
<property name="text">
<string>Zoom In X Axis</string>

View File

@ -114,6 +114,8 @@ tapall_tcpip_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, cons
copy_address(&segment->ip_src, &tcphdr->ip_src);
copy_address(&segment->ip_dst, &tcphdr->ip_dst);
segment->ack_karn=tcphdr->flagkarn;
segment->num_sack_ranges = MIN(MAX_TCP_SACK_RANGES, tcphdr->num_sack_ranges);
if (segment->num_sack_ranges > 0) {
/* Copy entries in the order they happen */
@ -359,7 +361,7 @@ select_tcpip_session(capture_file *cf)
return th_stream;
}
int rtt_is_retrans(struct rtt_unack *list, unsigned int seqno)
bool rtt_is_retrans(struct rtt_unack *list, unsigned int seqno)
{
struct rtt_unack *u;

View File

@ -27,6 +27,19 @@ typedef enum tcp_graph_type_ {
GRAPH_UNDEFINED
} tcp_graph_type;
#define RTT_ALL 0x0001
#define RTT_SAK 0x0002
#define RTT_RTT 0x0004
#define RTT_KRN 0x0008
typedef enum rtt_sampling_method_ {
SAMPLING_ALL,
SAMPLING_ALL_SACK,
SAMPLING_RTT,
SAMPLING_KARN,
SAMPLING_UNDEFINED
} rtt_sampling_method;
struct segment {
struct segment *next;
uint32_t num;
@ -49,6 +62,8 @@ struct segment {
address ip_src;
address ip_dst;
bool ack_karn; /* true when ambiguous according to Karn's algo */
uint8_t num_sack_ranges;
uint32_t sack_left_edge[MAX_TCP_SACK_RANGES];
uint32_t sack_right_edge[MAX_TCP_SACK_RANGES];
@ -57,6 +72,9 @@ struct segment {
struct tcp_graph {
tcp_graph_type type;
/* RTT sampling method (for RTT graphs only) */
uint8_t rtt_sampling;
/* The stream this graph will show */
address src_address;
uint16_t src_port;
@ -98,12 +116,28 @@ struct rtt_unack {
unsigned int end_seqno;
};
int rtt_is_retrans(struct rtt_unack * , unsigned int );
/**
* Check if a sequence number is currently in the Unacked list,
* typically for avoiding adding redundant sequences.
* In practice, the retrans meaning in this particular code is different
* from TCP's one and would rather cover Keep-Alives and Spurious Retrans.
*
* @param list The list containing the Unacked sequences
* @param seqno The sequence number to be searched for in the Unacked list
* @return true if the list contains the sequence number, false otherwise
*/
bool rtt_is_retrans(struct rtt_unack *list, unsigned int seqno);
struct rtt_unack *rtt_get_new_unack(double , unsigned int , unsigned int );
void rtt_put_unack_on_list(struct rtt_unack ** , struct rtt_unack * );
void rtt_delete_unack_from_list(struct rtt_unack ** , struct rtt_unack * );
void rtt_destroy_unack_list(struct rtt_unack ** );
static inline int
tcp_seq_eq(uint32_t s1, uint32_t s2) {
return (int32_t)(s1 - s2) == 0;
}
static inline int
tcp_seq_before(uint32_t s1, uint32_t s2) {
return (int32_t)(s1 - s2) < 0;
@ -119,7 +153,8 @@ tcp_seq_after(uint32_t s1, uint32_t s2) {
return (int32_t)(s1 - s2) > 0;
}
static inline int tcp_seq_before_or_eq(uint32_t s1, uint32_t s2) {
static inline int
tcp_seq_before_or_eq(uint32_t s1, uint32_t s2) {
return !tcp_seq_after(s1, s2);
}