net: allow setting ecn via routing table
authorFlorian Westphal <fw@strlen.de>
Mon, 3 Nov 2014 16:35:03 +0000 (17:35 +0100)
committerDavid S. Miller <davem@davemloft.net>
Tue, 4 Nov 2014 21:06:09 +0000 (16:06 -0500)
This patch allows to set ECN on a per-route basis in case the sysctl
tcp_ecn is not set to 1. In other words, when ECN is set for specific
routes, it provides a tcp_ecn=1 behaviour for that route while the rest
of the stack acts according to the global settings.

One can use 'ip route change dev $dev $net features ecn' to toggle this.

Having a more fine-grained per-route setting can be beneficial for various
reasons, for example, 1) within data centers, or 2) local ISPs may deploy
ECN support for their own video/streaming services [1], etc.

There was a recent measurement study/paper [2] which scanned the Alexa's
publicly available top million websites list from a vantage point in US,
Europe and Asia:

Half of the Alexa list will now happily use ECN (tcp_ecn=2, most likely
blamed to commit 255cac91c3 ("tcp: extend ECN sysctl to allow server-side
only ECN") ;)); the break in connectivity on-path was found is about
1 in 10,000 cases. Timeouts rather than receiving back RSTs were much
more common in the negotiation phase (and mostly seen in the Alexa
middle band, ranks around 50k-150k): from 12-thousand hosts on which
there _may_ be ECN-linked connection failures, only 79 failed with RST
when _not_ failing with RST when ECN is not requested.

It's unclear though, how much equipment in the wild actually marks CE
when buffers start to fill up.

We thought about a fallback to non-ECN for retransmitted SYNs as another
global option (which could perhaps one day be made default), but as Eric
points out, there's much more work needed to detect broken middleboxes.

Two examples Eric mentioned are buggy firewalls that accept only a single
SYN per flow, and middleboxes that successfully let an ECN flow establish,
but later mark CE for all packets (so cwnd converges to 1).

 [1] http://www.ietf.org/proceedings/89/slides/slides-89-tsvarea-1.pdf, p.15
 [2] http://ecn.ethz.ch/

Joint work with Daniel Borkmann.

Reference: http://thread.gmane.org/gmane.linux.network/335797
Suggested-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/tcp.h
net/ipv4/syncookies.c
net/ipv4/tcp_input.c
net/ipv4/tcp_output.c
net/ipv6/syncookies.c

index 36c5084964cd8aeafc87b7ad4bdd77d541d04a5d..f50f29faf76f1fbcc5237de63c76f7c8200f4a6b 100644 (file)
@@ -493,7 +493,7 @@ __u32 cookie_v4_init_sequence(struct sock *sk, const struct sk_buff *skb,
 __u32 cookie_init_timestamp(struct request_sock *req);
 bool cookie_timestamp_decode(struct tcp_options_received *opt);
 bool cookie_ecn_ok(const struct tcp_options_received *opt,
-                  const struct net *net);
+                  const struct net *net, const struct dst_entry *dst);
 
 /* From net/ipv6/syncookies.c */
 int __cookie_v6_check(const struct ipv6hdr *iph, const struct tcphdr *th,
index 6de772500ee9552028ef0098f4ab87ba4ca18f21..45fe60c5238e99d0a639ecf4698cddab141f0fdd 100644 (file)
@@ -273,7 +273,7 @@ bool cookie_timestamp_decode(struct tcp_options_received *tcp_opt)
 EXPORT_SYMBOL(cookie_timestamp_decode);
 
 bool cookie_ecn_ok(const struct tcp_options_received *tcp_opt,
-                  const struct net *net)
+                  const struct net *net, const struct dst_entry *dst)
 {
        bool ecn_ok = tcp_opt->rcv_tsecr & TS_OPT_ECN;
 
@@ -283,7 +283,7 @@ bool cookie_ecn_ok(const struct tcp_options_received *tcp_opt,
        if (net->ipv4.sysctl_tcp_ecn)
                return true;
 
-       return false;
+       return dst_feature(dst, RTAX_FEATURE_ECN);
 }
 EXPORT_SYMBOL(cookie_ecn_ok);
 
@@ -387,7 +387,7 @@ struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb)
                                  dst_metric(&rt->dst, RTAX_INITRWND));
 
        ireq->rcv_wscale  = rcv_wscale;
-       ireq->ecn_ok = cookie_ecn_ok(&tcp_opt, sock_net(sk));
+       ireq->ecn_ok = cookie_ecn_ok(&tcp_opt, sock_net(sk), &rt->dst);
 
        ret = get_cookie_sock(sk, skb, req, &rt->dst);
        /* ip_queue_xmit() depends on our flow being setup
index 4e4617e90417b3174ffcbe1ec69b92bb3dff4a33..196b4388116cc23b777d48c4dfa53c1bf7cde6a7 100644 (file)
@@ -5876,20 +5876,22 @@ static inline void pr_drop_req(struct request_sock *req, __u16 port, int family)
  */
 static void tcp_ecn_create_request(struct request_sock *req,
                                   const struct sk_buff *skb,
-                                  const struct sock *listen_sk)
+                                  const struct sock *listen_sk,
+                                  const struct dst_entry *dst)
 {
        const struct tcphdr *th = tcp_hdr(skb);
        const struct net *net = sock_net(listen_sk);
        bool th_ecn = th->ece && th->cwr;
-       bool ect, need_ecn;
+       bool ect, need_ecn, ecn_ok;
 
        if (!th_ecn)
                return;
 
        ect = !INET_ECN_is_not_ect(TCP_SKB_CB(skb)->ip_dsfield);
        need_ecn = tcp_ca_needs_ecn(listen_sk);
+       ecn_ok = net->ipv4.sysctl_tcp_ecn || dst_feature(dst, RTAX_FEATURE_ECN);
 
-       if (!ect && !need_ecn && net->ipv4.sysctl_tcp_ecn)
+       if (!ect && !need_ecn && ecn_ok)
                inet_rsk(req)->ecn_ok = 1;
        else if (ect && need_ecn)
                inet_rsk(req)->ecn_ok = 1;
@@ -5954,13 +5956,7 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
        if (security_inet_conn_request(sk, skb, req))
                goto drop_and_free;
 
-       if (!want_cookie || tmp_opt.tstamp_ok)
-               tcp_ecn_create_request(req, skb, sk);
-
-       if (want_cookie) {
-               isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
-               req->cookie_ts = tmp_opt.tstamp_ok;
-       } else if (!isn) {
+       if (!want_cookie && !isn) {
                /* VJ's idea. We save last timestamp seen
                 * from the destination in peer table, when entering
                 * state TIME-WAIT, and check against it before
@@ -6008,6 +6004,15 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
                        goto drop_and_free;
        }
 
+       tcp_ecn_create_request(req, skb, sk, dst);
+
+       if (want_cookie) {
+               isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
+               req->cookie_ts = tmp_opt.tstamp_ok;
+               if (!tmp_opt.tstamp_ok)
+                       inet_rsk(req)->ecn_ok = 0;
+       }
+
        tcp_rsk(req)->snt_isn = isn;
        tcp_openreq_init_rwin(req, sk, dst);
        fastopen = !want_cookie &&
index a3d453b94747c22dd0fad07dfe606fdd11c414b1..0b88158dd4a70d5007e79f0d8251fee2f7c6c7f8 100644 (file)
@@ -333,10 +333,19 @@ static void tcp_ecn_send_synack(struct sock *sk, struct sk_buff *skb)
 static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb)
 {
        struct tcp_sock *tp = tcp_sk(sk);
+       bool use_ecn = sock_net(sk)->ipv4.sysctl_tcp_ecn == 1 ||
+                      tcp_ca_needs_ecn(sk);
+
+       if (!use_ecn) {
+               const struct dst_entry *dst = __sk_dst_get(sk);
+
+               if (dst && dst_feature(dst, RTAX_FEATURE_ECN))
+                       use_ecn = true;
+       }
 
        tp->ecn_flags = 0;
-       if (sock_net(sk)->ipv4.sysctl_tcp_ecn == 1 ||
-           tcp_ca_needs_ecn(sk)) {
+
+       if (use_ecn) {
                TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ECE | TCPHDR_CWR;
                tp->ecn_flags = TCP_ECN_OK;
                if (tcp_ca_needs_ecn(sk))
index 52cc8cb02c0c00aae17980f8d326fd7b761a26cc..7337fc7947e2eba2c5e6eaccbc9cfd660d3a0ccd 100644 (file)
@@ -262,7 +262,7 @@ struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb)
                                  dst_metric(dst, RTAX_INITRWND));
 
        ireq->rcv_wscale = rcv_wscale;
-       ireq->ecn_ok = cookie_ecn_ok(&tcp_opt, sock_net(sk));
+       ireq->ecn_ok = cookie_ecn_ok(&tcp_opt, sock_net(sk), dst);
 
        ret = get_cookie_sock(sk, skb, req, dst);
 out: