2005-08-12 16:26:18 +04:00
/*
* 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 .
*
* Generic INET6 transport hashtables
*
2005-12-14 10:25:44 +03:00
* Authors : Lotsa people , from code originally in tcp , generalised here
* by Arnaldo Carvalho de Melo < acme @ mandriva . com >
2005-08-12 16:26:18 +04:00
*
* 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 .
*/
# include <linux/module.h>
2005-12-14 10:25:44 +03:00
# include <linux/random.h>
2005-08-12 16:26:18 +04:00
# include <net/inet_connection_sock.h>
# include <net/inet_hashtables.h>
# include <net/inet6_hashtables.h>
2005-12-14 10:25:44 +03:00
# include <net/ip.h>
2005-08-12 16:26:18 +04:00
2009-12-04 06:46:54 +03:00
int __inet6_hash ( struct sock * sk , struct inet_timewait_sock * tw )
2006-04-10 09:48:59 +04:00
{
2008-03-23 02:50:58 +03:00
struct inet_hashinfo * hashinfo = sk - > sk_prot - > h . hashinfo ;
2009-12-04 06:46:54 +03:00
int twrefcnt = 0 ;
2006-04-10 09:48:59 +04:00
2008-07-26 08:43:18 +04:00
WARN_ON ( ! sk_unhashed ( sk ) ) ;
2006-04-10 09:48:59 +04:00
if ( sk - > sk_state = = TCP_LISTEN ) {
2008-11-20 11:40:07 +03:00
struct inet_listen_hashbucket * ilb ;
2008-11-17 06:40:17 +03:00
2008-11-20 11:40:07 +03:00
ilb = & hashinfo - > listening_hash [ inet_sk_listen_hashfn ( sk ) ] ;
spin_lock ( & ilb - > lock ) ;
2008-11-24 04:22:55 +03:00
__sk_nulls_add_node_rcu ( sk , & ilb - > head ) ;
2008-11-20 11:40:07 +03:00
spin_unlock ( & ilb - > lock ) ;
2006-04-10 09:48:59 +04:00
} else {
unsigned int hash ;
2008-11-17 06:40:17 +03:00
struct hlist_nulls_head * list ;
2008-11-21 07:39:09 +03:00
spinlock_t * lock ;
2008-11-17 06:40:17 +03:00
2006-04-10 09:48:59 +04:00
sk - > sk_hash = hash = inet6_sk_ehashfn ( sk ) ;
2007-11-07 13:40:20 +03:00
list = & inet_ehash_bucket ( hashinfo , hash ) - > chain ;
lock = inet_ehash_lockp ( hashinfo , hash ) ;
2008-11-21 07:39:09 +03:00
spin_lock ( lock ) ;
2008-11-17 06:40:17 +03:00
__sk_nulls_add_node_rcu ( sk , list ) ;
2009-12-04 06:46:54 +03:00
if ( tw ) {
WARN_ON ( sk - > sk_hash ! = tw - > tw_hash ) ;
twrefcnt = inet_twsk_unhash ( tw ) ;
}
2008-11-21 07:39:09 +03:00
spin_unlock ( lock ) ;
2006-04-10 09:48:59 +04:00
}
2008-04-01 06:41:46 +04:00
sock_prot_inuse_add ( sock_net ( sk ) , sk - > sk_prot , 1 ) ;
2009-12-04 06:46:54 +03:00
return twrefcnt ;
2006-04-10 09:48:59 +04:00
}
EXPORT_SYMBOL ( __inet6_hash ) ;
/*
* 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 .
*/
2008-01-31 16:07:21 +03:00
struct sock * __inet6_lookup_established ( struct net * net ,
struct inet_hashinfo * hashinfo ,
2006-04-10 09:48:59 +04:00
const struct in6_addr * saddr ,
2006-11-08 11:20:00 +03:00
const __be16 sport ,
2006-04-10 09:48:59 +04:00
const struct in6_addr * daddr ,
const u16 hnum ,
const int dif )
{
struct sock * sk ;
2008-11-17 06:40:17 +03:00
const struct hlist_nulls_node * node ;
2006-09-28 05:43:07 +04:00
const __portpair ports = INET_COMBINED_PORTS ( sport , hnum ) ;
2006-04-10 09:48:59 +04:00
/* Optimize here for direct hit, only listening connections can
* have wildcards anyways .
*/
2008-06-17 04:13:48 +04:00
unsigned int hash = inet6_ehashfn ( net , daddr , hnum , saddr , sport ) ;
2009-10-09 04:16:19 +04:00
unsigned int slot = hash & hashinfo - > ehash_mask ;
2008-11-17 06:40:17 +03:00
struct inet_ehash_bucket * head = & hashinfo - > ehash [ slot ] ;
2006-04-10 09:48:59 +04:00
2008-11-17 06:40:17 +03:00
rcu_read_lock ( ) ;
begin :
sk_nulls_for_each_rcu ( sk , node , & head - > chain ) {
2006-04-10 09:48:59 +04:00
/* For IPV6 do the cheaper port and family tests first. */
2008-11-17 06:40:17 +03:00
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 ;
}
2006-04-10 09:48:59 +04:00
}
2008-11-17 06:40:17 +03:00
if ( get_nulls_value ( node ) ! = slot )
goto begin ;
begintw :
2006-04-10 09:48:59 +04:00
/* Must check for a TIME_WAIT'er before going to listener hash. */
2008-11-17 06:40:17 +03:00
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 ;
}
goto out ;
}
2006-04-10 09:48:59 +04:00
}
2008-11-17 06:40:17 +03:00
if ( get_nulls_value ( node ) ! = slot )
goto begintw ;
sk = NULL ;
out :
rcu_read_unlock ( ) ;
2006-04-10 09:48:59 +04:00
return sk ;
}
EXPORT_SYMBOL ( __inet6_lookup_established ) ;
2011-01-17 02:09:38 +03:00
static inline int compute_score ( struct sock * sk , struct net * net ,
2008-11-24 04:22:55 +03:00
const unsigned short hnum ,
const struct in6_addr * daddr ,
const int dif )
{
int score = - 1 ;
2009-10-15 10:30:45 +04:00
if ( net_eq ( sock_net ( sk ) , net ) & & inet_sk ( sk ) - > inet_num = = hnum & &
2008-11-24 04:22:55 +03:00
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 ) )
return - 1 ;
score + + ;
}
if ( sk - > sk_bound_dev_if ) {
if ( sk - > sk_bound_dev_if ! = dif )
return - 1 ;
score + + ;
}
}
return score ;
}
2008-01-31 16:07:21 +03:00
struct sock * inet6_lookup_listener ( struct net * net ,
struct inet_hashinfo * hashinfo , const struct in6_addr * daddr ,
const unsigned short hnum , const int dif )
2005-08-12 16:26:18 +04:00
{
struct sock * sk ;
2008-11-24 04:22:55 +03:00
const struct hlist_nulls_node * node ;
struct sock * result ;
int score , hiscore ;
unsigned int hash = inet_lhashfn ( net , hnum ) ;
struct inet_listen_hashbucket * ilb = & hashinfo - > listening_hash [ hash ] ;
rcu_read_lock ( ) ;
begin :
result = NULL ;
hiscore = - 1 ;
sk_nulls_for_each ( sk , node , & ilb - > head ) {
score = compute_score ( sk , net , hnum , daddr , dif ) ;
if ( score > hiscore ) {
hiscore = score ;
result = sk ;
2005-08-12 16:26:18 +04:00
}
}
2008-11-24 04:22:55 +03:00
/*
* if the nulls value we got at the end of this lookup is
* not the expected one , we must restart lookup .
* We probably met an item that was moved to another chain .
*/
if ( get_nulls_value ( node ) ! = hash + LISTENING_NULLS_BASE )
goto begin ;
if ( result ) {
if ( unlikely ( ! atomic_inc_not_zero ( & result - > sk_refcnt ) ) )
result = NULL ;
else if ( unlikely ( compute_score ( result , net , hnum , daddr ,
dif ) < hiscore ) ) {
sock_put ( result ) ;
goto begin ;
}
}
rcu_read_unlock ( ) ;
2005-08-12 16:26:18 +04:00
return result ;
}
EXPORT_SYMBOL_GPL ( inet6_lookup_listener ) ;
2008-01-31 16:07:21 +03:00
struct sock * inet6_lookup ( struct net * net , struct inet_hashinfo * hashinfo ,
2006-11-08 11:20:00 +03:00
const struct in6_addr * saddr , const __be16 sport ,
const struct in6_addr * daddr , const __be16 dport ,
2005-08-12 16:26:18 +04:00
const int dif )
{
struct sock * sk ;
local_bh_disable ( ) ;
2008-01-31 16:07:21 +03:00
sk = __inet6_lookup ( net , hashinfo , saddr , sport , daddr , ntohs ( dport ) , dif ) ;
2005-08-12 16:26:18 +04:00
local_bh_enable ( ) ;
return sk ;
}
EXPORT_SYMBOL_GPL ( inet6_lookup ) ;
2005-12-14 10:25:44 +03:00
static int __inet6_check_established ( struct inet_timewait_death_row * death_row ,
struct sock * sk , const __u16 lport ,
struct inet_timewait_sock * * twp )
{
struct inet_hashinfo * hinfo = death_row - > hashinfo ;
2006-03-14 01:26:12 +03:00
struct inet_sock * inet = inet_sk ( sk ) ;
2005-12-14 10:25:44 +03:00
const struct ipv6_pinfo * np = inet6_sk ( sk ) ;
const struct in6_addr * daddr = & np - > rcv_saddr ;
const struct in6_addr * saddr = & np - > daddr ;
const int dif = sk - > sk_bound_dev_if ;
2009-10-15 10:30:45 +04:00
const __portpair ports = INET_COMBINED_PORTS ( inet - > inet_dport , lport ) ;
2008-06-17 04:13:48 +04:00
struct net * net = sock_net ( sk ) ;
const unsigned int hash = inet6_ehashfn ( net , daddr , lport , saddr ,
2009-10-15 10:30:45 +04:00
inet - > inet_dport ) ;
2005-12-14 10:25:44 +03:00
struct inet_ehash_bucket * head = inet_ehash_bucket ( hinfo , hash ) ;
2008-11-21 07:39:09 +03:00
spinlock_t * lock = inet_ehash_lockp ( hinfo , hash ) ;
2005-12-14 10:25:44 +03:00
struct sock * sk2 ;
2008-11-17 06:40:17 +03:00
const struct hlist_nulls_node * node ;
2005-12-14 10:25:44 +03:00
struct inet_timewait_sock * tw ;
2009-12-03 01:31:19 +03:00
int twrefcnt = 0 ;
2005-12-14 10:25:44 +03:00
2008-11-21 07:39:09 +03:00
spin_lock ( lock ) ;
2005-12-14 10:25:44 +03:00
/* Check TIME-WAIT sockets first. */
2008-11-17 06:40:17 +03:00
sk_nulls_for_each ( sk2 , node , & head - > twchain ) {
2005-12-14 10:25:44 +03:00
tw = inet_twsk ( sk2 ) ;
2008-01-31 16:07:21 +03:00
if ( INET6_TW_MATCH ( sk2 , net , hash , saddr , daddr , ports , dif ) ) {
2005-12-14 10:25:44 +03:00
if ( twsk_unique ( sk , sk2 , twp ) )
goto unique ;
else
goto not_unique ;
}
}
tw = NULL ;
/* And established part... */
2008-11-17 06:40:17 +03:00
sk_nulls_for_each ( sk2 , node , & head - > chain ) {
2008-01-31 16:07:21 +03:00
if ( INET6_MATCH ( sk2 , net , hash , saddr , daddr , ports , dif ) )
2005-12-14 10:25:44 +03:00
goto not_unique ;
}
unique :
2006-03-14 01:26:12 +03:00
/* Must record num and sport now. Otherwise we will see
* in hash table socket with a funny identity . */
2009-10-15 10:30:45 +04:00
inet - > inet_num = lport ;
inet - > inet_sport = htons ( lport ) ;
2009-12-03 01:31:19 +03:00
sk - > sk_hash = hash ;
2008-07-26 08:43:18 +04:00
WARN_ON ( ! sk_unhashed ( sk ) ) ;
2008-11-17 06:40:17 +03:00
__sk_nulls_add_node_rcu ( sk , & head - > chain ) ;
2009-12-03 01:31:19 +03:00
if ( tw ) {
twrefcnt = inet_twsk_unhash ( tw ) ;
NET_INC_STATS_BH ( net , LINUX_MIB_TIMEWAITRECYCLED ) ;
}
2008-11-21 07:39:09 +03:00
spin_unlock ( lock ) ;
2009-12-03 01:31:19 +03:00
if ( twrefcnt )
inet_twsk_put ( tw ) ;
2008-04-01 06:41:46 +04:00
sock_prot_inuse_add ( sock_net ( sk ) , sk - > sk_prot , 1 ) ;
2005-12-14 10:25:44 +03:00
2009-12-03 01:31:19 +03:00
if ( twp ) {
2005-12-14 10:25:44 +03:00
* twp = tw ;
2009-12-03 01:31:19 +03:00
} else if ( tw ) {
2005-12-14 10:25:44 +03:00
/* Silly. Should hash-dance instead... */
inet_twsk_deschedule ( tw , death_row ) ;
inet_twsk_put ( tw ) ;
}
return 0 ;
not_unique :
2008-11-21 07:39:09 +03:00
spin_unlock ( lock ) ;
2005-12-14 10:25:44 +03:00
return - EADDRNOTAVAIL ;
}
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 ,
2009-10-15 10:30:45 +04:00
inet - > inet_dport ) ;
2005-12-14 10:25:44 +03:00
}
int inet6_hash_connect ( struct inet_timewait_death_row * death_row ,
struct sock * sk )
{
2008-02-05 14:14:44 +03:00
return __inet_hash_connect ( death_row , sk , inet6_sk_port_offset ( sk ) ,
2008-01-31 16:04:45 +03:00
__inet6_check_established , __inet6_hash ) ;
2005-12-14 10:25:44 +03:00
}
EXPORT_SYMBOL_GPL ( inet6_hash_connect ) ;