From nobody Sat Sep 26 12:28:44 2026 Received: from sender5-of-o54.zoho.com (sender5-of-o54.zoho.com [165.173.182.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 628694195B5 for ; Mon, 14 Sep 2026 09:20:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=165.173.182.54 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789377634; cv=pass; b=BG8G/M3I159jOrXJ32QdhORjvVm4FVdUbwq9YS4X4BgXTzRSIOsy5hBSI1PxdQio9VRUmY6W5nyyAY8Hc2s7aij1UGTUGfi25hWe60iHjC3scDrYV55YQgc+CQCFRARFL7u+bUOd0q8CNKonAZ+9l9gwRycZe1AiAHSQ0CP12JU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789377634; c=relaxed/simple; bh=1sLHBFlyTfbBE36W9ew9kX6DLDtiFr3vjC/jETscQlY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d8gebZSaehIIJ4zgVC74VMMpjERfVVdKRb+dMrSsYC/RSoBN35DZ0VDL2jsqhWXpLlUjuBIf/4GntwrzhVxLXJIUeeQq4sRHrxFgWZCStUdac4TMIYKkwjscxHDu43fMe5SwQPzQ77qQeDYxjfVnX0ExjGBqH/eenvqUsAj63PY= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=mpiricsoftware.com; spf=pass smtp.mailfrom=mpiricsoftware.com; dkim=fail (0-bit key) header.d=mpiricsoftware.com header.i=kalpan.jani@mpiricsoftware.com header.b=J9zS9Ugl reason="key not found in DNS"; arc=pass smtp.client-ip=165.173.182.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=mpiricsoftware.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mpiricsoftware.com Authentication-Results: smtp.subspace.kernel.org; dkim=fail reason="key not found in DNS" (0-bit key) header.d=mpiricsoftware.com header.i=kalpan.jani@mpiricsoftware.com header.b="J9zS9Ugl" ARC-Seal: i=1; a=rsa-sha256; t=1789377619; cv=none; d=zohomail.com; s=zohoarc; b=Q+Yr2ZI9BwmU1dsgP2T7zAVF4a79s98uiWfuvfI/QLGlfzeT1/VDSQ5yqb6pXiFL5H8nD22JER0fidNcRQk7fuIwrp8dOctLqAgCbE80FGx5oZOeJU1suwcgR3U+O5cjmma7XqWt6yJEjZivwDINHbnQIY2GkxiMWHJB8kxXpxQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1789377619; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=8kDSA/2A4VevC+5hQraUYgw7nPcvfDueIzTGTkIfGyI=; b=RBjY7qBc2RS9/3fBHaMG8migOY1YmuhAroC8o4yHpivYya5dHz8nLZFhZmP7ZSpBTB5rbVt/xiCKpqSPzs2WOiChO0Uc8DrRjXBRGY3cpkWlO7YO5OlR94vN7pq3zn6wmuZ7IqmNHD80rZ9JJowvswVn6H/TkPJPd8nmKFcAysM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=mpiricsoftware.com; spf=pass smtp.mailfrom=kalpan.jani@mpiricsoftware.com; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1789377619; s=mpiric; d=mpiricsoftware.com; i=kalpan.jani@mpiricsoftware.com; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=8kDSA/2A4VevC+5hQraUYgw7nPcvfDueIzTGTkIfGyI=; b=J9zS9UglDGR514O6jNMAF1BQZs6UZx5ueg/k7wv7BX6/hz/bB39jfo4q8erTAfh3 2ICuvZ8bBxZKA/SCYUrZgrxJN5fWPpX0oP/ML8dDxgIPqFcBOL7xCQuOJw/FP3CQ3MZ o9p15bQ7WRZl6o29/TmQ8Qu71nZhqQd7/ncSs09Q= Received: by smtp.zohomail.com with SMTPS id 1789377617952117.10437647247102; Mon, 14 Sep 2026 02:20:17 -0700 (PDT) From: Kalpan Jani To: mptcp@lists.linux.dev Cc: matttbe@kernel.org, martineau@kernel.org, pabeni@redhat.com, shardul.b@mpiricsoftware.com, janak@mpiric.us, kalpanjani009@gmail.com Subject: [PATCH mptcp-next v4 1/2] mptcp: sched: add subflow avoid flag and enforce it in default sched Date: Mon, 14 Sep 2026 14:49:59 +0530 Message-ID: <20260914092000.1306968-2-kalpan.jani@mpiricsoftware.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260914092000.1306968-1-kalpan.jani@mpiricsoftware.com> References: <20260914092000.1306968-1-kalpan.jani@mpiricsoftware.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 X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" The core benches a subflow via the "stale" bit after net.mptcp.stale_loss_cnt retransmission intervals without progress. That bit belongs to the core and is tightly coupled to the RTO: it gets cleared as soon as a packet is acked on the subflow (mptcp_subflow_active() calls mptcp_subflow_set_active() once rcv_tstamp moves forward). "scheduled" doesn't cover this either. The core clears it right after every send/retrans pass the scheduler set it in, so there's no way to express "leave this subflow alone across future rounds" with it, and nothing outside the scheduler that set it ever sees the value. Add a scheduler-owned "avoid" bool to mptcp_subflow_context: - only mptcp_subflow_set_avoid() touches it, never the core. It's exposed to BPF struct_ops schedulers as a kfunc with the same registration and filter as mptcp_subflow_set_scheduled(), so only a scheduler can call it; - it is a standalone bool, alongside "scheduled" and "data_avail", rather than a bit in the adjacent flag bitfield. mptcp_subflow_data_available() clears map_valid, a bit in that bitfield, from the subflow's own receive path without holding the msk lock, while set_avoid() runs under the msk lock from BPF scheduler context. Bitfield writes are a non-atomic read-modify-write on the whole containing word, so placing avoid there would race with that update and risk silently corrupting map_valid or other neighbouring bits. The standalone bools next to it are accessed with READ_ONCE()/WRITE_ONCE() for the same reason; - the default in-kernel subflow picker, mptcp_subflow_get_send() and mptcp_subflow_get_retrans(), now skips an avoided subflow next to the existing active-subflow check. This only applies when no custom scheduler is loaded (mptcp_sched_default_get_send() / _get_retrans() are what msk->sched->get_send() falls back to); a custom BPF scheduler bypasses this default picker entirely, so it only honours avoid if it explicitly checks subflow->avoid itself, exactly as the selftest scheduler in patch 2 does. avoid defaults to false and only an explicit set_avoid() call flips it, so this is a no-op for anyone who doesn't use it; - it's surfaced through MPTCP diag as MPTCP_SUBFLOW_FLAG_AVOID, alongside the existing BKUP/FULLY_ESTABLISHED flags. set_avoid() has no C callers in-tree, unlike set_scheduled() which the core send/retrans/close paths call directly -- mark it __bpf_kfunc so it survives LTO/dead-code-elimination before resolve_btfids looks for it in BTF. Link: https://github.com/multipath-tcp/mptcp_net-next/issues/349 Suggested-by: Paolo Abeni Signed-off-by: Kalpan Jani Changes since v3: - moved avoid back out of the __unused bitfield padding into a standalone bool, and restored READ_ONCE()/WRITE_ONCE() on every access. Sashiko flagged that mptcp_subflow_data_available() clears map_valid, a bit in the same bitfield word, without the msk lock held, so a scheduler setting avoid under the msk lock could race with it and corrupt neighbouring bits via the non-atomic read-modify-write. This reintroduces the 4-byte struct hole v2 removed; correctness takes priority over that optimisation. - corrected the commit message: avoid is only enforced by the default in-kernel subflow picker, not by every custom scheduler unconditionally. mptcp_sched_get_send()/_get_retrans() call the registered scheduler's own get_send()/get_retrans() directly when one is loaded, bypassing mptcp_subflow_get_send()/_get_retrans() entirely. A custom scheduler only honours avoid if it checks the field itself. Changes since v2: - rebased onto the current export tree. v2 was generated against a stale local snapshot that predated several unrelated commits touching the same bitfield region in mptcp_subflow_context; applying v2 there produced a merge conflict in protocol.h. Changes since v1: - moved avoid from a standalone bool into the existing __unused bitfield padding: the extra bool pushed lent_mem_frag onto a 4-byte-aligned boundary, adding a 4-byte hole. The bitfield slot was already reserved and unused. (Reverted in v4, see above.) - marked mptcp_subflow_set_avoid() __bpf_kfunc: no C callers, unlike set_scheduled(), so it could be dropped under LTO before resolve_btfids finds it in BTF. - mptcp_subflow_get_send() and mptcp_subflow_get_retrans() now skip a subflow with avoid set, next to the existing active-subflow check: previously nothing in the core consulted the flag. - exposed avoid via MPTCP diag as MPTCP_SUBFLOW_FLAG_AVOID, next to the existing BKUP_LOC/FULLY_ESTABLISHED flags. v3: https://lore.kernel.org/all/20260831094651.2682660-2-kalpan.jani@mpiric= software.com/ v2: https://lore.kernel.org/all/20260824102741.347492-2-kalpan.jani@mpirics= oftware.com/ v1: https://lore.kernel.org/all/20260715101148.2601045-2-kalpan.jani@mpiric= software.com/ --- include/uapi/linux/mptcp.h | 1 + net/mptcp/bpf.c | 1 + net/mptcp/diag.c | 2 ++ net/mptcp/protocol.c | 4 ++-- net/mptcp/protocol.h | 3 +++ net/mptcp/sched.c | 6 ++++++ 6 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h index 72a5d030154e..b4ca97e07445 100644 --- a/include/uapi/linux/mptcp.h +++ b/include/uapi/linux/mptcp.h @@ -22,6 +22,7 @@ #define MPTCP_SUBFLOW_FLAG_FULLY_ESTABLISHED _BITUL(6) #define MPTCP_SUBFLOW_FLAG_CONNECTED _BITUL(7) #define MPTCP_SUBFLOW_FLAG_MAPVALID _BITUL(8) +#define MPTCP_SUBFLOW_FLAG_AVOID _BITUL(9) =20 #define MPTCP_PM_CMD_GRP_NAME "mptcp_pm_cmds" #define MPTCP_PM_EV_GRP_NAME "mptcp_pm_events" diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c index 82b0ad25f700..3a229e28960d 100644 --- a/net/mptcp/bpf.c +++ b/net/mptcp/bpf.c @@ -318,6 +318,7 @@ BTF_KFUNCS_START(bpf_mptcp_common_kfunc_ids) BTF_ID_FLAGS(func, bpf_mptcp_subflow_ctx, KF_RET_NULL) BTF_ID_FLAGS(func, bpf_mptcp_subflow_tcp_sock, KF_RET_NULL) BTF_ID_FLAGS(func, mptcp_subflow_set_scheduled) +BTF_ID_FLAGS(func, mptcp_subflow_set_avoid) BTF_ID_FLAGS(func, mptcp_subflow_active) BTF_ID_FLAGS(func, mptcp_set_timeout) BTF_ID_FLAGS(func, mptcp_wnd_end) diff --git a/net/mptcp/diag.c b/net/mptcp/diag.c index 70cf9ebce833..7a2c40a63dfa 100644 --- a/net/mptcp/diag.c +++ b/net/mptcp/diag.c @@ -49,6 +49,8 @@ static int subflow_get_info(struct sock *sk, struct sk_bu= ff *skb, bool net_admin flags |=3D MPTCP_SUBFLOW_FLAG_BKUP_LOC; if (READ_ONCE(sf->fully_established)) flags |=3D MPTCP_SUBFLOW_FLAG_FULLY_ESTABLISHED; + if (READ_ONCE(sf->avoid)) + flags |=3D MPTCP_SUBFLOW_FLAG_AVOID; if (sf->conn_finished) flags |=3D MPTCP_SUBFLOW_FLAG_CONNECTED; if (sf->map_valid) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index f22d64ab1c53..56c9f1f878fb 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1645,7 +1645,7 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock= *msk) =20 trace_mptcp_subflow_get_send(subflow); ssk =3D mptcp_subflow_tcp_sock(subflow); - if (!mptcp_subflow_active(subflow)) + if (!mptcp_subflow_active(subflow) || READ_ONCE(subflow->avoid)) continue; =20 tout =3D max(tout, mptcp_timeout_from_subflow(subflow)); @@ -2563,7 +2563,7 @@ struct sock *mptcp_subflow_get_retrans(struct mptcp_s= ock *msk) mptcp_for_each_subflow(msk, subflow) { struct sock *ssk =3D mptcp_subflow_tcp_sock(subflow); =20 - if (!__mptcp_subflow_active(subflow)) + if (!__mptcp_subflow_active(subflow) || READ_ONCE(subflow->avoid)) continue; =20 /* still data outstanding at TCP level? skip this */ diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 3d250e8204d5..e6135ab87e3e 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -589,6 +589,7 @@ struct mptcp_subflow_context { __unused : 9; bool data_avail; bool scheduled; + bool avoid; /* pkt scheduler: skip subflow if possible */ bool pm_listener; /* a listener managed by the kernel PM? */ bool fully_established; /* path validated */ u32 lent_mem_frag; @@ -902,6 +903,8 @@ static inline bool __mptcp_subflow_active(struct mptcp_= subflow_context *subflow) =20 void mptcp_subflow_set_active(struct mptcp_subflow_context *subflow); =20 +void mptcp_subflow_set_avoid(struct mptcp_subflow_context *subflow, bool a= void); + bool mptcp_subflow_active(struct mptcp_subflow_context *subflow); =20 void mptcp_subflow_drop_ctx(struct sock *ssk); diff --git a/net/mptcp/sched.c b/net/mptcp/sched.c index 1e59072d478c..0ce59eb093df 100644 --- a/net/mptcp/sched.c +++ b/net/mptcp/sched.c @@ -165,6 +165,12 @@ void mptcp_subflow_set_scheduled(struct mptcp_subflow_= context *subflow, WRITE_ONCE(subflow->scheduled, scheduled); } =20 +__bpf_kfunc void mptcp_subflow_set_avoid(struct mptcp_subflow_context *sub= flow, + bool avoid) +{ + WRITE_ONCE(subflow->avoid, avoid); +} + int mptcp_sched_get_send(struct mptcp_sock *msk) { struct mptcp_subflow_context *subflow; --=20 2.43.0 From nobody Sat Sep 26 12:28:44 2026 Received: from sender4-of-o54.zoho.com (sender4-of-o54.zoho.com [136.143.188.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D43F9463 for ; Mon, 14 Sep 2026 09:20:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.54 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789377643; cv=pass; b=YSw5Tn2GGypIdpyYUYiVoOMd6dCZzUPnZag9A7kxHiHGlzQCf1/3TXDcu7FDBa4lXKPswmALegxsp/Rf6XWQZW5LOlk0uVrdGTX2kKlyR6Ce9o2mOxhhAprb0lFZFEaTiDWwEsQ420RVZF5VoCfWL7/qpNk+ZdXy/4h16f4C02Y= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789377643; c=relaxed/simple; bh=Nq0TkRQMtpSHt3bht/O9PyRC1Au8Go0AJacQrqXqilw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fL8pl1oomzQ2OHIcEqyNe9+ppN3dTLphhT6cZR2PPmEDgOVIwbl/uuQYBVWxQo8UD0PCKmrhoE/HGmly862g/+NVrPMJlPFZAiPIVD/GGcPEx658QdHLh8oK6srlhSBNXz178hQLjrB7XZd6/J9D1a11CEm04TPK3ua7JCla0aI= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=mpiricsoftware.com; spf=pass smtp.mailfrom=mpiricsoftware.com; dkim=fail (0-bit key) header.d=mpiricsoftware.com header.i=kalpan.jani@mpiricsoftware.com header.b=A349L2QO reason="key not found in DNS"; arc=pass smtp.client-ip=136.143.188.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=mpiricsoftware.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mpiricsoftware.com Authentication-Results: smtp.subspace.kernel.org; dkim=fail reason="key not found in DNS" (0-bit key) header.d=mpiricsoftware.com header.i=kalpan.jani@mpiricsoftware.com header.b="A349L2QO" ARC-Seal: i=1; a=rsa-sha256; t=1789377625; cv=none; d=zohomail.com; s=zohoarc; b=DAYCvqhgW7t3b9oksdM7cmYdi7A5NeoTXWFobh1rM7Ox60fk4/g1xARG7SXYrkTZpFoUZrrvq93tYYtIa0xLYJaelYDo+vQsr3Uu3HlqQZou2eefFiOksqltq0GB4eux+ocWbFz4foTivru7rrp6IWnGjEXfhfgBxza9sjhhsTk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1789377625; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=yLiZIAIe/97oL8SelMwuRXvFQM8gP5Paa47AcfUXRu4=; b=VVZnOxjUairgw+bc7gKvzwv4iN9/JVi46x2nlFlxhT9m4BU6tC2XtmKLCpnThY8KiaMlwybQZITal0T+iZY++0RgTE+WHaVehsI09gSi+TX/E/M36Z76l3UxPAEKhsaLdG9LLDtTrrkJL6vlL4cyqthMjbDpVfUdqLpPe44Wn0s= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=mpiricsoftware.com; spf=pass smtp.mailfrom=kalpan.jani@mpiricsoftware.com; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1789377625; s=mpiric; d=mpiricsoftware.com; i=kalpan.jani@mpiricsoftware.com; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=yLiZIAIe/97oL8SelMwuRXvFQM8gP5Paa47AcfUXRu4=; b=A349L2QOJn+G63hEJsgRlj+h/mh+2mJdpw4zV3tLRUvcAgBfdhIog5ozDjygqJL0 yhnpJw5a1KM5qDA1MkmC1p52fVbkGW6pt1iSTvJNipdbGys3DfJ4mRy++g/by5ik1wx WS4NXAXgtGh05mMW1Jbwd2D2bg23IneDzmYqedbc= Received: by smtp.zohomail.com with SMTPS id 1789377623395219.09538900257155; Mon, 14 Sep 2026 02:20:23 -0700 (PDT) From: Kalpan Jani To: mptcp@lists.linux.dev Cc: matttbe@kernel.org, martineau@kernel.org, pabeni@redhat.com, shardul.b@mpiricsoftware.com, janak@mpiric.us, kalpanjani009@gmail.com Subject: [PATCH mptcp-next v4 2/2] selftests: mptcp: bpf: exercise the subflow avoid flag across rounds Date: Mon, 14 Sep 2026 14:50:00 +0530 Message-ID: <20260914092000.1306968-3-kalpan.jani@mpiricsoftware.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260914092000.1306968-1-kalpan.jani@mpiricsoftware.com> References: <20260914092000.1306968-1-kalpan.jani@mpiricsoftware.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 X-ZohoMailClient: External Content-Type: text/plain; charset="utf-8" Add a BPF struct_ops scheduler selftest for mptcp_subflow_set_avoid(), and add the required extern declarations for it and for mptcp_subflow_active(), both declared locally in this file rather than in the shared mptcp_bpf.h. mptcp_subflow_active()'s extern was missing here even though the other BPF scheduler progs already have it (see mptcp_bpf_burst.c). "scheduled" gets cleared by the core after every single send/retrans pass, so a scheduler has to set it again every round. This test's scheduler does the opposite on purpose: it marks a subflow avoided exactly once, the first time get_send() runs, and never calls set_avoid() again after that. Every later round skips the marked subflow purely by reading back state set earlier, and the harness's own byte-count checks confirm the avoided path carried no data across the whole transfer. Per-msk state (whether avoid has been asserted yet) is kept in BPF_MAP_TYPE_SK_STORAGE keyed on msk, the same pattern mptcp_bpf_rr.c already uses, rather than in BPF global variables: globals are process-wide, not per-socket, so they would be shared and reset across any concurrent connections attached to this scheduler. Link: https://github.com/multipath-tcp/mptcp_net-next/issues/349 Suggested-by: Paolo Abeni Signed-off-by: Kalpan Jani Changes since v3: - moved the avoid_marked flag from a BPF global variable into per-msk BPF_MAP_TYPE_SK_STORAGE, mirroring mptcp_bpf_rr.c. Sashiko flagged that globals are shared across every socket the scheduler attaches to, so concurrent connections could reset each other's state. - dropped the get_send_calls counter and its assertion: it lived in the same global state and can't be read back from userspace once the counted storage is per-socket and freed on release. The test still exercises multiple scheduling rounds implicitly -- the harness's data-transfer size requires several send calls, and the addr1/addr2 byte-count checks already confirm the avoided path carried no data throughout. - reformatted the two multi-line comments to put the opening /* on its own line, per BPF subsystem comment style. Changes since v2: - rebased onto the current export tree, no functional change from v2. Changes since v1: - reworked the scheduler to set avoid once, on the first get_send() call, and rely on reading it back on every later call rather than re-asserting it: the previous version set and read the flag within the same call, which doesn't exercise standing state across scheduling rounds. - added the missing extern __ksym declaration for mptcp_subflow_active(), matching the existing pattern in mptcp_bpf_burst.c. - declared the mptcp_subflow_set_avoid() extern in this file instead of mptcp_bpf.h: checkpatch flags new externs in shared headers, and this kfunc has only one caller. v3: https://lore.kernel.org/all/20260831094651.2682660-3-kalpan.jani@mpiric= software.com/ v2: https://lore.kernel.org/all/20260824102741.347492-3-kalpan.jani@mpirics= oftware.com/ v1: https://lore.kernel.org/all/20260715101148.2601045-3-kalpan.jani@mpiric= software.com/ --- .../testing/selftests/bpf/prog_tests/mptcp.c | 15 ++++ .../selftests/bpf/progs/mptcp_bpf_avoid.c | 86 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/mptcp_bpf_avoid.c diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing= /selftests/bpf/prog_tests/mptcp.c index d77c9f8c53c7..3bff30d0d530 100644 --- a/tools/testing/selftests/bpf/prog_tests/mptcp.c +++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c @@ -18,6 +18,7 @@ #include "mptcp_bpf_rr.skel.h" #include "mptcp_bpf_red.skel.h" #include "mptcp_bpf_burst.skel.h" +#include "mptcp_bpf_avoid.skel.h" =20 #define NS_TEST "mptcp_ns" #define ADDR_1 "10.0.1.1" @@ -820,6 +821,18 @@ static void test_burst(void) mptcp_bpf_burst__destroy(skel); } =20 +static void test_avoid(void) +{ + struct mptcp_bpf_avoid *skel; + + skel =3D mptcp_bpf_avoid__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load: avoid")) + return; + + test_bpf_sched(skel->maps.avoid, "avoid", WITH_DATA, WITHOUT_DATA); + mptcp_bpf_avoid__destroy(skel); +} + void test_mptcp(void) { if (test__start_subtest("base")) @@ -842,4 +855,6 @@ void test_mptcp(void) test_red(); if (test__start_subtest("burst")) test_burst(); + if (test__start_subtest("avoid")) + test_avoid(); } diff --git a/tools/testing/selftests/bpf/progs/mptcp_bpf_avoid.c b/tools/te= sting/selftests/bpf/progs/mptcp_bpf_avoid.c new file mode 100644 index 000000000000..359b51580087 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/mptcp_bpf_avoid.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026, MPTCP. */ + +#include "mptcp_bpf.h" +#include + +extern bool mptcp_subflow_active(struct mptcp_subflow_context *subflow) __= ksym; +extern void mptcp_subflow_set_avoid(struct mptcp_subflow_context *subflow, + bool avoid) __ksym; + +char _license[] SEC("license") =3D "GPL"; + +struct mptcp_avoid_storage { + bool avoid_marked; +}; + +struct { + __uint(type, BPF_MAP_TYPE_SK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct mptcp_avoid_storage); +} mptcp_avoid_map SEC(".maps"); + +SEC("struct_ops") +void BPF_PROG(mptcp_sched_avoid_init, struct mptcp_sock *msk) +{ + bpf_sk_storage_get(&mptcp_avoid_map, msk, 0, + BPF_LOCAL_STORAGE_GET_F_CREATE); +} + +SEC("struct_ops") +void BPF_PROG(mptcp_sched_avoid_release, struct mptcp_sock *msk) +{ + bpf_sk_storage_delete(&mptcp_avoid_map, msk); +} + +SEC("struct_ops") +int BPF_PROG(bpf_avoid_get_send, struct mptcp_sock *msk) +{ + struct mptcp_subflow_context *subflow; + struct mptcp_avoid_storage *ptr; + + ptr =3D bpf_sk_storage_get(&mptcp_avoid_map, msk, 0, + BPF_LOCAL_STORAGE_GET_F_CREATE); + if (!ptr) + return -1; + + /* + * set once, on the first scheduling round, never touched again -- + * this is what makes "avoid" different from "scheduled": nothing + * here re-asserts the decision, and the core doesn't clear it + * either. + */ + if (!ptr->avoid_marked) { + int i =3D 0; + + /* bench every subflow but the first, once */ + bpf_for_each(mptcp_subflow, subflow, (struct sock *)msk) { + if (i++ > 0) + mptcp_subflow_set_avoid(subflow, true); + } + ptr->avoid_marked =3D true; + } + + /* + * every round -- including this one -- just reads back state set + * at most once above. no re-assertion, ever. + */ + bpf_for_each(mptcp_subflow, subflow, (struct sock *)msk) { + if (subflow->avoid) + continue; + if (mptcp_subflow_active(subflow)) { + mptcp_subflow_set_scheduled(subflow, true); + break; + } + } + return 0; +} + +SEC(".struct_ops.link") +struct mptcp_sched_ops avoid =3D { + .init =3D (void *)mptcp_sched_avoid_init, + .release =3D (void *)mptcp_sched_avoid_release, + .get_send =3D (void *)bpf_avoid_get_send, + .name =3D "bpf_avoid", +}; --=20 2.43.0