1/*
2 * Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * Development of this code funded by Astaro AG (http://www.astaro.com/)
9 */
10
11#include <linux/kernel.h>
12#include <linux/init.h>
13#include <linux/module.h>
14#include <linux/netlink.h>
15#include <linux/netfilter.h>
16#include <linux/netfilter/nf_tables.h>
17#include <net/netfilter/nf_tables.h>
18#include <net/netfilter/nf_conntrack.h>
19#include <net/netfilter/nf_conntrack_tuple.h>
20#include <net/netfilter/nf_conntrack_helper.h>
21#include <net/netfilter/nf_conntrack_ecache.h>
22#include <net/netfilter/nf_conntrack_labels.h>
23
24struct nft_ct {
25	enum nft_ct_keys	key:8;
26	enum ip_conntrack_dir	dir:8;
27	union {
28		enum nft_registers	dreg:8;
29		enum nft_registers	sreg:8;
30	};
31};
32
33static void nft_ct_get_eval(const struct nft_expr *expr,
34			    struct nft_data data[NFT_REG_MAX + 1],
35			    const struct nft_pktinfo *pkt)
36{
37	const struct nft_ct *priv = nft_expr_priv(expr);
38	struct nft_data *dest = &data[priv->dreg];
39	enum ip_conntrack_info ctinfo;
40	const struct nf_conn *ct;
41	const struct nf_conn_help *help;
42	const struct nf_conntrack_tuple *tuple;
43	const struct nf_conntrack_helper *helper;
44	long diff;
45	unsigned int state;
46
47	ct = nf_ct_get(pkt->skb, &ctinfo);
48
49	switch (priv->key) {
50	case NFT_CT_STATE:
51		if (ct == NULL)
52			state = NF_CT_STATE_INVALID_BIT;
53		else if (nf_ct_is_untracked(ct))
54			state = NF_CT_STATE_UNTRACKED_BIT;
55		else
56			state = NF_CT_STATE_BIT(ctinfo);
57		dest->data[0] = state;
58		return;
59	}
60
61	if (ct == NULL)
62		goto err;
63
64	switch (priv->key) {
65	case NFT_CT_DIRECTION:
66		dest->data[0] = CTINFO2DIR(ctinfo);
67		return;
68	case NFT_CT_STATUS:
69		dest->data[0] = ct->status;
70		return;
71#ifdef CONFIG_NF_CONNTRACK_MARK
72	case NFT_CT_MARK:
73		dest->data[0] = ct->mark;
74		return;
75#endif
76#ifdef CONFIG_NF_CONNTRACK_SECMARK
77	case NFT_CT_SECMARK:
78		dest->data[0] = ct->secmark;
79		return;
80#endif
81	case NFT_CT_EXPIRATION:
82		diff = (long)jiffies - (long)ct->timeout.expires;
83		if (diff < 0)
84			diff = 0;
85		dest->data[0] = jiffies_to_msecs(diff);
86		return;
87	case NFT_CT_HELPER:
88		if (ct->master == NULL)
89			goto err;
90		help = nfct_help(ct->master);
91		if (help == NULL)
92			goto err;
93		helper = rcu_dereference(help->helper);
94		if (helper == NULL)
95			goto err;
96		if (strlen(helper->name) >= sizeof(dest->data))
97			goto err;
98		strncpy((char *)dest->data, helper->name, sizeof(dest->data));
99		return;
100#ifdef CONFIG_NF_CONNTRACK_LABELS
101	case NFT_CT_LABELS: {
102		struct nf_conn_labels *labels = nf_ct_labels_find(ct);
103		unsigned int size;
104
105		if (!labels) {
106			memset(dest->data, 0, sizeof(dest->data));
107			return;
108		}
109
110		BUILD_BUG_ON(NF_CT_LABELS_MAX_SIZE > sizeof(dest->data));
111		size = labels->words * sizeof(long);
112
113		memcpy(dest->data, labels->bits, size);
114		if (size < sizeof(dest->data))
115			memset(((char *) dest->data) + size, 0,
116			       sizeof(dest->data) - size);
117		return;
118	}
119#endif
120	}
121
122	tuple = &ct->tuplehash[priv->dir].tuple;
123	switch (priv->key) {
124	case NFT_CT_L3PROTOCOL:
125		dest->data[0] = nf_ct_l3num(ct);
126		return;
127	case NFT_CT_SRC:
128		memcpy(dest->data, tuple->src.u3.all,
129		       nf_ct_l3num(ct) == NFPROTO_IPV4 ? 4 : 16);
130		return;
131	case NFT_CT_DST:
132		memcpy(dest->data, tuple->dst.u3.all,
133		       nf_ct_l3num(ct) == NFPROTO_IPV4 ? 4 : 16);
134		return;
135	case NFT_CT_PROTOCOL:
136		dest->data[0] = nf_ct_protonum(ct);
137		return;
138	case NFT_CT_PROTO_SRC:
139		dest->data[0] = (__force __u16)tuple->src.u.all;
140		return;
141	case NFT_CT_PROTO_DST:
142		dest->data[0] = (__force __u16)tuple->dst.u.all;
143		return;
144	}
145	return;
146err:
147	data[NFT_REG_VERDICT].verdict = NFT_BREAK;
148}
149
150static void nft_ct_set_eval(const struct nft_expr *expr,
151			    struct nft_data data[NFT_REG_MAX + 1],
152			    const struct nft_pktinfo *pkt)
153{
154	const struct nft_ct *priv = nft_expr_priv(expr);
155	struct sk_buff *skb = pkt->skb;
156#ifdef CONFIG_NF_CONNTRACK_MARK
157	u32 value = data[priv->sreg].data[0];
158#endif
159	enum ip_conntrack_info ctinfo;
160	struct nf_conn *ct;
161
162	ct = nf_ct_get(skb, &ctinfo);
163	if (ct == NULL)
164		return;
165
166	switch (priv->key) {
167#ifdef CONFIG_NF_CONNTRACK_MARK
168	case NFT_CT_MARK:
169		if (ct->mark != value) {
170			ct->mark = value;
171			nf_conntrack_event_cache(IPCT_MARK, ct);
172		}
173		break;
174#endif
175	}
176}
177
178static const struct nla_policy nft_ct_policy[NFTA_CT_MAX + 1] = {
179	[NFTA_CT_DREG]		= { .type = NLA_U32 },
180	[NFTA_CT_KEY]		= { .type = NLA_U32 },
181	[NFTA_CT_DIRECTION]	= { .type = NLA_U8 },
182	[NFTA_CT_SREG]		= { .type = NLA_U32 },
183};
184
185static int nft_ct_l3proto_try_module_get(uint8_t family)
186{
187	int err;
188
189	if (family == NFPROTO_INET) {
190		err = nf_ct_l3proto_try_module_get(NFPROTO_IPV4);
191		if (err < 0)
192			goto err1;
193		err = nf_ct_l3proto_try_module_get(NFPROTO_IPV6);
194		if (err < 0)
195			goto err2;
196	} else {
197		err = nf_ct_l3proto_try_module_get(family);
198		if (err < 0)
199			goto err1;
200	}
201	return 0;
202
203err2:
204	nf_ct_l3proto_module_put(NFPROTO_IPV4);
205err1:
206	return err;
207}
208
209static void nft_ct_l3proto_module_put(uint8_t family)
210{
211	if (family == NFPROTO_INET) {
212		nf_ct_l3proto_module_put(NFPROTO_IPV4);
213		nf_ct_l3proto_module_put(NFPROTO_IPV6);
214	} else
215		nf_ct_l3proto_module_put(family);
216}
217
218static int nft_ct_get_init(const struct nft_ctx *ctx,
219			   const struct nft_expr *expr,
220			   const struct nlattr * const tb[])
221{
222	struct nft_ct *priv = nft_expr_priv(expr);
223	int err;
224
225	priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY]));
226	switch (priv->key) {
227	case NFT_CT_STATE:
228	case NFT_CT_DIRECTION:
229	case NFT_CT_STATUS:
230#ifdef CONFIG_NF_CONNTRACK_MARK
231	case NFT_CT_MARK:
232#endif
233#ifdef CONFIG_NF_CONNTRACK_SECMARK
234	case NFT_CT_SECMARK:
235#endif
236#ifdef CONFIG_NF_CONNTRACK_LABELS
237	case NFT_CT_LABELS:
238#endif
239	case NFT_CT_EXPIRATION:
240	case NFT_CT_HELPER:
241		if (tb[NFTA_CT_DIRECTION] != NULL)
242			return -EINVAL;
243		break;
244	case NFT_CT_L3PROTOCOL:
245	case NFT_CT_PROTOCOL:
246	case NFT_CT_SRC:
247	case NFT_CT_DST:
248	case NFT_CT_PROTO_SRC:
249	case NFT_CT_PROTO_DST:
250		if (tb[NFTA_CT_DIRECTION] == NULL)
251			return -EINVAL;
252		break;
253	default:
254		return -EOPNOTSUPP;
255	}
256
257	if (tb[NFTA_CT_DIRECTION] != NULL) {
258		priv->dir = nla_get_u8(tb[NFTA_CT_DIRECTION]);
259		switch (priv->dir) {
260		case IP_CT_DIR_ORIGINAL:
261		case IP_CT_DIR_REPLY:
262			break;
263		default:
264			return -EINVAL;
265		}
266	}
267
268	priv->dreg = ntohl(nla_get_be32(tb[NFTA_CT_DREG]));
269	err = nft_validate_output_register(priv->dreg);
270	if (err < 0)
271		return err;
272
273	err = nft_validate_data_load(ctx, priv->dreg, NULL, NFT_DATA_VALUE);
274	if (err < 0)
275		return err;
276
277	err = nft_ct_l3proto_try_module_get(ctx->afi->family);
278	if (err < 0)
279		return err;
280
281	return 0;
282}
283
284static int nft_ct_set_init(const struct nft_ctx *ctx,
285			   const struct nft_expr *expr,
286			   const struct nlattr * const tb[])
287{
288	struct nft_ct *priv = nft_expr_priv(expr);
289	int err;
290
291	priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY]));
292	switch (priv->key) {
293#ifdef CONFIG_NF_CONNTRACK_MARK
294	case NFT_CT_MARK:
295		break;
296#endif
297	default:
298		return -EOPNOTSUPP;
299	}
300
301	priv->sreg = ntohl(nla_get_be32(tb[NFTA_CT_SREG]));
302	err = nft_validate_input_register(priv->sreg);
303	if (err < 0)
304		return err;
305
306	err = nft_ct_l3proto_try_module_get(ctx->afi->family);
307	if (err < 0)
308		return err;
309
310	return 0;
311}
312
313static void nft_ct_destroy(const struct nft_ctx *ctx,
314			   const struct nft_expr *expr)
315{
316	nft_ct_l3proto_module_put(ctx->afi->family);
317}
318
319static int nft_ct_get_dump(struct sk_buff *skb, const struct nft_expr *expr)
320{
321	const struct nft_ct *priv = nft_expr_priv(expr);
322
323	if (nla_put_be32(skb, NFTA_CT_DREG, htonl(priv->dreg)))
324		goto nla_put_failure;
325	if (nla_put_be32(skb, NFTA_CT_KEY, htonl(priv->key)))
326		goto nla_put_failure;
327
328	switch (priv->key) {
329	case NFT_CT_PROTOCOL:
330	case NFT_CT_SRC:
331	case NFT_CT_DST:
332	case NFT_CT_PROTO_SRC:
333	case NFT_CT_PROTO_DST:
334		if (nla_put_u8(skb, NFTA_CT_DIRECTION, priv->dir))
335			goto nla_put_failure;
336	default:
337		break;
338	}
339
340	return 0;
341
342nla_put_failure:
343	return -1;
344}
345
346static int nft_ct_set_dump(struct sk_buff *skb, const struct nft_expr *expr)
347{
348	const struct nft_ct *priv = nft_expr_priv(expr);
349
350	if (nla_put_be32(skb, NFTA_CT_SREG, htonl(priv->sreg)))
351		goto nla_put_failure;
352	if (nla_put_be32(skb, NFTA_CT_KEY, htonl(priv->key)))
353		goto nla_put_failure;
354	return 0;
355
356nla_put_failure:
357	return -1;
358}
359
360static struct nft_expr_type nft_ct_type;
361static const struct nft_expr_ops nft_ct_get_ops = {
362	.type		= &nft_ct_type,
363	.size		= NFT_EXPR_SIZE(sizeof(struct nft_ct)),
364	.eval		= nft_ct_get_eval,
365	.init		= nft_ct_get_init,
366	.destroy	= nft_ct_destroy,
367	.dump		= nft_ct_get_dump,
368};
369
370static const struct nft_expr_ops nft_ct_set_ops = {
371	.type		= &nft_ct_type,
372	.size		= NFT_EXPR_SIZE(sizeof(struct nft_ct)),
373	.eval		= nft_ct_set_eval,
374	.init		= nft_ct_set_init,
375	.destroy	= nft_ct_destroy,
376	.dump		= nft_ct_set_dump,
377};
378
379static const struct nft_expr_ops *
380nft_ct_select_ops(const struct nft_ctx *ctx,
381		    const struct nlattr * const tb[])
382{
383	if (tb[NFTA_CT_KEY] == NULL)
384		return ERR_PTR(-EINVAL);
385
386	if (tb[NFTA_CT_DREG] && tb[NFTA_CT_SREG])
387		return ERR_PTR(-EINVAL);
388
389	if (tb[NFTA_CT_DREG])
390		return &nft_ct_get_ops;
391
392	if (tb[NFTA_CT_SREG])
393		return &nft_ct_set_ops;
394
395	return ERR_PTR(-EINVAL);
396}
397
398static struct nft_expr_type nft_ct_type __read_mostly = {
399	.name		= "ct",
400	.select_ops	= &nft_ct_select_ops,
401	.policy		= nft_ct_policy,
402	.maxattr	= NFTA_CT_MAX,
403	.owner		= THIS_MODULE,
404};
405
406static int __init nft_ct_module_init(void)
407{
408	return nft_register_expr(&nft_ct_type);
409}
410
411static void __exit nft_ct_module_exit(void)
412{
413	nft_unregister_expr(&nft_ct_type);
414}
415
416module_init(nft_ct_module_init);
417module_exit(nft_ct_module_exit);
418
419MODULE_LICENSE("GPL");
420MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
421MODULE_ALIAS_NFT_EXPR("ct");
422