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