diff options
Diffstat (limited to 'net/ipv6/inet6_hashtables.c')
| -rw-r--r-- | net/ipv6/inet6_hashtables.c | 143 | 
1 files changed, 84 insertions, 59 deletions
diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c index 633a6c26613..262e13c02ec 100644 --- a/net/ipv6/inet6_hashtables.c +++ b/net/ipv6/inet6_hashtables.c @@ -20,8 +20,42 @@  #include <net/inet_connection_sock.h>  #include <net/inet_hashtables.h>  #include <net/inet6_hashtables.h> +#include <net/secure_seq.h>  #include <net/ip.h> +static unsigned int inet6_ehashfn(struct net *net, +				  const struct in6_addr *laddr, +				  const u16 lport, +				  const struct in6_addr *faddr, +				  const __be16 fport) +{ +	static u32 inet6_ehash_secret __read_mostly; +	static u32 ipv6_hash_secret __read_mostly; + +	u32 lhash, fhash; + +	net_get_random_once(&inet6_ehash_secret, sizeof(inet6_ehash_secret)); +	net_get_random_once(&ipv6_hash_secret, sizeof(ipv6_hash_secret)); + +	lhash = (__force u32)laddr->s6_addr32[3]; +	fhash = __ipv6_addr_jhash(faddr, ipv6_hash_secret); + +	return __inet6_ehashfn(lhash, lport, fhash, fport, +			       inet6_ehash_secret + net_hash_mix(net)); +} + +static int inet6_sk_ehashfn(const struct sock *sk) +{ +	const struct inet_sock *inet = inet_sk(sk); +	const struct in6_addr *laddr = &sk->sk_v6_rcv_saddr; +	const struct in6_addr *faddr = &sk->sk_v6_daddr; +	const __u16 lport = inet->inet_num; +	const __be16 fport = inet->inet_dport; +	struct net *net = sock_net(sk); + +	return inet6_ehashfn(net, laddr, lport, faddr, fport); +} +  int __inet6_hash(struct sock *sk, struct inet_timewait_sock *tw)  {  	struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; @@ -86,45 +120,30 @@ struct sock *__inet6_lookup_established(struct net *net,  	rcu_read_lock();  begin:  	sk_nulls_for_each_rcu(sk, node, &head->chain) { -		/* For IPV6 do the cheaper port and family tests first. */ -		if (INET6_MATCH(sk, net, hash, saddr, daddr, ports, dif)) { -			if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt))) -				goto begintw; -			if (!INET6_MATCH(sk, net, hash, saddr, daddr, ports, dif)) { -				sock_put(sk); -				goto begin; -			} -		goto out; -		} -	} -	if (get_nulls_value(node) != slot) -		goto begin; - -begintw: -	/* Must check for a TIME_WAIT'er before going to listener hash. */ -	sk_nulls_for_each_rcu(sk, node, &head->twchain) { -		if (INET6_TW_MATCH(sk, net, hash, saddr, daddr, ports, dif)) { -			if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt))) { -				sk = NULL; -				goto out; -			} -			if (!INET6_TW_MATCH(sk, net, hash, saddr, daddr, ports, dif)) { -				sock_put(sk); -				goto begintw; -			} +		if (sk->sk_hash != hash) +			continue; +		if (!INET6_MATCH(sk, net, saddr, daddr, ports, dif)) +			continue; +		if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt)))  			goto out; + +		if (unlikely(!INET6_MATCH(sk, net, saddr, daddr, ports, dif))) { +			sock_gen_put(sk); +			goto begin;  		} +		goto found;  	}  	if (get_nulls_value(node) != slot) -		goto begintw; -	sk = NULL; +		goto begin;  out: +	sk = NULL; +found:  	rcu_read_unlock();  	return sk;  }  EXPORT_SYMBOL(__inet6_lookup_established); -static int inline compute_score(struct sock *sk, struct net *net, +static inline int compute_score(struct sock *sk, struct net *net,  				const unsigned short hnum,  				const struct in6_addr *daddr,  				const int dif) @@ -133,11 +152,10 @@ static int inline compute_score(struct sock *sk, struct net *net,  	if (net_eq(sock_net(sk), net) && inet_sk(sk)->inet_num == hnum &&  	    sk->sk_family == PF_INET6) { -		const struct ipv6_pinfo *np = inet6_sk(sk);  		score = 1; -		if (!ipv6_addr_any(&np->rcv_saddr)) { -			if (!ipv6_addr_equal(&np->rcv_saddr, daddr)) +		if (!ipv6_addr_any(&sk->sk_v6_rcv_saddr)) { +			if (!ipv6_addr_equal(&sk->sk_v6_rcv_saddr, daddr))  				return -1;  			score++;  		} @@ -151,25 +169,38 @@ static int inline compute_score(struct sock *sk, struct net *net,  }  struct sock *inet6_lookup_listener(struct net *net, -		struct inet_hashinfo *hashinfo, const struct in6_addr *daddr, +		struct inet_hashinfo *hashinfo, const struct in6_addr *saddr, +		const __be16 sport, const struct in6_addr *daddr,  		const unsigned short hnum, const int dif)  {  	struct sock *sk;  	const struct hlist_nulls_node *node;  	struct sock *result; -	int score, hiscore; +	int score, hiscore, matches = 0, reuseport = 0; +	u32 phash = 0;  	unsigned int hash = inet_lhashfn(net, hnum);  	struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash];  	rcu_read_lock();  begin:  	result = NULL; -	hiscore = -1; +	hiscore = 0;  	sk_nulls_for_each(sk, node, &ilb->head) {  		score = compute_score(sk, net, hnum, daddr, dif);  		if (score > hiscore) {  			hiscore = score;  			result = sk; +			reuseport = sk->sk_reuseport; +			if (reuseport) { +				phash = inet6_ehashfn(net, daddr, hnum, +						      saddr, sport); +				matches = 1; +			} +		} else if (score == hiscore && reuseport) { +			matches++; +			if (((u64)phash * matches) >> 32 == 0) +				result = sk; +			phash = next_pseudo_random32(phash);  		}  	}  	/* @@ -216,9 +247,8 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row,  {  	struct inet_hashinfo *hinfo = death_row->hashinfo;  	struct inet_sock *inet = inet_sk(sk); -	const struct ipv6_pinfo *np = inet6_sk(sk); -	const struct in6_addr *daddr = &np->rcv_saddr; -	const struct in6_addr *saddr = &np->daddr; +	const struct in6_addr *daddr = &sk->sk_v6_rcv_saddr; +	const struct in6_addr *saddr = &sk->sk_v6_daddr;  	const int dif = sk->sk_bound_dev_if;  	const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);  	struct net *net = sock_net(sk); @@ -228,33 +258,28 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row,  	spinlock_t *lock = inet_ehash_lockp(hinfo, hash);  	struct sock *sk2;  	const struct hlist_nulls_node *node; -	struct inet_timewait_sock *tw; +	struct inet_timewait_sock *tw = NULL;  	int twrefcnt = 0;  	spin_lock(lock); -	/* Check TIME-WAIT sockets first. */ -	sk_nulls_for_each(sk2, node, &head->twchain) { -		tw = inet_twsk(sk2); - -		if (INET6_TW_MATCH(sk2, net, hash, saddr, daddr, ports, dif)) { -			if (twsk_unique(sk, sk2, twp)) -				goto unique; -			else -				goto not_unique; -		} -	} -	tw = NULL; - -	/* And established part... */  	sk_nulls_for_each(sk2, node, &head->chain) { -		if (INET6_MATCH(sk2, net, hash, saddr, daddr, ports, dif)) +		if (sk2->sk_hash != hash) +			continue; + +		if (likely(INET6_MATCH(sk2, net, saddr, daddr, ports, dif))) { +			if (sk2->sk_state == TCP_TIME_WAIT) { +				tw = inet_twsk(sk2); +				if (twsk_unique(sk, sk2, twp)) +					break; +			}  			goto not_unique; +		}  	} -unique:  	/* Must record num and sport now. Otherwise we will see -	 * in hash table socket with a funny identity. */ +	 * in hash table socket with a funny identity. +	 */  	inet->inet_num = lport;  	inet->inet_sport = htons(lport);  	sk->sk_hash = hash; @@ -287,9 +312,9 @@ not_unique:  static inline u32 inet6_sk_port_offset(const struct sock *sk)  {  	const struct inet_sock *inet = inet_sk(sk); -	const struct ipv6_pinfo *np = inet6_sk(sk); -	return secure_ipv6_port_ephemeral(np->rcv_saddr.s6_addr32, -					  np->daddr.s6_addr32, + +	return secure_ipv6_port_ephemeral(sk->sk_v6_rcv_saddr.s6_addr32, +					  sk->sk_v6_daddr.s6_addr32,  					  inet->inet_dport);  }  | 
