diff options
Diffstat (limited to 'net/sctp/input.c')
-rw-r--r-- | net/sctp/input.c | 200 |
1 files changed, 135 insertions, 65 deletions
diff --git a/net/sctp/input.c b/net/sctp/input.c index b6493b3f1..49d2cc751 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -782,65 +782,149 @@ hit: return ep; } -/* Insert association into the hash table. */ -static void __sctp_hash_established(struct sctp_association *asoc) +/* rhashtable for transport */ +struct sctp_hash_cmp_arg { + const struct sctp_endpoint *ep; + const union sctp_addr *laddr; + const union sctp_addr *paddr; + const struct net *net; +}; + +static inline int sctp_hash_cmp(struct rhashtable_compare_arg *arg, + const void *ptr) { - struct net *net = sock_net(asoc->base.sk); - struct sctp_ep_common *epb; - struct sctp_hashbucket *head; + const struct sctp_hash_cmp_arg *x = arg->key; + const struct sctp_transport *t = ptr; + struct sctp_association *asoc = t->asoc; + const struct net *net = x->net; - epb = &asoc->base; + if (!sctp_cmp_addr_exact(&t->ipaddr, x->paddr)) + return 1; + if (!net_eq(sock_net(asoc->base.sk), net)) + return 1; + if (x->ep) { + if (x->ep != asoc->ep) + return 1; + } else { + if (x->laddr->v4.sin_port != htons(asoc->base.bind_addr.port)) + return 1; + if (!sctp_bind_addr_match(&asoc->base.bind_addr, + x->laddr, sctp_sk(asoc->base.sk))) + return 1; + } - /* Calculate which chain this entry will belong to. */ - epb->hashent = sctp_assoc_hashfn(net, epb->bind_addr.port, - asoc->peer.port); + return 0; +} - head = &sctp_assoc_hashtable[epb->hashent]; +static inline u32 sctp_hash_obj(const void *data, u32 len, u32 seed) +{ + const struct sctp_transport *t = data; + const union sctp_addr *paddr = &t->ipaddr; + const struct net *net = sock_net(t->asoc->base.sk); + u16 lport = htons(t->asoc->base.bind_addr.port); + u32 addr; + + if (paddr->sa.sa_family == AF_INET6) + addr = jhash(&paddr->v6.sin6_addr, 16, seed); + else + addr = paddr->v4.sin_addr.s_addr; - write_lock(&head->lock); - hlist_add_head(&epb->node, &head->chain); - write_unlock(&head->lock); + return jhash_3words(addr, ((__u32)paddr->v4.sin_port) << 16 | + (__force __u32)lport, net_hash_mix(net), seed); } -/* Add an association to the hash. Local BH-safe. */ -void sctp_hash_established(struct sctp_association *asoc) +static inline u32 sctp_hash_key(const void *data, u32 len, u32 seed) { - if (asoc->temp) - return; + const struct sctp_hash_cmp_arg *x = data; + const union sctp_addr *paddr = x->paddr; + const struct net *net = x->net; + u16 lport; + u32 addr; + + lport = x->ep ? htons(x->ep->base.bind_addr.port) : + x->laddr->v4.sin_port; + if (paddr->sa.sa_family == AF_INET6) + addr = jhash(&paddr->v6.sin6_addr, 16, seed); + else + addr = paddr->v4.sin_addr.s_addr; - local_bh_disable(); - __sctp_hash_established(asoc); - local_bh_enable(); + return jhash_3words(addr, ((__u32)paddr->v4.sin_port) << 16 | + (__force __u32)lport, net_hash_mix(net), seed); } -/* Remove association from the hash table. */ -static void __sctp_unhash_established(struct sctp_association *asoc) +static const struct rhashtable_params sctp_hash_params = { + .head_offset = offsetof(struct sctp_transport, node), + .hashfn = sctp_hash_key, + .obj_hashfn = sctp_hash_obj, + .obj_cmpfn = sctp_hash_cmp, + .automatic_shrinking = true, +}; + +int sctp_transport_hashtable_init(void) { - struct net *net = sock_net(asoc->base.sk); - struct sctp_hashbucket *head; - struct sctp_ep_common *epb; + return rhashtable_init(&sctp_transport_hashtable, &sctp_hash_params); +} - epb = &asoc->base; +void sctp_transport_hashtable_destroy(void) +{ + rhashtable_destroy(&sctp_transport_hashtable); +} - epb->hashent = sctp_assoc_hashfn(net, epb->bind_addr.port, - asoc->peer.port); +void sctp_hash_transport(struct sctp_transport *t) +{ + struct sctp_hash_cmp_arg arg; - head = &sctp_assoc_hashtable[epb->hashent]; + if (t->asoc->temp) + return; - write_lock(&head->lock); - hlist_del_init(&epb->node); - write_unlock(&head->lock); + arg.ep = t->asoc->ep; + arg.paddr = &t->ipaddr; + arg.net = sock_net(t->asoc->base.sk); + +reinsert: + if (rhashtable_lookup_insert_key(&sctp_transport_hashtable, &arg, + &t->node, sctp_hash_params) == -EBUSY) + goto reinsert; } -/* Remove association from the hash table. Local BH-safe. */ -void sctp_unhash_established(struct sctp_association *asoc) +void sctp_unhash_transport(struct sctp_transport *t) { - if (asoc->temp) + if (t->asoc->temp) return; - local_bh_disable(); - __sctp_unhash_established(asoc); - local_bh_enable(); + rhashtable_remove_fast(&sctp_transport_hashtable, &t->node, + sctp_hash_params); +} + +struct sctp_transport *sctp_addrs_lookup_transport( + struct net *net, + const union sctp_addr *laddr, + const union sctp_addr *paddr) +{ + struct sctp_hash_cmp_arg arg = { + .ep = NULL, + .laddr = laddr, + .paddr = paddr, + .net = net, + }; + + return rhashtable_lookup_fast(&sctp_transport_hashtable, &arg, + sctp_hash_params); +} + +struct sctp_transport *sctp_epaddr_lookup_transport( + const struct sctp_endpoint *ep, + const union sctp_addr *paddr) +{ + struct net *net = sock_net(ep->base.sk); + struct sctp_hash_cmp_arg arg = { + .ep = ep, + .paddr = paddr, + .net = net, + }; + + return rhashtable_lookup_fast(&sctp_transport_hashtable, &arg, + sctp_hash_params); } /* Look up an association. */ @@ -850,38 +934,26 @@ static struct sctp_association *__sctp_lookup_association( const union sctp_addr *peer, struct sctp_transport **pt) { - struct sctp_hashbucket *head; - struct sctp_ep_common *epb; - struct sctp_association *asoc; - struct sctp_transport *transport; - int hash; + struct sctp_transport *t; + struct sctp_association *asoc = NULL; - /* Optimize here for direct hit, only listening connections can - * have wildcards anyways. - */ - hash = sctp_assoc_hashfn(net, ntohs(local->v4.sin_port), - ntohs(peer->v4.sin_port)); - head = &sctp_assoc_hashtable[hash]; - read_lock(&head->lock); - sctp_for_each_hentry(epb, &head->chain) { - asoc = sctp_assoc(epb); - transport = sctp_assoc_is_match(asoc, net, local, peer); - if (transport) - goto hit; - } + rcu_read_lock(); + t = sctp_addrs_lookup_transport(net, local, peer); + if (!t || !sctp_transport_hold(t)) + goto out; - read_unlock(&head->lock); + asoc = t->asoc; + sctp_association_hold(asoc); + *pt = t; - return NULL; + sctp_transport_put(t); -hit: - *pt = transport; - sctp_association_hold(asoc); - read_unlock(&head->lock); +out: + rcu_read_unlock(); return asoc; } -/* Look up an association. BH-safe. */ +/* Look up an association. protected by RCU read lock */ static struct sctp_association *sctp_lookup_association(struct net *net, const union sctp_addr *laddr, @@ -890,9 +962,7 @@ struct sctp_association *sctp_lookup_association(struct net *net, { struct sctp_association *asoc; - local_bh_disable(); asoc = __sctp_lookup_association(net, laddr, paddr, transportp); - local_bh_enable(); return asoc; } |