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 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 /* Must record num and sport now. Otherwise we will see 133 * in hash table socket with a funny identity. */ 134 inet->num = lport; 135 inet->sport = htons(lport); 136 BUG_TRAP(sk_unhashed(sk)); 137 __sk_add_node(sk, &head->chain); 138 sk->sk_hash = hash; 139 sock_prot_inc_use(sk->sk_prot); 140 write_unlock(&head->lock); 141 142 if (twp != NULL) { 143 *twp = tw; 144 NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); 145 } else if (tw != NULL) { 146 /* Silly. Should hash-dance instead... */ 147 inet_twsk_deschedule(tw, death_row); 148 NET_INC_STATS_BH(LINUX_MIB_TIMEWAITRECYCLED); 149 150 inet_twsk_put(tw); 151 } 152 return 0; 153 154 not_unique: 155 write_unlock(&head->lock); 156 return -EADDRNOTAVAIL; 157 } 158 159 static inline u32 inet6_sk_port_offset(const struct sock *sk) 160 { 161 const struct inet_sock *inet = inet_sk(sk); 162 const struct ipv6_pinfo *np = inet6_sk(sk); 163 return secure_ipv6_port_ephemeral(np->rcv_saddr.s6_addr32, 164 np->daddr.s6_addr32, 165 inet->dport); 166 } 167 168 int inet6_hash_connect(struct inet_timewait_death_row *death_row, 169 struct sock *sk) 170 { 171 struct inet_hashinfo *hinfo = death_row->hashinfo; 172 const unsigned short snum = inet_sk(sk)->num; 173 struct inet_bind_hashbucket *head; 174 struct inet_bind_bucket *tb; 175 int ret; 176 177 if (snum == 0) { 178 const int low = sysctl_local_port_range[0]; 179 const int high = sysctl_local_port_range[1]; 180 const int range = high - low; 181 int i, port; 182 static u32 hint; 183 const u32 offset = hint + inet6_sk_port_offset(sk); 184 struct hlist_node *node; 185 struct inet_timewait_sock *tw = NULL; 186 187 local_bh_disable(); 188 for (i = 1; i <= range; i++) { 189 port = low + (i + offset) % range; 190 head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)]; 191 spin_lock(&head->lock); 192 193 /* Does not bother with rcv_saddr checks, 194 * because the established check is already 195 * unique enough. 196 */ 197 inet_bind_bucket_for_each(tb, node, &head->chain) { 198 if (tb->port == port) { 199 BUG_TRAP(!hlist_empty(&tb->owners)); 200 if (tb->fastreuse >= 0) 201 goto next_port; 202 if (!__inet6_check_established(death_row, 203 sk, port, 204 &tw)) 205 goto ok; 206 goto next_port; 207 } 208 } 209 210 tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, 211 head, port); 212 if (!tb) { 213 spin_unlock(&head->lock); 214 break; 215 } 216 tb->fastreuse = -1; 217 goto ok; 218 219 next_port: 220 spin_unlock(&head->lock); 221 } 222 local_bh_enable(); 223 224 return -EADDRNOTAVAIL; 225 226 ok: 227 hint += i; 228 229 /* Head lock still held and bh's disabled */ 230 inet_bind_hash(sk, tb, port); 231 if (sk_unhashed(sk)) { 232 inet_sk(sk)->sport = htons(port); 233 __inet6_hash(hinfo, sk); 234 } 235 spin_unlock(&head->lock); 236 237 if (tw) { 238 inet_twsk_deschedule(tw, death_row); 239 inet_twsk_put(tw); 240 } 241 242 ret = 0; 243 goto out; 244 } 245 246 head = &hinfo->bhash[inet_bhashfn(snum, hinfo->bhash_size)]; 247 tb = inet_csk(sk)->icsk_bind_hash; 248 spin_lock_bh(&head->lock); 249 250 if (sk_head(&tb->owners) == sk && sk->sk_bind_node.next == NULL) { 251 __inet6_hash(hinfo, sk); 252 spin_unlock_bh(&head->lock); 253 return 0; 254 } else { 255 spin_unlock(&head->lock); 256 /* No definite answer... Walk to established hash table */ 257 ret = __inet6_check_established(death_row, sk, snum, NULL); 258 out: 259 local_bh_enable(); 260 return ret; 261 } 262 } 263 264 EXPORT_SYMBOL_GPL(inet6_hash_connect); 265