1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2015 Patrick McHardy <kaber@trash.net> |
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; |
19 | u8 sreg_key; |
20 | u8 sreg_data; |
21 | bool invert; |
22 | bool expr; |
23 | u8 num_exprs; |
24 | u64 timeout; |
25 | struct nft_expr *expr_array[NFT_SET_EXPR_MAX]; |
26 | struct nft_set_binding binding; |
27 | }; |
28 | |
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(dst: expr, src: 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 | |
47 | static struct nft_elem_priv *nft_dynset_new(struct nft_set *set, |
48 | const struct nft_expr *expr, |
49 | struct nft_regs *regs) |
50 | { |
51 | const struct nft_dynset *priv = nft_expr_priv(expr); |
52 | struct nft_set_ext *ext; |
53 | void *elem_priv; |
54 | u64 timeout; |
55 | |
56 | if (!atomic_add_unless(v: &set->nelems, a: 1, u: set->size)) |
57 | return NULL; |
58 | |
59 | timeout = priv->timeout ? : set->timeout; |
60 | elem_priv = nft_set_elem_init(set, tmpl: &priv->tmpl, |
61 | key: ®s->data[priv->sreg_key], NULL, |
62 | data: ®s->data[priv->sreg_data], |
63 | timeout, expiration: 0, GFP_ATOMIC); |
64 | if (IS_ERR(ptr: elem_priv)) |
65 | goto err1; |
66 | |
67 | ext = nft_set_elem_ext(set, elem_priv); |
68 | if (priv->num_exprs && nft_dynset_expr_setup(priv, ext) < 0) |
69 | goto err2; |
70 | |
71 | return elem_priv; |
72 | |
73 | err2: |
74 | nft_set_elem_destroy(set, elem_priv, destroy_expr: false); |
75 | err1: |
76 | if (set->size) |
77 | atomic_dec(v: &set->nelems); |
78 | return NULL; |
79 | } |
80 | |
81 | void nft_dynset_eval(const struct nft_expr *expr, |
82 | struct nft_regs *regs, const struct nft_pktinfo *pkt) |
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 | |
89 | if (priv->op == NFT_DYNSET_OP_DELETE) { |
90 | set->ops->delete(set, ®s->data[priv->sreg_key]); |
91 | return; |
92 | } |
93 | |
94 | if (set->ops->update(set, ®s->data[priv->sreg_key], nft_dynset_new, |
95 | expr, regs, &ext)) { |
96 | if (priv->op == NFT_DYNSET_OP_UPDATE && |
97 | nft_set_ext_exists(ext, id: NFT_SET_EXT_EXPIRATION)) { |
98 | timeout = priv->timeout ? : set->timeout; |
99 | *nft_set_ext_expiration(ext) = get_jiffies_64() + timeout; |
100 | } |
101 | |
102 | nft_set_elem_update_expr(ext, regs, pkt); |
103 | |
104 | if (priv->invert) |
105 | regs->verdict.code = NFT_BREAK; |
106 | return; |
107 | } |
108 | |
109 | if (!priv->invert) |
110 | regs->verdict.code = NFT_BREAK; |
111 | } |
112 | |
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(tmpl: &priv->tmpl, id: NFT_SET_EXT_EXPRESSIONS, |
122 | len: 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(ptr: 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(error: err); |
146 | } |
147 | |
148 | static const struct nla_policy nft_dynset_policy[NFTA_DYNSET_MAX + 1] = { |
149 | [NFTA_DYNSET_SET_NAME] = { .type = NLA_STRING, |
150 | .len = NFT_SET_MAXNAMELEN - 1 }, |
151 | [NFTA_DYNSET_SET_ID] = { .type = NLA_U32 }, |
152 | [NFTA_DYNSET_OP] = NLA_POLICY_MAX(NLA_BE32, 255), |
153 | [NFTA_DYNSET_SREG_KEY] = { .type = NLA_U32 }, |
154 | [NFTA_DYNSET_SREG_DATA] = { .type = NLA_U32 }, |
155 | [NFTA_DYNSET_TIMEOUT] = { .type = NLA_U64 }, |
156 | [NFTA_DYNSET_EXPR] = { .type = NLA_NESTED }, |
157 | [NFTA_DYNSET_FLAGS] = { .type = NLA_U32 }, |
158 | [NFTA_DYNSET_EXPRESSIONS] = { .type = NLA_NESTED }, |
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 | { |
165 | struct nftables_pernet *nft_net = nft_pernet(net: ctx->net); |
166 | struct nft_dynset *priv = nft_expr_priv(expr); |
167 | u8 genmask = nft_genmask_next(net: ctx->net); |
168 | struct nft_set *set; |
169 | u64 timeout; |
170 | int err, i; |
171 | |
172 | lockdep_assert_held(&nft_net->commit_mutex); |
173 | |
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 | |
179 | if (tb[NFTA_DYNSET_FLAGS]) { |
180 | u32 flags = ntohl(nla_get_be32(tb[NFTA_DYNSET_FLAGS])); |
181 | if (flags & ~(NFT_DYNSET_F_INV | NFT_DYNSET_F_EXPR)) |
182 | return -EOPNOTSUPP; |
183 | if (flags & NFT_DYNSET_F_INV) |
184 | priv->invert = true; |
185 | if (flags & NFT_DYNSET_F_EXPR) |
186 | priv->expr = true; |
187 | } |
188 | |
189 | set = nft_set_lookup_global(net: ctx->net, table: ctx->table, |
190 | nla_set_name: tb[NFTA_DYNSET_SET_NAME], |
191 | nla_set_id: tb[NFTA_DYNSET_SET_ID], genmask); |
192 | if (IS_ERR(ptr: set)) |
193 | return PTR_ERR(ptr: set); |
194 | |
195 | if (set->flags & NFT_SET_OBJECT) |
196 | return -EOPNOTSUPP; |
197 | |
198 | if (set->ops->update == NULL) |
199 | return -EOPNOTSUPP; |
200 | |
201 | if (set->flags & NFT_SET_CONSTANT) |
202 | return -EBUSY; |
203 | |
204 | priv->op = ntohl(nla_get_be32(tb[NFTA_DYNSET_OP])); |
205 | if (priv->op > NFT_DYNSET_OP_DELETE) |
206 | return -EOPNOTSUPP; |
207 | |
208 | timeout = 0; |
209 | if (tb[NFTA_DYNSET_TIMEOUT] != NULL) { |
210 | if (!(set->flags & NFT_SET_TIMEOUT)) |
211 | return -EOPNOTSUPP; |
212 | |
213 | err = nf_msecs_to_jiffies64(nla: tb[NFTA_DYNSET_TIMEOUT], result: &timeout); |
214 | if (err) |
215 | return err; |
216 | } |
217 | |
218 | err = nft_parse_register_load(attr: tb[NFTA_DYNSET_SREG_KEY], sreg: &priv->sreg_key, |
219 | len: set->klen); |
220 | if (err < 0) |
221 | return err; |
222 | |
223 | if (tb[NFTA_DYNSET_SREG_DATA] != NULL) { |
224 | if (!(set->flags & NFT_SET_MAP)) |
225 | return -EOPNOTSUPP; |
226 | if (set->dtype == NFT_DATA_VERDICT) |
227 | return -EOPNOTSUPP; |
228 | |
229 | err = nft_parse_register_load(attr: tb[NFTA_DYNSET_SREG_DATA], |
230 | sreg: &priv->sreg_data, len: set->dlen); |
231 | if (err < 0) |
232 | return err; |
233 | } else if (set->flags & NFT_SET_MAP) |
234 | return -EINVAL; |
235 | |
236 | if ((tb[NFTA_DYNSET_EXPR] || tb[NFTA_DYNSET_EXPRESSIONS]) && |
237 | !(set->flags & NFT_SET_EVAL)) |
238 | return -EINVAL; |
239 | |
240 | if (tb[NFTA_DYNSET_EXPR]) { |
241 | struct nft_expr *dynset_expr; |
242 | |
243 | dynset_expr = nft_dynset_expr_alloc(ctx, set, |
244 | attr: tb[NFTA_DYNSET_EXPR], pos: 0); |
245 | if (IS_ERR(ptr: dynset_expr)) |
246 | return PTR_ERR(ptr: dynset_expr); |
247 | |
248 | priv->num_exprs++; |
249 | priv->expr_array[0] = dynset_expr; |
250 | |
251 | if (set->num_exprs > 1 || |
252 | (set->num_exprs == 1 && |
253 | dynset_expr->ops != set->exprs[0]->ops)) { |
254 | err = -EOPNOTSUPP; |
255 | goto err_expr_free; |
256 | } |
257 | } else if (tb[NFTA_DYNSET_EXPRESSIONS]) { |
258 | struct nft_expr *dynset_expr; |
259 | struct nlattr *tmp; |
260 | int left; |
261 | |
262 | if (!priv->expr) |
263 | return -EINVAL; |
264 | |
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(nla: tmp) != NFTA_LIST_ELEM) { |
272 | err = -EINVAL; |
273 | goto err_expr_free; |
274 | } |
275 | dynset_expr = nft_dynset_expr_alloc(ctx, set, attr: tmp, pos: i); |
276 | if (IS_ERR(ptr: dynset_expr)) { |
277 | err = PTR_ERR(ptr: dynset_expr); |
278 | goto err_expr_free; |
279 | } |
280 | priv->expr_array[i] = dynset_expr; |
281 | priv->num_exprs++; |
282 | |
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 | } |
292 | } |
293 | i++; |
294 | } |
295 | if (set->num_exprs && set->num_exprs != i) { |
296 | err = -EOPNOTSUPP; |
297 | goto err_expr_free; |
298 | } |
299 | } else if (set->num_exprs > 0) { |
300 | err = nft_set_elem_expr_clone(ctx, set, expr_array: priv->expr_array); |
301 | if (err < 0) |
302 | return err; |
303 | |
304 | priv->num_exprs = set->num_exprs; |
305 | } |
306 | |
307 | nft_set_ext_prepare(tmpl: &priv->tmpl); |
308 | nft_set_ext_add_length(tmpl: &priv->tmpl, id: NFT_SET_EXT_KEY, len: set->klen); |
309 | if (set->flags & NFT_SET_MAP) |
310 | nft_set_ext_add_length(tmpl: &priv->tmpl, id: NFT_SET_EXT_DATA, len: set->dlen); |
311 | |
312 | if (priv->num_exprs) |
313 | nft_dynset_ext_add_expr(priv); |
314 | |
315 | if (set->flags & NFT_SET_TIMEOUT) { |
316 | if (timeout || set->timeout) { |
317 | nft_set_ext_add(tmpl: &priv->tmpl, id: NFT_SET_EXT_TIMEOUT); |
318 | nft_set_ext_add(tmpl: &priv->tmpl, id: NFT_SET_EXT_EXPIRATION); |
319 | } |
320 | } |
321 | |
322 | priv->timeout = timeout; |
323 | |
324 | err = nf_tables_bind_set(ctx, set, binding: &priv->binding); |
325 | if (err < 0) |
326 | goto err_expr_free; |
327 | |
328 | if (set->size == 0) |
329 | set->size = 0xffff; |
330 | |
331 | priv->set = set; |
332 | return 0; |
333 | |
334 | err_expr_free: |
335 | for (i = 0; i < priv->num_exprs; i++) |
336 | nft_expr_destroy(ctx, expr: priv->expr_array[i]); |
337 | return err; |
338 | } |
339 | |
340 | static void nft_dynset_deactivate(const struct nft_ctx *ctx, |
341 | const struct nft_expr *expr, |
342 | enum nft_trans_phase phase) |
343 | { |
344 | struct nft_dynset *priv = nft_expr_priv(expr); |
345 | |
346 | nf_tables_deactivate_set(ctx, set: priv->set, binding: &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); |
353 | |
354 | nf_tables_activate_set(ctx, set: priv->set); |
355 | } |
356 | |
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); |
361 | int i; |
362 | |
363 | for (i = 0; i < priv->num_exprs; i++) |
364 | nft_expr_destroy(ctx, expr: priv->expr_array[i]); |
365 | |
366 | nf_tables_destroy_set(ctx, set: priv->set); |
367 | } |
368 | |
369 | static int nft_dynset_dump(struct sk_buff *skb, |
370 | const struct nft_expr *expr, bool reset) |
371 | { |
372 | const struct nft_dynset *priv = nft_expr_priv(expr); |
373 | u32 flags = priv->invert ? NFT_DYNSET_F_INV : 0; |
374 | int i; |
375 | |
376 | if (nft_dump_register(skb, attr: NFTA_DYNSET_SREG_KEY, reg: priv->sreg_key)) |
377 | goto nla_put_failure; |
378 | if (priv->set->flags & NFT_SET_MAP && |
379 | nft_dump_register(skb, attr: NFTA_DYNSET_SREG_DATA, reg: priv->sreg_data)) |
380 | goto nla_put_failure; |
381 | if (nla_put_be32(skb, attrtype: NFTA_DYNSET_OP, htonl(priv->op))) |
382 | goto nla_put_failure; |
383 | if (nla_put_string(skb, attrtype: NFTA_DYNSET_SET_NAME, str: priv->set->name)) |
384 | goto nla_put_failure; |
385 | if (nla_put_be64(skb, attrtype: NFTA_DYNSET_TIMEOUT, |
386 | value: nf_jiffies64_to_msecs(input: priv->timeout), |
387 | padattr: NFTA_DYNSET_PAD)) |
388 | goto nla_put_failure; |
389 | if (priv->set->num_exprs == 0) { |
390 | if (priv->num_exprs == 1) { |
391 | if (nft_expr_dump(skb, attr: NFTA_DYNSET_EXPR, |
392 | expr: priv->expr_array[0], reset)) |
393 | goto nla_put_failure; |
394 | } else if (priv->num_exprs > 1) { |
395 | struct nlattr *nest; |
396 | |
397 | nest = nla_nest_start_noflag(skb, attrtype: 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, attr: NFTA_LIST_ELEM, |
403 | expr: priv->expr_array[i], reset)) |
404 | goto nla_put_failure; |
405 | } |
406 | nla_nest_end(skb, start: nest); |
407 | } |
408 | } |
409 | if (nla_put_be32(skb, attrtype: NFTA_DYNSET_FLAGS, htonl(flags))) |
410 | goto nla_put_failure; |
411 | return 0; |
412 | |
413 | nla_put_failure: |
414 | return -1; |
415 | } |
416 | |
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, |
423 | .activate = nft_dynset_activate, |
424 | .deactivate = nft_dynset_deactivate, |
425 | .dump = nft_dynset_dump, |
426 | .reduce = NFT_REDUCE_READONLY, |
427 | }; |
428 | |
429 | struct nft_expr_type nft_dynset_type __read_mostly = { |
430 | .name = "dynset" , |
431 | .ops = &nft_dynset_ops, |
432 | .policy = nft_dynset_policy, |
433 | .maxattr = NFTA_DYNSET_MAX, |
434 | .owner = THIS_MODULE, |
435 | }; |
436 | |