Add packetdrill tests for the interaction between SO_RCVBUF, a user TCP_WINDOW_CLAMP and the route "window" metric (RTAX_WINDOW). Active open, checked on the SYN: - tcp_rcvbuf_before_connect_window.pkt: SO_RCVBUF set before connect() must not defeat the route metric. - tcp_window_clamp_vs_rcvbuf.pkt: a clamp set with TCP_WINDOW_CLAMP must not be overwritten by a later SO_RCVBUF on the unconnected socket. - tcp_window_clamp_reset_rcvbuf.pkt: TCP_WINDOW_CLAMP=0 removes the user's clamp, so a later SO_RCVBUF must again not defeat the route metric. - tcp_fastopen_connect_rcvbuf_window.pkt: with TCP_FASTOPEN_CONNECT the SYN is deferred to the first write() while the socket sits in TCP_SYN_SENT; SO_RCVBUF applied in that gap must not defeat the route metric either. Passive open, checked on the SYN-ACK and on the child: - tcp_rcvbuf_listener_window.pkt: SO_RCVBUF on the listener must not defeat the route metric. - tcp_window_clamp_vs_rcvbuf_listener.pkt: TCP_WINDOW_CLAMP and then SO_RCVBUF on the listener; the SYN-ACK must use the user's clamp. - tcp_syncookies_rcvbuf_window_ip4.pkt and its _ip6 twin: with net.ipv4.tcp_syncookies=2, the child created from the cookie must use the window scale the SYN-ACK advertised (tcpi_rcv_wscale). Established socket: - tcp_window_clamp_rcvbuf_raise.pkt: after tcp_init_buffer_space() lowered window_clamp, a larger SO_RCVBUF must raise it again, up to the value set with TCP_WINDOW_CLAMP and not beyond. All nine tests fail without the previous patch and pass with it. They are picked up by the packetdrill selftest Makefile (TEST_PROGS := $(wildcard *.pkt)). Assisted-by: LLM Signed-off-by: Shashank Mohan Jain --- v4: - symbolic TCP_FASTOPEN_CONNECT / TCP_FASTOPEN_NO_COOKIE - new: listener (SYN-ACK) tests, SYN cookie tests for IPv4 and IPv6, TCP_WINDOW_CLAMP=0 reset test, SO_RCVBUF raise test .../tcp_fastopen_connect_rcvbuf_window.pkt | 21 +++++++++++++++ .../tcp_rcvbuf_before_connect_window.pkt | 27 +++++++++++++++++++ .../tcp_rcvbuf_listener_window.pkt | 20 ++++++++++++++ .../tcp_syncookies_rcvbuf_window_ip4.pkt | 22 +++++++++++++++ .../tcp_syncookies_rcvbuf_window_ip6.pkt | 22 +++++++++++++++ .../tcp_window_clamp_rcvbuf_raise.pkt | 27 +++++++++++++++++++ .../tcp_window_clamp_reset_rcvbuf.pkt | 22 +++++++++++++++ .../tcp_window_clamp_vs_rcvbuf.pkt | 23 ++++++++++++++++ .../tcp_window_clamp_vs_rcvbuf_listener.pkt | 25 +++++++++++++++++ 9 files changed, 209 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_fastopen_connect_rcvbuf_window.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcvbuf_before_connect_window.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcvbuf_listener_window.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip4.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip6.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_window_clamp_rcvbuf_raise.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_window_clamp_reset_rcvbuf.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf_listener.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_fastopen_connect_rcvbuf_window.pkt b/tools/testing/selftests/net/packetdrill/tcp_fastopen_connect_rcvbuf_window.pkt new file mode 100644 index 00000000000..b297294ed16 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_fastopen_connect_rcvbuf_window.pkt @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +// TCP_FASTOPEN_CONNECT defers the SYN until the first write(); between +// connect() and that write the socket is in TCP_SYN_SENT. SO_RCVBUF applied +// in that gap must not defeat the route "window" metric on the deferred SYN. +--ip_version=ipv4 + +`./defaults.sh +sysctl -q net.ipv4.tcp_fastopen=5 +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_TCP, TCP_FASTOPEN_CONNECT, [1], 4) = 0 + +0 setsockopt(3, SOL_TCP, TCP_FASTOPEN_NO_COOKIE, [1], 4) = 0 + +0 connect(3, ..., ...) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 + +0 write(3, ..., 1) = 1 + +// The deferred SYN (carrying 1 byte of data) must advertise the route +// clamped window and window scale 0 (not win 65535, wscale 5). + +0 > S 0:1(1) win 2920 diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcvbuf_before_connect_window.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcvbuf_before_connect_window.pkt new file mode 100644 index 00000000000..7ec7f14d8e8 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rcvbuf_before_connect_window.pkt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +// Setting SO_RCVBUF before connect() must not defeat the route "window" +// metric on the SYN. +// +// The route to the peer carries "window 4096" (RTAX_WINDOW). With SO_RCVBUF +// also set before connect(), the SYN the kernel sends must still advertise +// the small, route-clamped receive window and a window scale of 0 (the +// window scale option is sent, with a shift of 0). +// +// Since commit b025461303d8 ("tcp: update window_clamp when SO_RCVBUF is +// set"), setsockopt(SO_RCVBUF) wrote tp->window_clamp while the socket was +// still in TCP_CLOSE. tcp_connect_init() then treated that as a user clamp +// and skipped RTAX_WINDOW, so the SYN carried win 65535, wscale 5 instead. +--ip_version=ipv4 + +`./defaults.sh +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 + +0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) + +// The SYN must advertise the route-clamped window and window scale 0. + +0 > S 0:0(0) win 2920 + +.1 < S. 0:0(0) ack 1 win 65535 + +0 > . 1:1(0) ack 1 diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcvbuf_listener_window.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcvbuf_listener_window.pkt new file mode 100644 index 00000000000..ed20a75fa9a --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rcvbuf_listener_window.pkt @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +// Passive side: SO_RCVBUF set on a listener must not defeat the route +// "window" metric on the SYN-ACK, and the child must use the window scale +// that the SYN-ACK advertised. +--ip_version=ipv4 + +`./defaults.sh +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 65535 + +0 > S. 0:0(0) ack 1 win 2896 + +.01 < . 1:1(0) ack 1 win 514 + +0 accept(3, ..., ...) = 4 + +0 %{ assert tcpi_rcv_wscale == 0, tcpi_rcv_wscale }% diff --git a/tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip4.pkt b/tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip4.pkt new file mode 100644 index 00000000000..e26f78704ab --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip4.pkt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +// SYN cookies: the child built by cookie_v4_check() must use the same +// window clamp rule as the SYN-ACK. With SO_RCVBUF set on the listener and +// a route "window" metric, the SYN-ACK advertises window scale 0, so the +// child must use a receive window scale of 0 too. +--ip_version=ipv4 + +`./defaults.sh +sysctl -q net.ipv4.tcp_syncookies=2 +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 10) = 0 + + +0 < S 0:0(0) win 65535 + +0 > S. 0:0(0) ack 1 win 2896 + +.01 < . 1:1(0) ack 1 win 514 + +0 accept(3, ..., ...) = 4 + +0 %{ assert tcpi_rcv_wscale == 0, tcpi_rcv_wscale }% diff --git a/tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip6.pkt b/tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip6.pkt new file mode 100644 index 00000000000..8b5dd71d765 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_syncookies_rcvbuf_window_ip6.pkt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +// SYN cookies: the child built by cookie_v6_check() must use the same +// window clamp rule as the SYN-ACK. With SO_RCVBUF set on the listener and +// a route "window" metric, the SYN-ACK advertises window scale 0, so the +// child must use a receive window scale of 0 too. +--ip_version=ipv6 + +`./defaults.sh +sysctl -q net.ipv4.tcp_syncookies=2 +ip -6 route replace fd3d:fa7b:d17d::1 via fd3d:0a0b:17d6:8888::1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 10) = 0 + + +0 < S 0:0(0) win 65535 + +0 > S. 0:0(0) ack 1 win 2896 + +.01 < . 1:1(0) ack 1 win 514 + +0 accept(3, ..., ...) = 4 + +0 %{ assert tcpi_rcv_wscale == 0, tcpi_rcv_wscale }% diff --git a/tools/testing/selftests/net/packetdrill/tcp_window_clamp_rcvbuf_raise.pkt b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_rcvbuf_raise.pkt new file mode 100644 index 00000000000..08ef005bf99 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_rcvbuf_raise.pkt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +// After the handshake, tcp_init_buffer_space() lowers window_clamp to the +// receive space. A later, larger SO_RCVBUF must raise window_clamp again, +// but only up to the clamp the user set with TCP_WINDOW_CLAMP. +// +// packetdrill has no symbol for TCP_WINDOW_CLAMP, so optname 10 is used. +--ip_version=ipv4 + +`./defaults.sh` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_TCP, 10, [40000], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [8192], 4) = 0 + +0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 + +0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) + +0 > S 0:0(0) <...> + +.1 < S. 0:0(0) ack 1 win 65535 + +0 > . 1:1(0) ack 1 + +// The small receive buffer keeps window_clamp below the user's clamp: +// 8192 (tcp_full_space()) minus the one segment (advmss 1460) that +// tcp_init_buffer_space() reserves. + +0 getsockopt(3, SOL_TCP, 10, [6732], [4]) = 0 + +// A larger SO_RCVBUF raises it again, up to the user's clamp. + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [212992], 4) = 0 + +0 getsockopt(3, SOL_TCP, 10, [40000], [4]) = 0 diff --git a/tools/testing/selftests/net/packetdrill/tcp_window_clamp_reset_rcvbuf.pkt b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_reset_rcvbuf.pkt new file mode 100644 index 00000000000..458f2f313c7 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_reset_rcvbuf.pkt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +// TCP_WINDOW_CLAMP set back to 0 on an unconnected socket removes the +// user's clamp: a later SO_RCVBUF must then not stop the route "window" +// metric from being applied on the SYN. +// +// packetdrill has no symbol for TCP_WINDOW_CLAMP, so optname 10 is used. +--ip_version=ipv4 + +`./defaults.sh +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_TCP, 10, [16384], 4) = 0 + +0 setsockopt(3, SOL_TCP, 10, [0], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 + +0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) + +// No user clamp is left, so the SYN advertises the route window. + +0 > S 0:0(0) win 2920 + +.1 < S. 0:0(0) ack 1 win 65535 + +0 > . 1:1(0) ack 1 diff --git a/tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf.pkt b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf.pkt new file mode 100644 index 00000000000..69a05959e42 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf.pkt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +// A window clamp the user set with setsockopt(TCP_WINDOW_CLAMP) must be +// honoured on the SYN even when SO_RCVBUF is set afterwards on the still +// unconnected socket: SO_RCVBUF must not raise window_clamp above the +// user's clamp, and the route "window" metric must not replace it either. +// +// packetdrill has no symbol for TCP_WINDOW_CLAMP, so optname 10 is used. +--ip_version=ipv4 + +`./defaults.sh +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_TCP, 10, [16384], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 + +0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) + +// The SYN advertises the user's clamp (win 16060), not the route window +// (2920) and not the SO_RCVBUF-derived full window. + +0 > S 0:0(0) win 16060 + +.1 < S. 0:0(0) ack 1 win 65535 + +0 > . 1:1(0) ack 1 diff --git a/tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf_listener.pkt b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf_listener.pkt new file mode 100644 index 00000000000..f7e7ddb5000 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_window_clamp_vs_rcvbuf_listener.pkt @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +// Passive side: a TCP_WINDOW_CLAMP set on a listener must be honoured on +// the SYN-ACK even when SO_RCVBUF is set afterwards on the listener, and +// the route "window" metric must not replace it. +// +// packetdrill has no symbol for TCP_WINDOW_CLAMP, so optname 10 is used. +--ip_version=ipv4 + +`./defaults.sh +ip route replace 192.0.2.1 via 192.168.0.1 dev tun0 window 4096` + + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 setsockopt(3, SOL_TCP, 10, [16384], 4) = 0 + +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +// The SYN-ACK advertises the user's clamp, not the route window and not +// the SO_RCVBUF-derived full window. + +0 < S 0:0(0) win 65535 + +0 > S. 0:0(0) ack 1 win 15928 + +.01 < . 1:1(0) ack 1 win 514 + +0 accept(3, ..., ...) = 4 + +0 %{ assert tcpi_rcv_wscale == 0, tcpi_rcv_wscale }% -- 2.43.0