Coverage Report

Created: 2026-03-12 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/network/networkd-route-util.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <linux/rtnetlink.h>
4
#include <threads.h>
5
6
#include "alloc-util.h"
7
#include "bitfield.h"
8
#include "extract-word.h"
9
#include "logarithm.h"
10
#include "networkd-address.h"
11
#include "networkd-link.h"
12
#include "networkd-manager.h"
13
#include "networkd-route.h"
14
#include "networkd-route-util.h"
15
#include "parse-util.h"
16
#include "set.h"
17
#include "string-table.h"
18
#include "string-util.h"
19
#include "sysctl-util.h"
20
21
35.7k
#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096
22
23
17.8k
unsigned routes_max(void) {
24
17.8k
        static thread_local unsigned cached = 0;
25
17.8k
        int val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY;
26
27
17.8k
        if (cached > 0)
28
17.8k
                return cached;
29
30
        /* The kernel internally stores these maximum size in int. */
31
32
1
        if (sysctl_read_ip_property_int(AF_INET, /* ifname= */ NULL, "route/max_size", &val4) >= 0)
33
0
                if (val4 == INT_MAX)
34
                        /* This is the default "no limit" value in the kernel */
35
0
                        val4 = ROUTES_DEFAULT_MAX_PER_FAMILY;
36
37
1
        if (sysctl_read_ip_property_int(AF_INET6, /* ifname= */ NULL, "route/max_size", &val6) >= 0)
38
1
                if (val6 == INT_MAX)
39
                        /* This is the default "no limit" value in the kernel */
40
1
                        val6 = ROUTES_DEFAULT_MAX_PER_FAMILY;
41
42
1
        cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) +
43
1
                 MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6);
44
1
        return cached;
45
17.8k
}
46
47
61.1k
bool route_type_is_reject(uint8_t type) {
48
61.1k
        return IN_SET(type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW);
49
61.1k
}
50
51
61.1k
bool route_is_reject(const Route *route) {
52
61.1k
        return route_type_is_reject(ASSERT_PTR(route)->type);
53
61.1k
}
54
55
0
static bool route_lifetime_is_valid(const Route *route) {
56
0
        assert(route);
57
58
0
        return
59
0
                route->lifetime_usec == USEC_INFINITY ||
60
0
                route->lifetime_usec > now(CLOCK_BOOTTIME);
61
0
}
62
63
0
bool link_find_default_gateway(Link *link, int family, Route **gw) {
64
0
        bool found = false;
65
0
        Route *route;
66
67
0
        assert(link);
68
0
        assert(link->manager);
69
70
0
        SET_FOREACH(route, link->manager->routes) {
71
0
                if (route->nexthop.ifindex != link->ifindex)
72
0
                        continue;
73
0
                if (!route_exists(route))
74
0
                        continue;
75
0
                if (family != AF_UNSPEC && route->family != family)
76
0
                        continue;
77
0
                if (route->dst_prefixlen != 0)
78
0
                        continue;
79
0
                if (route->src_prefixlen != 0)
80
0
                        continue;
81
0
                if (route->table != RT_TABLE_MAIN)
82
0
                        continue;
83
0
                if (route->type != RTN_UNICAST)
84
0
                        continue;
85
0
                if (route->scope != RT_SCOPE_UNIVERSE)
86
0
                        continue;
87
0
                if (!in_addr_is_set(route->nexthop.family, &route->nexthop.gw))
88
0
                        continue;
89
90
                /* Found a default gateway. */
91
0
                if (!gw)
92
0
                        return true;
93
94
                /* If we have already found another gw, then let's compare their weight and priority. */
95
0
                if (*gw) {
96
0
                        if (route->nexthop.weight > (*gw)->nexthop.weight)
97
0
                                continue;
98
0
                        if (route->priority >= (*gw)->priority)
99
0
                                continue;
100
0
                }
101
102
0
                *gw = route;
103
0
                found = true;
104
0
        }
105
106
0
        return found;
107
0
}
108
109
0
int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) {
110
0
        Route *gw = NULL;
111
0
        Link *link;
112
113
0
        assert(m);
114
0
        assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6));
115
116
        /* Looks for a suitable "uplink", via black magic: an interface that is up and where the
117
         * default route with the highest priority points to. */
118
119
0
        HASHMAP_FOREACH(link, m->links_by_index) {
120
0
                if (link == exclude)
121
0
                        continue;
122
123
0
                if (link->state != LINK_STATE_CONFIGURED)
124
0
                        continue;
125
126
0
                link_find_default_gateway(link, family, &gw);
127
0
        }
128
129
0
        if (!gw)
130
0
                return -ENOENT;
131
132
0
        return link_get_by_index(m, gw->nexthop.ifindex, ret);
133
0
}
134
135
0
bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw) {
136
0
        Route *route;
137
0
        Address *a;
138
139
0
        assert(link);
140
0
        assert(link->manager);
141
142
0
        if (link->set_flags_messages > 0)
143
0
                return false;
144
145
0
        if (!link_is_up(link))
146
0
                return false;
147
148
0
        if (onlink)
149
0
                return true;
150
151
0
        if (!gw || !in_addr_is_set(family, gw))
152
0
                return true;
153
154
0
        if (family == AF_INET6 && in6_addr_is_link_local(&gw->in6))
155
0
                return true;
156
157
0
        SET_FOREACH(route, link->manager->routes) {
158
0
                if (route->nexthop.ifindex != link->ifindex)
159
0
                        continue;
160
0
                if (!route_exists(route))
161
0
                        continue;
162
0
                if (!route_lifetime_is_valid(route))
163
0
                        continue;
164
0
                if (route->family != family)
165
0
                        continue;
166
0
                if (!in_addr_is_set(route->family, &route->dst) && route->dst_prefixlen == 0)
167
0
                        continue;
168
0
                if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, gw) > 0)
169
0
                        return true;
170
0
        }
171
172
0
        if (link->manager->manage_foreign_routes)
173
0
                return false;
174
175
        /* If we do not manage foreign routes, then there may exist a prefix route we do not know,
176
         * which was created on configuring an address. Hence, also check the addresses. */
177
0
        SET_FOREACH(a, link->addresses) {
178
0
                if (!address_is_ready(a))
179
0
                        continue;
180
0
                if (a->family != family)
181
0
                        continue;
182
0
                if (FLAGS_SET(a->flags, IFA_F_NOPREFIXROUTE))
183
0
                        continue;
184
0
                if (in_addr_prefix_covers(a->family,
185
0
                                          in_addr_is_set(a->family, &a->in_addr_peer) ? &a->in_addr_peer : &a->in_addr,
186
0
                                          a->prefixlen, gw) > 0)
187
0
                        return true;
188
0
        }
189
190
0
        return false;
191
0
}
192
193
static int link_address_is_reachable_internal(
194
                Link *link,
195
                int family,
196
                const union in_addr_union *address,
197
                const union in_addr_union *prefsrc, /* optional */
198
0
                Route **ret) {
199
200
0
        Route *route, *found = NULL;
201
202
0
        assert(link);
203
0
        assert(link->manager);
204
0
        assert(IN_SET(family, AF_INET, AF_INET6));
205
0
        assert(address);
206
207
0
        SET_FOREACH(route, link->manager->routes) {
208
0
                if (route->nexthop.ifindex != link->ifindex)
209
0
                        continue;
210
211
0
                if (!route_exists(route))
212
0
                        continue;
213
214
0
                if (!route_lifetime_is_valid(route))
215
0
                        continue;
216
217
0
                if (route->type != RTN_UNICAST)
218
0
                        continue;
219
220
0
                if (route->family != family)
221
0
                        continue;
222
223
0
                if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, address) <= 0)
224
0
                        continue;
225
226
0
                if (prefsrc &&
227
0
                    in_addr_is_set(family, prefsrc) &&
228
0
                    in_addr_is_set(family, &route->prefsrc) &&
229
0
                    !in_addr_equal(family, prefsrc, &route->prefsrc))
230
0
                        continue;
231
232
0
                if (found && found->priority <= route->priority)
233
0
                        continue;
234
235
0
                found = route;
236
0
        }
237
238
0
        if (!found)
239
0
                return -ENOENT;
240
241
0
        if (ret)
242
0
                *ret = found;
243
244
0
        return 0;
245
0
}
246
247
int link_address_is_reachable(
248
                Link *link,
249
                int family,
250
                const union in_addr_union *address,
251
                const union in_addr_union *prefsrc, /* optional */
252
0
                Address **ret) {
253
254
0
        Route *route;
255
0
        Address *a;
256
0
        int r;
257
258
0
        assert(link);
259
0
        assert(IN_SET(family, AF_INET, AF_INET6));
260
0
        assert(address);
261
262
        /* This checks if the address is reachable, and optionally return the Address object of the
263
         * preferred source to access the address. */
264
265
0
        r = link_address_is_reachable_internal(link, family, address, prefsrc, &route);
266
0
        if (r < 0)
267
0
                return r;
268
269
0
        if (!in_addr_is_set(route->family, &route->prefsrc)) {
270
0
                if (ret)
271
0
                        *ret = NULL;
272
0
                return 0;
273
0
        }
274
275
0
        r = link_get_address(link, route->family, &route->prefsrc, &a);
276
0
        if (r < 0)
277
0
                return r;
278
279
0
        if (!address_is_ready(a))
280
0
                return -EBUSY;
281
282
0
        if (ret)
283
0
                *ret = a;
284
285
0
        return 0;
286
0
}
287
288
int manager_address_is_reachable(
289
                Manager *manager,
290
                int family,
291
                const union in_addr_union *address,
292
                const union in_addr_union *prefsrc, /* optional */
293
0
                Address **ret) {
294
295
0
        Route *route, *found = NULL;
296
0
        Address *a;
297
0
        Link *link;
298
0
        int r;
299
300
0
        assert(manager);
301
302
0
        HASHMAP_FOREACH(link, manager->links_by_index) {
303
0
                if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
304
0
                        continue;
305
306
0
                if (link_address_is_reachable_internal(link, family, address, prefsrc, &route) < 0)
307
0
                        continue;
308
309
0
                if (found && found->priority <= route->priority)
310
0
                        continue;
311
312
0
                found = route;
313
0
        }
314
315
0
        if (!found)
316
0
                return -ENOENT;
317
318
0
        if (!in_addr_is_set(found->family, &found->prefsrc)) {
319
0
                if (ret)
320
0
                        *ret = NULL;
321
0
                return 0;
322
0
        }
323
324
0
        r = link_get_by_index(manager, found->nexthop.ifindex, &link);
325
0
        if (r < 0)
326
0
                return r;
327
328
0
        r = link_get_address(link, found->family, &found->prefsrc, &a);
329
0
        if (r < 0)
330
0
                return r;
331
332
0
        if (!address_is_ready(a))
333
0
                return -EBUSY;
334
335
0
        if (ret)
336
0
                *ret = a;
337
338
0
        return 0;
339
0
}
340
341
static const char * const route_type_table[__RTN_MAX] = {
342
        [RTN_UNICAST]     = "unicast",
343
        [RTN_LOCAL]       = "local",
344
        [RTN_BROADCAST]   = "broadcast",
345
        [RTN_ANYCAST]     = "anycast",
346
        [RTN_MULTICAST]   = "multicast",
347
        [RTN_BLACKHOLE]   = "blackhole",
348
        [RTN_UNREACHABLE] = "unreachable",
349
        [RTN_PROHIBIT]    = "prohibit",
350
        [RTN_THROW]       = "throw",
351
        [RTN_NAT]         = "nat",
352
        [RTN_XRESOLVE]    = "xresolve",
353
};
354
355
assert_cc(__RTN_MAX <= UCHAR_MAX);
356
DEFINE_STRING_TABLE_LOOKUP(route_type, int);
357
358
static const char * const route_scope_table[] = {
359
        [RT_SCOPE_UNIVERSE] = "global",
360
        [RT_SCOPE_SITE]     = "site",
361
        [RT_SCOPE_LINK]     = "link",
362
        [RT_SCOPE_HOST]     = "host",
363
        [RT_SCOPE_NOWHERE]  = "nowhere",
364
};
365
366
DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_scope, int, UINT8_MAX);
367
368
static const char * const route_protocol_table[] = {
369
        [RTPROT_KERNEL] = "kernel",
370
        [RTPROT_BOOT]   = "boot",
371
        [RTPROT_STATIC] = "static",
372
};
373
374
DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol, int, UINT8_MAX);
375
376
static const char * const route_protocol_full_table[] = {
377
        [RTPROT_REDIRECT] = "redirect",
378
        [RTPROT_KERNEL]   = "kernel",
379
        [RTPROT_BOOT]     = "boot",
380
        [RTPROT_STATIC]   = "static",
381
        [RTPROT_GATED]    = "gated",
382
        [RTPROT_RA]       = "ra",
383
        [RTPROT_MRT]      = "mrt",
384
        [RTPROT_ZEBRA]    = "zebra",
385
        [RTPROT_BIRD]     = "bird",
386
        [RTPROT_DNROUTED] = "dnrouted",
387
        [RTPROT_XORP]     = "xorp",
388
        [RTPROT_NTK]      = "ntk",
389
        [RTPROT_DHCP]     = "dhcp",
390
        [RTPROT_MROUTED]  = "mrouted",
391
        [RTPROT_BABEL]    = "babel",
392
        [RTPROT_BGP]      = "bgp",
393
        [RTPROT_ISIS]     = "isis",
394
        [RTPROT_OSPF]     = "ospf",
395
        [RTPROT_RIP]      = "rip",
396
        [RTPROT_EIGRP]    = "eigrp",
397
};
398
399
DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol_full, int, UINT8_MAX);
400
401
0
int route_flags_to_string_alloc(uint32_t flags, char **ret) {
402
0
        _cleanup_free_ char *str = NULL;
403
0
        static const char* map[] = {
404
0
                [LOG2U(RTNH_F_DEAD)]       = "dead",       /* Nexthop is dead (used by multipath) */
405
0
                [LOG2U(RTNH_F_PERVASIVE)]  = "pervasive",  /* Do recursive gateway lookup */
406
0
                [LOG2U(RTNH_F_ONLINK)]     = "onlink" ,    /* Gateway is forced on link */
407
0
                [LOG2U(RTNH_F_OFFLOAD)]    = "offload",    /* Nexthop is offloaded */
408
0
                [LOG2U(RTNH_F_LINKDOWN)]   = "linkdown",   /* carrier-down on nexthop */
409
0
                [LOG2U(RTNH_F_UNRESOLVED)] = "unresolved", /* The entry is unresolved (ipmr) */
410
0
                [LOG2U(RTNH_F_TRAP)]       = "trap",       /* Nexthop is trapping packets */
411
0
        };
412
413
0
        assert(ret);
414
415
0
        for (size_t i = 0; i < ELEMENTSOF(map); i++)
416
0
                if (BIT_SET(flags, i) && map[i])
417
0
                        if (!strextend_with_separator(&str, ",", map[i]))
418
0
                                return -ENOMEM;
419
420
0
        *ret = TAKE_PTR(str);
421
0
        return 0;
422
0
}
423
424
static const char * const route_table_table[] = {
425
        [RT_TABLE_DEFAULT] = "default",
426
        [RT_TABLE_MAIN]    = "main",
427
        [RT_TABLE_LOCAL]   = "local",
428
};
429
430
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int);
431
432
4.47k
int manager_get_route_table_from_string(const Manager *m, const char *s, uint32_t *ret) {
433
4.47k
        uint32_t t;
434
4.47k
        int r;
435
436
4.47k
        assert(m);
437
4.47k
        assert(s);
438
4.47k
        assert(ret);
439
440
4.47k
        r = route_table_from_string(s);
441
4.47k
        if (r >= 0) {
442
407
                *ret = (uint32_t) r;
443
407
                return 0;
444
407
        }
445
446
4.06k
        t = PTR_TO_UINT32(hashmap_get(m->route_table_numbers_by_name, s));
447
4.06k
        if (t != 0) {
448
0
                *ret = t;
449
0
                return 0;
450
0
        }
451
452
4.06k
        r = safe_atou32(s, &t);
453
4.06k
        if (r < 0)
454
1.12k
                return r;
455
456
2.94k
        if (t == 0)
457
597
                return -ERANGE;
458
459
2.35k
        *ret = t;
460
2.35k
        return 0;
461
2.94k
}
462
463
0
int manager_get_route_table_to_string(const Manager *m, uint32_t table, bool append_num, char **ret) {
464
0
        _cleanup_free_ char *str = NULL;
465
0
        const char *s;
466
467
0
        assert(m);
468
0
        assert(ret);
469
470
        /* Unlike manager_get_route_table_from_string(), this accepts 0, as the kernel may create routes with
471
         * table 0. See issue #25089. */
472
473
0
        s = route_table_to_string(table);
474
0
        if (!s)
475
0
                s = hashmap_get(m->route_table_names_by_number, UINT32_TO_PTR(table));
476
477
0
        if (s && !append_num) {
478
0
                str = strdup(s);
479
0
                if (!str)
480
0
                        return -ENOMEM;
481
482
0
        } else if (asprintf(&str, "%s%s%" PRIu32 "%s",
483
0
                            strempty(s),
484
0
                            s ? "(" : "",
485
0
                            table,
486
0
                            s ? ")" : "") < 0)
487
0
                return -ENOMEM;
488
489
0
        *ret = TAKE_PTR(str);
490
0
        return 0;
491
0
}
492
493
int config_parse_route_table_names(
494
                const char *unit,
495
                const char *filename,
496
                unsigned line,
497
                const char *section,
498
                unsigned section_line,
499
                const char *lvalue,
500
                int ltype,
501
                const char *rvalue,
502
                void *data,
503
0
                void *userdata) {
504
505
0
        Manager *m = ASSERT_PTR(userdata);
506
0
        int r;
507
508
0
        assert(filename);
509
0
        assert(lvalue);
510
0
        assert(rvalue);
511
512
0
        if (isempty(rvalue)) {
513
0
                m->route_table_names_by_number = hashmap_free(m->route_table_names_by_number);
514
0
                m->route_table_numbers_by_name = hashmap_free(m->route_table_numbers_by_name);
515
0
                return 0;
516
0
        }
517
518
0
        for (const char *p = rvalue;;) {
519
0
                _cleanup_free_ char *name = NULL;
520
0
                uint32_t table;
521
0
                char *num;
522
523
0
                r = extract_first_word(&p, &name, NULL, 0);
524
0
                if (r == -ENOMEM)
525
0
                        return log_oom();
526
0
                if (r < 0) {
527
0
                        log_syntax(unit, LOG_WARNING, filename, line, r,
528
0
                                   "Invalid RouteTable=, ignoring assignment: %s", rvalue);
529
0
                        return 0;
530
0
                }
531
0
                if (r == 0)
532
0
                        return 0;
533
534
0
                num = strchr(name, ':');
535
0
                if (!num) {
536
0
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
537
0
                                   "Invalid route table name and number pair, ignoring assignment: %s", name);
538
0
                        continue;
539
0
                }
540
541
0
                *num++ = '\0';
542
543
0
                if (isempty(name)) {
544
0
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
545
0
                                   "Route table name cannot be empty. Ignoring assignment: %s:%s", name, num);
546
0
                        continue;
547
0
                }
548
0
                if (in_charset(name, DIGITS)) {
549
0
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
550
0
                                   "Route table name cannot be numeric. Ignoring assignment: %s:%s", name, num);
551
0
                        continue;
552
0
                }
553
0
                if (route_table_from_string(name) >= 0) {
554
0
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
555
0
                                   "Route table name %s is predefined for %i. Ignoring assignment: %s:%s",
556
0
                                   name, route_table_from_string(name), name, num);
557
0
                        continue;
558
0
                }
559
560
0
                r = safe_atou32(num, &table);
561
0
                if (r < 0) {
562
0
                        log_syntax(unit, LOG_WARNING, filename, line, r,
563
0
                                   "Failed to parse route table number '%s', ignoring assignment: %s:%s", num, name, num);
564
0
                        continue;
565
0
                }
566
0
                if (table == 0) {
567
0
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
568
0
                                   "Invalid route table number, ignoring assignment: %s:%s", name, num);
569
0
                        continue;
570
0
                }
571
0
                if (route_table_to_string(table)) {
572
0
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
573
0
                                   "Route table name for %s is predefined (%s). Ignoring assignment: %s:%s",
574
0
                                   num, route_table_to_string(table), name, num);
575
0
                        continue;
576
0
                }
577
578
0
                r = hashmap_ensure_put(&m->route_table_numbers_by_name, &string_hash_ops_free, name, UINT32_TO_PTR(table));
579
0
                if (r == -ENOMEM)
580
0
                        return log_oom();
581
0
                if (r == -EEXIST) {
582
0
                        log_syntax(unit, LOG_WARNING, filename, line, r,
583
0
                                   "Specified route table name and number pair conflicts with others, ignoring assignment: %s:%s", name, num);
584
0
                        continue;
585
0
                }
586
0
                if (r < 0) {
587
0
                        log_syntax(unit, LOG_WARNING, filename, line, r,
588
0
                                   "Failed to store route table name and number pair, ignoring assignment: %s:%s", name, num);
589
0
                        continue;
590
0
                }
591
0
                if (r == 0)
592
                        /* The entry is duplicated. It should not be added to route_table_names_by_number hashmap. */
593
0
                        continue;
594
595
0
                r = hashmap_ensure_put(&m->route_table_names_by_number, NULL, UINT32_TO_PTR(table), name);
596
0
                if (r < 0) {
597
0
                        hashmap_remove(m->route_table_numbers_by_name, name);
598
599
0
                        if (r == -ENOMEM)
600
0
                                return log_oom();
601
0
                        if (r == -EEXIST)
602
0
                                log_syntax(unit, LOG_WARNING, filename, line, r,
603
0
                                           "Specified route table name and number pair conflicts with others, ignoring assignment: %s:%s", name, num);
604
0
                        else
605
0
                                log_syntax(unit, LOG_WARNING, filename, line, r,
606
0
                                           "Failed to store route table name and number pair, ignoring assignment: %s:%s", name, num);
607
0
                        continue;
608
0
                }
609
0
                assert(r > 0);
610
611
0
                TAKE_PTR(name);
612
0
        }
613
0
}