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