Merge tag 'dmaengine-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw...
[linux-flexiantxendom0-3.2.10.git] / net / ipv6 / tcp_ipv6.c
index fcb3e4f..86cfe60 100644 (file)
@@ -540,19 +540,7 @@ static void tcp_v6_reqsk_destructor(struct request_sock *req)
 static struct tcp_md5sig_key *tcp_v6_md5_do_lookup(struct sock *sk,
                                                   const 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_equal(&tp->md5sig_info->keys6[i].addr, addr))
-                       return &tp->md5sig_info->keys6[i].base;
-       }
-       return NULL;
+       return tcp_md5_do_lookup(sk, (union tcp_md5_addr *)addr, AF_INET6);
 }
 
 static struct tcp_md5sig_key *tcp_v6_md5_lookup(struct sock *sk,
@@ -567,136 +555,11 @@ static struct tcp_md5sig_key *tcp_v6_reqsk_md5_lookup(struct sock *sk,
        return tcp_v6_md5_do_lookup(sk, &inet6_rsk(req)->rmt_addr);
 }
 
-static int tcp_v6_md5_do_add(struct sock *sk, const 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_nocaps_add(sk, NETIF_F_GSO_MASK);
-               }
-               if (tp->md5sig_info->entries6 == 0 &&
-                       tcp_alloc_md5sig_pool(sk) == 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) {
-                               kfree(newkey);
-                               if (tp->md5sig_info->entries6 == 0)
-                                       tcp_free_md5sig_pool();
-                               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++;
-               }
-
-               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, const 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_equal(&tp->md5sig_info->keys6[i].addr, peer)) {
-                       /* 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;
-                               tcp_free_md5sig_pool();
-                       } 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]));
-                       }
-                       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;
@@ -708,36 +571,22 @@ static int tcp_v6_parse_md5_keys (struct sock *sk, char __user *optval,
                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);
+                       return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3],
+                                             AF_INET);
+               return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin6->sin6_addr,
+                                     AF_INET6);
        }
 
        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;
+       if (ipv6_addr_v4mapped(&sin6->sin6_addr))
+               return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3],
+                                     AF_INET, cmd.tcpm_key, cmd.tcpm_keylen, GFP_KERNEL);
 
-               tp->md5sig_info = p;
-               sk_nocaps_add(sk, 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);
+       return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin6->sin6_addr,
+                             AF_INET6, cmd.tcpm_key, cmd.tcpm_keylen, GFP_KERNEL);
 }
 
 static int tcp_v6_md5_hash_pseudoheader(struct tcp_md5sig_pool *hp,
@@ -1074,6 +923,13 @@ static void tcp_v6_send_reset(struct sock *sk, struct sk_buff *skb)
        const struct tcphdr *th = tcp_hdr(skb);
        u32 seq = 0, ack_seq = 0;
        struct tcp_md5sig_key *key = NULL;
+#ifdef CONFIG_TCP_MD5SIG
+       const __u8 *hash_location = NULL;
+       struct ipv6hdr *ipv6h = ipv6_hdr(skb);
+       unsigned char newhash[16];
+       int genhash;
+       struct sock *sk1 = NULL;
+#endif
 
        if (th->rst)
                return;
@@ -1082,8 +938,32 @@ static void tcp_v6_send_reset(struct sock *sk, struct sk_buff *skb)
                return;
 
 #ifdef CONFIG_TCP_MD5SIG
-       if (sk)
-               key = tcp_v6_md5_do_lookup(sk, &ipv6_hdr(skb)->saddr);
+       hash_location = tcp_parse_md5sig_option(th);
+       if (!sk && hash_location) {
+               /*
+                * active side is lost. Try to find listening socket through
+                * source port, and then find md5 key through listening socket.
+                * we are not loose security here:
+                * Incoming packet is checked with md5 hash with finding key,
+                * no RST generated if md5 hash doesn't match.
+                */
+               sk1 = inet6_lookup_listener(dev_net(skb_dst(skb)->dev),
+                                          &tcp_hashinfo, &ipv6h->daddr,
+                                          ntohs(th->source), inet6_iif(skb));
+               if (!sk1)
+                       return;
+
+               rcu_read_lock();
+               key = tcp_v6_md5_do_lookup(sk1, &ipv6h->saddr);
+               if (!key)
+                       goto release_sk1;
+
+               genhash = tcp_v6_md5_hash_skb(newhash, key, NULL, NULL, skb);
+               if (genhash || memcmp(hash_location, newhash, 16) != 0)
+                       goto release_sk1;
+       } else {
+               key = sk ? tcp_v6_md5_do_lookup(sk, &ipv6h->saddr) : NULL;
+       }
 #endif
 
        if (th->ack)
@@ -1093,6 +973,14 @@ static void tcp_v6_send_reset(struct sock *sk, struct sk_buff *skb)
                          (th->doff << 2);
 
        tcp_v6_send_response(skb, seq, ack_seq, 0, 0, key, 1, 0);
+
+#ifdef CONFIG_TCP_MD5SIG
+release_sk1:
+       if (sk1) {
+               rcu_read_unlock();
+               sock_put(sk1);
+       }
+#endif
 }
 
 static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts,
@@ -1394,6 +1282,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  = ipv6_hdr(skb)->hop_limit;
+               newnp->rcv_tclass  = ipv6_tclass(ipv6_hdr(skb));
 
                /*
                 * No need to charge this sock to the relevant IPv6 refcnt debug socks count
@@ -1472,6 +1361,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 = ipv6_hdr(skb)->hop_limit;
+       newnp->rcv_tclass = ipv6_tclass(ipv6_hdr(skb));
 
        /* Clone native IPv6 options from listening socket (if any)
 
@@ -1510,10 +1400,8 @@ static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
                 * 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, &newnp->daddr,
-                                         newkey, key->keylen);
+               tcp_md5_do_add(newsk, (union tcp_md5_addr *)&newnp->daddr,
+                              AF_INET6, key->key, key->keylen, GFP_ATOMIC);
        }
 #endif
 
@@ -1676,6 +1564,8 @@ ipv6_pktoptions:
                        np->mcast_oif = inet6_iif(opt_skb);
                if (np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim)
                        np->mcast_hops = ipv6_hdr(opt_skb)->hop_limit;
+               if (np->rxopt.bits.rxtclass)
+                       np->rcv_tclass = ipv6_tclass(ipv6_hdr(skb));
                if (ipv6_opt_accepted(sk, opt_skb)) {
                        skb_set_owner_r(opt_skb, sk);
                        opt_skb = xchg(&np->pktoptions, opt_skb);
@@ -1898,7 +1788,6 @@ static const struct inet_connection_sock_af_ops ipv6_specific = {
 static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
        .md5_lookup     =       tcp_v6_md5_lookup,
        .calc_md5_hash  =       tcp_v6_md5_hash_skb,
-       .md5_add        =       tcp_v6_md5_add_func,
        .md5_parse      =       tcp_v6_parse_md5_keys,
 };
 #endif
@@ -1930,7 +1819,6 @@ static const struct inet_connection_sock_af_ops ipv6_mapped = {
 static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {
        .md5_lookup     =       tcp_v4_md5_lookup,
        .calc_md5_hash  =       tcp_v4_md5_hash_skb,
-       .md5_add        =       tcp_v6_md5_add_func,
        .md5_parse      =       tcp_v6_parse_md5_keys,
 };
 #endif
@@ -2004,11 +1892,6 @@ static int tcp_v6_init_sock(struct sock *sk)
 
 static void 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);
        inet6_destroy_sock(sk);
 }