* [RFC PATCH net-next v2 0/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl @ 2026-07-15 14:53 Leon Hwang 2026-07-15 14:53 ` [RFC PATCH net-next v2 1/2] " Leon Hwang 2026-07-15 14:53 ` [RFC PATCH net-next v2 2/2] selftests/net: packetdrill: Add two tcp_purge_receive_queue tests Leon Hwang 0 siblings, 2 replies; 7+ messages in thread From: Leon Hwang @ 2026-07-15 14:53 UTC (permalink / raw) To: netdev Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Leon Hwang, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest Introduce a new sysctl knob, net.ipv4.tcp_purge_receive_queue, to address an unreleased SKBs issue related to TCP sockets. Issue: When a TCP socket in the CLOSE_WAIT state receives a RST packet, the current implementation does not clear the socket's receive queue. This causes SKBs in the queue to remain allocated until the socket is explicitly closed by the application. As a consequence: 1. The page pool pages held by these SKBs are not released. 2. The associated page pool cannot be freed. RFC 9293 Section 3.10.7.4 specifies that when a RST is received in CLOSE_WAIT state, "all segment queues should be flushed." However, the current implementation does not flush the receive queue. Solution: Add a per-namespace sysctl (net.ipv4.tcp_purge_receive_queue) that, when enabled, causes the kernel to purge the receive queue when a RST packet is received in CLOSE_WAIT state. This allows immediate release of SKBs and their associated memory resources. The feature is disabled by default to maintain backward compatibility with existing behavior. Note: the user-space issue, the root cause of the unreleased SKBs, has been fixed by https://github.com/IBM/sarama/pull/3384. Changes: v1 -> v2: * Update 'tp->copied_seq', 'tp->urg_data', and 'sk->sk_peek_off' like 'tcp_disconnect()'. * Drop "memory leak" words in commit msg. (per Eric) * Add two packetdrill tests. (per Eric) * v1: https://lore.kernel.org/netdev/20260225074633.149590-1-leon.huangfu@shopee.com/ Leon Hwang (2): tcp: Add net.ipv4.tcp_purge_receive_queue sysctl selftests/net: packetdrill: Add two tcp_purge_receive_queue tests Documentation/networking/ip-sysctl.rst | 18 ++++++++ .../net_cachelines/netns_ipv4_sysctl.rst | 1 + include/net/netns/ipv4.h | 1 + net/ipv4/sysctl_net_ipv4.c | 9 ++++ net/ipv4/tcp_input.c | 22 ++++++++++ .../tcp_purge_receive_queue_disabled.pkt | 40 ++++++++++++++++++ .../tcp_purge_receive_queue_enabled.pkt | 42 +++++++++++++++++++ 7 files changed, 133 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_disabled.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_enabled.pkt -- 2.55.0 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH net-next v2 1/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl 2026-07-15 14:53 [RFC PATCH net-next v2 0/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl Leon Hwang @ 2026-07-15 14:53 ` Leon Hwang 2026-07-15 15:15 ` Eric Dumazet 2026-07-15 14:53 ` [RFC PATCH net-next v2 2/2] selftests/net: packetdrill: Add two tcp_purge_receive_queue tests Leon Hwang 1 sibling, 1 reply; 7+ messages in thread From: Leon Hwang @ 2026-07-15 14:53 UTC (permalink / raw) To: netdev Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Leon Hwang, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest Introduce a new sysctl knob, net.ipv4.tcp_purge_receive_queue, to address an unreleased SKBs issue related to TCP sockets. Issue: When a TCP socket in the CLOSE_WAIT state receives a RST packet, the current implementation does not clear the socket's receive queue. This causes SKBs in the queue to remain allocated until the socket is explicitly closed by the application. As a consequence: 1. The page pool pages held by these SKBs are not released. 2. The associated page pool cannot be freed. RFC 9293 Section 3.10.7.4 specifies that when a RST is received in CLOSE_WAIT state, "all segment queues should be flushed." However, the current implementation does not flush the receive queue. Solution: Add a per-namespace sysctl (net.ipv4.tcp_purge_receive_queue) that, when enabled, causes the kernel to purge the receive queue when a RST packet is received in CLOSE_WAIT state. This allows immediate release of SKBs and their associated memory resources. The feature is disabled by default to maintain backward compatibility with existing behavior. Signed-off-by: Leon Hwang <leon.hwang@linux.dev> --- Documentation/networking/ip-sysctl.rst | 18 +++++++++++++++ .../net_cachelines/netns_ipv4_sysctl.rst | 1 + include/net/netns/ipv4.h | 1 + net/ipv4/sysctl_net_ipv4.c | 9 ++++++++ net/ipv4/tcp_input.c | 22 +++++++++++++++++++ 5 files changed, 51 insertions(+) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 208f46967ee5..abbee0326f15 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -1448,6 +1448,24 @@ tcp_rto_max_ms - INTEGER Default: 120,000 +tcp_purge_receive_queue - BOOLEAN + When a socket in the TCP_CLOSE_WAIT state receives a RST packet, the + default behavior is to not clear its receive queue. As a result, + any SKBs in the queue are not freed until the socket is closed. + Consequently, the pages held by these SKBs are not released, which + can also prevent the associated page pool from being freed. + + If enabled, the receive queue is purged upon receiving the RST, + allowing the SKBs and their associated memory to be released + promptly. + + Possible values: + + - 0 (disabled) + - 1 (enabled) + + Default: 0 (disabled) + UDP variables ============= diff --git a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst index 3dc03bff739e..2945de40ad78 100644 --- a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst +++ b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst @@ -126,6 +126,7 @@ unsigned_long sysctl_tcp_comp_sack_delay_ns unsigned_long sysctl_tcp_comp_sack_slack_ns __tcp_ack_snd_check int sysctl_max_syn_backlog int sysctl_tcp_fastopen +u8 sysctl_tcp_purge_receive_queue struct_tcp_congestion_ops tcp_congestion_control init_cc struct_tcp_fastopen_context tcp_fastopen_ctx unsigned_int sysctl_tcp_fastopen_blackhole_timeout diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index cb7f8bf15671..ab9b92807d9f 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -222,6 +222,7 @@ struct netns_ipv4 { u8 sysctl_tcp_nometrics_save; u8 sysctl_tcp_no_ssthresh_metrics_save; u8 sysctl_tcp_workaround_signed_windows; + u8 sysctl_tcp_purge_receive_queue; int sysctl_tcp_challenge_ack_limit; u8 sysctl_tcp_min_tso_segs; u8 sysctl_tcp_reflect_tos; diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index ca1180dba1de..82412fd98e0e 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -1652,6 +1652,15 @@ static struct ctl_table ipv4_net_table[] = { .extra1 = SYSCTL_ONE_THOUSAND, .extra2 = &tcp_rto_max_max, }, + { + .procname = "tcp_purge_receive_queue", + .data = &init_net.ipv4.sysctl_tcp_purge_receive_queue, + .maxlen = sizeof(u8), + .mode = 0644, + .proc_handler = proc_dou8vec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, }; static __net_init int ipv4_sysctl_init_net(struct net *net) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 61045a8886e4..4f1027173e95 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -4853,6 +4853,7 @@ void tcp_done_with_error(struct sock *sk, int err) /* When we get a reset we do this. */ void tcp_reset(struct sock *sk, struct sk_buff *skb) { + const struct net *net = sock_net(sk); int err; trace_tcp_receive_reset(sk); @@ -4869,6 +4870,27 @@ void tcp_reset(struct sock *sk, struct sk_buff *skb) err = ECONNREFUSED; break; case TCP_CLOSE_WAIT: + /* RFC9293 3.10.7.4. Other States + * Second, check the RST bit: + * CLOSE-WAIT STATE + * + * If the RST bit is set, then any outstanding RECEIVEs and + * SEND should receive "reset" responses. All segment queues + * should be flushed. Users should also receive an unsolicited + * general "connection reset" signal. Enter the CLOSED state, + * delete the TCB, and return. + * + * If net.ipv4.tcp_purge_receive_queue is enabled, + * sk_receive_queue will be flushed too. + */ + if (unlikely(READ_ONCE(net->ipv4.sysctl_tcp_purge_receive_queue))) { + struct tcp_sock *tp = tcp_sk(sk); + + skb_queue_purge(&sk->sk_receive_queue); + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); + WRITE_ONCE(tp->urg_data, 0); + sk_set_peek_off(sk, -1); + } err = EPIPE; break; case TCP_CLOSE: -- 2.55.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next v2 1/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl 2026-07-15 14:53 ` [RFC PATCH net-next v2 1/2] " Leon Hwang @ 2026-07-15 15:15 ` Eric Dumazet 2026-07-15 15:25 ` Leon Hwang 0 siblings, 1 reply; 7+ messages in thread From: Eric Dumazet @ 2026-07-15 15:15 UTC (permalink / raw) To: Leon Hwang Cc: netdev, David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest On Wed, Jul 15, 2026 at 4:54 PM Leon Hwang <leon.hwang@linux.dev> wrote: > > Introduce a new sysctl knob, net.ipv4.tcp_purge_receive_queue, to > address an unreleased SKBs issue related to TCP sockets. > > Issue: > When a TCP socket in the CLOSE_WAIT state receives a RST packet, the > current implementation does not clear the socket's receive queue. This > causes SKBs in the queue to remain allocated until the socket is > explicitly closed by the application. As a consequence: > > 1. The page pool pages held by these SKBs are not released. > 2. The associated page pool cannot be freed. > > RFC 9293 Section 3.10.7.4 specifies that when a RST is received in > CLOSE_WAIT state, "all segment queues should be flushed." However, the > current implementation does not flush the receive queue. > > Solution: > Add a per-namespace sysctl (net.ipv4.tcp_purge_receive_queue) that, > when enabled, causes the kernel to purge the receive queue when a RST > packet is received in CLOSE_WAIT state. This allows immediate release > of SKBs and their associated memory resources. > > The feature is disabled by default to maintain backward compatibility > with existing behavior. > > Signed-off-by: Leon Hwang <leon.hwang@linux.dev> > --- > Documentation/networking/ip-sysctl.rst | 18 +++++++++++++++ > .../net_cachelines/netns_ipv4_sysctl.rst | 1 + > include/net/netns/ipv4.h | 1 + > net/ipv4/sysctl_net_ipv4.c | 9 ++++++++ > net/ipv4/tcp_input.c | 22 +++++++++++++++++++ > 5 files changed, 51 insertions(+) > > diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst > index 208f46967ee5..abbee0326f15 100644 > --- a/Documentation/networking/ip-sysctl.rst > +++ b/Documentation/networking/ip-sysctl.rst > @@ -1448,6 +1448,24 @@ tcp_rto_max_ms - INTEGER > > Default: 120,000 > > +tcp_purge_receive_queue - BOOLEAN > + When a socket in the TCP_CLOSE_WAIT state receives a RST packet, the > + default behavior is to not clear its receive queue. As a result, > + any SKBs in the queue are not freed until the socket is closed. > + Consequently, the pages held by these SKBs are not released, which > + can also prevent the associated page pool from being freed. > + > + If enabled, the receive queue is purged upon receiving the RST, > + allowing the SKBs and their associated memory to be released > + promptly. > + > + Possible values: > + > + - 0 (disabled) > + - 1 (enabled) > + > + Default: 0 (disabled) > + > UDP variables > ============= > > diff --git a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst > index 3dc03bff739e..2945de40ad78 100644 > --- a/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst > +++ b/Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst > @@ -126,6 +126,7 @@ unsigned_long sysctl_tcp_comp_sack_delay_ns > unsigned_long sysctl_tcp_comp_sack_slack_ns __tcp_ack_snd_check > int sysctl_max_syn_backlog > int sysctl_tcp_fastopen > +u8 sysctl_tcp_purge_receive_queue > struct_tcp_congestion_ops tcp_congestion_control init_cc > struct_tcp_fastopen_context tcp_fastopen_ctx > unsigned_int sysctl_tcp_fastopen_blackhole_timeout > diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h > index cb7f8bf15671..ab9b92807d9f 100644 > --- a/include/net/netns/ipv4.h > +++ b/include/net/netns/ipv4.h > @@ -222,6 +222,7 @@ struct netns_ipv4 { > u8 sysctl_tcp_nometrics_save; > u8 sysctl_tcp_no_ssthresh_metrics_save; > u8 sysctl_tcp_workaround_signed_windows; > + u8 sysctl_tcp_purge_receive_queue; > int sysctl_tcp_challenge_ack_limit; > u8 sysctl_tcp_min_tso_segs; > u8 sysctl_tcp_reflect_tos; > diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c > index ca1180dba1de..82412fd98e0e 100644 > --- a/net/ipv4/sysctl_net_ipv4.c > +++ b/net/ipv4/sysctl_net_ipv4.c > @@ -1652,6 +1652,15 @@ static struct ctl_table ipv4_net_table[] = { > .extra1 = SYSCTL_ONE_THOUSAND, > .extra2 = &tcp_rto_max_max, > }, > + { > + .procname = "tcp_purge_receive_queue", > + .data = &init_net.ipv4.sysctl_tcp_purge_receive_queue, > + .maxlen = sizeof(u8), > + .mode = 0644, > + .proc_handler = proc_dou8vec_minmax, > + .extra1 = SYSCTL_ZERO, > + .extra2 = SYSCTL_ONE, > + }, > }; > > static __net_init int ipv4_sysctl_init_net(struct net *net) > diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c > index 61045a8886e4..4f1027173e95 100644 > --- a/net/ipv4/tcp_input.c > +++ b/net/ipv4/tcp_input.c > @@ -4853,6 +4853,7 @@ void tcp_done_with_error(struct sock *sk, int err) > /* When we get a reset we do this. */ > void tcp_reset(struct sock *sk, struct sk_buff *skb) > { > + const struct net *net = sock_net(sk); > int err; > > trace_tcp_receive_reset(sk); > @@ -4869,6 +4870,27 @@ void tcp_reset(struct sock *sk, struct sk_buff *skb) > err = ECONNREFUSED; > break; > case TCP_CLOSE_WAIT: > + /* RFC9293 3.10.7.4. Other States > + * Second, check the RST bit: > + * CLOSE-WAIT STATE > + * > + * If the RST bit is set, then any outstanding RECEIVEs and > + * SEND should receive "reset" responses. All segment queues > + * should be flushed. Users should also receive an unsolicited > + * general "connection reset" signal. Enter the CLOSED state, > + * delete the TCB, and return. > + * > + * If net.ipv4.tcp_purge_receive_queue is enabled, > + * sk_receive_queue will be flushed too. > + */ > + if (unlikely(READ_ONCE(net->ipv4.sysctl_tcp_purge_receive_queue))) { > + struct tcp_sock *tp = tcp_sk(sk); > + > + skb_queue_purge(&sk->sk_receive_queue); > + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); > + WRITE_ONCE(tp->urg_data, 0); > + sk_set_peek_off(sk, -1); > + } > err = EPIPE; > break; > case TCP_CLOSE: > -- > 2.55.0 > My thoughts are: out_of_order_queue has been forgotten. skbs could be there and still 'block devmem' WRITE_ONCE(tp->copied_seq, tp->rcv_nxt) is certainly wrong, because read() will return 0, instead of -1 (errno = EPIPE or ECONNRESET) So the application will not know a RST was received :/ I think that BSD and linux implementations have historically retained acknowledged, buffered receive data upon RST to allow applications to drain data already ACKed prior to the reset. Adding a narrow sysctl specifically for CLOSE_WAIT creates inconsistent behavior across TCP states. Thanks. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next v2 1/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl 2026-07-15 15:15 ` Eric Dumazet @ 2026-07-15 15:25 ` Leon Hwang 2026-07-15 15:30 ` Eric Dumazet 0 siblings, 1 reply; 7+ messages in thread From: Leon Hwang @ 2026-07-15 15:25 UTC (permalink / raw) To: Eric Dumazet Cc: netdev, David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest On 2026/7/15 23:15, Eric Dumazet wrote: > On Wed, Jul 15, 2026 at 4:54 PM Leon Hwang <leon.hwang@linux.dev> wrote: [...] >> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c >> index 61045a8886e4..4f1027173e95 100644 >> --- a/net/ipv4/tcp_input.c >> +++ b/net/ipv4/tcp_input.c >> @@ -4853,6 +4853,7 @@ void tcp_done_with_error(struct sock *sk, int err) >> /* When we get a reset we do this. */ >> void tcp_reset(struct sock *sk, struct sk_buff *skb) >> { >> + const struct net *net = sock_net(sk); >> int err; >> >> trace_tcp_receive_reset(sk); >> @@ -4869,6 +4870,27 @@ void tcp_reset(struct sock *sk, struct sk_buff *skb) >> err = ECONNREFUSED; >> break; >> case TCP_CLOSE_WAIT: >> + /* RFC9293 3.10.7.4. Other States >> + * Second, check the RST bit: >> + * CLOSE-WAIT STATE >> + * >> + * If the RST bit is set, then any outstanding RECEIVEs and >> + * SEND should receive "reset" responses. All segment queues >> + * should be flushed. Users should also receive an unsolicited >> + * general "connection reset" signal. Enter the CLOSED state, >> + * delete the TCB, and return. >> + * >> + * If net.ipv4.tcp_purge_receive_queue is enabled, >> + * sk_receive_queue will be flushed too. >> + */ >> + if (unlikely(READ_ONCE(net->ipv4.sysctl_tcp_purge_receive_queue))) { >> + struct tcp_sock *tp = tcp_sk(sk); >> + >> + skb_queue_purge(&sk->sk_receive_queue); >> + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); >> + WRITE_ONCE(tp->urg_data, 0); >> + sk_set_peek_off(sk, -1); >> + } >> err = EPIPE; >> break; >> case TCP_CLOSE: >> -- >> 2.55.0 >> > > My thoughts are: > > out_of_order_queue has been forgotten. skbs could be there and still > 'block devmem' > > WRITE_ONCE(tp->copied_seq, tp->rcv_nxt) is certainly wrong, because > read() will return 0, instead of -1 (errno = EPIPE or ECONNRESET) > So the application will not know a RST was received :/ > > I think that BSD and linux implementations have historically retained > acknowledged, > buffered receive data upon RST to allow applications to drain data > already ACKed prior to the reset. > > Adding a narrow sysctl specifically for CLOSE_WAIT creates > inconsistent behavior across TCP states. Got it. I won't pursue this sysctl approach in the future. Thanks for the review. Leon ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next v2 1/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl 2026-07-15 15:25 ` Leon Hwang @ 2026-07-15 15:30 ` Eric Dumazet 2026-07-15 15:48 ` Leon Hwang 0 siblings, 1 reply; 7+ messages in thread From: Eric Dumazet @ 2026-07-15 15:30 UTC (permalink / raw) To: Leon Hwang Cc: netdev, David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest On Wed, Jul 15, 2026 at 5:26 PM Leon Hwang <leon.hwang@linux.dev> wrote: > > On 2026/7/15 23:15, Eric Dumazet wrote: > > On Wed, Jul 15, 2026 at 4:54 PM Leon Hwang <leon.hwang@linux.dev> wrote: > > [...] > > >> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c > >> index 61045a8886e4..4f1027173e95 100644 > >> --- a/net/ipv4/tcp_input.c > >> +++ b/net/ipv4/tcp_input.c > >> @@ -4853,6 +4853,7 @@ void tcp_done_with_error(struct sock *sk, int err) > >> /* When we get a reset we do this. */ > >> void tcp_reset(struct sock *sk, struct sk_buff *skb) > >> { > >> + const struct net *net = sock_net(sk); > >> int err; > >> > >> trace_tcp_receive_reset(sk); > >> @@ -4869,6 +4870,27 @@ void tcp_reset(struct sock *sk, struct sk_buff *skb) > >> err = ECONNREFUSED; > >> break; > >> case TCP_CLOSE_WAIT: > >> + /* RFC9293 3.10.7.4. Other States > >> + * Second, check the RST bit: > >> + * CLOSE-WAIT STATE > >> + * > >> + * If the RST bit is set, then any outstanding RECEIVEs and > >> + * SEND should receive "reset" responses. All segment queues > >> + * should be flushed. Users should also receive an unsolicited > >> + * general "connection reset" signal. Enter the CLOSED state, > >> + * delete the TCB, and return. > >> + * > >> + * If net.ipv4.tcp_purge_receive_queue is enabled, > >> + * sk_receive_queue will be flushed too. > >> + */ > >> + if (unlikely(READ_ONCE(net->ipv4.sysctl_tcp_purge_receive_queue))) { > >> + struct tcp_sock *tp = tcp_sk(sk); > >> + > >> + skb_queue_purge(&sk->sk_receive_queue); > >> + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); > >> + WRITE_ONCE(tp->urg_data, 0); > >> + sk_set_peek_off(sk, -1); > >> + } > >> err = EPIPE; > >> break; > >> case TCP_CLOSE: > >> -- > >> 2.55.0 > >> > > > > My thoughts are: > > > > out_of_order_queue has been forgotten. skbs could be there and still > > 'block devmem' > > > > WRITE_ONCE(tp->copied_seq, tp->rcv_nxt) is certainly wrong, because > > read() will return 0, instead of -1 (errno = EPIPE or ECONNRESET) > > So the application will not know a RST was received :/ > > > > I think that BSD and linux implementations have historically retained > > acknowledged, > > buffered receive data upon RST to allow applications to drain data > > already ACKed prior to the reset. > > > > Adding a narrow sysctl specifically for CLOSE_WAIT creates > > inconsistent behavior across TCP states. > > > Got it. I won't pursue this sysctl approach in the future. Thanks for > the review. My intention was not to kill your proposal, only to start a conversation... ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next v2 1/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl 2026-07-15 15:30 ` Eric Dumazet @ 2026-07-15 15:48 ` Leon Hwang 0 siblings, 0 replies; 7+ messages in thread From: Leon Hwang @ 2026-07-15 15:48 UTC (permalink / raw) To: Eric Dumazet Cc: netdev, David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest On 2026/7/15 23:30, Eric Dumazet wrote: > On Wed, Jul 15, 2026 at 5:26 PM Leon Hwang <leon.hwang@linux.dev> wrote: >> >> On 2026/7/15 23:15, Eric Dumazet wrote: >>> On Wed, Jul 15, 2026 at 4:54 PM Leon Hwang <leon.hwang@linux.dev> wrote: >> >> [...] >> >>>> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c >>>> index 61045a8886e4..4f1027173e95 100644 >>>> --- a/net/ipv4/tcp_input.c >>>> +++ b/net/ipv4/tcp_input.c >>>> @@ -4853,6 +4853,7 @@ void tcp_done_with_error(struct sock *sk, int err) >>>> /* When we get a reset we do this. */ >>>> void tcp_reset(struct sock *sk, struct sk_buff *skb) >>>> { >>>> + const struct net *net = sock_net(sk); >>>> int err; >>>> >>>> trace_tcp_receive_reset(sk); >>>> @@ -4869,6 +4870,27 @@ void tcp_reset(struct sock *sk, struct sk_buff *skb) >>>> err = ECONNREFUSED; >>>> break; >>>> case TCP_CLOSE_WAIT: >>>> + /* RFC9293 3.10.7.4. Other States >>>> + * Second, check the RST bit: >>>> + * CLOSE-WAIT STATE >>>> + * >>>> + * If the RST bit is set, then any outstanding RECEIVEs and >>>> + * SEND should receive "reset" responses. All segment queues >>>> + * should be flushed. Users should also receive an unsolicited >>>> + * general "connection reset" signal. Enter the CLOSED state, >>>> + * delete the TCB, and return. >>>> + * >>>> + * If net.ipv4.tcp_purge_receive_queue is enabled, >>>> + * sk_receive_queue will be flushed too. >>>> + */ >>>> + if (unlikely(READ_ONCE(net->ipv4.sysctl_tcp_purge_receive_queue))) { >>>> + struct tcp_sock *tp = tcp_sk(sk); >>>> + >>>> + skb_queue_purge(&sk->sk_receive_queue); >>>> + WRITE_ONCE(tp->copied_seq, tp->rcv_nxt); >>>> + WRITE_ONCE(tp->urg_data, 0); >>>> + sk_set_peek_off(sk, -1); >>>> + } >>>> err = EPIPE; >>>> break; >>>> case TCP_CLOSE: >>>> -- >>>> 2.55.0 >>>> >>> >>> My thoughts are: >>> >>> out_of_order_queue has been forgotten. skbs could be there and still >>> 'block devmem' >>> >>> WRITE_ONCE(tp->copied_seq, tp->rcv_nxt) is certainly wrong, because >>> read() will return 0, instead of -1 (errno = EPIPE or ECONNRESET) >>> So the application will not know a RST was received :/ >>> >>> I think that BSD and linux implementations have historically retained >>> acknowledged, >>> buffered receive data upon RST to allow applications to drain data >>> already ACKed prior to the reset. >>> >>> Adding a narrow sysctl specifically for CLOSE_WAIT creates >>> inconsistent behavior across TCP states. >> >> >> Got it. I won't pursue this sysctl approach in the future. Thanks for >> the review. > > My intention was not to kill your proposal, only to start a conversation... Thanks for clarifying. I agree this approach needs more thought. Thanks, Leon ^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH net-next v2 2/2] selftests/net: packetdrill: Add two tcp_purge_receive_queue tests 2026-07-15 14:53 [RFC PATCH net-next v2 0/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl Leon Hwang 2026-07-15 14:53 ` [RFC PATCH net-next v2 1/2] " Leon Hwang @ 2026-07-15 14:53 ` Leon Hwang 1 sibling, 0 replies; 7+ messages in thread From: Leon Hwang @ 2026-07-15 14:53 UTC (permalink / raw) To: netdev Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Neal Cardwell, Kuniyuki Iwashima, Ido Schimmel, Ilpo Järvinen, Leon Hwang, Chia-Yu Chang, Yung Chih Su, Wyatt Feng, Jason Xing, Lance Yang, Jiayuan Chen, linux-doc, linux-kernel, linux-kselftest Run the tests: # ./ksft_runner.sh ./tcp_purge_receive_queue_disabled.pkt TAP version 13 1..3 ok 1 ipv4 ok 2 ipv6 ok 3 ipv4-mapped-ipv6 # Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0 # ./ksft_runner.sh ./tcp_purge_receive_queue_enabled.pkt TAP version 13 1..3 ok 1 ipv4 ok 2 ipv6 ok 3 ipv4-mapped-ipv6 # Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0 Assisted-by: Codex:gpt-5.5 Signed-off-by: Leon Hwang <leon.hwang@linux.dev> --- .../tcp_purge_receive_queue_disabled.pkt | 40 ++++++++++++++++++ .../tcp_purge_receive_queue_enabled.pkt | 42 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_disabled.pkt create mode 100644 tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_enabled.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_disabled.pkt b/tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_disabled.pkt new file mode 100644 index 000000000000..a46f93eaab1f --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_disabled.pkt @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 +// Test: with tcp_purge_receive_queue disabled (default), receiving RST +// in CLOSE_WAIT state does NOT purge the receive queue. +// The unread data in the receive queue is still accessible to the +// application. + +`./defaults.sh +./set_sysctls.py /proc/sys/net/ipv4/tcp_purge_receive_queue=0` + +// Establish a connection (server side). + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7> + +0 > S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 8> + +.1 < . 1:1(0) ack 1 win 257 + + +0 accept(3, ..., ...) = 4 + +// Remote sends 4KiB data so that receive queue is populated. + +.1 < . 1:4097(4096) ack 1 win 257 + +0 > . 1:1(0) ack 4097 + +// Remote sends FIN -> socket enters CLOSE_WAIT. + +.1 < F. 4097:4097(0) ack 1 win 257 + +0~+.04 > . 1:1(0) ack 4098 + +// Inject RST directly while in CLOSE_WAIT. + +.1 < R. 4098:4098(0) ack 1 win 257 + +// With tcp_purge_receive_queue=0, the receive queue is NOT purged. +// Data is still in the receive queue; read returns data. + +0 read(4, ..., 1) = 1 + + +0 close(4) = 0 + +// Restore sysctls. +`/tmp/sysctl_restore_${PPID}.sh` diff --git a/tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_enabled.pkt b/tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_enabled.pkt new file mode 100644 index 000000000000..b1d1dff28f50 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_purge_receive_queue_enabled.pkt @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +// Test: with tcp_purge_receive_queue enabled, receiving RST in +// CLOSE_WAIT state purges the receive queue immediately. +// The unread data is discarded, so read() observes EOF. +// +// RFC 9293 Section 3.10.7.4: +// "all segment queues should be flushed" + +`./defaults.sh +./set_sysctls.py /proc/sys/net/ipv4/tcp_purge_receive_queue=1` + +// Establish a connection (server side). + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + + +0 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7> + +0 > S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 8> + +.1 < . 1:1(0) ack 1 win 257 + + +0 accept(3, ..., ...) = 4 + +// Remote sends 4KiB data so that receive queue is populated. + +.1 < . 1:4097(4096) ack 1 win 257 + +0 > . 1:1(0) ack 4097 + +// Remote sends FIN -> socket enters CLOSE_WAIT. + +.1 < F. 4097:4097(0) ack 1 win 257 + +0~+.04 > . 1:1(0) ack 4098 + +// Inject RST directly while in CLOSE_WAIT. + +.1 < R. 4098:4098(0) ack 1 win 257 + +// With tcp_purge_receive_queue=1, the receive queue IS purged. +// Queue was purged by RST handler; read returns EOF. + +0 read(4, ..., 1) = 0 + + +0 close(4) = 0 + +// Restore sysctls. +`/tmp/sysctl_restore_${PPID}.sh` -- 2.55.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-15 15:49 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-15 14:53 [RFC PATCH net-next v2 0/2] tcp: Add net.ipv4.tcp_purge_receive_queue sysctl Leon Hwang 2026-07-15 14:53 ` [RFC PATCH net-next v2 1/2] " Leon Hwang 2026-07-15 15:15 ` Eric Dumazet 2026-07-15 15:25 ` Leon Hwang 2026-07-15 15:30 ` Eric Dumazet 2026-07-15 15:48 ` Leon Hwang 2026-07-15 14:53 ` [RFC PATCH net-next v2 2/2] selftests/net: packetdrill: Add two tcp_purge_receive_queue tests Leon Hwang
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.