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