Coverage Report

Created: 2026-08-08 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/shared/firewall-util.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <endian.h>
4
#include <linux/netfilter/nf_tables.h>
5
#include <linux/netfilter_ipv4.h>
6
#include <netinet/ip.h>
7
#include <netinet/ip6.h>
8
#include <stdlib.h>
9
#include <string.h>
10
11
#include "sd-netlink.h"
12
13
#include "alloc-util.h"
14
#include "escape.h"
15
#include "extract-word.h"
16
#include "firewall-util.h"
17
#include "in-addr-util.h"
18
#include "log.h"
19
#include "netlink-internal.h"
20
#include "parse-util.h"
21
#include "socket-util.h"
22
#include "string-table.h"
23
#include "string-util.h"
24
#include "time-util.h"
25
26
0
#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
27
0
#define NFT_SYSTEMD_TABLE_NAME    "io.systemd.nat"
28
0
#define NFT_SYSTEMD_MASQ_SET_NAME "masq_saddr"
29
30
0
#define NFNL_DEFAULT_TIMEOUT_USECS (1ULL * USEC_PER_SEC)
31
32
0
#define UDP_DPORT_OFFSET 2
33
34
/* for test-firewall-util */
35
0
static const char* nft_table_name(void) {
36
0
        static const char *cached = NULL;
37
38
0
        if (!cached)
39
0
                cached = secure_getenv("SYSTEMD_FIREWALL_UTIL_NFT_TABLE_NAME") ?: NFT_SYSTEMD_TABLE_NAME;
40
41
0
        return cached;
42
0
}
43
44
0
static const char* dnat_map_name(void) {
45
0
        static const char *cached = NULL;
46
47
0
        if (!cached)
48
0
                cached = secure_getenv("SYSTEMD_FIREWALL_UTIL_DNAT_MAP_NAME") ?: NFT_SYSTEMD_DNAT_MAP_NAME;
49
50
0
        return cached;
51
0
}
52
53
0
static DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_netlink_message*, sd_netlink_message_unref);
54
55
0
static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) {
56
0
        int r;
57
58
0
        assert(m);
59
0
        assert(name);
60
61
0
        r = sd_netlink_message_open_array(m, NFTA_LIST_ELEM);
62
0
        if (r < 0)
63
0
                return r;
64
65
0
        return sd_netlink_message_open_container_union(m, NFTA_EXPR_DATA, name);
66
0
}
67
68
0
static int nfnl_close_expr_container(sd_netlink_message *m) {
69
0
        int r;
70
71
0
        assert(m);
72
73
0
        r = sd_netlink_message_close_container(m); /* NFTA_EXPR_DATA */
74
0
        if (r < 0)
75
0
                return r;
76
77
0
        return sd_netlink_message_close_container(m); /* NFTA_LIST_ELEM */
78
0
}
79
80
static int nfnl_add_expr_fib(
81
                sd_netlink_message *m,
82
                uint32_t nft_fib_flags,
83
                enum nft_fib_result result,
84
0
                enum nft_registers dreg) {
85
86
0
        int r;
87
88
0
        assert(m);
89
90
0
        r = nfnl_open_expr_container(m, "fib");
91
0
        if (r < 0)
92
0
                return r;
93
94
0
        r = sd_netlink_message_append_u32(m, NFTA_FIB_FLAGS, htobe32(nft_fib_flags));
95
0
        if (r < 0)
96
0
                return r;
97
98
0
        r = sd_netlink_message_append_u32(m, NFTA_FIB_RESULT, htobe32(result));
99
0
        if (r < 0)
100
0
                return r;
101
102
0
        r = sd_netlink_message_append_u32(m, NFTA_FIB_DREG, htobe32(dreg));
103
0
        if (r < 0)
104
0
                return r;
105
106
0
        return nfnl_close_expr_container(m);
107
0
}
108
109
static int nfnl_add_expr_meta(
110
                sd_netlink_message *m,
111
                enum nft_meta_keys key,
112
0
                enum nft_registers dreg) {
113
114
0
        int r;
115
116
0
        assert(m);
117
118
0
        r = nfnl_open_expr_container(m, "meta");
119
0
        if (r < 0)
120
0
                return r;
121
122
0
        r = sd_netlink_message_append_u32(m, NFTA_META_KEY, htobe32(key));
123
0
        if (r < 0)
124
0
                return r;
125
126
0
        r = sd_netlink_message_append_u32(m, NFTA_META_DREG, htobe32(dreg));
127
0
        if (r < 0)
128
0
                return r;
129
130
0
        return nfnl_close_expr_container(m);
131
0
}
132
133
static int nfnl_add_expr_payload(
134
                sd_netlink_message *m,
135
                enum nft_payload_bases pb,
136
                uint32_t offset,
137
                uint32_t len,
138
0
                enum nft_registers dreg) {
139
140
0
        int r;
141
142
0
        assert(m);
143
144
0
        r = nfnl_open_expr_container(m, "payload");
145
0
        if (r < 0)
146
0
                return r;
147
148
0
        r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_DREG, htobe32(dreg));
149
0
        if (r < 0)
150
0
                return r;
151
152
0
        r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_BASE, htobe32(pb));
153
0
        if (r < 0)
154
0
                return r;
155
156
0
        r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_OFFSET, htobe32(offset));
157
0
        if (r < 0)
158
0
                return r;
159
160
0
        r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_LEN, htobe32(len));
161
0
        if (r < 0)
162
0
                return r;
163
164
0
        return nfnl_close_expr_container(m);
165
0
}
166
167
static int nfnl_add_expr_lookup(
168
                sd_netlink_message *m,
169
                const char *set_name,
170
                enum nft_registers sreg,
171
0
                enum nft_registers dreg) {
172
173
0
        int r;
174
175
0
        assert(m);
176
0
        assert(set_name);
177
178
0
        r = nfnl_open_expr_container(m, "lookup");
179
0
        if (r < 0)
180
0
                return r;
181
182
0
        r = sd_netlink_message_append_string(m, NFTA_LOOKUP_SET, set_name);
183
0
        if (r < 0)
184
0
                return r;
185
186
0
        r = sd_netlink_message_append_u32(m, NFTA_LOOKUP_SREG, htobe32(sreg));
187
0
        if (r < 0)
188
0
                return r;
189
190
0
        if (dreg != 0) {
191
0
                r = sd_netlink_message_append_u32(m, NFTA_LOOKUP_DREG, htobe32(dreg));
192
0
                if (r < 0)
193
0
                        return r;
194
0
        }
195
196
0
        return nfnl_close_expr_container(m);
197
0
}
198
199
static int nfnl_add_expr_cmp(
200
                sd_netlink_message *m,
201
                enum nft_cmp_ops cmp_op,
202
                enum nft_registers sreg,
203
                const void *data,
204
0
                size_t dlen) {
205
206
0
        int r;
207
208
0
        assert(m);
209
0
        assert(data);
210
211
0
        r = nfnl_open_expr_container(m, "cmp");
212
0
        if (r < 0)
213
0
                return r;
214
215
0
        r = sd_netlink_message_append_u32(m, NFTA_CMP_OP, htobe32(cmp_op));
216
0
        if (r < 0)
217
0
                return r;
218
219
0
        r = sd_netlink_message_append_u32(m, NFTA_CMP_SREG, htobe32(sreg));
220
0
        if (r < 0)
221
0
                return r;
222
223
0
        r = sd_netlink_message_append_container_data(m, NFTA_CMP_DATA, NFTA_DATA_VALUE, data, dlen);
224
0
        if (r < 0)
225
0
                return r;
226
227
0
        return nfnl_close_expr_container(m);
228
0
}
229
230
static int nfnl_add_expr_bitwise(
231
                sd_netlink_message *m,
232
                enum nft_registers sreg,
233
                enum nft_registers dreg,
234
                const void *and,
235
                const void *xor,
236
0
                uint32_t len) {
237
238
0
        int r;
239
240
0
        assert(m);
241
0
        assert(and);
242
0
        assert(xor);
243
244
0
        r = nfnl_open_expr_container(m, "bitwise");
245
0
        if (r < 0)
246
0
                return r;
247
248
0
        r = sd_netlink_message_append_u32(m, NFTA_BITWISE_SREG, htobe32(sreg));
249
0
        if (r < 0)
250
0
                return r;
251
252
0
        r = sd_netlink_message_append_u32(m, NFTA_BITWISE_DREG, htobe32(dreg));
253
0
        if (r < 0)
254
0
                return r;
255
256
0
        r = sd_netlink_message_append_u32(m, NFTA_BITWISE_LEN, htobe32(len));
257
0
        if (r < 0)
258
0
                return r;
259
260
0
        r = sd_netlink_message_append_container_data(m, NFTA_BITWISE_MASK, NFTA_DATA_VALUE, and, len);
261
0
        if (r < 0)
262
0
                return r;
263
264
0
        r = sd_netlink_message_append_container_data(m, NFTA_BITWISE_XOR, NFTA_DATA_VALUE, xor, len);
265
0
        if (r < 0)
266
0
                return r;
267
268
0
        return nfnl_close_expr_container(m);
269
0
}
270
271
static int nfnl_add_expr_dnat(
272
                sd_netlink_message *m,
273
                int family,
274
                enum nft_registers areg,
275
0
                enum nft_registers preg) {
276
277
0
        int r;
278
279
0
        assert(m);
280
281
0
        r = nfnl_open_expr_container(m, "nat");
282
0
        if (r < 0)
283
0
                return r;
284
285
0
        r = sd_netlink_message_append_u32(m, NFTA_NAT_TYPE, htobe32(NFT_NAT_DNAT));
286
0
        if (r < 0)
287
0
                return r;
288
289
0
        r = sd_netlink_message_append_u32(m, NFTA_NAT_FAMILY, htobe32(family));
290
0
        if (r < 0)
291
0
                return r;
292
293
0
        r = sd_netlink_message_append_u32(m, NFTA_NAT_REG_ADDR_MIN, htobe32(areg));
294
0
        if (r < 0)
295
0
                return r;
296
297
0
        r = sd_netlink_message_append_u32(m, NFTA_NAT_REG_PROTO_MIN, htobe32(preg));
298
0
        if (r < 0)
299
0
                return r;
300
301
0
        return nfnl_close_expr_container(m);
302
0
}
303
304
0
static int nfnl_add_expr_masq(sd_netlink_message *m) {
305
0
        int r;
306
307
0
        r = sd_netlink_message_open_array(m, NFTA_LIST_ELEM);
308
0
        if (r < 0)
309
0
                return r;
310
311
0
        r = sd_netlink_message_append_string(m, NFTA_EXPR_NAME, "masq");
312
0
        if (r < 0)
313
0
                return r;
314
315
0
        return sd_netlink_message_close_container(m); /* NFTA_LIST_ELEM */
316
0
}
317
318
static int sd_nfnl_message_new_masq_rule(
319
                sd_netlink *nfnl,
320
                sd_netlink_message **ret,
321
                int family,
322
0
                const char *chain) {
323
324
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
325
0
        int r;
326
327
        /* -t nat -A POSTROUTING -p protocol -s source/pflen -o out_interface -d destination/pflen -j MASQUERADE */
328
329
0
        assert(nfnl);
330
0
        assert(ret);
331
0
        assert(IN_SET(family, AF_INET, AF_INET6));
332
0
        assert(chain);
333
334
0
        r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain);
335
0
        if (r < 0)
336
0
                return r;
337
338
0
        r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS);
339
0
        if (r < 0)
340
0
                return r;
341
342
        /* 1st statement: ip saddr @masq_saddr. Place iph->saddr in reg1, resp. ipv6 in reg1..reg4. */
343
0
        if (family == AF_INET)
344
0
                r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, saddr),
345
0
                                          sizeof(uint32_t), NFT_REG32_01);
346
0
        else
347
0
                r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct ip6_hdr, ip6_src.s6_addr),
348
0
                                          sizeof(struct in6_addr), NFT_REG32_01);
349
0
        if (r < 0)
350
0
                return r;
351
352
        /* 1st statement: use reg1 content to make lookup in @masq_saddr set. */
353
0
        r = nfnl_add_expr_lookup(m, NFT_SYSTEMD_MASQ_SET_NAME, NFT_REG32_01, 0);
354
0
        if (r < 0)
355
0
                return r;
356
357
        /* 2nd statement: masq.  Only executed by kernel if the previous lookup was successful. */
358
0
        r = nfnl_add_expr_masq(m);
359
0
        if (r < 0)
360
0
                return r;
361
362
0
        r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */
363
0
        if (r < 0)
364
0
                return r;
365
366
0
        *ret = TAKE_PTR(m);
367
0
        return 0;
368
0
}
369
370
static int sd_nfnl_message_new_dnat_rule_pre(
371
                sd_netlink *nfnl,
372
                sd_netlink_message **ret,
373
                int family,
374
0
                const char *chain) {
375
376
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
377
0
        enum nft_registers proto_reg;
378
0
        uint32_t local = RTN_LOCAL;
379
0
        int r;
380
381
        /* -t nat -A PREROUTING -p protocol --dport local_port -i in_interface -s source/pflen
382
         * -d destination/pflen -j DNAT --to-destination remote_addr:remote_port */
383
384
0
        assert(nfnl);
385
0
        assert(ret);
386
0
        assert(IN_SET(family, AF_INET, AF_INET6));
387
0
        assert(chain);
388
389
0
        r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain);
390
0
        if (r < 0)
391
0
                return r;
392
393
0
        r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS);
394
0
        if (r < 0)
395
0
                return r;
396
397
        /* 1st statement: fib daddr type local */
398
0
        r = nfnl_add_expr_fib(m, NFTA_FIB_F_DADDR, NFT_FIB_RESULT_ADDRTYPE, NFT_REG32_01);
399
0
        if (r < 0)
400
0
                return r;
401
402
        /* 1st statement (cont.): compare RTN_LOCAL */
403
0
        r = nfnl_add_expr_cmp(m, NFT_CMP_EQ, NFT_REG32_01, &local, sizeof(local));
404
0
        if (r < 0)
405
0
                return r;
406
407
        /* 2nd statement: lookup local port in map, fetch address:dport to map to */
408
0
        r = nfnl_add_expr_meta(m, NFT_META_L4PROTO, NFT_REG32_01);
409
0
        if (r < 0)
410
0
                return r;
411
412
0
        r = nfnl_add_expr_payload(m, NFT_PAYLOAD_TRANSPORT_HEADER, UDP_DPORT_OFFSET,
413
0
                                  sizeof(uint16_t), NFT_REG32_02);
414
0
        if (r < 0)
415
0
                return r;
416
417
        /* 3rd statement: lookup 'l4proto . dport', e.g. 'tcp . 22' as key and
418
         * store address and port for the dnat mapping in REG1/REG2. */
419
0
        r = nfnl_add_expr_lookup(m, dnat_map_name(), NFT_REG32_01, NFT_REG32_01);
420
0
        if (r < 0)
421
0
                return r;
422
423
0
        proto_reg = family == AF_INET ? NFT_REG32_02 : NFT_REG32_05;
424
0
        r = nfnl_add_expr_dnat(m, family, NFT_REG32_01, proto_reg);
425
0
        if (r < 0)
426
0
                return r;
427
428
0
        r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */
429
0
        if (r < 0)
430
0
                return r;
431
432
0
        *ret = TAKE_PTR(m);
433
0
        return 0;
434
0
}
435
436
static int sd_nfnl_message_new_dnat_rule_out(
437
                sd_netlink *nfnl,
438
                sd_netlink_message **ret,
439
                int family,
440
0
                const char *chain) {
441
442
0
        static const uint32_t zero = 0, one = 1;
443
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
444
0
        enum nft_registers proto_reg;
445
0
        int r;
446
447
0
        assert(nfnl);
448
0
        assert(ret);
449
0
        assert(IN_SET(family, AF_INET, AF_INET6));
450
0
        assert(chain);
451
452
0
        r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain);
453
0
        if (r < 0)
454
0
                return r;
455
456
0
        r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS);
457
0
        if (r < 0)
458
0
                return r;
459
460
        /* 1st statement: exclude 127.0.0.1/8: ip daddr != 127.0.0.1/8, resp. avoid ::1 */
461
0
        if (family == AF_INET) {
462
0
                uint32_t lonet = htobe32(UINT32_C(0x7F000000)), lomask = htobe32(UINT32_C(0xff000000));
463
464
0
                r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, daddr),
465
0
                                          sizeof(lonet), NFT_REG32_01);
466
0
                if (r < 0)
467
0
                        return r;
468
                /* 1st statement (cont.): bitops/prefix */
469
0
                r = nfnl_add_expr_bitwise(m, NFT_REG32_01, NFT_REG32_01, &lomask, &zero, sizeof(lomask));
470
0
                if (r < 0)
471
0
                        return r;
472
473
                /* 1st statement (cont.): compare reg1 with 127/8 */
474
0
                r = nfnl_add_expr_cmp(m, NFT_CMP_NEQ, NFT_REG32_01, &lonet, sizeof(lonet));
475
0
        } else {
476
0
                struct in6_addr loaddr = IN6ADDR_LOOPBACK_INIT;
477
478
0
                r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct ip6_hdr, ip6_dst.s6_addr),
479
0
                                          sizeof(loaddr), NFT_REG32_01);
480
0
                if (r < 0)
481
0
                        return r;
482
483
0
                r = nfnl_add_expr_cmp(m, NFT_CMP_NEQ, NFT_REG32_01, &loaddr, sizeof(loaddr));
484
0
        }
485
0
        if (r < 0)
486
0
                return r;
487
488
        /* 2nd statement: meta oif lo */
489
0
        r = nfnl_add_expr_meta(m, NFT_META_OIF, NFT_REG32_01);
490
0
        if (r < 0)
491
0
                return r;
492
493
        /* 2nd statement (cont.): compare to lo ifindex (1) */
494
0
        r = nfnl_add_expr_cmp(m, NFT_CMP_EQ, NFT_REG32_01, &one, sizeof(one));
495
0
        if (r < 0)
496
0
                return r;
497
498
        /* 3rd statement: meta l4proto . th dport dnat ip . port to map @map_port_ipport */
499
0
        r = nfnl_add_expr_meta(m, NFT_META_L4PROTO, NFT_REG32_01);
500
0
        if (r < 0)
501
0
                return r;
502
503
        /* 3rd statement (cont): store the port number in reg2 */
504
0
        r = nfnl_add_expr_payload(m, NFT_PAYLOAD_TRANSPORT_HEADER, UDP_DPORT_OFFSET,
505
0
                                  sizeof(uint16_t), NFT_REG32_02);
506
0
        if (r < 0)
507
0
                return r;
508
509
        /* 3rd statement (cont): use reg1 and reg2 and retrieve
510
         * the new destination ip and port number.
511
         *
512
         * reg1 and reg2 are clobbered and will then contain the new
513
         * address/port number. */
514
0
        r = nfnl_add_expr_lookup(m, dnat_map_name(), NFT_REG32_01, NFT_REG32_01);
515
0
        if (r < 0)
516
0
                return r;
517
518
        /* 4th statement: dnat connection to address/port retrieved by the
519
         * preceding expression. */
520
0
        proto_reg = family == AF_INET ? NFT_REG32_02 : NFT_REG32_05;
521
0
        r = nfnl_add_expr_dnat(m, family, NFT_REG32_01, proto_reg);
522
0
        if (r < 0)
523
0
                return r;
524
525
0
        r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */
526
0
        if (r < 0)
527
0
                return r;
528
529
0
        *ret = TAKE_PTR(m);
530
0
        return 0;
531
0
}
532
533
static int nft_new_set(
534
                struct sd_netlink *nfnl,
535
                sd_netlink_message **ret,
536
                int family,
537
                const char *set_name,
538
                uint32_t set_id,
539
                uint32_t flags,
540
                uint32_t type,
541
0
                uint32_t klen) {
542
543
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
544
0
        int r;
545
546
0
        assert(nfnl);
547
0
        assert(ret);
548
0
        assert(IN_SET(family, AF_INET, AF_INET6));
549
0
        assert(set_name);
550
551
0
        r = sd_nfnl_nft_message_new_set(nfnl, &m, family, nft_table_name(), set_name, set_id, klen);
552
0
        if (r < 0)
553
0
                return r;
554
555
0
        if (flags != 0) {
556
0
                r = sd_netlink_message_append_u32(m, NFTA_SET_FLAGS, htobe32(flags));
557
0
                if (r < 0)
558
0
                        return r;
559
0
        }
560
561
0
        r = sd_netlink_message_append_u32(m, NFTA_SET_KEY_TYPE, htobe32(type));
562
0
        if (r < 0)
563
0
                return r;
564
565
0
        *ret = TAKE_PTR(m);
566
0
        return r;
567
0
}
568
569
static int nft_new_map(
570
                struct sd_netlink *nfnl,
571
                sd_netlink_message **ret,
572
                int family,
573
                const char *set_name,
574
                uint32_t set_id,
575
                uint32_t flags,
576
                uint32_t type,
577
                uint32_t klen,
578
                uint32_t dtype,
579
0
                uint32_t dlen) {
580
581
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
582
0
        int r;
583
584
0
        assert(nfnl);
585
0
        assert(ret);
586
0
        assert(IN_SET(family, AF_INET, AF_INET6));
587
0
        assert(set_name);
588
589
0
        r = nft_new_set(nfnl, &m, family, set_name, set_id, flags | NFT_SET_MAP, type, klen);
590
0
        if (r < 0)
591
0
                return r;
592
593
0
        r = sd_netlink_message_append_u32(m, NFTA_SET_DATA_TYPE, htobe32(dtype));
594
0
        if (r < 0)
595
0
               return r;
596
597
0
        r = sd_netlink_message_append_u32(m, NFTA_SET_DATA_LEN, htobe32(dlen));
598
0
        if (r < 0)
599
0
                return r;
600
601
0
        *ret = TAKE_PTR(m);
602
0
        return 0;
603
0
}
604
605
static int nft_add_element(
606
                sd_netlink *nfnl,
607
                sd_netlink_message **ret,
608
                int nfproto,
609
                const char *table_name,
610
                const char *set_name,
611
                const void *key,
612
                uint32_t klen,
613
                const void *data,
614
0
                uint32_t dlen) {
615
616
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
617
0
        int r;
618
619
0
        assert(nfnl);
620
0
        assert(ret);
621
0
        assert(nfproto_is_valid(nfproto));
622
0
        assert(table_name);
623
0
        assert(set_name);
624
0
        assert(key);
625
0
        assert(data || dlen == 0);
626
627
        /*
628
         * Ideally there would be an API that provides:
629
         *
630
         * 1) an init function to add the main ruleset skeleton
631
         * 2) a function that populates the sets with all known address/port pairs to s/dnat for
632
         * 3) a function that can remove address/port pairs again.
633
         *
634
         * At this time, the existing API is used which is built on a
635
         * 'add/delete a rule' paradigm.
636
         *
637
         * This replicated here and each element gets added to the set
638
         * one-by-one.
639
         */
640
0
        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add= */ true, nfproto, table_name, set_name);
641
0
        if (r < 0)
642
0
                return r;
643
644
0
        r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
645
0
        if (r < 0)
646
0
                return r;
647
648
0
        r = sd_nfnl_nft_message_append_setelem(m, 0, key, klen, data, dlen, 0);
649
0
        if (r < 0)
650
0
                return r;
651
652
        /* could theoretically append more set elements to add here */
653
654
0
        r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
655
0
        if (r < 0)
656
0
                return r;
657
658
0
        *ret = TAKE_PTR(m);
659
0
        return 0;
660
0
}
661
662
static int nft_del_element(
663
                sd_netlink *nfnl,
664
                sd_netlink_message **ret,
665
                int nfproto,
666
                const char *table_name,
667
                const char *set_name,
668
                const void *key,
669
                uint32_t klen,
670
                const void *data,
671
0
                uint32_t dlen) {
672
673
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
674
0
        int r;
675
676
0
        assert(nfnl);
677
0
        assert(ret);
678
0
        assert(nfproto_is_valid(nfproto));
679
0
        assert(table_name);
680
0
        assert(set_name);
681
0
        assert(key);
682
0
        assert(data || dlen == 0);
683
684
0
        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add= */ false, nfproto, table_name, set_name);
685
0
        if (r < 0)
686
0
               return r;
687
688
0
        r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
689
0
        if (r < 0)
690
0
                return r;
691
692
0
        r = sd_nfnl_nft_message_append_setelem(m, 0, key, klen, data, dlen, 0);
693
0
        if (r < 0)
694
0
               return r;
695
696
0
        r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
697
0
        if (r < 0)
698
0
                return r;
699
700
0
        *ret = TAKE_PTR(m);
701
0
        return 0;
702
0
}
703
704
/* This is needed so 'nft' userspace tool can properly format the contents
705
 * of the set/map when someone uses 'nft' to inspect their content.
706
 *
707
 * The values cannot be changed, they are part of the nft tool type identifier ABI. */
708
0
#define TYPE_BITS 6
709
710
enum nft_key_types {
711
        TYPE_IPADDR        = 7,
712
        TYPE_IP6ADDR       = 8,
713
        TYPE_INET_PROTOCOL = 12,
714
        TYPE_INET_SERVICE  = 13,
715
};
716
717
0
static uint32_t concat_types2(enum nft_key_types a, enum nft_key_types b) {
718
0
        uint32_t type = (uint32_t)a;
719
720
0
        type <<= TYPE_BITS;
721
0
        type |= (uint32_t)b;
722
723
0
        return type;
724
0
}
725
726
0
static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
727
0
        sd_netlink_message *messages[10] = {};
728
0
        CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear);
729
0
        size_t msgcnt = 0, ip_type_size;
730
0
        uint32_t set_id = 0;
731
0
        int ip_type, r;
732
733
0
        assert(nfnl);
734
0
        assert(IN_SET(family, AF_INET, AF_INET6));
735
736
        /* Set F_EXCL so table add fails if the table already exists. */
737
0
        r = sd_nfnl_nft_message_new_table(nfnl, &messages[msgcnt++], family, nft_table_name());
738
0
        if (r < 0)
739
0
                return r;
740
741
0
        r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(),
742
0
                                              "prerouting", "nat",
743
0
                                              NF_INET_PRE_ROUTING, NF_IP_PRI_NAT_DST + 1);
744
0
        if (r < 0)
745
0
                return r;
746
747
0
        r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(),
748
0
                                              "output", "nat",
749
0
                                              NF_INET_LOCAL_OUT, NF_IP_PRI_NAT_DST + 1);
750
0
        if (r < 0)
751
0
                return r;
752
753
0
        r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(),
754
0
                                              "postrouting", "nat",
755
0
                                              NF_INET_POST_ROUTING, NF_IP_PRI_NAT_SRC + 1);
756
0
        if (r < 0)
757
0
                return r;
758
759
0
        if (family == AF_INET) {
760
0
                ip_type_size = sizeof(uint32_t);
761
0
                ip_type = TYPE_IPADDR;
762
0
        } else {
763
0
                assert(family == AF_INET6);
764
0
                ip_type_size = sizeof(struct in6_addr);
765
0
                ip_type = TYPE_IP6ADDR;
766
0
        }
767
        /* set to store ip address ranges we should masquerade for */
768
0
        r = nft_new_set(nfnl, &messages[msgcnt++], family, NFT_SYSTEMD_MASQ_SET_NAME, ++set_id, NFT_SET_INTERVAL, ip_type, ip_type_size);
769
0
        if (r < 0)
770
0
                return r;
771
772
        /*
773
         * map to store ip address:port pair to dnat to.  elements in concatenation
774
         * are rounded up to 4 bytes.
775
         *
776
         * Example: ip protocol . tcp daddr is sizeof(uint32_t) + sizeof(uint32_t), not
777
         * sizeof(uint8_t) + sizeof(uint16_t).
778
         */
779
0
        r = nft_new_map(nfnl, &messages[msgcnt++], family, dnat_map_name(), ++set_id, 0,
780
0
                        concat_types2(TYPE_INET_PROTOCOL, TYPE_INET_SERVICE), sizeof(uint32_t) * 2,
781
0
                        concat_types2(ip_type, TYPE_INET_SERVICE), ip_type_size + sizeof(uint32_t));
782
0
        if (r < 0)
783
0
                return r;
784
785
0
        r = sd_nfnl_message_new_dnat_rule_pre(nfnl, &messages[msgcnt++], family, "prerouting");
786
0
        if (r < 0)
787
0
                return r;
788
789
0
        r = sd_nfnl_message_new_dnat_rule_out(nfnl, &messages[msgcnt++], family, "output");
790
0
        if (r < 0)
791
0
                return r;
792
793
0
        r = sd_nfnl_message_new_masq_rule(nfnl, &messages[msgcnt++], family, "postrouting");
794
0
        if (r < 0)
795
0
                return r;
796
797
0
        assert(msgcnt < ELEMENTSOF(messages));
798
0
        r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS);
799
0
        if (r < 0 && r != -EEXIST)
800
0
                return r;
801
802
0
        return 0;
803
0
}
804
805
static int nft_message_append_setelem_iprange(
806
                sd_netlink_message *m,
807
                const union in_addr_union *source,
808
0
                unsigned prefixlen) {
809
810
0
        uint32_t mask, start, end;
811
0
        unsigned nplen;
812
0
        int r;
813
814
0
        assert(m);
815
0
        assert(source);
816
0
        assert(prefixlen <= 32);
817
818
0
        nplen = 32 - prefixlen;
819
820
0
        mask = (1U << nplen) - 1U;
821
0
        mask = htobe32(~mask);
822
0
        start = source->in.s_addr & mask;
823
824
0
        r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
825
0
        if (r < 0)
826
0
                return r;
827
828
0
        r = sd_nfnl_nft_message_append_setelem(m, 0, &start, sizeof(start), NULL, 0, 0);
829
0
        if (r < 0)
830
0
                return r;
831
832
0
        end = be32toh(start) + (1U << nplen);
833
0
        if (end < be32toh(start))
834
0
                end = 0U;
835
0
        end = htobe32(end);
836
837
0
        r = sd_nfnl_nft_message_append_setelem(m, 1, &end, sizeof(end), NULL, 0, NFT_SET_ELEM_INTERVAL_END);
838
0
        if (r < 0)
839
0
                return r;
840
841
0
        return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
842
0
}
843
844
static int nft_message_append_setelem_ip6range(
845
                sd_netlink_message *m,
846
                const union in_addr_union *source,
847
0
                unsigned prefixlen) {
848
849
0
        union in_addr_union start, end;
850
0
        int r;
851
852
0
        assert(m);
853
0
        assert(source);
854
855
0
        r = in_addr_prefix_range(AF_INET6, source, prefixlen, &start, &end);
856
0
        if (r < 0)
857
0
                return r;
858
859
0
        r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
860
0
        if (r < 0)
861
0
                return r;
862
863
0
        r = sd_nfnl_nft_message_append_setelem(m, 0, &start.in6, sizeof(start.in6), NULL, 0, 0);
864
0
        if (r < 0)
865
0
                return r;
866
867
0
        r = sd_nfnl_nft_message_append_setelem(m, 1, &end.in6, sizeof(end.in6), NULL, 0, NFT_SET_ELEM_INTERVAL_END);
868
0
        if (r < 0)
869
0
                return r;
870
871
0
        return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
872
0
}
873
874
int nft_set_element_modify_iprange(
875
                sd_netlink *nfnl,
876
                bool add,
877
                int nfproto,
878
                int af,
879
                const char *table,
880
                const char *set,
881
                const union in_addr_union *source,
882
0
                unsigned source_prefixlen) {
883
884
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
885
0
        int r;
886
887
0
        assert(nfnl);
888
0
        assert(IN_SET(af, AF_INET, AF_INET6));
889
0
        assert(nfproto_is_valid(nfproto));
890
0
        assert(table);
891
0
        assert(set);
892
893
0
        if (!source || source_prefixlen == 0)
894
0
                return -EINVAL;
895
896
0
        if (af == AF_INET6 && source_prefixlen < 8)
897
0
                return -EINVAL;
898
899
0
        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, add, nfproto, table, set);
900
0
        if (r < 0)
901
0
                return r;
902
903
0
        if (af == AF_INET)
904
0
                 r = nft_message_append_setelem_iprange(m, source, source_prefixlen);
905
0
        else
906
0
                 r = nft_message_append_setelem_ip6range(m, source, source_prefixlen);
907
0
        if (r < 0)
908
0
                return r;
909
910
0
        return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS);
911
0
}
912
913
int nft_set_element_modify_ip(
914
                sd_netlink *nfnl,
915
                bool add,
916
                int nfproto,
917
                int af,
918
                const char *table,
919
                const char *set,
920
0
                const union in_addr_union *source) {
921
922
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
923
0
        int r;
924
925
0
        assert(nfnl);
926
0
        assert(IN_SET(af, AF_INET, AF_INET6));
927
0
        assert(nfproto_is_valid(nfproto));
928
0
        assert(table);
929
0
        assert(set);
930
931
0
        if (!source)
932
0
                return -EINVAL;
933
934
0
        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, add, nfproto, table, set);
935
0
        if (r < 0)
936
0
                return r;
937
938
0
        r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
939
0
        if (r < 0)
940
0
                return r;
941
942
0
        r = sd_nfnl_nft_message_append_setelem(m, 0, source, FAMILY_ADDRESS_SIZE(af), NULL, 0, 0);
943
0
        if (r < 0)
944
0
                return r;
945
946
0
        r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
947
0
        if (r < 0)
948
0
                return r;
949
950
0
        return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS);
951
0
}
952
953
int nft_set_element_modify_any(
954
                sd_netlink *nfnl,
955
                bool add,
956
                int nfproto,
957
                const char *table,
958
                const char *set,
959
                const void *element,
960
0
                size_t element_size) {
961
962
0
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
963
0
        int r;
964
965
0
        assert(nfnl);
966
0
        assert(nfproto_is_valid(nfproto));
967
0
        assert(table);
968
0
        assert(set);
969
0
        assert(element);
970
971
0
        if (add)
972
0
                r = nft_add_element(nfnl, &m, nfproto, table, set, element, element_size, NULL, 0);
973
0
        else
974
0
                r = nft_del_element(nfnl, &m, nfproto, table, set, element, element_size, NULL, 0);
975
0
        if (r < 0)
976
0
                return r;
977
978
0
        return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS);
979
0
}
980
981
0
static int af_to_nfproto(int af) {
982
0
        assert(IN_SET(af, AF_INET, AF_INET6));
983
984
0
        switch (af) {
985
0
        case AF_INET:
986
0
                return NFPROTO_IPV4;
987
0
        case AF_INET6:
988
0
                return NFPROTO_IPV6;
989
0
        default:
990
0
                assert_not_reached();
991
0
        }
992
0
}
993
994
int fw_nftables_add_masquerade(
995
                sd_netlink *nfnl,
996
                bool add,
997
                int af,
998
                const union in_addr_union *source,
999
0
                unsigned source_prefixlen) {
1000
1001
0
        int r;
1002
1003
0
        assert(nfnl);
1004
0
        assert(IN_SET(af, AF_INET, AF_INET6));
1005
1006
0
        if (!socket_ipv6_is_supported() && af == AF_INET6)
1007
0
                return -EOPNOTSUPP;
1008
1009
0
        r = nft_set_element_modify_iprange(nfnl, add, af_to_nfproto(af), af, nft_table_name(), NFT_SYSTEMD_MASQ_SET_NAME,
1010
0
                                           source, source_prefixlen);
1011
0
        if (r != -ENOENT)
1012
0
                return r;
1013
1014
        /* When someone runs 'nft flush ruleset' in the same net namespace this will also tear down the
1015
         * systemd nat table.
1016
         *
1017
         * Unlike iptables -t nat -F (which will remove all rules added by the systemd iptables
1018
         * backend, iptables has builtin chains that cannot be deleted -- the next add operation will
1019
         * 'just work'.
1020
         *
1021
         * In the nftables case, everything gets removed. The next add operation will yield -ENOENT.
1022
         *
1023
         * If we see -ENOENT on add, replay the initial table setup. If that works, re-do the add
1024
         * operation.
1025
         *
1026
         * Note that this doesn't protect against external sabotage such as a
1027
         * 'while true; nft flush ruleset; done'. There is nothing that could be done about that short
1028
         * of extending the kernel to allow tables to be owned by stystemd-networkd and making them
1029
         * non-deletable except by the 'owning process'. */
1030
1031
0
        r = fw_nftables_init_family(nfnl, af);
1032
0
        if (r < 0)
1033
0
                return r;
1034
1035
0
        return nft_set_element_modify_iprange(nfnl, add, af_to_nfproto(af), af, nft_table_name(), NFT_SYSTEMD_MASQ_SET_NAME,
1036
0
                                              source, source_prefixlen);
1037
0
}
1038
1039
static int fw_nftables_add_local_dnat_internal(
1040
                sd_netlink *nfnl,
1041
                bool add,
1042
                int af,
1043
                int protocol,
1044
                uint16_t local_port,
1045
                const union in_addr_union *remote,
1046
                uint16_t remote_port,
1047
0
                const union in_addr_union *previous_remote) {
1048
1049
0
        sd_netlink_message *messages[3] = {};
1050
0
        CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear);
1051
0
        uint32_t data[5], key[2], dlen;
1052
0
        size_t msgcnt = 0;
1053
0
        int r;
1054
1055
0
        assert(nfnl);
1056
0
        assert(add || !previous_remote);
1057
0
        assert(IN_SET(af, AF_INET, AF_INET6));
1058
1059
0
        if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP))
1060
0
                return -EPROTONOSUPPORT;
1061
1062
0
        if (local_port <= 0)
1063
0
                return -EINVAL;
1064
1065
0
        key[0] = protocol;
1066
0
        key[1] = htobe16(local_port);
1067
1068
0
        if (!remote)
1069
0
                return -EOPNOTSUPP;
1070
1071
0
        if (remote_port <= 0)
1072
0
                return -EINVAL;
1073
1074
0
        if (af == AF_INET) {
1075
0
                dlen = 8;
1076
0
                data[1] = htobe16(remote_port);
1077
0
        } else {
1078
0
                assert(af == AF_INET6);
1079
0
                dlen = sizeof(data);
1080
0
                data[4] = htobe16(remote_port);
1081
0
        }
1082
1083
        /* If a previous remote is set, remove its entry */
1084
0
        if (add && previous_remote && !in_addr_equal(af, previous_remote, remote)) {
1085
0
                if (af == AF_INET)
1086
0
                        data[0] = previous_remote->in.s_addr;
1087
0
                else
1088
0
                        memcpy(data, &previous_remote->in6, sizeof(previous_remote->in6));
1089
1090
0
                r = nft_del_element(nfnl, &messages[msgcnt++], af, nft_table_name(), dnat_map_name(),
1091
0
                                    key, sizeof(key), data, dlen);
1092
0
                if (r < 0)
1093
0
                        return r;
1094
0
        }
1095
1096
0
        if (af == AF_INET)
1097
0
                data[0] = remote->in.s_addr;
1098
0
        else
1099
0
                memcpy(data, &remote->in6, sizeof(remote->in6));
1100
1101
0
        if (add)
1102
0
                r = nft_add_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), nft_table_name(), dnat_map_name(),
1103
0
                                    key, sizeof(key), data, dlen);
1104
0
        else
1105
0
                r = nft_del_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), nft_table_name(), dnat_map_name(),
1106
0
                                    key, sizeof(key), data, dlen);
1107
0
        if (r < 0)
1108
0
                return r;
1109
1110
0
        assert(msgcnt < ELEMENTSOF(messages));
1111
0
        r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS);
1112
0
        if (r < 0)
1113
0
                return r;
1114
1115
0
        return 0;
1116
0
}
1117
1118
int fw_nftables_add_local_dnat(
1119
                sd_netlink *nfnl,
1120
                bool add,
1121
                int af,
1122
                int protocol,
1123
                uint16_t local_port,
1124
                const union in_addr_union *remote,
1125
                uint16_t remote_port,
1126
0
                const union in_addr_union *previous_remote) {
1127
1128
0
        int r;
1129
1130
0
        assert(nfnl);
1131
0
        assert(IN_SET(af, AF_INET, AF_INET6));
1132
1133
0
        if (!socket_ipv6_is_supported() && af == AF_INET6)
1134
0
                return -EOPNOTSUPP;
1135
1136
0
        r = fw_nftables_add_local_dnat_internal(nfnl, add, af, protocol, local_port, remote, remote_port, previous_remote);
1137
0
        if (r != -ENOENT)
1138
0
                return r;
1139
1140
        /* See comment in fw_nftables_add_masquerade(). */
1141
0
        r = fw_nftables_init_family(nfnl, af);
1142
0
        if (r < 0)
1143
0
                return r;
1144
1145
        /* table created anew; previous address already gone */
1146
0
        return fw_nftables_add_local_dnat_internal(nfnl, add, af, protocol, local_port, remote, remote_port, NULL);
1147
0
}
1148
1149
static const char *const nfproto_table[] = {
1150
        [NFPROTO_ARP]    = "arp",
1151
        [NFPROTO_BRIDGE] = "bridge",
1152
        [NFPROTO_INET]   = "inet",
1153
        [NFPROTO_IPV4]   = "ip",
1154
        [NFPROTO_IPV6]   = "ip6",
1155
        [NFPROTO_NETDEV] = "netdev",
1156
};
1157
1158
DEFINE_STRING_TABLE_LOOKUP(nfproto, int);
1159
1160
static const char *const nft_set_source_table[] = {
1161
        [NFT_SET_SOURCE_ADDRESS] = "address",
1162
        [NFT_SET_SOURCE_PREFIX]  = "prefix",
1163
        [NFT_SET_SOURCE_IFINDEX] = "ifindex",
1164
        [NFT_SET_SOURCE_CGROUP]  = "cgroup",
1165
        [NFT_SET_SOURCE_USER]    = "user",
1166
        [NFT_SET_SOURCE_GROUP]   = "group",
1167
};
1168
1169
DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int);
1170
1171
390k
void nft_set_context_clear(NFTSetContext *s) {
1172
390k
        assert(s);
1173
1174
390k
        FOREACH_ARRAY(nft_set, s->sets, s->n_sets) {
1175
4.15k
                free(nft_set->table);
1176
4.15k
                free(nft_set->set);
1177
4.15k
        }
1178
1179
390k
        s->n_sets = 0;
1180
390k
        s->sets = mfree(s->sets);
1181
390k
}
1182
1183
4.15k
int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
1184
4.15k
        _cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
1185
1186
4.15k
        assert(s);
1187
4.15k
        assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP));
1188
4.15k
        assert(nfproto_is_valid(nfproto));
1189
4.15k
        assert(table);
1190
4.15k
        assert(set);
1191
1192
4.15k
        table_dup = strdup(table);
1193
4.15k
        if (!table_dup)
1194
0
                return -ENOMEM;
1195
1196
4.15k
        set_dup = strdup(set);
1197
4.15k
        if (!set_dup)
1198
0
                return -ENOMEM;
1199
1200
4.15k
        if (!GREEDY_REALLOC(s->sets, s->n_sets + 1))
1201
0
                return -ENOMEM;
1202
1203
4.15k
        s->sets[s->n_sets++] = (NFTSet) {
1204
4.15k
                .source = source,
1205
4.15k
                .nfproto = nfproto,
1206
4.15k
                .table = TAKE_PTR(table_dup),
1207
4.15k
                .set = TAKE_PTR(set_dup),
1208
4.15k
        };
1209
1210
4.15k
        return 0;
1211
4.15k
}
1212
1213
0
int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst) {
1214
0
        int r;
1215
0
        _cleanup_(nft_set_context_clear) NFTSetContext d = (NFTSetContext) {};
1216
1217
0
        assert(src);
1218
0
        assert(dst);
1219
1220
0
        FOREACH_ARRAY(nft_set, src->sets, src->n_sets) {
1221
0
                r = nft_set_add(&d, nft_set->source, nft_set->nfproto, nft_set->table, nft_set->set);
1222
0
                if (r < 0)
1223
0
                        return r;
1224
0
        }
1225
1226
0
        *dst = TAKE_STRUCT(d);
1227
1228
0
        return 0;
1229
0
}
1230
1231
int config_parse_nft_set(
1232
                const char *unit,
1233
                const char *filename,
1234
                unsigned line,
1235
                const char *section,
1236
                unsigned section_line,
1237
                const char *lvalue,
1238
                int ltype,
1239
                const char *rvalue,
1240
                void *data,
1241
13.4k
                void *userdata) {
1242
1243
13.4k
        NFTSetContext *nft_set_context = ASSERT_PTR(data);
1244
13.4k
        int r;
1245
1246
13.4k
        assert(IN_SET(ltype, NFT_SET_PARSE_NETWORK, NFT_SET_PARSE_CGROUP));
1247
1248
13.4k
        if (isempty(rvalue)) {
1249
1.61k
                nft_set_context_clear(nft_set_context);
1250
1.61k
                return 1;
1251
1.61k
        }
1252
1253
15.9k
        for (const char *p = rvalue;;) {
1254
15.9k
                _cleanup_free_ char *tuple = NULL, *source_str = NULL, *family_str = NULL, *table = NULL, *set = NULL;
1255
15.9k
                const char *q = NULL;
1256
15.9k
                int nfproto;
1257
15.9k
                NFTSetSource source;
1258
1259
15.9k
                r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
1260
15.9k
                if (r < 0)
1261
652
                        return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
1262
15.3k
                if (r == 0)
1263
3.06k
                        return 1;
1264
1265
12.2k
                q = tuple;
1266
12.2k
                r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set);
1267
12.2k
                if (r == -ENOMEM)
1268
0
                        return log_oom();
1269
12.2k
                if (r != 4 || !isempty(q)) {
1270
4.70k
                        _cleanup_free_ char *esc = NULL;
1271
1272
4.70k
                        esc = cescape(tuple);
1273
4.70k
                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse NFT set %s, ignoring", strna(esc));
1274
4.70k
                }
1275
1276
7.54k
                assert(source_str);
1277
7.54k
                assert(family_str);
1278
7.54k
                assert(table);
1279
7.54k
                assert(set);
1280
1281
7.54k
                source = nft_set_source_from_string(source_str);
1282
7.54k
                if (source < 0 ||
1283
6.66k
                    (ltype == NFT_SET_PARSE_NETWORK && !IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX)) ||
1284
6.47k
                    (ltype == NFT_SET_PARSE_CGROUP && !IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))) {
1285
1.61k
                        _cleanup_free_ char *esc = NULL;
1286
1287
1.61k
                        esc = cescape(source_str);
1288
1.61k
                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT source %s, ignoring", strna(esc));
1289
1.61k
                }
1290
1291
5.92k
                nfproto = nfproto_from_string(family_str);
1292
5.92k
                if (nfproto < 0) {
1293
332
                        _cleanup_free_ char *esc = NULL;
1294
1295
332
                        esc = cescape(family_str);
1296
332
                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family %s, ignoring", strna(esc));
1297
332
                }
1298
1299
5.59k
                if (!nft_identifier_valid(table)) {
1300
655
                        _cleanup_free_ char *esc = NULL;
1301
1302
655
                        esc = cescape(table);
1303
655
                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", strna(esc));
1304
655
                }
1305
1306
4.94k
                if (!nft_identifier_valid(set)) {
1307
789
                        _cleanup_free_ char *esc = NULL;
1308
1309
789
                        esc = cescape(set);
1310
789
                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", strna(esc));
1311
789
                }
1312
1313
4.15k
                r = nft_set_add(nft_set_context, source, nfproto, table, set);
1314
4.15k
                if (r < 0)
1315
0
                        return log_oom();
1316
4.15k
        }
1317
1318
0
        assert_not_reached();
1319
0
}