From 4b142087713a800646a711ad8d54766f0c8af370 Mon Sep 17 00:00:00 2001 From: zhekunren Date: Fri, 4 Sep 2026 09:57:40 +0800 Subject: [PATCH] net/tcp: add configurable delayed ACK threshold The delayed ACK logic previously sent an ACK for at least every second received segment (hard-coded threshold of 2 per RFC 1122). Add the NET_TCP_ACK_FREQUENCY Kconfig option (range 1-255, default 2) to make this threshold configurable at build time. The delayed ACK timer still forces an ACK after at most 0.5 seconds, so RFC 1122 timing compliance is preserved regardless of the configured threshold. The default value of 2 keeps the exact current behavior: the new condition rx_unackseg >= FREQ - 1 is equivalent to the previous rx_unackseg > 0, and the counter increment degenerates to the previous rx_unackseg = 1 assignment. Signed-off-by: zhekunren Assisted-by: GLM-5.2 --- .../components/net/delay_act_and_tcp_perf.rst | 23 +++++++++- net/tcp/Kconfig | 22 ++++++++++ net/tcp/tcp_appsend.c | 42 ++++++++++--------- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/Documentation/components/net/delay_act_and_tcp_perf.rst b/Documentation/components/net/delay_act_and_tcp_perf.rst index 50247b9a57f7a..1d3a0154cc6b0 100644 --- a/Documentation/components/net/delay_act_and_tcp_perf.rst +++ b/Documentation/components/net/delay_act_and_tcp_perf.rst @@ -131,9 +131,30 @@ ACKing behavior and you have MSS sizes that are larger that the average size of the user buffers, then your throughput can probably be greatly improved by enabling ``CONFIG_NET_TCP_SPLIT=y`` -NOTE: NuttX is `not` an RFC 1122 recipient; NuttX will ACK +NOTE: NuttX is `not` an RFC 1122 recipient; NuttX will ACK every TCP/IP packet that it receives. +The Delayed ACK Threshold Configuration +======================================= + +The delayed ACK logic is enabled with ``CONFIG_NET_TCP_DELAYED_ACK``. +When enabled, the number of received data segments that trigger one +ACK can be configured with ``CONFIG_NET_TCP_ACK_FREQUENCY``: + +* Value 1 degenerates to an immediate ACK per received segment + (delayed ACK effectively disabled). +* Value 2 (default) is the RFC 1122 compliant behavior: an ACK for + at least every second segment in a stream of full-sized segments. +* Values above 2 reduce ACK traffic and can improve throughput when + bulk data is received, at the cost of a coarser ACK clock and + slower peer congestion window growth. + +NOTE: RFC 1122 Section 4.2.3.2 states that in a stream of full-sized +segments there SHOULD be an ACK for at least every second segment. +Setting ``CONFIG_NET_TCP_ACK_FREQUENCY`` above 2 no longer meets +that requirement. The delayed ACK timer still forces an ACK after +at most 0.5 seconds as required by RFC 1122. + Write Buffering =============== diff --git a/net/tcp/Kconfig b/net/tcp/Kconfig index 09c1dda7cd542..4475ca3d3e3a2 100644 --- a/net/tcp/Kconfig +++ b/net/tcp/Kconfig @@ -35,6 +35,28 @@ config NET_TCP_DELAYED_ACK 0.5 seconds, and in a stream of full-sized segments there should be an ACK for at least every second segments. +config NET_TCP_ACK_FREQUENCY + int "Segments received per ACK (delayed ACK threshold)" + depends on NET_TCP_DELAYED_ACK + range 1 255 + default 2 + ---help--- + Number of received data segments that trigger one ACK (the + delayed ACK threshold). The delayed ACK timer still forces + an ACK after at most 0.5 seconds per RFC 1122. + + Value 1 degenerates to an immediate ACK per received segment + (delayed ACK effectively disabled). Value 2 (default) is the + RFC 1122 compliant behavior (an ACK for at least every second + segment). Values above 2 reduce the ACK traffic at the cost of + a coarser ACK clock and slower peer congestion window growth. + + NOTE: RFC 1122 Section 4.2.3.2 states that in a stream of + full-sized segments there SHOULD be an ACK for at least every + second segment. Setting this value above 2 no longer meets + that requirement, but the delayed ACK timer still limits the + ACK delay to 0.5 seconds as required by RFC 1122. + config NET_TCP_KEEPALIVE bool "TCP/IP Keep-alive support" default n diff --git a/net/tcp/tcp_appsend.c b/net/tcp/tcp_appsend.c index 98fa10d6313d1..497c8daa5e652 100644 --- a/net/tcp/tcp_appsend.c +++ b/net/tcp/tcp_appsend.c @@ -118,19 +118,22 @@ void tcp_appsend(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, * NOTES: * 1. If there is a data payload or other flags to be sent with the * outgoing packet, then we may as well include the ACK too. - * 2. The RFC refers to full-size segments. It is not clear what + * 2. The ACK threshold is CONFIG_NET_TCP_ACK_FREQUENCY segments. + * The default value of 2 preserves the RFC 1122 behavior (an + * ACK for at least every second segment). + * 3. The RFC refers to full-size segments. It is not clear what * "full-size" means. Does that mean that the payload is the size * of the MSS? Payload size is not considered other there being * a payload or or not. Should there be some special action for * small payloads of size < MSS? - * 3. Experimentation shows that Windows and Linux behave somewhat + * 4. Experimentation shows that Windows and Linux behave somewhat * differently; they delay the ACKs for many more segments (6 or * more). Delaying for more segments would provide less network * traffic and better performance but seems non-compliant. */ - if (conn->rx_unackseg > 0 || dev->d_sndlen > 0 || - result != TCP_SNDACK) + if (conn->rx_unackseg >= CONFIG_NET_TCP_ACK_FREQUENCY - 1 || + dev->d_sndlen > 0 || result != TCP_SNDACK) { /* Reset the delayed ACK state and send the ACK with this packet. */ @@ -138,12 +141,11 @@ void tcp_appsend(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, } else { - /* This is only an ACK and there is no pending delayed ACK and - * no TX data is being sent. Indicate that there is one un-ACKed - * segment and don't send anything now. + /* This is only an ACK and there is no TX data being sent. + * Count one more un-ACKed segment and don't send anything now. */ - conn->rx_unackseg = 1; + conn->rx_unackseg++; return; } } @@ -224,21 +226,21 @@ void tcp_appsend(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, { #endif - /* If d_sndlen > 0, the application has data to be sent. */ + /* If d_sndlen > 0, the application has data to be sent. */ - if (dev->d_sndlen > 0) - { - /* Remember how much data we send out now so that we know - * when everything has been acknowledged. Just increment the - * amount of data sent. This will be needed in sequence number - * calculations and we know that this is not a re-transmission. - * Retransmissions do not go through this path. - */ + if (dev->d_sndlen > 0) + { + /* Remember how much data we send out now so that we know + * when everything has been acknowledged. Just increment the + * amount of data sent. This will be needed in sequence number + * calculations and we know that this is not a re-transmission. + * Retransmissions do not go through this path. + */ - conn->tx_unacked += dev->d_sndlen; - } + conn->tx_unacked += dev->d_sndlen; + } - conn->nrtx = 0; + conn->nrtx = 0; #ifdef CONFIG_NET_TCP_WRITE_BUFFERS }