TCPCT part 1d: define TCP cookie option, extend existing struct's
William Allen Simpson [Wed, 2 Dec 2009 18:17:05 +0000 (18:17 +0000)]
Data structures are carefully composed to require minimal additions.
For example, the struct tcp_options_received cookie_plus variable fits
between existing 16-bit and 8-bit variables, requiring no additional
space (taking alignment into consideration).  There are no additions to
tcp_request_sock, and only 1 pointer in tcp_sock.

This is a significantly revised implementation of an earlier (year-old)
patch that no longer applies cleanly, with permission of the original
author (Adam Langley):

    http://thread.gmane.org/gmane.linux.network/102586

The principle difference is using a TCP option to carry the cookie nonce,
instead of a user configured offset in the data.  This is more flexible and
less subject to user configuration error.  Such a cookie option has been
suggested for many years, and is also useful without SYN data, allowing
several related concepts to use the same extension option.

    "Re: SYN floods (was: does history repeat itself?)", September 9, 1996.
    http://www.merit.net/mail.archives/nanog/1996-09/msg00235.html

    "Re: what a new TCP header might look like", May 12, 1998.
    ftp://ftp.isi.edu/end2end/end2end-interest-1998.mail

These functions will also be used in subsequent patches that implement
additional features.

Requires:
   TCPCT part 1a: add request_values parameter for sending SYNACK
   TCPCT part 1b: generate Responder Cookie secret
   TCPCT part 1c: sysctl_tcp_cookie_size, socket option TCP_COOKIE_TRANSACTIONS

Signed-off-by: William.Allen.Simpson@gmail.com
Signed-off-by: David S. Miller <davem@davemloft.net>

include/linux/tcp.h
include/net/tcp.h
net/ipv4/tcp_ipv4.c
net/ipv4/tcp_minisocks.c
net/ipv6/tcp_ipv6.c

index eaa3113..7fee8a4 100644 (file)
@@ -247,31 +247,38 @@ struct tcp_options_received {
                sack_ok : 4,    /* SACK seen on SYN packet              */
                snd_wscale : 4, /* Window scaling received from sender  */
                rcv_wscale : 4; /* Window scaling to send to receiver   */
-/*     SACKs data      */
+       u8      cookie_plus:6,  /* bytes in authenticator/cookie option */
+               cookie_out_never:1,
+               cookie_in_always:1;
        u8      num_sacks;      /* Number of SACK blocks                */
-       u16     user_mss;       /* mss requested by user in ioctl */
+       u16     user_mss;       /* mss requested by user in ioctl       */
        u16     mss_clamp;      /* Maximal mss, negotiated at connection setup */
 };
 
 static inline void tcp_clear_options(struct tcp_options_received *rx_opt)
 {
-       rx_opt->tstamp_ok = rx_opt->sack_ok = rx_opt->wscale_ok = rx_opt->snd_wscale = 0;
+       rx_opt->tstamp_ok = rx_opt->sack_ok = 0;
+       rx_opt->wscale_ok = rx_opt->snd_wscale = 0;
+       rx_opt->cookie_plus = 0;
 }
 
 /* This is the max number of SACKS that we'll generate and process. It's safe
- * to increse this, although since:
+ * to increase this, although since:
  *   size = TCPOLEN_SACK_BASE_ALIGNED (4) + n * TCPOLEN_SACK_PERBLOCK (8)
  * only four options will fit in a standard TCP header */
 #define TCP_NUM_SACKS 4
 
+struct tcp_cookie_values;
+struct tcp_request_sock_ops;
+
 struct tcp_request_sock {
        struct inet_request_sock        req;
 #ifdef CONFIG_TCP_MD5SIG
        /* Only used by TCP MD5 Signature so far. */
        const struct tcp_request_sock_ops *af_specific;
 #endif
-       u32                             rcv_isn;
-       u32                             snt_isn;
+       u32                             rcv_isn;
+       u32                             snt_isn;
 };
 
 static inline struct tcp_request_sock *tcp_rsk(const struct request_sock *req)
@@ -441,6 +448,12 @@ struct tcp_sock {
 /* TCP MD5 Signature Option information */
        struct tcp_md5sig_info  *md5sig_info;
 #endif
+
+       /* When the cookie options are generated and exchanged, then this
+        * object holds a reference to them (cookie_values->kref).  Also
+        * contains related tcp_cookie_transactions fields.
+        */
+       struct tcp_cookie_values  *cookie_values;
 };
 
 static inline struct tcp_sock *tcp_sk(const struct sock *sk)
@@ -459,6 +472,10 @@ struct tcp_timewait_sock {
        u16                       tw_md5_keylen;
        u8                        tw_md5_key[TCP_MD5SIG_MAXKEYLEN];
 #endif
+       /* Few sockets in timewait have cookies; in that case, then this
+        * object holds a reference to them (tw_cookie_values->kref).
+        */
+       struct tcp_cookie_values  *tw_cookie_values;
 };
 
 static inline struct tcp_timewait_sock *tcp_twsk(const struct sock *sk)
index 738b65f..f9abd9b 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/dmaengine.h>
 #include <linux/crypto.h>
 #include <linux/cryptohash.h>
+#include <linux/kref.h>
 
 #include <net/inet_connection_sock.h>
 #include <net/inet_timewait_sock.h>
@@ -164,6 +165,7 @@ extern void tcp_time_wait(struct sock *sk, int state, int timeo);
 #define TCPOPT_SACK             5       /* SACK Block */
 #define TCPOPT_TIMESTAMP       8       /* Better RTT estimations/PAWS */
 #define TCPOPT_MD5SIG          19      /* MD5 Signature (RFC2385) */
+#define TCPOPT_COOKIE          253     /* Cookie extension (experimental) */
 
 /*
  *     TCP option lengths
@@ -174,6 +176,10 @@ extern void tcp_time_wait(struct sock *sk, int state, int timeo);
 #define TCPOLEN_SACK_PERM      2
 #define TCPOLEN_TIMESTAMP      10
 #define TCPOLEN_MD5SIG         18
+#define TCPOLEN_COOKIE_BASE    2       /* Cookie-less header extension */
+#define TCPOLEN_COOKIE_PAIR    3       /* Cookie pair header extension */
+#define TCPOLEN_COOKIE_MIN     (TCPOLEN_COOKIE_BASE+TCP_COOKIE_MIN)
+#define TCPOLEN_COOKIE_MAX     (TCPOLEN_COOKIE_BASE+TCP_COOKIE_MAX)
 
 /* But this is what stacks really send out. */
 #define TCPOLEN_TSTAMP_ALIGNED         12
@@ -1482,6 +1488,83 @@ struct tcp_request_sock_ops {
 
 extern int tcp_cookie_generator(u32 *bakery);
 
+/**
+ *     struct tcp_cookie_values - each socket needs extra space for the
+ *     cookies, together with (optional) space for any SYN data.
+ *
+ *     A tcp_sock contains a pointer to the current value, and this is
+ *     cloned to the tcp_timewait_sock.
+ *
+ * @cookie_pair:       variable data from the option exchange.
+ *
+ * @cookie_desired:    user specified tcpct_cookie_desired.  Zero
+ *                     indicates default (sysctl_tcp_cookie_size).
+ *                     After cookie sent, remembers size of cookie.
+ *                     Range 0, TCP_COOKIE_MIN to TCP_COOKIE_MAX.
+ *
+ * @s_data_desired:    user specified tcpct_s_data_desired.  When the
+ *                     constant payload is specified (@s_data_constant),
+ *                     holds its length instead.
+ *                     Range 0 to TCP_MSS_DESIRED.
+ *
+ * @s_data_payload:    constant data that is to be included in the
+ *                     payload of SYN or SYNACK segments when the
+ *                     cookie option is present.
+ */
+struct tcp_cookie_values {
+       struct kref     kref;
+       u8              cookie_pair[TCP_COOKIE_PAIR_SIZE];
+       u8              cookie_pair_size;
+       u8              cookie_desired;
+       u16             s_data_desired:11,
+                       s_data_constant:1,
+                       s_data_in:1,
+                       s_data_out:1,
+                       s_data_unused:2;
+       u8              s_data_payload[0];
+};
+
+static inline void tcp_cookie_values_release(struct kref *kref)
+{
+       kfree(container_of(kref, struct tcp_cookie_values, kref));
+}
+
+/* The length of constant payload data.  Note that s_data_desired is
+ * overloaded, depending on s_data_constant: either the length of constant
+ * data (returned here) or the limit on variable data.
+ */
+static inline int tcp_s_data_size(const struct tcp_sock *tp)
+{
+       return (tp->cookie_values != NULL && tp->cookie_values->s_data_constant)
+               ? tp->cookie_values->s_data_desired
+               : 0;
+}
+
+/**
+ *     struct tcp_extend_values - tcp_ipv?.c to tcp_output.c workspace.
+ *
+ *     As tcp_request_sock has already been extended in other places, the
+ *     only remaining method is to pass stack values along as function
+ *     parameters.  These parameters are not needed after sending SYNACK.
+ *
+ * @cookie_bakery:     cryptographic secret and message workspace.
+ *
+ * @cookie_plus:       bytes in authenticator/cookie option, copied from
+ *                     struct tcp_options_received (above).
+ */
+struct tcp_extend_values {
+       struct request_values           rv;
+       u32                             cookie_bakery[COOKIE_WORKSPACE_WORDS];
+       u8                              cookie_plus:6,
+                                       cookie_out_never:1,
+                                       cookie_in_always:1;
+};
+
+static inline struct tcp_extend_values *tcp_xv(struct request_values *rvp)
+{
+       return (struct tcp_extend_values *)rvp;
+}
+
 extern void tcp_v4_init(void);
 extern void tcp_init(void);
 
index 649a36d..a2bcac9 100644 (file)
@@ -1833,6 +1833,19 @@ static int tcp_v4_init_sock(struct sock *sk)
        tp->af_specific = &tcp_sock_ipv4_specific;
 #endif
 
+       /* TCP Cookie Transactions */
+       if (sysctl_tcp_cookie_size > 0) {
+               /* Default, cookies without s_data_payload. */
+               tp->cookie_values =
+                       kzalloc(sizeof(*tp->cookie_values),
+                               sk->sk_allocation);
+               if (tp->cookie_values != NULL)
+                       kref_init(&tp->cookie_values->kref);
+       }
+       /* Presumed zeroed, in order of appearance:
+        *      cookie_in_always, cookie_out_never,
+        *      s_data_constant, s_data_in, s_data_out
+        */
        sk->sk_sndbuf = sysctl_tcp_wmem[1];
        sk->sk_rcvbuf = sysctl_tcp_rmem[1];
 
@@ -1886,6 +1899,13 @@ void tcp_v4_destroy_sock(struct sock *sk)
                sk->sk_sndmsg_page = NULL;
        }
 
+       /* TCP Cookie Transactions */
+       if (tp->cookie_values != NULL) {
+               kref_put(&tp->cookie_values->kref,
+                        tcp_cookie_values_release);
+               tp->cookie_values = NULL;
+       }
+
        percpu_counter_dec(&tcp_sockets_allocated);
 }
 
index d3f6bbf..96852af 100644 (file)
@@ -383,14 +383,43 @@ struct sock *tcp_create_openreq_child(struct sock *sk, struct request_sock *req,
                const struct inet_request_sock *ireq = inet_rsk(req);
                struct tcp_request_sock *treq = tcp_rsk(req);
                struct inet_connection_sock *newicsk = inet_csk(newsk);
-               struct tcp_sock *newtp;
+               struct tcp_sock *newtp = tcp_sk(newsk);
+               struct tcp_sock *oldtp = tcp_sk(sk);
+               struct tcp_cookie_values *oldcvp = oldtp->cookie_values;
+
+               /* TCP Cookie Transactions require space for the cookie pair,
+                * as it differs for each connection.  There is no need to
+                * copy any s_data_payload stored at the original socket.
+                * Failure will prevent resuming the connection.
+                *
+                * Presumed copied, in order of appearance:
+                *      cookie_in_always, cookie_out_never
+                */
+               if (oldcvp != NULL) {
+                       struct tcp_cookie_values *newcvp =
+                               kzalloc(sizeof(*newtp->cookie_values),
+                                       GFP_ATOMIC);
+
+                       if (newcvp != NULL) {
+                               kref_init(&newcvp->kref);
+                               newcvp->cookie_desired =
+                                               oldcvp->cookie_desired;
+                               newtp->cookie_values = newcvp;
+                       } else {
+                               /* Not Yet Implemented */
+                               newtp->cookie_values = NULL;
+                       }
+               }
 
                /* Now setup tcp_sock */
-               newtp = tcp_sk(newsk);
                newtp->pred_flags = 0;
-               newtp->rcv_wup = newtp->copied_seq = newtp->rcv_nxt = treq->rcv_isn + 1;
-               newtp->snd_sml = newtp->snd_una = newtp->snd_nxt = treq->snt_isn + 1;
-               newtp->snd_up = treq->snt_isn + 1;
+
+               newtp->rcv_wup = newtp->copied_seq =
+               newtp->rcv_nxt = treq->rcv_isn + 1;
+
+               newtp->snd_sml = newtp->snd_una =
+               newtp->snd_nxt = newtp->snd_up =
+                       treq->snt_isn + 1 + tcp_s_data_size(oldtp);
 
                tcp_prequeue_init(newtp);
 
@@ -423,8 +452,8 @@ struct sock *tcp_create_openreq_child(struct sock *sk, struct request_sock *req,
                tcp_set_ca_state(newsk, TCP_CA_Open);
                tcp_init_xmit_timers(newsk);
                skb_queue_head_init(&newtp->out_of_order_queue);
-               newtp->write_seq = treq->snt_isn + 1;
-               newtp->pushed_seq = newtp->write_seq;
+               newtp->write_seq = newtp->pushed_seq =
+                       treq->snt_isn + 1 + tcp_s_data_size(oldtp);
 
                newtp->rx_opt.saw_tstamp = 0;
 
@@ -590,7 +619,8 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
         * Invalid ACK: reset will be sent by listening socket
         */
        if ((flg & TCP_FLAG_ACK) &&
-           (TCP_SKB_CB(skb)->ack_seq != tcp_rsk(req)->snt_isn + 1))
+           (TCP_SKB_CB(skb)->ack_seq !=
+            tcp_rsk(req)->snt_isn + 1 + tcp_s_data_size(tcp_sk(sk))))
                return sk;
 
        /* Also, it would be not so bad idea to check rcv_tsecr, which
index da6e244..f2ec382 100644 (file)
@@ -1864,6 +1864,19 @@ static int tcp_v6_init_sock(struct sock *sk)
        tp->af_specific = &tcp_sock_ipv6_specific;
 #endif
 
+       /* TCP Cookie Transactions */
+       if (sysctl_tcp_cookie_size > 0) {
+               /* Default, cookies without s_data_payload. */
+               tp->cookie_values =
+                       kzalloc(sizeof(*tp->cookie_values),
+                               sk->sk_allocation);
+               if (tp->cookie_values != NULL)
+                       kref_init(&tp->cookie_values->kref);
+       }
+       /* Presumed zeroed, in order of appearance:
+        *      cookie_in_always, cookie_out_never,
+        *      s_data_constant, s_data_in, s_data_out
+        */
        sk->sk_sndbuf = sysctl_tcp_wmem[1];
        sk->sk_rcvbuf = sysctl_tcp_rmem[1];