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