]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tcp: accecn: handle unexpected AccECN negotiation feedback
authorChia-Yu Chang <chia-yu.chang@nokia-bell-labs.com>
Sat, 31 Jan 2026 22:25:06 +0000 (23:25 +0100)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 3 Feb 2026 14:13:24 +0000 (15:13 +0100)
According to Sections 3.1.2 and 3.1.3 of AccECN spec (RFC9768).

In Section 3.1.2, it says an AccECN implementation has no need to
recognize or support the Server response labelled 'Nonce' or ECN-nonce
feedback more generally, as RFC 3540 has been reclassified as Historic.
AccECN is compatible with alternative ECN feedback integrity approaches
to the nonce. The SYN/ACK labelled 'Nonce' with (AE,CWR,ECE) = (1,0,1)
is reserved for future use. A TCP Client (A) that receives such a SYN/ACK
follows the procedure for forward compatibility given in Section 3.1.3.

Then in Section 3.1.3, it says if a TCP Client has sent a SYN requesting
AccECN feedback with (AE,CWR,ECE) = (1,1,1) then receives a SYN/ACK with
the currently reserved combination (AE,CWR,ECE) = (1,0,1) but it does not
have logic specific to such a combination, the Client MUST enable AccECN
mode as if the SYN/ACK onfirmed that the Server supported AccECN and as
if it fed back that the IP-ECN field on the SYN had arrived unchanged.

Fixes: 3cae34274c79 ("tcp: accecn: AccECN negotiation").
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-7-chia-yu.chang@nokia-bell-labs.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
include/net/tcp_ecn.h

index 2e1637edf1d3c7f78b707bddbf124cc100c5f05e..a709fb1756eb758e77bc4bfab4258ecaca1c4527 100644 (file)
@@ -473,6 +473,26 @@ static inline u8 tcp_accecn_option_init(const struct sk_buff *skb,
        return TCP_ACCECN_OPT_COUNTER_SEEN;
 }
 
+static inline void tcp_ecn_rcv_synack_accecn(struct sock *sk,
+                                            const struct sk_buff *skb, u8 dsf)
+{
+       struct tcp_sock *tp = tcp_sk(sk);
+
+       tcp_ecn_mode_set(tp, TCP_ECN_MODE_ACCECN);
+       tp->syn_ect_rcv = dsf & INET_ECN_MASK;
+       /* Demand Accurate ECN option in response to the SYN on the SYN/ACK
+        * and the TCP server will try to send one more packet with an AccECN
+        * Option at a later point during the connection.
+        */
+       if (tp->rx_opt.accecn &&
+           tp->saw_accecn_opt < TCP_ACCECN_OPT_COUNTER_SEEN) {
+               u8 saw_opt = tcp_accecn_option_init(skb, tp->rx_opt.accecn);
+
+               tcp_accecn_saw_opt_fail_recv(tp, saw_opt);
+               tp->accecn_opt_demand = 2;
+       }
+}
+
 /* See Table 2 of the AccECN draft */
 static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb,
                                      const struct tcphdr *th, u8 ip_dsfield)
@@ -495,13 +515,11 @@ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb
                tcp_ecn_mode_set(tp, TCP_ECN_DISABLED);
                break;
        case 0x1:
-       case 0x5:
                /* +========+========+============+=============+
                 * | A      | B      |  SYN/ACK   |  Feedback   |
                 * |        |        |    B->A    |  Mode of A  |
                 * |        |        | AE CWR ECE |             |
                 * +========+========+============+=============+
-                * | AccECN | Nonce  | 1   0   1  | (Reserved)  |
                 * | AccECN | ECN    | 0   0   1  | Classic ECN |
                 * | Nonce  | AccECN | 0   0   1  | Classic ECN |
                 * | ECN    | AccECN | 0   0   1  | Classic ECN |
@@ -509,20 +527,20 @@ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb
                 */
                if (tcp_ca_no_fallback_rfc3168(sk))
                        tcp_ecn_mode_set(tp, TCP_ECN_DISABLED);
-               else if (tcp_ecn_mode_pending(tp))
-                       /* Downgrade from AccECN, or requested initially */
+               else
                        tcp_ecn_mode_set(tp, TCP_ECN_MODE_RFC3168);
                break;
-       default:
-               tcp_ecn_mode_set(tp, TCP_ECN_MODE_ACCECN);
-               tp->syn_ect_rcv = ip_dsfield & INET_ECN_MASK;
-               if (tp->rx_opt.accecn &&
-                   tp->saw_accecn_opt < TCP_ACCECN_OPT_COUNTER_SEEN) {
-                       u8 saw_opt = tcp_accecn_option_init(skb, tp->rx_opt.accecn);
-
-                       tcp_accecn_saw_opt_fail_recv(tp, saw_opt);
-                       tp->accecn_opt_demand = 2;
+       case 0x5:
+               if (tcp_ecn_mode_pending(tp)) {
+                       tcp_ecn_rcv_synack_accecn(sk, skb, ip_dsfield);
+                       if (INET_ECN_is_ce(ip_dsfield)) {
+                               tp->received_ce++;
+                               tp->received_ce_pending++;
+                       }
                }
+               break;
+       default:
+               tcp_ecn_rcv_synack_accecn(sk, skb, ip_dsfield);
                if (INET_ECN_is_ce(ip_dsfield) &&
                    tcp_accecn_validate_syn_feedback(sk, ace,
                                                     tp->syn_ect_snt)) {