Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1/* SPDX-License-Identifier: GPL-2.0 */
2#include <linux/kernel.h>
3#include <linux/init.h>
4#include <linux/module.h>
5#include <linux/spinlock.h>
6#include <linux/netlink.h>
7#include <linux/netfilter.h>
8#include <linux/netfilter/nf_tables.h>
9#include <net/netfilter/nf_tables.h>
10#include <net/netfilter/nf_conntrack.h>
11#include <net/netfilter/nf_conntrack_count.h>
12#include <net/netfilter/nf_conntrack_core.h>
13#include <net/netfilter/nf_conntrack_tuple.h>
14#include <net/netfilter/nf_conntrack_zones.h>
15
16struct nft_connlimit {
17 struct nf_conncount_list *list;
18 u32 limit;
19 bool invert;
20};
21
22static inline void nft_connlimit_do_eval(struct nft_connlimit *priv,
23 struct nft_regs *regs,
24 const struct nft_pktinfo *pkt,
25 const struct nft_set_ext *ext)
26{
27 unsigned int count;
28 int err;
29
30 err = nf_conncount_add_skb(nft_net(pkt), pkt->skb, nft_pf(pkt), priv->list);
31 if (err) {
32 if (err == -EEXIST) {
33 /* Call gc to update the list count if any connection has
34 * been closed already. This is useful for softlimit
35 * connections like limiting bandwidth based on a number
36 * of open connections.
37 */
38 nf_conncount_gc_list(nft_net(pkt), priv->list);
39 } else {
40 regs->verdict.code = NF_DROP;
41 return;
42 }
43 }
44
45 count = READ_ONCE(priv->list->count);
46
47 if ((count > READ_ONCE(priv->limit)) ^ READ_ONCE(priv->invert)) {
48 regs->verdict.code = NFT_BREAK;
49 return;
50 }
51}
52
53static int nft_connlimit_do_init(const struct nft_ctx *ctx,
54 const struct nlattr * const tb[],
55 struct nft_connlimit *priv)
56{
57 bool invert = false;
58 u32 flags, limit;
59 int err;
60
61 if (!tb[NFTA_CONNLIMIT_COUNT])
62 return -EINVAL;
63
64 limit = ntohl(nla_get_be32(tb[NFTA_CONNLIMIT_COUNT]));
65
66 if (tb[NFTA_CONNLIMIT_FLAGS]) {
67 flags = ntohl(nla_get_be32(tb[NFTA_CONNLIMIT_FLAGS]));
68 if (flags & ~NFT_CONNLIMIT_F_INV)
69 return -EOPNOTSUPP;
70 if (flags & NFT_CONNLIMIT_F_INV)
71 invert = true;
72 }
73
74 priv->list = kmalloc(sizeof(*priv->list), GFP_KERNEL_ACCOUNT);
75 if (!priv->list)
76 return -ENOMEM;
77
78 nf_conncount_list_init(priv->list);
79 priv->limit = limit;
80 priv->invert = invert;
81
82 err = nf_ct_netns_get(ctx->net, ctx->family);
83 if (err < 0)
84 goto err_netns;
85
86 return 0;
87err_netns:
88 kfree(priv->list);
89
90 return err;
91}
92
93static void nft_connlimit_do_destroy(const struct nft_ctx *ctx,
94 struct nft_connlimit *priv)
95{
96 nf_ct_netns_put(ctx->net, ctx->family);
97 nf_conncount_cache_free(priv->list);
98 kfree(priv->list);
99}
100
101static int nft_connlimit_do_dump(struct sk_buff *skb,
102 struct nft_connlimit *priv)
103{
104 if (nla_put_be32(skb, NFTA_CONNLIMIT_COUNT, htonl(priv->limit)))
105 goto nla_put_failure;
106 if (priv->invert &&
107 nla_put_be32(skb, NFTA_CONNLIMIT_FLAGS, htonl(NFT_CONNLIMIT_F_INV)))
108 goto nla_put_failure;
109
110 return 0;
111
112nla_put_failure:
113 return -1;
114}
115
116static inline void nft_connlimit_obj_eval(struct nft_object *obj,
117 struct nft_regs *regs,
118 const struct nft_pktinfo *pkt)
119{
120 struct nft_connlimit *priv = nft_obj_data(obj);
121
122 nft_connlimit_do_eval(priv, regs, pkt, NULL);
123}
124
125static int nft_connlimit_obj_init(const struct nft_ctx *ctx,
126 const struct nlattr * const tb[],
127 struct nft_object *obj)
128{
129 struct nft_connlimit *priv = nft_obj_data(obj);
130
131 return nft_connlimit_do_init(ctx, tb, priv);
132}
133
134static void nft_connlimit_obj_update(struct nft_object *obj,
135 struct nft_object *newobj)
136{
137 struct nft_connlimit *newpriv = nft_obj_data(newobj);
138 struct nft_connlimit *priv = nft_obj_data(obj);
139
140 WRITE_ONCE(priv->limit, newpriv->limit);
141 WRITE_ONCE(priv->invert, newpriv->invert);
142}
143
144static void nft_connlimit_obj_destroy(const struct nft_ctx *ctx,
145 struct nft_object *obj)
146{
147 struct nft_connlimit *priv = nft_obj_data(obj);
148
149 nft_connlimit_do_destroy(ctx, priv);
150}
151
152static int nft_connlimit_obj_dump(struct sk_buff *skb,
153 struct nft_object *obj, bool reset)
154{
155 struct nft_connlimit *priv = nft_obj_data(obj);
156
157 return nft_connlimit_do_dump(skb, priv);
158}
159
160static const struct nla_policy nft_connlimit_policy[NFTA_CONNLIMIT_MAX + 1] = {
161 [NFTA_CONNLIMIT_COUNT] = { .type = NLA_U32 },
162 [NFTA_CONNLIMIT_FLAGS] = { .type = NLA_U32 },
163};
164
165static struct nft_object_type nft_connlimit_obj_type;
166static const struct nft_object_ops nft_connlimit_obj_ops = {
167 .type = &nft_connlimit_obj_type,
168 .size = sizeof(struct nft_connlimit),
169 .eval = nft_connlimit_obj_eval,
170 .init = nft_connlimit_obj_init,
171 .destroy = nft_connlimit_obj_destroy,
172 .dump = nft_connlimit_obj_dump,
173 .update = nft_connlimit_obj_update,
174};
175
176static struct nft_object_type nft_connlimit_obj_type __read_mostly = {
177 .type = NFT_OBJECT_CONNLIMIT,
178 .ops = &nft_connlimit_obj_ops,
179 .maxattr = NFTA_CONNLIMIT_MAX,
180 .policy = nft_connlimit_policy,
181 .owner = THIS_MODULE,
182};
183
184static void nft_connlimit_eval(const struct nft_expr *expr,
185 struct nft_regs *regs,
186 const struct nft_pktinfo *pkt)
187{
188 struct nft_connlimit *priv = nft_expr_priv(expr);
189
190 nft_connlimit_do_eval(priv, regs, pkt, NULL);
191}
192
193static int nft_connlimit_dump(struct sk_buff *skb,
194 const struct nft_expr *expr, bool reset)
195{
196 struct nft_connlimit *priv = nft_expr_priv(expr);
197
198 return nft_connlimit_do_dump(skb, priv);
199}
200
201static int nft_connlimit_init(const struct nft_ctx *ctx,
202 const struct nft_expr *expr,
203 const struct nlattr * const tb[])
204{
205 struct nft_connlimit *priv = nft_expr_priv(expr);
206
207 return nft_connlimit_do_init(ctx, tb, priv);
208}
209
210static void nft_connlimit_destroy(const struct nft_ctx *ctx,
211 const struct nft_expr *expr)
212{
213 struct nft_connlimit *priv = nft_expr_priv(expr);
214
215 nft_connlimit_do_destroy(ctx, priv);
216}
217
218static int nft_connlimit_clone(struct nft_expr *dst, const struct nft_expr *src, gfp_t gfp)
219{
220 struct nft_connlimit *priv_dst = nft_expr_priv(dst);
221 struct nft_connlimit *priv_src = nft_expr_priv(src);
222
223 priv_dst->list = kmalloc(sizeof(*priv_dst->list), gfp);
224 if (!priv_dst->list)
225 return -ENOMEM;
226
227 nf_conncount_list_init(priv_dst->list);
228 priv_dst->limit = priv_src->limit;
229 priv_dst->invert = priv_src->invert;
230
231 return 0;
232}
233
234static void nft_connlimit_destroy_clone(const struct nft_ctx *ctx,
235 const struct nft_expr *expr)
236{
237 struct nft_connlimit *priv = nft_expr_priv(expr);
238
239 nf_conncount_cache_free(priv->list);
240 kfree(priv->list);
241}
242
243static bool nft_connlimit_gc(struct net *net, const struct nft_expr *expr)
244{
245 struct nft_connlimit *priv = nft_expr_priv(expr);
246
247 return nf_conncount_gc_list(net, priv->list);
248}
249
250static struct nft_expr_type nft_connlimit_type;
251static const struct nft_expr_ops nft_connlimit_ops = {
252 .type = &nft_connlimit_type,
253 .size = NFT_EXPR_SIZE(sizeof(struct nft_connlimit)),
254 .eval = nft_connlimit_eval,
255 .init = nft_connlimit_init,
256 .destroy = nft_connlimit_destroy,
257 .clone = nft_connlimit_clone,
258 .destroy_clone = nft_connlimit_destroy_clone,
259 .dump = nft_connlimit_dump,
260 .gc = nft_connlimit_gc,
261 .reduce = NFT_REDUCE_READONLY,
262};
263
264static struct nft_expr_type nft_connlimit_type __read_mostly = {
265 .name = "connlimit",
266 .ops = &nft_connlimit_ops,
267 .policy = nft_connlimit_policy,
268 .maxattr = NFTA_CONNLIMIT_MAX,
269 .flags = NFT_EXPR_STATEFUL | NFT_EXPR_GC,
270 .owner = THIS_MODULE,
271};
272
273static int __init nft_connlimit_module_init(void)
274{
275 int err;
276
277 err = nft_register_obj(&nft_connlimit_obj_type);
278 if (err < 0)
279 return err;
280
281 err = nft_register_expr(&nft_connlimit_type);
282 if (err < 0)
283 goto err1;
284
285 return 0;
286err1:
287 nft_unregister_obj(&nft_connlimit_obj_type);
288 return err;
289}
290
291static void __exit nft_connlimit_module_exit(void)
292{
293 nft_unregister_expr(&nft_connlimit_type);
294 nft_unregister_obj(&nft_connlimit_obj_type);
295}
296
297module_init(nft_connlimit_module_init);
298module_exit(nft_connlimit_module_exit);
299
300MODULE_LICENSE("GPL");
301MODULE_AUTHOR("Pablo Neira Ayuso");
302MODULE_ALIAS_NFT_EXPR("connlimit");
303MODULE_ALIAS_NFT_OBJ(NFT_OBJECT_CONNLIMIT);
304MODULE_DESCRIPTION("nftables connlimit rule support");