]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - net/ipv6/reassembly.c
inet: frags: get rif of inet_frag_evicting()
[mirror_ubuntu-bionic-kernel.git] / net / ipv6 / reassembly.c
index afbc000ad4f2912acd475246e2ff2368c1bbeacb..70acad126d044a0f6a1efc63f307805fcf7b1df9 100644 (file)
@@ -79,57 +79,17 @@ static struct inet_frags ip6_frags;
 static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *prev,
                          struct net_device *dev);
 
-/*
- * callers should be careful not to use the hash value outside the ipfrag_lock
- * as doing so could race with ipfrag_hash_rnd being recalculated.
- */
-static unsigned int inet6_hash_frag(__be32 id, const struct in6_addr *saddr,
-                                   const struct in6_addr *daddr)
-{
-       net_get_random_once(&ip6_frags.rnd, sizeof(ip6_frags.rnd));
-       return jhash_3words(ipv6_addr_hash(saddr), ipv6_addr_hash(daddr),
-                           (__force u32)id, ip6_frags.rnd);
-}
-
-static unsigned int ip6_hashfn(const struct inet_frag_queue *q)
-{
-       const struct frag_queue *fq;
-
-       fq = container_of(q, struct frag_queue, q);
-       return inet6_hash_frag(fq->id, &fq->saddr, &fq->daddr);
-}
-
-bool ip6_frag_match(const struct inet_frag_queue *q, const void *a)
-{
-       const struct frag_queue *fq;
-       const struct ip6_create_arg *arg = a;
-
-       fq = container_of(q, struct frag_queue, q);
-       return  fq->id == arg->id &&
-               fq->user == arg->user &&
-               ipv6_addr_equal(&fq->saddr, arg->src) &&
-               ipv6_addr_equal(&fq->daddr, arg->dst) &&
-               (arg->iif == fq->iif ||
-                !(ipv6_addr_type(arg->dst) & (IPV6_ADDR_MULTICAST |
-                                              IPV6_ADDR_LINKLOCAL)));
-}
-EXPORT_SYMBOL(ip6_frag_match);
-
 void ip6_frag_init(struct inet_frag_queue *q, const void *a)
 {
        struct frag_queue *fq = container_of(q, struct frag_queue, q);
-       const struct ip6_create_arg *arg = a;
+       const struct frag_v6_compare_key *key = a;
 
-       fq->id = arg->id;
-       fq->user = arg->user;
-       fq->saddr = *arg->src;
-       fq->daddr = *arg->dst;
-       fq->ecn = arg->ecn;
+       q->key.v6 = *key;
+       fq->ecn = 0;
 }
 EXPORT_SYMBOL(ip6_frag_init);
 
-void ip6_expire_frag_queue(struct net *net, struct frag_queue *fq,
-                          struct inet_frags *frags)
+void ip6_expire_frag_queue(struct net *net, struct frag_queue *fq)
 {
        struct net_device *dev = NULL;
 
@@ -138,7 +98,7 @@ void ip6_expire_frag_queue(struct net *net, struct frag_queue *fq,
        if (fq->q.flags & INET_FRAG_COMPLETE)
                goto out;
 
-       inet_frag_kill(&fq->q, frags);
+       inet_frag_kill(&fq->q);
 
        rcu_read_lock();
        dev = dev_get_by_index_rcu(net, fq->iif);
@@ -146,10 +106,6 @@ void ip6_expire_frag_queue(struct net *net, struct frag_queue *fq,
                goto out_rcu_unlock;
 
        __IP6_INC_STATS(net, __in6_dev_get(dev), IPSTATS_MIB_REASMFAILS);
-
-       if (inet_frag_evicting(&fq->q))
-               goto out_rcu_unlock;
-
        __IP6_INC_STATS(net, __in6_dev_get(dev), IPSTATS_MIB_REASMTIMEOUT);
 
        /* Don't send error if the first segment did not arrive. */
@@ -166,7 +122,7 @@ out_rcu_unlock:
        rcu_read_unlock();
 out:
        spin_unlock(&fq->q.lock);
-       inet_frag_put(&fq->q, frags);
+       inet_frag_put(&fq->q);
 }
 EXPORT_SYMBOL(ip6_expire_frag_queue);
 
@@ -179,27 +135,26 @@ static void ip6_frag_expire(struct timer_list *t)
        fq = container_of(frag, struct frag_queue, q);
        net = container_of(fq->q.net, struct net, ipv6.frags);
 
-       ip6_expire_frag_queue(net, fq, &ip6_frags);
+       ip6_expire_frag_queue(net, fq);
 }
 
 static struct frag_queue *
-fq_find(struct net *net, __be32 id, const struct in6_addr *src,
-       const struct in6_addr *dst, int iif, u8 ecn)
+fq_find(struct net *net, __be32 id, const struct ipv6hdr *hdr, int iif)
 {
+       struct frag_v6_compare_key key = {
+               .id = id,
+               .saddr = hdr->saddr,
+               .daddr = hdr->daddr,
+               .user = IP6_DEFRAG_LOCAL_DELIVER,
+               .iif = iif,
+       };
        struct inet_frag_queue *q;
-       struct ip6_create_arg arg;
-       unsigned int hash;
 
-       arg.id = id;
-       arg.user = IP6_DEFRAG_LOCAL_DELIVER;
-       arg.src = src;
-       arg.dst = dst;
-       arg.iif = iif;
-       arg.ecn = ecn;
+       if (!(ipv6_addr_type(&hdr->daddr) & (IPV6_ADDR_MULTICAST |
+                                           IPV6_ADDR_LINKLOCAL)))
+               key.iif = 0;
 
-       hash = inet6_hash_frag(id, src, dst);
-
-       q = inet_frag_find(&net->ipv6.frags, &ip6_frags, &arg, hash);
+       q = inet_frag_find(&net->ipv6.frags, &key);
        if (IS_ERR_OR_NULL(q)) {
                inet_frag_maybe_warn_overflow(q, pr_fmt());
                return NULL;
@@ -364,7 +319,7 @@ found:
        return -1;
 
 discard_fq:
-       inet_frag_kill(&fq->q, &ip6_frags);
+       inet_frag_kill(&fq->q);
 err:
        __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
                        IPSTATS_MIB_REASMFAILS);
@@ -391,7 +346,7 @@ static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *prev,
        int sum_truesize;
        u8 ecn;
 
-       inet_frag_kill(&fq->q, &ip6_frags);
+       inet_frag_kill(&fq->q);
 
        ecn = ip_frag_ecn_table[fq->ecn];
        if (unlikely(ecn == 0xff))
@@ -531,6 +486,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
        struct frag_queue *fq;
        const struct ipv6hdr *hdr = ipv6_hdr(skb);
        struct net *net = dev_net(skb_dst(skb)->dev);
+       int iif;
 
        if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED)
                goto fail_hdr;
@@ -559,17 +515,18 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
                return 1;
        }
 
-       fq = fq_find(net, fhdr->identification, &hdr->saddr, &hdr->daddr,
-                    skb->dev ? skb->dev->ifindex : 0, ip6_frag_ecn(hdr));
+       iif = skb->dev ? skb->dev->ifindex : 0;
+       fq = fq_find(net, fhdr->identification, hdr, iif);
        if (fq) {
                int ret;
 
                spin_lock(&fq->q.lock);
 
+               fq->iif = iif;
                ret = ip6_frag_queue(fq, skb, fhdr, IP6CB(skb)->nhoff);
 
                spin_unlock(&fq->q.lock);
-               inet_frag_put(&fq->q, &ip6_frags);
+               inet_frag_put(&fq->q);
                return ret;
        }
 
@@ -650,10 +607,6 @@ static int __net_init ip6_frags_ns_sysctl_register(struct net *net)
                table[1].data = &net->ipv6.frags.low_thresh;
                table[1].extra2 = &net->ipv6.frags.high_thresh;
                table[2].data = &net->ipv6.frags.timeout;
-
-               /* Don't export sysctls to unprivileged users */
-               if (net->user_ns != &init_user_ns)
-                       table[0].procname = NULL;
        }
 
        hdr = register_net_sysctl(net, "net/ipv6", table);
@@ -715,19 +668,27 @@ static void ip6_frags_sysctl_unregister(void)
 
 static int __net_init ipv6_frags_init_net(struct net *net)
 {
+       int res;
+
        net->ipv6.frags.high_thresh = IPV6_FRAG_HIGH_THRESH;
        net->ipv6.frags.low_thresh = IPV6_FRAG_LOW_THRESH;
        net->ipv6.frags.timeout = IPV6_FRAG_TIMEOUT;
+       net->ipv6.frags.f = &ip6_frags;
 
-       inet_frags_init_net(&net->ipv6.frags);
+       res = inet_frags_init_net(&net->ipv6.frags);
+       if (res < 0)
+               return res;
 
-       return ip6_frags_ns_sysctl_register(net);
+       res = ip6_frags_ns_sysctl_register(net);
+       if (res < 0)
+               inet_frags_exit_net(&net->ipv6.frags);
+       return res;
 }
 
 static void __net_exit ipv6_frags_exit_net(struct net *net)
 {
        ip6_frags_ns_sysctl_unregister(net);
-       inet_frags_exit_net(&net->ipv6.frags, &ip6_frags);
+       inet_frags_exit_net(&net->ipv6.frags);
 }
 
 static struct pernet_operations ip6_frags_ops = {
@@ -735,14 +696,55 @@ static struct pernet_operations ip6_frags_ops = {
        .exit = ipv6_frags_exit_net,
 };
 
+static u32 ip6_key_hashfn(const void *data, u32 len, u32 seed)
+{
+       return jhash2(data,
+                     sizeof(struct frag_v6_compare_key) / sizeof(u32), seed);
+}
+
+static u32 ip6_obj_hashfn(const void *data, u32 len, u32 seed)
+{
+       const struct inet_frag_queue *fq = data;
+
+       return jhash2((const u32 *)&fq->key.v6,
+                     sizeof(struct frag_v6_compare_key) / sizeof(u32), seed);
+}
+
+static int ip6_obj_cmpfn(struct rhashtable_compare_arg *arg, const void *ptr)
+{
+       const struct frag_v6_compare_key *key = arg->key;
+       const struct inet_frag_queue *fq = ptr;
+
+       return !!memcmp(&fq->key, key, sizeof(*key));
+}
+
+const struct rhashtable_params ip6_rhash_params = {
+       .head_offset            = offsetof(struct inet_frag_queue, node),
+       .hashfn                 = ip6_key_hashfn,
+       .obj_hashfn             = ip6_obj_hashfn,
+       .obj_cmpfn              = ip6_obj_cmpfn,
+       .automatic_shrinking    = true,
+};
+EXPORT_SYMBOL(ip6_rhash_params);
+
 int __init ipv6_frag_init(void)
 {
        int ret;
 
-       ret = inet6_add_protocol(&frag_protocol, IPPROTO_FRAGMENT);
+       ip6_frags.constructor = ip6_frag_init;
+       ip6_frags.destructor = NULL;
+       ip6_frags.qsize = sizeof(struct frag_queue);
+       ip6_frags.frag_expire = ip6_frag_expire;
+       ip6_frags.frags_cache_name = ip6_frag_cache_name;
+       ip6_frags.rhash_params = ip6_rhash_params;
+       ret = inet_frags_init(&ip6_frags);
        if (ret)
                goto out;
 
+       ret = inet6_add_protocol(&frag_protocol, IPPROTO_FRAGMENT);
+       if (ret)
+               goto err_protocol;
+
        ret = ip6_frags_sysctl_register();
        if (ret)
                goto err_sysctl;
@@ -751,16 +753,6 @@ int __init ipv6_frag_init(void)
        if (ret)
                goto err_pernet;
 
-       ip6_frags.hashfn = ip6_hashfn;
-       ip6_frags.constructor = ip6_frag_init;
-       ip6_frags.destructor = NULL;
-       ip6_frags.qsize = sizeof(struct frag_queue);
-       ip6_frags.match = ip6_frag_match;
-       ip6_frags.frag_expire = ip6_frag_expire;
-       ip6_frags.frags_cache_name = ip6_frag_cache_name;
-       ret = inet_frags_init(&ip6_frags);
-       if (ret)
-               goto err_pernet;
 out:
        return ret;
 
@@ -768,6 +760,8 @@ err_pernet:
        ip6_frags_sysctl_unregister();
 err_sysctl:
        inet6_del_protocol(&frag_protocol, IPPROTO_FRAGMENT);
+err_protocol:
+       inet_frags_fini(&ip6_frags);
        goto out;
 }