[PATCH mptcp-next v4] mptcp: honour configured min/max RTO in retransmit paths

Kalpan Jani <[email protected]>
Newsgroups dev.linux.lists.mptcp
Message-ID <[email protected]>
The MPTCP-level retransmit timers (DATA_FIN retransmissions and the
fallback timeout) used the hard-coded TCP_RTO_MIN / TCP_RTO_MAX
constants, ignoring the tcp_rto_min_us and tcp_rto_max_ms sysctls.

Make them follow the sysctls instead: seed icsk_rto_min / icsk_rto_max
on the MPTCP socket from the per-netns sysctls in __mptcp_init_sock()
-- the msk does not go through tcp_init_sock(), so these fields would
otherwise stay zero -- and read them directly where the constants were
used:

- mptcp_set_datafin_timeout(): both the backoff cap computation and
  the resulting timer_ival. The two sysctls are validated
  independently, so rto_min > rto_max is a valid configuration; keep
  a max_t() guard so ilog2() is never called with 0.

- __mptcp_set_timeout(): the fallback when no subflow timeout is
  available.

The icsk fields are read directly instead of using the
tcp_rto_min()/tcp_rto_max() helpers: the MPTCP socket does not perform
routing lookups in these paths, so the rto_min route metric checked by
tcp_rto_min() can never apply here. The TCP_RTO_MIN_US /
TCP_RTO_MAX_MS socket options are not supported by MPTCP setsockopt()
either; this can be revisited if they get supported on MPTCP sockets.

The remaining uses of TCP_RTO_MAX in net/mptcp/ctrl.c (default
add_addr_timeout) and net/mptcp/subflow.c (MP_FAIL timeout) are
intentionally left unchanged: they use the constant as a default
duration, not as an RTO bound on a retransmit timer.

Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/618
Reported-by: Li Xiasong <[email protected]>
Closes: https://lore.kernel.org/all/[email protected]/
Signed-off-by: Kalpan Jani <[email protected]>
---
Link to v1: https://lore.kernel.org/mptcp/[email protected]/
Link to v2: https://lore.kernel.org/mptcp/[email protected]/
Link to v3: https://lore.kernel.org/mptcp/[email protected]/

Changes since v3:
- read icsk_rto_min / icsk_rto_max directly instead of using the
  tcp_rto_min() / tcp_rto_max() helpers: the route metric checked by
  the helpers cannot apply on the msk (no routing lookup in these
  paths). This also removes the v2/v3 special case in
  __mptcp_init_sock() that avoided the dst lookup.
- drop the rto_min == 0 floor: without the route metric, rto_min
  comes from tcp_rto_min_us which has a floor of 1us, and
  usecs_to_jiffies() rounds up.
- rewrite the commit message: only the sysctls are honoured; the
  route metric and the TCP_RTO_MIN_US / TCP_RTO_MAX_MS socket
  options are explicitly out of scope.
- a Packetdrill patch extending mptcp/dss/dss_fin_retrans_*
  will be sent separately to validate mptcp_set_datafin_timeout().

Changes since v2:
- mptcp_set_datafin_timeout(): guard the division against a zero
  rto_min. Thanks to Li Xiasong for spotting it.
- __mptcp_init_sock(): order the local declarations longest-first
  (reverse christmas tree).

Changes since v1:
- __mptcp_init_sock(): seed icsk_rto_min / icsk_rto_max from the
  per-netns sysctls so the fields hold meaningful values on the msk
  (MPTCP does not call tcp_init_sock() on the msk).
- __mptcp_init_sock(): use icsk->icsk_rto_min directly for the initial
  timer_ival instead of tcp_rto_min(sk), to avoid a
  lockdep_rcu_suspicious() splat from __sk_dst_get() at socket init
  time. Reported by the mptcp CI on v1.
- mptcp_set_datafin_timeout(): add an ilog2(0) shift-safety guard for
  the rto_min >= rto_max corner case.

 net/mptcp/protocol.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 373406cff72e..aab78daa8bdf 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -570,17 +570,23 @@ static bool mptcp_pending_data_fin(struct sock *sk, u64 *seq)
 static void mptcp_set_datafin_timeout(struct sock *sk)
 {
 	struct inet_connection_sock *icsk = inet_csk(sk);
+	u32 rto_min = READ_ONCE(icsk->icsk_rto_min);
+	u32 rto_max = READ_ONCE(icsk->icsk_rto_max);
 	u32 retransmits;
 
+	/* The sysctls are validated independently: rto_min > rto_max is
+	 * possible, guard against ilog2(0).
+	 */
 	retransmits = min_t(u32, icsk->icsk_retransmits,
-			    ilog2(TCP_RTO_MAX / TCP_RTO_MIN));
+			    ilog2(max_t(u32, rto_max / rto_min, 1)));
 
-	mptcp_sk(sk)->timer_ival = TCP_RTO_MIN << retransmits;
+	mptcp_sk(sk)->timer_ival = rto_min << retransmits;
 }
 
 static void __mptcp_set_timeout(struct sock *sk, long tout)
 {
-	mptcp_sk(sk)->timer_ival = tout > 0 ? tout : TCP_RTO_MIN;
+	mptcp_sk(sk)->timer_ival = tout > 0 ? tout :
+				   READ_ONCE(inet_csk(sk)->icsk_rto_min);
 }
 
 static long mptcp_timeout_from_subflow(const struct mptcp_subflow_context *subflow)
@@ -3196,7 +3202,9 @@ static void mptcp_worker(struct work_struct *work)
 
 static void __mptcp_init_sock(struct sock *sk)
 {
+	struct inet_connection_sock *icsk = inet_csk(sk);
 	struct mptcp_sock *msk = mptcp_sk(sk);
+	struct net *net = sock_net(sk);
 
 	INIT_LIST_HEAD(&msk->conn_list);
 	INIT_LIST_HEAD(&msk->join_list);
@@ -3205,7 +3213,13 @@ static void __mptcp_init_sock(struct sock *sk)
 	INIT_WORK(&msk->work, mptcp_worker);
 	msk->out_of_order_queue = RB_ROOT;
 	msk->first_pending = NULL;
-	msk->timer_ival = TCP_RTO_MIN;
+
+	/* msk does not go through tcp_init_sock(); seed RTO bounds. */
+	icsk->icsk_rto_min =
+		usecs_to_jiffies(READ_ONCE(net->ipv4.sysctl_tcp_rto_min_us));
+	icsk->icsk_rto_max =
+		msecs_to_jiffies(READ_ONCE(net->ipv4.sysctl_tcp_rto_max_ms));
+	msk->timer_ival = icsk->icsk_rto_min;
 	msk->scaling_ratio = TCP_DEFAULT_SCALING_RATIO;
 	msk->backlog_len = 0;
 	mptcp_init_rtt_est(msk);
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.