From nobody Sun Mar 22 09:34:07 2026 Received: from mail-oa1-f45.google.com (mail-oa1-f45.google.com [209.85.160.45]) (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 D4E5538E12A for ; Sat, 14 Mar 2026 20:15:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773519307; cv=none; b=BgFV5ZYePpiTFVa3qk8RiSzaf2kZIop38w8ocamut3SEDD+8ViehttbLe3oL2LYzNvjwmoG8TDy0KVS1oTYQCdAFIXQ2LmjqLeoRLC6sxvLES0t0oUUsXJhpxUdF71AdYA5RV4FDslT/HojRAX38ioUD18VdbzSyJ4cOf863gVY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773519307; c=relaxed/simple; bh=6JX3Yctd8bDnEGsPO4CjvDQJ7QxYVDLwhFVJibho2l4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JtSMwUCqh450y8TJ1O15kZHHjKFARFQS6QSDJzwX7N6Os2TtOt1q+S3Dgees4dOz0X5FJkiDtu+r/Q7gMM0CcgV6ycNMnnOrJ/SYnOIqq3+rfpNNTX3YBtlMJgkZg7BISjAz0uDqwxYX2a3lqA6jANvY6fNwjlnXsRwjKvX3QAM= 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=XGOBVJqS; arc=none smtp.client-ip=209.85.160.45 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="XGOBVJqS" Received: by mail-oa1-f45.google.com with SMTP id 586e51a60fabf-417571c6083so1955135fac.2 for ; Sat, 14 Mar 2026 13:15:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773519303; x=1774124103; 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; bh=5cgfXJCeRN9gp7HY5pu2xM6oON8ruZZKeF5hMpMsDXs=; b=XGOBVJqSDKew2eGmfYE/7xIafCiEVLUdrEOoBwDpp0OhX/x4DvdRTQ9hdXeVSljPue cT/q9aajQYD6qH/FgTKwnY/6KKI+I4t0FG3SeC2+/Wqk1Z5WhPnb29U4e9YSd+V3r9Jy vpt+bCz2k0jlxsQJ35xBSd4BnhPREPXDOT9Be4zgret1RnoP5IkOmpGEem3VwMz4AkyI qx/H+a0uWKeTmGGiuEYWX8jMt4tZm2oTOXIuEF8/tO9Rd0Ef+KbXB+U05DstzHl4acd8 L+EOtmjynzEfWfAC2jRyqTw+PRzRbjoGZDfOCpgiqzl+pfen5t24dqYk6wO1MWIH/bQb MNdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773519303; x=1774124103; 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; bh=5cgfXJCeRN9gp7HY5pu2xM6oON8ruZZKeF5hMpMsDXs=; b=CsZAlJQMqArbJo1aeMEidWdd3hNhEQBzcJgFkZnyIDErPPil1MV3QXoV6TYcft91In V0A++Gb3a0mgYiEvoUZBKCxKTXxFjZRbSkM82+ZNvPsPJI8tHjuluQxoUb67pdshX8kn 2Qi8A6xm8VJ/My2f06HVNuujXdI259T8FIAxaLatrbXXOxoGwC7CkXqyatHCIetzDnoB zKc5wfXzllFdXsLdoJihmPjjv7a2BDYC8+nocESat40GnIyW0As/DnENg/xnX5DoF5yc Y1H9L57S34ymBKDENUgbtJd06AUTfSS8DwdGRhDiWf69FfQxc7IUsKs1P2oLZu8ZTFl8 i8Lw== X-Forwarded-Encrypted: i=1; AJvYcCVCiG8fXXWoez+YRp/Y08O3QDlnzY+9yIfgTPDTDGFBBcX4EUnhdnd1Umo7dRZK3O4g/dgDrw==@lists.linux.dev X-Gm-Message-State: AOJu0YwPE9qpnFW577ZltoQZ0IzFRbPOyXd1j6VPQY69MMcn0LoFnBLh K6mjCNA3WcnQpyfIhDqjKl8ioshq2U+CU7hjxPNN+RHml16z7BQOzSWm X-Gm-Gg: ATEYQzyEy7PcT3SKThGEjhNu3oo+McKWuYKIKos7ImGZXNGqxxu4mrO2dBandh5oXhA z/DxfxIed7msrOErQCBT5gnVFefj42H2AGL1qPntoAwhnDcNr9NibEFaVL82CeWVtEE4NUt44D1 zMZoEeYZNQnNuZTO0a2BFWF/Lf0U/eILA6tTds6F3w/q5JzpR8d1o18+PS3Xm8OJf9NwHrogaOC WT4vwLs2lXeIQRquC8ix2sdpJl4GET/tjV/9az38p/KY52rGEvxAMmxP4uiHG3MClX9A3QuowlP CG4eEu5OViB0UQJdoaNnUYl9JSQNoFmzN9c/7gFuN6f5Ptwz+WEKzCG20b0wM6mKi6b8AgMjHc8 /oXNoSVPUtnbshdA5k2Iw2APeM87br8cKezHnG/L8v0Itdc2aZbxJTNHBOTKpCJfxxCD4lQNhNX SE28REYMnQ0R5mJLhFULtxO/soy/LLZ5GwbGjq3A3six0HbtykAFmUGg9G07YiZWa1yIxUsW3nY /LoosYwtOrZwWmEmmL9BrzAYZy0LFm9voin3RO/ X-Received: by 2002:a05:6870:7d16:b0:417:4bd3:f5f5 with SMTP id 586e51a60fabf-417b93e2407mr4714434fac.37.1773519302604; Sat, 14 Mar 2026 13:15:02 -0700 (PDT) Received: from Atwell-Laptop.. (108-212-132-20.lightspeed.irvnca.sbcglobal.net. [108.212.132.20]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4177e5e8185sm11914165fac.12.2026.03.14.13.15.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 14 Mar 2026 13:15:02 -0700 (PDT) From: atwellwea@gmail.com To: netdev@vger.kernel.org, davem@davemloft.net, kuba@kernel.org, pabeni@redhat.com, edumazet@google.com, ncardwell@google.com Cc: linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-trace-kernel@vger.kernel.org, mptcp@lists.linux.dev, dsahern@kernel.org, horms@kernel.org, kuniyu@google.com, andrew+netdev@lunn.ch, willemdebruijn.kernel@gmail.com, jasowang@redhat.com, skhan@linuxfoundation.org, corbet@lwn.net, matttbe@kernel.org, martineau@kernel.org, geliang@kernel.org, rostedt@goodmis.org, mhiramat@kernel.org, mathieu.desnoyers@efficios.com, 0x7f454c46@gmail.com Subject: [PATCH net-next v2 13/14] netdevsim: add peer RX truesize support for selftests Date: Sat, 14 Mar 2026 14:13:47 -0600 Message-ID: <20260314201348.1786972-14-atwellwea@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260314201348.1786972-1-atwellwea@gmail.com> References: <20260314201348.1786972-1-atwellwea@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" From: Wesley Atwell Add a debugfs-controlled peer RX truesize knob to netdevsim, inflate the forwarded skb only on the peer RX side, and cover the resulting socket memory-accounting behavior with a dedicated selftest. This keeps the synthetic cost out of the sender-side skb geometry while giving the selftests a second runtime vehicle for the receive-memory accounting exercised by the TCP rwnd work. Signed-off-by: Wesley Atwell --- drivers/net/netdevsim/netdev.c | 145 +++++- drivers/net/netdevsim/netdevsim.h | 4 + .../selftests/drivers/net/netdevsim/Makefile | 1 + .../drivers/net/netdevsim/peer-rx-truesize.sh | 426 ++++++++++++++++++ 4 files changed, 575 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/drivers/net/netdevsim/peer-rx-t= ruesize.sh diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index 5ec028a00c62..22238df79b6a 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -17,8 +17,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -37,6 +39,91 @@ MODULE_IMPORT_NS("NETDEV_INTERNAL"); =20 #define NSIM_RING_SIZE 256 =20 +struct nsim_rx_truesize { + refcount_t refs; + u32 value; +}; + +static struct nsim_rx_truesize * +nsim_rx_truesize_get(struct nsim_rx_truesize *rx_truesize) +{ + if (!rx_truesize) + return NULL; + + if (!refcount_inc_not_zero(&rx_truesize->refs)) + return NULL; + + return rx_truesize; +} + +static void nsim_rx_truesize_put(struct nsim_rx_truesize *rx_truesize) +{ + if (!rx_truesize) + return; + + if (refcount_dec_and_test(&rx_truesize->refs)) + kfree(rx_truesize); +} + +static ssize_t nsim_rx_truesize_read(struct file *file, char __user *user_= buf, + size_t count, loff_t *ppos) +{ + struct nsim_rx_truesize *rx_truesize =3D file->private_data; + char buf[24]; + int len; + + len =3D scnprintf(buf, sizeof(buf), "%u\n", + READ_ONCE(rx_truesize->value)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t nsim_rx_truesize_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct nsim_rx_truesize *rx_truesize =3D file->private_data; + u32 value; + int err; + + err =3D kstrtou32_from_user(user_buf, count, 0, &value); + if (err) + return err; + + WRITE_ONCE(rx_truesize->value, value); + + return count; +} + +static int nsim_rx_truesize_open(struct inode *inode, struct file *file) +{ + struct nsim_rx_truesize *rx_truesize; + + rx_truesize =3D nsim_rx_truesize_get(inode->i_private); + if (!rx_truesize) + return -ENODEV; + + file->private_data =3D rx_truesize; + + return nonseekable_open(inode, file); +} + +static int nsim_rx_truesize_release(struct inode *inode, struct file *file) +{ + nsim_rx_truesize_put(file->private_data); + + return 0; +} + +static const struct file_operations nsim_rx_truesize_fops =3D { + .owner =3D THIS_MODULE, + .open =3D nsim_rx_truesize_open, + .read =3D nsim_rx_truesize_read, + .write =3D nsim_rx_truesize_write, + .release =3D nsim_rx_truesize_release, + .llseek =3D noop_llseek, +}; + static void nsim_start_peer_tx_queue(struct net_device *dev, struct nsim_r= q *rq) { struct netdevsim *ns =3D netdev_priv(dev); @@ -117,6 +204,28 @@ static int nsim_forward_skb(struct net_device *tx_dev, return nsim_napi_rx(tx_dev, rx_dev, rq, skb); } =20 +/* Tests can inflate peer RX skb->truesize to exercise receiver-side TCP + * accounting under scaling-ratio drift without perturbing sender-side skb + * ownership. + */ +static void nsim_rx_update_truesize(struct sk_buff *skb, u32 extra) +{ + unsigned int truesize; + + if (!extra) + return; + + if (check_add_overflow(skb->truesize, extra, &truesize)) + truesize =3D UINT_MAX; + + skb->truesize =3D truesize; +} + +static u32 nsim_rx_extra_truesize(const struct netdevsim *ns) +{ + return READ_ONCE(ns->rx_truesize->value); +} + static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device = *dev) { struct netdevsim *ns =3D netdev_priv(dev); @@ -125,7 +234,9 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb,= struct net_device *dev) unsigned int len =3D skb->len; struct netdevsim *peer_ns; struct netdev_config *cfg; + struct sk_buff *nskb; struct nsim_rq *rq; + u32 extra; int rxq; int dr; =20 @@ -160,7 +271,24 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb= , struct net_device *dev) cfg->hds_thresh > len))) skb_linearize(skb); =20 + extra =3D nsim_rx_extra_truesize(peer_ns); skb_tx_timestamp(skb); + if (extra) { + /* Clone before inflating truesize so only the peer RX path sees + * the synthetic cost; sender-side skb accounting stays put. + */ + nskb =3D skb_clone(skb, GFP_ATOMIC); + if (!nskb) { + if (psp_ext) + __skb_ext_put(psp_ext); + goto out_drop_free; + } + + consume_skb(skb); + skb =3D nskb; + nsim_rx_update_truesize(skb, extra); + } + if (unlikely(nsim_forward_skb(dev, peer_dev, skb, rq, psp_ext) =3D=3D NET_RX_DROP)) goto out_drop_cnt; @@ -1121,6 +1249,7 @@ struct netdevsim *nsim_create(struct nsim_dev *nsim_d= ev, u8 perm_addr[ETH_ALEN]) { struct net_device *dev; + struct nsim_rx_truesize *rx_truesize; struct netdevsim *ns; int err; =20 @@ -1140,6 +1269,13 @@ struct netdevsim *nsim_create(struct nsim_dev *nsim_= dev, ns->nsim_bus_dev =3D nsim_dev->nsim_bus_dev; SET_NETDEV_DEV(dev, &ns->nsim_bus_dev->dev); SET_NETDEV_DEVLINK_PORT(dev, &nsim_dev_port->devlink_port); + rx_truesize =3D kzalloc_obj(*rx_truesize); + if (!rx_truesize) { + err =3D -ENOMEM; + goto err_free_netdev; + } + refcount_set(&rx_truesize->refs, 1); + ns->rx_truesize =3D rx_truesize; nsim_ethtool_init(ns); if (nsim_dev_port_is_pf(nsim_dev_port)) err =3D nsim_init_netdevsim(ns); @@ -1153,21 +1289,27 @@ struct netdevsim *nsim_create(struct nsim_dev *nsim= _dev, ns->qr_dfs =3D debugfs_create_file("queue_reset", 0200, nsim_dev_port->ddir, ns, &nsim_qreset_fops); + ns->rx_truesize_dfs =3D debugfs_create_file("rx_extra_truesize", 0600, + nsim_dev_port->ddir, + ns->rx_truesize, + &nsim_rx_truesize_fops); return ns; =20 err_free_netdev: + nsim_rx_truesize_put(ns->rx_truesize); free_netdev(dev); return ERR_PTR(err); } =20 void nsim_destroy(struct netdevsim *ns) { + struct nsim_rx_truesize *rx_truesize =3D ns->rx_truesize; struct net_device *dev =3D ns->netdev; struct netdevsim *peer; =20 + debugfs_remove(ns->rx_truesize_dfs); debugfs_remove(ns->qr_dfs); debugfs_remove(ns->pp_dfs); - if (ns->nb.notifier_call) unregister_netdevice_notifier_dev_net(ns->netdev, &ns->nb, &ns->nn); @@ -1198,6 +1340,7 @@ void nsim_destroy(struct netdevsim *ns) } =20 free_netdev(dev); + nsim_rx_truesize_put(rx_truesize); } =20 bool netdev_is_nsim(struct net_device *dev) diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netd= evsim.h index f767fc8a7505..972ad274060e 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -75,6 +75,8 @@ struct nsim_macsec { u8 nsim_secy_count; }; =20 +struct nsim_rx_truesize; + struct nsim_ethtool_pauseparam { bool rx; bool tx; @@ -144,6 +146,8 @@ struct netdevsim { } udp_ports; =20 struct page *page; + struct nsim_rx_truesize *rx_truesize; + struct dentry *rx_truesize_dfs; struct dentry *pp_dfs; struct dentry *qr_dfs; =20 diff --git a/tools/testing/selftests/drivers/net/netdevsim/Makefile b/tools= /testing/selftests/drivers/net/netdevsim/Makefile index 1a228c5430f5..9e9e48d5913b 100644 --- a/tools/testing/selftests/drivers/net/netdevsim/Makefile +++ b/tools/testing/selftests/drivers/net/netdevsim/Makefile @@ -14,6 +14,7 @@ TEST_PROGS :=3D \ macsec-offload.sh \ nexthop.sh \ peer.sh \ + peer-rx-truesize.sh \ psample.sh \ tc-mq-visibility.sh \ udp_tunnel_nic.sh \ diff --git a/tools/testing/selftests/drivers/net/netdevsim/peer-rx-truesize= .sh b/tools/testing/selftests/drivers/net/netdevsim/peer-rx-truesize.sh new file mode 100755 index 000000000000..6d1101d20847 --- /dev/null +++ b/tools/testing/selftests/drivers/net/netdevsim/peer-rx-truesize.sh @@ -0,0 +1,426 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only + +set -euo pipefail + +lib_dir=3D$(dirname "$0")/../../../net +source "$lib_dir"/lib.sh + +NSIM_SRV_ID=3D$((1024 + RANDOM % 1024)) +NSIM_CLI_ID=3D$((2048 + RANDOM % 1024)) +NSIM_SYS_LINK=3D/sys/bus/netdevsim/link_device +SERVER_ADDR=3D192.0.2.1 +CLIENT_ADDR=3D192.0.2.2 +RMEM_PORT=3D12345 +WARM_PORT=3D12346 +RMEM_QUEUED_LEN=3D65000 +RMEM_INFLATED_LEN=3D65000 +RMEM_SMALL_EXTRA=3D4096 +RMEM_LARGE_EXTRA=3D65536 +WARM_WARMUP_ROUNDS=3D16 +WARM_WARMUP_LEN=3D65000 +WARM_QUEUED_LEN=3D62000 +WARM_INFLATED_LEN=3D65000 +WARM_EXTRA=3D65536 + +srv_dev=3D +cli_dev=3D +srv_pid=3D +cli_pid=3D +srv_fd=3D +cli_fd=3D +stage_dir=3D +CASE_BASE_METRIC=3D +CASE_FINAL_METRIC=3D + +cleanup() +{ + local rc=3D$? + + if [ -n "${srv_pid:-}" ]; then + kill "${srv_pid}" 2>/dev/null || true + wait "${srv_pid}" 2>/dev/null || true + fi + + if [ -n "${cli_pid:-}" ]; then + kill "${cli_pid}" 2>/dev/null || true + wait "${cli_pid}" 2>/dev/null || true + fi + + if [ -n "${srv_fd:-}" ]; then + eval "exec ${srv_fd}<&-" + fi + + if [ -n "${cli_fd:-}" ]; then + eval "exec ${cli_fd}<&-" + fi + + if [ -d "${stage_dir:-}" ]; then + rm -rf "${stage_dir}" + fi + + cleanup_netdevsim "${NSIM_SRV_ID}" 2>/dev/null || true + cleanup_netdevsim "${NSIM_CLI_ID}" 2>/dev/null || true + cleanup_ns "${SRV:-}" "${CLI:-}" 2>/dev/null || true + + exit "${rc}" +} + +trap cleanup EXIT + +ensure_debugfs() +{ + if mount | grep -q 'on /sys/kernel/debug type debugfs'; then + return 0 + fi + + if ! mount -t debugfs none /sys/kernel/debug >/dev/null 2>&1; then + echo "SKIP: failed to mount debugfs" + exit "${ksft_skip}" + fi +} + +ensure_netdevsim() +{ + if [ -w /sys/bus/netdevsim/new_device ]; then + return 0 + fi + + if ! modprobe netdevsim >/dev/null 2>&1; then + echo "SKIP: no netdevsim support" + exit "${ksft_skip}" + fi +} + +create_nsim() +{ + local id=3D"$1" + local ns=3D"$2" + local addr=3D"$3" + local dev + + echo "${id}" | ip netns exec "${ns}" tee /sys/bus/netdevsim/new_device >/= dev/null + udevadm settle + + dev=3D$(ip netns exec "${ns}" ls /sys/bus/netdevsim/devices/netdevsim"${i= d}"/net) + ip -netns "${ns}" link set dev "${dev}" name "nsim${id}" + ip -netns "${ns}" addr add "${addr}/24" dev "nsim${id}" + ip -netns "${ns}" link set dev "nsim${id}" up + + echo "nsim${id}" +} + +link_nsim_peers() +{ + local srv_ifindex + local cli_ifindex + + eval "exec {srv_fd} "${NSIM_SYS_LI= NK}" +} + +wait_for_file() +{ + local path=3D"$1" + local i + + for i in $(seq 100); do + if [ -e "${path}" ]; then + return 0 + fi + sleep 0.1 + done + + return 1 +} + +server_python=3D' +import array +import fcntl +import os +import socket +import struct +import sys +import time + +SO_MEMINFO =3D 55 +SK_MEMINFO_RMEM_ALLOC =3D 0 +TCP_MAXSEG =3D getattr(socket, "TCP_MAXSEG", 2) +FIONREAD =3D 0x541B +POLL_INTERVAL =3D 0.01 +POLL_TIMEOUT =3D 20.0 + +(mode, host, port, warmup_rounds, warmup_len, queued_len, inflated_len, + ready_file, result_file) =3D sys.argv[1:] +port =3D int(port) +warmup_rounds =3D int(warmup_rounds) +warmup_len =3D int(warmup_len) +queued_len =3D int(queued_len) +inflated_len =3D int(inflated_len) + +def queued_bytes(sock): + buf =3D array.array("I", [0]) + fcntl.ioctl(sock.fileno(), FIONREAD, buf, True) + return buf[0] + +def wait_for_queued(sock, target): + deadline =3D time.time() + POLL_TIMEOUT + while time.time() < deadline: + if queued_bytes(sock) >=3D target: + return + time.sleep(POLL_INTERVAL) + raise SystemExit(f"timed out waiting for {target} queued bytes") + +def meminfo(sock): + raw =3D sock.getsockopt(socket.SOL_SOCKET, SO_MEMINFO, 9 * 4) + return struct.unpack("=3D9I", raw) + +def wait_for_growth(sock, idx, base): + deadline =3D time.time() + POLL_TIMEOUT + while time.time() < deadline: + cur =3D meminfo(sock)[idx] + if cur > base: + return cur + time.sleep(POLL_INTERVAL) + raise SystemExit(f"timed out waiting for SO_MEMINFO[{idx}] growth from= {base}") + +def write_metric(path, value): + with open(path, "w", encoding=3D"ascii") as fp: + fp.write(f"{value}\n") + +def recv_all(sock, total): + remaining =3D total + while remaining: + chunk =3D sock.recv(min(65536, remaining)) + if not chunk: + raise SystemExit("unexpected EOF while draining receive data") + remaining -=3D len(chunk) + +listener =3D socket.socket(socket.AF_INET, socket.SOCK_STREAM) +listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +listener.setsockopt(socket.IPPROTO_TCP, TCP_MAXSEG, 1000) +listener.bind((host, port)) +listener.listen(1) +conn, _ =3D listener.accept() + +for _ in range(warmup_rounds): + recv_all(conn, warmup_len) + +if mode =3D=3D "rmem_alloc": + wait_for_queued(conn, queued_len) + base_metric =3D meminfo(conn)[SK_MEMINFO_RMEM_ALLOC] + write_metric(ready_file, base_metric) + + recv_all(conn, queued_len) + wait_for_queued(conn, inflated_len) + grown_metric =3D meminfo(conn)[SK_MEMINFO_RMEM_ALLOC] + write_metric(result_file, grown_metric) +elif mode =3D=3D "rmem_alloc_warm": + wait_for_queued(conn, queued_len) + base_metric =3D meminfo(conn)[SK_MEMINFO_RMEM_ALLOC] + write_metric(ready_file, base_metric) + + wait_for_queued(conn, queued_len + 1) + grown_metric =3D wait_for_growth(conn, SK_MEMINFO_RMEM_ALLOC, base_met= ric) + write_metric(result_file, grown_metric) +elif mode =3D=3D "rmem_alloc_growth": + # The growth cases compare against a live socket metric, so wait for + # observed growth instead of trusting one instantaneous post-queue sam= ple. + wait_for_queued(conn, queued_len) + base_metric =3D meminfo(conn)[SK_MEMINFO_RMEM_ALLOC] + write_metric(ready_file, base_metric) + + recv_all(conn, queued_len) + wait_for_queued(conn, inflated_len) + grown_metric =3D wait_for_growth(conn, SK_MEMINFO_RMEM_ALLOC, base_met= ric) + write_metric(result_file, grown_metric) +else: + raise SystemExit(f"unknown mode: {mode}") +' + +client_python=3D' +import os +import socket +import sys +import time + +POLL_INTERVAL =3D 0.01 +POLL_TIMEOUT =3D 20.0 + +host, port, warmup_rounds, warmup_len, queued_len, inflated_len, gate_file= =3D sys.argv[1:] +port =3D int(port) +warmup_rounds =3D int(warmup_rounds) +warmup_len =3D int(warmup_len) +queued_len =3D int(queued_len) +inflated_len =3D int(inflated_len) + +def send_all(sock, total): + payload =3D b"a" * min(total, 65536) + left =3D total + while left: + chunk =3D payload[: min(len(payload), left)] + sent =3D sock.send(chunk) + if sent <=3D 0: + raise SystemExit("short send") + left -=3D sent + +def wait_for_file(path): + deadline =3D time.time() + POLL_TIMEOUT + while time.time() < deadline: + if os.path.exists(path): + return + time.sleep(POLL_INTERVAL) + raise SystemExit(f"timed out waiting for {path}") + +cli =3D socket.socket(socket.AF_INET, socket.SOCK_STREAM) +cli.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1000) +cli.connect((host, port)) +for _ in range(warmup_rounds): + send_all(cli, warmup_len) +send_all(cli, queued_len) +wait_for_file(gate_file) +send_all(cli, inflated_len) +cli.close() +' + +read_metric() +{ + local path=3D"$1" + local value + + if ! read -r value < "${path}"; then + echo "FAIL: unable to read metric from ${path}" + exit "${ksft_fail}" + fi + + printf '%s\n' "${value}" +} + +run_case() +{ + local case_id=3D"$1" + local mode=3D"$2" + local port=3D"$3" + local warmups=3D"$4" + local warmup_len=3D"$5" + local queued_len=3D"$6" + local inflated_len=3D"$7" + local extra=3D"$8" + local label=3D"$9" + local ready_file=3D"${stage_dir}/${case_id}.ready" + local result_file=3D"${stage_dir}/${case_id}.result" + local gate_file=3D"${stage_dir}/${case_id}.gate" + + rm -f "${ready_file}" "${result_file}" "${gate_file}" + echo 0 > "${dfs_file}" + + ip netns exec "${SRV}" python3 - "${mode}" "${SERVER_ADDR}" "${port}" \ + "${warmups}" "${warmup_len}" "${queued_len}" "${inflated_len}" \ + "${ready_file}" "${result_file}" < "${dfs_file}" + touch "${gate_file}" + + wait "${cli_pid}" + cli_pid=3D + wait "${srv_pid}" + srv_pid=3D + + CASE_BASE_METRIC=3D$(read_metric "${ready_file}") + CASE_FINAL_METRIC=3D$(read_metric "${result_file}") + + echo "PASS: ${label}" +} + +# This test only proves that injected truesize reaches socket memory +# accounting. Packetdrill covers the sender-visible rwnd accept/drop logic. + +assert_no_growth() +{ + local label=3D"$1" + + if [ "${CASE_FINAL_METRIC}" -gt "${CASE_BASE_METRIC}" ]; then + echo "FAIL: ${label}: metric grew unexpectedly:" \ + "base=3D${CASE_BASE_METRIC}" \ + "after=3D${CASE_FINAL_METRIC}" + exit "${ksft_fail}" + fi +} + +assert_growth() +{ + local label=3D"$1" + + if [ "${CASE_FINAL_METRIC}" -le "${CASE_BASE_METRIC}" ]; then + echo "FAIL: ${label}: metric did not grow:" \ + "base=3D${CASE_BASE_METRIC}" \ + "after=3D${CASE_FINAL_METRIC}" + exit "${ksft_fail}" + fi +} + +ensure_debugfs +ensure_netdevsim +set +u +setup_ns SRV CLI +set -u + +srv_dev=3D$(create_nsim "${NSIM_SRV_ID}" "${SRV}" "${SERVER_ADDR}") +cli_dev=3D$(create_nsim "${NSIM_CLI_ID}" "${CLI}" "${CLIENT_ADDR}") +link_nsim_peers + +ip netns exec "${SRV}" sysctl -wq net.ipv4.tcp_moderate_rcvbuf=3D0 + +stage_dir=3D$(mktemp -d) +dfs_file=3D"/sys/kernel/debug/netdevsim/netdevsim${NSIM_SRV_ID}/ports/0/rx= _extra_truesize" + +run_case "rmem_noop" "rmem_alloc" "${RMEM_PORT}" 0 0 \ + "${RMEM_QUEUED_LEN}" "${RMEM_INFLATED_LEN}" 0 \ + "peer rx truesize zero no-op" +assert_no_growth "peer rx truesize zero no-op" + +run_case "rmem_small" "rmem_alloc_growth" "${RMEM_PORT}" 0 0 \ + "${RMEM_QUEUED_LEN}" "${RMEM_INFLATED_LEN}" "${RMEM_SMALL_EXTRA}" \ + "peer rx truesize small rmem_alloc" +assert_growth "peer rx truesize small rmem_alloc" +small_delta=3D$((CASE_FINAL_METRIC - CASE_BASE_METRIC)) + +run_case "rmem_large" "rmem_alloc_growth" "${RMEM_PORT}" 0 0 \ + "${RMEM_QUEUED_LEN}" "${RMEM_INFLATED_LEN}" "${RMEM_LARGE_EXTRA}" \ + "peer rx truesize large rmem_alloc" +assert_growth "peer rx truesize large rmem_alloc" +large_delta=3D$((CASE_FINAL_METRIC - CASE_BASE_METRIC)) + +if [ "${large_delta}" -le "${small_delta}" ]; then + echo "FAIL: peer rx truesize stepped rmem_alloc:" \ + "small_delta=3D${small_delta}" \ + "large_delta=3D${large_delta}" + exit "${ksft_fail}" +fi + +run_case "rmem_warm" "rmem_alloc_warm" "${WARM_PORT}" "${WARM_WARMUP_ROUND= S}" "${WARM_WARMUP_LEN}" \ + "${WARM_QUEUED_LEN}" "${WARM_INFLATED_LEN}" "${WARM_EXTRA}" \ + "peer rx truesize warm rmem_alloc" +assert_growth "peer rx truesize warm rmem_alloc" --=20 2.43.0