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