summaryrefslogtreecommitdiff
path: root/include/net/inet6_hashtables.h
blob: 25f708ff020e9b158e26fe95c56b2706774c8bdf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
 * INET		An implementation of the TCP/IP protocol suite for the LINUX
 *		operating system.  INET is implemented using the BSD Socket
 *		interface as the means of communication with the user level.
 *
 * Authors:	Lotsa people, from code originally in tcp
 *
 *	This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 */

#ifndef _INET6_HASHTABLES_H
#define _INET6_HASHTABLES_H

#include <linux/config.h>

#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
#include <linux/in6.h>
#include <linux/ipv6.h>
#include <linux/types.h>

#include <net/ipv6.h>

struct inet_hashinfo;

/* I have no idea if this is a good hash for v6 or not. -DaveM */
static inline unsigned int inet6_ehashfn(const struct in6_addr *laddr, const u16 lport,
				const struct in6_addr *faddr, const u16 fport)
{
	unsigned int hashent = (lport ^ fport);

	hashent ^= (laddr->s6_addr32[3] ^ faddr->s6_addr32[3]);
	hashent ^= hashent >> 16;
	hashent ^= hashent >> 8;
	return hashent;
}

static inline int inet6_sk_ehashfn(const struct sock *sk)
{
	const struct inet_sock *inet = inet_sk(sk);
	const struct ipv6_pinfo *np = inet6_sk(sk);
	const struct in6_addr *laddr = &np->rcv_saddr;
	const struct in6_addr *faddr = &np->daddr;
	const __u16 lport = inet->num;
	const __u16 fport = inet->dport;
	return inet6_ehashfn(laddr, lport, faddr, fport);
}

static inline void __inet6_hash(struct inet_hashinfo *hashinfo,
				struct sock *sk)
{
	struct hlist_head *list;
	rwlock_t *lock;

	BUG_TRAP(sk_unhashed(sk));

	if (sk->sk_state == TCP_LISTEN) {
		list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
		lock = &hashinfo->lhash_lock;
		inet_listen_wlock(hashinfo);
	} else {
		unsigned int hash;
		sk->sk_hash = hash = inet6_sk_ehashfn(sk);
		hash &= (hashinfo->ehash_size - 1);
		list = &hashinfo->ehash[hash].chain;
		lock = &hashinfo->ehash[hash].lock;
		write_lock(lock);
	}

	__sk_add_node(sk, list);
	sock_prot_inc_use(sk->sk_prot);
	write_unlock(lock);
}

/*
 * Sockets in TCP_CLOSE state are _always_ taken out of the hash, so
 * we need not check it for TCP lookups anymore, thanks Alexey. -DaveM
 *
 * The sockhash lock must be held as a reader here.
 */
static inline struct sock *
		__inet6_lookup_established(struct inet_hashinfo *hashinfo,
					   const struct in6_addr *saddr,
					   const u16 sport,
					   const struct in6_addr *daddr,
					   const u16 hnum,
					   const int dif)
{
	struct sock *sk;
	const struct hlist_node *node;
	const __u32 ports = INET_COMBINED_PORTS(sport, hnum);
	/* Optimize here for direct hit, only listening connections can
	 * have wildcards anyways.
	 */
	unsigned int hash = inet6_ehashfn(daddr, hnum, saddr, sport);
	struct inet_ehash_bucket *head = inet_ehash_bucket(hashinfo, hash);

	prefetch(head->chain.first);
	read_lock(&head->lock);
	sk_for_each(sk, node, &head->chain) {
		/* For IPV6 do the cheaper port and family tests first. */
		if (INET6_MATCH(sk, hash, saddr, daddr, ports, dif))
			goto hit; /* You sunk my battleship! */
	}
	/* Must check for a TIME_WAIT'er before going to listener hash. */
	sk_for_each(sk, node, &(head + hashinfo->ehash_size)->chain) {
		const struct inet_timewait_sock *tw = inet_twsk(sk);

		if(*((__u32 *)&(tw->tw_dport))	== ports	&&
		   sk->sk_family		== PF_INET6) {
			const struct inet6_timewait_sock *tw6 = inet6_twsk(sk);

			if (ipv6_addr_equal(&tw6->tw_v6_daddr, saddr)	&&
			    ipv6_addr_equal(&tw6->tw_v6_rcv_saddr, daddr)	&&
			    (!sk->sk_bound_dev_if || sk->sk_bound_dev_if == dif))
				goto hit;
		}
	}
	read_unlock(&head->lock);
	return NULL;

hit:
	sock_hold(sk);
	read_unlock(&head->lock);
	return sk;
}

extern struct sock *inet6_lookup_listener(struct inet_hashinfo *hashinfo,
					  const struct in6_addr *daddr,
					  const unsigned short hnum,
					  const int dif);

static inline struct sock *__inet6_lookup(struct inet_hashinfo *hashinfo,
					  const struct in6_addr *saddr,
					  const u16 sport,
					  const struct in6_addr *daddr,
					  const u16 hnum,
					  const int dif)
{
	struct sock *sk = __inet6_lookup_established(hashinfo, saddr, sport,
						     daddr, hnum, dif);
	if (sk)
		return sk;

	return inet6_lookup_listener(hashinfo, daddr, hnum, dif);
}

extern struct sock *inet6_lookup(struct inet_hashinfo *hashinfo,
				 const struct in6_addr *saddr, const u16 sport,
				 const struct in6_addr *daddr, const u16 dport,
				 const int dif);
#endif /* defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) */
#endif /* _INET6_HASHTABLES_H */