]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tcp: accecn: detect loss ACK w/ AccECN option and add TCP_ACCECN_OPTION_PERSIST
authorChia-Yu Chang <chia-yu.chang@nokia-bell-labs.com>
Sat, 31 Jan 2026 22:25:12 +0000 (23:25 +0100)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 3 Feb 2026 14:13:25 +0000 (15:13 +0100)
Detect spurious retransmission of a previously sent ACK carrying the
AccECN option after the second retransmission. Since this might be caused
by the middlebox dropping ACK with options it does not recognize, disable
the sending of the AccECN option in all subsequent ACKs. This patch
follows Section 3.2.3.2.2 of AccECN spec (RFC9768), and a new field
(accecn_opt_sent_w_dsack) is added to indicate that an AccECN option was
sent with duplicate SACK info.

Also, a new AccECN option sending mode is added to tcp_ecn_option sysctl:
(TCP_ECN_OPTION_PERSIST), which ignores the AccECN fallback policy and
persistently sends AccECN option once it fits into TCP option space.

Signed-off-by: Chia-Yu Chang <chia-yu.chang@nokia-bell-labs.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260131222515.8485-13-chia-yu.chang@nokia-bell-labs.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Documentation/networking/ip-sysctl.rst
include/linux/tcp.h
include/net/tcp_ecn.h
net/ipv4/sysctl_net_ipv4.c
net/ipv4/tcp_input.c
net/ipv4/tcp_output.c

index bc9a01606daf5a3c86477d74fdc8565eb44fd508..28c7e4f5ecf9e6b7519659e4e3eee7da508ded29 100644 (file)
@@ -482,7 +482,9 @@ tcp_ecn_option - INTEGER
        1 Send AccECN option sparingly according to the minimum option
          rules outlined in draft-ietf-tcpm-accurate-ecn.
        2 Send AccECN option on every packet whenever it fits into TCP
-         option space.
+         option space except when AccECN fallback is triggered.
+       3 Send AccECN option on every packet whenever it fits into TCP
+         option space even when AccECN fallback is triggered.
        = ============================================================
 
        Default: 2
index fbc514d582e7dc95743b93e846babf7dfe38ccc9..f72eef31fa23cc584f2f0cefacdc35cae43aa52d 100644 (file)
@@ -291,7 +291,8 @@ struct tcp_sock {
        u8      nonagle     : 4,/* Disable Nagle algorithm?             */
                rate_app_limited:1;  /* rate_{delivered,interval_us} limited? */
        u8      received_ce_pending:4, /* Not yet transmit cnt of received_ce */
-               unused2:4;
+               accecn_opt_sent_w_dsack:1,/* Sent ACCECN opt in previous ACK w/ D-SACK */
+               unused2:3;
        u8      accecn_minlen:2,/* Minimum length of AccECN option sent */
                est_ecnfield:2,/* ECN field for AccECN delivered estimates */
                accecn_opt_demand:2,/* Demand AccECN option for n next ACKs */
index 49e0b865fe028026a302d513633c809b86b926b7..e01653bbf181b3960b393f7651630f9219d1e58d 100644 (file)
@@ -29,6 +29,7 @@ enum tcp_accecn_option {
        TCP_ACCECN_OPTION_DISABLED = 0,
        TCP_ACCECN_OPTION_MINIMUM = 1,
        TCP_ACCECN_OPTION_FULL = 2,
+       TCP_ACCECN_OPTION_PERSIST = 3,
 };
 
 /* Apply either ECT(0) or ECT(1) based on TCP_CONG_ECT_1_NEGOTIATION flag */
@@ -406,6 +407,7 @@ static inline void tcp_accecn_init_counters(struct tcp_sock *tp)
        tp->received_ce_pending = 0;
        __tcp_accecn_init_bytes_counters(tp->received_ecn_bytes);
        __tcp_accecn_init_bytes_counters(tp->delivered_ecn_bytes);
+       tp->accecn_opt_sent_w_dsack = 0;
        tp->accecn_minlen = 0;
        tp->accecn_opt_demand = 0;
        tp->est_ecnfield = 0;
index a1a50a5c80dc11eac19aa5c4d2ef0d48ab12a071..385b5b986d23fe815e9cd42c9eed4113dd0ad9a1 100644 (file)
@@ -749,7 +749,7 @@ static struct ctl_table ipv4_net_table[] = {
                .mode           = 0644,
                .proc_handler   = proc_dou8vec_minmax,
                .extra1         = SYSCTL_ZERO,
-               .extra2         = SYSCTL_TWO,
+               .extra2         = SYSCTL_THREE,
        },
        {
                .procname       = "tcp_ecn_option_beacon",
index 988d161e9918c7d79d9b62a3e68de559319484e0..89526f0f23011e49de5e172b33f12712df9c3945 100644 (file)
@@ -5046,8 +5046,11 @@ static void tcp_dsack_extend(struct sock *sk, u32 seq, u32 end_seq)
                tcp_sack_extend(tp->duplicate_sack, seq, end_seq);
 }
 
-static void tcp_rcv_spurious_retrans(struct sock *sk, const struct sk_buff *skb)
+static void tcp_rcv_spurious_retrans(struct sock *sk,
+                                    const struct sk_buff *skb)
 {
+       struct tcp_sock *tp = tcp_sk(sk);
+
        /* When the ACK path fails or drops most ACKs, the sender would
         * timeout and spuriously retransmit the same segment repeatedly.
         * If it seems our ACKs are not reaching the other side,
@@ -5067,6 +5070,14 @@ static void tcp_rcv_spurious_retrans(struct sock *sk, const struct sk_buff *skb)
        /* Save last flowlabel after a spurious retrans. */
        tcp_save_lrcv_flowlabel(sk, skb);
 #endif
+       /* Check DSACK info to detect that the previous ACK carrying the
+        * AccECN option was lost after the second retransmision, and then
+        * stop sending AccECN option in all subsequent ACKs.
+        */
+       if (tcp_ecn_mode_accecn(tp) &&
+           tp->accecn_opt_sent_w_dsack &&
+           TCP_SKB_CB(skb)->seq == tp->duplicate_sack[0].start_seq)
+               tcp_accecn_fail_mode_set(tp, TCP_ACCECN_OPT_FAIL_SEND);
 }
 
 static void tcp_send_dupack(struct sock *sk, const struct sk_buff *skb)
index 2b356fdbf2ca3907183822925ed2fce39155c033..f44d60d13b9ff4d59b05be76c4c3001142451df7 100644 (file)
@@ -715,9 +715,12 @@ static void tcp_options_write(struct tcphdr *th, struct tcp_sock *tp,
                if (tp) {
                        tp->accecn_minlen = 0;
                        tp->accecn_opt_tstamp = tp->tcp_mstamp;
+                       tp->accecn_opt_sent_w_dsack = tp->rx_opt.dsack;
                        if (tp->accecn_opt_demand)
                                tp->accecn_opt_demand--;
                }
+       } else if (tp) {
+               tp->accecn_opt_sent_w_dsack = 0;
        }
 
        if (unlikely(OPTION_SACK_ADVERTISE & options)) {
@@ -1189,7 +1192,9 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
        if (tcp_ecn_mode_accecn(tp)) {
                int ecn_opt = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_ecn_option);
 
-               if (ecn_opt && tp->saw_accecn_opt && !tcp_accecn_opt_fail_send(tp) &&
+               if (ecn_opt && tp->saw_accecn_opt &&
+                   (ecn_opt >= TCP_ACCECN_OPTION_PERSIST ||
+                    !tcp_accecn_opt_fail_send(tp)) &&
                    (ecn_opt >= TCP_ACCECN_OPTION_FULL || tp->accecn_opt_demand ||
                     tcp_accecn_option_beacon_check(sk))) {
                        opts->use_synack_ecn_bytes = 0;