From nobody Thu Oct 15 23:35:21 2026 Received: from mail-dy1-f172.google.com (mail-dy1-f172.google.com [74.125.82.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E041B545DB2 for ; Wed, 14 Oct 2026 14:33:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791988438; cv=none; b=XkHfjE10Mw8RQ6WYbWo9kh2XA1VkXdu0cAAPc//Ze6m020svO5J7itz9L6aCOdveGRxsBFT85U11Gw2sV0DSTSu3PmkXdIa/rj24Eu5QURZoYD5sRh8nvlR5p+qtNwI+1vutWyTJqKS+cstjTvgh6V1BG20sxuPk/5UKHa95OMs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791988438; c=relaxed/simple; bh=pVt48CC94/NaejNT25qHB2vtvqzTbEe+Gr/A6gG792I=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Ay9os6ATc4msfQUlWy8ub97mHIAz0NSqGJYtw2vX/Ikd35hD8GvUsLPm/Gd86npW0NJOV4KO5bgzpQAWd0dx2zTA46MszPvCJDUTC2dwIsFRgMjshOziL2FTBhaoy9PbWO8kI3Z/p0cV34UyuyE6kFB3gCuMZF7rKvNncPzxoJM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Gc1y3l/Q; arc=none smtp.client-ip=74.125.82.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Gc1y3l/Q" Received: by mail-dy1-f172.google.com with SMTP id 5a478bee46e88-3514b90bb89so1746890eec.1 for ; Wed, 14 Oct 2026 07:33:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1791988436; x=1792593236; darn=lists.linux.dev; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=jaZZ2Je7Ko3TIq7T5nVmc2S7oeolCRfMbqqxEkAHg7c=; b=Gc1y3l/QjS6F+HS/Dg1e3kXTofaMMBL8IqrxCyvbxLlJsEvVJPVCpmuQbQxT2XympY ek4oKYcI+GerUjnLxLfhSps7W/aO0khuJcaB5wFISRmymczH66iZSvApp9jxzTNKFZRJ Slsrpn2WaDyuWOFnyo4JdvDS6cZ7+2cAWhzXXosx/3HTEOzTosgYV3wqQ8/7IJAIwfXs FXykv0bh01RGl931uu1C7wxvRIiMWCOxxQ2aiCwEqrk29MOs98Qr92cTPWjAxM/LpRUp o6yk8A5m4KcqoX5mzsrJ7B3KUlZ0IDBqRrdbl3ZSd7gkphS5qyCUIkPjlqCLwMmPioOV M12g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791988436; x=1792593236; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=jaZZ2Je7Ko3TIq7T5nVmc2S7oeolCRfMbqqxEkAHg7c=; b=hgJB0NNaQ2qKIbxbCthJF52OpbqBjzPFlAaYFptLdjWUEYiKxMyNkpD8gAiEkNOZa1 dQb0/3gBGhF1qJpkaESeIErV/rIra4K9ifhfexkFPv4qitqPim+eiYnO0cXICn7SvhcM zISWuIx5nwrnn7leA1rEqi+XLY24NReNhbtZYv7boOD+BFYIEDRMLpJ/69kC5Rzkpedp OkPKBeajBuHmgvfgbhqspfROG+4MZFETqEyq/omcVo/BIH7Cy0Nb1+K+Vk2Y0eyTYIVV /DmJKzSwBHJ+InsDbkykn9QDVM2Zh3xdIu/a66U29vgIANuf4FGOH7Rgv94rEbxvfIWQ Hu+w== X-Gm-Message-State: AFq9FYLvFWdl+1MmgFtC9wLEc6nOrrLE4OKWnuEdwqB5I5HnBEHbxT80 gdXxKbE//GsEjoFMxfQHSBFPfvprcKw8bpDwinbWqVO+5BGtPO5S1nNuQeHqww== X-Gm-Gg: AYBFou3ab2+qUMvdZ4+laRv1Su/laHagYjbN3kwF3x4K3skN/JXgqgS//oRdJrCgR/o yrIZbPjhOyA8BvJDpgqdo+BtB1MUUJwo+FBWCPJUqXVlyF+UbgiinjFfggphN8rRhc52+PQ/ceK OFf6g9Y6lnt2puyV2HAGM6/eKo1EDj+Sbj35JKAYDm/vbBDats4dS/QS3HTG6W40/Ew536FSvWS oMB47uHvVHsc0Rzknse0ECvB6Vizvv3z/3CckFBUbbqRgOnCKsDaZMrhme4PnGihOnLuBAtbw2o t1kMU+SF3IzM5jxPibsjy6qCqHqgXxVuX2vrN/SO8rb4OkXEhU6pkGaly7E9DZVOMoAgAWFX5ah 4JXPlEAc0ehSyqwT85+ciQMevCCmnsa1gwyzjcWM5j0I5VG34ecykINl0/NZTAZCeUeNIKWGdvN Ar/2BpYvz1L6JrFkYAkj0jJlKbhSe9aL5IEZspGt+gd/eLY1mEeywhHNWi5Jc0konj1km2fAB/d yl1VxJRXFFq1LDkI7bgq0wIWR+NHDCQtJro0dN36qaElyOVB0viqUDzbqPacQgYDXe1votqpI2j pKSsbdT8ZE6YxnBW6eSHg9qmjEwKchSIBkOeLy94Onw= X-Received: by 2002:a05:693c:20d1:20b0:35c:e696:2d4a with SMTP id 5a478bee46e88-35ce69632e9mr5660032eec.24.1791988435374; Wed, 14 Oct 2026 07:33:55 -0700 (PDT) Received: from ramen-dev-security-2.asia-south1-c.c.ramen-dev-345017.internal (90.156.200.35.bc.googleusercontent.com. [35.200.156.90]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-353810403e9sm54997609eec.2.2026.10.14.07.33.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 07:33:54 -0700 (PDT) From: Anil Kaushik To: mptcp@lists.linux.dev Cc: Anil Kaushik Subject: [RFC PATCH mptcp-next v3 1/3] selftests: mptcp: connect: retry write() on EAGAIN Date: Wed, 14 Oct 2026 14:33:47 +0000 Message-Id: <20261014143349.768138-2-anilkaushikwireless@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20261014143349.768138-1-anilkaushikwireless@gmail.com> References: <20261014143349.768138-1-anilkaushikwireless@gmail.com> Precedence: bulk X-Mailing-List: mptcp@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" mptcp_connect treats any write() error as fatal. But write() can fail with EAGAIN even after poll() said the socket was writable: the MPTCP send buffer shrinks when a subflow is closed. The tool then exits and the transfer fails on both sides. Instead, go back to poll() and wait for more space. If EAGAIN lasts longer than the poll timeout, still fail: poll() does not check every cause of EAGAIN. Fixes: 048d19d444be ("mptcp: add basic kselftest for mptcp") Assisted-by: LLM Signed-off-by: Anil Kaushik --- .../testing/selftests/net/mptcp/mptcp_connect.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/test= ing/selftests/net/mptcp/mptcp_connect.c index 178d98d91fea..03fff859e4d1 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.c +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c @@ -638,6 +638,7 @@ static int copyfd_io_poll(int infd, int peerfd, int out= fd, .events =3D POLLIN | POLLOUT, }; unsigned int total_wlen =3D 0, total_rlen =3D 0; + int eagain_ms =3D 0; =20 set_nonblock(peerfd, true); =20 @@ -711,6 +712,20 @@ static int copyfd_io_poll(int infd, int peerfd, int ou= tfd, =20 bw =3D do_rnd_write(peerfd, winfo->buf + winfo->off, winfo->len); if (bw < 0) { + /* POLLOUT does not guarantee that + * the next write will not block: + * the MPTCP send buffer can shrink + * in between, e.g. when a subflow + * is closed. Wait for more space, + * but not forever: poll() does not + * check every cause of EAGAIN. + */ + if (errno =3D=3D EAGAIN && + ++eagain_ms <=3D poll_timeout) { + usleep(1000); + continue; + } + /* expected reset, continue to read */ if (cfg_rcv_trunc && (errno =3D=3D ECONNRESET || @@ -723,6 +738,7 @@ static int copyfd_io_poll(int infd, int peerfd, int out= fd, return 111; } =20 + eagain_ms =3D 0; winfo->off +=3D bw; winfo->len -=3D bw; total_wlen +=3D bw; --=20 2.25.1 From nobody Thu Oct 15 23:35:21 2026 Received: from mail-dy1-f182.google.com (mail-dy1-f182.google.com [74.125.82.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E47A85476CD for ; Wed, 14 Oct 2026 14:33:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791988441; cv=none; b=Gh+w+1wwChc/jEt4G2omHgcZkpDSIyDk5Kckq40sdpkk8kpYOkP8vjMr7KmyESdt6w6XzkI7MfCOID2fn1ghMN1+t9nJgEJFvPm0tsk23FzQSM3n9+nPWyHSUYBOI83XU42ke4DXkPjcLjfd/jap/ixuUJ5Fs2hGDXxwY4Z7BMo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791988441; c=relaxed/simple; bh=YPOS5/6gpFMKyx+NTfT6iyfCxapfqq1OlvqAkHDoo4A=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=oS82mHqygUa7p5HMRY7I7hCUvPwWZvOcN85IGFTGCodAxwuz7EvnQFtvWfYUtp9HeArR3qOa8J7uLtfd5emrZ3fxDS7He+d21DpQ4As45vfl/aE6PLoZMB8TvM6hW0/PGEEHJdL6XIB5KTSplPLHB2oHX76/KEN7UKWr8Ak5stI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=hAHU+1hD; arc=none smtp.client-ip=74.125.82.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hAHU+1hD" Received: by mail-dy1-f182.google.com with SMTP id 5a478bee46e88-35a31bb9fb6so5303792eec.1 for ; Wed, 14 Oct 2026 07:33:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1791988438; x=1792593238; darn=lists.linux.dev; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=AiD2K7sqgGtgzeBZoKpm1lFuT+NP39XkXXBlMaVrkdA=; b=hAHU+1hDBOWmh6c441oyMYLWicrkxYaZNjUrINc2Av4Vr/R2GlIIebOIpcN74HFQdX Gvsw9/2BlBEa4RMqTo2V4lfeV4glQlHx8pc3gCgAthgJhhY29eQksrGzV7FPlpkwCXHz KPkD/AMTFWy3CyUDPC05voVCCE79qNALbgTqh971gUGDAbsh/O7ch3zRXOYgvP3D8+qD k7ny8FRKb1sOqE13xpMZ9aBoZ7uOMHVt9Krw5dpw+00Op3e7QDSKb0jkD1IvrUdhUh4/ lme7BFrbjsxyjaa0Ipv83SnRJsZodZ7obK9xvIGoKy7RzNDIJrNd6IaD83Acyz5XEygF BolQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791988438; x=1792593238; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=AiD2K7sqgGtgzeBZoKpm1lFuT+NP39XkXXBlMaVrkdA=; b=0gQsE6OXz2FdMi14yAu91L2J8S5rNvLuwjA6GHb3V+vOEgmC9bjanyio9wOq8D19Qf rj/Paf88RWcb+G6YT73DlAD6jz3TWz2Sg8nEyI4JwhEYaNzmgW2sO0ad68RhLvZr+Yfs Czl7N/1TBHvpyNPW4L4SitQbUsye+p+U1qMAUYZ3ceA8NfED9QibSofAz7L+fkk5OHuA lQCJehE+T0koZT9osqMMxHdz+qbcgVWPOJBW5jBigWgZspWvBNlZsizpRM8n9hgSuvFH p2kEx+wTd62oopHlEUcSP/Hllr8s8jWS+UgM3jLdk1RpHfBc/bKjd8m7XQb2795YKKZg 3zDQ== X-Gm-Message-State: AFq9FYKzMtubdqO6tJYoCrLNdd2xNEQ+INgjbPmRyon5rHAcKnwv2z+7 z5l3JXACDtoF1w2gQwzpB3ejM2FDHT02iNx4wAZ0dT7gQquK0+5hixqBBS6yxg== X-Gm-Gg: AYBFou2w4Zb1ySoiaoaD464cASOK1ago51Jb0LFX1GJ9wbuSVVaXd1NzbHynm48gZEM EahCjRZU06iGMbjo4dEBAROJOrux/BqgwLTGVXhGLFC8Qw4hxsZLfu8yazh2/YnrudP0n+MGxDO OpMiE1RHkR1VSZQAStKgmPMaeOExGjYpSl8aclqIsqc4LekvCSpW3gi7H5HbNgCDo5e35EVT95j Ype+IcJc4wDFGtzIwhqYCVm32D/ZbmfuHWQhvxE2LpnuR7ZpuX9ZMPBpTHaeLKNudfv797NS3QR OR5INup34sGXEDvw5YLV7Yi8WbX8dZ1CUatWO8QOD0fos86GQc7/S2NGMIyHRnkFJMjZr/og27v T2DchgNFJBwg/ohiwNwG9iV+Mj8ilRU1lJn1F8M45UtXXeF73bRE1WbGO2cR4csgOG+hhhQdEaZ eivngw4DfDgv7Az6rvJc+cxQuE6tgjRpGZ3oxjndtRzk3Cc89aaMjD1i09AHiH1D3zE2pi/Y8g/ I0P2pgrZ6WVW5RRz3G+PmFJrzYhl8HXNFlRSDQCzc21FkIJVKMS2FCT8DWtyJAj2ry/fTrIQH6Y t8HMlfPbaUFJSl7clTcooCsp57QVpb3892D0WQiL8zg= X-Received: by 2002:a05:7300:2214:b0:35d:4185:5395 with SMTP id 5a478bee46e88-35d4185542fmr7663005eec.16.1791988437535; Wed, 14 Oct 2026 07:33:57 -0700 (PDT) Received: from ramen-dev-security-2.asia-south1-c.c.ramen-dev-345017.internal (90.156.200.35.bc.googleusercontent.com. [35.200.156.90]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-353810403e9sm54997609eec.2.2026.10.14.07.33.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 07:33:56 -0700 (PDT) From: Anil Kaushik To: mptcp@lists.linux.dev Cc: Anil Kaushik Subject: [RFC PATCH mptcp-next v3 2/3] mptcp: pm: kernel: retry subflows closed after an error Date: Wed, 14 Oct 2026 14:33:48 +0000 Message-Id: <20261014143349.768138-3-anilkaushikwireless@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20261014143349.768138-1-anilkaushikwireless@gmail.com> References: <20261014143349.768138-1-anilkaushikwireless@gmail.com> Precedence: bulk X-Mailing-List: mptcp@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" With the in-kernel PM, a subflow closed after an error, e.g. a timeout when a path silently drops packets, is never re-created, even once the path works again. For example, with a main link and a backup link, connections move to the backup after an outage but never come back. Add an opt-in sysctl, net.mptcp.subflow_retries (0 by default): the maximum number of attempts to re-create such a subflow. When TCP closes a subflow created by this side after an error, the PM creates a new one with the same addresses right away. Its SYN retransmissions space out the attempts, so no extra state or timer is needed. If it cannot connect, the same socket is used for the next attempt. The number of attempts is kept in the subflow context, and counted from scratch once a re-created subflow stayed established for a minute. Re-creating the initial subflow uses one of the additional subflows allowed by the limits. Subflows refused by the peer, reset by the peer without the transient flag (RFC 8684 section 3.6), aborted locally, or using a removed address are not retried. A new counter, SubflowRetry, counts the attempts. Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/440 Assisted-by: LLM Signed-off-by: Anil Kaushik --- Documentation/networking/mptcp-sysctl.rst | 18 +++ net/mptcp/ctrl.c | 13 ++ net/mptcp/mib.c | 1 + net/mptcp/mib.h | 1 + net/mptcp/pm.c | 8 ++ net/mptcp/pm_kernel.c | 145 ++++++++++++++++++++++ net/mptcp/protocol.c | 9 ++ net/mptcp/protocol.h | 24 ++++ net/mptcp/subflow.c | 63 ++++++++++ 9 files changed, 282 insertions(+) diff --git a/Documentation/networking/mptcp-sysctl.rst b/Documentation/netw= orking/mptcp-sysctl.rst index b9b5f58e0625..544f6b84d4af 100644 --- a/Documentation/networking/mptcp-sysctl.rst +++ b/Documentation/networking/mptcp-sysctl.rst @@ -149,6 +149,24 @@ stale_loss_cnt - INTEGER =20 Default: 4 =20 +subflow_retries - INTEGER + Maximum number of attempts to re-create a subflow closed by TCP after + an error, e.g. a timeout or an unreachable path, while the MPTCP + connection is still established. The in-kernel path manager creates a + new subflow with the same local and remote addresses: its SYN + retransmissions, see tcp_syn_retries, space out the attempts. If it + cannot connect, the same socket is used for the next attempt. The + count starts again once a re-created subflow stayed established for a + minute. Only subflows initiated by this side are re-created, within + the limits set for the path manager: re-creating the initial subflow + uses one of the additional subflows. Subflows refused by the peer, + reset by the peer without the transient flag, aborted locally, or + using a removed address are not retried. + + 0 disables the retries. This is a per-namespace sysctl. + + Default: 0 + syn_retrans_before_tcp_fallback - INTEGER The number of SYN + MP_CAPABLE retransmissions before falling back to TCP, i.e. dropping the MPTCP options. In other words, if all the packets diff --git a/net/mptcp/ctrl.c b/net/mptcp/ctrl.c index 63c5747f0f63..37d96f89c4b1 100644 --- a/net/mptcp/ctrl.c +++ b/net/mptcp/ctrl.c @@ -34,6 +34,7 @@ struct mptcp_pernet { atomic_t active_disable_times; unsigned long active_disable_stamp; u8 syn_retrans_before_tcp_fallback; + u8 subflow_retries; u8 mptcp_enabled; u8 checksum_enabled; u8 allow_join_initial_addr_port; @@ -73,6 +74,11 @@ unsigned int mptcp_stale_loss_cnt(const struct net *net) return mptcp_get_pernet(net)->stale_loss_cnt; } =20 +u8 mptcp_subflow_retries(const struct net *net) +{ + return READ_ONCE(mptcp_get_pernet(net)->subflow_retries); +} + unsigned int mptcp_close_timeout(const struct sock *sk) { if (sock_flag(sk, SOCK_DEAD)) @@ -377,6 +383,12 @@ static struct ctl_table mptcp_sysctl_table[] =3D { .extra1 =3D SYSCTL_ZERO, .extra2 =3D SYSCTL_ONE }, + { + .procname =3D "subflow_retries", + .maxlen =3D sizeof(u8), + .mode =3D 0644, + .proc_handler =3D proc_dou8vec_minmax, + }, }; =20 static int mptcp_pernet_new_table(struct net *net, struct mptcp_pernet *pe= rnet) @@ -405,6 +417,7 @@ static int mptcp_pernet_new_table(struct net *net, stru= ct mptcp_pernet *pernet) table[11].data =3D &pernet->path_manager; /* table[12] is for available_path_managers which is read-only info */ table[13].data =3D &pernet->add_addr_v6_port_drop_ts; + table[14].data =3D &pernet->subflow_retries; =20 hdr =3D register_net_sysctl_sz(net, MPTCP_SYSCTL_PATH, table, ARRAY_SIZE(mptcp_sysctl_table)); diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c index 608cb568897c..5263ac6f2c43 100644 --- a/net/mptcp/mib.c +++ b/net/mptcp/mib.c @@ -95,6 +95,7 @@ static const struct snmp_mib mptcp_snmp_list[] =3D { SNMP_MIB_ITEM("BacklogDrop", MPTCP_MIB_BACKLOGDROP), SNMP_MIB_ITEM("RcvPruned", MPTCP_MIB_RCVPRUNED), SNMP_MIB_ITEM("OFOPruned", MPTCP_MIB_OFOPRUNED), + SNMP_MIB_ITEM("SubflowRetry", MPTCP_MIB_SUBFLOWRETRY), }; =20 /* mptcp_mib_alloc - allocate percpu mib counters diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h index 1ebdb55e9534..1359839f4bbe 100644 --- a/net/mptcp/mib.h +++ b/net/mptcp/mib.h @@ -98,6 +98,7 @@ enum linux_mptcp_mib_field { MPTCP_MIB_BACKLOGDROP, /* Backlog over memory limit */ MPTCP_MIB_RCVPRUNED, /* Dropped due to memory constraints */ MPTCP_MIB_OFOPRUNED, /* MPTCP-level OoO queue pruned */ + MPTCP_MIB_SUBFLOWRETRY, /* Retried a subflow closed on error */ __MPTCP_MIB_MAX }; =20 diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index d7c5b50b34cc..b3f7f7b2ef84 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -794,6 +794,14 @@ static void mptcp_pm_rm_addr_or_subflow(struct mptcp_s= ock *msk, int how =3D RCV_SHUTDOWN | SEND_SHUTDOWN; u8 id =3D subflow_get_local_id(subflow); =20 + /* a dead subflow still waiting to be closed must not + * be re-created on a removed address + */ + if ((rm_type =3D=3D MPTCP_MIB_RMADDR && + remote_id =3D=3D rm_id) || + (rm_type =3D=3D MPTCP_MIB_RMSUBFLOW && id =3D=3D rm_id)) + subflow->retries =3D MPTCP_SUBFLOW_NO_RETRY; + if ((1 << inet_sk_state_load(ssk)) & (TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | TCPF_CLOSING | TCPF_CLOSE)) continue; diff --git a/net/mptcp/pm_kernel.c b/net/mptcp/pm_kernel.c index d3014bf57bf3..d020e5f69c01 100644 --- a/net/mptcp/pm_kernel.c +++ b/net/mptcp/pm_kernel.c @@ -1591,6 +1591,151 @@ bool mptcp_pm_nl_check_work_pending(struct mptcp_so= ck *msk) return true; } =20 +/* a re-created subflow reset sooner counts as a failed attempt */ +#define MPTCP_PM_RETRY_STABLE (60 * HZ) + +static bool mptcp_pm_retry_path_error(int err) +{ + switch (err) { + case ETIMEDOUT: + case EHOSTUNREACH: + case ENETUNREACH: + case EHOSTDOWN: + case ENETDOWN: + return true; + } + + return false; +} + +/* A subflow initiated by this side and closed by TCP after an error can be + * re-created with the same addresses, see the 'subflow_retries' sysctl. T= he + * SYN retransmissions of the new subflow space out the attempts. Called w= ith + * the msk socket lock held, before the subflow is closed. + */ +bool mptcp_pm_kernel_retry_check(struct mptcp_sock *msk, + const struct mptcp_subflow_context *subflow, + struct mptcp_pm_retry *retry) +{ + struct sock *ssk =3D mptcp_subflow_tcp_sock(subflow); + bool established =3D READ_ONCE(subflow->fully_established); + struct sock *sk =3D (struct sock *)msk; + int err =3D READ_ONCE(ssk->sk_err); + + if (!mptcp_pm_is_kernel(msk) || !err || + sk->sk_state !=3D TCP_ESTABLISHED || __mptcp_check_fallback(msk)) + return false; + + /* mptcp_close_ssk() will not handle it again, e.g. the initial + * subflow, kept in the list once closed + */ + if (subflow->close_event_done || READ_ONCE(subflow->local_id) < 0) + return false; + + /* removed address, see mptcp_pm_rm_addr_or_subflow() */ + if (subflow->retries =3D=3D MPTCP_SUBFLOW_NO_RETRY) + return false; + + /* The peer asked not to use this path again (MP_RST without the T + * flag), the subflow has been aborted locally, or it hit an MPTCP + * protocol error. + */ + if ((subflow->reset_seen && !subflow->reset_transient) || + err =3D=3D ECONNABORTED || err =3D=3D EBADMSG) + return false; + + /* A subflow that never got established is retried only after network + * errors: if the peer rejected it, trying again will not help. + */ + if (!established && !mptcp_pm_retry_path_error(err)) + return false; + + /* Only re-create subflows initiated by this side: MP_JOIN ones, or the + * initial one on the client side. + */ + if (!subflow->request_join && + (subflow->mp_join || READ_ONCE(msk->pm.server_side))) + return false; + + /* Count the attempts from scratch only if the path worked for a + * while: a re-created subflow reset soon after its creation counts + * as a failed attempt. + */ + retry->retries =3D subflow->retries; + if (established && + (!subflow->retries || + (u32)(tcp_jiffies32 - subflow->retry_stamp) >=3D + MPTCP_PM_RETRY_STABLE)) + retry->retries =3D 0; + /* a failed attempt is retried with the same socket */ + retry->reuse =3D !established && subflow->retries; + if (retry->retries >=3D mptcp_subflow_retries(sock_net(sk))) + return false; + + mptcp_remote_address((struct sock_common *)ssk, &retry->remote); + retry->remote.id =3D READ_ONCE(subflow->remote_id); + if (!retry->remote.id && READ_ONCE(msk->pm.remote_deny_join_id0)) + return false; + + mptcp_local_address((struct sock_common *)ssk, &retry->local.addr); + retry->local.addr.port =3D 0; + retry->local.addr.id =3D subflow_get_local_id(subflow); + retry->local.flags =3D subflow->request_bkup ? + MPTCP_PM_ADDR_FLAG_BACKUP : 0; + retry->local.ifindex =3D ssk->sk_bound_dev_if; + + return true; +} + +/* Called with the msk socket lock held, instead of closing the subflow */ +bool mptcp_pm_kernel_retry_reconnect(struct mptcp_sock *msk, struct sock *= ssk, + const struct mptcp_pm_retry *retry) +{ + struct mptcp_subflow_context *subflow =3D mptcp_subflow_ctx(ssk); + struct sock *sk =3D (struct sock *)msk; + + /* like mptcp_close_ssk() does for the failed attempt */ + mptcp_event(MPTCP_EVENT_SUB_CLOSED, msk, ssk, GFP_KERNEL); + + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_SUBFLOWRETRY); + if (mptcp_subflow_reconnect(sk, ssk, &retry->local, &retry->remote)) + return false; + + /* the reset cleared them */ + subflow->retries =3D retry->retries + 1; + subflow->retry_stamp =3D tcp_jiffies32; + return true; +} + +/* Called with the msk socket lock held, after the subflow has been closed= */ +void mptcp_pm_kernel_retry(struct mptcp_sock *msk, + const struct mptcp_pm_retry *retry) +{ + struct mptcp_subflow_context *subflow; + struct sock *sk =3D (struct sock *)msk; + bool allowed; + + spin_lock_bh(&msk->pm.lock); + allowed =3D msk->pm.extra_subflows < + mptcp_pm_get_limit_extra_subflows(msk); + if (allowed) + msk->pm.extra_subflows++; + spin_unlock_bh(&msk->pm.lock); + + if (!allowed) + return; + + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_SUBFLOWRETRY); + if (__mptcp_subflow_connect(sk, &retry->local, &retry->remote)) + return; + + /* the new subflow is the last one: it carries the number of attempts */ + subflow =3D list_last_entry(&msk->conn_list, struct mptcp_subflow_context, + node); + subflow->retries =3D retry->retries + 1; + subflow->retry_stamp =3D tcp_jiffies32; +} + /* Called under PM lock */ void __mptcp_pm_kernel_worker(struct mptcp_sock *msk) { diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 60cbd91be632..f29cfd2ad69b 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2816,6 +2816,8 @@ static void __mptcp_close_subflow(struct sock *sk) mptcp_for_each_subflow_safe(msk, subflow, tmp) { struct sock *ssk =3D mptcp_subflow_tcp_sock(subflow); int ssk_state =3D inet_sk_state_load(ssk); + struct mptcp_pm_retry retry; + bool do_retry; =20 if (ssk_state !=3D TCP_CLOSE && (ssk_state !=3D TCP_CLOSE_WAIT || @@ -2827,7 +2829,14 @@ static void __mptcp_close_subflow(struct sock *sk) if (!skb_queue_empty_lockless(&ssk->sk_receive_queue)) continue; =20 + do_retry =3D mptcp_pm_kernel_retry_check(msk, subflow, &retry); + if (do_retry && retry.reuse && + mptcp_pm_kernel_retry_reconnect(msk, ssk, &retry)) + continue; + mptcp_close_ssk(sk, ssk, subflow); + if (do_retry && !retry.reuse) + mptcp_pm_kernel_retry(msk, &retry); } =20 } diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index ed2f363bb915..1aa92c9614cd 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -622,6 +622,8 @@ struct mptcp_subflow_context { u8 reset_transient:1; u8 reset_reason:4; u8 stale_count; /* Protected by the msk socket lock */ + u8 retries; /* re-creation attempts, msk socket lock */ + u32 retry_stamp; /* creation of a re-created subflow */ u8 stale; /* Protected by the msk socket lock, * if set the subflow is unable to snd/rcv * data, the schedule should skip it @@ -818,6 +820,7 @@ int mptcp_is_checksum_enabled(const struct net *net); int mptcp_allow_join_id0(const struct net *net); unsigned int mptcp_stale_loss_cnt(const struct net *net); unsigned int mptcp_close_timeout(const struct sock *sk); +u8 mptcp_subflow_retries(const struct net *net); int mptcp_get_pm_type(const struct net *net); const char *mptcp_get_path_manager(const struct net *net); const char *mptcp_get_scheduler(const struct net *net); @@ -859,6 +862,9 @@ void mptcp_remote_address(const struct sock_common *skc, struct mptcp_addr_info *addr); =20 /* called with sk socket lock held */ +int mptcp_subflow_reconnect(struct sock *sk, struct sock *ssk, + const struct mptcp_pm_local *local, + const struct mptcp_addr_info *remote); int __mptcp_subflow_connect(struct sock *sk, const struct mptcp_pm_local *= local, const struct mptcp_addr_info *remote); int mptcp_subflow_create_socket(struct sock *sk, unsigned short family, @@ -1258,6 +1264,24 @@ void __init mptcp_pm_userspace_register(void); void __init mptcp_pm_nl_init(void); void mptcp_pm_worker(struct mptcp_sock *msk); void __mptcp_pm_kernel_worker(struct mptcp_sock *msk); + +/* subflow->retries value preventing any re-creation */ +#define MPTCP_SUBFLOW_NO_RETRY U8_MAX + +struct mptcp_pm_retry { + struct mptcp_pm_local local; + struct mptcp_addr_info remote; + u8 retries; + bool reuse; /* failed attempt: same socket */ +}; + +bool mptcp_pm_kernel_retry_check(struct mptcp_sock *msk, + const struct mptcp_subflow_context *subflow, + struct mptcp_pm_retry *retry); +void mptcp_pm_kernel_retry(struct mptcp_sock *msk, + const struct mptcp_pm_retry *retry); +bool mptcp_pm_kernel_retry_reconnect(struct mptcp_sock *msk, struct sock *= ssk, + const struct mptcp_pm_retry *retry); u8 mptcp_pm_get_endp_signal_max(const struct mptcp_sock *msk); u8 mptcp_pm_get_endp_subflow_max(const struct mptcp_sock *msk); u8 mptcp_pm_get_endp_laminar_max(const struct mptcp_sock *msk); diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 78e886b20258..0cdeae4b45fa 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -1766,6 +1766,69 @@ int __mptcp_subflow_connect(struct sock *sk, const s= truct mptcp_pm_local *local, return err; } =20 +/* Connect again an MPJ subflow that failed to connect, with the same sock= et. + * The subflow never carried data: like for the initial subflow in + * __mptcp_subflow_disconnect(), resetting the TCP and the subflow states = is + * enough. Called with the msk socket lock held. + */ +int mptcp_subflow_reconnect(struct sock *sk, struct sock *ssk, + const struct mptcp_pm_local *local, + const struct mptcp_addr_info *remote) +{ + struct mptcp_subflow_context *subflow =3D mptcp_subflow_ctx(ssk); + struct sockaddr_unsized *uaddr; + struct mptcp_sock *msk =3D mptcp_sk(sk); + struct sockaddr_storage addr; + u32 remote_token; + int addrlen; + int err; + + lock_sock_nested(ssk, SINGLE_DEPTH_NESTING); + err =3D tcp_disconnect(ssk, 0); + if (err) + goto out; + + mptcp_subflow_ctx_reset(subflow); + subflow->request_mptcp =3D 0; + do { + subflow->local_nonce =3D get_random_u32(); + } while (!subflow->local_nonce); + subflow_set_local_id(subflow, local->addr.id); + subflow->remote_key_valid =3D 1; + subflow->remote_key =3D READ_ONCE(msk->remote_key); + subflow->local_key =3D READ_ONCE(msk->local_key); + subflow->token =3D msk->token; + mptcp_crypto_key_sha(subflow->remote_key, &remote_token, NULL); + subflow->remote_token =3D remote_token; + WRITE_ONCE(subflow->remote_id, remote->id); + subflow->request_join =3D 1; + subflow->request_bkup =3D !!(local->flags & MPTCP_PM_ADDR_FLAG_BACKUP); + subflow->subflow_id =3D msk->subflow_id++; + + mptcp_info2sockaddr(remote, &addr, ssk->sk_family); + addrlen =3D sizeof(struct sockaddr_in); +#if IS_ENABLED(CONFIG_MPTCP_IPV6) + if (addr.ss_family =3D=3D AF_INET6) + addrlen =3D sizeof(struct sockaddr_in6); +#endif + uaddr =3D (struct sockaddr_unsized *)&addr; + + /* like __inet_stream_connect() for __mptcp_subflow_connect() */ + if (BPF_CGROUP_PRE_CONNECT_ENABLED(ssk)) { + err =3D ssk->sk_prot->pre_connect(ssk, uaddr, addrlen); + if (err) + goto out; + } + + err =3D ssk->sk_prot->connect(ssk, uaddr, addrlen); + +out: + release_sock(ssk); + MPTCP_INC_STATS(sock_net(sk), err ? MPTCP_MIB_JOINSYNTXCONNECTERR : + MPTCP_MIB_JOINSYNTX); + return err; +} + void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp) { /* Only if the msk has been accepted already (and not orphaned).*/ --=20 2.25.1 From nobody Thu Oct 15 23:35:21 2026 Received: from mail-dy1-f173.google.com (mail-dy1-f173.google.com [74.125.82.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E2FBB38E8A1 for ; Wed, 14 Oct 2026 14:34:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791988442; cv=none; b=ASB3oufrKRrxIyDQrMmKRdmlC3A0hYYsPcZPz7navsA1iPN8chELSmTYLNHPrQL1nLfEqpPYMSIMumbLJp2tsW2RfU5yos9QJDwuZGhFWDGtf6T/nqmmC7m1n3gmpO3FVpZ8ukHGUwGm9La7WbyEmzJ7KNY50ifnehPZJ1FauBA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791988442; c=relaxed/simple; bh=noAVtA+7uv++mf4e6CUtE7thOGqYcvwmez50yioeelE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=fKqCy9x86nzWdyiTTmSvXR5USPLToGJG4iK+tiDgqEblkPX36pM4fkceHhWV1WgbM4DH16kb9JuKCf9GuahlNvZLeUJ/XtfcQRgviaclEx+8GJGh19oi9ANLVJbvm921afOuE8UDfvYE4yf6U1uioD9dRDBLp5LzXhnBKpQGZY8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=dm/Dz4eH; arc=none smtp.client-ip=74.125.82.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dm/Dz4eH" Received: by mail-dy1-f173.google.com with SMTP id 5a478bee46e88-35b2c599f43so5127854eec.0 for ; Wed, 14 Oct 2026 07:34:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1791988440; x=1792593240; darn=lists.linux.dev; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=F8Kb4zP7iCujVb4rPtJclw5x7/s5folut5hqnEE0P3Y=; b=dm/Dz4eHjlDFkL/6diamJamDXLK2+NeRk8yjr2QD//HAhkjwn+Edl8mXlPKUYJ7ak4 AM58nBIS8aewV8I7yzIcsuybEW4tY57Tnc3+xkx4TWtQhKbrA2Drbw7RQc6kFeSo4EJY QGzep+ApAagNJ/E5sBNU0UbrqKC1vtL5GUuvnYf6dkHodJHOjQ5ibrats1bcYFilc+41 Ko3+Bf8fVHVVfwp7RxasWmpapqcMM7z7yKpGYNxMA2EsJpgRblUkcqWKuPWYDXLKoTZ0 2ftziwpoMjMiDgpjzmj8U4Wb718IOIZRH7emn8PvQLvfwdtGsBWrFlbpqSLoIJ91mF0h c5eA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791988440; x=1792593240; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=F8Kb4zP7iCujVb4rPtJclw5x7/s5folut5hqnEE0P3Y=; b=vB68hRYNFQonE+QbqGfaRMPRXEf5F8x9P9fSfWgKFZdtfclwcJK3tOX8PQwqU7BIgE QM1ltOyVFn3xxiRYRYMlZ5pmpd05Lav5rFmC1vaUT9gs/pGJR9FQOyPpNammhO53Q9Jy 4frPj2VURXRMDGwpiGYynbdgyBda9V0pMQ36siR64Vh+/Dh0mXBbTORxZRYu8oSbV56W pMjKSId6ApvZb8ynULU3W7rXEQvkYS5AJoj8N4QbjVSCxpuBq4/gO5kEwWgG43yAMY27 UcYjYKGC6nrZbegM18sJ9clHQFzlDD2rVF1F+bLbdD2A3XLxOkrx3qU/WGl5B1tyxCw7 pVMg== X-Gm-Message-State: AFq9FYLUDurTXMUGEemH24vy2SlDr3CD7wTIr14Efx1L6mVQnN+WFZ2x E6+Mz8gel5hZNrhNsPX8xIg7sFfAJSVM40Q07nzp8xbLwHqV3Aga/AwP+6xMKg== X-Gm-Gg: AYBFou2m+UL1x4fMQNfoUsDtltYgsqKQHithqAXVTfbJtrh+9jNDr5xprd/DAoGjMeB EC9VuSS9phM9aUkL5A0jn2kqRWmfu/KeOeqawUJohXIcwzKEufGT+oSzfTa59CKR1dhDCH6QRsB FQcMoRP/dmwpRsf/XuHuUswml3vuH0GFrP4uObvezqts+s27sSIwK/DCCOpcSpjPOTyW1y4NPkm WNHiUSPpxHf6n59lMhOpawNwSoAJAD1zWXPzqm/Btd/4lM530G3WUTY8ZKdC+pyh27tIM/aK0HA KnUNjQhks6h0/Ya/bbEPhJsX/c7wRr9H2N/G6WQbU0DGJIskQD8LOoZHa0yHuG2DjYqLgUgJQez CfRI+JHBzFdQOHO7CbgyiHuiRWi+6rN8AIe+T4Jv+lzhBnadkDZuArSn9k103QTlTCKDW6kXdIN bBKGtHfrP2HZvv6HntljMZYUzF8W3wblxAOi5hlhJfO6WkQaIgtWrDxs3FXpFUEEPyA/p/u61uy U2wPg8xCJ+AmmJHP0mMv+FQL7hd1ZIOfbRKgYKyQFcthqvwu8WYXOMfIortef+/8aSrvOzVHiHh P8r713DFbXD7bz5/dCmwH0yJnzTfYI+WqAL3CDSIjx2vuzgLMRO0YA== X-Received: by 2002:a05:7300:2216:b0:351:28d9:4908 with SMTP id 5a478bee46e88-3537db99e8amr29992722eec.0.1791988439564; Wed, 14 Oct 2026 07:33:59 -0700 (PDT) Received: from ramen-dev-security-2.asia-south1-c.c.ramen-dev-345017.internal (90.156.200.35.bc.googleusercontent.com. [35.200.156.90]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-353810403e9sm54997609eec.2.2026.10.14.07.33.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 07:33:59 -0700 (PDT) From: Anil Kaushik To: mptcp@lists.linux.dev Cc: Anil Kaushik Subject: [RFC PATCH mptcp-next v3 3/3] selftests: mptcp: join: validate subflow retry after link failure Date: Wed, 14 Oct 2026 14:33:49 +0000 Message-Id: <20261014143349.768138-4-anilkaushikwireless@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20261014143349.768138-1-anilkaushikwireless@gmail.com> References: <20261014143349.768138-1-anilkaushikwireless@gmail.com> Precedence: bulk X-Mailing-List: mptcp@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Test the new sysctl with a long connection in the background: drop the packets the client sends from the additional subflow address with netfilter, without bringing the link down, until the subflow is closed and a retry happens. Then let them through again, and check the subflow comes back, from the same address. Then do the same with the initial subflow. Assisted-by: LLM Signed-off-by: Anil Kaushik --- .../testing/selftests/net/mptcp/mptcp_join.sh | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testin= g/selftests/net/mptcp/mptcp_join.sh index f4d8afaeda4f..1048c9cfceb7 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -2151,6 +2151,76 @@ wait_attempt_fail() return 1 } =20 +# $1: ns ; $2: minimum number of retries +wait_sf_retry() +{ + local ns=3D"${1}" + local retry_min=3D"${2}" + local cnt + + for _ in $(seq $((timeout_poll * 10))); do + cnt=3D$(mptcp_lib_get_counter "${ns}" "MPTcpExtSubflowRetry") + [ "${cnt:-0}" -ge "${retry_min}" ] && return 0 + sleep 0.1 + done + + return 1 +} + +# $1: expected number of MPJ ACK received by the server +wait_mpj_ack() +{ + local exp_cnt=3D"${1}" + local cnt + + for _ in $(seq $((timeout_poll * 10))); do + cnt=3D$(mptcp_lib_get_counter "${ns1}" "MPTcpExtMPJoinAckRx") + [ "${cnt:-0}" -ge "${exp_cnt}" ] && return 0 + sleep 0.1 + done + + return 1 +} + +# $1: ns ; $2: minimum number of retries +chk_sf_retry_nr() +{ + local ns=3D"${1}" + local retry_min=3D"${2}" + local count + + print_check "subflow retry" + count=3D$(mptcp_lib_get_counter "${ns}" "MPTcpExtSubflowRetry") + if [ -z "${count}" ]; then + print_skip + elif [ "${count}" -lt "${retry_min}" ]; then + fail_test "got ${count} retry[s] expected at least ${retry_min}" + else + print_ok + fi +} + +# $1: client address to block ; $2: retries ; $3: MPJ, expected after it +# +# The link stays up, but the client packets from this address are dropped: +# the subflow using it is closed after a few retransmissions, then re-crea= ted +# once the packets go through again. +sf_retry_after_block() +{ + ip netns exec $ns2 nft add rule ip filter OUTPUT ip saddr "${1}" drop + wait_sf_retry $ns2 "${2}" + ip netns exec $ns2 nft flush chain ip filter OUTPUT + wait_mpj_ack "${3}" + + print_check "subflow from ${1}" + if ip netns exec $ns2 ss -tnH state established src "${1}" | + grep -q .; then + print_ok + else + fail_test "no established subflow from ${1}" + fi +} + set_userspace_pm() { local ns=3D$1 @@ -2585,6 +2655,42 @@ link_failure_tests() chk_stale_nr $ns2 1 -1 2 chk_link_usage $ns2 ns2eth3 $cinsent 50 fi + + # subflows closed after a silent link failure, then re-created: the + # additional one, then the initial one, with an MPJ using ID 0 + if reset "subflow retry after link failure" && + continue_if mptcp_lib_has_file \ + '/proc/sys/net/mptcp/subflow_retries'; then + init_nftables + init_shapers + ip netns exec $ns2 sysctl -q net.mptcp.subflow_retries=3D15 + ip netns exec $ns2 sysctl -q net.ipv4.tcp_retries2=3D3 + ip netns exec $ns2 sysctl -q net.ipv4.tcp_syn_retries=3D1 + pm_nl_set_limits $ns1 0 4 + pm_nl_set_limits $ns2 0 2 + pm_nl_add_endpoint $ns2 10.0.3.2 dev ns2eth3 flags subflow + # created here, not in the background job, for cleanup() + if [ -z "$cinfail" ]; then + cinfail=3D$(mktemp) + fi + if [ -z "$sinfail" ]; then + sinfail=3D$(mktemp) + fi + { timeout_test=3D120 test_linkfail=3D131072 \ + run_tests $ns1 $ns2 10.0.1.1 & } 2>/dev/null + local tests_pid=3D$! + + wait_mpj_ack 1 + sf_retry_after_block 10.0.3.2 2 2 + sf_retry_after_block 10.0.1.2 3 3 + mptcp_lib_kill_group_wait $tests_pid + + local retry_nr + retry_nr=3D$(mptcp_lib_get_counter $ns2 "MPTcpExtSubflowRetry") + join_syn_tx=3D$((1 + ${retry_nr:-0})) \ + chk_join_nr 3 3 3 + chk_sf_retry_nr $ns2 3 + fi } =20 add_addr_timeout_tests() --=20 2.25.1