]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/ipv4/tcp_ipv4.c
Merge tag 'dmaengine-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw...
[karo-tx-linux.git] / net / ipv4 / tcp_ipv4.c
index 3810b6fe0a1e801ed7820072b6776bc94a6fa0c5..0cb86ceb652ff66432ba584fedef8231d94decc6 100644 (file)
@@ -50,6 +50,7 @@
  *                                     a single port at the same time.
  */
 
+#define pr_fmt(fmt) "TCP: " fmt
 
 #include <linux/bottom_half.h>
 #include <linux/types.h>
@@ -90,16 +91,8 @@ EXPORT_SYMBOL(sysctl_tcp_low_latency);
 
 
 #ifdef CONFIG_TCP_MD5SIG
-static struct tcp_md5sig_key *tcp_v4_md5_do_lookup(struct sock *sk,
-                                                  __be32 addr);
-static int tcp_v4_md5_hash_hdr(char *md5_hash, struct tcp_md5sig_key *key,
+static int tcp_v4_md5_hash_hdr(char *md5_hash, const struct tcp_md5sig_key *key,
                               __be32 daddr, __be32 saddr, const struct tcphdr *th);
-#else
-static inline
-struct tcp_md5sig_key *tcp_v4_md5_do_lookup(struct sock *sk, __be32 addr)
-{
-       return NULL;
-}
 #endif
 
 struct inet_hashinfo tcp_hashinfo;
@@ -601,6 +594,10 @@ static void tcp_v4_send_reset(struct sock *sk, struct sk_buff *skb)
        struct ip_reply_arg arg;
 #ifdef CONFIG_TCP_MD5SIG
        struct tcp_md5sig_key *key;
+       const __u8 *hash_location = NULL;
+       unsigned char newhash[16];
+       int genhash;
+       struct sock *sk1 = NULL;
 #endif
        struct net *net;
 
@@ -631,7 +628,36 @@ static void tcp_v4_send_reset(struct sock *sk, struct sk_buff *skb)
        arg.iov[0].iov_len  = sizeof(rep.th);
 
 #ifdef CONFIG_TCP_MD5SIG
-       key = sk ? tcp_v4_md5_do_lookup(sk, ip_hdr(skb)->saddr) : NULL;
+       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 = __inet_lookup_listener(dev_net(skb_dst(skb)->dev),
+                                            &tcp_hashinfo, ip_hdr(skb)->daddr,
+                                            ntohs(th->source), inet_iif(skb));
+               /* don't send rst if it can't find key */
+               if (!sk1)
+                       return;
+               rcu_read_lock();
+               key = tcp_md5_do_lookup(sk1, (union tcp_md5_addr *)
+                                       &ip_hdr(skb)->saddr, AF_INET);
+               if (!key)
+                       goto release_sk1;
+
+               genhash = tcp_v4_md5_hash_skb(newhash, key, NULL, NULL, skb);
+               if (genhash || memcmp(hash_location, newhash, 16) != 0)
+                       goto release_sk1;
+       } else {
+               key = sk ? tcp_md5_do_lookup(sk, (union tcp_md5_addr *)
+                                            &ip_hdr(skb)->saddr,
+                                            AF_INET) : NULL;
+       }
+
        if (key) {
                rep.opt[0] = htonl((TCPOPT_NOP << 24) |
                                   (TCPOPT_NOP << 16) |
@@ -664,6 +690,14 @@ static void tcp_v4_send_reset(struct sock *sk, struct sk_buff *skb)
 
        TCP_INC_STATS_BH(net, TCP_MIB_OUTSEGS);
        TCP_INC_STATS_BH(net, TCP_MIB_OUTRSTS);
+
+#ifdef CONFIG_TCP_MD5SIG
+release_sk1:
+       if (sk1) {
+               rcu_read_unlock();
+               sock_put(sk1);
+       }
+#endif
 }
 
 /* The code following below sending ACKs in SYN-RECV and TIME-WAIT states
@@ -764,7 +798,8 @@ static void tcp_v4_reqsk_send_ack(struct sock *sk, struct sk_buff *skb,
                        tcp_rsk(req)->rcv_isn + 1, req->rcv_wnd,
                        req->ts_recent,
                        0,
-                       tcp_v4_md5_do_lookup(sk, ip_hdr(skb)->daddr),
+                       tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&ip_hdr(skb)->daddr,
+                                         AF_INET),
                        inet_rsk(req)->no_srccheck ? IP_REPLY_ARG_NOSRCCHECK : 0,
                        ip_hdr(skb)->tos);
 }
@@ -842,8 +877,7 @@ int tcp_syn_flood_action(struct sock *sk,
        lopt = inet_csk(sk)->icsk_accept_queue.listen_opt;
        if (!lopt->synflood_warned) {
                lopt->synflood_warned = 1;
-               pr_info("%s: Possible SYN flooding on port %d. %s. "
-                       " Check SNMP counters.\n",
+               pr_info("%s: Possible SYN flooding on port %d. %s.  Check SNMP counters.\n",
                        proto, ntohs(tcp_hdr(skb)->dest), msg);
        }
        return want_cookie;
@@ -881,153 +915,138 @@ static struct ip_options_rcu *tcp_v4_save_options(struct sock *sk,
  */
 
 /* Find the Key structure for an address.  */
-static struct tcp_md5sig_key *
-                       tcp_v4_md5_do_lookup(struct sock *sk, __be32 addr)
+struct tcp_md5sig_key *tcp_md5_do_lookup(struct sock *sk,
+                                        const union tcp_md5_addr *addr,
+                                        int family)
 {
        struct tcp_sock *tp = tcp_sk(sk);
-       int i;
-
-       if (!tp->md5sig_info || !tp->md5sig_info->entries4)
+       struct tcp_md5sig_key *key;
+       struct hlist_node *pos;
+       unsigned int size = sizeof(struct in_addr);
+       struct tcp_md5sig_info *md5sig;
+
+       /* caller either holds rcu_read_lock() or socket lock */
+       md5sig = rcu_dereference_check(tp->md5sig_info,
+                                      sock_owned_by_user(sk) ||
+                                      lockdep_is_held(&sk->sk_lock.slock));
+       if (!md5sig)
                return NULL;
-       for (i = 0; i < tp->md5sig_info->entries4; i++) {
-               if (tp->md5sig_info->keys4[i].addr == addr)
-                       return &tp->md5sig_info->keys4[i].base;
+#if IS_ENABLED(CONFIG_IPV6)
+       if (family == AF_INET6)
+               size = sizeof(struct in6_addr);
+#endif
+       hlist_for_each_entry_rcu(key, pos, &md5sig->head, node) {
+               if (key->family != family)
+                       continue;
+               if (!memcmp(&key->addr, addr, size))
+                       return key;
        }
        return NULL;
 }
+EXPORT_SYMBOL(tcp_md5_do_lookup);
 
 struct tcp_md5sig_key *tcp_v4_md5_lookup(struct sock *sk,
                                         struct sock *addr_sk)
 {
-       return tcp_v4_md5_do_lookup(sk, inet_sk(addr_sk)->inet_daddr);
+       union tcp_md5_addr *addr;
+
+       addr = (union tcp_md5_addr *)&inet_sk(addr_sk)->inet_daddr;
+       return tcp_md5_do_lookup(sk, addr, AF_INET);
 }
 EXPORT_SYMBOL(tcp_v4_md5_lookup);
 
 static struct tcp_md5sig_key *tcp_v4_reqsk_md5_lookup(struct sock *sk,
                                                      struct request_sock *req)
 {
-       return tcp_v4_md5_do_lookup(sk, inet_rsk(req)->rmt_addr);
+       union tcp_md5_addr *addr;
+
+       addr = (union tcp_md5_addr *)&inet_rsk(req)->rmt_addr;
+       return tcp_md5_do_lookup(sk, addr, AF_INET);
 }
 
 /* This can be called on a newly created socket, from other files */
-int tcp_v4_md5_do_add(struct sock *sk, __be32 addr,
-                     u8 *newkey, u8 newkeylen)
+int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
+                  int family, const u8 *newkey, u8 newkeylen, gfp_t gfp)
 {
        /* Add Key to the list */
        struct tcp_md5sig_key *key;
        struct tcp_sock *tp = tcp_sk(sk);
-       struct tcp4_md5sig_key *keys;
+       struct tcp_md5sig_info *md5sig;
 
-       key = tcp_v4_md5_do_lookup(sk, addr);
+       key = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&addr, AF_INET);
        if (key) {
                /* Pre-existing entry - just update that one. */
-               kfree(key->key);
-               key->key = newkey;
+               memcpy(key->key, newkey, newkeylen);
                key->keylen = newkeylen;
-       } else {
-               struct tcp_md5sig_info *md5sig;
-
-               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);
-               }
+               return 0;
+       }
 
-               md5sig = tp->md5sig_info;
-               if (md5sig->entries4 == 0 &&
-                   tcp_alloc_md5sig_pool(sk) == NULL) {
-                       kfree(newkey);
+       md5sig = rcu_dereference_protected(tp->md5sig_info,
+                                          sock_owned_by_user(sk));
+       if (!md5sig) {
+               md5sig = kmalloc(sizeof(*md5sig), gfp);
+               if (!md5sig)
                        return -ENOMEM;
-               }
-
-               if (md5sig->alloced4 == md5sig->entries4) {
-                       keys = kmalloc((sizeof(*keys) *
-                                       (md5sig->entries4 + 1)), GFP_ATOMIC);
-                       if (!keys) {
-                               kfree(newkey);
-                               if (md5sig->entries4 == 0)
-                                       tcp_free_md5sig_pool();
-                               return -ENOMEM;
-                       }
 
-                       if (md5sig->entries4)
-                               memcpy(keys, md5sig->keys4,
-                                      sizeof(*keys) * md5sig->entries4);
+               sk_nocaps_add(sk, NETIF_F_GSO_MASK);
+               INIT_HLIST_HEAD(&md5sig->head);
+               rcu_assign_pointer(tp->md5sig_info, md5sig);
+       }
 
-                       /* Free old key list, and reference new one */
-                       kfree(md5sig->keys4);
-                       md5sig->keys4 = keys;
-                       md5sig->alloced4++;
-               }
-               md5sig->entries4++;
-               md5sig->keys4[md5sig->entries4 - 1].addr        = addr;
-               md5sig->keys4[md5sig->entries4 - 1].base.key    = newkey;
-               md5sig->keys4[md5sig->entries4 - 1].base.keylen = newkeylen;
+       key = sock_kmalloc(sk, sizeof(*key), gfp);
+       if (!key)
+               return -ENOMEM;
+       if (hlist_empty(&md5sig->head) && !tcp_alloc_md5sig_pool(sk)) {
+               sock_kfree_s(sk, key, sizeof(*key));
+               return -ENOMEM;
        }
-       return 0;
-}
-EXPORT_SYMBOL(tcp_v4_md5_do_add);
 
-static int tcp_v4_md5_add_func(struct sock *sk, struct sock *addr_sk,
-                              u8 *newkey, u8 newkeylen)
-{
-       return tcp_v4_md5_do_add(sk, inet_sk(addr_sk)->inet_daddr,
-                                newkey, newkeylen);
+       memcpy(key->key, newkey, newkeylen);
+       key->keylen = newkeylen;
+       key->family = family;
+       memcpy(&key->addr, addr,
+              (family == AF_INET6) ? sizeof(struct in6_addr) :
+                                     sizeof(struct in_addr));
+       hlist_add_head_rcu(&key->node, &md5sig->head);
+       return 0;
 }
+EXPORT_SYMBOL(tcp_md5_do_add);
 
-int tcp_v4_md5_do_del(struct sock *sk, __be32 addr)
+int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int family)
 {
        struct tcp_sock *tp = tcp_sk(sk);
-       int i;
-
-       for (i = 0; i < tp->md5sig_info->entries4; i++) {
-               if (tp->md5sig_info->keys4[i].addr == addr) {
-                       /* Free the key */
-                       kfree(tp->md5sig_info->keys4[i].base.key);
-                       tp->md5sig_info->entries4--;
-
-                       if (tp->md5sig_info->entries4 == 0) {
-                               kfree(tp->md5sig_info->keys4);
-                               tp->md5sig_info->keys4 = NULL;
-                               tp->md5sig_info->alloced4 = 0;
-                               tcp_free_md5sig_pool();
-                       } else if (tp->md5sig_info->entries4 != i) {
-                               /* Need to do some manipulation */
-                               memmove(&tp->md5sig_info->keys4[i],
-                                       &tp->md5sig_info->keys4[i+1],
-                                       (tp->md5sig_info->entries4 - i) *
-                                        sizeof(struct tcp4_md5sig_key));
-                       }
-                       return 0;
-               }
-       }
-       return -ENOENT;
+       struct tcp_md5sig_key *key;
+       struct tcp_md5sig_info *md5sig;
+
+       key = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&addr, AF_INET);
+       if (!key)
+               return -ENOENT;
+       hlist_del_rcu(&key->node);
+       atomic_sub(sizeof(*key), &sk->sk_omem_alloc);
+       kfree_rcu(key, rcu);
+       md5sig = rcu_dereference_protected(tp->md5sig_info,
+                                          sock_owned_by_user(sk));
+       if (hlist_empty(&md5sig->head))
+               tcp_free_md5sig_pool();
+       return 0;
 }
-EXPORT_SYMBOL(tcp_v4_md5_do_del);
+EXPORT_SYMBOL(tcp_md5_do_del);
 
-static void tcp_v4_clear_md5_list(struct sock *sk)
+void tcp_clear_md5_list(struct sock *sk)
 {
        struct tcp_sock *tp = tcp_sk(sk);
+       struct tcp_md5sig_key *key;
+       struct hlist_node *pos, *n;
+       struct tcp_md5sig_info *md5sig;
 
-       /* Free each key, then the set of key keys,
-        * the crypto element, and then decrement our
-        * hold on the last resort crypto.
-        */
-       if (tp->md5sig_info->entries4) {
-               int i;
-               for (i = 0; i < tp->md5sig_info->entries4; i++)
-                       kfree(tp->md5sig_info->keys4[i].base.key);
-               tp->md5sig_info->entries4 = 0;
+       md5sig = rcu_dereference_protected(tp->md5sig_info, 1);
+
+       if (!hlist_empty(&md5sig->head))
                tcp_free_md5sig_pool();
-       }
-       if (tp->md5sig_info->keys4) {
-               kfree(tp->md5sig_info->keys4);
-               tp->md5sig_info->keys4 = NULL;
-               tp->md5sig_info->alloced4  = 0;
+       hlist_for_each_entry_safe(key, pos, n, &md5sig->head, node) {
+               hlist_del_rcu(&key->node);
+               atomic_sub(sizeof(*key), &sk->sk_omem_alloc);
+               kfree_rcu(key, rcu);
        }
 }
 
@@ -1036,7 +1055,6 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char __user *optval,
 {
        struct tcp_md5sig cmd;
        struct sockaddr_in *sin = (struct sockaddr_in *)&cmd.tcpm_addr;
-       u8 *newkey;
 
        if (optlen < sizeof(cmd))
                return -EINVAL;
@@ -1047,32 +1065,16 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char __user *optval,
        if (sin->sin_family != AF_INET)
                return -EINVAL;
 
-       if (!cmd.tcpm_key || !cmd.tcpm_keylen) {
-               if (!tcp_sk(sk)->md5sig_info)
-                       return -ENOENT;
-               return tcp_v4_md5_do_del(sk, sin->sin_addr.s_addr);
-       }
+       if (!cmd.tcpm_key || !cmd.tcpm_keylen)
+               return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,
+                                     AF_INET);
 
        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(*p), sk->sk_allocation);
-               if (!p)
-                       return -EINVAL;
-
-               tp->md5sig_info = p;
-               sk_nocaps_add(sk, NETIF_F_GSO_MASK);
-       }
-
-       newkey = kmemdup(cmd.tcpm_key, cmd.tcpm_keylen, sk->sk_allocation);
-       if (!newkey)
-               return -ENOMEM;
-       return tcp_v4_md5_do_add(sk, sin->sin_addr.s_addr,
-                                newkey, cmd.tcpm_keylen);
+       return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,
+                             AF_INET, cmd.tcpm_key, cmd.tcpm_keylen,
+                             GFP_KERNEL);
 }
 
 static int tcp_v4_md5_hash_pseudoheader(struct tcp_md5sig_pool *hp,
@@ -1098,7 +1100,7 @@ static int tcp_v4_md5_hash_pseudoheader(struct tcp_md5sig_pool *hp,
        return crypto_hash_update(&hp->md5_desc, &sg, sizeof(*bp));
 }
 
-static int tcp_v4_md5_hash_hdr(char *md5_hash, struct tcp_md5sig_key *key,
+static int tcp_v4_md5_hash_hdr(char *md5_hash, const struct tcp_md5sig_key *key,
                               __be32 daddr, __be32 saddr, const struct tcphdr *th)
 {
        struct tcp_md5sig_pool *hp;
@@ -1198,7 +1200,8 @@ static int tcp_v4_inbound_md5_hash(struct sock *sk, const struct sk_buff *skb)
        int genhash;
        unsigned char newhash[16];
 
-       hash_expected = tcp_v4_md5_do_lookup(sk, iph->saddr);
+       hash_expected = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&iph->saddr,
+                                         AF_INET);
        hash_location = tcp_parse_md5sig_option(th);
 
        /* We've parsed the options - do we have a hash? */
@@ -1224,10 +1227,10 @@ static int tcp_v4_inbound_md5_hash(struct sock *sk, const struct sk_buff *skb)
 
        if (genhash || memcmp(hash_location, newhash, 16) != 0) {
                if (net_ratelimit()) {
-                       printk(KERN_INFO "MD5 Hash failed for (%pI4, %d)->(%pI4, %d)%s\n",
-                              &iph->saddr, ntohs(th->source),
-                              &iph->daddr, ntohs(th->dest),
-                              genhash ? " tcp_v4_calc_md5_hash failed" : "");
+                       pr_info("MD5 Hash failed for (%pI4, %d)->(%pI4, %d)%s\n",
+                               &iph->saddr, ntohs(th->source),
+                               &iph->daddr, ntohs(th->dest),
+                               genhash ? " tcp_v4_calc_md5_hash failed" : "");
                }
                return 1;
        }
@@ -1396,7 +1399,7 @@ int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
                         * to destinations, already remembered
                         * to the moment of synflood.
                         */
-                       LIMIT_NETDEBUG(KERN_DEBUG "TCP: drop open request from %pI4/%u\n",
+                       LIMIT_NETDEBUG(KERN_DEBUG pr_fmt("drop open request from %pI4/%u\n"),
                                       &saddr, ntohs(tcp_hdr(skb)->source));
                        goto drop_and_release;
                }
@@ -1461,6 +1464,7 @@ struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
        ireq->opt             = NULL;
        newinet->mc_index     = inet_iif(skb);
        newinet->mc_ttl       = ip_hdr(skb)->ttl;
+       newinet->rcv_tos      = ip_hdr(skb)->tos;
        inet_csk(newsk)->icsk_ext_hdr_len = 0;
        if (inet_opt)
                inet_csk(newsk)->icsk_ext_hdr_len = inet_opt->opt.optlen;
@@ -1490,7 +1494,8 @@ struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
 
 #ifdef CONFIG_TCP_MD5SIG
        /* Copy over the MD5 key from the original socket */
-       key = tcp_v4_md5_do_lookup(sk, newinet->inet_daddr);
+       key = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&newinet->inet_daddr,
+                               AF_INET);
        if (key != NULL) {
                /*
                 * We're using one, so create a matching key
@@ -1498,10 +1503,8 @@ struct sock *tcp_v4_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_v4_md5_do_add(newsk, newinet->inet_daddr,
-                                         newkey, key->keylen);
+               tcp_md5_do_add(newsk, (union tcp_md5_addr *)&newinet->inet_daddr,
+                              AF_INET, key->key, key->keylen, GFP_ATOMIC);
                sk_nocaps_add(newsk, NETIF_F_GSO_MASK);
        }
 #endif
@@ -1862,7 +1865,6 @@ EXPORT_SYMBOL(ipv4_specific);
 static const struct tcp_sock_af_ops tcp_sock_ipv4_specific = {
        .md5_lookup             = tcp_v4_md5_lookup,
        .calc_md5_hash          = tcp_v4_md5_hash_skb,
-       .md5_add                = tcp_v4_md5_add_func,
        .md5_parse              = tcp_v4_parse_md5_keys,
 };
 #endif
@@ -1951,8 +1953,8 @@ void tcp_v4_destroy_sock(struct sock *sk)
 #ifdef CONFIG_TCP_MD5SIG
        /* Clean up the MD5 key list, if any */
        if (tp->md5sig_info) {
-               tcp_v4_clear_md5_list(sk);
-               kfree(tp->md5sig_info);
+               tcp_clear_md5_list(sk);
+               kfree_rcu(tp->md5sig_info, rcu);
                tp->md5sig_info = NULL;
        }
 #endif