nft-cache.c 14.5 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
27
#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"

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
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);
}

74
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
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)
{
112
113
114
115
	struct nftnl_table *nftnl = nftnl_table_alloc();
	const struct builtin_table *t;
	struct nft_handle *h = data;
	const char *name;
116

117
118
	if (!nftnl)
		return MNL_CB_OK;
119

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

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

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

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

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

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

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

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
	for (i = 0; i < NFT_TABLE_MAX; i++) {
		enum nft_table_type type = h->tables[i].type;

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

		h->cache->table[type].chains = nftnl_chain_list_alloc();
		if (!h->cache->table[type].chains)
			return 0;

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

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
	return 1;
}

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 nftnl_chain_list *list;
	struct nft_handle *h = d->h;
	struct nftnl_chain *c;
180
	const char *tname;
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294

	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;
	}

	list = h->cache->table[t->type].chains;
	nftnl_chain_list_add_tail(c, list);

	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,
	};
295
296
	uint16_t flags = NLM_F_DUMP;
	struct nftnl_set *s = NULL;
297
298
299
300
	struct nlmsghdr *nlh;
	char buf[16536];
	int i, ret;

301
302
303
304
	if (t) {
		s = nftnl_set_alloc();
		if (!s)
			return -1;
305

306
		nftnl_set_set_str(s, NFTNL_SET_TABLE, t->name);
307

308
309
310
		if (set) {
			nftnl_set_set_str(s, NFTNL_SET_NAME, set);
			flags = NLM_F_ACK;
311
312
313
		}
	}

314
315
	nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET,
					h->family, flags, h->seq);
316

317
	if (s) {
318
319
320
321
322
323
324
325
326
327
		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;
	}

328
	if (t) {
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
		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;
}

345
346
347
static int __fetch_chain_cache(struct nft_handle *h,
			       const struct builtin_table *t,
			       const struct nftnl_chain *c)
348
349
350
351
352
353
354
{
	struct nftnl_chain_list_cb_data d = {
		.h = h,
		.t = t,
	};
	char buf[16536];
	struct nlmsghdr *nlh;
355
	int ret;
356

357
358
359
360
	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);
361

362
363
364
	ret = mnl_talk(h, nlh, nftnl_chain_list_cb, &d);
	if (ret < 0 && errno == EINTR)
		assert(nft_restart(h) >= 0);
365

366
367
	return ret;
}
368

369
370
371
372
373
374
375
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;
376

377
378
	if (!chains)
		return __fetch_chain_cache(h, t, NULL);
379

380
	assert(t);
381

382
383
384
	c = nftnl_chain_alloc();
	if (!c)
		return -1;
385

386
387
388
389
390
391
392
393
	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;
	}
394

395
	nftnl_chain_free(c);
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
	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;
}

static int nft_rule_list_update(struct nftnl_chain *c, void *data)
{
	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,
454
			    const struct builtin_table *t)
455
456
457
458
{
	int i;

	if (t) {
459
460
		struct nftnl_chain_list *list =
			h->cache->table[t->type].chains;
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477

		return nftnl_chain_list_foreach(list, nft_rule_list_update, h);
	}

	for (i = 0; i < NFT_TABLE_MAX; i++) {
		enum nft_table_type type = h->tables[i].type;

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

		if (nftnl_chain_list_foreach(h->cache->table[type].chains,
					     nft_rule_list_update, h))
			return -1;
	}
	return 0;
}

478
479
480
static int flush_cache(struct nft_handle *h, struct nft_cache *c,
		       const char *tablename);

481
static void
482
__nft_build_cache(struct nft_handle *h)
483
{
484
485
486
487
	struct nft_cache_req *req = &h->cache_req;
	const struct builtin_table *t = NULL;
	struct list_head *chains = NULL;
	uint32_t genid_check;
488

489
	if (h->cache_init)
490
491
		return;

492
493
494
495
	if (req->table) {
		t = nft_table_builtin_find(h, req->table);
		if (!req->all_chains)
			chains = &req->chain_list;
496
497
	}

498
499
500
	h->cache_init = true;
retry:
	mnl_genid_get(h, &h->nft_genid);
501

502
503
504
	if (req->level >= NFT_CL_TABLES)
		fetch_table_cache(h);
	if (req->level == NFT_CL_FAKE)
505
		goto genid_check;
506
507
508
509
510
511
	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);
512
genid_check:
513
514
515
516
	mnl_genid_get(h, &genid_check);
	if (h->nft_genid != genid_check) {
		flush_cache(h, h->cache, NULL);
		goto retry;
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
	}
}

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;
}

static int __flush_rule_cache(struct nftnl_chain *c, void *data)
{
	return nftnl_rule_foreach(c, ____flush_rule_cache, NULL);
}

int flush_rule_cache(struct nft_handle *h, const char *table,
		     struct nftnl_chain *c)
{
	const struct builtin_table *t;

	if (c)
		return __flush_rule_cache(c, NULL);

	t = nft_table_builtin_find(h, table);
	if (!t || !h->cache->table[t->type].chains)
		return 0;

	return nftnl_chain_list_foreach(h->cache->table[t->type].chains,
					__flush_rule_cache, NULL);
}

static int __flush_chain_cache(struct nftnl_chain *c, void *data)
{
	nftnl_chain_list_del(c);
	nftnl_chain_free(c);

	return 0;
}

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

	return 0;
}

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;
		if (c->table[table->type].chains)
			nftnl_chain_list_foreach(c->table[table->type].chains,
						 __flush_chain_cache, NULL);
		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;

598
599
600
601
602
		if (c->table[i].chains) {
			nftnl_chain_list_free(c->table[i].chains);
			c->table[i].chains = NULL;
		}
		if (c->table[i].sets) {
603
			nftnl_set_list_free(c->table[i].sets);
604
605
			c->table[i].sets = NULL;
		}
606
607

		c->table[i].exists = false;
608
609
610
611
612
613
614
	}

	return 1;
}

void flush_chain_cache(struct nft_handle *h, const char *tablename)
{
615
	if (!h->cache_init)
616
617
618
		return;

	if (flush_cache(h, h->cache, tablename))
619
		h->cache_init = false;
620
621
622
623
}

void nft_rebuild_cache(struct nft_handle *h)
{
624
	if (h->cache_init) {
625
		__nft_flush_cache(h);
626
627
628
629
630
631
632
633
634
635
636
637
638
639
		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);
640

641
642
643
644
645
646
647
648
649
650
651
652
653
	/* 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);
654
655
656
657
}

void nft_release_cache(struct nft_handle *h)
{
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
	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);
	}
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
}

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;
}

struct nftnl_chain_list *
nft_chain_list_get(struct nft_handle *h, const char *table, const char *chain)
{
	const struct builtin_table *t;

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

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