[NETNS][IPV6] tcp6 - make proc per namespace
[safe/jmp/linux-2.6] / net / ipv6 / tcp_ipv6.c
index 46922e5..56d0cea 100644 (file)
@@ -1,13 +1,13 @@
 /*
  *     TCP over IPv6
- *     Linux INET6 implementation 
+ *     Linux INET6 implementation
  *
  *     Authors:
- *     Pedro Roque             <roque@di.fc.ul.pt>     
+ *     Pedro Roque             <roque@di.fc.ul.pt>
  *
  *     $Id: tcp_ipv6.c,v 1.144 2002/02/01 22:01:04 davem Exp $
  *
- *     Based on: 
+ *     Based on:
  *     linux/net/ipv4/tcp.c
  *     linux/net/ipv4/tcp_input.c
  *     linux/net/ipv4/tcp_output.c
 #include <net/inet_ecn.h>
 #include <net/protocol.h>
 #include <net/xfrm.h>
-#include <net/addrconf.h>
 #include <net/snmp.h>
 #include <net/dsfield.h>
 #include <net/timewait_sock.h>
+#include <net/netdma.h>
 
 #include <asm/uaccess.h>
 
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
 
-/* Socket used for sending RSTs and ACKs */
-static struct socket *tcp6_socket;
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
 
-static void    tcp_v6_send_reset(struct sk_buff *skb);
+static void    tcp_v6_send_reset(struct sock *sk, struct sk_buff *skb);
 static void    tcp_v6_reqsk_send_ack(struct sk_buff *skb, struct request_sock *req);
-static void    tcp_v6_send_check(struct sock *sk, int len, 
+static void    tcp_v6_send_check(struct sock *sk, int len,
                                  struct sk_buff *skb);
 
 static int     tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb);
 
 static struct inet_connection_sock_af_ops ipv6_mapped;
 static struct inet_connection_sock_af_ops ipv6_specific;
-
-static int tcp_v6_get_port(struct sock *sk, unsigned short snum)
-{
-       return inet_csk_get_port(&tcp_hashinfo, sk, snum,
-                                inet6_csk_bind_conflict);
-}
+#ifdef CONFIG_TCP_MD5SIG
+static struct tcp_sock_af_ops tcp_sock_ipv6_specific;
+static struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific;
+#endif
 
 static void tcp_v6_hash(struct sock *sk)
 {
@@ -93,39 +91,32 @@ static void tcp_v6_hash(struct sock *sk)
                        return;
                }
                local_bh_disable();
-               __inet6_hash(&tcp_hashinfo, sk);
+               __inet6_hash(sk);
                local_bh_enable();
        }
 }
 
-static __inline__ u16 tcp_v6_check(struct tcphdr *th, int len,
-                                  struct in6_addr *saddr, 
-                                  struct in6_addr *daddr, 
-                                  unsigned long base)
+static __inline__ __sum16 tcp_v6_check(struct tcphdr *th, int len,
+                                  struct in6_addr *saddr,
+                                  struct in6_addr *daddr,
+                                  __wsum base)
 {
        return csum_ipv6_magic(saddr, daddr, len, IPPROTO_TCP, base);
 }
 
-static __u32 tcp_v6_init_sequence(struct sock *sk, struct sk_buff *skb)
+static __u32 tcp_v6_init_sequence(struct sk_buff *skb)
 {
-       if (skb->protocol == htons(ETH_P_IPV6)) {
-               return secure_tcpv6_sequence_number(skb->nh.ipv6h->daddr.s6_addr32,
-                                                   skb->nh.ipv6h->saddr.s6_addr32,
-                                                   skb->h.th->dest,
-                                                   skb->h.th->source);
-       } else {
-               return secure_tcp_sequence_number(skb->nh.iph->daddr,
-                                                 skb->nh.iph->saddr,
-                                                 skb->h.th->dest,
-                                                 skb->h.th->source);
-       }
+       return secure_tcpv6_sequence_number(ipv6_hdr(skb)->daddr.s6_addr32,
+                                           ipv6_hdr(skb)->saddr.s6_addr32,
+                                           tcp_hdr(skb)->dest,
+                                           tcp_hdr(skb)->source);
 }
 
-static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, 
+static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
                          int addr_len)
 {
        struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
-       struct inet_sock *inet = inet_sk(sk);
+       struct inet_sock *inet = inet_sk(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
        struct ipv6_pinfo *np = inet6_sk(sk);
        struct tcp_sock *tp = tcp_sk(sk);
@@ -135,10 +126,10 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
        int addr_type;
        int err;
 
-       if (addr_len < SIN6_LEN_RFC2133) 
+       if (addr_len < SIN6_LEN_RFC2133)
                return -EINVAL;
 
-       if (usin->sin6_family != AF_INET6) 
+       if (usin->sin6_family != AF_INET6)
                return(-EAFNOSUPPORT);
 
        memset(&fl, 0, sizeof(fl));
@@ -157,11 +148,11 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
        }
 
        /*
-        *      connect() to INADDR_ANY means loopback (BSD'ism).
-        */
-       
-       if(ipv6_addr_any(&usin->sin6_addr))
-               usin->sin6_addr.s6_addr[15] = 0x1; 
+        *      connect() to INADDR_ANY means loopback (BSD'ism).
+        */
+
+       if(ipv6_addr_any(&usin->sin6_addr))
+               usin->sin6_addr.s6_addr[15] = 0x1;
 
        addr_type = ipv6_addr_type(&usin->sin6_addr);
 
@@ -215,6 +206,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
 
                icsk->icsk_af_ops = &ipv6_mapped;
                sk->sk_backlog_rcv = tcp_v4_do_rcv;
+#ifdef CONFIG_TCP_MD5SIG
+               tp->af_specific = &tcp_sock_ipv6_mapped_specific;
+#endif
 
                err = tcp_v4_connect(sk, (struct sockaddr *)&sin, sizeof(sin));
 
@@ -222,6 +216,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
                        icsk->icsk_ext_hdr_len = exthdrlen;
                        icsk->icsk_af_ops = &ipv6_specific;
                        sk->sk_backlog_rcv = tcp_v6_do_rcv;
+#ifdef CONFIG_TCP_MD5SIG
+                       tp->af_specific = &tcp_sock_ipv6_specific;
+#endif
                        goto failure;
                } else {
                        ipv6_addr_set(&np->saddr, 0, 0, htonl(0x0000FFFF),
@@ -259,8 +256,12 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
        if (final_p)
                ipv6_addr_copy(&fl.fl6_dst, final_p);
 
-       if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
-               goto failure;
+       if ((err = __xfrm_lookup(&dst, &fl, sk, XFRM_LOOKUP_WAIT)) < 0) {
+               if (err == -EREMOTE)
+                       err = ip6_dst_blackhole(sk, &dst, &fl);
+               if (err < 0)
+                       goto failure;
+       }
 
        if (saddr == NULL) {
                saddr = &fl.fl6_src;
@@ -272,7 +273,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
        inet->rcv_saddr = LOOPBACK4_IPV6;
 
        sk->sk_gso_type = SKB_GSO_TCPV6;
-       __ip6_dst_store(sk, dst, NULL);
+       __ip6_dst_store(sk, dst, NULL, NULL);
 
        icsk->icsk_ext_hdr_len = 0;
        if (np->opt)
@@ -310,18 +311,18 @@ failure:
 }
 
 static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
-               int type, int code, int offset, __u32 info)
+               int type, int code, int offset, __be32 info)
 {
        struct ipv6hdr *hdr = (struct ipv6hdr*)skb->data;
        const struct tcphdr *th = (struct tcphdr *)(skb->data+offset);
        struct ipv6_pinfo *np;
        struct sock *sk;
        int err;
-       struct tcp_sock *tp; 
+       struct tcp_sock *tp;
        __u32 seq;
 
-       sk = inet6_lookup(&tcp_hashinfo, &hdr->daddr, th->dest, &hdr->saddr,
-                         th->source, skb->dev->ifindex);
+       sk = inet6_lookup(skb->dev->nd_net, &tcp_hashinfo, &hdr->daddr,
+                       th->dest, &hdr->saddr, th->source, skb->dev->ifindex);
 
        if (sk == NULL) {
                ICMP6_INC_STATS_BH(__in6_dev_get(skb->dev), ICMP6_MIB_INERRORS);
@@ -329,7 +330,7 @@ static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
        }
 
        if (sk->sk_state == TCP_TIME_WAIT) {
-               inet_twsk_put((struct inet_timewait_sock *)sk);
+               inet_twsk_put(inet_twsk(sk));
                return;
        }
 
@@ -341,7 +342,7 @@ static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
                goto out;
 
        tp = tcp_sk(sk);
-       seq = ntohl(th->seq); 
+       seq = ntohl(th->seq);
        if (sk->sk_state != TCP_LISTEN &&
            !between(seq, tp->snd_una, tp->snd_nxt)) {
                NET_INC_STATS_BH(LINUX_MIB_OUTOFWINDOWICMPS);
@@ -428,7 +429,7 @@ static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
 
        case TCP_SYN_SENT:
        case TCP_SYN_RECV:  /* Cannot happen.
-                              It can, it SYNs are crossed. --ANK */ 
+                              It can, it SYNs are crossed. --ANK */
                if (!sock_owned_by_user(sk)) {
                        sk->sk_err = err;
                        sk->sk_error_report(sk);                /* Wake people up to see the error (see connect in sock.c) */
@@ -451,8 +452,7 @@ out:
 }
 
 
-static int tcp_v6_send_synack(struct sock *sk, struct request_sock *req,
-                             struct dst_entry *dst)
+static int tcp_v6_send_synack(struct sock *sk, struct request_sock *req)
 {
        struct inet6_request_sock *treq = inet6_rsk(req);
        struct ipv6_pinfo *np = inet6_sk(sk);
@@ -460,6 +460,7 @@ static int tcp_v6_send_synack(struct sock *sk, struct request_sock *req,
        struct ipv6_txoptions *opt = NULL;
        struct in6_addr * final_p = NULL, final;
        struct flowi fl;
+       struct dst_entry *dst;
        int err = -1;
 
        memset(&fl, 0, sizeof(fl));
@@ -470,38 +471,27 @@ static int tcp_v6_send_synack(struct sock *sk, struct request_sock *req,
        fl.oif = treq->iif;
        fl.fl_ip_dport = inet_rsk(req)->rmt_port;
        fl.fl_ip_sport = inet_sk(sk)->sport;
-       security_sk_classify_flow(sk, &fl);
-
-       if (dst == NULL) {
-               opt = np->opt;
-               if (opt == NULL &&
-                   np->rxopt.bits.osrcrt == 2 &&
-                   treq->pktopts) {
-                       struct sk_buff *pktopts = treq->pktopts;
-                       struct inet6_skb_parm *rxopt = IP6CB(pktopts);
-                       if (rxopt->srcrt)
-                               opt = ipv6_invert_rthdr(sk, (struct ipv6_rt_hdr*)(pktopts->nh.raw + rxopt->srcrt));
-               }
-
-               if (opt && opt->srcrt) {
-                       struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
-                       ipv6_addr_copy(&final, &fl.fl6_dst);
-                       ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
-                       final_p = &final;
-               }
+       security_req_classify_flow(req, &fl);
 
-               err = ip6_dst_lookup(sk, &dst, &fl);
-               if (err)
-                       goto done;
-               if (final_p)
-                       ipv6_addr_copy(&fl.fl6_dst, final_p);
-               if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
-                       goto done;
+       opt = np->opt;
+       if (opt && opt->srcrt) {
+               struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
+               ipv6_addr_copy(&final, &fl.fl6_dst);
+               ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
+               final_p = &final;
        }
 
+       err = ip6_dst_lookup(sk, &dst, &fl);
+       if (err)
+               goto done;
+       if (final_p)
+               ipv6_addr_copy(&fl.fl6_dst, final_p);
+       if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
+               goto done;
+
        skb = tcp_make_synack(sk, dst, req);
        if (skb) {
-               struct tcphdr *th = skb->h.th;
+               struct tcphdr *th = tcp_hdr(skb);
 
                th->check = tcp_v6_check(th, skb->len,
                                         &treq->loc_addr, &treq->rmt_addr,
@@ -509,24 +499,434 @@ static int tcp_v6_send_synack(struct sock *sk, struct request_sock *req,
 
                ipv6_addr_copy(&fl.fl6_dst, &treq->rmt_addr);
                err = ip6_xmit(sk, skb, &fl, opt, 0);
-               if (err == NET_XMIT_CN)
-                       err = 0;
+               err = net_xmit_eval(err);
        }
 
 done:
-        if (opt && opt != np->opt)
+       if (opt && opt != np->opt)
                sock_kfree_s(sk, opt, opt->tot_len);
        dst_release(dst);
        return err;
 }
 
+static inline void syn_flood_warning(struct sk_buff *skb)
+{
+#ifdef CONFIG_SYN_COOKIES
+       if (sysctl_tcp_syncookies)
+               printk(KERN_INFO
+                      "TCPv6: Possible SYN flooding on port %d. "
+                      "Sending cookies.\n", ntohs(tcp_hdr(skb)->dest));
+       else
+#endif
+               printk(KERN_INFO
+                      "TCPv6: Possible SYN flooding on port %d. "
+                      "Dropping request.\n", ntohs(tcp_hdr(skb)->dest));
+}
+
 static void tcp_v6_reqsk_destructor(struct request_sock *req)
 {
        if (inet6_rsk(req)->pktopts)
                kfree_skb(inet6_rsk(req)->pktopts);
 }
 
-static struct request_sock_ops tcp6_request_sock_ops = {
+#ifdef CONFIG_TCP_MD5SIG
+static struct tcp_md5sig_key *tcp_v6_md5_do_lookup(struct sock *sk,
+                                                  struct in6_addr *addr)
+{
+       struct tcp_sock *tp = tcp_sk(sk);
+       int i;
+
+       BUG_ON(tp == NULL);
+
+       if (!tp->md5sig_info || !tp->md5sig_info->entries6)
+               return NULL;
+
+       for (i = 0; i < tp->md5sig_info->entries6; i++) {
+               if (ipv6_addr_cmp(&tp->md5sig_info->keys6[i].addr, addr) == 0)
+                       return &tp->md5sig_info->keys6[i].base;
+       }
+       return NULL;
+}
+
+static struct tcp_md5sig_key *tcp_v6_md5_lookup(struct sock *sk,
+                                               struct sock *addr_sk)
+{
+       return tcp_v6_md5_do_lookup(sk, &inet6_sk(addr_sk)->daddr);
+}
+
+static struct tcp_md5sig_key *tcp_v6_reqsk_md5_lookup(struct sock *sk,
+                                                     struct request_sock *req)
+{
+       return tcp_v6_md5_do_lookup(sk, &inet6_rsk(req)->rmt_addr);
+}
+
+static int tcp_v6_md5_do_add(struct sock *sk, struct in6_addr *peer,
+                            char *newkey, u8 newkeylen)
+{
+       /* Add key to the list */
+       struct tcp_md5sig_key *key;
+       struct tcp_sock *tp = tcp_sk(sk);
+       struct tcp6_md5sig_key *keys;
+
+       key = tcp_v6_md5_do_lookup(sk, peer);
+       if (key) {
+               /* modify existing entry - just update that one */
+               kfree(key->key);
+               key->key = newkey;
+               key->keylen = newkeylen;
+       } else {
+               /* reallocate new list if current one is full. */
+               if (!tp->md5sig_info) {
+                       tp->md5sig_info = kzalloc(sizeof(*tp->md5sig_info), GFP_ATOMIC);
+                       if (!tp->md5sig_info) {
+                               kfree(newkey);
+                               return -ENOMEM;
+                       }
+                       sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
+               }
+               if (tcp_alloc_md5sig_pool() == NULL) {
+                       kfree(newkey);
+                       return -ENOMEM;
+               }
+               if (tp->md5sig_info->alloced6 == tp->md5sig_info->entries6) {
+                       keys = kmalloc((sizeof (tp->md5sig_info->keys6[0]) *
+                                      (tp->md5sig_info->entries6 + 1)), GFP_ATOMIC);
+
+                       if (!keys) {
+                               tcp_free_md5sig_pool();
+                               kfree(newkey);
+                               return -ENOMEM;
+                       }
+
+                       if (tp->md5sig_info->entries6)
+                               memmove(keys, tp->md5sig_info->keys6,
+                                       (sizeof (tp->md5sig_info->keys6[0]) *
+                                        tp->md5sig_info->entries6));
+
+                       kfree(tp->md5sig_info->keys6);
+                       tp->md5sig_info->keys6 = keys;
+                       tp->md5sig_info->alloced6++;
+               }
+
+               ipv6_addr_copy(&tp->md5sig_info->keys6[tp->md5sig_info->entries6].addr,
+                              peer);
+               tp->md5sig_info->keys6[tp->md5sig_info->entries6].base.key = newkey;
+               tp->md5sig_info->keys6[tp->md5sig_info->entries6].base.keylen = newkeylen;
+
+               tp->md5sig_info->entries6++;
+       }
+       return 0;
+}
+
+static int tcp_v6_md5_add_func(struct sock *sk, struct sock *addr_sk,
+                              u8 *newkey, __u8 newkeylen)
+{
+       return tcp_v6_md5_do_add(sk, &inet6_sk(addr_sk)->daddr,
+                                newkey, newkeylen);
+}
+
+static int tcp_v6_md5_do_del(struct sock *sk, struct in6_addr *peer)
+{
+       struct tcp_sock *tp = tcp_sk(sk);
+       int i;
+
+       for (i = 0; i < tp->md5sig_info->entries6; i++) {
+               if (ipv6_addr_cmp(&tp->md5sig_info->keys6[i].addr, peer) == 0) {
+                       /* Free the key */
+                       kfree(tp->md5sig_info->keys6[i].base.key);
+                       tp->md5sig_info->entries6--;
+
+                       if (tp->md5sig_info->entries6 == 0) {
+                               kfree(tp->md5sig_info->keys6);
+                               tp->md5sig_info->keys6 = NULL;
+                               tp->md5sig_info->alloced6 = 0;
+                       } else {
+                               /* shrink the database */
+                               if (tp->md5sig_info->entries6 != i)
+                                       memmove(&tp->md5sig_info->keys6[i],
+                                               &tp->md5sig_info->keys6[i+1],
+                                               (tp->md5sig_info->entries6 - i)
+                                               * sizeof (tp->md5sig_info->keys6[0]));
+                       }
+                       tcp_free_md5sig_pool();
+                       return 0;
+               }
+       }
+       return -ENOENT;
+}
+
+static void tcp_v6_clear_md5_list (struct sock *sk)
+{
+       struct tcp_sock *tp = tcp_sk(sk);
+       int i;
+
+       if (tp->md5sig_info->entries6) {
+               for (i = 0; i < tp->md5sig_info->entries6; i++)
+                       kfree(tp->md5sig_info->keys6[i].base.key);
+               tp->md5sig_info->entries6 = 0;
+               tcp_free_md5sig_pool();
+       }
+
+       kfree(tp->md5sig_info->keys6);
+       tp->md5sig_info->keys6 = NULL;
+       tp->md5sig_info->alloced6 = 0;
+
+       if (tp->md5sig_info->entries4) {
+               for (i = 0; i < tp->md5sig_info->entries4; i++)
+                       kfree(tp->md5sig_info->keys4[i].base.key);
+               tp->md5sig_info->entries4 = 0;
+               tcp_free_md5sig_pool();
+       }
+
+       kfree(tp->md5sig_info->keys4);
+       tp->md5sig_info->keys4 = NULL;
+       tp->md5sig_info->alloced4 = 0;
+}
+
+static int tcp_v6_parse_md5_keys (struct sock *sk, char __user *optval,
+                                 int optlen)
+{
+       struct tcp_md5sig cmd;
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd.tcpm_addr;
+       u8 *newkey;
+
+       if (optlen < sizeof(cmd))
+               return -EINVAL;
+
+       if (copy_from_user(&cmd, optval, sizeof(cmd)))
+               return -EFAULT;
+
+       if (sin6->sin6_family != AF_INET6)
+               return -EINVAL;
+
+       if (!cmd.tcpm_keylen) {
+               if (!tcp_sk(sk)->md5sig_info)
+                       return -ENOENT;
+               if (ipv6_addr_v4mapped(&sin6->sin6_addr))
+                       return tcp_v4_md5_do_del(sk, sin6->sin6_addr.s6_addr32[3]);
+               return tcp_v6_md5_do_del(sk, &sin6->sin6_addr);
+       }
+
+       if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
+               return -EINVAL;
+
+       if (!tcp_sk(sk)->md5sig_info) {
+               struct tcp_sock *tp = tcp_sk(sk);
+               struct tcp_md5sig_info *p;
+
+               p = kzalloc(sizeof(struct tcp_md5sig_info), GFP_KERNEL);
+               if (!p)
+                       return -ENOMEM;
+
+               tp->md5sig_info = p;
+               sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
+       }
+
+       newkey = kmemdup(cmd.tcpm_key, cmd.tcpm_keylen, GFP_KERNEL);
+       if (!newkey)
+               return -ENOMEM;
+       if (ipv6_addr_v4mapped(&sin6->sin6_addr)) {
+               return tcp_v4_md5_do_add(sk, sin6->sin6_addr.s6_addr32[3],
+                                        newkey, cmd.tcpm_keylen);
+       }
+       return tcp_v6_md5_do_add(sk, &sin6->sin6_addr, newkey, cmd.tcpm_keylen);
+}
+
+static int tcp_v6_do_calc_md5_hash(char *md5_hash, struct tcp_md5sig_key *key,
+                                  struct in6_addr *saddr,
+                                  struct in6_addr *daddr,
+                                  struct tcphdr *th, int protocol,
+                                  unsigned int tcplen)
+{
+       struct scatterlist sg[4];
+       __u16 data_len;
+       int block = 0;
+       __sum16 cksum;
+       struct tcp_md5sig_pool *hp;
+       struct tcp6_pseudohdr *bp;
+       struct hash_desc *desc;
+       int err;
+       unsigned int nbytes = 0;
+
+       hp = tcp_get_md5sig_pool();
+       if (!hp) {
+               printk(KERN_WARNING "%s(): hash pool not found...\n", __func__);
+               goto clear_hash_noput;
+       }
+       bp = &hp->md5_blk.ip6;
+       desc = &hp->md5_desc;
+
+       /* 1. TCP pseudo-header (RFC2460) */
+       ipv6_addr_copy(&bp->saddr, saddr);
+       ipv6_addr_copy(&bp->daddr, daddr);
+       bp->len = htonl(tcplen);
+       bp->protocol = htonl(protocol);
+
+       sg_init_table(sg, 4);
+
+       sg_set_buf(&sg[block++], bp, sizeof(*bp));
+       nbytes += sizeof(*bp);
+
+       /* 2. TCP header, excluding options */
+       cksum = th->check;
+       th->check = 0;
+       sg_set_buf(&sg[block++], th, sizeof(*th));
+       nbytes += sizeof(*th);
+
+       /* 3. TCP segment data (if any) */
+       data_len = tcplen - (th->doff << 2);
+       if (data_len > 0) {
+               u8 *data = (u8 *)th + (th->doff << 2);
+               sg_set_buf(&sg[block++], data, data_len);
+               nbytes += data_len;
+       }
+
+       /* 4. shared key */
+       sg_set_buf(&sg[block++], key->key, key->keylen);
+       nbytes += key->keylen;
+
+       sg_mark_end(&sg[block - 1]);
+
+       /* Now store the hash into the packet */
+       err = crypto_hash_init(desc);
+       if (err) {
+               printk(KERN_WARNING "%s(): hash_init failed\n", __func__);
+               goto clear_hash;
+       }
+       err = crypto_hash_update(desc, sg, nbytes);
+       if (err) {
+               printk(KERN_WARNING "%s(): hash_update failed\n", __func__);
+               goto clear_hash;
+       }
+       err = crypto_hash_final(desc, md5_hash);
+       if (err) {
+               printk(KERN_WARNING "%s(): hash_final failed\n", __func__);
+               goto clear_hash;
+       }
+
+       /* Reset header, and free up the crypto */
+       tcp_put_md5sig_pool();
+       th->check = cksum;
+out:
+       return 0;
+clear_hash:
+       tcp_put_md5sig_pool();
+clear_hash_noput:
+       memset(md5_hash, 0, 16);
+       goto out;
+}
+
+static int tcp_v6_calc_md5_hash(char *md5_hash, struct tcp_md5sig_key *key,
+                               struct sock *sk,
+                               struct dst_entry *dst,
+                               struct request_sock *req,
+                               struct tcphdr *th, int protocol,
+                               unsigned int tcplen)
+{
+       struct in6_addr *saddr, *daddr;
+
+       if (sk) {
+               saddr = &inet6_sk(sk)->saddr;
+               daddr = &inet6_sk(sk)->daddr;
+       } else {
+               saddr = &inet6_rsk(req)->loc_addr;
+               daddr = &inet6_rsk(req)->rmt_addr;
+       }
+       return tcp_v6_do_calc_md5_hash(md5_hash, key,
+                                      saddr, daddr,
+                                      th, protocol, tcplen);
+}
+
+static int tcp_v6_inbound_md5_hash (struct sock *sk, struct sk_buff *skb)
+{
+       __u8 *hash_location = NULL;
+       struct tcp_md5sig_key *hash_expected;
+       struct ipv6hdr *ip6h = ipv6_hdr(skb);
+       struct tcphdr *th = tcp_hdr(skb);
+       int length = (th->doff << 2) - sizeof (*th);
+       int genhash;
+       u8 *ptr;
+       u8 newhash[16];
+
+       hash_expected = tcp_v6_md5_do_lookup(sk, &ip6h->saddr);
+
+       /* If the TCP option is too short, we can short cut */
+       if (length < TCPOLEN_MD5SIG)
+               return hash_expected ? 1 : 0;
+
+       /* parse options */
+       ptr = (u8*)(th + 1);
+       while (length > 0) {
+               int opcode = *ptr++;
+               int opsize;
+
+               switch(opcode) {
+               case TCPOPT_EOL:
+                       goto done_opts;
+               case TCPOPT_NOP:
+                       length--;
+                       continue;
+               default:
+                       opsize = *ptr++;
+                       if (opsize < 2 || opsize > length)
+                               goto done_opts;
+                       if (opcode == TCPOPT_MD5SIG) {
+                               hash_location = ptr;
+                               goto done_opts;
+                       }
+               }
+               ptr += opsize - 2;
+               length -= opsize;
+       }
+
+done_opts:
+       /* do we have a hash as expected? */
+       if (!hash_expected) {
+               if (!hash_location)
+                       return 0;
+               if (net_ratelimit()) {
+                       printk(KERN_INFO "MD5 Hash NOT expected but found "
+                              "(" NIP6_FMT ", %u)->"
+                              "(" NIP6_FMT ", %u)\n",
+                              NIP6(ip6h->saddr), ntohs(th->source),
+                              NIP6(ip6h->daddr), ntohs(th->dest));
+               }
+               return 1;
+       }
+
+       if (!hash_location) {
+               if (net_ratelimit()) {
+                       printk(KERN_INFO "MD5 Hash expected but NOT found "
+                              "(" NIP6_FMT ", %u)->"
+                              "(" NIP6_FMT ", %u)\n",
+                              NIP6(ip6h->saddr), ntohs(th->source),
+                              NIP6(ip6h->daddr), ntohs(th->dest));
+               }
+               return 1;
+       }
+
+       /* check the signature */
+       genhash = tcp_v6_do_calc_md5_hash(newhash,
+                                         hash_expected,
+                                         &ip6h->saddr, &ip6h->daddr,
+                                         th, sk->sk_protocol,
+                                         skb->len);
+       if (genhash || memcmp(hash_location, newhash, 16) != 0) {
+               if (net_ratelimit()) {
+                       printk(KERN_INFO "MD5 Hash %s for "
+                              "(" NIP6_FMT ", %u)->"
+                              "(" NIP6_FMT ", %u)\n",
+                              genhash ? "failed" : "mismatch",
+                              NIP6(ip6h->saddr), ntohs(th->source),
+                              NIP6(ip6h->daddr), ntohs(th->dest));
+               }
+               return 1;
+       }
+       return 0;
+}
+#endif
+
+struct request_sock_ops tcp6_request_sock_ops __read_mostly = {
        .family         =       AF_INET6,
        .obj_size       =       sizeof(struct tcp6_request_sock),
        .rtx_syn_ack    =       tcp_v6_send_synack,
@@ -535,22 +935,30 @@ static struct request_sock_ops tcp6_request_sock_ops = {
        .send_reset     =       tcp_v6_send_reset
 };
 
+#ifdef CONFIG_TCP_MD5SIG
+static struct tcp_request_sock_ops tcp_request_sock_ipv6_ops = {
+       .md5_lookup     =       tcp_v6_reqsk_md5_lookup,
+};
+#endif
+
 static struct timewait_sock_ops tcp6_timewait_sock_ops = {
        .twsk_obj_size  = sizeof(struct tcp6_timewait_sock),
        .twsk_unique    = tcp_twsk_unique,
+       .twsk_destructor= tcp_twsk_destructor,
 };
 
 static void tcp_v6_send_check(struct sock *sk, int len, struct sk_buff *skb)
 {
        struct ipv6_pinfo *np = inet6_sk(sk);
-       struct tcphdr *th = skb->h.th;
+       struct tcphdr *th = tcp_hdr(skb);
 
-       if (skb->ip_summed == CHECKSUM_HW) {
+       if (skb->ip_summed == CHECKSUM_PARTIAL) {
                th->check = ~csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP,  0);
-               skb->csum = offsetof(struct tcphdr, check);
+               skb->csum_start = skb_transport_header(skb) - skb->head;
+               skb->csum_offset = offsetof(struct tcphdr, check);
        } else {
-               th->check = csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP, 
-                                           csum_partial((char *)th, th->doff<<2, 
+               th->check = csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP,
+                                           csum_partial((char *)th, th->doff<<2,
                                                         skb->csum));
        }
 }
@@ -563,63 +971,94 @@ static int tcp_v6_gso_send_check(struct sk_buff *skb)
        if (!pskb_may_pull(skb, sizeof(*th)))
                return -EINVAL;
 
-       ipv6h = skb->nh.ipv6h;
-       th = skb->h.th;
+       ipv6h = ipv6_hdr(skb);
+       th = tcp_hdr(skb);
 
        th->check = 0;
        th->check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len,
                                     IPPROTO_TCP, 0);
-       skb->csum = offsetof(struct tcphdr, check);
-       skb->ip_summed = CHECKSUM_HW;
+       skb->csum_start = skb_transport_header(skb) - skb->head;
+       skb->csum_offset = offsetof(struct tcphdr, check);
+       skb->ip_summed = CHECKSUM_PARTIAL;
        return 0;
 }
 
-static void tcp_v6_send_reset(struct sk_buff *skb)
+static void tcp_v6_send_reset(struct sock *sk, struct sk_buff *skb)
 {
-       struct tcphdr *th = skb->h.th, *t1; 
+       struct tcphdr *th = tcp_hdr(skb), *t1;
        struct sk_buff *buff;
        struct flowi fl;
+       struct net *net = skb->dst->dev->nd_net;
+       struct sock *ctl_sk = net->ipv6.tcp_sk;
+       unsigned int tot_len = sizeof(*th);
+#ifdef CONFIG_TCP_MD5SIG
+       struct tcp_md5sig_key *key;
+#endif
 
        if (th->rst)
                return;
 
        if (!ipv6_unicast_destination(skb))
-               return; 
+               return;
+
+#ifdef CONFIG_TCP_MD5SIG
+       if (sk)
+               key = tcp_v6_md5_do_lookup(sk, &ipv6_hdr(skb)->daddr);
+       else
+               key = NULL;
+
+       if (key)
+               tot_len += TCPOLEN_MD5SIG_ALIGNED;
+#endif
 
        /*
         * We need to grab some memory, and put together an RST,
         * and then put it into the queue to be sent.
         */
 
-       buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + sizeof(struct tcphdr),
+       buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + tot_len,
                         GFP_ATOMIC);
-       if (buff == NULL) 
-               return;
+       if (buff == NULL)
+               return;
 
-       skb_reserve(buff, MAX_HEADER + sizeof(struct ipv6hdr) + sizeof(struct tcphdr));
+       skb_reserve(buff, MAX_HEADER + sizeof(struct ipv6hdr) + tot_len);
 
-       t1 = (struct tcphdr *) skb_push(buff,sizeof(struct tcphdr));
+       t1 = (struct tcphdr *) skb_push(buff, tot_len);
 
        /* Swap the send and the receive. */
        memset(t1, 0, sizeof(*t1));
        t1->dest = th->source;
        t1->source = th->dest;
-       t1->doff = sizeof(*t1)/4;
+       t1->doff = tot_len / 4;
        t1->rst = 1;
-  
+
        if(th->ack) {
-               t1->seq = th->ack_seq;
+               t1->seq = th->ack_seq;
        } else {
                t1->ack = 1;
                t1->ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin
                                    + skb->len - (th->doff<<2));
        }
 
+#ifdef CONFIG_TCP_MD5SIG
+       if (key) {
+               __be32 *opt = (__be32*)(t1 + 1);
+               opt[0] = htonl((TCPOPT_NOP << 24) |
+                              (TCPOPT_NOP << 16) |
+                              (TCPOPT_MD5SIG << 8) |
+                              TCPOLEN_MD5SIG);
+               tcp_v6_do_calc_md5_hash((__u8 *)&opt[1], key,
+                                       &ipv6_hdr(skb)->daddr,
+                                       &ipv6_hdr(skb)->saddr,
+                                       t1, IPPROTO_TCP, tot_len);
+       }
+#endif
+
        buff->csum = csum_partial((char *)t1, sizeof(*t1), 0);
 
        memset(&fl, 0, sizeof(fl));
-       ipv6_addr_copy(&fl.fl6_dst, &skb->nh.ipv6h->saddr);
-       ipv6_addr_copy(&fl.fl6_src, &skb->nh.ipv6h->daddr);
+       ipv6_addr_copy(&fl.fl6_dst, &ipv6_hdr(skb)->saddr);
+       ipv6_addr_copy(&fl.fl6_src, &ipv6_hdr(skb)->daddr);
 
        t1->check = csum_ipv6_magic(&fl.fl6_src, &fl.fl6_dst,
                                    sizeof(*t1), IPPROTO_TCP,
@@ -631,11 +1070,14 @@ static void tcp_v6_send_reset(struct sk_buff *skb)
        fl.fl_ip_sport = t1->source;
        security_skb_classify_flow(skb, &fl);
 
-       /* sk = NULL, but it is safe for now. RST socket required. */
-       if (!ip6_dst_lookup(NULL, &buff->dst, &fl)) {
+       /* Pass a socket to ip6_dst_lookup either it is for RST
+        * Underlying function will use this to retrieve the network
+        * namespace
+        */
+       if (!ip6_dst_lookup(ctl_sk, &buff->dst, &fl)) {
 
                if (xfrm_lookup(&buff->dst, &fl, NULL, 0) >= 0) {
-                       ip6_xmit(tcp6_socket->sk, buff, &fl, NULL, 0);
+                       ip6_xmit(ctl_sk, buff, &fl, NULL, 0);
                        TCP_INC_STATS_BH(TCP_MIB_OUTSEGS);
                        TCP_INC_STATS_BH(TCP_MIB_OUTRSTS);
                        return;
@@ -645,15 +1087,39 @@ static void tcp_v6_send_reset(struct sk_buff *skb)
        kfree_skb(buff);
 }
 
-static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts)
+static void tcp_v6_send_ack(struct tcp_timewait_sock *tw,
+                           struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts)
 {
-       struct tcphdr *th = skb->h.th, *t1;
+       struct tcphdr *th = tcp_hdr(skb), *t1;
        struct sk_buff *buff;
        struct flowi fl;
-       int tot_len = sizeof(struct tcphdr);
+       struct net *net = skb->dev->nd_net;
+       struct sock *ctl_sk = net->ipv6.tcp_sk;
+       unsigned int tot_len = sizeof(struct tcphdr);
+       __be32 *topt;
+#ifdef CONFIG_TCP_MD5SIG
+       struct tcp_md5sig_key *key;
+       struct tcp_md5sig_key tw_key;
+#endif
+
+#ifdef CONFIG_TCP_MD5SIG
+       if (!tw && skb->sk) {
+               key = tcp_v6_md5_do_lookup(skb->sk, &ipv6_hdr(skb)->daddr);
+       } else if (tw && tw->tw_md5_keylen) {
+               tw_key.key = tw->tw_md5_key;
+               tw_key.keylen = tw->tw_md5_keylen;
+               key = &tw_key;
+       } else {
+               key = NULL;
+       }
+#endif
 
        if (ts)
-               tot_len += 3*4;
+               tot_len += TCPOLEN_TSTAMP_ALIGNED;
+#ifdef CONFIG_TCP_MD5SIG
+       if (key)
+               tot_len += TCPOLEN_MD5SIG_ALIGNED;
+#endif
 
        buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + tot_len,
                         GFP_ATOMIC);
@@ -673,20 +1139,32 @@ static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32
        t1->ack_seq = htonl(ack);
        t1->ack = 1;
        t1->window = htons(win);
-       
+
+       topt = (__be32 *)(t1 + 1);
+
        if (ts) {
-               u32 *ptr = (u32*)(t1 + 1);
-               *ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
-                              (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP);
-               *ptr++ = htonl(tcp_time_stamp);
-               *ptr = htonl(ts);
+               *topt++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
+                               (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP);
+               *topt++ = htonl(tcp_time_stamp);
+               *topt = htonl(ts);
+       }
+
+#ifdef CONFIG_TCP_MD5SIG
+       if (key) {
+               *topt++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
+                               (TCPOPT_MD5SIG << 8) | TCPOLEN_MD5SIG);
+               tcp_v6_do_calc_md5_hash((__u8 *)topt, key,
+                                       &ipv6_hdr(skb)->daddr,
+                                       &ipv6_hdr(skb)->saddr,
+                                       t1, IPPROTO_TCP, tot_len);
        }
+#endif
 
        buff->csum = csum_partial((char *)t1, tot_len, 0);
 
        memset(&fl, 0, sizeof(fl));
-       ipv6_addr_copy(&fl.fl6_dst, &skb->nh.ipv6h->saddr);
-       ipv6_addr_copy(&fl.fl6_src, &skb->nh.ipv6h->daddr);
+       ipv6_addr_copy(&fl.fl6_dst, &ipv6_hdr(skb)->saddr);
+       ipv6_addr_copy(&fl.fl6_src, &ipv6_hdr(skb)->daddr);
 
        t1->check = csum_ipv6_magic(&fl.fl6_src, &fl.fl6_dst,
                                    tot_len, IPPROTO_TCP,
@@ -698,9 +1176,9 @@ static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32
        fl.fl_ip_sport = t1->source;
        security_skb_classify_flow(skb, &fl);
 
-       if (!ip6_dst_lookup(NULL, &buff->dst, &fl)) {
+       if (!ip6_dst_lookup(ctl_sk, &buff->dst, &fl)) {
                if (xfrm_lookup(&buff->dst, &fl, NULL, 0) >= 0) {
-                       ip6_xmit(tcp6_socket->sk, buff, &fl, NULL, 0);
+                       ip6_xmit(ctl_sk, buff, &fl, NULL, 0);
                        TCP_INC_STATS_BH(TCP_MIB_OUTSEGS);
                        return;
                }
@@ -712,9 +1190,9 @@ static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32
 static void tcp_v6_timewait_ack(struct sock *sk, struct sk_buff *skb)
 {
        struct inet_timewait_sock *tw = inet_twsk(sk);
-       const struct tcp_timewait_sock *tcptw = tcp_twsk(sk);
+       struct tcp_timewait_sock *tcptw = tcp_twsk(sk);
 
-       tcp_v6_send_ack(skb, tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt,
+       tcp_v6_send_ack(tcptw, skb, tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt,
                        tcptw->tw_rcv_wnd >> tw->tw_rcv_wscale,
                        tcptw->tw_ts_recent);
 
@@ -723,39 +1201,39 @@ static void tcp_v6_timewait_ack(struct sock *sk, struct sk_buff *skb)
 
 static void tcp_v6_reqsk_send_ack(struct sk_buff *skb, struct request_sock *req)
 {
-       tcp_v6_send_ack(skb, tcp_rsk(req)->snt_isn + 1, tcp_rsk(req)->rcv_isn + 1, req->rcv_wnd, req->ts_recent);
+       tcp_v6_send_ack(NULL, skb, tcp_rsk(req)->snt_isn + 1, tcp_rsk(req)->rcv_isn + 1, req->rcv_wnd, req->ts_recent);
 }
 
 
 static struct sock *tcp_v6_hnd_req(struct sock *sk,struct sk_buff *skb)
 {
        struct request_sock *req, **prev;
-       const struct tcphdr *th = skb->h.th;
+       const struct tcphdr *th = tcp_hdr(skb);
        struct sock *nsk;
 
        /* Find possible connection requests. */
        req = inet6_csk_search_req(sk, &prev, th->source,
-                                  &skb->nh.ipv6h->saddr,
-                                  &skb->nh.ipv6h->daddr, inet6_iif(skb));
+                                  &ipv6_hdr(skb)->saddr,
+                                  &ipv6_hdr(skb)->daddr, inet6_iif(skb));
        if (req)
                return tcp_check_req(sk, skb, req, prev);
 
-       nsk = __inet6_lookup_established(&tcp_hashinfo, &skb->nh.ipv6h->saddr,
-                                        th->source, &skb->nh.ipv6h->daddr,
-                                        ntohs(th->dest), inet6_iif(skb));
+       nsk = __inet6_lookup_established(sk->sk_net, &tcp_hashinfo,
+                       &ipv6_hdr(skb)->saddr, th->source,
+                       &ipv6_hdr(skb)->daddr, ntohs(th->dest), inet6_iif(skb));
 
        if (nsk) {
                if (nsk->sk_state != TCP_TIME_WAIT) {
                        bh_lock_sock(nsk);
                        return nsk;
                }
-               inet_twsk_put((struct inet_timewait_sock *)nsk);
+               inet_twsk_put(inet_twsk(nsk));
                return NULL;
        }
 
-#if 0 /*def CONFIG_SYN_COOKIES*/
+#ifdef CONFIG_SYN_COOKIES
        if (!th->rst && !th->syn && th->ack)
-               sk = cookie_v6_check(sk, skb, &(IPCB(skb)->opt));
+               sk = cookie_v6_check(sk, skb);
 #endif
        return sk;
 }
@@ -771,20 +1249,27 @@ static int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
        struct tcp_sock *tp = tcp_sk(sk);
        struct request_sock *req = NULL;
        __u32 isn = TCP_SKB_CB(skb)->when;
+#ifdef CONFIG_SYN_COOKIES
+       int want_cookie = 0;
+#else
+#define want_cookie 0
+#endif
 
        if (skb->protocol == htons(ETH_P_IP))
                return tcp_v4_conn_request(sk, skb);
 
        if (!ipv6_unicast_destination(skb))
-               goto drop; 
+               goto drop;
 
-       /*
-        *      There are no SYN attacks on IPv6, yet...        
-        */
        if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
                if (net_ratelimit())
-                       printk(KERN_INFO "TCPv6: dropping request, synflood is possible\n");
-               goto drop;              
+                       syn_flood_warning(skb);
+#ifdef CONFIG_SYN_COOKIES
+               if (sysctl_tcp_syncookies)
+                       want_cookie = 1;
+               else
+#endif
+               goto drop;
        }
 
        if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
@@ -794,43 +1279,61 @@ static int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
        if (req == NULL)
                goto drop;
 
+#ifdef CONFIG_TCP_MD5SIG
+       tcp_rsk(req)->af_specific = &tcp_request_sock_ipv6_ops;
+#endif
+
        tcp_clear_options(&tmp_opt);
        tmp_opt.mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
        tmp_opt.user_mss = tp->rx_opt.user_mss;
 
        tcp_parse_options(skb, &tmp_opt, 0);
 
+       if (want_cookie) {
+               tcp_clear_options(&tmp_opt);
+               tmp_opt.saw_tstamp = 0;
+       }
+
        tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
        tcp_openreq_init(req, &tmp_opt, skb);
 
        treq = inet6_rsk(req);
-       ipv6_addr_copy(&treq->rmt_addr, &skb->nh.ipv6h->saddr);
-       ipv6_addr_copy(&treq->loc_addr, &skb->nh.ipv6h->daddr);
-       TCP_ECN_create_request(req, skb->h.th);
+       ipv6_addr_copy(&treq->rmt_addr, &ipv6_hdr(skb)->saddr);
+       ipv6_addr_copy(&treq->loc_addr, &ipv6_hdr(skb)->daddr);
        treq->pktopts = NULL;
-       if (ipv6_opt_accepted(sk, skb) ||
-           np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||
-           np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
-               atomic_inc(&skb->users);
-               treq->pktopts = skb;
-       }
-       treq->iif = sk->sk_bound_dev_if;
+       if (!want_cookie)
+               TCP_ECN_create_request(req, tcp_hdr(skb));
+
+       if (want_cookie) {
+               isn = cookie_v6_init_sequence(sk, skb, &req->mss);
+       } else if (!isn) {
+               if (ipv6_opt_accepted(sk, skb) ||
+                   np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||
+                   np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
+                       atomic_inc(&skb->users);
+                       treq->pktopts = skb;
+               }
+               treq->iif = sk->sk_bound_dev_if;
 
-       /* So that link locals have meaning */
-       if (!sk->sk_bound_dev_if &&
-           ipv6_addr_type(&treq->rmt_addr) & IPV6_ADDR_LINKLOCAL)
-               treq->iif = inet6_iif(skb);
+               /* So that link locals have meaning */
+               if (!sk->sk_bound_dev_if &&
+                   ipv6_addr_type(&treq->rmt_addr) & IPV6_ADDR_LINKLOCAL)
+                       treq->iif = inet6_iif(skb);
 
-       if (isn == 0) 
-               isn = tcp_v6_init_sequence(sk,skb);
+               isn = tcp_v6_init_sequence(skb);
+       }
 
        tcp_rsk(req)->snt_isn = isn;
 
-       if (tcp_v6_send_synack(sk, req, NULL))
+       security_inet_conn_request(sk, skb, req);
+
+       if (tcp_v6_send_synack(sk, req))
                goto drop;
 
-       inet6_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
-       return 0;
+       if (!want_cookie) {
+               inet6_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
+               return 0;
+       }
 
 drop:
        if (req)
@@ -850,6 +1353,9 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
        struct tcp_sock *newtp;
        struct sock *newsk;
        struct ipv6_txoptions *opt;
+#ifdef CONFIG_TCP_MD5SIG
+       struct tcp_md5sig_key *key;
+#endif
 
        if (skb->protocol == htons(ETH_P_IP)) {
                /*
@@ -858,7 +1364,7 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
 
                newsk = tcp_v4_syn_recv_sock(sk, skb, req, dst);
 
-               if (newsk == NULL) 
+               if (newsk == NULL)
                        return NULL;
 
                newtcp6sk = (struct tcp6_sock *)newsk;
@@ -880,10 +1386,14 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
 
                inet_csk(newsk)->icsk_af_ops = &ipv6_mapped;
                newsk->sk_backlog_rcv = tcp_v4_do_rcv;
+#ifdef CONFIG_TCP_MD5SIG
+               newtp->af_specific = &tcp_sock_ipv6_mapped_specific;
+#endif
+
                newnp->pktoptions  = NULL;
                newnp->opt         = NULL;
                newnp->mcast_oif   = inet6_iif(skb);
-               newnp->mcast_hops  = skb->nh.ipv6h->hop_limit;
+               newnp->mcast_hops  = ipv6_hdr(skb)->hop_limit;
 
                /*
                 * No need to charge this sock to the relevant IPv6 refcnt debug socks count
@@ -905,13 +1415,6 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
        if (sk_acceptq_is_full(sk))
                goto out_overflow;
 
-       if (np->rxopt.bits.osrcrt == 2 &&
-           opt == NULL && treq->pktopts) {
-               struct inet6_skb_parm *rxopt = IP6CB(treq->pktopts);
-               if (rxopt->srcrt)
-                       opt = ipv6_invert_rthdr(sk, (struct ipv6_rt_hdr *)(treq->pktopts->nh.raw + rxopt->srcrt));
-       }
-
        if (dst == NULL) {
                struct in6_addr *final_p = NULL, final;
                struct flowi fl;
@@ -929,7 +1432,7 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
                fl.oif = sk->sk_bound_dev_if;
                fl.fl_ip_dport = inet_rsk(req)->rmt_port;
                fl.fl_ip_sport = inet_sk(sk)->sport;
-               security_sk_classify_flow(sk, &fl);
+               security_req_classify_flow(req, &fl);
 
                if (ip6_dst_lookup(sk, &dst, &fl))
                        goto out;
@@ -939,7 +1442,7 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
 
                if ((xfrm_lookup(&dst, &fl, sk, 0)) < 0)
                        goto out;
-       } 
+       }
 
        newsk = tcp_create_openreq_child(sk, req, skb);
        if (newsk == NULL)
@@ -952,7 +1455,7 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
         */
 
        newsk->sk_gso_type = SKB_GSO_TCPV6;
-       __ip6_dst_store(newsk, dst, NULL);
+       __ip6_dst_store(newsk, dst, NULL, NULL);
 
        newtcp6sk = (struct tcp6_sock *)newsk;
        inet_sk(newsk)->pinet6 = &newtcp6sk->inet6;
@@ -968,11 +1471,12 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
        ipv6_addr_copy(&newnp->rcv_saddr, &treq->loc_addr);
        newsk->sk_bound_dev_if = treq->iif;
 
-       /* Now IPv6 options... 
+       /* Now IPv6 options...
 
           First: no IPv4 options.
         */
        newinet->opt = NULL;
+       newnp->ipv6_fl_list = NULL;
 
        /* Clone RX bits */
        newnp->rxopt.all = np->rxopt.all;
@@ -988,7 +1492,7 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
        }
        newnp->opt        = NULL;
        newnp->mcast_oif  = inet6_iif(skb);
-       newnp->mcast_hops = skb->nh.ipv6h->hop_limit;
+       newnp->mcast_hops = ipv6_hdr(skb)->hop_limit;
 
        /* Clone native IPv6 options from listening socket (if any)
 
@@ -1014,8 +1518,23 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
 
        newinet->daddr = newinet->saddr = newinet->rcv_saddr = LOOPBACK4_IPV6;
 
-       __inet6_hash(&tcp_hashinfo, newsk);
-       inet_inherit_port(&tcp_hashinfo, sk, newsk);
+#ifdef CONFIG_TCP_MD5SIG
+       /* Copy over the MD5 key from the original socket */
+       if ((key = tcp_v6_md5_do_lookup(sk, &newnp->daddr)) != NULL) {
+               /* We're using one, so create a matching key
+                * on the newsk structure. If we fail to get
+                * memory, then we end up not copying the key
+                * across. Shucks.
+                */
+               char *newkey = kmemdup(key->key, key->keylen, GFP_ATOMIC);
+               if (newkey != NULL)
+                       tcp_v6_md5_do_add(newsk, &inet6_sk(sk)->daddr,
+                                         newkey, key->keylen);
+       }
+#endif
+
+       __inet6_hash(newsk);
+       inet_inherit_port(sk, newsk);
 
        return newsk;
 
@@ -1029,18 +1548,19 @@ out:
        return NULL;
 }
 
-static int tcp_v6_checksum_init(struct sk_buff *skb)
+static __sum16 tcp_v6_checksum_init(struct sk_buff *skb)
 {
-       if (skb->ip_summed == CHECKSUM_HW) {
-               if (!tcp_v6_check(skb->h.th,skb->len,&skb->nh.ipv6h->saddr,
-                                 &skb->nh.ipv6h->daddr,skb->csum)) {
+       if (skb->ip_summed == CHECKSUM_COMPLETE) {
+               if (!tcp_v6_check(tcp_hdr(skb), skb->len, &ipv6_hdr(skb)->saddr,
+                                 &ipv6_hdr(skb)->daddr, skb->csum)) {
                        skb->ip_summed = CHECKSUM_UNNECESSARY;
                        return 0;
                }
        }
 
-       skb->csum = ~tcp_v6_check(skb->h.th,skb->len,&skb->nh.ipv6h->saddr,
-                                 &skb->nh.ipv6h->daddr, 0);
+       skb->csum = ~csum_unfold(tcp_v6_check(tcp_hdr(skb), skb->len,
+                                             &ipv6_hdr(skb)->saddr,
+                                             &ipv6_hdr(skb)->daddr, 0));
 
        if (skb->len <= 76) {
                return __skb_checksum_complete(skb);
@@ -1073,7 +1593,12 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
        if (skb->protocol == htons(ETH_P_IP))
                return tcp_v4_do_rcv(sk, skb);
 
-       if (sk_filter(sk, skb, 0))
+#ifdef CONFIG_TCP_MD5SIG
+       if (tcp_v6_inbound_md5_hash (sk, skb))
+               goto discard;
+#endif
+
+       if (sk_filter(sk, skb))
                goto discard;
 
        /*
@@ -1092,14 +1617,14 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
           looks not very well thought. For now we latch
           options, received in the last packet, enqueued
           by tcp. Feel free to propose better solution.
-                                              --ANK (980728)
+                                              --ANK (980728)
         */
        if (np->rxopt.all)
                opt_skb = skb_clone(skb, GFP_ATOMIC);
 
        if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
                TCP_CHECK_TIMER(sk);
-               if (tcp_rcv_established(sk, skb, skb->h.th, skb->len))
+               if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len))
                        goto reset;
                TCP_CHECK_TIMER(sk);
                if (opt_skb)
@@ -1107,10 +1632,10 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
                return 0;
        }
 
-       if (skb->len < (skb->h.th->doff<<2) || tcp_checksum_complete(skb))
+       if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
                goto csum_err;
 
-       if (sk->sk_state == TCP_LISTEN) { 
+       if (sk->sk_state == TCP_LISTEN) {
                struct sock *nsk = tcp_v6_hnd_req(sk, skb);
                if (!nsk)
                        goto discard;
@@ -1120,7 +1645,7 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
                 * otherwise we just shortcircuit this and continue with
                 * the new socket..
                 */
-               if(nsk != sk) {
+               if(nsk != sk) {
                        if (tcp_child_process(sk, nsk, skb))
                                goto reset;
                        if (opt_skb)
@@ -1130,7 +1655,7 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
        }
 
        TCP_CHECK_TIMER(sk);
-       if (tcp_rcv_state_process(sk, skb, skb->h.th, skb->len))
+       if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len))
                goto reset;
        TCP_CHECK_TIMER(sk);
        if (opt_skb)
@@ -1138,7 +1663,7 @@ static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
        return 0;
 
 reset:
-       tcp_v6_send_reset(skb);
+       tcp_v6_send_reset(sk, skb);
 discard:
        if (opt_skb)
                __kfree_skb(opt_skb);
@@ -1163,7 +1688,7 @@ ipv6_pktoptions:
                if (np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo)
                        np->mcast_oif = inet6_iif(opt_skb);
                if (np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim)
-                       np->mcast_hops = opt_skb->nh.ipv6h->hop_limit;
+                       np->mcast_hops = ipv6_hdr(opt_skb)->hop_limit;
                if (ipv6_opt_accepted(sk, opt_skb)) {
                        skb_set_owner_r(opt_skb, sk);
                        opt_skb = xchg(&np->pktoptions, opt_skb);
@@ -1178,10 +1703,9 @@ ipv6_pktoptions:
        return 0;
 }
 
-static int tcp_v6_rcv(struct sk_buff **pskb)
+static int tcp_v6_rcv(struct sk_buff *skb)
 {
-       struct sk_buff *skb = *pskb;
-       struct tcphdr *th;      
+       struct tcphdr *th;
        struct sock *sk;
        int ret;
 
@@ -1196,29 +1720,29 @@ static int tcp_v6_rcv(struct sk_buff **pskb)
        if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
                goto discard_it;
 
-       th = skb->h.th;
+       th = tcp_hdr(skb);
 
        if (th->doff < sizeof(struct tcphdr)/4)
                goto bad_packet;
        if (!pskb_may_pull(skb, th->doff*4))
                goto discard_it;
 
-       if ((skb->ip_summed != CHECKSUM_UNNECESSARY &&
-            tcp_v6_checksum_init(skb)))
+       if (!skb_csum_unnecessary(skb) && tcp_v6_checksum_init(skb))
                goto bad_packet;
 
-       th = skb->h.th;
+       th = tcp_hdr(skb);
        TCP_SKB_CB(skb)->seq = ntohl(th->seq);
        TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                                    skb->len - th->doff*4);
        TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
        TCP_SKB_CB(skb)->when = 0;
-       TCP_SKB_CB(skb)->flags = ipv6_get_dsfield(skb->nh.ipv6h);
+       TCP_SKB_CB(skb)->flags = ipv6_get_dsfield(ipv6_hdr(skb));
        TCP_SKB_CB(skb)->sacked = 0;
 
-       sk = __inet6_lookup(&tcp_hashinfo, &skb->nh.ipv6h->saddr, th->source,
-                           &skb->nh.ipv6h->daddr, ntohs(th->dest),
-                           inet6_iif(skb));
+       sk = __inet6_lookup(skb->dev->nd_net, &tcp_hashinfo,
+                       &ipv6_hdr(skb)->saddr, th->source,
+                       &ipv6_hdr(skb)->daddr, ntohs(th->dest),
+                       inet6_iif(skb));
 
        if (!sk)
                goto no_tcp_socket;
@@ -1230,19 +1754,21 @@ process:
        if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb))
                goto discard_and_relse;
 
-       if (sk_filter(sk, skb, 0))
+       if (sk_filter(sk, skb))
                goto discard_and_relse;
 
        skb->dev = NULL;
 
-       bh_lock_sock(sk);
+       bh_lock_sock_nested(sk);
        ret = 0;
        if (!sock_owned_by_user(sk)) {
 #ifdef CONFIG_NET_DMA
-                struct tcp_sock *tp = tcp_sk(sk);
-                if (tp->ucopy.dma_chan)
-                        ret = tcp_v6_do_rcv(sk, skb);
-                else
+               struct tcp_sock *tp = tcp_sk(sk);
+               if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
+                       tp->ucopy.dma_chan = get_softnet_dma();
+               if (tp->ucopy.dma_chan)
+                       ret = tcp_v6_do_rcv(sk, skb);
+               else
 #endif
                {
                        if (!tcp_prequeue(sk, skb))
@@ -1263,7 +1789,7 @@ no_tcp_socket:
 bad_packet:
                TCP_INC_STATS_BH(TCP_MIB_INERRS);
        } else {
-               tcp_v6_send_reset(skb);
+               tcp_v6_send_reset(NULL, skb);
        }
 
 discard_it:
@@ -1281,24 +1807,23 @@ discard_and_relse:
 
 do_time_wait:
        if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
-               inet_twsk_put((struct inet_timewait_sock *)sk);
+               inet_twsk_put(inet_twsk(sk));
                goto discard_it;
        }
 
        if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
                TCP_INC_STATS_BH(TCP_MIB_INERRS);
-               inet_twsk_put((struct inet_timewait_sock *)sk);
+               inet_twsk_put(inet_twsk(sk));
                goto discard_it;
        }
 
-       switch (tcp_timewait_state_process((struct inet_timewait_sock *)sk,
-                                          skb, th)) {
+       switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
        case TCP_TW_SYN:
        {
                struct sock *sk2;
 
-               sk2 = inet6_lookup_listener(&tcp_hashinfo,
-                                           &skb->nh.ipv6h->daddr,
+               sk2 = inet6_lookup_listener(skb->dev->nd_net, &tcp_hashinfo,
+                                           &ipv6_hdr(skb)->daddr,
                                            ntohs(th->dest), inet6_iif(skb));
                if (sk2 != NULL) {
                        struct inet_timewait_sock *tw = inet_twsk(sk);
@@ -1337,12 +1862,22 @@ static struct inet_connection_sock_af_ops ipv6_specific = {
        .getsockopt        = ipv6_getsockopt,
        .addr2sockaddr     = inet6_csk_addr2sockaddr,
        .sockaddr_len      = sizeof(struct sockaddr_in6),
+       .bind_conflict     = inet6_csk_bind_conflict,
 #ifdef CONFIG_COMPAT
        .compat_setsockopt = compat_ipv6_setsockopt,
        .compat_getsockopt = compat_ipv6_getsockopt,
 #endif
 };
 
+#ifdef CONFIG_TCP_MD5SIG
+static struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
+       .md5_lookup     =       tcp_v6_md5_lookup,
+       .calc_md5_hash  =       tcp_v6_calc_md5_hash,
+       .md5_add        =       tcp_v6_md5_add_func,
+       .md5_parse      =       tcp_v6_parse_md5_keys,
+};
+#endif
+
 /*
  *     TCP over IPv4 via INET6 API
  */
@@ -1359,12 +1894,22 @@ static struct inet_connection_sock_af_ops ipv6_mapped = {
        .getsockopt        = ipv6_getsockopt,
        .addr2sockaddr     = inet6_csk_addr2sockaddr,
        .sockaddr_len      = sizeof(struct sockaddr_in6),
+       .bind_conflict     = inet6_csk_bind_conflict,
 #ifdef CONFIG_COMPAT
        .compat_setsockopt = compat_ipv6_setsockopt,
        .compat_getsockopt = compat_ipv6_getsockopt,
 #endif
 };
 
+#ifdef CONFIG_TCP_MD5SIG
+static struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {
+       .md5_lookup     =       tcp_v4_md5_lookup,
+       .calc_md5_hash  =       tcp_v4_calc_md5_hash,
+       .md5_add        =       tcp_v6_md5_add_func,
+       .md5_parse      =       tcp_v6_parse_md5_keys,
+};
+#endif
+
 /* NOTE: A lot of things set to zero explicitly by call to
  *       sk_alloc() so need not be done here.
  */
@@ -1404,6 +1949,10 @@ static int tcp_v6_init_sock(struct sock *sk)
        sk->sk_write_space = sk_stream_write_space;
        sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
 
+#ifdef CONFIG_TCP_MD5SIG
+       tp->af_specific = &tcp_sock_ipv6_specific;
+#endif
+
        sk->sk_sndbuf = sysctl_tcp_wmem[1];
        sk->sk_rcvbuf = sysctl_tcp_rmem[1];
 
@@ -1414,12 +1963,18 @@ static int tcp_v6_init_sock(struct sock *sk)
 
 static int tcp_v6_destroy_sock(struct sock *sk)
 {
+#ifdef CONFIG_TCP_MD5SIG
+       /* Clean up the MD5 key list */
+       if (tcp_sk(sk)->md5sig_info)
+               tcp_v6_clear_md5_list(sk);
+#endif
        tcp_v4_destroy_sock(sk);
        return inet6_destroy_sock(sk);
 }
 
+#ifdef CONFIG_PROC_FS
 /* Proc filesystem TCPv6 sock list dumping. */
-static void get_openreq6(struct seq_file *seq, 
+static void get_openreq6(struct seq_file *seq,
                         struct sock *sk, struct request_sock *req, int i, int uid)
 {
        int ttd = req->expires - jiffies;
@@ -1441,11 +1996,11 @@ static void get_openreq6(struct seq_file *seq,
                   ntohs(inet_rsk(req)->rmt_port),
                   TCP_SYN_RECV,
                   0,0, /* could print option size, but that is af dependent. */
-                  1,   /* timers active (only the expire timer) */  
-                  jiffies_to_clock_t(ttd), 
+                  1,   /* timers active (only the expire timer) */
+                  jiffies_to_clock_t(ttd),
                   req->retrans,
                   uid,
-                  0,  /* non standard timer */  
+                  0,  /* non standard timer */
                   0, /* open_requests have no inode */
                   0, req);
 }
@@ -1488,7 +2043,7 @@ static void get_tcp6_sock(struct seq_file *seq, struct sock *sp, int i)
                   src->s6_addr32[2], src->s6_addr32[3], srcp,
                   dest->s6_addr32[0], dest->s6_addr32[1],
                   dest->s6_addr32[2], dest->s6_addr32[3], destp,
-                  sp->sk_state, 
+                  sp->sk_state,
                   tp->write_seq-tp->snd_una,
                   (sp->sk_state == TCP_LISTEN) ? sp->sk_ack_backlog : (tp->rcv_nxt - tp->copied_seq),
                   timer_active,
@@ -1505,7 +2060,7 @@ static void get_tcp6_sock(struct seq_file *seq, struct sock *sp, int i)
                   );
 }
 
-static void get_timewait6_sock(struct seq_file *seq, 
+static void get_timewait6_sock(struct seq_file *seq,
                               struct inet_timewait_sock *tw, int i)
 {
        struct in6_addr *dest, *src;
@@ -1534,7 +2089,6 @@ static void get_timewait6_sock(struct seq_file *seq,
                   atomic_read(&tw->tw_refcnt), tw);
 }
 
-#ifdef CONFIG_PROC_FS
 static int tcp6_seq_show(struct seq_file *seq, void *v)
 {
        struct tcp_iter_state *st;
@@ -1575,17 +2129,19 @@ static struct tcp_seq_afinfo tcp6_seq_afinfo = {
        .seq_fops       = &tcp6_seq_fops,
 };
 
-int __init tcp6_proc_init(void)
+int tcp6_proc_init(struct net *net)
 {
-       return tcp_proc_register(&tcp6_seq_afinfo);
+       return tcp_proc_register(net, &tcp6_seq_afinfo);
 }
 
-void tcp6_proc_exit(void)
+void tcp6_proc_exit(struct net *net)
 {
-       tcp_proc_unregister(&tcp6_seq_afinfo);
+       tcp_proc_unregister(net, &tcp6_seq_afinfo);
 }
 #endif
 
+DEFINE_PROTO_INUSE(tcpv6)
+
 struct proto tcpv6_prot = {
        .name                   = "TCPv6",
        .owner                  = THIS_MODULE,
@@ -1599,12 +2155,11 @@ struct proto tcpv6_prot = {
        .shutdown               = tcp_shutdown,
        .setsockopt             = tcp_setsockopt,
        .getsockopt             = tcp_getsockopt,
-       .sendmsg                = tcp_sendmsg,
        .recvmsg                = tcp_recvmsg,
        .backlog_rcv            = tcp_v6_do_rcv,
        .hash                   = tcp_v6_hash,
-       .unhash                 = tcp_unhash,
-       .get_port               = tcp_v6_get_port,
+       .unhash                 = inet_unhash,
+       .get_port               = inet_csk_get_port,
        .enter_memory_pressure  = tcp_enter_memory_pressure,
        .sockets_allocated      = &tcp_sockets_allocated,
        .memory_allocated       = &tcp_memory_allocated,
@@ -1617,10 +2172,12 @@ struct proto tcpv6_prot = {
        .obj_size               = sizeof(struct tcp6_sock),
        .twsk_prot              = &tcp6_timewait_sock_ops,
        .rsk_prot               = &tcp6_request_sock_ops,
+       .hashinfo               = &tcp_hashinfo,
 #ifdef CONFIG_COMPAT
        .compat_setsockopt      = compat_tcp_setsockopt,
        .compat_getsockopt      = compat_tcp_getsockopt,
 #endif
+       REF_PROTO_INUSE(tcpv6)
 };
 
 static struct inet6_protocol tcpv6_protocol = {
@@ -1642,14 +2199,60 @@ static struct inet_protosw tcpv6_protosw = {
                                INET_PROTOSW_ICSK,
 };
 
-void __init tcpv6_init(void)
+static int tcpv6_net_init(struct net *net)
+{
+       int err;
+       struct socket *sock;
+       struct sock *sk;
+
+       err = inet_csk_ctl_sock_create(&sock, PF_INET6, SOCK_RAW, IPPROTO_TCP);
+       if (err)
+               return err;
+
+       net->ipv6.tcp_sk = sk = sock->sk;
+       sk_change_net(sk, net);
+       return err;
+}
+
+static void tcpv6_net_exit(struct net *net)
+{
+       sk_release_kernel(net->ipv6.tcp_sk);
+}
+
+static struct pernet_operations tcpv6_net_ops = {
+       .init = tcpv6_net_init,
+       .exit = tcpv6_net_exit,
+};
+
+int __init tcpv6_init(void)
 {
+       int ret;
+
+       ret = inet6_add_protocol(&tcpv6_protocol, IPPROTO_TCP);
+       if (ret)
+               goto out;
+
        /* register inet6 protocol */
-       if (inet6_add_protocol(&tcpv6_protocol, IPPROTO_TCP) < 0)
-               printk(KERN_ERR "tcpv6_init: Could not register protocol\n");
-       inet6_register_protosw(&tcpv6_protosw);
+       ret = inet6_register_protosw(&tcpv6_protosw);
+       if (ret)
+               goto out_tcpv6_protocol;
+
+       ret = register_pernet_subsys(&tcpv6_net_ops);
+       if (ret)
+               goto out_tcpv6_protosw;
+out:
+       return ret;
+
+out_tcpv6_protocol:
+       inet6_del_protocol(&tcpv6_protocol, IPPROTO_TCP);
+out_tcpv6_protosw:
+       inet6_unregister_protosw(&tcpv6_protosw);
+       goto out;
+}
 
-       if (inet_csk_ctl_sock_create(&tcp6_socket, PF_INET6, SOCK_RAW,
-                                    IPPROTO_TCP) < 0)
-               panic("Failed to create the TCPv6 control socket.\n");
+void tcpv6_exit(void)
+{
+       unregister_pernet_subsys(&tcpv6_net_ops);
+       inet6_unregister_protosw(&tcpv6_protosw);
+       inet6_del_protocol(&tcpv6_protocol, IPPROTO_TCP);
 }