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