linux/net/ipv6/inet6_hashtables.c
<<
>>
Prefs
   1/*
   2 * INET         An implementation of the TCP/IP protocol suite for the LINUX
   3 *              operating system.  INET is implemented using the BSD Socket
   4 *              interface as the means of communication with the user level.
   5 *
   6 *              Generic INET6 transport hashtables
   7 *
   8 * Authors:     Lotsa people, from code originally in tcp, generalised here
   9 *              by Arnaldo Carvalho de Melo <acme@mandriva.com>
  10 *
  11 *      This program is free software; you can redistribute it and/or
  12 *      modify it under the terms of the GNU General Public License
  13 *      as published by the Free Software Foundation; either version
  14 *      2 of the License, or (at your option) any later version.
  15 */
  16
  17#include <linux/module.h>
  18#include <linux/random.h>
  19
  20#include <net/inet_connection_sock.h>
  21#include <net/inet_hashtables.h>
  22#include <net/inet6_hashtables.h>
  23#include <net/ip.h>
  24
  25void __inet6_hash(struct sock *sk)
  26{
  27        struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
  28
  29        WARN_ON(!sk_unhashed(sk));
  30
  31        if (sk->sk_state == TCP_LISTEN) {
  32                struct inet_listen_hashbucket *ilb;
  33
  34                ilb = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
  35                spin_lock(&ilb->lock);
  36                __sk_nulls_add_node_rcu(sk, &ilb->head);
  37                spin_unlock(&ilb->lock);
  38        } else {
  39                unsigned int hash;
  40                struct hlist_nulls_head *list;
  41                spinlock_t *lock;
  42
  43                sk->sk_hash = hash = inet6_sk_ehashfn(sk);
  44                list = &inet_ehash_bucket(hashinfo, hash)->chain;
  45                lock = inet_ehash_lockp(hashinfo, hash);
  46                spin_lock(lock);
  47                __sk_nulls_add_node_rcu(sk, list);
  48                spin_unlock(lock);
  49        }
  50
  51        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
  52}
  53EXPORT_SYMBOL(__inet6_hash);
  54
  55/*
  56 * Sockets in TCP_CLOSE state are _always_ taken out of the hash, so
  57 * we need not check it for TCP lookups anymore, thanks Alexey. -DaveM
  58 *
  59 * The sockhash lock must be held as a reader here.
  60 */
  61struct sock *__inet6_lookup_established(struct net *net,
  62                                        struct inet_hashinfo *hashinfo,
  63                                           const struct in6_addr *saddr,
  64                                           const __be16 sport,
  65                                           const struct in6_addr *daddr,
  66                                           const u16 hnum,
  67                                           const int dif)
  68{
  69        struct sock *sk;
  70        const struct hlist_nulls_node *node;
  71        const __portpair ports = INET_COMBINED_PORTS(sport, hnum);
  72        /* Optimize here for direct hit, only listening connections can
  73         * have wildcards anyways.
  74         */
  75        unsigned int hash = inet6_ehashfn(net, daddr, hnum, saddr, sport);
  76        unsigned int slot = hash & (hashinfo->ehash_size - 1);
  77        struct inet_ehash_bucket *head = &hashinfo->ehash[slot];
  78
  79
  80        rcu_read_lock();
  81begin:
  82        sk_nulls_for_each_rcu(sk, node, &head->chain) {
  83                /* For IPV6 do the cheaper port and family tests first. */
  84                if (INET6_MATCH(sk, net, hash, saddr, daddr, ports, dif)) {
  85                        if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt)))
  86                                goto begintw;
  87                        if (!INET6_MATCH(sk, net, hash, saddr, daddr, ports, dif)) {
  88                                sock_put(sk);
  89                                goto begin;
  90                        }
  91                goto out;
  92                }
  93        }
  94        if (get_nulls_value(node) != slot)
  95                goto begin;
  96
  97begintw:
  98        /* Must check for a TIME_WAIT'er before going to listener hash. */
  99        sk_nulls_for_each_rcu(sk, node, &head->twchain) {
 100                if (INET6_TW_MATCH(sk, net, hash, saddr, daddr, ports, dif)) {
 101                        if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt))) {
 102                                sk = NULL;
 103                                goto out;
 104                        }
 105                        if (!INET6_TW_MATCH(sk, net, hash, saddr, daddr, ports, dif)) {
 106                                sock_put(sk);
 107                                goto begintw;
 108                        }
 109                        goto out;
 110                }
 111        }
 112        if (get_nulls_value(node) != slot)
 113                goto begintw;
 114        sk = NULL;
 115out:
 116        rcu_read_unlock();
 117        return sk;
 118}
 119EXPORT_SYMBOL(__inet6_lookup_established);
 120
 121static int inline compute_score(struct sock *sk, struct net *net,
 122                                const unsigned short hnum,
 123                                const struct in6_addr *daddr,
 124                                const int dif)
 125{
 126        int score = -1;
 127
 128        if (net_eq(sock_net(sk), net) && inet_sk(sk)->num == hnum &&
 129            sk->sk_family == PF_INET6) {
 130                const struct ipv6_pinfo *np = inet6_sk(sk);
 131
 132                score = 1;
 133                if (!ipv6_addr_any(&np->rcv_saddr)) {
 134                        if (!ipv6_addr_equal(&np->rcv_saddr, daddr))
 135                                return -1;
 136                        score++;
 137                }
 138                if (sk->sk_bound_dev_if) {
 139                        if (sk->sk_bound_dev_if != dif)
 140                                return -1;
 141                        score++;
 142                }
 143        }
 144        return score;
 145}
 146
 147struct sock *inet6_lookup_listener(struct net *net,
 148                struct inet_hashinfo *hashinfo, const struct in6_addr *daddr,
 149                const unsigned short hnum, const int dif)
 150{
 151        struct sock *sk;
 152        const struct hlist_nulls_node *node;
 153        struct sock *result;
 154        int score, hiscore;
 155        unsigned int hash = inet_lhashfn(net, hnum);
 156        struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash];
 157
 158        rcu_read_lock();
 159begin:
 160        result = NULL;
 161        hiscore = -1;
 162        sk_nulls_for_each(sk, node, &ilb->head) {
 163                score = compute_score(sk, net, hnum, daddr, dif);
 164                if (score > hiscore) {
 165                        hiscore = score;
 166                        result = sk;
 167                }
 168        }
 169        /*
 170         * if the nulls value we got at the end of this lookup is
 171         * not the expected one, we must restart lookup.
 172         * We probably met an item that was moved to another chain.
 173         */
 174        if (get_nulls_value(node) != hash + LISTENING_NULLS_BASE)
 175                goto begin;
 176        if (result) {
 177                if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt)))
 178                        result = NULL;
 179                else if (unlikely(compute_score(result, net, hnum, daddr,
 180                                  dif) < hiscore)) {
 181                        sock_put(result);
 182                        goto begin;
 183                }
 184        }
 185        rcu_read_unlock();
 186        return result;
 187}
 188
 189EXPORT_SYMBOL_GPL(inet6_lookup_listener);
 190
 191struct sock *inet6_lookup(struct net *net, struct inet_hashinfo *hashinfo,
 192                          const struct in6_addr *saddr, const __be16 sport,
 193                          const struct in6_addr *daddr, const __be16 dport,
 194                          const int dif)
 195{
 196        struct sock *sk;
 197
 198        local_bh_disable();
 199        sk = __inet6_lookup(net, hashinfo, saddr, sport, daddr, ntohs(dport), dif);
 200        local_bh_enable();
 201
 202        return sk;
 203}
 204
 205EXPORT_SYMBOL_GPL(inet6_lookup);
 206
 207static int __inet6_check_established(struct inet_timewait_death_row *death_row,
 208                                     struct sock *sk, const __u16 lport,
 209                                     struct inet_timewait_sock **twp)
 210{
 211        struct inet_hashinfo *hinfo = death_row->hashinfo;
 212        struct inet_sock *inet = inet_sk(sk);
 213        const struct ipv6_pinfo *np = inet6_sk(sk);
 214        const struct in6_addr *daddr = &np->rcv_saddr;
 215        const struct in6_addr *saddr = &np->daddr;
 216        const int dif = sk->sk_bound_dev_if;
 217        const __portpair ports = INET_COMBINED_PORTS(inet->dport, lport);
 218        struct net *net = sock_net(sk);
 219        const unsigned int hash = inet6_ehashfn(net, daddr, lport, saddr,
 220                                                inet->dport);
 221        struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
 222        spinlock_t *lock = inet_ehash_lockp(hinfo, hash);
 223        struct sock *sk2;
 224        const struct hlist_nulls_node *node;
 225        struct inet_timewait_sock *tw;
 226
 227        spin_lock(lock);
 228
 229        /* Check TIME-WAIT sockets first. */
 230        sk_nulls_for_each(sk2, node, &head->twchain) {
 231                tw = inet_twsk(sk2);
 232
 233                if (INET6_TW_MATCH(sk2, net, hash, saddr, daddr, ports, dif)) {
 234                        if (twsk_unique(sk, sk2, twp))
 235                                goto unique;
 236                        else
 237                                goto not_unique;
 238                }
 239        }
 240        tw = NULL;
 241
 242        /* And established part... */
 243        sk_nulls_for_each(sk2, node, &head->chain) {
 244                if (INET6_MATCH(sk2, net, hash, saddr, daddr, ports, dif))
 245                        goto not_unique;
 246        }
 247
 248unique:
 249        /* Must record num and sport now. Otherwise we will see
 250         * in hash table socket with a funny identity. */
 251        inet->num = lport;
 252        inet->sport = htons(lport);
 253        WARN_ON(!sk_unhashed(sk));
 254        __sk_nulls_add_node_rcu(sk, &head->chain);
 255        sk->sk_hash = hash;
 256        spin_unlock(lock);
 257        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
 258
 259        if (twp != NULL) {
 260                *twp = tw;
 261                NET_INC_STATS_BH(net, LINUX_MIB_TIMEWAITRECYCLED);
 262        } else if (tw != NULL) {
 263                /* Silly. Should hash-dance instead... */
 264                inet_twsk_deschedule(tw, death_row);
 265                NET_INC_STATS_BH(net, LINUX_MIB_TIMEWAITRECYCLED);
 266
 267                inet_twsk_put(tw);
 268        }
 269        return 0;
 270
 271not_unique:
 272        spin_unlock(lock);
 273        return -EADDRNOTAVAIL;
 274}
 275
 276static inline u32 inet6_sk_port_offset(const struct sock *sk)
 277{
 278        const struct inet_sock *inet = inet_sk(sk);
 279        const struct ipv6_pinfo *np = inet6_sk(sk);
 280        return secure_ipv6_port_ephemeral(np->rcv_saddr.s6_addr32,
 281                                          np->daddr.s6_addr32,
 282                                          inet->dport);
 283}
 284
 285int inet6_hash_connect(struct inet_timewait_death_row *death_row,
 286                       struct sock *sk)
 287{
 288        return __inet_hash_connect(death_row, sk, inet6_sk_port_offset(sk),
 289                        __inet6_check_established, __inet6_hash);
 290}
 291
 292EXPORT_SYMBOL_GPL(inet6_hash_connect);
 293