Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
22fe54d5 PM |
2 | /* |
3 | * Copyright (c) 2015 Patrick McHardy <kaber@trash.net> | |
22fe54d5 PM |
4 | */ |
5 | ||
6 | #include <linux/kernel.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/netlink.h> | |
10 | #include <linux/netfilter.h> | |
11 | #include <linux/netfilter/nf_tables.h> | |
12 | #include <net/netfilter/nf_tables.h> | |
13 | #include <net/netfilter/nf_tables_core.h> | |
14 | ||
15 | struct nft_dynset { | |
16 | struct nft_set *set; | |
17 | struct nft_set_ext_tmpl tmpl; | |
18 | enum nft_dynset_ops op:8; | |
4f16d25c PNA |
19 | u8 sreg_key; |
20 | u8 sreg_data; | |
dbd2be06 | 21 | bool invert; |
b4e70d8d | 22 | bool expr; |
563125a7 | 23 | u8 num_exprs; |
22fe54d5 | 24 | u64 timeout; |
563125a7 | 25 | struct nft_expr *expr_array[NFT_SET_EXPR_MAX]; |
22fe54d5 PM |
26 | struct nft_set_binding binding; |
27 | }; | |
28 | ||
563125a7 PNA |
29 | static int nft_dynset_expr_setup(const struct nft_dynset *priv, |
30 | const struct nft_set_ext *ext) | |
31 | { | |
32 | struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext); | |
33 | struct nft_expr *expr; | |
34 | int i; | |
35 | ||
36 | for (i = 0; i < priv->num_exprs; i++) { | |
37 | expr = nft_setelem_expr_at(elem_expr, elem_expr->size); | |
38 | if (nft_expr_clone(expr, priv->expr_array[i]) < 0) | |
39 | return -1; | |
40 | ||
41 | elem_expr->size += priv->expr_array[i]->ops->size; | |
42 | } | |
43 | ||
44 | return 0; | |
45 | } | |
46 | ||
22fe54d5 | 47 | static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr, |
a55e22e9 | 48 | struct nft_regs *regs) |
22fe54d5 PM |
49 | { |
50 | const struct nft_dynset *priv = nft_expr_priv(expr); | |
3e135cd4 | 51 | struct nft_set_ext *ext; |
22fe54d5 PM |
52 | u64 timeout; |
53 | void *elem; | |
54 | ||
99a0efbe | 55 | if (!atomic_add_unless(&set->nelems, 1, set->size)) |
22fe54d5 PM |
56 | return NULL; |
57 | ||
58 | timeout = priv->timeout ? : set->timeout; | |
59 | elem = nft_set_elem_init(set, &priv->tmpl, | |
7b225d0b | 60 | ®s->data[priv->sreg_key], NULL, |
a55e22e9 | 61 | ®s->data[priv->sreg_data], |
79ebb5bb | 62 | timeout, 0, GFP_ATOMIC); |
61f9e292 LZ |
63 | if (elem == NULL) |
64 | goto err1; | |
3e135cd4 PM |
65 | |
66 | ext = nft_set_elem_ext(set, elem); | |
563125a7 | 67 | if (priv->num_exprs && nft_dynset_expr_setup(priv, ext) < 0) |
61f9e292 | 68 | goto err2; |
3e135cd4 | 69 | |
22fe54d5 | 70 | return elem; |
61f9e292 LZ |
71 | |
72 | err2: | |
73 | nft_set_elem_destroy(set, elem, false); | |
74 | err1: | |
75 | if (set->size) | |
76 | atomic_dec(&set->nelems); | |
77 | return NULL; | |
22fe54d5 PM |
78 | } |
79 | ||
10870dd8 FW |
80 | void nft_dynset_eval(const struct nft_expr *expr, |
81 | struct nft_regs *regs, const struct nft_pktinfo *pkt) | |
22fe54d5 PM |
82 | { |
83 | const struct nft_dynset *priv = nft_expr_priv(expr); | |
84 | struct nft_set *set = priv->set; | |
85 | const struct nft_set_ext *ext; | |
86 | u64 timeout; | |
87 | ||
d0a8d877 AJ |
88 | if (priv->op == NFT_DYNSET_OP_DELETE) { |
89 | set->ops->delete(set, ®s->data[priv->sreg_key]); | |
90 | return; | |
91 | } | |
92 | ||
a55e22e9 PM |
93 | if (set->ops->update(set, ®s->data[priv->sreg_key], nft_dynset_new, |
94 | expr, regs, &ext)) { | |
22fe54d5 PM |
95 | if (priv->op == NFT_DYNSET_OP_UPDATE && |
96 | nft_set_ext_exists(ext, NFT_SET_EXT_EXPIRATION)) { | |
97 | timeout = priv->timeout ? : set->timeout; | |
b13468dc | 98 | *nft_set_ext_expiration(ext) = get_jiffies_64() + timeout; |
277a2928 | 99 | } |
22fe54d5 | 100 | |
76adfafe | 101 | nft_set_elem_update_expr(ext, regs, pkt); |
dbd2be06 PNA |
102 | |
103 | if (priv->invert) | |
104 | regs->verdict.code = NFT_BREAK; | |
3e135cd4 PM |
105 | return; |
106 | } | |
277a2928 | 107 | |
dbd2be06 PNA |
108 | if (!priv->invert) |
109 | regs->verdict.code = NFT_BREAK; | |
22fe54d5 PM |
110 | } |
111 | ||
563125a7 PNA |
112 | static void nft_dynset_ext_add_expr(struct nft_dynset *priv) |
113 | { | |
114 | u8 size = 0; | |
115 | int i; | |
116 | ||
117 | for (i = 0; i < priv->num_exprs; i++) | |
118 | size += priv->expr_array[i]->ops->size; | |
119 | ||
120 | nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_EXPRESSIONS, | |
121 | sizeof(struct nft_set_elem_expr) + size); | |
122 | } | |
123 | ||
124 | static struct nft_expr * | |
125 | nft_dynset_expr_alloc(const struct nft_ctx *ctx, const struct nft_set *set, | |
126 | const struct nlattr *attr, int pos) | |
127 | { | |
128 | struct nft_expr *expr; | |
129 | int err; | |
130 | ||
131 | expr = nft_set_elem_expr_alloc(ctx, set, attr); | |
132 | if (IS_ERR(expr)) | |
133 | return expr; | |
134 | ||
135 | if (set->exprs[pos] && set->exprs[pos]->ops != expr->ops) { | |
136 | err = -EOPNOTSUPP; | |
137 | goto err_dynset_expr; | |
138 | } | |
139 | ||
140 | return expr; | |
141 | ||
142 | err_dynset_expr: | |
143 | nft_expr_destroy(ctx, expr); | |
144 | return ERR_PTR(err); | |
145 | } | |
146 | ||
22fe54d5 | 147 | static const struct nla_policy nft_dynset_policy[NFTA_DYNSET_MAX + 1] = { |
b2fbd044 LZ |
148 | [NFTA_DYNSET_SET_NAME] = { .type = NLA_STRING, |
149 | .len = NFT_SET_MAXNAMELEN - 1 }, | |
22fe54d5 PM |
150 | [NFTA_DYNSET_SET_ID] = { .type = NLA_U32 }, |
151 | [NFTA_DYNSET_OP] = { .type = NLA_U32 }, | |
152 | [NFTA_DYNSET_SREG_KEY] = { .type = NLA_U32 }, | |
153 | [NFTA_DYNSET_SREG_DATA] = { .type = NLA_U32 }, | |
154 | [NFTA_DYNSET_TIMEOUT] = { .type = NLA_U64 }, | |
3e135cd4 | 155 | [NFTA_DYNSET_EXPR] = { .type = NLA_NESTED }, |
dbd2be06 | 156 | [NFTA_DYNSET_FLAGS] = { .type = NLA_U32 }, |
48b0ae04 | 157 | [NFTA_DYNSET_EXPRESSIONS] = { .type = NLA_NESTED }, |
22fe54d5 PM |
158 | }; |
159 | ||
160 | static int nft_dynset_init(const struct nft_ctx *ctx, | |
161 | const struct nft_expr *expr, | |
162 | const struct nlattr * const tb[]) | |
163 | { | |
164 | struct nft_dynset *priv = nft_expr_priv(expr); | |
37a9cc52 | 165 | u8 genmask = nft_genmask_next(ctx->net); |
22fe54d5 PM |
166 | struct nft_set *set; |
167 | u64 timeout; | |
563125a7 | 168 | int err, i; |
22fe54d5 | 169 | |
f102d66b FW |
170 | lockdep_assert_held(&ctx->net->nft.commit_mutex); |
171 | ||
22fe54d5 PM |
172 | if (tb[NFTA_DYNSET_SET_NAME] == NULL || |
173 | tb[NFTA_DYNSET_OP] == NULL || | |
174 | tb[NFTA_DYNSET_SREG_KEY] == NULL) | |
175 | return -EINVAL; | |
176 | ||
dbd2be06 PNA |
177 | if (tb[NFTA_DYNSET_FLAGS]) { |
178 | u32 flags = ntohl(nla_get_be32(tb[NFTA_DYNSET_FLAGS])); | |
b4e70d8d | 179 | if (flags & ~(NFT_DYNSET_F_INV | NFT_DYNSET_F_EXPR)) |
95cd4bca | 180 | return -EOPNOTSUPP; |
dbd2be06 PNA |
181 | if (flags & NFT_DYNSET_F_INV) |
182 | priv->invert = true; | |
b4e70d8d PNA |
183 | if (flags & NFT_DYNSET_F_EXPR) |
184 | priv->expr = true; | |
dbd2be06 PNA |
185 | } |
186 | ||
10659cba PNA |
187 | set = nft_set_lookup_global(ctx->net, ctx->table, |
188 | tb[NFTA_DYNSET_SET_NAME], | |
189 | tb[NFTA_DYNSET_SET_ID], genmask); | |
c7a72e3f PNA |
190 | if (IS_ERR(set)) |
191 | return PTR_ERR(set); | |
22fe54d5 | 192 | |
bb6a6e8e LZ |
193 | if (set->ops->update == NULL) |
194 | return -EOPNOTSUPP; | |
195 | ||
22fe54d5 PM |
196 | if (set->flags & NFT_SET_CONSTANT) |
197 | return -EBUSY; | |
198 | ||
199 | priv->op = ntohl(nla_get_be32(tb[NFTA_DYNSET_OP])); | |
200 | switch (priv->op) { | |
201 | case NFT_DYNSET_OP_ADD: | |
d0a8d877 | 202 | case NFT_DYNSET_OP_DELETE: |
22fe54d5 PM |
203 | break; |
204 | case NFT_DYNSET_OP_UPDATE: | |
205 | if (!(set->flags & NFT_SET_TIMEOUT)) | |
206 | return -EOPNOTSUPP; | |
207 | break; | |
208 | default: | |
209 | return -EOPNOTSUPP; | |
210 | } | |
211 | ||
212 | timeout = 0; | |
213 | if (tb[NFTA_DYNSET_TIMEOUT] != NULL) { | |
214 | if (!(set->flags & NFT_SET_TIMEOUT)) | |
95cd4bca | 215 | return -EOPNOTSUPP; |
917d80d3 PNA |
216 | |
217 | err = nf_msecs_to_jiffies64(tb[NFTA_DYNSET_TIMEOUT], &timeout); | |
218 | if (err) | |
219 | return err; | |
22fe54d5 PM |
220 | } |
221 | ||
4f16d25c PNA |
222 | err = nft_parse_register_load(tb[NFTA_DYNSET_SREG_KEY], &priv->sreg_key, |
223 | set->klen); | |
22fe54d5 PM |
224 | if (err < 0) |
225 | return err; | |
226 | ||
227 | if (tb[NFTA_DYNSET_SREG_DATA] != NULL) { | |
228 | if (!(set->flags & NFT_SET_MAP)) | |
95cd4bca | 229 | return -EOPNOTSUPP; |
22fe54d5 PM |
230 | if (set->dtype == NFT_DATA_VERDICT) |
231 | return -EOPNOTSUPP; | |
232 | ||
4f16d25c PNA |
233 | err = nft_parse_register_load(tb[NFTA_DYNSET_SREG_DATA], |
234 | &priv->sreg_data, set->dlen); | |
22fe54d5 PM |
235 | if (err < 0) |
236 | return err; | |
237 | } else if (set->flags & NFT_SET_MAP) | |
238 | return -EINVAL; | |
239 | ||
48b0ae04 PNA |
240 | if ((tb[NFTA_DYNSET_EXPR] || tb[NFTA_DYNSET_EXPRESSIONS]) && |
241 | !(set->flags & NFT_SET_EVAL)) | |
242 | return -EINVAL; | |
243 | ||
563125a7 PNA |
244 | if (tb[NFTA_DYNSET_EXPR]) { |
245 | struct nft_expr *dynset_expr; | |
246 | ||
563125a7 PNA |
247 | dynset_expr = nft_dynset_expr_alloc(ctx, set, |
248 | tb[NFTA_DYNSET_EXPR], 0); | |
249 | if (IS_ERR(dynset_expr)) | |
250 | return PTR_ERR(dynset_expr); | |
3e135cd4 | 251 | |
563125a7 PNA |
252 | priv->num_exprs++; |
253 | priv->expr_array[0] = dynset_expr; | |
8548bde9 | 254 | |
563125a7 PNA |
255 | if (set->num_exprs > 1 || |
256 | (set->num_exprs == 1 && | |
257 | dynset_expr->ops != set->exprs[0]->ops)) { | |
8548bde9 PNA |
258 | err = -EOPNOTSUPP; |
259 | goto err_expr_free; | |
260 | } | |
48b0ae04 PNA |
261 | } else if (tb[NFTA_DYNSET_EXPRESSIONS]) { |
262 | struct nft_expr *dynset_expr; | |
263 | struct nlattr *tmp; | |
264 | int left; | |
265 | ||
b4e70d8d PNA |
266 | if (!priv->expr) |
267 | return -EINVAL; | |
268 | ||
48b0ae04 PNA |
269 | i = 0; |
270 | nla_for_each_nested(tmp, tb[NFTA_DYNSET_EXPRESSIONS], left) { | |
271 | if (i == NFT_SET_EXPR_MAX) { | |
272 | err = -E2BIG; | |
273 | goto err_expr_free; | |
274 | } | |
275 | if (nla_type(tmp) != NFTA_LIST_ELEM) { | |
276 | err = -EINVAL; | |
277 | goto err_expr_free; | |
278 | } | |
279 | dynset_expr = nft_dynset_expr_alloc(ctx, set, tmp, i); | |
280 | if (IS_ERR(dynset_expr)) { | |
281 | err = PTR_ERR(dynset_expr); | |
282 | goto err_expr_free; | |
283 | } | |
284 | priv->expr_array[i] = dynset_expr; | |
285 | priv->num_exprs++; | |
286 | ||
287 | if (set->num_exprs && | |
288 | dynset_expr->ops != set->exprs[i]->ops) { | |
289 | err = -EOPNOTSUPP; | |
290 | goto err_expr_free; | |
291 | } | |
292 | i++; | |
293 | } | |
294 | if (set->num_exprs && set->num_exprs != i) { | |
8548bde9 PNA |
295 | err = -EOPNOTSUPP; |
296 | goto err_expr_free; | |
297 | } | |
fca05d4d PNA |
298 | } else if (set->num_exprs > 0) { |
299 | err = nft_set_elem_expr_clone(ctx, set, priv->expr_array); | |
300 | if (err < 0) | |
301 | return err; | |
302 | ||
303 | priv->num_exprs = set->num_exprs; | |
215a31f1 | 304 | } |
3e135cd4 | 305 | |
22fe54d5 PM |
306 | nft_set_ext_prepare(&priv->tmpl); |
307 | nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_KEY, set->klen); | |
308 | if (set->flags & NFT_SET_MAP) | |
309 | nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_DATA, set->dlen); | |
563125a7 PNA |
310 | |
311 | if (priv->num_exprs) | |
312 | nft_dynset_ext_add_expr(priv); | |
313 | ||
22fe54d5 | 314 | if (set->flags & NFT_SET_TIMEOUT) { |
0c5b7a50 PNA |
315 | if (timeout || set->timeout) { |
316 | nft_set_ext_add(&priv->tmpl, NFT_SET_EXT_TIMEOUT); | |
22fe54d5 | 317 | nft_set_ext_add(&priv->tmpl, NFT_SET_EXT_EXPIRATION); |
0c5b7a50 | 318 | } |
22fe54d5 PM |
319 | } |
320 | ||
321 | priv->timeout = timeout; | |
322 | ||
323 | err = nf_tables_bind_set(ctx, set, &priv->binding); | |
324 | if (err < 0) | |
8548bde9 | 325 | goto err_expr_free; |
22fe54d5 | 326 | |
99a0efbe FW |
327 | if (set->size == 0) |
328 | set->size = 0xffff; | |
329 | ||
22fe54d5 PM |
330 | priv->set = set; |
331 | return 0; | |
3e135cd4 | 332 | |
8548bde9 | 333 | err_expr_free: |
563125a7 PNA |
334 | for (i = 0; i < priv->num_exprs; i++) |
335 | nft_expr_destroy(ctx, priv->expr_array[i]); | |
3e135cd4 | 336 | return err; |
22fe54d5 PM |
337 | } |
338 | ||
cd5125d8 | 339 | static void nft_dynset_deactivate(const struct nft_ctx *ctx, |
f6ac8585 PNA |
340 | const struct nft_expr *expr, |
341 | enum nft_trans_phase phase) | |
cd5125d8 FW |
342 | { |
343 | struct nft_dynset *priv = nft_expr_priv(expr); | |
344 | ||
273fe3f1 PNA |
345 | nf_tables_deactivate_set(ctx, priv->set, &priv->binding, phase); |
346 | } | |
347 | ||
348 | static void nft_dynset_activate(const struct nft_ctx *ctx, | |
349 | const struct nft_expr *expr) | |
350 | { | |
351 | struct nft_dynset *priv = nft_expr_priv(expr); | |
f6ac8585 | 352 | |
273fe3f1 | 353 | priv->set->use++; |
cd5125d8 FW |
354 | } |
355 | ||
22fe54d5 PM |
356 | static void nft_dynset_destroy(const struct nft_ctx *ctx, |
357 | const struct nft_expr *expr) | |
358 | { | |
359 | struct nft_dynset *priv = nft_expr_priv(expr); | |
563125a7 | 360 | int i; |
22fe54d5 | 361 | |
563125a7 PNA |
362 | for (i = 0; i < priv->num_exprs; i++) |
363 | nft_expr_destroy(ctx, priv->expr_array[i]); | |
cd5125d8 FW |
364 | |
365 | nf_tables_destroy_set(ctx, priv->set); | |
22fe54d5 PM |
366 | } |
367 | ||
368 | static int nft_dynset_dump(struct sk_buff *skb, const struct nft_expr *expr) | |
369 | { | |
370 | const struct nft_dynset *priv = nft_expr_priv(expr); | |
dbd2be06 | 371 | u32 flags = priv->invert ? NFT_DYNSET_F_INV : 0; |
48b0ae04 | 372 | int i; |
22fe54d5 | 373 | |
b1c96ed3 | 374 | if (nft_dump_register(skb, NFTA_DYNSET_SREG_KEY, priv->sreg_key)) |
22fe54d5 PM |
375 | goto nla_put_failure; |
376 | if (priv->set->flags & NFT_SET_MAP && | |
b1c96ed3 | 377 | nft_dump_register(skb, NFTA_DYNSET_SREG_DATA, priv->sreg_data)) |
22fe54d5 PM |
378 | goto nla_put_failure; |
379 | if (nla_put_be32(skb, NFTA_DYNSET_OP, htonl(priv->op))) | |
380 | goto nla_put_failure; | |
381 | if (nla_put_string(skb, NFTA_DYNSET_SET_NAME, priv->set->name)) | |
382 | goto nla_put_failure; | |
a8b1e36d | 383 | if (nla_put_be64(skb, NFTA_DYNSET_TIMEOUT, |
917d80d3 | 384 | nf_jiffies64_to_msecs(priv->timeout), |
b46f6ded | 385 | NFTA_DYNSET_PAD)) |
22fe54d5 | 386 | goto nla_put_failure; |
ce537996 PNA |
387 | if (priv->set->num_exprs == 0) { |
388 | if (priv->num_exprs == 1) { | |
389 | if (nft_expr_dump(skb, NFTA_DYNSET_EXPR, | |
390 | priv->expr_array[0])) | |
48b0ae04 | 391 | goto nla_put_failure; |
ce537996 PNA |
392 | } else if (priv->num_exprs > 1) { |
393 | struct nlattr *nest; | |
394 | ||
395 | nest = nla_nest_start_noflag(skb, NFTA_DYNSET_EXPRESSIONS); | |
396 | if (!nest) | |
397 | goto nla_put_failure; | |
398 | ||
399 | for (i = 0; i < priv->num_exprs; i++) { | |
400 | if (nft_expr_dump(skb, NFTA_LIST_ELEM, | |
401 | priv->expr_array[i])) | |
402 | goto nla_put_failure; | |
403 | } | |
404 | nla_nest_end(skb, nest); | |
48b0ae04 | 405 | } |
563125a7 | 406 | } |
dbd2be06 PNA |
407 | if (nla_put_be32(skb, NFTA_DYNSET_FLAGS, htonl(flags))) |
408 | goto nla_put_failure; | |
22fe54d5 PM |
409 | return 0; |
410 | ||
411 | nla_put_failure: | |
412 | return -1; | |
413 | } | |
414 | ||
22fe54d5 PM |
415 | static const struct nft_expr_ops nft_dynset_ops = { |
416 | .type = &nft_dynset_type, | |
417 | .size = NFT_EXPR_SIZE(sizeof(struct nft_dynset)), | |
418 | .eval = nft_dynset_eval, | |
419 | .init = nft_dynset_init, | |
420 | .destroy = nft_dynset_destroy, | |
273fe3f1 | 421 | .activate = nft_dynset_activate, |
cd5125d8 | 422 | .deactivate = nft_dynset_deactivate, |
22fe54d5 PM |
423 | .dump = nft_dynset_dump, |
424 | }; | |
425 | ||
4e24877e | 426 | struct nft_expr_type nft_dynset_type __read_mostly = { |
22fe54d5 PM |
427 | .name = "dynset", |
428 | .ops = &nft_dynset_ops, | |
429 | .policy = nft_dynset_policy, | |
430 | .maxattr = NFTA_DYNSET_MAX, | |
431 | .owner = THIS_MODULE, | |
432 | }; |