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/config.h> 18 #include <linux/module.h> 19 #include <linux/random.h> 20 21 #include <net/inet_connection_sock.h> 22 #include <net/inet_hashtables.h> 23 #include <net/inet6_hashtables.h> 24 #include <net/ip.h> 25 26 struct sock *inet6_lookup_listener(struct inet_hashinfo *hashinfo, 27 const struct in6_addr *daddr, 28 const unsigned short hnum, const int dif) 29 { 30 struct sock *sk; 31 const struct hlist_node *node; 32 struct sock *result = NULL; 33 int score, hiscore = 0; 34 35 read_lock(&hashinfo->lhash_lock); 36 sk_for_each(sk, node, &hashinfo->listening_hash[inet_lhashfn(hnum)]) { 37 if (inet_sk(sk)->num == hnum && sk->sk_family == PF_INET6) { 38 const struct ipv6_pinfo *np = inet6_sk(sk); 39 40 score = 1; 41 if (!ipv6_addr_any(&np->rcv_saddr)) { 42 if (!ipv6_addr_equal(&np->rcv_saddr, daddr)) 43 continue; 44 score++; 45 } 46 if (sk->sk_bound_dev_if) { 47 if (sk->sk_bound_dev_if != dif) 48 continue; 49 score++; 50 } 51 if (score == 3) { 52 result = sk; 53 break; 54 } 55 if (score > hiscore) { 56 hiscore = score; 57 result = sk; 58 } 59 } 60 } 61 if (result) 62 sock_hold(result); 63 read_unlock(&hashinfo->lhash_lock); 64 return result; 65 } 66 67 EXPORT_SYMBOL_GPL(inet6_lookup_listener); 68 69 struct sock *inet6_lookup(struct inet_hashinfo *hashinfo, 70 const struct in6_addr *saddr, const u16 sport, 71 const struct in6_addr *daddr, const u16 dport, 72 const int dif) 73 { 74 struct sock *sk; 75 76 local_bh_disable(); 77 sk = __inet6_lookup(hashinfo, saddr, sport, daddr, ntohs(dport), dif); 78 local_bh_enable(); 79 80 return sk; 81 } 82 83 EXPORT_SYMBOL_GPL(inet6_lookup); 84 85 static int __inet6_check_established(struct inet_timewait_death_row *death_row, 86 struct sock *sk, const __u16 lport, 87 struct inet_timewait_sock **twp) 88 { 89 struct inet_hashinfo *hinfo = death_row->hashinfo; 90 const struct inet_sock *inet = inet_sk(sk); 91 const struct ipv6_pinfo *np = inet6_sk(sk); 92 const struct in6_addr *daddr = &np->rcv_saddr; 93 const struct in6_addr *saddr = &np->daddr; 94 const int dif = sk->sk_bound_dev_if; 95 const u32 ports = INET_COMBINED_PORTS(inet->dport, lport); 96 const unsigned int hash = inet6_ehashfn(daddr, inet->num, saddr, 97 inet->dport); 98 struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash); 99 struct sock *sk2; 100 const struct hlist_node *node; 101 struct inet_timewait_sock *tw; 102 103 prefetch(head->chain.first); 104 write_lock(&head->lock); 105 106 /* Check TIME-WAIT sockets first. */ 107 sk_for_each(sk2, node, &(head + hinfo->ehash_size)->chain) { 108 const struct inet6_timewait_sock *tw6 = inet6_twsk(sk2); 109 110 tw = inet_twsk(sk2); 111 112 if(*((__u32 *)&(tw->tw_dport)) == ports && 113 sk2->sk_family == PF_INET6 && 114 ipv6_addr_equal(&tw6->tw_v6_daddr, saddr) && 115 ipv6_addr_equal(&tw6->tw_v6_rcv_saddr, daddr) && 116 sk2->sk_bound_dev_if == sk->sk_bound_dev_if) { 117 if (twsk_unique(sk, sk2, twp)) 118 goto unique; 119 else 120 goto not_unique; 121 } 122 } 123 tw = NULL; 124 125 /* And established part... */ 126 sk_for_each(sk2, node, &head->chain) { 127 if (INET6_MATCH(sk2, hash, saddr, daddr, ports, dif)) 128 goto not_unique; 129 } 130 131 unique: 132 BUG_TRAP(sk_unhashed(sk)); 133 __sk_add_node(sk, &head->chain); 134 sk->sk_hash = hash; 135 sock_prot_inc_use(sk->sk_prot); 136 write_unlock(&head->lock); 137 138 if (twp != NULL) { 139 *twp = tw; 140 NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); 141 } else if (tw != NULL) { 142 /* Silly. Should hash-dance instead... */ 143 inet_twsk_deschedule(tw, death_row); 144 NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); 145 146 inet_twsk_put(tw); 147 } 148 return 0; 149 150 not_unique: 151 write_unlock(&head->lock); 152 return -EADDRNOTAVAIL; 153 } 154 155 static inline u32 inet6_sk_port_offset(const struct sock *sk) 156 { 157 const struct inet_sock *inet = inet_sk(sk); 158 const struct ipv6_pinfo *np = inet6_sk(sk); 159 return secure_ipv6_port_ephemeral(np->rcv_saddr.s6_addr32, 160 np->daddr.s6_addr32, 161 inet->dport); 162 } 163 164 int inet6_hash_connect(struct inet_timewait_death_row *death_row, 165 struct sock *sk) 166 { 167 struct inet_hashinfo *hinfo = death_row->hashinfo; 168 const unsigned short snum = inet_sk(sk)->num; 169 struct inet_bind_hashbucket *head; 170 struct inet_bind_bucket *tb; 171 int ret; 172 173 if (snum == 0) { 174 const int low = sysctl_local_port_range[0]; 175 const int high = sysctl_local_port_range[1]; 176 const int range = high - low; 177 int i, port; 178 static u32 hint; 179 const u32 offset = hint + inet6_sk_port_offset(sk); 180 struct hlist_node *node; 181 struct inet_timewait_sock *tw = NULL; 182 183 local_bh_disable(); 184 for (i = 1; i <= range; i++) { 185 port = low + (i + offset) % range; 186 head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)]; 187 spin_lock(&head->lock); 188 189 /* Does not bother with rcv_saddr checks, 190 * because the established check is already 191 * unique enough. 192 */ 193 inet_bind_bucket_for_each(tb, node, &head->chain) { 194 if (tb->port == port) { 195 BUG_TRAP(!hlist_empty(&tb->owners)); 196 if (tb->fastreuse >= 0) 197 goto next_port; 198 if (!__inet6_check_established(death_row, 199 sk, port, 200 &tw)) 201 goto ok; 202 goto next_port; 203 } 204 } 205 206 tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, 207 head, port); 208 if (!tb) { 209 spin_unlock(&head->lock); 210 break; 211 } 212 tb->fastreuse = -1; 213 goto ok; 214 215 next_port: 216 spin_unlock(&head->lock); 217 } 218 local_bh_enable(); 219 220 return -EADDRNOTAVAIL; 221 222 ok: 223 hint += i; 224 225 /* Head lock still held and bh's disabled */ 226 inet_bind_hash(sk, tb, port); 227 if (sk_unhashed(sk)) { 228 inet_sk(sk)->sport = htons(port); 229 __inet6_hash(hinfo, sk); 230 } 231 spin_unlock(&head->lock); 232 233 if (tw) { 234 inet_twsk_deschedule(tw, death_row); 235 inet_twsk_put(tw); 236 } 237 238 ret = 0; 239 goto out; 240 } 241 242 head = &hinfo->bhash[inet_bhashfn(snum, hinfo->bhash_size)]; 243 tb = inet_csk(sk)->icsk_bind_hash; 244 spin_lock_bh(&head->lock); 245 246 if (sk_head(&tb->owners) == sk && sk->sk_bind_node.next == NULL) { 247 __inet6_hash(hinfo, sk); 248 spin_unlock_bh(&head->lock); 249 return 0; 250 } else { 251 spin_unlock(&head->lock); 252 /* No definite answer... Walk to established hash table */ 253 ret = __inet6_check_established(death_row, sk, snum, NULL); 254 out: 255 local_bh_enable(); 256 return ret; 257 } 258 } 259 260 EXPORT_SYMBOL_GPL(inet6_hash_connect); 261