1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright (C) 2008-2013 Jozsef Kadlecsik <kadlec@netfilter.org> */ |
3 | |
4 | /* Kernel module implementing an IP set type: the list:set type */ |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/ip.h> |
8 | #include <linux/rculist.h> |
9 | #include <linux/skbuff.h> |
10 | #include <linux/errno.h> |
11 | |
12 | #include <linux/netfilter/ipset/ip_set.h> |
13 | #include <linux/netfilter/ipset/ip_set_list.h> |
14 | |
15 | #define IPSET_TYPE_REV_MIN 0 |
16 | /* 1 Counters support added */ |
17 | /* 2 Comments support added */ |
18 | #define IPSET_TYPE_REV_MAX 3 /* skbinfo support added */ |
19 | |
20 | MODULE_LICENSE("GPL" ); |
21 | MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@netfilter.org>" ); |
22 | IP_SET_MODULE_DESC("list:set" , IPSET_TYPE_REV_MIN, IPSET_TYPE_REV_MAX); |
23 | MODULE_ALIAS("ip_set_list:set" ); |
24 | |
25 | /* Member elements */ |
26 | struct set_elem { |
27 | struct rcu_head rcu; |
28 | struct list_head list; |
29 | struct ip_set *set; /* Sigh, in order to cleanup reference */ |
30 | ip_set_id_t id; |
31 | } __aligned(__alignof__(u64)); |
32 | |
33 | struct set_adt_elem { |
34 | ip_set_id_t id; |
35 | ip_set_id_t refid; |
36 | int before; |
37 | }; |
38 | |
39 | /* Type structure */ |
40 | struct list_set { |
41 | u32 size; /* size of set list array */ |
42 | struct timer_list gc; /* garbage collection */ |
43 | struct ip_set *set; /* attached to this ip_set */ |
44 | struct net *net; /* namespace */ |
45 | struct list_head members; /* the set members */ |
46 | }; |
47 | |
48 | static int |
49 | list_set_ktest(struct ip_set *set, const struct sk_buff *skb, |
50 | const struct xt_action_param *par, |
51 | struct ip_set_adt_opt *opt, const struct ip_set_ext *ext) |
52 | { |
53 | struct list_set *map = set->data; |
54 | struct ip_set_ext *mext = &opt->ext; |
55 | struct set_elem *e; |
56 | u32 flags = opt->cmdflags; |
57 | int ret; |
58 | |
59 | /* Don't lookup sub-counters at all */ |
60 | opt->cmdflags &= ~IPSET_FLAG_MATCH_COUNTERS; |
61 | if (opt->cmdflags & IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE) |
62 | opt->cmdflags |= IPSET_FLAG_SKIP_COUNTER_UPDATE; |
63 | list_for_each_entry_rcu(e, &map->members, list) { |
64 | ret = ip_set_test(id: e->id, skb, par, opt); |
65 | if (ret <= 0) |
66 | continue; |
67 | if (ip_set_match_extensions(set, ext, mext, flags, data: e)) |
68 | return 1; |
69 | } |
70 | return 0; |
71 | } |
72 | |
73 | static int |
74 | list_set_kadd(struct ip_set *set, const struct sk_buff *skb, |
75 | const struct xt_action_param *par, |
76 | struct ip_set_adt_opt *opt, const struct ip_set_ext *ext) |
77 | { |
78 | struct list_set *map = set->data; |
79 | struct set_elem *e; |
80 | int ret; |
81 | |
82 | list_for_each_entry(e, &map->members, list) { |
83 | if (SET_WITH_TIMEOUT(set) && |
84 | ip_set_timeout_expired(ext_timeout(e, set))) |
85 | continue; |
86 | ret = ip_set_add(id: e->id, skb, par, opt); |
87 | if (ret == 0) |
88 | return ret; |
89 | } |
90 | return 0; |
91 | } |
92 | |
93 | static int |
94 | list_set_kdel(struct ip_set *set, const struct sk_buff *skb, |
95 | const struct xt_action_param *par, |
96 | struct ip_set_adt_opt *opt, const struct ip_set_ext *ext) |
97 | { |
98 | struct list_set *map = set->data; |
99 | struct set_elem *e; |
100 | int ret; |
101 | |
102 | list_for_each_entry(e, &map->members, list) { |
103 | if (SET_WITH_TIMEOUT(set) && |
104 | ip_set_timeout_expired(ext_timeout(e, set))) |
105 | continue; |
106 | ret = ip_set_del(id: e->id, skb, par, opt); |
107 | if (ret == 0) |
108 | return ret; |
109 | } |
110 | return 0; |
111 | } |
112 | |
113 | static int |
114 | list_set_kadt(struct ip_set *set, const struct sk_buff *skb, |
115 | const struct xt_action_param *par, |
116 | enum ipset_adt adt, struct ip_set_adt_opt *opt) |
117 | { |
118 | struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); |
119 | int ret = -EINVAL; |
120 | |
121 | rcu_read_lock(); |
122 | switch (adt) { |
123 | case IPSET_TEST: |
124 | ret = list_set_ktest(set, skb, par, opt, ext: &ext); |
125 | break; |
126 | case IPSET_ADD: |
127 | ret = list_set_kadd(set, skb, par, opt, ext: &ext); |
128 | break; |
129 | case IPSET_DEL: |
130 | ret = list_set_kdel(set, skb, par, opt, ext: &ext); |
131 | break; |
132 | default: |
133 | break; |
134 | } |
135 | rcu_read_unlock(); |
136 | |
137 | return ret; |
138 | } |
139 | |
140 | /* Userspace interfaces: we are protected by the nfnl mutex */ |
141 | |
142 | static void |
143 | __list_set_del_rcu(struct rcu_head * rcu) |
144 | { |
145 | struct set_elem *e = container_of(rcu, struct set_elem, rcu); |
146 | struct ip_set *set = e->set; |
147 | |
148 | ip_set_ext_destroy(set, data: e); |
149 | kfree(objp: e); |
150 | } |
151 | |
152 | static void |
153 | list_set_del(struct ip_set *set, struct set_elem *e) |
154 | { |
155 | struct list_set *map = set->data; |
156 | |
157 | set->elements--; |
158 | list_del_rcu(entry: &e->list); |
159 | ip_set_put_byindex(net: map->net, index: e->id); |
160 | call_rcu(head: &e->rcu, func: __list_set_del_rcu); |
161 | } |
162 | |
163 | static void |
164 | list_set_replace(struct ip_set *set, struct set_elem *e, struct set_elem *old) |
165 | { |
166 | struct list_set *map = set->data; |
167 | |
168 | list_replace_rcu(old: &old->list, new: &e->list); |
169 | ip_set_put_byindex(net: map->net, index: old->id); |
170 | call_rcu(head: &old->rcu, func: __list_set_del_rcu); |
171 | } |
172 | |
173 | static void |
174 | set_cleanup_entries(struct ip_set *set) |
175 | { |
176 | struct list_set *map = set->data; |
177 | struct set_elem *e, *n; |
178 | |
179 | list_for_each_entry_safe(e, n, &map->members, list) |
180 | if (ip_set_timeout_expired(ext_timeout(e, set))) |
181 | list_set_del(set, e); |
182 | } |
183 | |
184 | static int |
185 | list_set_utest(struct ip_set *set, void *value, const struct ip_set_ext *ext, |
186 | struct ip_set_ext *mext, u32 flags) |
187 | { |
188 | struct list_set *map = set->data; |
189 | struct set_adt_elem *d = value; |
190 | struct set_elem *e, *next, *prev = NULL; |
191 | int ret; |
192 | |
193 | list_for_each_entry(e, &map->members, list) { |
194 | if (SET_WITH_TIMEOUT(set) && |
195 | ip_set_timeout_expired(ext_timeout(e, set))) |
196 | continue; |
197 | else if (e->id != d->id) { |
198 | prev = e; |
199 | continue; |
200 | } |
201 | |
202 | if (d->before == 0) { |
203 | ret = 1; |
204 | } else if (d->before > 0) { |
205 | next = list_next_entry(e, list); |
206 | ret = !list_is_last(list: &e->list, head: &map->members) && |
207 | next->id == d->refid; |
208 | } else { |
209 | ret = prev && prev->id == d->refid; |
210 | } |
211 | return ret; |
212 | } |
213 | return 0; |
214 | } |
215 | |
216 | static void |
217 | list_set_init_extensions(struct ip_set *set, const struct ip_set_ext *ext, |
218 | struct set_elem *e) |
219 | { |
220 | if (SET_WITH_COUNTER(set)) |
221 | ip_set_init_counter(ext_counter(e, set), ext); |
222 | if (SET_WITH_COMMENT(set)) |
223 | ip_set_init_comment(set, ext_comment(e, set), ext); |
224 | if (SET_WITH_SKBINFO(set)) |
225 | ip_set_init_skbinfo(ext_skbinfo(e, set), ext); |
226 | /* Update timeout last */ |
227 | if (SET_WITH_TIMEOUT(set)) |
228 | ip_set_timeout_set(ext_timeout(e, set), value: ext->timeout); |
229 | } |
230 | |
231 | static int |
232 | list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext, |
233 | struct ip_set_ext *mext, u32 flags) |
234 | { |
235 | struct list_set *map = set->data; |
236 | struct set_adt_elem *d = value; |
237 | struct set_elem *e, *n, *prev, *next; |
238 | bool flag_exist = flags & IPSET_FLAG_EXIST; |
239 | |
240 | /* Find where to add the new entry */ |
241 | n = prev = next = NULL; |
242 | list_for_each_entry(e, &map->members, list) { |
243 | if (SET_WITH_TIMEOUT(set) && |
244 | ip_set_timeout_expired(ext_timeout(e, set))) |
245 | continue; |
246 | else if (d->id == e->id) |
247 | n = e; |
248 | else if (d->before == 0 || e->id != d->refid) |
249 | continue; |
250 | else if (d->before > 0) |
251 | next = e; |
252 | else |
253 | prev = e; |
254 | } |
255 | |
256 | /* If before/after is used on an empty set */ |
257 | if ((d->before > 0 && !next) || |
258 | (d->before < 0 && !prev)) |
259 | return -IPSET_ERR_REF_EXIST; |
260 | |
261 | /* Re-add already existing element */ |
262 | if (n) { |
263 | if (!flag_exist) |
264 | return -IPSET_ERR_EXIST; |
265 | /* Update extensions */ |
266 | ip_set_ext_destroy(set, data: n); |
267 | list_set_init_extensions(set, ext, e: n); |
268 | |
269 | /* Set is already added to the list */ |
270 | ip_set_put_byindex(net: map->net, index: d->id); |
271 | return 0; |
272 | } |
273 | /* Add new entry */ |
274 | if (d->before == 0) { |
275 | /* Append */ |
276 | n = list_empty(head: &map->members) ? NULL : |
277 | list_last_entry(&map->members, struct set_elem, list); |
278 | } else if (d->before > 0) { |
279 | /* Insert after next element */ |
280 | if (!list_is_last(list: &next->list, head: &map->members)) |
281 | n = list_next_entry(next, list); |
282 | } else { |
283 | /* Insert before prev element */ |
284 | if (prev->list.prev != &map->members) |
285 | n = list_prev_entry(prev, list); |
286 | } |
287 | /* Can we replace a timed out entry? */ |
288 | if (n && |
289 | !(SET_WITH_TIMEOUT(set) && |
290 | ip_set_timeout_expired(ext_timeout(n, set)))) |
291 | n = NULL; |
292 | |
293 | e = kzalloc(size: set->dsize, GFP_ATOMIC); |
294 | if (!e) |
295 | return -ENOMEM; |
296 | e->id = d->id; |
297 | e->set = set; |
298 | INIT_LIST_HEAD(list: &e->list); |
299 | list_set_init_extensions(set, ext, e); |
300 | if (n) |
301 | list_set_replace(set, e, old: n); |
302 | else if (next) |
303 | list_add_tail_rcu(new: &e->list, head: &next->list); |
304 | else if (prev) |
305 | list_add_rcu(new: &e->list, head: &prev->list); |
306 | else |
307 | list_add_tail_rcu(new: &e->list, head: &map->members); |
308 | set->elements++; |
309 | |
310 | return 0; |
311 | } |
312 | |
313 | static int |
314 | list_set_udel(struct ip_set *set, void *value, const struct ip_set_ext *ext, |
315 | struct ip_set_ext *mext, u32 flags) |
316 | { |
317 | struct list_set *map = set->data; |
318 | struct set_adt_elem *d = value; |
319 | struct set_elem *e, *next, *prev = NULL; |
320 | |
321 | list_for_each_entry(e, &map->members, list) { |
322 | if (SET_WITH_TIMEOUT(set) && |
323 | ip_set_timeout_expired(ext_timeout(e, set))) |
324 | continue; |
325 | else if (e->id != d->id) { |
326 | prev = e; |
327 | continue; |
328 | } |
329 | |
330 | if (d->before > 0) { |
331 | next = list_next_entry(e, list); |
332 | if (list_is_last(list: &e->list, head: &map->members) || |
333 | next->id != d->refid) |
334 | return -IPSET_ERR_REF_EXIST; |
335 | } else if (d->before < 0) { |
336 | if (!prev || prev->id != d->refid) |
337 | return -IPSET_ERR_REF_EXIST; |
338 | } |
339 | list_set_del(set, e); |
340 | return 0; |
341 | } |
342 | return d->before != 0 ? -IPSET_ERR_REF_EXIST : -IPSET_ERR_EXIST; |
343 | } |
344 | |
345 | static int |
346 | list_set_uadt(struct ip_set *set, struct nlattr *tb[], |
347 | enum ipset_adt adt, u32 *lineno, u32 flags, bool retried) |
348 | { |
349 | struct list_set *map = set->data; |
350 | ipset_adtfn adtfn = set->variant->adt[adt]; |
351 | struct set_adt_elem e = { .refid = IPSET_INVALID_ID }; |
352 | struct ip_set_ext ext = IP_SET_INIT_UEXT(set); |
353 | struct ip_set *s; |
354 | int ret = 0; |
355 | |
356 | if (tb[IPSET_ATTR_LINENO]) |
357 | *lineno = nla_get_u32(nla: tb[IPSET_ATTR_LINENO]); |
358 | |
359 | if (unlikely(!tb[IPSET_ATTR_NAME] || |
360 | !ip_set_optattr_netorder(tb, IPSET_ATTR_CADT_FLAGS))) |
361 | return -IPSET_ERR_PROTOCOL; |
362 | |
363 | ret = ip_set_get_extensions(set, tb, ext: &ext); |
364 | if (ret) |
365 | return ret; |
366 | e.id = ip_set_get_byname(net: map->net, name: nla_data(nla: tb[IPSET_ATTR_NAME]), set: &s); |
367 | if (e.id == IPSET_INVALID_ID) |
368 | return -IPSET_ERR_NAME; |
369 | /* "Loop detection" */ |
370 | if (s->type->features & IPSET_TYPE_NAME) { |
371 | ret = -IPSET_ERR_LOOP; |
372 | goto finish; |
373 | } |
374 | |
375 | if (tb[IPSET_ATTR_CADT_FLAGS]) { |
376 | u32 f = ip_set_get_h32(attr: tb[IPSET_ATTR_CADT_FLAGS]); |
377 | |
378 | e.before = f & IPSET_FLAG_BEFORE; |
379 | } |
380 | |
381 | if (e.before && !tb[IPSET_ATTR_NAMEREF]) { |
382 | ret = -IPSET_ERR_BEFORE; |
383 | goto finish; |
384 | } |
385 | |
386 | if (tb[IPSET_ATTR_NAMEREF]) { |
387 | e.refid = ip_set_get_byname(net: map->net, |
388 | name: nla_data(nla: tb[IPSET_ATTR_NAMEREF]), |
389 | set: &s); |
390 | if (e.refid == IPSET_INVALID_ID) { |
391 | ret = -IPSET_ERR_NAMEREF; |
392 | goto finish; |
393 | } |
394 | if (!e.before) |
395 | e.before = -1; |
396 | } |
397 | if (adt != IPSET_TEST && SET_WITH_TIMEOUT(set)) |
398 | set_cleanup_entries(set); |
399 | |
400 | ret = adtfn(set, &e, &ext, &ext, flags); |
401 | |
402 | finish: |
403 | if (e.refid != IPSET_INVALID_ID) |
404 | ip_set_put_byindex(net: map->net, index: e.refid); |
405 | if (adt != IPSET_ADD || ret) |
406 | ip_set_put_byindex(net: map->net, index: e.id); |
407 | |
408 | return ip_set_eexist(ret, flags) ? 0 : ret; |
409 | } |
410 | |
411 | static void |
412 | list_set_flush(struct ip_set *set) |
413 | { |
414 | struct list_set *map = set->data; |
415 | struct set_elem *e, *n; |
416 | |
417 | list_for_each_entry_safe(e, n, &map->members, list) |
418 | list_set_del(set, e); |
419 | set->elements = 0; |
420 | set->ext_size = 0; |
421 | } |
422 | |
423 | static void |
424 | list_set_destroy(struct ip_set *set) |
425 | { |
426 | struct list_set *map = set->data; |
427 | struct set_elem *e, *n; |
428 | |
429 | list_for_each_entry_safe(e, n, &map->members, list) { |
430 | list_del(entry: &e->list); |
431 | ip_set_put_byindex(net: map->net, index: e->id); |
432 | ip_set_ext_destroy(set, data: e); |
433 | kfree(objp: e); |
434 | } |
435 | kfree(objp: map); |
436 | |
437 | set->data = NULL; |
438 | } |
439 | |
440 | /* Calculate the actual memory size of the set data */ |
441 | static size_t |
442 | list_set_memsize(const struct list_set *map, size_t dsize) |
443 | { |
444 | struct set_elem *e; |
445 | u32 n = 0; |
446 | |
447 | rcu_read_lock(); |
448 | list_for_each_entry_rcu(e, &map->members, list) |
449 | n++; |
450 | rcu_read_unlock(); |
451 | |
452 | return (sizeof(*map) + n * dsize); |
453 | } |
454 | |
455 | static int |
456 | list_set_head(struct ip_set *set, struct sk_buff *skb) |
457 | { |
458 | const struct list_set *map = set->data; |
459 | struct nlattr *nested; |
460 | size_t memsize = list_set_memsize(map, dsize: set->dsize) + set->ext_size; |
461 | |
462 | nested = nla_nest_start(skb, attrtype: IPSET_ATTR_DATA); |
463 | if (!nested) |
464 | goto nla_put_failure; |
465 | if (nla_put_net32(skb, attrtype: IPSET_ATTR_SIZE, htonl(map->size)) || |
466 | nla_put_net32(skb, attrtype: IPSET_ATTR_REFERENCES, htonl(set->ref)) || |
467 | nla_put_net32(skb, attrtype: IPSET_ATTR_MEMSIZE, htonl(memsize)) || |
468 | nla_put_net32(skb, attrtype: IPSET_ATTR_ELEMENTS, htonl(set->elements))) |
469 | goto nla_put_failure; |
470 | if (unlikely(ip_set_put_flags(skb, set))) |
471 | goto nla_put_failure; |
472 | nla_nest_end(skb, start: nested); |
473 | |
474 | return 0; |
475 | nla_put_failure: |
476 | return -EMSGSIZE; |
477 | } |
478 | |
479 | static int |
480 | list_set_list(const struct ip_set *set, |
481 | struct sk_buff *skb, struct netlink_callback *cb) |
482 | { |
483 | const struct list_set *map = set->data; |
484 | struct nlattr *atd, *nested; |
485 | u32 i = 0, first = cb->args[IPSET_CB_ARG0]; |
486 | char name[IPSET_MAXNAMELEN]; |
487 | struct set_elem *e; |
488 | int ret = 0; |
489 | |
490 | atd = nla_nest_start(skb, attrtype: IPSET_ATTR_ADT); |
491 | if (!atd) |
492 | return -EMSGSIZE; |
493 | |
494 | rcu_read_lock(); |
495 | list_for_each_entry_rcu(e, &map->members, list) { |
496 | if (i < first || |
497 | (SET_WITH_TIMEOUT(set) && |
498 | ip_set_timeout_expired(ext_timeout(e, set)))) { |
499 | i++; |
500 | continue; |
501 | } |
502 | nested = nla_nest_start(skb, attrtype: IPSET_ATTR_DATA); |
503 | if (!nested) |
504 | goto nla_put_failure; |
505 | ip_set_name_byindex(net: map->net, index: e->id, name); |
506 | if (nla_put_string(skb, attrtype: IPSET_ATTR_NAME, str: name)) |
507 | goto nla_put_failure; |
508 | if (ip_set_put_extensions(skb, set, e, active: true)) |
509 | goto nla_put_failure; |
510 | nla_nest_end(skb, start: nested); |
511 | i++; |
512 | } |
513 | |
514 | nla_nest_end(skb, start: atd); |
515 | /* Set listing finished */ |
516 | cb->args[IPSET_CB_ARG0] = 0; |
517 | goto out; |
518 | |
519 | nla_put_failure: |
520 | nla_nest_cancel(skb, start: nested); |
521 | if (unlikely(i == first)) { |
522 | nla_nest_cancel(skb, start: atd); |
523 | cb->args[IPSET_CB_ARG0] = 0; |
524 | ret = -EMSGSIZE; |
525 | } else { |
526 | cb->args[IPSET_CB_ARG0] = i; |
527 | nla_nest_end(skb, start: atd); |
528 | } |
529 | out: |
530 | rcu_read_unlock(); |
531 | return ret; |
532 | } |
533 | |
534 | static bool |
535 | list_set_same_set(const struct ip_set *a, const struct ip_set *b) |
536 | { |
537 | const struct list_set *x = a->data; |
538 | const struct list_set *y = b->data; |
539 | |
540 | return x->size == y->size && |
541 | a->timeout == b->timeout && |
542 | a->extensions == b->extensions; |
543 | } |
544 | |
545 | static void |
546 | list_set_cancel_gc(struct ip_set *set) |
547 | { |
548 | struct list_set *map = set->data; |
549 | |
550 | if (SET_WITH_TIMEOUT(set)) |
551 | timer_shutdown_sync(timer: &map->gc); |
552 | } |
553 | |
554 | static const struct ip_set_type_variant set_variant = { |
555 | .kadt = list_set_kadt, |
556 | .uadt = list_set_uadt, |
557 | .adt = { |
558 | [IPSET_ADD] = list_set_uadd, |
559 | [IPSET_DEL] = list_set_udel, |
560 | [IPSET_TEST] = list_set_utest, |
561 | }, |
562 | .destroy = list_set_destroy, |
563 | .flush = list_set_flush, |
564 | .head = list_set_head, |
565 | .list = list_set_list, |
566 | .same_set = list_set_same_set, |
567 | .cancel_gc = list_set_cancel_gc, |
568 | }; |
569 | |
570 | static void |
571 | list_set_gc(struct timer_list *t) |
572 | { |
573 | struct list_set *map = from_timer(map, t, gc); |
574 | struct ip_set *set = map->set; |
575 | |
576 | spin_lock_bh(lock: &set->lock); |
577 | set_cleanup_entries(set); |
578 | spin_unlock_bh(lock: &set->lock); |
579 | |
580 | map->gc.expires = jiffies + IPSET_GC_PERIOD(set->timeout) * HZ; |
581 | add_timer(timer: &map->gc); |
582 | } |
583 | |
584 | static void |
585 | list_set_gc_init(struct ip_set *set, void (*gc)(struct timer_list *t)) |
586 | { |
587 | struct list_set *map = set->data; |
588 | |
589 | timer_setup(&map->gc, gc, 0); |
590 | mod_timer(timer: &map->gc, expires: jiffies + IPSET_GC_PERIOD(set->timeout) * HZ); |
591 | } |
592 | |
593 | /* Create list:set type of sets */ |
594 | |
595 | static bool |
596 | init_list_set(struct net *net, struct ip_set *set, u32 size) |
597 | { |
598 | struct list_set *map; |
599 | |
600 | map = kzalloc(size: sizeof(*map), GFP_KERNEL); |
601 | if (!map) |
602 | return false; |
603 | |
604 | map->size = size; |
605 | map->net = net; |
606 | map->set = set; |
607 | INIT_LIST_HEAD(list: &map->members); |
608 | set->data = map; |
609 | |
610 | return true; |
611 | } |
612 | |
613 | static int |
614 | list_set_create(struct net *net, struct ip_set *set, struct nlattr *tb[], |
615 | u32 flags) |
616 | { |
617 | u32 size = IP_SET_LIST_DEFAULT_SIZE; |
618 | |
619 | if (unlikely(!ip_set_optattr_netorder(tb, IPSET_ATTR_SIZE) || |
620 | !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT) || |
621 | !ip_set_optattr_netorder(tb, IPSET_ATTR_CADT_FLAGS))) |
622 | return -IPSET_ERR_PROTOCOL; |
623 | |
624 | if (tb[IPSET_ATTR_SIZE]) |
625 | size = ip_set_get_h32(attr: tb[IPSET_ATTR_SIZE]); |
626 | if (size < IP_SET_LIST_MIN_SIZE) |
627 | size = IP_SET_LIST_MIN_SIZE; |
628 | |
629 | set->variant = &set_variant; |
630 | set->dsize = ip_set_elem_len(set, tb, len: sizeof(struct set_elem), |
631 | align: __alignof__(struct set_elem)); |
632 | if (!init_list_set(net, set, size)) |
633 | return -ENOMEM; |
634 | if (tb[IPSET_ATTR_TIMEOUT]) { |
635 | set->timeout = ip_set_timeout_uget(tb: tb[IPSET_ATTR_TIMEOUT]); |
636 | list_set_gc_init(set, gc: list_set_gc); |
637 | } |
638 | return 0; |
639 | } |
640 | |
641 | static struct ip_set_type list_set_type __read_mostly = { |
642 | .name = "list:set" , |
643 | .protocol = IPSET_PROTOCOL, |
644 | .features = IPSET_TYPE_NAME | IPSET_DUMP_LAST, |
645 | .dimension = IPSET_DIM_ONE, |
646 | .family = NFPROTO_UNSPEC, |
647 | .revision_min = IPSET_TYPE_REV_MIN, |
648 | .revision_max = IPSET_TYPE_REV_MAX, |
649 | .create = list_set_create, |
650 | .create_policy = { |
651 | [IPSET_ATTR_SIZE] = { .type = NLA_U32 }, |
652 | [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, |
653 | [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, |
654 | }, |
655 | .adt_policy = { |
656 | [IPSET_ATTR_NAME] = { .type = NLA_STRING, |
657 | .len = IPSET_MAXNAMELEN }, |
658 | [IPSET_ATTR_NAMEREF] = { .type = NLA_STRING, |
659 | .len = IPSET_MAXNAMELEN }, |
660 | [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, |
661 | [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, |
662 | [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, |
663 | [IPSET_ATTR_BYTES] = { .type = NLA_U64 }, |
664 | [IPSET_ATTR_PACKETS] = { .type = NLA_U64 }, |
665 | [IPSET_ATTR_COMMENT] = { .type = NLA_NUL_STRING, |
666 | .len = IPSET_MAX_COMMENT_SIZE }, |
667 | [IPSET_ATTR_SKBMARK] = { .type = NLA_U64 }, |
668 | [IPSET_ATTR_SKBPRIO] = { .type = NLA_U32 }, |
669 | [IPSET_ATTR_SKBQUEUE] = { .type = NLA_U16 }, |
670 | }, |
671 | .me = THIS_MODULE, |
672 | }; |
673 | |
674 | static int __init |
675 | list_set_init(void) |
676 | { |
677 | return ip_set_type_register(set_type: &list_set_type); |
678 | } |
679 | |
680 | static void __exit |
681 | list_set_fini(void) |
682 | { |
683 | rcu_barrier(); |
684 | ip_set_type_unregister(set_type: &list_set_type); |
685 | } |
686 | |
687 | module_init(list_set_init); |
688 | module_exit(list_set_fini); |
689 | |