nft-cache.c 16 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
 * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
 */

#include <assert.h>
#include <errno.h>
14
#include <stdlib.h>
15
16
17
18
19
20
21
22
23
24
25
26
#include <string.h>
#include <xtables.h>

#include <linux/netfilter/nf_tables.h>

#include <libmnl/libmnl.h>
#include <libnftnl/gen.h>
#include <libnftnl/set.h>
#include <libnftnl/table.h>

#include "nft.h"
#include "nft-cache.h"
27
#include "nft-chain.h"
28

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
static void cache_chain_list_insert(struct list_head *list, const char *name)
{
	struct cache_chain *pos = NULL, *new;

	list_for_each_entry(pos, list, head) {
		int cmp = strcmp(pos->name, name);

		if (!cmp)
			return;
		if (cmp > 0)
			break;
	}

	new = xtables_malloc(sizeof(*new));
	new->name = strdup(name);
	list_add_tail(&new->head, pos ? &pos->head : list);
}

void nft_cache_level_set(struct nft_handle *h, int level,
			 const struct nft_cmd *cmd)
{
	struct nft_cache_req *req = &h->cache_req;

	if (level > req->level)
		req->level = level;

	if (!cmd || !cmd->table || req->all_chains)
		return;

	if (!req->table)
		req->table = strdup(cmd->table);
	else
		assert(!strcmp(req->table, cmd->table));

	if (!cmd->chain) {
		req->all_chains = true;
		return;
	}

	cache_chain_list_insert(&req->chain_list, cmd->chain);
	if (cmd->rename)
		cache_chain_list_insert(&req->chain_list, cmd->rename);
	if (cmd->jumpto)
		cache_chain_list_insert(&req->chain_list, cmd->jumpto);
}

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
static int genid_cb(const struct nlmsghdr *nlh, void *data)
{
	uint32_t *genid = data;
	struct nftnl_gen *gen;

	gen = nftnl_gen_alloc();
	if (!gen)
		return MNL_CB_ERROR;

	if (nftnl_gen_nlmsg_parse(nlh, gen) < 0)
		goto out;

	*genid = nftnl_gen_get_u32(gen, NFTNL_GEN_ID);

	nftnl_gen_free(gen);
	return MNL_CB_STOP;
out:
	nftnl_gen_free(gen);
	return MNL_CB_ERROR;
}

static void mnl_genid_get(struct nft_handle *h, uint32_t *genid)
{
	char buf[MNL_SOCKET_BUFFER_SIZE];
	struct nlmsghdr *nlh;
	int ret;

	nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETGEN, 0, 0, h->seq);
	ret = mnl_talk(h, nlh, genid_cb, genid);
	if (ret == 0)
		return;

	xtables_error(RESOURCE_PROBLEM,
		      "Could not fetch rule set generation id: %s\n", nft_strerror(errno));
}

static int nftnl_table_list_cb(const struct nlmsghdr *nlh, void *data)
{
113
114
115
116
	struct nftnl_table *nftnl = nftnl_table_alloc();
	const struct builtin_table *t;
	struct nft_handle *h = data;
	const char *name;
117

118
119
	if (!nftnl)
		return MNL_CB_OK;
120

121
	if (nftnl_table_nlmsg_parse(nlh, nftnl) < 0)
122
123
		goto out;

124
125
126
	name = nftnl_table_get_str(nftnl, NFTNL_TABLE_NAME);
	if (!name)
		goto out;
127

128
129
130
131
132
	t = nft_table_builtin_find(h, name);
	if (!t)
		goto out;

	h->cache->table[t->type].exists = true;
133
out:
134
	nftnl_table_free(nftnl);
135
136
137
138
139
140
	return MNL_CB_OK;
}

static int fetch_table_cache(struct nft_handle *h)
{
	struct nlmsghdr *nlh;
141
	char buf[16536];
142
	int i, ret;
143
144
145
146

	nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family,
					NLM_F_DUMP, h->seq);

147
	ret = mnl_talk(h, nlh, nftnl_table_list_cb, h);
148
149
150
	if (ret < 0 && errno == EINTR)
		assert(nft_restart(h) >= 0);

151
152
153
154
155
156
	for (i = 0; i < NFT_TABLE_MAX; i++) {
		enum nft_table_type type = h->tables[i].type;

		if (!h->tables[i].name)
			continue;

157
		h->cache->table[type].chains = nft_chain_list_alloc();
158
159
160
161
162
163

		h->cache->table[type].sets = nftnl_set_list_alloc();
		if (!h->cache->table[type].sets)
			return 0;
	}

164
165
166
	return 1;
}

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
static uint32_t djb_hash(const char *key)
{
	uint32_t i, hash = 5381;

	for (i = 0; i < strlen(key); i++)
		hash = ((hash << 5) + hash) + key[i];

	return hash;
}

static struct hlist_head *chain_name_hlist(struct nft_handle *h,
					   const struct builtin_table *t,
					   const char *chain)
{
	int key = djb_hash(chain) % CHAIN_NAME_HSIZE;

	return &h->cache->table[t->type].chains->names[key];
}

struct nft_chain *
nft_chain_find(struct nft_handle *h, const char *table, const char *chain)
{
	const struct builtin_table *t;
	struct hlist_node *node;
	struct nft_chain *c;

	t = nft_table_builtin_find(h, table);
	if (!t)
		return NULL;

	hlist_for_each_entry(c, node, chain_name_hlist(h, t, chain), hnode) {
		if (!strcmp(nftnl_chain_get_str(c->nftnl, NFTNL_CHAIN_NAME),
			    chain))
			return c;
	}
	return NULL;
}

int nft_cache_add_chain(struct nft_handle *h, const struct builtin_table *t,
			struct nftnl_chain *c)
{
	const char *cname = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
	struct nft_chain *nc = nft_chain_alloc(c);

	if (nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) {
		uint32_t hooknum = nftnl_chain_get_u32(c, NFTNL_CHAIN_HOOKNUM);

		if (hooknum >= NF_INET_NUMHOOKS) {
			nft_chain_free(nc);
			return -EINVAL;
		}

		if (h->cache->table[t->type].base_chains[hooknum]) {
			nft_chain_free(nc);
			return -EEXIST;
		}

		h->cache->table[t->type].base_chains[hooknum] = nc;
	} else {
		struct nft_chain_list *clist = h->cache->table[t->type].chains;
		struct list_head *pos = &clist->list;
		struct nft_chain *cur;
		const char *n;

		list_for_each_entry(cur, &clist->list, head) {
			n = nftnl_chain_get_str(cur->nftnl, NFTNL_CHAIN_NAME);
			if (strcmp(cname, n) <= 0) {
				pos = &cur->head;
				break;
			}
		}
		list_add_tail(&nc->head, pos);
	}
	hlist_add_head(&nc->hnode, chain_name_hlist(h, t, cname));
	return 0;
}

244
245
246
247
248
249
250
251
252
253
254
struct nftnl_chain_list_cb_data {
	struct nft_handle *h;
	const struct builtin_table *t;
};

static int nftnl_chain_list_cb(const struct nlmsghdr *nlh, void *data)
{
	struct nftnl_chain_list_cb_data *d = data;
	const struct builtin_table *t = d->t;
	struct nft_handle *h = d->h;
	struct nftnl_chain *c;
255
	const char *tname;
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273

	c = nftnl_chain_alloc();
	if (c == NULL)
		goto err;

	if (nftnl_chain_nlmsg_parse(nlh, c) < 0)
		goto out;

	tname = nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE);

	if (!t) {
		t = nft_table_builtin_find(h, tname);
		if (!t)
			goto out;
	} else if (strcmp(t->name, tname)) {
		goto out;
	}

274
275
	if (nft_cache_add_chain(h, t, c))
		goto out;
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369

	return MNL_CB_OK;
out:
	nftnl_chain_free(c);
err:
	return MNL_CB_OK;
}

struct nftnl_set_list_cb_data {
	struct nft_handle *h;
	const struct builtin_table *t;
};

static int nftnl_set_list_cb(const struct nlmsghdr *nlh, void *data)
{
	struct nftnl_set_list_cb_data *d = data;
	const struct builtin_table *t = d->t;
	struct nftnl_set_list *list;
	struct nft_handle *h = d->h;
	const char *tname, *sname;
	struct nftnl_set *s;

	s = nftnl_set_alloc();
	if (s == NULL)
		return MNL_CB_OK;

	if (nftnl_set_nlmsg_parse(nlh, s) < 0)
		goto out_free;

	tname = nftnl_set_get_str(s, NFTNL_SET_TABLE);

	if (!t)
		t = nft_table_builtin_find(h, tname);
	else if (strcmp(t->name, tname))
		goto out_free;

	if (!t)
		goto out_free;

	list = h->cache->table[t->type].sets;
	sname = nftnl_set_get_str(s, NFTNL_SET_NAME);

	if (nftnl_set_list_lookup_byname(list, sname))
		goto out_free;

	nftnl_set_list_add_tail(s, list);

	return MNL_CB_OK;
out_free:
	nftnl_set_free(s);
	return MNL_CB_OK;
}

static int set_elem_cb(const struct nlmsghdr *nlh, void *data)
{
	return nftnl_set_elems_nlmsg_parse(nlh, data) ? -1 : MNL_CB_OK;
}

static bool set_has_elements(struct nftnl_set *s)
{
	struct nftnl_set_elems_iter *iter;
	bool ret = false;

	iter = nftnl_set_elems_iter_create(s);
	if (iter) {
		ret = !!nftnl_set_elems_iter_cur(iter);
		nftnl_set_elems_iter_destroy(iter);
	}
	return ret;
}

static int set_fetch_elem_cb(struct nftnl_set *s, void *data)
{
	char buf[MNL_SOCKET_BUFFER_SIZE];
	struct nft_handle *h = data;
	struct nlmsghdr *nlh;

	if (set_has_elements(s))
		return 0;

	nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, h->family,
				    NLM_F_DUMP, h->seq);
	nftnl_set_elems_nlmsg_build_payload(nlh, s);

	return mnl_talk(h, nlh, set_elem_cb, s);
}

static int fetch_set_cache(struct nft_handle *h,
			   const struct builtin_table *t, const char *set)
{
	struct nftnl_set_list_cb_data d = {
		.h = h,
		.t = t,
	};
370
371
	uint16_t flags = NLM_F_DUMP;
	struct nftnl_set *s = NULL;
372
373
374
375
	struct nlmsghdr *nlh;
	char buf[16536];
	int i, ret;

376
377
378
379
	if (t) {
		s = nftnl_set_alloc();
		if (!s)
			return -1;
380

381
		nftnl_set_set_str(s, NFTNL_SET_TABLE, t->name);
382

383
384
385
		if (set) {
			nftnl_set_set_str(s, NFTNL_SET_NAME, set);
			flags = NLM_F_ACK;
386
387
388
		}
	}

389
390
	nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET,
					h->family, flags, h->seq);
391

392
	if (s) {
393
394
395
396
397
398
399
400
401
402
		nftnl_set_nlmsg_build_payload(nlh, s);
		nftnl_set_free(s);
	}

	ret = mnl_talk(h, nlh, nftnl_set_list_cb, &d);
	if (ret < 0 && errno == EINTR) {
		assert(nft_restart(h) >= 0);
		return ret;
	}

403
	if (t) {
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
		nftnl_set_list_foreach(h->cache->table[t->type].sets,
				       set_fetch_elem_cb, h);
	} else {
		for (i = 0; i < NFT_TABLE_MAX; i++) {
			enum nft_table_type type = h->tables[i].type;

			if (!h->tables[i].name)
				continue;

			nftnl_set_list_foreach(h->cache->table[type].sets,
					       set_fetch_elem_cb, h);
		}
	}
	return ret;
}

420
421
422
static int __fetch_chain_cache(struct nft_handle *h,
			       const struct builtin_table *t,
			       const struct nftnl_chain *c)
423
424
425
426
427
428
429
{
	struct nftnl_chain_list_cb_data d = {
		.h = h,
		.t = t,
	};
	char buf[16536];
	struct nlmsghdr *nlh;
430
	int ret;
431

432
433
434
435
	nlh = nftnl_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family,
					  c ? NLM_F_ACK : NLM_F_DUMP, h->seq);
	if (c)
		nftnl_chain_nlmsg_build_payload(nlh, c);
436

437
438
439
	ret = mnl_talk(h, nlh, nftnl_chain_list_cb, &d);
	if (ret < 0 && errno == EINTR)
		assert(nft_restart(h) >= 0);
440

441
442
	return ret;
}
443

444
445
446
447
448
449
450
static int fetch_chain_cache(struct nft_handle *h,
			     const struct builtin_table *t,
			     struct list_head *chains)
{
	struct cache_chain *cc;
	struct nftnl_chain *c;
	int rc, ret = 0;
451

452
453
	if (!chains)
		return __fetch_chain_cache(h, t, NULL);
454

455
	assert(t);
456

457
458
459
	c = nftnl_chain_alloc();
	if (!c)
		return -1;
460

461
462
463
464
465
466
467
468
	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, t->name);

	list_for_each_entry(cc, chains, head) {
		nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, cc->name);
		rc = __fetch_chain_cache(h, t, c);
		if (rc)
			ret = rc;
	}
469

470
	nftnl_chain_free(c);
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
	return ret;
}

static int nftnl_rule_list_cb(const struct nlmsghdr *nlh, void *data)
{
	struct nftnl_chain *c = data;
	struct nftnl_rule *r;

	r = nftnl_rule_alloc();
	if (r == NULL)
		return MNL_CB_OK;

	if (nftnl_rule_nlmsg_parse(nlh, r) < 0) {
		nftnl_rule_free(r);
		return MNL_CB_OK;
	}

	nftnl_chain_rule_add_tail(r, c);
	return MNL_CB_OK;
}

492
static int nft_rule_list_update(struct nft_chain *nc, void *data)
493
{
494
	struct nftnl_chain *c = nc->nftnl;
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
	struct nft_handle *h = data;
	char buf[16536];
	struct nlmsghdr *nlh;
	struct nftnl_rule *rule;
	int ret;

	if (nftnl_rule_lookup_byindex(c, 0))
		return 0;

	rule = nftnl_rule_alloc();
	if (!rule)
		return -1;

	nftnl_rule_set_str(rule, NFTNL_RULE_TABLE,
			   nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE));
	nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN,
			   nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));

	nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, h->family,
					NLM_F_DUMP, h->seq);
	nftnl_rule_nlmsg_build_payload(nlh, rule);

	ret = mnl_talk(h, nlh, nftnl_rule_list_cb, c);
	if (ret < 0 && errno == EINTR)
		assert(nft_restart(h) >= 0);

	nftnl_rule_free(rule);

	if (h->family == NFPROTO_BRIDGE)
		nft_bridge_chain_postprocess(h, c);

	return 0;
}

static int fetch_rule_cache(struct nft_handle *h,
530
			    const struct builtin_table *t)
531
532
533
{
	int i;

534
535
	if (t)
		return nft_chain_foreach(h, t->name, nft_rule_list_update, h);
536
537
538
539
540
541

	for (i = 0; i < NFT_TABLE_MAX; i++) {

		if (!h->tables[i].name)
			continue;

542
543
		if (nft_chain_foreach(h, h->tables[i].name,
				      nft_rule_list_update, h))
544
545
546
547
548
			return -1;
	}
	return 0;
}

549
550
551
static int flush_cache(struct nft_handle *h, struct nft_cache *c,
		       const char *tablename);

552
static void
553
__nft_build_cache(struct nft_handle *h)
554
{
555
556
557
558
	struct nft_cache_req *req = &h->cache_req;
	const struct builtin_table *t = NULL;
	struct list_head *chains = NULL;
	uint32_t genid_check;
559

560
	if (h->cache_init)
561
562
		return;

563
564
565
566
	if (req->table) {
		t = nft_table_builtin_find(h, req->table);
		if (!req->all_chains)
			chains = &req->chain_list;
567
568
	}

569
570
571
	h->cache_init = true;
retry:
	mnl_genid_get(h, &h->nft_genid);
572

573
574
575
	if (req->level >= NFT_CL_TABLES)
		fetch_table_cache(h);
	if (req->level == NFT_CL_FAKE)
576
		goto genid_check;
577
578
579
580
581
582
	if (req->level >= NFT_CL_CHAINS)
		fetch_chain_cache(h, t, chains);
	if (req->level >= NFT_CL_SETS)
		fetch_set_cache(h, t, NULL);
	if (req->level >= NFT_CL_RULES)
		fetch_rule_cache(h, t);
583
genid_check:
584
585
586
587
	mnl_genid_get(h, &genid_check);
	if (h->nft_genid != genid_check) {
		flush_cache(h, h->cache, NULL);
		goto retry;
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
	}
}

static void __nft_flush_cache(struct nft_handle *h)
{
	if (!h->cache_index) {
		h->cache_index++;
		h->cache = &h->__cache[h->cache_index];
	} else {
		flush_chain_cache(h, NULL);
	}
}

static int ____flush_rule_cache(struct nftnl_rule *r, void *data)
{
	nftnl_rule_list_del(r);
	nftnl_rule_free(r);

	return 0;
}

609
static int __flush_rule_cache(struct nft_chain *c, void *data)
610
{
611
	return nftnl_rule_foreach(c->nftnl, ____flush_rule_cache, NULL);
612
613
614
}

int flush_rule_cache(struct nft_handle *h, const char *table,
615
		     struct nft_chain *c)
616
617
618
619
{
	if (c)
		return __flush_rule_cache(c, NULL);

620
621
	nft_chain_foreach(h, table, __flush_rule_cache, NULL);
	return 0;
622
623
}

624
static int __flush_chain_cache(struct nft_chain *c, void *data)
625
{
626
627
	nft_chain_list_del(c);
	nft_chain_free(c);
628
629
630
631
632
633
634
635
636
637
638
639

	return 0;
}

static int __flush_set_cache(struct nftnl_set *s, void *data)
{
	nftnl_set_list_del(s);
	nftnl_set_free(s);

	return 0;
}

640
641
642
643
644
645
646
647
648
649
650
651
652
static void flush_base_chain_cache(struct nft_chain **base_chains)
{
	int i;

	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
		if (!base_chains[i])
			continue;
		hlist_del(&base_chains[i]->hnode);
		nft_chain_free(base_chains[i]);
		base_chains[i] = NULL;
	}
}

653
654
655
656
657
658
659
660
661
662
static int flush_cache(struct nft_handle *h, struct nft_cache *c,
		       const char *tablename)
{
	const struct builtin_table *table;
	int i;

	if (tablename) {
		table = nft_table_builtin_find(h, tablename);
		if (!table)
			return 0;
663
664
665
666

		flush_base_chain_cache(c->table[table->type].base_chains);
		nft_chain_foreach(h, tablename, __flush_chain_cache, NULL);

667
668
669
670
671
672
673
674
675
676
		if (c->table[table->type].sets)
			nftnl_set_list_foreach(c->table[table->type].sets,
					       __flush_set_cache, NULL);
		return 0;
	}

	for (i = 0; i < NFT_TABLE_MAX; i++) {
		if (h->tables[i].name == NULL)
			continue;

677
		flush_base_chain_cache(c->table[i].base_chains);
678
		if (c->table[i].chains) {
679
			nft_chain_list_free(c->table[i].chains);
680
681
			c->table[i].chains = NULL;
		}
682

683
		if (c->table[i].sets) {
684
			nftnl_set_list_free(c->table[i].sets);
685
686
			c->table[i].sets = NULL;
		}
687
688

		c->table[i].exists = false;
689
690
691
692
693
694
695
	}

	return 1;
}

void flush_chain_cache(struct nft_handle *h, const char *tablename)
{
696
	if (!h->cache_init)
697
698
699
		return;

	if (flush_cache(h, h->cache, tablename))
700
		h->cache_init = false;
701
702
703
704
}

void nft_rebuild_cache(struct nft_handle *h)
{
705
	if (h->cache_init) {
706
		__nft_flush_cache(h);
707
708
709
710
711
712
713
714
715
716
717
718
719
720
		h->cache_init = false;
	}

	__nft_build_cache(h);
}

void nft_cache_build(struct nft_handle *h)
{
	struct nft_cache_req *req = &h->cache_req;
	const struct builtin_table *t = NULL;
	int i;

	if (req->table)
		t = nft_table_builtin_find(h, req->table);
721

722
723
724
725
726
727
728
729
730
731
732
733
734
	/* fetch builtin chains as well (if existing) so nft_xt_builtin_init()
	 * doesn't override policies by accident */
	if (t && !req->all_chains) {
		for (i = 0; i < NF_INET_NUMHOOKS; i++) {
			const char *cname = t->chains[i].name;

			if (!cname)
				break;
			cache_chain_list_insert(&req->chain_list, cname);
		}
	}

	__nft_build_cache(h);
735
736
737
738
}

void nft_release_cache(struct nft_handle *h)
{
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
	struct nft_cache_req *req = &h->cache_req;
	struct cache_chain *cc, *cc_tmp;

	while (h->cache_index)
		flush_cache(h, &h->__cache[h->cache_index--], NULL);
	flush_cache(h, &h->__cache[0], NULL);
	h->cache = &h->__cache[0];
	h->cache_init = false;

	if (req->level != NFT_CL_FAKE)
		req->level = NFT_CL_TABLES;
	if (req->table) {
		free(req->table);
		req->table = NULL;
	}
	req->all_chains = false;
	list_for_each_entry_safe(cc, cc_tmp, &req->chain_list, head) {
		list_del(&cc->head);
		free(cc->name);
		free(cc);
	}
760
761
762
763
764
765
766
767
768
769
770
771
772
}

struct nftnl_set_list *
nft_set_list_get(struct nft_handle *h, const char *table, const char *set)
{
	const struct builtin_table *t;

	t = nft_table_builtin_find(h, table);
	if (!t)
		return NULL;

	return h->cache->table[t->type].sets;
}