Netdev List
 help / color / mirror / Atom feed
From: Jakub Kicinski <kuba@kernel.org>
To: marcelomspessoto@gmail.com
Cc: Jakub Kicinski <kuba@kernel.org>,
	netdev@vger.kernel.org, linux-kselftest@vger.kernel.org,
	marcelospe@proton.me
Subject: Re: [PATCH net-next 2/4] selftests: net: test IPV6_FL_F_REMOTE
Date: Thu, 30 Jul 2026 18:39:54 -0700	[thread overview]
Message-ID: <20260731013954.752355-1-kuba@kernel.org> (raw)
In-Reply-To: <20260727043516.93101-3-marcelospe@proton.me>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
--- Patch [2]: [PATCH net-next 2/4] selftests: net: test IPV6_FL_F_REMOTE ---
commit bdc4decc572fa37fb4ef0e74f408cc6167e8342d
Author: Marcelo Mendes Spessoto Junior <marcelomspessoto@gmail.com>

selftests: net: test IPV6_FL_F_REMOTE

This adds coverage for the IPV6_FL_F_REMOTE flag of IPV6_FLOWLABEL_MGR by
setting up a loopback TCP connection with a flow label on the SYN and then
querying the accepted socket with getsockopt().  The connection setup is
factored into two new helpers, tcp_listen() and tcp_connect().

A few questions below.

[Severity: Low]
The commit message says:

  "This flag retrieves the latest label received by the socket with
   a getsockopt query."

Does the new test actually cover the "latest label" part?

ipv6_flowlabel_opt_get() in net/ipv6/ip6_flowlabel.c returns
np->rcv_flowinfo masked with IPV6_FLOWLABEL_MASK.  For TCP, rcv_flowinfo is
set unconditionally for the child socket from the SYN in
tcp_v6_syn_recv_sock():

  net/ipv6/tcp_ipv6.c:tcp_v6_syn_recv_sock() {
      ...
      newnp->rcv_flowinfo = ip6_flowinfo(ipv6_hdr(skb));
      ...
  }

and it is only refreshed later, in tcp_v6_do_rcv(), when rxflow or rxtclass
are enabled on the socket:

  net/ipv6/tcp_ipv6.c:tcp_v6_do_rcv() {
      ...
      if (np->rxopt.bits.rxflow || np->rxopt.bits.rxtclass)
              np->rcv_flowinfo = ip6_flowinfo(ipv6_hdr(opt_skb));
      ...
  }

The new test queries the accepted socket right after accept(), never sets
IPV6_FLOWINFO or IPV6_TCLASS on it, and never exchanges data afterwards, so
only the SYN-time value is observed and the refresh path above would remain
untested.  Would it make sense to either extend the test (enable
IPV6_FLOWINFO on the accepted socket and send data carrying a second label)
or reword the commit message to say the label seen at connection setup?

> diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c
> index cfa7e6270994a..383a84ea9385c 100644
> --- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c
> +++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c

[ ... ]

> @@ -82,6 +92,52 @@ static int flowlabel_renew(int fd, uint32_t label, uint16_t linger)

[ ... ]

> +static void tcp_connect(int listener, uint32_t flowlabel, int *client, int *accepted)
> +{
> +	INIT_SIN6_LOOPBACK(addr);
> +	const int one = 1;
> +	int cfd, afd;
> +
> +	cfd = socket(PF_INET6, SOCK_STREAM, 0);
> +	if (cfd == -1)
> +		error(1, errno, "socket client");
> +
> +	if (flowlabel_get(cfd, flowlabel, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE))
> +		error(1, errno, "flowlabel_get");

[Severity: Low]
Should this get be paired with the existing flowlabel_put() helper?

Every other test in this file pairs get and put explicitly, but tcp_connect()
only releases the label implicitly through close(cfd).  That path goes
fl6_free_socklist() -> fl_release(), and fl_release() applies the linger
timeout rather than freeing the label right away:

  net/ipv6/ip6_flowlabel.c:fl_release() {
      ...
      fl->lastuse = jiffies;
      if (atomic_dec_and_test(&fl->users)) {
              unsigned long ttd = fl->lastuse + fl->linger;
      ...
  }

So the label value stays reserved for roughly FL_MIN_LINGER seconds after the
helper's socket is closed.  The commit message says the helpers exist so other
features such as IPV6_FL_F_REFLECT can reuse them; wouldn't a second
tcp_connect(listener, 6, ...) in the same run then fail in flowlabel_get()
with IPV6_FL_S_EXCL|IPV6_FL_F_CREATE and abort the whole binary via
error(1, ...)?  Adding flowlabel_put(cfd, flowlabel) before closing the client
socket, or making the share mode a parameter, would match the style used
elsewhere in this file.

> +	if (setsockopt(cfd, SOL_IPV6, IPV6_FLOWINFO_SEND, &one, sizeof(one)))
> +		error(1, errno, "setsockopt flowinfo_send");
> +	addr.sin6_flowinfo = htonl(flowlabel);
> +
> +	if (connect(cfd, (void *)&addr, sizeof(addr)))
> +		error(1, errno, "connect");
> +
> +	afd = accept(listener, NULL, NULL);
> +	if (afd == -1)
> +		error(1, errno, "accept");
> +
> +	*client = cfd;
> +	*accepted = afd;
> +}
> +
>  static void run_tests(int fd)
>  {
>  	int wstatus;
> @@ -183,6 +239,21 @@ static void run_tests(int fd)
>  		expect_fail(flowlabel_get(fd, 5, IPV6_FL_S_ANY, IPV6_FL_F_CREATE));
>  	}
>  
> +	explain("Prepare TCP SYN for REMOTE flag validation");
> +	int remote_listener = tcp_listen();
> +	int remote_cfd, remote_afd;

[Severity: Low]
This isn't a bug, but these declarations come after executable statements,
while run_tests() already declares wstatus and pid at the top of the function
and the rest of the file uses top-of-block declarations.  selftests/net
CFLAGS do not turn on -Wdeclaration-after-statement so it builds, but hoisting
remote_listener, remote_cfd, remote_afd, freq and freq_len to the top of
run_tests() (or putting the new test in its own block) would keep the style
consistent with no change in behaviour.

> +	tcp_connect(remote_listener, 6, &remote_cfd, &remote_afd);
> +	struct in6_flowlabel_req freq = {
> +		.flr_action = IPV6_FL_A_GET,
> +		.flr_flags = IPV6_FL_F_REMOTE,
> +	};
> +	socklen_t freq_len = sizeof(freq);
> +	explain("Query for label sent by client with IPV6_FL_F_REMOTE");
> +	expect_pass(getsockopt(remote_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &freq, &freq_len));
> +	expect_pass(ntohl(freq.flr_label) != 6);
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Low]
Is this assertion written the way it was intended?

expect_pass() succeeds when its argument evaluates to zero:

  #define __expect(x)						\
	do {							\
		if (!(x))					\
			fprintf(stderr, "[OK]   " #x "\n");	\
		else						\
			error(1, 0, "[ERR]  " #x " (line %d)", __LINE__); \
	} while (0)

  #define expect_pass(x)	__expect(x)
  #define expect_fail(x)	__expect(!(x))

So expect_pass(ntohl(freq.flr_label) != 6) passes exactly when the label is
6, which is the intended check, but it reads as the opposite and the log line
printed on success is "[OK]   ntohl(freq.flr_label) != 6".  Every other use of
expect_pass()/expect_fail() in this file passes a setsockopt()/getsockopt()
return code rather than a value comparison.

Would a plain check be clearer here, for example:

  if (ntohl(freq.flr_label) != REMOTE_LABEL)
	error(1, 0, "unexpected remote flowlabel %u", ntohl(freq.flr_label));

Also, the expected label appears twice as a bare literal, in
tcp_connect(remote_listener, 6, ...) and in the comparison against 6.  Could a
named constant be used at both sites so they cannot drift apart?

> +	close(remote_afd);
> +	close(remote_cfd);
> +	close(remote_listener);
>  }

  reply	other threads:[~2026-07-31  1:39 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-27  4:25 [PATCH net-next 0/4] net: selftests: adjustments to ipv6_flowlabel_mgr Marcelo Mendes Spessoto Junior
2026-07-27  4:25 ` [PATCH net-next 1/4] selftests: net: test IPV6_FL_A_RENEW Marcelo Mendes Spessoto Junior
2026-07-31  1:39   ` Jakub Kicinski
2026-07-27  4:25 ` [PATCH net-next 2/4] selftests: net: test IPV6_FL_F_REMOTE Marcelo Mendes Spessoto Junior
2026-07-31  1:39   ` Jakub Kicinski [this message]
2026-07-31  1:42   ` Jakub Kicinski
2026-07-27  4:25 ` [PATCH net-next 3/4] selftests: net: test IPV6_FL_F_REFLECT Marcelo Mendes Spessoto Junior
2026-07-31  1:39   ` Jakub Kicinski
2026-07-27  4:25 ` [PATCH 4/4] selftests: net: adopt harness for flow label mgr Marcelo Mendes Spessoto Junior
2026-07-31  1:39   ` Jakub Kicinski
2026-07-31  1:41 ` [PATCH net-next 0/4] net: selftests: adjustments to ipv6_flowlabel_mgr Jakub Kicinski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260731013954.752355-1-kuba@kernel.org \
    --to=kuba@kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=marcelomspessoto@gmail.com \
    --cc=marcelospe@proton.me \
    --cc=netdev@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox