diff --git a/net/quic/Makefile b/net/quic/Makefile
index 1565fb5cef9d..4d4a42c6d565 100644
--- a/net/quic/Makefile
+++ b/net/quic/Makefile
@@ -5,4 +5,5 @@
obj-$(CONFIG_IP_QUIC) += quic.o
-quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
+ cong.o
diff --git a/net/quic/cong.c b/net/quic/cong.c
new file mode 100644
index 000000000000..b829755e8ac9
--- /dev/null
+++ b/net/quic/cong.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* QUIC kernel implementation
+ * (C) Copyright Red Hat Corp. 2023
+ *
+ * This file is part of the QUIC kernel implementation
+ *
+ * Congestion control algorithms.
+ *
+ * Written or modified by:
+ * Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <linux/quic.h>
+
+#include "common.h"
+#include "cong.h"
+
+/* NEW RENO APIs */
+static void reno_recovery(struct quic_cong *cong)
+{
+ cong->recovery_time = cong->time;
+ cong->ssthresh = max(cong->window >> 1U, cong->min_window);
+ cong->window = cong->ssthresh;
+}
+
+static void quic_reno_on_packet_lost(struct quic_cong *cong, u64 time,
+ u32 bytes, s64 number)
+{
+ reno_recovery(cong);
+}
+
+static void quic_reno_on_packet_acked(struct quic_cong *cong, u64 time,
+ u32 bytes, s64 number)
+{
+ u64 new_window;
+
+ if (quic_cong_in_slow_start(cong)) { /* Slow start */
+ new_window = (u64)cong->window + bytes;
+ cong->window = min_t(u64, new_window, cong->max_window);
+ return;
+ }
+ /* Congestion avoidance - cong->window is never zero; it is initialized
+ * by quic_packet_route() during connect/accept.
+ */
+ new_window = div64_ul((u64)cong->mss * bytes, cong->window) +
+ cong->window;
+ cong->window = min_t(u64, new_window, cong->max_window);
+}
+
+static void quic_reno_on_process_ecn(struct quic_cong *cong)
+{
+ reno_recovery(cong);
+}
+
+static void quic_reno_on_init(struct quic_cong *cong)
+{
+}
+
+static const struct quic_cong_ops quic_congs[] = {
+ { /* QUIC_CONG_ALG_RENO */
+ .on_packet_acked = quic_reno_on_packet_acked,
+ .on_packet_lost = quic_reno_on_packet_lost,
+ .on_process_ecn = quic_reno_on_process_ecn,
+ .on_init = quic_reno_on_init,
+ },
+};
+
+static bool quic_cong_check_persistent_congestion(struct quic_cong *cong,
+ u64 time)
+{
+ u64 ssthresh;
+
+ time -= cong->pc_start_time;
+
+ /* rfc9002#section-7.6.1:
+ * (smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay) *
+ * kPersistentCongestionThreshold
+ */
+ ssthresh = cong->smoothed_rtt +
+ max(4 * cong->rttvar, QUIC_KGRANULARITY);
+ ssthresh = (ssthresh + cong->max_ack_delay) *
+ QUIC_KPERSISTENT_CONGESTION_THRESHOLD;
+
+ return time > ssthresh;
+}
+
+/* COMMON APIs */
+void quic_cong_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number)
+{
+ if (cong->pc_detected)
+ return;
+
+ if (cong->pc_start_time && time > cong->pc_start_time &&
+ quic_cong_check_persistent_congestion(cong, time)) {
+ cong->pc_detected = 1;
+ cong->min_rtt_valid = 0;
+ cong->window = cong->min_window;
+ cong->recovery_time = 0;
+ return;
+ }
+
+ if (!cong->pc_start_time && cong->first_rtt_time &&
+ time > cong->first_rtt_time)
+ cong->pc_start_time = time;
+
+ if (quic_cong_in_recovery(cong, time))
+ return;
+ cong->ops->on_packet_lost(cong, time, bytes, number);
+}
+
+void quic_cong_on_packet_acked(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number)
+{
+ if (quic_cong_in_recovery(cong, time))
+ return;
+ cong->ops->on_packet_acked(cong, time, bytes, number);
+}
+
+void quic_cong_on_process_ecn(struct quic_cong *cong, u64 time)
+{
+ if (quic_cong_in_recovery(cong, time))
+ return;
+ cong->ops->on_process_ecn(cong);
+}
+
+/* Update Probe Timeout (PTO) and loss detection delay based on RTT stats. */
+static void quic_cong_pto_update(struct quic_cong *cong)
+{
+ u32 loss_delay;
+
+ /* rfc9002#section-6.2.1:
+ * PTO = smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay
+ *
+ * Calculate the base PTO here, excluding max_ack_delay. max_ack_delay
+ * is added when calculating the PTO for the App packet number space.
+ */
+ cong->pto = cong->smoothed_rtt +
+ max(4 * cong->rttvar, QUIC_KGRANULARITY);
+
+ /* rfc9002#section-6.1.2:
+ * max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity)
+ */
+ loss_delay = QUIC_KTIME_THRESHOLD(max(cong->smoothed_rtt,
+ cong->latest_rtt));
+ cong->loss_delay = max(loss_delay, QUIC_KGRANULARITY);
+
+ pr_debug("%s: update pto: %u\n", __func__, cong->pto);
+}
+
+/* Update pacing timestamp after sending 'bytes' bytes.
+ *
+ * This function tracks when the next packet is allowed to be sent based on
+ * pacing rate.
+ */
+static void quic_cong_update_pacing_time(struct quic_cong *cong, u32 bytes)
+{
+ u64 prior_time, credit, len_ns, rate = cong->pacing_rate;
+
+ if (!rate)
+ return;
+
+ prior_time = cong->pacing_time;
+ cong->pacing_time = max(cong->pacing_time, ktime_get_ns());
+ credit = cong->pacing_time - prior_time;
+
+ /* take into account OS jitter */
+ len_ns = div64_u64((u64)bytes * NSEC_PER_SEC, rate);
+ len_ns -= min_t(u64, len_ns / 2, credit);
+ cong->pacing_time += len_ns;
+}
+
+void quic_cong_on_packet_sent(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number)
+{
+ if (!bytes)
+ return;
+ if (cong->ops->on_packet_sent)
+ cong->ops->on_packet_sent(cong, time, bytes, number);
+ quic_cong_update_pacing_time(cong, bytes);
+}
+
+/* Compute and update the pacing rate based on congestion window and smoothed
+ * RTT.
+ */
+void quic_cong_pace_update(struct quic_cong *cong, u64 max_rate)
+{
+ u64 rate;
+
+ if (unlikely(!cong->smoothed_rtt))
+ return;
+
+ /* rate = N * congestion_window / smoothed_rtt */
+ rate = div64_ul((u64)cong->window * USEC_PER_SEC * 2,
+ cong->smoothed_rtt);
+
+ cong->pacing_rate = min_t(u64, rate, max_rate);
+ pr_debug("%s: update pacing rate: %llu, max rate: %llu, srtt: %u\n",
+ __func__, cong->pacing_rate, max_rate, cong->smoothed_rtt);
+}
+
+/* rfc9002#section-5: Estimating the Round-Trip Time */
+void quic_cong_rtt_update(struct quic_cong *cong, u64 time, u32 ack_delay)
+{
+ u32 adjusted_rtt, rttvar_sample;
+ u64 raw_rtt;
+
+ /* Ignore RTT sample if ACK delay is suspiciously large. */
+ if (ack_delay > cong->max_ack_delay * 2)
+ return;
+
+ raw_rtt = cong->time - time;
+ if (raw_rtt > ack_delay + QUIC_RTT_MAX)
+ return;
+
+ /* rfc9002#section-5.1:
+ * latest_rtt = ack_time - send_time_of_largest_acked
+ */
+ cong->latest_rtt = (u32)raw_rtt;
+
+ /* rfc9002#section-5.2: Estimating min_rtt */
+ if (!cong->min_rtt_valid) {
+ cong->min_rtt = cong->latest_rtt;
+ cong->min_rtt_valid = 1;
+ }
+ if (cong->min_rtt > cong->latest_rtt)
+ cong->min_rtt = cong->latest_rtt;
+
+ if (!cong->first_rtt_time) {
+ /* rfc9002#section-5.3:
+ * smoothed_rtt = latest_rtt
+ * rttvar = latest_rtt / 2
+ */
+ cong->smoothed_rtt = cong->latest_rtt;
+ cong->rttvar = cong->smoothed_rtt / 2;
+ cong->first_rtt_time = cong->time;
+ goto out;
+ }
+
+ /* rfc9002#section-5.3:
+ * adjusted_rtt = latest_rtt
+ * if (latest_rtt >= min_rtt + ack_delay):
+ * adjusted_rtt = latest_rtt - ack_delay
+ * smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt
+ * rttvar_sample = abs(smoothed_rtt - adjusted_rtt)
+ * rttvar = 3/4 * rttvar + 1/4 * rttvar_sample
+ */
+ adjusted_rtt = cong->latest_rtt;
+ if (cong->latest_rtt >= cong->min_rtt + ack_delay)
+ adjusted_rtt = cong->latest_rtt - ack_delay;
+
+ cong->smoothed_rtt = (cong->smoothed_rtt * 7 + adjusted_rtt) / 8;
+ rttvar_sample = abs_diff(cong->smoothed_rtt, adjusted_rtt);
+ cong->rttvar = (cong->rttvar * 3 + rttvar_sample) / 4;
+out:
+ quic_cong_pto_update(cong);
+
+ if (cong->ops->on_rtt_update)
+ cong->ops->on_rtt_update(cong);
+}
+
+void quic_cong_set_algo(struct quic_cong *cong, u8 algo)
+{
+ /* The caller must ensure algo < QUIC_CONG_ALG_MAX. */
+ if (WARN_ON_ONCE(algo >= QUIC_CONG_ALG_MAX))
+ return;
+ cong->algo = algo;
+ cong->ssthresh = U32_MAX;
+ cong->ops = &quic_congs[algo];
+ cong->ops->on_init(cong);
+}
+
+void quic_cong_set_srtt(struct quic_cong *cong, u32 srtt)
+{
+ /* rfc9002#section-5.3:
+ * smoothed_rtt = kInitialRtt
+ * rttvar = kInitialRtt / 2
+ */
+ cong->initial_srtt = srtt;
+ cong->latest_rtt = srtt;
+ cong->smoothed_rtt = cong->latest_rtt;
+ cong->rttvar = cong->smoothed_rtt / 2;
+ quic_cong_pto_update(cong);
+}
+
+void quic_cong_init(struct quic_cong *cong)
+{
+ cong->max_ack_delay = QUIC_DEF_ACK_DELAY;
+ cong->max_window = S32_MAX / 4;
+ quic_cong_set_algo(cong, QUIC_CONG_ALG_RENO);
+ quic_cong_set_srtt(cong, QUIC_RTT_INIT);
+}diff --git a/net/quic/cong.h b/net/quic/cong.h
new file mode 100644
index 000000000000..eaa7b59c0769
--- /dev/null
+++ b/net/quic/cong.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* QUIC kernel implementation
+ * (C) Copyright Red Hat Corp. 2023
+ *
+ * This file is part of the QUIC kernel implementation
+ *
+ * Written or modified by:
+ * Xin Long <lucien.xin@gmail.com>
+ */
+
+#define QUIC_KPERSISTENT_CONGESTION_THRESHOLD 3
+#define QUIC_KPACKET_THRESHOLD 3
+#define QUIC_KTIME_THRESHOLD(rtt) ((rtt) * 9 / 8)
+#define QUIC_KGRANULARITY 1000U
+
+#define QUIC_RTT_INIT 333000U
+#define QUIC_RTT_MAX 6000000U
+#define QUIC_RTT_MIN QUIC_KGRANULARITY
+
+/* rfc9002#section-7.3: Congestion Control States
+ *
+ * The implementation derives congestion states from conditions rather than
+ * maintaining an explicit state variable:
+ *
+ * - Slow Start: window < ssthresh
+ * - Recovery Period: time <= recovery_time
+ * - Congestion Avoidance: window >= ssthresh && time > recovery_time
+ *
+ * New path or +------------+
+ * persistent congestion | Slow |
+ * (O)---------------------->| Start |
+ * +------------+
+ * |
+ * Loss or |
+ * ECN-CE increase |
+ * v
+ * +------------+ Loss or +------------+
+ * | Congestion | ECN-CE increase | Recovery |
+ * | Avoidance |------------------>| Period |
+ * +------------+ +------------+
+ * ^ |
+ * | |
+ * +----------------------------+
+ * Acknowledgment of packet
+ * sent during recovery
+ */
+
+struct quic_cong {
+ /* RTT tracking */
+ u32 max_ack_delay; /* max_ack_delay from rfc9000#section-18.2 */
+ u32 smoothed_rtt; /* Smoothed RTT */
+ u32 latest_rtt; /* Latest RTT sample */
+ u32 min_rtt; /* Lowest observed RTT */
+ u32 rttvar; /* RTT variation */
+ u32 pto; /* Probe timeout */
+
+ /* Timing & pacing */
+ u64 first_rtt_time; /* First RTT sample timestamp */
+ u64 pc_start_time; /* Persistent congestion tracking timestamp */
+ u64 recovery_time; /* Recovery period start timestamp */
+ u64 pacing_rate; /* Packet sending speed Bytes/sec */
+ u64 pacing_time; /* Next scheduled send timestamp (ns) */
+ u64 time; /* Cached current timestamp */
+
+ /* Congestion window */
+ u32 max_window; /* Max growth cap */
+ u32 min_window; /* Min window limit */
+ u32 loss_delay; /* Time before marking loss */
+ u32 ssthresh; /* Slow start threshold */
+ u32 window; /* Bytes in flight allowed */
+ u32 mss; /* QUIC MSS (excl. UDP) */
+
+ /* Algorithm-specific */
+ const struct quic_cong_ops *ops;
+ u64 priv[8]; /* Algo private data */
+
+ u32 initial_srtt; /* Initial smoothed RTT */
+ u8 algo; /* Congestion control algorithm */
+
+ /* Flags & state */
+ u8 min_rtt_valid; /* min_rtt initialized */
+ u8 pc_detected; /* Persistent congestion detected */
+};
+
+/* Hooks for congestion control algorithms */
+struct quic_cong_ops {
+ void (*on_packet_acked)(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number);
+ void (*on_packet_lost)(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number);
+ void (*on_process_ecn)(struct quic_cong *cong);
+ void (*on_init)(struct quic_cong *cong);
+
+ /* Optional callbacks */
+ void (*on_packet_sent)(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number);
+ void (*on_rtt_update)(struct quic_cong *cong);
+};
+
+static inline void quic_cong_set_mss(struct quic_cong *cong, u32 mss)
+{
+ if (cong->mss == mss)
+ return;
+
+ /* rfc9002#section-7.2: Initial and Minimum Congestion Window */
+ cong->mss = mss;
+ cong->min_window = mss * 2;
+
+ if (!cong->window)
+ cong->window = min(mss * 10, max(14720U, mss * 2));
+ if (cong->window < cong->min_window)
+ cong->window = cong->min_window;
+}
+
+static inline void *quic_cong_priv(struct quic_cong *cong)
+{
+ return (void *)cong->priv;
+}
+
+static inline bool quic_cong_in_slow_start(struct quic_cong *cong)
+{
+ return cong->window < cong->ssthresh;
+}
+
+static inline bool quic_cong_in_recovery(struct quic_cong *cong, u64 time)
+{
+ return time <= cong->recovery_time;
+}
+
+void quic_cong_on_packet_acked(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number);
+void quic_cong_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number);
+void quic_cong_on_process_ecn(struct quic_cong *cong, u64 time);
+
+void quic_cong_on_packet_sent(struct quic_cong *cong, u64 time, u32 bytes,
+ s64 number);
+void quic_cong_rtt_update(struct quic_cong *cong, u64 time, u32 ack_delay);
+void quic_cong_pace_update(struct quic_cong *cong, u64 max_rate);
+
+void quic_cong_set_srtt(struct quic_cong *cong, u32 srtt);
+void quic_cong_set_algo(struct quic_cong *cong, u8 algo);
+void quic_cong_init(struct quic_cong *cong);diff --git a/net/quic/socket.c b/net/quic/socket.c
index 5ad2b08d632c..41f7de7d163a 100644
--- a/net/quic/socket.c
+++ b/net/quic/socket.c
@@ -53,6 +53,7 @@ static int quic_init_sock(struct sock *sk)
quic_conn_id_set_init(quic_source(sk), true);
quic_conn_id_set_init(quic_dest(sk), false);
quic_path_init(quic_paths(sk));
+ quic_cong_init(quic_cong(sk));
if (quic_stream_init(quic_streams(sk)))
return -ENOMEM;
diff --git a/net/quic/socket.h b/net/quic/socket.h
index 91338601905e..9201ca3edad0 100644
--- a/net/quic/socket.h
+++ b/net/quic/socket.h
@@ -16,6 +16,7 @@
#include "stream.h"
#include "connid.h"
#include "path.h"
+#include "cong.h"
#include "protocol.h"
@@ -41,6 +42,7 @@ struct quic_sock {
struct quic_conn_id_set source;
struct quic_conn_id_set dest;
struct quic_path_group paths;
+ struct quic_cong cong;
};
struct quic6_sock {@@ -98,6 +100,11 @@ static inline bool quic_is_serv(const struct sock *sk)
return !!sk->sk_max_ack_backlog;
}
+static inline struct quic_cong *quic_cong(const struct sock *sk)
+{
+ return &quic_sk(sk)->cong;
+}
+
static inline bool quic_is_establishing(struct sock *sk)
{
return sk->sk_state == QUIC_SS_ESTABLISHING;--
2.47.1