summaryrefslogtreecommitdiff
path: root/net/sctp/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/sctp/input.c')
-rw-r--r--net/sctp/input.c200
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;
}