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