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