xref: /openbmc/linux/net/netfilter/xt_connlimit.c (revision a34c4589)
1370786f9SJan Engelhardt /*
2370786f9SJan Engelhardt  * netfilter module to limit the number of parallel tcp
3370786f9SJan Engelhardt  * connections per IP address.
4370786f9SJan Engelhardt  *   (c) 2000 Gerd Knorr <kraxel@bytesex.org>
5370786f9SJan Engelhardt  *   Nov 2002: Martin Bene <martin.bene@icomedias.com>:
6370786f9SJan Engelhardt  *		only ignore TIME_WAIT or gone connections
7370786f9SJan Engelhardt  *   Copyright © Jan Engelhardt <jengelh@gmx.de>, 2007
8370786f9SJan Engelhardt  *
9370786f9SJan Engelhardt  * based on ...
10370786f9SJan Engelhardt  *
11370786f9SJan Engelhardt  * Kernel module to match connection tracking information.
12370786f9SJan Engelhardt  * GPL (C) 1999  Rusty Russell (rusty@rustcorp.com.au).
13370786f9SJan Engelhardt  */
14370786f9SJan Engelhardt #include <linux/in.h>
15370786f9SJan Engelhardt #include <linux/in6.h>
16370786f9SJan Engelhardt #include <linux/ip.h>
17370786f9SJan Engelhardt #include <linux/ipv6.h>
18370786f9SJan Engelhardt #include <linux/jhash.h>
19370786f9SJan Engelhardt #include <linux/list.h>
20370786f9SJan Engelhardt #include <linux/module.h>
21370786f9SJan Engelhardt #include <linux/random.h>
22370786f9SJan Engelhardt #include <linux/skbuff.h>
23370786f9SJan Engelhardt #include <linux/spinlock.h>
24370786f9SJan Engelhardt #include <linux/netfilter/nf_conntrack_tcp.h>
25370786f9SJan Engelhardt #include <linux/netfilter/x_tables.h>
26370786f9SJan Engelhardt #include <linux/netfilter/xt_connlimit.h>
27370786f9SJan Engelhardt #include <net/netfilter/nf_conntrack.h>
28370786f9SJan Engelhardt #include <net/netfilter/nf_conntrack_core.h>
29370786f9SJan Engelhardt #include <net/netfilter/nf_conntrack_tuple.h>
30370786f9SJan Engelhardt 
31370786f9SJan Engelhardt /* we will save the tuples of all connections we care about */
32370786f9SJan Engelhardt struct xt_connlimit_conn {
33370786f9SJan Engelhardt 	struct list_head list;
34370786f9SJan Engelhardt 	struct nf_conntrack_tuple tuple;
35370786f9SJan Engelhardt };
36370786f9SJan Engelhardt 
37370786f9SJan Engelhardt struct xt_connlimit_data {
38370786f9SJan Engelhardt 	struct list_head iphash[256];
39370786f9SJan Engelhardt 	spinlock_t lock;
40370786f9SJan Engelhardt };
41370786f9SJan Engelhardt 
42370786f9SJan Engelhardt static u_int32_t connlimit_rnd;
43370786f9SJan Engelhardt static bool connlimit_rnd_inited;
44370786f9SJan Engelhardt 
45a34c4589SAl Viro static inline unsigned int connlimit_iphash(__be32 addr)
46370786f9SJan Engelhardt {
47370786f9SJan Engelhardt 	if (unlikely(!connlimit_rnd_inited)) {
48370786f9SJan Engelhardt 		get_random_bytes(&connlimit_rnd, sizeof(connlimit_rnd));
49370786f9SJan Engelhardt 		connlimit_rnd_inited = true;
50370786f9SJan Engelhardt 	}
51a34c4589SAl Viro 	return jhash_1word((__force __u32)addr, connlimit_rnd) & 0xFF;
52370786f9SJan Engelhardt }
53370786f9SJan Engelhardt 
54370786f9SJan Engelhardt static inline unsigned int
55370786f9SJan Engelhardt connlimit_iphash6(const union nf_conntrack_address *addr,
56370786f9SJan Engelhardt 		  const union nf_conntrack_address *mask)
57370786f9SJan Engelhardt {
58370786f9SJan Engelhardt 	union nf_conntrack_address res;
59370786f9SJan Engelhardt 	unsigned int i;
60370786f9SJan Engelhardt 
61370786f9SJan Engelhardt 	if (unlikely(!connlimit_rnd_inited)) {
62370786f9SJan Engelhardt 		get_random_bytes(&connlimit_rnd, sizeof(connlimit_rnd));
63370786f9SJan Engelhardt 		connlimit_rnd_inited = true;
64370786f9SJan Engelhardt 	}
65370786f9SJan Engelhardt 
66370786f9SJan Engelhardt 	for (i = 0; i < ARRAY_SIZE(addr->ip6); ++i)
67370786f9SJan Engelhardt 		res.ip6[i] = addr->ip6[i] & mask->ip6[i];
68370786f9SJan Engelhardt 
69a34c4589SAl Viro 	return jhash2((u32 *)res.ip6, ARRAY_SIZE(res.ip6), connlimit_rnd) & 0xFF;
70370786f9SJan Engelhardt }
71370786f9SJan Engelhardt 
72370786f9SJan Engelhardt static inline bool already_closed(const struct nf_conn *conn)
73370786f9SJan Engelhardt {
74370786f9SJan Engelhardt 	u_int16_t proto = conn->tuplehash[0].tuple.dst.protonum;
75370786f9SJan Engelhardt 
76370786f9SJan Engelhardt 	if (proto == IPPROTO_TCP)
77370786f9SJan Engelhardt 		return conn->proto.tcp.state == TCP_CONNTRACK_TIME_WAIT;
78370786f9SJan Engelhardt 	else
79370786f9SJan Engelhardt 		return 0;
80370786f9SJan Engelhardt }
81370786f9SJan Engelhardt 
82370786f9SJan Engelhardt static inline unsigned int
83370786f9SJan Engelhardt same_source_net(const union nf_conntrack_address *addr,
84370786f9SJan Engelhardt 		const union nf_conntrack_address *mask,
85370786f9SJan Engelhardt 		const union nf_conntrack_address *u3, unsigned int family)
86370786f9SJan Engelhardt {
87370786f9SJan Engelhardt 	if (family == AF_INET) {
88370786f9SJan Engelhardt 		return (addr->ip & mask->ip) == (u3->ip & mask->ip);
89370786f9SJan Engelhardt 	} else {
90370786f9SJan Engelhardt 		union nf_conntrack_address lh, rh;
91370786f9SJan Engelhardt 		unsigned int i;
92370786f9SJan Engelhardt 
93370786f9SJan Engelhardt 		for (i = 0; i < ARRAY_SIZE(addr->ip6); ++i) {
94370786f9SJan Engelhardt 			lh.ip6[i] = addr->ip6[i] & mask->ip6[i];
95370786f9SJan Engelhardt 			rh.ip6[i] = u3->ip6[i] & mask->ip6[i];
96370786f9SJan Engelhardt 		}
97370786f9SJan Engelhardt 
98370786f9SJan Engelhardt 		return memcmp(&lh.ip6, &rh.ip6, sizeof(lh.ip6)) == 0;
99370786f9SJan Engelhardt 	}
100370786f9SJan Engelhardt }
101370786f9SJan Engelhardt 
102370786f9SJan Engelhardt static int count_them(struct xt_connlimit_data *data,
103370786f9SJan Engelhardt 		      const struct nf_conntrack_tuple *tuple,
104370786f9SJan Engelhardt 		      const union nf_conntrack_address *addr,
105370786f9SJan Engelhardt 		      const union nf_conntrack_address *mask,
106370786f9SJan Engelhardt 		      const struct xt_match *match)
107370786f9SJan Engelhardt {
108370786f9SJan Engelhardt 	struct nf_conntrack_tuple_hash *found;
109370786f9SJan Engelhardt 	struct xt_connlimit_conn *conn;
110370786f9SJan Engelhardt 	struct xt_connlimit_conn *tmp;
111370786f9SJan Engelhardt 	struct nf_conn *found_ct;
112370786f9SJan Engelhardt 	struct list_head *hash;
113370786f9SJan Engelhardt 	bool addit = true;
114370786f9SJan Engelhardt 	int matches = 0;
115370786f9SJan Engelhardt 
116370786f9SJan Engelhardt 
117370786f9SJan Engelhardt 	if (match->family == AF_INET6)
118370786f9SJan Engelhardt 		hash = &data->iphash[connlimit_iphash6(addr, mask)];
119370786f9SJan Engelhardt 	else
120370786f9SJan Engelhardt 		hash = &data->iphash[connlimit_iphash(addr->ip & mask->ip)];
121370786f9SJan Engelhardt 
122370786f9SJan Engelhardt 	read_lock_bh(&nf_conntrack_lock);
123370786f9SJan Engelhardt 
124370786f9SJan Engelhardt 	/* check the saved connections */
125370786f9SJan Engelhardt 	list_for_each_entry_safe(conn, tmp, hash, list) {
126370786f9SJan Engelhardt 		found    = __nf_conntrack_find(&conn->tuple, NULL);
127370786f9SJan Engelhardt 		found_ct = NULL;
128370786f9SJan Engelhardt 
129370786f9SJan Engelhardt 		if (found != NULL)
130370786f9SJan Engelhardt 			found_ct = nf_ct_tuplehash_to_ctrack(found);
131370786f9SJan Engelhardt 
132370786f9SJan Engelhardt 		if (found_ct != NULL &&
133370786f9SJan Engelhardt 		    nf_ct_tuple_equal(&conn->tuple, tuple) &&
134370786f9SJan Engelhardt 		    !already_closed(found_ct))
135370786f9SJan Engelhardt 			/*
136370786f9SJan Engelhardt 			 * Just to be sure we have it only once in the list.
137370786f9SJan Engelhardt 			 * We should not see tuples twice unless someone hooks
138370786f9SJan Engelhardt 			 * this into a table without "-p tcp --syn".
139370786f9SJan Engelhardt 			 */
140370786f9SJan Engelhardt 			addit = false;
141370786f9SJan Engelhardt 
142370786f9SJan Engelhardt 		if (found == NULL) {
143370786f9SJan Engelhardt 			/* this one is gone */
144370786f9SJan Engelhardt 			list_del(&conn->list);
145370786f9SJan Engelhardt 			kfree(conn);
146370786f9SJan Engelhardt 			continue;
147370786f9SJan Engelhardt 		}
148370786f9SJan Engelhardt 
149370786f9SJan Engelhardt 		if (already_closed(found_ct)) {
150370786f9SJan Engelhardt 			/*
151370786f9SJan Engelhardt 			 * we do not care about connections which are
152370786f9SJan Engelhardt 			 * closed already -> ditch it
153370786f9SJan Engelhardt 			 */
154370786f9SJan Engelhardt 			list_del(&conn->list);
155370786f9SJan Engelhardt 			kfree(conn);
156370786f9SJan Engelhardt 			continue;
157370786f9SJan Engelhardt 		}
158370786f9SJan Engelhardt 
159370786f9SJan Engelhardt 		if (same_source_net(addr, mask, &conn->tuple.src.u3,
160370786f9SJan Engelhardt 		    match->family))
161370786f9SJan Engelhardt 			/* same source network -> be counted! */
162370786f9SJan Engelhardt 			++matches;
163370786f9SJan Engelhardt 	}
164370786f9SJan Engelhardt 
165370786f9SJan Engelhardt 	read_unlock_bh(&nf_conntrack_lock);
166370786f9SJan Engelhardt 
167370786f9SJan Engelhardt 	if (addit) {
168370786f9SJan Engelhardt 		/* save the new connection in our list */
169370786f9SJan Engelhardt 		conn = kzalloc(sizeof(*conn), GFP_ATOMIC);
170370786f9SJan Engelhardt 		if (conn == NULL)
171370786f9SJan Engelhardt 			return -ENOMEM;
172370786f9SJan Engelhardt 		conn->tuple = *tuple;
173370786f9SJan Engelhardt 		list_add(&conn->list, hash);
174370786f9SJan Engelhardt 		++matches;
175370786f9SJan Engelhardt 	}
176370786f9SJan Engelhardt 
177370786f9SJan Engelhardt 	return matches;
178370786f9SJan Engelhardt }
179370786f9SJan Engelhardt 
180370786f9SJan Engelhardt static bool connlimit_match(const struct sk_buff *skb,
181370786f9SJan Engelhardt 			    const struct net_device *in,
182370786f9SJan Engelhardt 			    const struct net_device *out,
183370786f9SJan Engelhardt 			    const struct xt_match *match,
184370786f9SJan Engelhardt 			    const void *matchinfo, int offset,
185370786f9SJan Engelhardt 			    unsigned int protoff, bool *hotdrop)
186370786f9SJan Engelhardt {
187370786f9SJan Engelhardt 	const struct xt_connlimit_info *info = matchinfo;
188370786f9SJan Engelhardt 	union nf_conntrack_address addr, mask;
189370786f9SJan Engelhardt 	struct nf_conntrack_tuple tuple;
190370786f9SJan Engelhardt 	const struct nf_conntrack_tuple *tuple_ptr = &tuple;
191370786f9SJan Engelhardt 	enum ip_conntrack_info ctinfo;
192370786f9SJan Engelhardt 	const struct nf_conn *ct;
193370786f9SJan Engelhardt 	int connections;
194370786f9SJan Engelhardt 
195370786f9SJan Engelhardt 	ct = nf_ct_get(skb, &ctinfo);
196370786f9SJan Engelhardt 	if (ct != NULL)
197370786f9SJan Engelhardt 		tuple_ptr = &ct->tuplehash[0].tuple;
198370786f9SJan Engelhardt 	else if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
199370786f9SJan Engelhardt 				    match->family, &tuple))
200370786f9SJan Engelhardt 		goto hotdrop;
201370786f9SJan Engelhardt 
202370786f9SJan Engelhardt 	if (match->family == AF_INET6) {
203370786f9SJan Engelhardt 		const struct ipv6hdr *iph = ipv6_hdr(skb);
204370786f9SJan Engelhardt 		memcpy(&addr.ip6, &iph->saddr, sizeof(iph->saddr));
205370786f9SJan Engelhardt 		memcpy(&mask.ip6, info->v6_mask, sizeof(info->v6_mask));
206370786f9SJan Engelhardt 	} else {
207370786f9SJan Engelhardt 		const struct iphdr *iph = ip_hdr(skb);
208370786f9SJan Engelhardt 		addr.ip = iph->saddr;
209370786f9SJan Engelhardt 		mask.ip = info->v4_mask;
210370786f9SJan Engelhardt 	}
211370786f9SJan Engelhardt 
212370786f9SJan Engelhardt 	spin_lock_bh(&info->data->lock);
213370786f9SJan Engelhardt 	connections = count_them(info->data, tuple_ptr, &addr, &mask, match);
214370786f9SJan Engelhardt 	spin_unlock_bh(&info->data->lock);
215370786f9SJan Engelhardt 
216370786f9SJan Engelhardt 	if (connections < 0) {
217370786f9SJan Engelhardt 		/* kmalloc failed, drop it entirely */
218370786f9SJan Engelhardt 		*hotdrop = true;
219370786f9SJan Engelhardt 		return false;
220370786f9SJan Engelhardt 	}
221370786f9SJan Engelhardt 
222370786f9SJan Engelhardt 	return (connections > info->limit) ^ info->inverse;
223370786f9SJan Engelhardt 
224370786f9SJan Engelhardt  hotdrop:
225370786f9SJan Engelhardt 	*hotdrop = true;
226370786f9SJan Engelhardt 	return false;
227370786f9SJan Engelhardt }
228370786f9SJan Engelhardt 
229370786f9SJan Engelhardt static bool connlimit_check(const char *tablename, const void *ip,
230370786f9SJan Engelhardt 			    const struct xt_match *match, void *matchinfo,
231370786f9SJan Engelhardt 			    unsigned int hook_mask)
232370786f9SJan Engelhardt {
233370786f9SJan Engelhardt 	struct xt_connlimit_info *info = matchinfo;
234370786f9SJan Engelhardt 	unsigned int i;
235370786f9SJan Engelhardt 
236370786f9SJan Engelhardt 	if (nf_ct_l3proto_try_module_get(match->family) < 0) {
237370786f9SJan Engelhardt 		printk(KERN_WARNING "cannot load conntrack support for "
238370786f9SJan Engelhardt 		       "address family %u\n", match->family);
239370786f9SJan Engelhardt 		return false;
240370786f9SJan Engelhardt 	}
241370786f9SJan Engelhardt 
242370786f9SJan Engelhardt 	/* init private data */
243370786f9SJan Engelhardt 	info->data = kmalloc(sizeof(struct xt_connlimit_data), GFP_KERNEL);
244370786f9SJan Engelhardt 	if (info->data == NULL) {
245370786f9SJan Engelhardt 		nf_ct_l3proto_module_put(match->family);
246370786f9SJan Engelhardt 		return false;
247370786f9SJan Engelhardt 	}
248370786f9SJan Engelhardt 
249370786f9SJan Engelhardt 	spin_lock_init(&info->data->lock);
250370786f9SJan Engelhardt 	for (i = 0; i < ARRAY_SIZE(info->data->iphash); ++i)
251370786f9SJan Engelhardt 		INIT_LIST_HEAD(&info->data->iphash[i]);
252370786f9SJan Engelhardt 
253370786f9SJan Engelhardt 	return true;
254370786f9SJan Engelhardt }
255370786f9SJan Engelhardt 
256370786f9SJan Engelhardt static void connlimit_destroy(const struct xt_match *match, void *matchinfo)
257370786f9SJan Engelhardt {
258370786f9SJan Engelhardt 	struct xt_connlimit_info *info = matchinfo;
259370786f9SJan Engelhardt 	struct xt_connlimit_conn *conn;
260370786f9SJan Engelhardt 	struct xt_connlimit_conn *tmp;
261370786f9SJan Engelhardt 	struct list_head *hash = info->data->iphash;
262370786f9SJan Engelhardt 	unsigned int i;
263370786f9SJan Engelhardt 
264370786f9SJan Engelhardt 	nf_ct_l3proto_module_put(match->family);
265370786f9SJan Engelhardt 
266370786f9SJan Engelhardt 	for (i = 0; i < ARRAY_SIZE(info->data->iphash); ++i) {
267370786f9SJan Engelhardt 		list_for_each_entry_safe(conn, tmp, &hash[i], list) {
268370786f9SJan Engelhardt 			list_del(&conn->list);
269370786f9SJan Engelhardt 			kfree(conn);
270370786f9SJan Engelhardt 		}
271370786f9SJan Engelhardt 	}
272370786f9SJan Engelhardt 
273370786f9SJan Engelhardt 	kfree(info->data);
274370786f9SJan Engelhardt }
275370786f9SJan Engelhardt 
276370786f9SJan Engelhardt static struct xt_match connlimit_reg[] __read_mostly = {
277370786f9SJan Engelhardt 	{
278370786f9SJan Engelhardt 		.name       = "connlimit",
279370786f9SJan Engelhardt 		.family     = AF_INET,
280370786f9SJan Engelhardt 		.checkentry = connlimit_check,
281370786f9SJan Engelhardt 		.match      = connlimit_match,
282370786f9SJan Engelhardt 		.matchsize  = sizeof(struct xt_connlimit_info),
283370786f9SJan Engelhardt 		.destroy    = connlimit_destroy,
284370786f9SJan Engelhardt 		.me         = THIS_MODULE,
285370786f9SJan Engelhardt 	},
286370786f9SJan Engelhardt 	{
287370786f9SJan Engelhardt 		.name       = "connlimit",
288370786f9SJan Engelhardt 		.family     = AF_INET6,
289370786f9SJan Engelhardt 		.checkentry = connlimit_check,
290370786f9SJan Engelhardt 		.match      = connlimit_match,
291370786f9SJan Engelhardt 		.matchsize  = sizeof(struct xt_connlimit_info),
292370786f9SJan Engelhardt 		.destroy    = connlimit_destroy,
293370786f9SJan Engelhardt 		.me         = THIS_MODULE,
294370786f9SJan Engelhardt 	},
295370786f9SJan Engelhardt };
296370786f9SJan Engelhardt 
297370786f9SJan Engelhardt static int __init xt_connlimit_init(void)
298370786f9SJan Engelhardt {
299370786f9SJan Engelhardt 	return xt_register_matches(connlimit_reg, ARRAY_SIZE(connlimit_reg));
300370786f9SJan Engelhardt }
301370786f9SJan Engelhardt 
302370786f9SJan Engelhardt static void __exit xt_connlimit_exit(void)
303370786f9SJan Engelhardt {
304370786f9SJan Engelhardt 	xt_unregister_matches(connlimit_reg, ARRAY_SIZE(connlimit_reg));
305370786f9SJan Engelhardt }
306370786f9SJan Engelhardt 
307370786f9SJan Engelhardt module_init(xt_connlimit_init);
308370786f9SJan Engelhardt module_exit(xt_connlimit_exit);
309370786f9SJan Engelhardt MODULE_AUTHOR("Jan Engelhardt <jengelh@gmx.de>");
310370786f9SJan Engelhardt MODULE_DESCRIPTION("netfilter xt_connlimit match module");
311370786f9SJan Engelhardt MODULE_LICENSE("GPL");
312370786f9SJan Engelhardt MODULE_ALIAS("ipt_connlimit");
313370786f9SJan Engelhardt MODULE_ALIAS("ip6t_connlimit");
314