tools/testing/selftests/net/mptcp/mptcp_sockopt.c | 60 +++++++++++++---------- 1 file changed, 34 insertions(+), 26 deletions(-)
From: Quanye Yang <quanyeyang@proton.me>
process_one_client() used a single read() and write() before waiting
for hangup. A SOCK_STREAM read() can return a short count when the
payload is segmented, so the server echoed only part of the data and
then blocked on FIN.
The client keeps reading until it has the full length and only then
closes the socket. The two sides wait on each other and the test
hangs until SIGALRM.
The server does not know the payload length, so echo each chunk until
read() returns 0. Retry short writes, and accumulate nread/nwritten for
the existing sockopt checks. Cast those totals to uint64_t when passing
them to PRIu64 so the xerror() format is correct on 32-bit.
Fixes: ce9979129a0b ("selftests: mptcp: add mptcp getsockopt test cases")
Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/639
Signed-off-by: Quanye Yang <quanyeyang@proton.me>
---
Changes in v3:
- rebase on latest mptcp/export
- cast nread/nwritten to uint64_t for PRIu64 in xerror()
- Link to v2: https://patch.msgid.link/20261023-fix-mptcp-issue-639-v2-1-14a3d3746d2d@proton.me
Changes in v2:
- target mptcp-next instead of mptcp-net
- echo until hangup with goto; retry short writes; use nread/nwritten
- Link to v1: https://patch.msgid.link/20261017-fix-mptcp-issue-639-v1-1-0554d07ab8f8@proton.me
---
tools/testing/selftests/net/mptcp/mptcp_sockopt.c | 60 +++++++++++++----------
1 file changed, 34 insertions(+), 26 deletions(-)
diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
index b8158a1198eb..367ff12bf6c7 100644
--- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
+++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
@@ -640,7 +640,8 @@ static void connect_one_server(int fd, int pipefd)
static void process_one_client(int fd, int pipefd)
{
- ssize_t ret, ret2, ret3;
+ size_t nread = 0, nwritten = 0;
+ ssize_t ret, n, w, off;
struct so_state s;
char buf[4096];
@@ -650,46 +651,53 @@ static void process_one_client(int fd, int pipefd)
ret = write(pipefd, "xmit", 4);
assert(ret == 4);
- ret = read(fd, buf, sizeof(buf));
- if (ret < 0)
+read_more:
+ n = read(fd, buf, sizeof(buf));
+ if (n < 0)
die_perror("read");
+ if (n == 0)
+ goto hangup;
+
+ for (off = 0; off < n; off += w) {
+ w = write(fd, buf + off, n - off);
+ if (w < 0)
+ die_perror("write");
+ if (w == 0)
+ xerror("write returned 0");
+ }
+ nread += n;
+ nwritten += n;
+ goto read_more;
- assert(s.mptcpi_rcv_delta <= (uint64_t)ret);
+hangup:
+ assert(s.mptcpi_rcv_delta <= nread);
if (s.tcpi_rcv_delta)
- assert(s.tcpi_rcv_delta == (uint64_t)ret);
-
- ret2 = write(fd, buf, ret);
- if (ret2 < 0)
- die_perror("write");
-
- /* wait for hangup */
- ret3 = read(fd, buf, 1);
- if (ret3 != 0)
- xerror("expected EOF, got %lu", ret3);
+ assert(s.tcpi_rcv_delta <= nread);
- do_getsockopts(&s, fd, ret, ret2);
- if (s.mptcpi_rcv_delta != (uint64_t)ret + 1)
+ do_getsockopts(&s, fd, nread, nwritten);
+ if (s.mptcpi_rcv_delta != (uint64_t)nread + 1)
xerror("mptcpi_rcv_delta %" PRIu64 ", expect %" PRIu64 ", diff %" PRId64,
- s.mptcpi_rcv_delta, ret + 1, s.mptcpi_rcv_delta - (ret + 1));
+ s.mptcpi_rcv_delta, (uint64_t)nread + 1,
+ s.mptcpi_rcv_delta - ((uint64_t)nread + 1));
/* be nice when running on top of older kernel */
if (s.pkt_stats_avail) {
- if (s.last_sample.mptcpi_bytes_sent != ret2)
+ if (s.last_sample.mptcpi_bytes_sent != nwritten)
xerror("mptcpi_bytes_sent %" PRIu64 ", expect %" PRIu64
", diff %" PRId64,
- s.last_sample.mptcpi_bytes_sent, ret2,
- s.last_sample.mptcpi_bytes_sent - ret2);
- if (s.last_sample.mptcpi_bytes_received != ret)
+ s.last_sample.mptcpi_bytes_sent, (uint64_t)nwritten,
+ s.last_sample.mptcpi_bytes_sent - (uint64_t)nwritten);
+ if (s.last_sample.mptcpi_bytes_received != nread)
xerror("mptcpi_bytes_received %" PRIu64 ", expect %" PRIu64
", diff %" PRId64,
- s.last_sample.mptcpi_bytes_received, ret,
- s.last_sample.mptcpi_bytes_received - ret);
- if (s.last_sample.mptcpi_bytes_acked != ret)
+ s.last_sample.mptcpi_bytes_received, (uint64_t)nread,
+ s.last_sample.mptcpi_bytes_received - (uint64_t)nread);
+ if (s.last_sample.mptcpi_bytes_acked != nread)
xerror("mptcpi_bytes_acked %" PRIu64 ", expect %" PRIu64
", diff %" PRId64,
- s.last_sample.mptcpi_bytes_acked, ret,
- s.last_sample.mptcpi_bytes_acked - ret);
+ s.last_sample.mptcpi_bytes_acked, (uint64_t)nread,
+ s.last_sample.mptcpi_bytes_acked - (uint64_t)nread);
}
close(fd);
---
base-commit: 77f43f9f764392db19087a46499b301ae629f437
change-id: 20261017-fix-mptcp-issue-639-96339a3d921a
Best regards,
--
Quanye Yang <quanyeyang@proton.me>
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message gives the wrong cause for the failure it
fixes.
--
selftests: mptcp: sockopt: echo until EOF on partial reads
This commit changes process_one_client() in mptcp_sockopt.c to echo data
in a loop until read() returns 0 and to retry short writes. The totals
are kept in nread/nwritten for the existing sockopt checks. Those totals
are also cast to uint64_t for the PRIu64 format strings.
Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/639
> process_one_client() used a single read() and write() before waiting
> for hangup. A SOCK_STREAM read() can return a short count when the
> payload is segmented, so the server echoed only part of the data and
> then blocked on FIN.
>
> The client keeps reading until it has the full length and only then
> closes the socket. The two sides wait on each other and the test
> hangs until SIGALRM.
[Severity: Medium]
Can a short read in the old process_one_client() actually cause this
hang?
connect_one_server() sends all len bytes with a single write(). len is
at most 4094, so it fits in the server's 4096 byte buffer, and a short
write is treated as an error:
ret = write(fd, buf, len);
if (ret < 0)
die_perror("write");
if (ret != (ssize_t)len)
xerror("short write");
So if the server's first read() returns fewer than len bytes, the rest
of the payload is already queued for the server. In that case the old
1-byte "wait for hangup" read would return 1 instead of blocking:
/* wait for hangup */
ret3 = read(fd, buf, 1);
if (ret3 != 0)
xerror("expected EOF, got %lu", ret3);
The server would then exit through xerror(), and the client's read loop
in connect_one_server() would hit "unexpected EOF" or a reset. That is a
fast failure, not a hang until SIGALRM.
In the old code, the only way to reach the mutual wait described here
seems to be a short echo write. In that wait, the client is still
expecting bytes and the server is blocked in the 1-byte read. ret2 was
only checked for a negative value:
ret2 = write(fd, buf, ret);
if (ret2 < 0)
die_perror("write");
The new loop handles both the short read and the short write, so the
code change itself looks fine.
Could the commit message be updated to describe the actual cause of the
hang?
Also, the hang in issue 639 might have had a different cause, such as
the unchecked short write or a stall on the receive side. If so, does
the explanation as written still support the Closes: and Fixes: tags?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261026-fix-mptcp-issue-639-v3-1-165485982827%40proton.me
Hi Quanye,
Thank you for your modifications, that's great!
Our CI did some validations and here is its report:
- KVM Validation: normal (except selftest_mptcp_join): Success! ✅
- KVM Validation: normal (only selftest_mptcp_join): Success! ✅
- KVM Validation: debug (except selftest_mptcp_join): Success! ✅
- KVM Validation: debug (only selftest_mptcp_join): Success! ✅
- KVM Validation: btf-normal (only bpftest_all): Success! ✅
- KVM Validation: btf-debug (only bpftest_all): Success! ✅
- Perf: Success! ✅
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/40311317868
Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/de81d26e21ff
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1193611
If there are some issues, you can reproduce them using the same environment as
the one used by the CI thanks to a docker image, e.g.:
$ cd [kernel source code]
$ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
--pull always mptcp/mptcp-upstream-virtme-docker:latest \
auto-normal
For more details:
https://github.com/multipath-tcp/mptcp-upstream-virtme-docker
Please note that despite all the efforts that have been already done to have a
stable tests suite when executed on a public CI like here, it is possible some
reported issues are not due to your modifications. Still, do not hesitate to
help us improve that ;-)
Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)
© 2016 - 2026 Red Hat, Inc.