Branch data Line data Source code
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/secure_seq.h>
24 : : #include <net/ip.h>
25 : :
26 : 0 : static unsigned int inet6_ehashfn(struct net *net,
27 : : const struct in6_addr *laddr,
28 : : const u16 lport,
29 : : const struct in6_addr *faddr,
30 : : const __be16 fport)
31 : : {
32 : : static u32 inet6_ehash_secret __read_mostly;
33 : : static u32 ipv6_hash_secret __read_mostly;
34 : :
35 : : u32 lhash, fhash;
36 : :
37 [ # # ]: 0 : net_get_random_once(&inet6_ehash_secret, sizeof(inet6_ehash_secret));
38 [ # # ]: 0 : net_get_random_once(&ipv6_hash_secret, sizeof(ipv6_hash_secret));
39 : :
40 : 0 : lhash = (__force u32)laddr->s6_addr32[3];
41 : 0 : fhash = __ipv6_addr_jhash(faddr, ipv6_hash_secret);
42 : :
43 : 0 : return __inet6_ehashfn(lhash, lport, fhash, fport,
44 : : inet6_ehash_secret + net_hash_mix(net));
45 : : }
46 : :
47 : : static int inet6_sk_ehashfn(const struct sock *sk)
48 : : {
49 : : const struct inet_sock *inet = inet_sk(sk);
50 : : const struct in6_addr *laddr = &sk->sk_v6_rcv_saddr;
51 : 0 : const struct in6_addr *faddr = &sk->sk_v6_daddr;
52 : 0 : const __u16 lport = inet->inet_num;
53 : 0 : const __be16 fport = inet->inet_dport;
54 : : struct net *net = sock_net(sk);
55 : :
56 : 0 : return inet6_ehashfn(net, laddr, lport, faddr, fport);
57 : : }
58 : :
59 : 0 : int __inet6_hash(struct sock *sk, struct inet_timewait_sock *tw)
60 : : {
61 : 0 : struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
62 : : int twrefcnt = 0;
63 : :
64 [ # # ]: 0 : WARN_ON(!sk_unhashed(sk));
65 : :
66 [ # # ]: 0 : if (sk->sk_state == TCP_LISTEN) {
67 : : struct inet_listen_hashbucket *ilb;
68 : :
69 : 0 : ilb = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
70 : : spin_lock(&ilb->lock);
71 : : __sk_nulls_add_node_rcu(sk, &ilb->head);
72 : : spin_unlock(&ilb->lock);
73 : : } else {
74 : : unsigned int hash;
75 : : struct hlist_nulls_head *list;
76 : : spinlock_t *lock;
77 : :
78 : 0 : sk->sk_hash = hash = inet6_sk_ehashfn(sk);
79 : : list = &inet_ehash_bucket(hashinfo, hash)->chain;
80 : : lock = inet_ehash_lockp(hashinfo, hash);
81 : : spin_lock(lock);
82 : : __sk_nulls_add_node_rcu(sk, list);
83 [ # # ]: 0 : if (tw) {
84 [ # # ]: 0 : WARN_ON(sk->sk_hash != tw->tw_hash);
85 : 0 : twrefcnt = inet_twsk_unhash(tw);
86 : : }
87 : : spin_unlock(lock);
88 : : }
89 : :
90 : 0 : sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
91 : 0 : return twrefcnt;
92 : : }
93 : : EXPORT_SYMBOL(__inet6_hash);
94 : :
95 : : /*
96 : : * Sockets in TCP_CLOSE state are _always_ taken out of the hash, so
97 : : * we need not check it for TCP lookups anymore, thanks Alexey. -DaveM
98 : : *
99 : : * The sockhash lock must be held as a reader here.
100 : : */
101 : 0 : struct sock *__inet6_lookup_established(struct net *net,
102 : : struct inet_hashinfo *hashinfo,
103 : : const struct in6_addr *saddr,
104 : : const __be16 sport,
105 : : const struct in6_addr *daddr,
106 : : const u16 hnum,
107 : : const int dif)
108 : : {
109 : : struct sock *sk;
110 : : const struct hlist_nulls_node *node;
111 : 0 : const __portpair ports = INET_COMBINED_PORTS(sport, hnum);
112 : : /* Optimize here for direct hit, only listening connections can
113 : : * have wildcards anyways.
114 : : */
115 : 0 : unsigned int hash = inet6_ehashfn(net, daddr, hnum, saddr, sport);
116 : 0 : unsigned int slot = hash & hashinfo->ehash_mask;
117 : 0 : struct inet_ehash_bucket *head = &hashinfo->ehash[slot];
118 : :
119 : :
120 : : rcu_read_lock();
121 : : begin:
122 [ # # ]: 0 : sk_nulls_for_each_rcu(sk, node, &head->chain) {
123 [ # # ]: 0 : if (sk->sk_hash != hash)
124 : 0 : continue;
125 [ # # ][ # # ]: 0 : if (!INET6_MATCH(sk, net, saddr, daddr, ports, dif))
[ # # ][ # # ]
[ # # ][ # # ]
126 : 0 : continue;
127 [ # # ]: 0 : if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt)))
128 : : goto out;
129 : :
130 [ # # ][ # # ]: 0 : if (unlikely(!INET6_MATCH(sk, net, saddr, daddr, ports, dif))) {
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
131 : 0 : sock_gen_put(sk);
132 : 0 : goto begin;
133 : : }
134 : : goto found;
135 : : }
136 [ # # ]: 0 : if (get_nulls_value(node) != slot)
137 : : goto begin;
138 : : out:
139 : : sk = NULL;
140 : : found:
141 : : rcu_read_unlock();
142 : 0 : return sk;
143 : : }
144 : : EXPORT_SYMBOL(__inet6_lookup_established);
145 : :
146 : : static inline int compute_score(struct sock *sk, struct net *net,
147 : : const unsigned short hnum,
148 : : const struct in6_addr *daddr,
149 : : const int dif)
150 : : {
151 : : int score = -1;
152 : :
153 [ # # ][ # # ]: 0 : if (net_eq(sock_net(sk), net) && inet_sk(sk)->inet_num == hnum &&
[ # # ][ # # ]
154 : 0 : sk->sk_family == PF_INET6) {
155 : :
156 : : score = 1;
157 [ # # ][ # # ]: 0 : if (!ipv6_addr_any(&sk->sk_v6_rcv_saddr)) {
158 [ # # ][ # # ]: 0 : if (!ipv6_addr_equal(&sk->sk_v6_rcv_saddr, daddr))
159 : : return -1;
160 : : score++;
161 : : }
162 [ # # ][ # # ]: 0 : if (sk->sk_bound_dev_if) {
163 [ # # ][ # # ]: 0 : if (sk->sk_bound_dev_if != dif)
164 : : return -1;
165 : 0 : score++;
166 : : }
167 : : }
168 : : return score;
169 : : }
170 : :
171 : 0 : struct sock *inet6_lookup_listener(struct net *net,
172 : : struct inet_hashinfo *hashinfo, const struct in6_addr *saddr,
173 : : const __be16 sport, const struct in6_addr *daddr,
174 : : const unsigned short hnum, const int dif)
175 : : {
176 : : struct sock *sk;
177 : : const struct hlist_nulls_node *node;
178 : : struct sock *result;
179 : : int score, hiscore, matches = 0, reuseport = 0;
180 : : u32 phash = 0;
181 : 0 : unsigned int hash = inet_lhashfn(net, hnum);
182 : 0 : struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash];
183 : :
184 : : rcu_read_lock();
185 : : begin:
186 : : result = NULL;
187 : : hiscore = 0;
188 [ # # ]: 0 : sk_nulls_for_each(sk, node, &ilb->head) {
189 : : score = compute_score(sk, net, hnum, daddr, dif);
190 [ # # ]: 0 : if (score > hiscore) {
191 : : hiscore = score;
192 : : result = sk;
193 : 0 : reuseport = sk->sk_reuseport;
194 [ # # ]: 0 : if (reuseport) {
195 : 0 : phash = inet6_ehashfn(net, daddr, hnum,
196 : : saddr, sport);
197 : : matches = 1;
198 : : }
199 [ # # ]: 0 : } else if (score == hiscore && reuseport) {
200 : 0 : matches++;
201 [ # # ]: 0 : if (((u64)phash * matches) >> 32 == 0)
202 : : result = sk;
203 : : phash = next_pseudo_random32(phash);
204 : : }
205 : : }
206 : : /*
207 : : * if the nulls value we got at the end of this lookup is
208 : : * not the expected one, we must restart lookup.
209 : : * We probably met an item that was moved to another chain.
210 : : */
211 [ # # ]: 0 : if (get_nulls_value(node) != hash + LISTENING_NULLS_BASE)
212 : : goto begin;
213 [ # # ]: 0 : if (result) {
214 [ # # ]: 0 : if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt)))
215 : : result = NULL;
216 [ # # ]: 0 : else if (unlikely(compute_score(result, net, hnum, daddr,
217 : : dif) < hiscore)) {
218 : : sock_put(result);
219 : : goto begin;
220 : : }
221 : : }
222 : : rcu_read_unlock();
223 : 0 : return result;
224 : : }
225 : :
226 : : EXPORT_SYMBOL_GPL(inet6_lookup_listener);
227 : :
228 : 0 : struct sock *inet6_lookup(struct net *net, struct inet_hashinfo *hashinfo,
229 : : const struct in6_addr *saddr, const __be16 sport,
230 : : const struct in6_addr *daddr, const __be16 dport,
231 : : const int dif)
232 : : {
233 : : struct sock *sk;
234 : :
235 : : local_bh_disable();
236 [ # # ]: 0 : sk = __inet6_lookup(net, hashinfo, saddr, sport, daddr, ntohs(dport), dif);
237 : : local_bh_enable();
238 : :
239 : 0 : return sk;
240 : : }
241 : :
242 : : EXPORT_SYMBOL_GPL(inet6_lookup);
243 : :
244 : 0 : static int __inet6_check_established(struct inet_timewait_death_row *death_row,
245 : : struct sock *sk, const __u16 lport,
246 : : struct inet_timewait_sock **twp)
247 : : {
248 : 0 : struct inet_hashinfo *hinfo = death_row->hashinfo;
249 : : struct inet_sock *inet = inet_sk(sk);
250 : : const struct in6_addr *daddr = &sk->sk_v6_rcv_saddr;
251 : 0 : const struct in6_addr *saddr = &sk->sk_v6_daddr;
252 : 0 : const int dif = sk->sk_bound_dev_if;
253 : 0 : const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);
254 : : struct net *net = sock_net(sk);
255 : 0 : const unsigned int hash = inet6_ehashfn(net, daddr, lport, saddr,
256 : : inet->inet_dport);
257 : : struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
258 : : spinlock_t *lock = inet_ehash_lockp(hinfo, hash);
259 : : struct sock *sk2;
260 : : const struct hlist_nulls_node *node;
261 : : struct inet_timewait_sock *tw = NULL;
262 : : int twrefcnt = 0;
263 : :
264 : : spin_lock(lock);
265 : :
266 [ # # ]: 0 : sk_nulls_for_each(sk2, node, &head->chain) {
267 [ # # ]: 0 : if (sk2->sk_hash != hash)
268 : 0 : continue;
269 : :
270 [ # # ][ # # ]: 0 : if (likely(INET6_MATCH(sk2, net, saddr, daddr, ports, dif))) {
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
271 [ # # ]: 0 : if (sk2->sk_state == TCP_TIME_WAIT) {
272 : : tw = inet_twsk(sk2);
273 [ # # ]: 0 : if (twsk_unique(sk, sk2, twp))
274 : : break;
275 : : }
276 : : goto not_unique;
277 : : }
278 : : }
279 : :
280 : : /* Must record num and sport now. Otherwise we will see
281 : : * in hash table socket with a funny identity.
282 : : */
283 : 0 : inet->inet_num = lport;
284 [ # # ]: 0 : inet->inet_sport = htons(lport);
285 : 0 : sk->sk_hash = hash;
286 [ # # ]: 0 : WARN_ON(!sk_unhashed(sk));
287 : : __sk_nulls_add_node_rcu(sk, &head->chain);
288 [ # # ]: 0 : if (tw) {
289 : 0 : twrefcnt = inet_twsk_unhash(tw);
290 : 0 : NET_INC_STATS_BH(net, LINUX_MIB_TIMEWAITRECYCLED);
291 : : }
292 : : spin_unlock(lock);
293 [ # # ]: 0 : if (twrefcnt)
294 : 0 : inet_twsk_put(tw);
295 : 0 : sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
296 : :
297 [ # # ]: 0 : if (twp) {
298 : 0 : *twp = tw;
299 [ # # ]: 0 : } else if (tw) {
300 : : /* Silly. Should hash-dance instead... */
301 : 0 : inet_twsk_deschedule(tw, death_row);
302 : :
303 : 0 : inet_twsk_put(tw);
304 : : }
305 : : return 0;
306 : :
307 : : not_unique:
308 : : spin_unlock(lock);
309 : 0 : return -EADDRNOTAVAIL;
310 : : }
311 : :
312 : : static inline u32 inet6_sk_port_offset(const struct sock *sk)
313 : : {
314 : : const struct inet_sock *inet = inet_sk(sk);
315 : :
316 : 0 : return secure_ipv6_port_ephemeral(sk->sk_v6_rcv_saddr.s6_addr32,
317 : 0 : sk->sk_v6_daddr.s6_addr32,
318 : : inet->inet_dport);
319 : : }
320 : :
321 : 0 : int inet6_hash_connect(struct inet_timewait_death_row *death_row,
322 : : struct sock *sk)
323 : : {
324 : 0 : return __inet_hash_connect(death_row, sk, inet6_sk_port_offset(sk),
325 : : __inet6_check_established, __inet6_hash);
326 : : }
327 : :
328 : : EXPORT_SYMBOL_GPL(inet6_hash_connect);
|