aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv4/inet_timewait_sock.c
blob: 5cba59b869feb8e75c9e3a9527722da088e934f9 (plain) (blame)
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
/*
 * 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 TIME_WAIT sockets functions
 *
 *		From code orinally in TCP
 */

#include <linux/config.h>

#include <net/inet_hashtables.h>
#include <net/inet_timewait_sock.h>

/* Must be called with locally disabled BHs. */
void __inet_twsk_kill(struct inet_timewait_sock *tw, struct inet_hashinfo *hashinfo)
{
	struct inet_bind_hashbucket *bhead;
	struct inet_bind_bucket *tb;
	/* Unlink from established hashes. */
	struct inet_ehash_bucket *ehead = &hashinfo->ehash[tw->tw_hashent];

	write_lock(&ehead->lock);
	if (hlist_unhashed(&tw->tw_node)) {
		write_unlock(&ehead->lock);
		return;
	}
	__hlist_del(&tw->tw_node);
	sk_node_init(&tw->tw_node);
	write_unlock(&ehead->lock);

	/* Disassociate with bind bucket. */
	bhead = &hashinfo->bhash[inet_bhashfn(tw->tw_num, hashinfo->bhash_size)];
	spin_lock(&bhead->lock);
	tb = tw->tw_tb;
	__hlist_del(&tw->tw_bind_node);
	tw->tw_tb = NULL;
	inet_bind_bucket_destroy(hashinfo->bind_bucket_cachep, tb);
	spin_unlock(&bhead->lock);
#ifdef SOCK_REFCNT_DEBUG
	if (atomic_read(&tw->tw_refcnt) != 1) {
		printk(KERN_DEBUG "%s timewait_sock %p refcnt=%d\n",
		       tw->tw_prot->name, tw, atomic_read(&tw->tw_refcnt));
	}
#endif
	inet_twsk_put(tw);
}

/*
 * Enter the time wait state. This is called with locally disabled BH.
 * Essentially we whip up a timewait bucket, copy the relevant info into it
 * from the SK, and mess with hash chains and list linkage.
 */
void __inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
			   struct inet_hashinfo *hashinfo)
{
	const struct inet_sock *inet = inet_sk(sk);
	const struct inet_connection_sock *icsk = inet_csk(sk);
	struct inet_ehash_bucket *ehead = &hashinfo->ehash[sk->sk_hashent];
	struct inet_bind_hashbucket *bhead;
	/* Step 1: Put TW into bind hash. Original socket stays there too.
	   Note, that any socket with inet->num != 0 MUST be bound in
	   binding cache, even if it is closed.
	 */
	bhead = &hashinfo->bhash[inet_bhashfn(inet->num, hashinfo->bhash_size)];
	spin_lock(&bhead->lock);
	tw->tw_tb = icsk->icsk_bind_hash;
	BUG_TRAP(icsk->icsk_bind_hash);
	inet_twsk_add_bind_node(tw, &tw->tw_tb->owners);
	spin_unlock(&bhead->lock);

	write_lock(&ehead->lock);

	/* Step 2: Remove SK from established hash. */
	if (__sk_del_node_init(sk))
		sock_prot_dec_use(sk->sk_prot);

	/* Step 3: Hash TW into TIMEWAIT half of established hash table. */
	inet_twsk_add_node(tw, &(ehead + hashinfo->ehash_size)->chain);
	atomic_inc(&tw->tw_refcnt);

	write_unlock(&ehead->lock);
}

struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk, const int state)
{
	struct inet_timewait_sock *tw = kmem_cache_alloc(sk->sk_prot_creator->twsk_slab,
							 SLAB_ATOMIC);
	if (tw != NULL) {
		const struct inet_sock *inet = inet_sk(sk);

		/* Give us an identity. */
		tw->tw_daddr	    = inet->daddr;
		tw->tw_rcv_saddr    = inet->rcv_saddr;
		tw->tw_bound_dev_if = sk->sk_bound_dev_if;
		tw->tw_num	    = inet->num;
		tw->tw_state	    = TCP_TIME_WAIT;
		tw->tw_substate	    = state;
		tw->tw_sport	    = inet->sport;
		tw->tw_dport	    = inet->dport;
		tw->tw_family	    = sk->sk_family;
		tw->tw_reuse	    = sk->sk_reuse;
		tw->tw_hashent	    = sk->sk_hashent;
		tw->tw_ipv6only	    = 0;
		tw->tw_prot	    = sk->sk_prot_creator;
		atomic_set(&tw->tw_refcnt, 1);
		inet_twsk_dead_node_init(tw);
	}

	return tw;
}